From 672aa14f7ceb566a0922e490ebe57530be3d1cc7 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Mon, 13 Sep 2021 23:45:02 +0200 Subject: [PATCH 01/96] Cosmetic: reorganise WasmLayout match statment --- compiler/gen_wasm/src/backend.rs | 62 ++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index acd2ce69b4..ab091ad350 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -41,36 +41,44 @@ pub enum WasmLayout { impl WasmLayout { fn new(layout: &Layout) -> Self { + use roc_mono::layout::Builtin::*; + use UnionLayout::*; use ValueType::*; + let size = layout.stack_size(PTR_SIZE); + match layout { - Layout::Builtin(Builtin::Int128) => Self::StackMemory(size), - Layout::Builtin(Builtin::Int64) => Self::LocalOnly(I64, size), - Layout::Builtin(Builtin::Int32) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int16) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int8) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Int1) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Usize) => Self::LocalOnly(I32, size), - Layout::Builtin(Builtin::Decimal) => Self::StackMemory(size), - Layout::Builtin(Builtin::Float128) => Self::StackMemory(size), - Layout::Builtin(Builtin::Float64) => Self::LocalOnly(F64, size), - Layout::Builtin(Builtin::Float32) => Self::LocalOnly(F32, size), - Layout::Builtin(Builtin::Float16) => Self::LocalOnly(F32, size), - Layout::Builtin(Builtin::Str) => Self::StackMemory(size), - Layout::Builtin(Builtin::Dict(_, _)) => Self::StackMemory(size), - Layout::Builtin(Builtin::Set(_)) => Self::StackMemory(size), - Layout::Builtin(Builtin::List(_)) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyStr) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyList) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptyDict) => Self::StackMemory(size), - Layout::Builtin(Builtin::EmptySet) => Self::StackMemory(size), - Layout::Struct(_) => Self::StackMemory(size), - Layout::Union(UnionLayout::NonRecursive(_)) => Self::StackMemory(size), - Layout::Union(UnionLayout::Recursive(_)) => Self::HeapMemory, - Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => Self::HeapMemory, - Layout::Union(UnionLayout::NullableWrapped { .. }) => Self::HeapMemory, - Layout::Union(UnionLayout::NullableUnwrapped { .. }) => Self::HeapMemory, - Layout::RecursivePointer => Self::HeapMemory, + Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), + + Layout::Builtin(Int64) => Self::LocalOnly(I64, size), + + Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), + + Layout::Builtin(Float64) => Self::LocalOnly(F64, size), + + Layout::Builtin( + Int128 + | Decimal + | Float128 + | Str + | Dict(_, _) + | Set(_) + | List(_) + | EmptyStr + | EmptyList + | EmptyDict + | EmptySet, + ) + | Layout::Struct(_) + | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), + + Layout::Union( + Recursive(_) + | NonNullableUnwrapped(_) + | NullableWrapped { .. } + | NullableUnwrapped { .. }, + ) + | Layout::RecursivePointer => Self::HeapMemory, } } From 258513a57cf298c0cf451d1f1c565938d087f41c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 14 Sep 2021 08:18:33 +0200 Subject: [PATCH 02/96] reset join/jump data --- compiler/gen_wasm/src/backend.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index ab091ad350..862a324feb 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -214,7 +214,8 @@ impl<'a> WasmBackend<'a> { // Functions: internal state & IR mappings self.stack_memory = 0; self.symbol_storage_map.clear(); - // joinpoint_label_map.clear(); + self.joinpoint_label_map.clear(); + assert_eq!(self.block_depth, 0); } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { From 866d9f47a08cda4bed81f0130e20d45706f32694 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 14 Sep 2021 08:31:32 +0200 Subject: [PATCH 03/96] Move WasmLayout to its own module --- compiler/gen_wasm/src/backend.rs | 136 +------------------------------ compiler/gen_wasm/src/layout.rs | 136 +++++++++++++++++++++++++++++++ compiler/gen_wasm/src/lib.rs | 1 + 3 files changed, 139 insertions(+), 134 deletions(-) create mode 100644 compiler/gen_wasm/src/layout.rs diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 862a324feb..e43a4228fe 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -8,9 +8,9 @@ use roc_collections::all::MutMap; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; -use roc_mono::layout::{Builtin, Layout, UnionLayout}; +use roc_mono::layout::{Builtin, Layout}; -use crate::*; +use crate::layout::WasmLayout; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -25,138 +25,6 @@ struct LabelId(u32); #[derive(Debug)] struct SymbolStorage(LocalId, WasmLayout); -// See README for background information on Wasm locals, memory and function calls -#[derive(Debug)] -pub enum WasmLayout { - // Most number types can fit in a Wasm local without any stack memory. - // Roc i8 is represented as an i32 local. Store the type and the original size. - LocalOnly(ValueType, u32), - - // A `local` pointing to stack memory - StackMemory(u32), - - // A `local` pointing to heap memory - HeapMemory, -} - -impl WasmLayout { - fn new(layout: &Layout) -> Self { - use roc_mono::layout::Builtin::*; - use UnionLayout::*; - use ValueType::*; - - let size = layout.stack_size(PTR_SIZE); - - match layout { - Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), - - Layout::Builtin(Int64) => Self::LocalOnly(I64, size), - - Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), - - Layout::Builtin(Float64) => Self::LocalOnly(F64, size), - - Layout::Builtin( - Int128 - | Decimal - | Float128 - | Str - | Dict(_, _) - | Set(_) - | List(_) - | EmptyStr - | EmptyList - | EmptyDict - | EmptySet, - ) - | Layout::Struct(_) - | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), - - Layout::Union( - Recursive(_) - | NonNullableUnwrapped(_) - | NullableWrapped { .. } - | NullableUnwrapped { .. }, - ) - | Layout::RecursivePointer => Self::HeapMemory, - } - } - - fn value_type(&self) -> ValueType { - match self { - Self::LocalOnly(type_, _) => *type_, - _ => PTR_TYPE, - } - } - - fn stack_memory(&self) -> u32 { - match self { - Self::StackMemory(size) => *size, - _ => 0, - } - } - - #[allow(dead_code)] - fn load(&self, offset: u32) -> Result { - use crate::backend::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), - - // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Load(ALIGN_8, offset)) - } else { - Ok(I32Load(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate load instruction for WasmLayout {:?}", - self - )), - } - } - - #[allow(dead_code)] - fn store(&self, offset: u32) -> Result { - use crate::backend::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)), - - // LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Store(ALIGN_8, offset)) - } else { - Ok(I32Store(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate store instruction for WasmLayout {:?}", - self - )), - } - } -} - pub struct WasmBackend<'a> { // Module: Wasm AST pub builder: ModuleBuilder, diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs new file mode 100644 index 0000000000..edf94e6c78 --- /dev/null +++ b/compiler/gen_wasm/src/layout.rs @@ -0,0 +1,136 @@ +use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +use roc_mono::layout::{Layout, UnionLayout}; + +use crate::{ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; + +// See README for background information on Wasm locals, memory and function calls +#[derive(Debug)] +pub enum WasmLayout { + // Most number types can fit in a Wasm local without any stack memory. + // Roc i8 is represented as an i32 local. Store the type and the original size. + LocalOnly(ValueType, u32), + + // A `local` pointing to stack memory + StackMemory(u32), + + // A `local` pointing to heap memory + HeapMemory, +} + +impl WasmLayout { + pub fn new(layout: &Layout) -> Self { + use roc_mono::layout::Builtin::*; + use UnionLayout::*; + use ValueType::*; + + let size = layout.stack_size(PTR_SIZE); + + match layout { + Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), + + Layout::Builtin(Int64) => Self::LocalOnly(I64, size), + + Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), + + Layout::Builtin(Float64) => Self::LocalOnly(F64, size), + + Layout::Builtin( + Int128 + | Decimal + | Float128 + | Str + | Dict(_, _) + | Set(_) + | List(_) + | EmptyStr + | EmptyList + | EmptyDict + | EmptySet, + ) + | Layout::Struct(_) + | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), + + Layout::Union( + Recursive(_) + | NonNullableUnwrapped(_) + | NullableWrapped { .. } + | NullableUnwrapped { .. }, + ) + | Layout::RecursivePointer => Self::HeapMemory, + } + } + + pub fn value_type(&self) -> ValueType { + match self { + Self::LocalOnly(type_, _) => *type_, + _ => PTR_TYPE, + } + } + + pub fn stack_memory(&self) -> u32 { + match self { + Self::StackMemory(size) => *size, + _ => 0, + } + } + + #[allow(dead_code)] + fn load(&self, offset: u32) -> Result { + use crate::layout::WasmLayout::*; + use ValueType::*; + + match self { + LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)), + LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)), + LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)), + LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)), + LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), + LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), + + // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) + // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? + HeapMemory => { + if PTR_TYPE == I64 { + Ok(I64Load(ALIGN_8, offset)) + } else { + Ok(I32Load(ALIGN_4, offset)) + } + } + + _ => Err(format!( + "Failed to generate load instruction for WasmLayout {:?}", + self + )), + } + } + + #[allow(dead_code)] + fn store(&self, offset: u32) -> Result { + use crate::layout::WasmLayout::*; + use ValueType::*; + + match self { + LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)), + LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)), + LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)), + LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)), + LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)), + LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)), + + // LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?) + // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? + HeapMemory => { + if PTR_TYPE == I64 { + Ok(I64Store(ALIGN_8, offset)) + } else { + Ok(I32Store(ALIGN_4, offset)) + } + } + + _ => Err(format!( + "Failed to generate store instruction for WasmLayout {:?}", + self + )), + } + } +} diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 37eb3c1d5b..748044ea20 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,5 +1,6 @@ mod backend; pub mod from_wasm32_memory; +mod layout; use bumpalo::Bump; use parity_wasm::builder; From 3d18d34135896addfd9b8a0b483682cb0deae2aa Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 15 Sep 2021 18:45:20 +0200 Subject: [PATCH 04/96] Insert extra argument when returning on stack --- compiler/gen_wasm/src/backend.rs | 68 ++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index e43a4228fe..2520019d86 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,6 +11,7 @@ use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; +use crate::PTR_TYPE; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -25,6 +26,12 @@ struct LabelId(u32); #[derive(Debug)] struct SymbolStorage(LocalId, WasmLayout); +enum LocalKind { + Parameter, + Variable, +} + +// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43) pub struct WasmBackend<'a> { // Module: Wasm AST pub builder: ModuleBuilder, @@ -36,11 +43,11 @@ pub struct WasmBackend<'a> { // Functions: Wasm AST instructions: std::vec::Vec, - ret_type: ValueType, arg_types: std::vec::Vec, locals: std::vec::Vec, // Functions: internal state & IR mappings + next_local_index: u32, stack_memory: u32, symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) @@ -61,11 +68,11 @@ impl<'a> WasmBackend<'a> { // Functions: Wasm AST instructions: std::vec::Vec::with_capacity(256), - ret_type: ValueType::I32, arg_types: std::vec::Vec::with_capacity(8), locals: std::vec::Vec::with_capacity(32), // Functions: internal state & IR mappings + next_local_index: 0, stack_memory: 0, symbol_storage_map: MutMap::default(), block_depth: 0, @@ -80,6 +87,7 @@ impl<'a> WasmBackend<'a> { self.locals.clear(); // Functions: internal state & IR mappings + self.next_local_index = 0; self.stack_memory = 0; self.symbol_storage_map.clear(); self.joinpoint_label_map.clear(); @@ -89,38 +97,30 @@ impl<'a> WasmBackend<'a> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { let ret_layout = WasmLayout::new(&proc.ret_layout); - if let WasmLayout::StackMemory { .. } = ret_layout { - return Err(format!( - "Not yet implemented: Returning values to callee stack memory {:?} {:?}", - proc.name, sym - )); - } - - self.ret_type = ret_layout.value_type(); - self.arg_types.reserve(proc.args.len()); + let sig_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + self.arg_types.push(PTR_TYPE); + self.next_local_index += 1; + builder::signature() + } else { + builder::signature().with_result(ret_layout.value_type()) + }; for (layout, symbol) in proc.args { - let wasm_layout = WasmLayout::new(layout); - self.arg_types.push(wasm_layout.value_type()); - self.insert_local(wasm_layout, *symbol); + self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); } + let signature = sig_builder.with_params(self.arg_types.clone()).build_sig(); + self.build_stmt(&proc.body, &proc.ret_layout)?; - let signature = builder::signature() - .with_params(self.arg_types.clone()) // requires std::Vec, not Bumpalo - .with_result(self.ret_type) - .build_sig(); - // functions must end with an End instruction/opcode - let mut instructions = self.instructions.clone(); - instructions.push(Instruction::End); + self.instructions.push(Instruction::End); let function_def = builder::function() .with_signature(signature) .body() .with_locals(self.locals.clone()) - .with_instructions(Instructions::new(instructions)) + .with_instructions(Instructions::new(self.instructions.clone())) .build() // body .build(); // function @@ -132,15 +132,24 @@ impl<'a> WasmBackend<'a> { Ok(function_index) } - fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId { + fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol, kind: LocalKind) -> LocalId { self.stack_memory += layout.stack_memory(); - let index = self.symbol_storage_map.len(); - if index >= self.arg_types.len() { - self.locals.push(Local::new(1, layout.value_type())); + + match kind { + LocalKind::Parameter => { + self.arg_types.push(layout.value_type()); + } + LocalKind::Variable => { + self.locals.push(Local::new(1, layout.value_type())); + } } - let local_id = LocalId(index as u32); + + let local_id = LocalId(self.next_local_index); + self.next_local_index += 1; + let storage = SymbolStorage(local_id, layout); self.symbol_storage_map.insert(symbol, storage); + local_id } @@ -193,7 +202,7 @@ impl<'a> WasmBackend<'a> { Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let local_id = self.insert_local(wasm_layout, *sym); + let local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable); self.build_expr(sym, expr, layout)?; self.instructions.push(SetLocal(local_id.0)); @@ -275,7 +284,8 @@ impl<'a> WasmBackend<'a> { let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let local_id = self.insert_local(wasm_layout, parameter.symbol); + let local_id = + self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); jp_parameter_local_ids.push(local_id); } From 036503c750a4c7da732d5055c31120722d40fde3 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 17 Sep 2021 19:42:29 +0100 Subject: [PATCH 05/96] copy returned structs to caller stack --- compiler/gen_wasm/src/backend.rs | 39 ++++++++++++-------- compiler/gen_wasm/src/layout.rs | 61 +++++++++++++++++++++----------- compiler/gen_wasm/src/lib.rs | 49 ++++++++++++++++++++++++- 3 files changed, 114 insertions(+), 35 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 2520019d86..d07adc266e 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -10,16 +10,13 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::layout::WasmLayout; -use crate::PTR_TYPE; +use crate::layout::{WasmLayout}; +use crate::{PTR_TYPE, copy_memory, LocalId}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) const UNUSED_DATA_SECTION_BYTES: u32 = 1024; -#[derive(Clone, Copy, Debug)] -struct LocalId(u32); - #[derive(Clone, Copy, Debug)] struct LabelId(u32); @@ -212,16 +209,30 @@ impl<'a> WasmBackend<'a> { } Stmt::Ret(sym) => { - if let Some(SymbolStorage(local_id, _)) = self.symbol_storage_map.get(sym) { - self.instructions.push(GetLocal(local_id.0)); - self.instructions.push(Return); - Ok(()) - } else { - Err(format!( - "Not yet implemented: returning values with layout {:?}", - ret_layout - )) + use crate::layout::WasmLayout::*; + + let SymbolStorage(local_id, wasm_layout) = + self.symbol_storage_map.get(sym).unwrap(); + + match wasm_layout { + LocalOnly(_, _) | HeapMemory => { + self.instructions.push(GetLocal(local_id.0)); + self.instructions.push(Return); + } + + StackMemory { + size, + alignment_bytes, + } => { + let from = local_id.clone(); + let to = LocalId(0); + let copy_size: u32 = *size; + let copy_alignment_bytes: u32 = *alignment_bytes; + copy_memory(&mut self.instructions, from, to, copy_size, copy_alignment_bytes)?; + } } + + Ok(()) } Stmt::Switch { diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 46acf9fad8..d45a502055 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,7 +1,7 @@ -use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +use parity_wasm::elements::ValueType; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; +use crate::{PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug)] @@ -11,7 +11,7 @@ pub enum WasmLayout { LocalOnly(ValueType, u32), // A `local` pointing to stack memory - StackMemory(u32), + StackMemory { size: u32, alignment_bytes: u32 }, // A `local` pointing to heap memory HeapMemory, @@ -24,6 +24,7 @@ impl WasmLayout { use ValueType::*; let size = layout.stack_size(PTR_SIZE); + let alignment_bytes = layout.alignment_bytes(PTR_SIZE); match layout { Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size), @@ -49,7 +50,10 @@ impl WasmLayout { ) | Layout::Struct(_) | Layout::LambdaSet(_) - | Layout::Union(NonRecursive(_)) => Self::StackMemory(size), + | Layout::Union(NonRecursive(_)) => Self::StackMemory { + size, + alignment_bytes, + }, Layout::Union( Recursive(_) @@ -70,11 +74,12 @@ impl WasmLayout { pub fn stack_memory(&self) -> u32 { match self { - Self::StackMemory(size) => *size, + Self::StackMemory { size, .. } => *size, _ => 0, } } + /* #[allow(dead_code)] fn load(&self, offset: u32) -> Result { use crate::layout::WasmLayout::*; @@ -104,34 +109,50 @@ impl WasmLayout { )), } } + */ + /* + TODO: this is probably in the wrong place, need specific locals for the StackMemory case. + Come back to it later. #[allow(dead_code)] - fn store(&self, offset: u32) -> Result { + pub fn store(&self, offset: u32, instructions: &mut Vec) -> Result<(), String> { use crate::layout::WasmLayout::*; use ValueType::*; + let mut result = Ok(()); match self { - LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)), + LocalOnly(I32, 4) => instructions.push(I32Store(ALIGN_4, offset)), + LocalOnly(I32, 2) => instructions.push(I32Store16(ALIGN_2, offset)), + LocalOnly(I32, 1) => instructions.push(I32Store8(ALIGN_1, offset)), + LocalOnly(I64, 8) => instructions.push(I64Store(ALIGN_8, offset)), + LocalOnly(F64, 8) => instructions.push(F64Store(ALIGN_8, offset)), + LocalOnly(F32, 4) => instructions.push(F32Store(ALIGN_4, offset)), + + StackMemory { + size, + alignment_bytes, + } => { + let from_ptr = LocalId(0); + let to_ptr = LocalId(0); + copy_memory(instructions, from_ptr, to_ptr: size, alignment_bytes) + } - // LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? HeapMemory => { if PTR_TYPE == I64 { - Ok(I64Store(ALIGN_8, offset)) + instructions.push(I64Store(ALIGN_8, offset)); } else { - Ok(I32Store(ALIGN_4, offset)) + instructions.push(I32Store(ALIGN_4, offset)); } } - _ => Err(format!( - "Failed to generate store instruction for WasmLayout {:?}", - self - )), + _ => { + result = Err(format!( + "Failed to generate store instruction for WasmLayout {:?}", + self + )); + } } + result } + */ } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 748044ea20..e3bfcd6fcf 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -4,7 +4,7 @@ mod layout; use bumpalo::Bump; use parity_wasm::builder; -use parity_wasm::elements::{Instruction, Internal, ValueType}; +use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType}; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::{Interns, Symbol}; @@ -24,6 +24,9 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; +#[derive(Clone, Copy, Debug)] +struct LocalId(u32); + pub struct Env<'a> { pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot pub interns: Interns, @@ -105,3 +108,47 @@ pub fn build_module_help<'a>( Ok((backend.builder, main_function_index)) } + +fn encode_alignment(bytes: u32) -> Result { + match bytes { + 1 => Ok(ALIGN_1), + 2 => Ok(ALIGN_2), + 4 => Ok(ALIGN_4), + 8 => Ok(ALIGN_8), + _ => Err(format!("{:?}-byte alignment is not supported", bytes)), + } +} + +fn copy_memory( + instructions: &mut Vec, + from_ptr: LocalId, + to_ptr: LocalId, + size_with_alignment: u32, + alignment_bytes: u32, +) -> Result<(), String> { + let alignment_flag = encode_alignment(alignment_bytes)?; + let size = size_with_alignment - alignment_bytes; + let mut offset = 0; + while size - offset >= 8 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I64Load(alignment_flag, offset)); + instructions.push(I64Store(alignment_flag, offset)); + offset += 8; + } + if size - offset >= 4 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I32Load(alignment_flag, offset)); + instructions.push(I32Store(alignment_flag, offset)); + offset += 4; + } + while size - offset > 0 { + instructions.push(GetLocal(to_ptr.0)); + instructions.push(GetLocal(from_ptr.0)); + instructions.push(I32Load8U(alignment_flag, offset)); + instructions.push(I32Store8(alignment_flag, offset)); + offset += 1; + } + Ok(()) +} From 4f55b7a56e4ab6805515afa9323bdc7c8fb74b2c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 17 Sep 2021 21:17:05 +0100 Subject: [PATCH 06/96] Allocate and free stack frames --- compiler/gen_wasm/src/backend.rs | 25 +++++++++++++------ compiler/gen_wasm/src/lib.rs | 22 +++++++++++++++- .../tests/helpers/wasm32_test_result.rs | 17 ++++--------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index d07adc266e..06a7ac50aa 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -10,8 +10,8 @@ use roc_module::symbol::Symbol; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; -use crate::layout::{WasmLayout}; -use crate::{PTR_TYPE, copy_memory, LocalId}; +use crate::layout::WasmLayout; +use crate::{allocate_stack_frame, copy_memory, free_stack_frame, LocalId, PTR_TYPE}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -110,14 +110,17 @@ impl<'a> WasmBackend<'a> { self.build_stmt(&proc.body, &proc.ret_layout)?; - // functions must end with an End instruction/opcode - self.instructions.push(Instruction::End); + let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); + allocate_stack_frame(&mut final_instructions, self.stack_memory as i32); + final_instructions.extend(self.instructions.clone()); + free_stack_frame(&mut final_instructions, self.stack_memory as i32); + final_instructions.push(Instruction::End); let function_def = builder::function() .with_signature(signature) .body() .with_locals(self.locals.clone()) - .with_instructions(Instructions::new(self.instructions.clone())) + .with_instructions(Instructions::new(final_instructions)) .build() // body .build(); // function @@ -130,13 +133,13 @@ impl<'a> WasmBackend<'a> { } fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol, kind: LocalKind) -> LocalId { - self.stack_memory += layout.stack_memory(); - match kind { LocalKind::Parameter => { + // Don't increment stack_memory! Structs are allocated in caller's stack memory and passed as pointers. self.arg_types.push(layout.value_type()); } LocalKind::Variable => { + self.stack_memory += layout.stack_memory(); self.locals.push(Local::new(1, layout.value_type())); } } @@ -228,7 +231,13 @@ impl<'a> WasmBackend<'a> { let to = LocalId(0); let copy_size: u32 = *size; let copy_alignment_bytes: u32 = *alignment_bytes; - copy_memory(&mut self.instructions, from, to, copy_size, copy_alignment_bytes)?; + copy_memory( + &mut self.instructions, + from, + to, + copy_size, + copy_alignment_bytes, + )?; } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index e3bfcd6fcf..95dbc267d0 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -25,7 +25,7 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; #[derive(Clone, Copy, Debug)] -struct LocalId(u32); +pub struct LocalId(u32); pub struct Env<'a> { pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot @@ -152,3 +152,23 @@ fn copy_memory( } Ok(()) } + +pub fn allocate_stack_frame(instructions: &mut Vec, size: i32) { + if size == 0 { + return; + } + instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); + instructions.push(I32Const(size)); + instructions.push(I32Sub); + instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); +} + +pub fn free_stack_frame(instructions: &mut Vec, size: i32) { + if size == 0 { + return; + } + instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); + instructions.push(I32Const(size)); + instructions.push(I32Add); + instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); +} diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 184edf43f7..a2d0463ba0 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -35,20 +35,12 @@ pub trait Wasm32TestResult { fn build_wrapper_body(main_function_index: u32) -> Vec; } -fn build_wrapper_body_prelude(stack_memory_size: usize) -> Vec { - vec![ - GetGlobal(STACK_POINTER_GLOBAL_ID), - I32Const(stack_memory_size as i32), - I32Sub, - SetGlobal(STACK_POINTER_GLOBAL_ID), - ] -} - macro_rules! build_wrapper_body_primitive { ($store_instruction: expr, $align: expr) => { fn build_wrapper_body(main_function_index: u32) -> Vec { - const MAX_ALIGNED_SIZE: usize = 16; - let mut instructions = build_wrapper_body_prelude(MAX_ALIGNED_SIZE); + const MAX_ALIGNED_SIZE: i32 = 16; + let mut instructions = Vec::with_capacity(9); + allocate_stack_frame(&mut instructions, MAX_ALIGNED_SIZE); instructions.extend([ GetGlobal(STACK_POINTER_GLOBAL_ID), // @@ -76,7 +68,8 @@ macro_rules! wasm_test_result_primitive { } fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { - let mut instructions = build_wrapper_body_prelude(size); + let mut instructions = Vec::with_capacity(8); + allocate_stack_frame(&mut instructions, size as i32); instructions.extend([ // // Call the main function with the allocated address to write the result. From 52a56bfa271b02893dd088b9b998984464a2fc47 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Fri, 17 Sep 2021 21:49:21 +0100 Subject: [PATCH 07/96] Optimise away a memory copy for returned structs in simple cases --- compiler/gen_wasm/src/backend.rs | 10 ++++++++-- compiler/gen_wasm/src/layout.rs | 8 ++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 06a7ac50aa..3652709546 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -192,9 +192,15 @@ impl<'a> WasmBackend<'a> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { match stmt { - // This pattern is a simple optimisation to get rid of one local and two instructions per proc. - // If we are just returning the expression result, then don't SetLocal and immediately GetLocal + // Simple optimisation: if we are just returning the expression, we don't need a local Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { + let wasm_layout = WasmLayout::new(layout); + if let WasmLayout::StackMemory { .. } = wasm_layout { + // Map this symbol to the first argument (pointer into caller's stack) + // Saves us from having to copy it later + let storage = SymbolStorage(LocalId(0), wasm_layout); + self.symbol_storage_map.insert(*let_sym, storage); + } self.build_expr(let_sym, expr, layout)?; self.instructions.push(Return); Ok(()) diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index d45a502055..43580a9aec 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -6,14 +6,14 @@ use crate::{PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug)] pub enum WasmLayout { - // Most number types can fit in a Wasm local without any stack memory. - // Roc i8 is represented as an i32 local. Store the type and the original size. + // Primitive number value. Just a Wasm local, without any stack memory. + // For example, Roc i8 is represented as Wasm i32. Store the type and the original size. LocalOnly(ValueType, u32), - // A `local` pointing to stack memory + // Local pointer to stack memory StackMemory { size: u32, alignment_bytes: u32 }, - // A `local` pointing to heap memory + // Local pointer to heap memory HeapMemory, } From c3b5ac6c8210738816041f8e7cc034ed119f6d1b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 13:39:38 +0100 Subject: [PATCH 08/96] Allocate stack memory to local variables --- compiler/gen_wasm/src/backend.rs | 123 +++++++++++++----- compiler/gen_wasm/src/layout.rs | 23 ++-- compiler/gen_wasm/src/lib.rs | 34 ++++- .../tests/helpers/wasm32_test_result.rs | 45 +++++-- 4 files changed, 167 insertions(+), 58 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 3652709546..00e2dcbe68 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,5 +1,5 @@ use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, ModuleBuilder}; +use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder}; use parity_wasm::elements::{ BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, }; @@ -44,8 +44,8 @@ pub struct WasmBackend<'a> { locals: std::vec::Vec, // Functions: internal state & IR mappings - next_local_index: u32, - stack_memory: u32, + stack_memory: i32, + stack_frame_pointer: Option, symbol_storage_map: MutMap, /// how many blocks deep are we (used for jumps) block_depth: u32, @@ -69,8 +69,8 @@ impl<'a> WasmBackend<'a> { locals: std::vec::Vec::with_capacity(32), // Functions: internal state & IR mappings - next_local_index: 0, stack_memory: 0, + stack_frame_pointer: None, symbol_storage_map: MutMap::default(), block_depth: 0, joinpoint_label_map: MutMap::default(), @@ -84,8 +84,8 @@ impl<'a> WasmBackend<'a> { self.locals.clear(); // Functions: internal state & IR mappings - self.next_local_index = 0; self.stack_memory = 0; + self.stack_frame_pointer = None; self.symbol_storage_map.clear(); self.joinpoint_label_map.clear(); assert_eq!(self.block_depth, 0); @@ -94,28 +94,57 @@ impl<'a> WasmBackend<'a> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { let ret_layout = WasmLayout::new(&proc.ret_layout); - let sig_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + let ret_type = if let WasmLayout::StackMemory { .. } = ret_layout { self.arg_types.push(PTR_TYPE); - self.next_local_index += 1; - builder::signature() + None } else { - builder::signature().with_result(ret_layout.value_type()) + Some(ret_layout.value_type()) }; for (layout, symbol) in proc.args { self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); } - let signature = sig_builder.with_params(self.arg_types.clone()).build_sig(); - self.build_stmt(&proc.body, &proc.ret_layout)?; + let function_def = self.finalize(ret_type); + let location = self.builder.push_function(function_def); + let function_index = location.body; + self.proc_symbol_map.insert(sym, location); + self.reset(); + + Ok(function_index) + } + + fn finalize(&mut self, return_type: Option) -> FunctionDefinition { let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); - allocate_stack_frame(&mut final_instructions, self.stack_memory as i32); - final_instructions.extend(self.instructions.clone()); - free_stack_frame(&mut final_instructions, self.stack_memory as i32); + + allocate_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer, + ); + + final_instructions.extend(self.instructions.drain(0..)); + + free_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer, + ); + final_instructions.push(Instruction::End); + let signature_builder = if let Some(t) = return_type { + builder::signature().with_result(t) + } else { + builder::signature() + }; + + let signature = signature_builder + .with_params(self.arg_types.clone()) + .build_sig(); + let function_def = builder::function() .with_signature(signature) .body() @@ -124,35 +153,69 @@ impl<'a> WasmBackend<'a> { .build() // body .build(); // function - let location = self.builder.push_function(function_def); - let function_index = location.body; - self.proc_symbol_map.insert(sym, location); - self.reset(); - - Ok(function_index) + function_def } - fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol, kind: LocalKind) -> LocalId { + fn insert_local( + &mut self, + wasm_layout: WasmLayout, + symbol: Symbol, + kind: LocalKind, + ) -> LocalId { + let local_index = (self.arg_types.len() + self.locals.len()) as u32; + let local_id = LocalId(local_index); + match kind { LocalKind::Parameter => { - // Don't increment stack_memory! Structs are allocated in caller's stack memory and passed as pointers. - self.arg_types.push(layout.value_type()); + // Already stack-allocated by the caller if needed. + self.arg_types.push(wasm_layout.value_type()); } LocalKind::Variable => { - self.stack_memory += layout.stack_memory(); - self.locals.push(Local::new(1, layout.value_type())); + self.locals.push(Local::new(1, wasm_layout.value_type())); + + if let WasmLayout::StackMemory { + size, + alignment_bytes, + } = wasm_layout + { + let align = alignment_bytes as i32; + let mut offset = self.stack_memory; + offset += align - 1; + offset &= -align; + self.stack_memory = offset + (size - alignment_bytes) as i32; + + let frame_pointer = self.get_or_create_frame_pointer(); + + // initialise the local with the appropriate address + self.instructions.extend([ + GetLocal(frame_pointer.0), + I32Const(offset), + I32Add, + SetLocal(local_index), + ]); + } } } - let local_id = LocalId(self.next_local_index); - self.next_local_index += 1; - - let storage = SymbolStorage(local_id, layout); + let storage = SymbolStorage(local_id, wasm_layout); self.symbol_storage_map.insert(symbol, storage); local_id } + fn get_or_create_frame_pointer(&mut self) -> LocalId { + match self.stack_frame_pointer { + Some(local_id) => local_id, + None => { + let local_index = (self.arg_types.len() + self.locals.len()) as u32; + let local_id = LocalId(local_index); + self.stack_frame_pointer = Some(local_id); + self.locals.push(Local::new(1, ValueType::I32)); + local_id + } + } + } + fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { self.symbol_storage_map.get(sym).ok_or_else(|| { format!( @@ -199,7 +262,7 @@ impl<'a> WasmBackend<'a> { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later let storage = SymbolStorage(LocalId(0), wasm_layout); - self.symbol_storage_map.insert(*let_sym, storage); + self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; self.instructions.push(Return); diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 43580a9aec..576316a049 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,7 +1,7 @@ -use parity_wasm::elements::ValueType; +use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{PTR_SIZE, PTR_TYPE}; +use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls #[derive(Debug)] @@ -72,6 +72,7 @@ impl WasmLayout { } } + #[allow(dead_code)] pub fn stack_memory(&self) -> u32 { match self { Self::StackMemory { size, .. } => *size, @@ -79,7 +80,6 @@ impl WasmLayout { } } - /* #[allow(dead_code)] fn load(&self, offset: u32) -> Result { use crate::layout::WasmLayout::*; @@ -93,8 +93,10 @@ impl WasmLayout { LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), + // TODO: Come back to this when we need to access fields of structs // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? + HeapMemory => { if PTR_TYPE == I64 { Ok(I64Load(ALIGN_8, offset)) @@ -109,10 +111,6 @@ impl WasmLayout { )), } } - */ - /* - TODO: this is probably in the wrong place, need specific locals for the StackMemory case. - Come back to it later. #[allow(dead_code)] pub fn store(&self, offset: u32, instructions: &mut Vec) -> Result<(), String> { @@ -132,9 +130,13 @@ impl WasmLayout { size, alignment_bytes, } => { - let from_ptr = LocalId(0); - let to_ptr = LocalId(0); - copy_memory(instructions, from_ptr, to_ptr: size, alignment_bytes) + // TODO + // Need extra arguments for this case that we don't need for primitives. + // Maybe it should be somewhere we have more relevant context? + // Come back to it when we need to insert things into structs. + let from_ptr = LocalId(0); // TODO + let to_ptr = LocalId(0); // TODO + copy_memory(instructions, from_ptr, to_ptr, *size, *alignment_bytes)?; } HeapMemory => { @@ -154,5 +156,4 @@ impl WasmLayout { } result } - */ } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 95dbc267d0..8a612572bf 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -25,7 +25,7 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; #[derive(Clone, Copy, Debug)] -pub struct LocalId(u32); +pub struct LocalId(pub u32); pub struct Env<'a> { pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot @@ -153,22 +153,42 @@ fn copy_memory( Ok(()) } -pub fn allocate_stack_frame(instructions: &mut Vec, size: i32) { +pub fn allocate_stack_frame( + instructions: &mut Vec, + size: i32, + local_frame_pointer: Option, +) { if size == 0 { return; } instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); instructions.push(I32Const(size)); instructions.push(I32Sub); + if let Some(LocalId(local_index)) = local_frame_pointer { + instructions.push(TeeLocal(local_index)); + } instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); } -pub fn free_stack_frame(instructions: &mut Vec, size: i32) { +pub fn free_stack_frame( + instructions: &mut Vec, + size: i32, + local_frame_pointer: Option, +) { if size == 0 { return; } - instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); - instructions.push(I32Const(size)); - instructions.push(I32Add); - instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); + let get_stack_frame = + if let Some(LocalId(local_index)) = local_frame_pointer { + GetLocal(local_index) + } else { + GetGlobal(STACK_POINTER_GLOBAL_ID) + }; + + instructions.extend([ + get_stack_frame, + I32Const(size), + I32Add, + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]); } diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index a2d0463ba0..dd069ec0bb 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -1,11 +1,15 @@ use parity_wasm::builder; use parity_wasm::builder::ModuleBuilder; -use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Internal, ValueType}; +use parity_wasm::elements::{ + Instruction, Instruction::*, Instructions, Internal, Local, ValueType, +}; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_gen_wasm::*; use roc_std::{RocDec, RocList, RocOrder, RocStr}; +const STACK_POINTER_LOCAL_ID: u32 = 0; + pub trait Wasm32TestResult { fn insert_test_wrapper( module_builder: &mut ModuleBuilder, @@ -16,9 +20,11 @@ pub trait Wasm32TestResult { let signature = builder::signature().with_result(ValueType::I32).build_sig(); + let stack_frame_pointer = Local::new(1, ValueType::I32); let function_def = builder::function() .with_signature(signature) .body() + .with_locals(vec![stack_frame_pointer]) .with_instructions(Instructions::new(instructions)) .build() // body .build(); // function @@ -39,10 +45,15 @@ macro_rules! build_wrapper_body_primitive { ($store_instruction: expr, $align: expr) => { fn build_wrapper_body(main_function_index: u32) -> Vec { const MAX_ALIGNED_SIZE: i32 = 16; - let mut instructions = Vec::with_capacity(9); - allocate_stack_frame(&mut instructions, MAX_ALIGNED_SIZE); + let mut instructions = Vec::with_capacity(16); + allocate_stack_frame( + &mut instructions, + MAX_ALIGNED_SIZE, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); instructions.extend([ - GetGlobal(STACK_POINTER_GLOBAL_ID), + // load result address to prepare for the store instruction later + GetLocal(STACK_POINTER_LOCAL_ID), // // Call the main function with no arguments. Get primitive back. Call(main_function_index), @@ -51,9 +62,14 @@ macro_rules! build_wrapper_body_primitive { $store_instruction($align, 0), // // Return the result pointer - GetGlobal(STACK_POINTER_GLOBAL_ID), - End, + GetLocal(STACK_POINTER_LOCAL_ID), ]); + free_stack_frame( + &mut instructions, + MAX_ALIGNED_SIZE, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); + instructions.push(End); instructions } }; @@ -68,19 +84,28 @@ macro_rules! wasm_test_result_primitive { } fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { - let mut instructions = Vec::with_capacity(8); - allocate_stack_frame(&mut instructions, size as i32); + let mut instructions = Vec::with_capacity(16); + allocate_stack_frame( + &mut instructions, + size as i32, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); instructions.extend([ // // Call the main function with the allocated address to write the result. // No value is returned to the VM stack. This is the same as in compiled C. - GetGlobal(STACK_POINTER_GLOBAL_ID), + GetLocal(STACK_POINTER_LOCAL_ID), Call(main_function_index), // // Return the result address GetGlobal(STACK_POINTER_GLOBAL_ID), - End, ]); + free_stack_frame( + &mut instructions, + size as i32, + Some(LocalId(STACK_POINTER_LOCAL_ID)), + ); + instructions.push(End); instructions } From 65446ce1e865564ec8e8de09f92fb5e98ef62c08 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 14:46:09 +0100 Subject: [PATCH 09/96] Remove unnecessary Option --- compiler/gen_wasm/src/backend.rs | 25 ++++++------ compiler/gen_wasm/src/lib.rs | 38 +++++++------------ .../tests/helpers/wasm32_test_result.rs | 16 ++++---- 3 files changed, 36 insertions(+), 43 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 00e2dcbe68..924c86b992 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -119,20 +119,23 @@ impl<'a> WasmBackend<'a> { fn finalize(&mut self, return_type: Option) -> FunctionDefinition { let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); - allocate_stack_frame( - &mut final_instructions, - self.stack_memory, - self.stack_frame_pointer, - ); + if self.stack_memory > 0 { + allocate_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer.unwrap(), + ); + } final_instructions.extend(self.instructions.drain(0..)); - free_stack_frame( - &mut final_instructions, - self.stack_memory, - self.stack_frame_pointer, - ); - + if self.stack_memory > 0 { + free_stack_frame( + &mut final_instructions, + self.stack_memory, + self.stack_frame_pointer.unwrap(), + ); + } final_instructions.push(Instruction::End); let signature_builder = if let Some(t) = return_type { diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 8a612572bf..d439fca3cc 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -23,6 +23,7 @@ pub const ALIGN_4: u32 = 2; pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; +pub const STACK_ALIGNMENT_BYTES: i32 = 16; #[derive(Clone, Copy, Debug)] pub struct LocalId(pub u32); @@ -156,38 +157,27 @@ fn copy_memory( pub fn allocate_stack_frame( instructions: &mut Vec, size: i32, - local_frame_pointer: Option, + local_frame_pointer: LocalId, ) { - if size == 0 { - return; - } - instructions.push(GetGlobal(STACK_POINTER_GLOBAL_ID)); - instructions.push(I32Const(size)); - instructions.push(I32Sub); - if let Some(LocalId(local_index)) = local_frame_pointer { - instructions.push(TeeLocal(local_index)); - } - instructions.push(SetGlobal(STACK_POINTER_GLOBAL_ID)); + let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); + instructions.extend([ + GetGlobal(STACK_POINTER_GLOBAL_ID), + I32Const(aligned_size), + I32Sub, + TeeLocal(local_frame_pointer.0), + SetGlobal(STACK_POINTER_GLOBAL_ID), + ]); } pub fn free_stack_frame( instructions: &mut Vec, size: i32, - local_frame_pointer: Option, + local_frame_pointer: LocalId, ) { - if size == 0 { - return; - } - let get_stack_frame = - if let Some(LocalId(local_index)) = local_frame_pointer { - GetLocal(local_index) - } else { - GetGlobal(STACK_POINTER_GLOBAL_ID) - }; - + let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); instructions.extend([ - get_stack_frame, - I32Const(size), + GetLocal(local_frame_pointer.0), + I32Const(aligned_size), I32Add, SetGlobal(STACK_POINTER_GLOBAL_ID), ]); diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index dd069ec0bb..a84bf48e89 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -44,12 +44,12 @@ pub trait Wasm32TestResult { macro_rules! build_wrapper_body_primitive { ($store_instruction: expr, $align: expr) => { fn build_wrapper_body(main_function_index: u32) -> Vec { - const MAX_ALIGNED_SIZE: i32 = 16; + let size: i32 = 8; let mut instructions = Vec::with_capacity(16); allocate_stack_frame( &mut instructions, - MAX_ALIGNED_SIZE, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + size, + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.extend([ // load result address to prepare for the store instruction later @@ -66,8 +66,8 @@ macro_rules! build_wrapper_body_primitive { ]); free_stack_frame( &mut instructions, - MAX_ALIGNED_SIZE, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + size, + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.push(End); instructions @@ -88,7 +88,7 @@ fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec allocate_stack_frame( &mut instructions, size as i32, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.extend([ // @@ -98,12 +98,12 @@ fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec Call(main_function_index), // // Return the result address - GetGlobal(STACK_POINTER_GLOBAL_ID), + GetLocal(STACK_POINTER_LOCAL_ID), ]); free_stack_frame( &mut instructions, size as i32, - Some(LocalId(STACK_POINTER_LOCAL_ID)), + LocalId(STACK_POINTER_LOCAL_ID), ); instructions.push(End); instructions From af34541b673c0dcfd3d378b0ef3bf5879efcc851 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Tue, 21 Sep 2021 11:13:56 +0200 Subject: [PATCH 10/96] upgrade to wgpu-10, fix save on s without Ctrl --- Cargo.lock | 379 ++++++---------------- editor/Cargo.toml | 4 +- editor/src/editor/ed_error.rs | 8 - editor/src/editor/main.rs | 34 +- editor/src/editor/mvc/ed_update.rs | 48 +-- editor/src/graphics/lowlevel/buffer.rs | 6 +- editor/src/graphics/lowlevel/ortho.rs | 8 +- editor/src/graphics/lowlevel/pipelines.rs | 9 +- editor/src/graphics/lowlevel/vertex.rs | 2 +- 9 files changed, 156 insertions(+), 342 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a03a50a6f..580353bc1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,10 +131,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] -name = "ash" -version = "0.32.1" +name = "arrayvec" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112" +checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" + +[[package]] +name = "ash" +version = "0.33.3+1.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" dependencies = [ "libloading 0.7.0", ] @@ -332,9 +338,6 @@ name = "cc" version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" -dependencies = [ - "jobserver", -] [[package]] name = "cfg-if" @@ -1074,15 +1077,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" -dependencies = [ - "serde", -] - [[package]] name = "dtoa" version = "0.4.8" @@ -1154,16 +1148,6 @@ dependencies = [ "serde", ] -[[package]] -name = "external-memory" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4dfe8d292b014422776a8c516862d2bff8a81b223a4461dfdc45f3862dc9d39" -dependencies = [ - "bitflags", - "drm-fourcc", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -1182,6 +1166,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "fixedbitset" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" + [[package]] name = "flate2" version = "1.0.21" @@ -1397,168 +1387,6 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] -[[package]] -name = "gfx-auxil" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1694991b11d642680e82075a75c7c2bd75556b805efa7660b705689f05b1ab1c" -dependencies = [ - "fxhash", - "gfx-hal", - "spirv_cross", -] - -[[package]] -name = "gfx-backend-dx11" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9e453baf3aaef2b0c354ce0b3d63d76402e406a59b64b7182d123cfa6635ae" -dependencies = [ - "arrayvec", - "bitflags", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", - "libloading 0.7.0", - "log", - "parking_lot", - "range-alloc", - "raw-window-handle", - "smallvec", - "spirv_cross", - "thunderdome", - "winapi 0.3.9", - "wio", -] - -[[package]] -name = "gfx-backend-dx12" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21506399f64a3c4d389182a89a30073856ae33eb712315456b4fd8f39ee7682a" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags", - "d3d12", - "gfx-auxil", - "gfx-hal", - "gfx-renderdoc", - "log", - "parking_lot", - "range-alloc", - "raw-window-handle", - "smallvec", - "spirv_cross", - "thunderdome", - "winapi 0.3.9", -] - -[[package]] -name = "gfx-backend-empty" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c8f813c47791918aa00dc9c9ddf961d23fa8c2a5d869e6cb8ea84f944820f4" -dependencies = [ - "gfx-hal", - "log", - "raw-window-handle", -] - -[[package]] -name = "gfx-backend-gl" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bae057fc3a0ab23ecf97ae51d4017d27d5ddf0aab16ee6dcb58981af88c3152" -dependencies = [ - "arrayvec", - "bitflags", - "fxhash", - "gfx-hal", - "glow", - "js-sys", - "khronos-egl", - "libloading 0.7.0", - "log", - "naga", - "parking_lot", - "raw-window-handle", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gfx-backend-metal" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de85808e2a98994c6af925253f8a9593bc57180ef1ea137deab6d35cc949517" -dependencies = [ - "arrayvec", - "bitflags", - "block", - "cocoa-foundation", - "copyless", - "core-graphics-types", - "foreign-types", - "fxhash", - "gfx-hal", - "log", - "metal", - "naga", - "objc", - "parking_lot", - "profiling", - "range-alloc", - "raw-window-handle", - "storage-map", -] - -[[package]] -name = "gfx-backend-vulkan" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9861ec855acbbc65c0e4f966d761224886e811dc2c6d413a4776e9293d0e5c0" -dependencies = [ - "arrayvec", - "ash", - "byteorder", - "core-graphics-types", - "gfx-hal", - "gfx-renderdoc", - "inplace_it", - "log", - "naga", - "objc", - "parking_lot", - "raw-window-handle", - "smallvec", - "winapi 0.3.9", -] - -[[package]] -name = "gfx-hal" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbb575ea793dd0507b3082f4f2cde62dc9f3cebd98f5cd49ba2a4da97a976fd" -dependencies = [ - "bitflags", - "external-memory", - "naga", - "raw-window-handle", - "thiserror", -] - -[[package]] -name = "gfx-renderdoc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8027995e247e2426d3a00d13f5191dd56c314bff02dc4b54cbf727f1ba9c40a" -dependencies = [ - "libloading 0.7.0", - "log", - "renderdoc-sys", -] - [[package]] name = "ghost" version = "0.1.2" @@ -1589,9 +1417,9 @@ checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" [[package]] name = "glow" -version = "0.9.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108" +checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520" dependencies = [ "js-sys", "slotmap", @@ -1640,9 +1468,9 @@ dependencies = [ [[package]] name = "gpu-alloc" -version = "0.4.7" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc1b6ca374e81862526786d9cb42357ce03706ed1b8761730caafd02ab91f3a" +checksum = "ab8524eac5fc9d05625c891adf78fcf64dc0ee9f8d0882874b9f220f42b442bf" dependencies = [ "bitflags", "gpu-alloc-types", @@ -1659,9 +1487,9 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" +checksum = "d7a237f0419ab10d17006d55c62ac4f689a6bf52c75d3f38b8361d249e8d4b0b" dependencies = [ "bitflags", "gpu-descriptor-types", @@ -1954,15 +1782,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.54" @@ -2289,9 +2108,9 @@ dependencies = [ [[package]] name = "naga" -version = "0.5.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef670817eef03d356d5a509ea275e7dd3a78ea9e24261ea3cb2dfed1abb08f64" +checksum = "8c5859e55c51da10b98e7a73068e0a0c5da7bbcae4fc38f86043d0c6d1b917cf" dependencies = [ "bit-set", "bitflags", @@ -2299,9 +2118,8 @@ dependencies = [ "fxhash", "log", "num-traits", - "petgraph", - "rose_tree", - "spirv_headers", + "petgraph 0.6.0", + "spirv", "thiserror", ] @@ -2648,7 +2466,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "petgraph", + "petgraph 0.5.1", "redox_syscall", "smallvec", "thread-id", @@ -2710,7 +2528,17 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ - "fixedbitset", + "fixedbitset 0.2.0", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset 0.4.0", "indexmap", ] @@ -3946,15 +3774,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "rose_tree" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2" -dependencies = [ - "petgraph", -] - [[package]] name = "rustc-demangle" version = "0.1.21" @@ -4231,9 +4050,12 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" [[package]] name = "slotmap" -version = "0.4.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf34684c5767b87de9119790e92e9a1d60056be2ceeaf16a8e6ef13082aeab1" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] [[package]] name = "smallvec" @@ -4310,21 +4132,10 @@ dependencies = [ ] [[package]] -name = "spirv_cross" -version = "0.23.1" +name = "spirv" +version = "0.2.0+1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" -dependencies = [ - "cc", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "spirv_headers" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c" +checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830" dependencies = [ "bitflags", "num-traits", @@ -4342,15 +4153,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "storage-map" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c" -dependencies = [ - "lock_api", -] - [[package]] name = "strip-ansi-escapes" version = "0.1.1" @@ -4569,12 +4371,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "thunderdome" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b4947742c93ece24a0032141d9caa3d853752e694a57e35029dd2bd08673e0" - [[package]] name = "time" version = "0.1.43" @@ -4794,7 +4590,7 @@ dependencies = [ name = "ven_pretty" version = "0.9.1-alpha.0" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "criterion", "difference", "tempfile", @@ -4820,7 +4616,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" dependencies = [ - "arrayvec", + "arrayvec 0.5.2", "utf8parse", "vte_generate_state_changes", ] @@ -5237,9 +5033,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.50" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" +checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" dependencies = [ "js-sys", "wasm-bindgen", @@ -5247,14 +5043,13 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd247f8b26fd3d42ef2f320d378025cd6e84d782ef749fab45cc3b981fbe3275" +checksum = "3d92a4fe73b1e7d7ef99938dacd49258cbf1ad87cdb5bf6efa20c27447442b45" dependencies = [ - "arrayvec", + "arrayvec 0.7.1", "js-sys", "log", - "naga", "parking_lot", "raw-window-handle", "smallvec", @@ -5262,29 +5057,21 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "wgpu-core", + "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-core" -version = "0.9.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958a8a5e418492723ab4e7933bf6dbdf06f5dc87274ba2ae0e4f9c891aac579c" +checksum = "5f1b4d918c970526cbc83b72ccb72dbefd38aec45f07b2310de4ffcd7f4bd8c5" dependencies = [ - "arrayvec", + "arrayvec 0.7.1", "bitflags", "cfg_aliases", "copyless", "fxhash", - "gfx-backend-dx11", - "gfx-backend-dx12", - "gfx-backend-empty", - "gfx-backend-gl", - "gfx-backend-metal", - "gfx-backend-vulkan", - "gfx-hal", - "gpu-alloc", - "gpu-descriptor", "log", "naga", "parking_lot", @@ -5292,23 +5079,58 @@ dependencies = [ "raw-window-handle", "smallvec", "thiserror", + "wgpu-hal", "wgpu-types", ] [[package]] -name = "wgpu-types" -version = "0.9.0" +name = "wgpu-hal" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5c9678cd533558e28b416d66947b099742df1939307478db54f867137f1b60" +checksum = "27cd894b17bff1958ee93da1cc991fd64bf99667746d4bd2a7403855f4d37fe2" +dependencies = [ + "arrayvec 0.7.1", + "ash", + "bit-set", + "bitflags", + "block", + "core-graphics-types", + "d3d12", + "foreign-types", + "fxhash", + "glow", + "gpu-alloc", + "gpu-descriptor", + "inplace_it", + "khronos-egl", + "libloading 0.7.0", + "log", + "metal", + "naga", + "objc", + "parking_lot", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "thiserror", + "wgpu-types", + "winapi 0.3.9", +] + +[[package]] +name = "wgpu-types" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25feb2fbf24ab3219a9f10890ceb8e1ef02b13314ed89d64a9ae99dcad883e18" dependencies = [ "bitflags", ] [[package]] name = "wgpu_glyph" -version = "0.13.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fee8c96eda18195a7ad9989737183e0a357f14b15e98838c76abbcf56a5f970" +checksum = "cbf11aebbcf20806535bee127367bcb393c83d77c60c4f7917184d839716cf41" dependencies = [ "bytemuck", "glyph_brush", @@ -5401,15 +5223,6 @@ dependencies = [ "x11-dl", ] -[[package]] -name = "wio" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/editor/Cargo.toml b/editor/Cargo.toml index f9207b2e38..603498ce87 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -29,13 +29,13 @@ arraystring = "0.3.0" libc = "0.2" page_size = "0.4" winit = "0.24" -wgpu = "0.9" +wgpu = "0.10" glyph_brush = "0.7" log = "0.4" zerocopy = "0.3" env_logger = "0.8" futures = "0.3" -wgpu_glyph = "0.13" +wgpu_glyph = "0.14" cgmath = "0.18.0" snafu = { version = "0.6", features = ["backtraces"] } colored = "2" diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index b838fdef42..f0c280eeab 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,5 +1,4 @@ use crate::lang::parse::ASTNodeId; -use crate::ui::ui_error::UIResult; use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; use colored::*; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; @@ -292,10 +291,3 @@ impl From for EdError { dummy_res.context(UIErrorBacktrace { msg }).unwrap_err() } } - -pub fn from_ui_res(ui_res: UIResult) -> EdResult { - match ui_res { - Ok(t) => Ok(t), - Err(ui_err) => Err(EdError::from(ui_err)), - } -} diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 9ab70d32ba..ba31393054 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -70,7 +70,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box { size = new_size; - swap_chain = gpu_device.create_swap_chain( - &surface, - &wgpu::SwapChainDescriptor { - usage: wgpu::TextureUsage::RENDER_ATTACHMENT, + surface.configure( + &gpu_device, + &wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, format: render_format, width: size.width, height: size.height, @@ -274,11 +274,15 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box EdModel<'a> { virtual_keycode: VirtualKeyCode, ) -> EdResult<()> { match virtual_keycode { - Left => from_ui_res(self.move_caret_left(modifiers)), + Left => self.move_caret_left(modifiers)?, Up => { if modifiers.cmd_or_ctrl() && modifiers.shift { - self.select_expr() + self.select_expr()? } else { - from_ui_res(self.move_caret_up(modifiers)) + self.move_caret_up(modifiers)? } } - Right => from_ui_res(self.move_caret_right(modifiers)), - Down => from_ui_res(self.move_caret_down(modifiers)), + Right => self.move_caret_right(modifiers)?, + Down => self.move_caret_down(modifiers)?, + + A => { + if modifiers.cmd_or_ctrl() { + self.select_all()? + } + } + S => { + if modifiers.cmd_or_ctrl() { + self.save_file()? + } + } + R => { + if modifiers.cmd_or_ctrl() { + self.run_file()? + } + } + + Home => self.move_caret_home(modifiers)?, + End => self.move_caret_end(modifiers)?, - A => if_modifiers(modifiers, self.select_all()), - S => if_modifiers(modifiers, self.save_file()), - R => if_modifiers(modifiers, self.run_file()), - Home => from_ui_res(self.move_caret_home(modifiers)), - End => from_ui_res(self.move_caret_end(modifiers)), F11 => { self.show_debug_view = !self.show_debug_view; self.dirty = true; - Ok(()) } - _ => Ok(()), + _ => (), } + + Ok(()) } // Replaces selected expression with blank. @@ -787,14 +801,6 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> }) } -fn if_modifiers(modifiers: &Modifiers, shortcut_result: UIResult<()>) -> EdResult<()> { - if modifiers.cmd_or_ctrl() { - from_ui_res(shortcut_result) - } else { - Ok(()) - } -} - // current(=caret is here) MarkupNode corresponds to a Def2 in the AST pub fn handle_new_char_def( received_char: &char, diff --git a/editor/src/graphics/lowlevel/buffer.rs b/editor/src/graphics/lowlevel/buffer.rs index f9cf9fc9a4..723e5957ef 100644 --- a/editor/src/graphics/lowlevel/buffer.rs +++ b/editor/src/graphics/lowlevel/buffer.rs @@ -105,7 +105,7 @@ pub fn create_rect_buffers( let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { label: None, size: Vertex::SIZE * 4 * nr_of_rects, - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -114,7 +114,7 @@ pub fn create_rect_buffers( let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { label: None, size: u32_size * 6 * nr_of_rects, - usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false, }); @@ -148,7 +148,7 @@ impl StagingBuffer { StagingBuffer { buffer: device.create_buffer_init(&BufferInitDescriptor { contents: bytemuck::cast_slice(data), - usage: wgpu::BufferUsage::COPY_SRC, + usage: wgpu::BufferUsages::COPY_SRC, label: Some("Staging Buffer"), }), size: size_of_slice(data) as wgpu::BufferAddress, diff --git a/editor/src/graphics/lowlevel/ortho.rs b/editor/src/graphics/lowlevel/ortho.rs index 0b5871bb93..2f4577871a 100644 --- a/editor/src/graphics/lowlevel/ortho.rs +++ b/editor/src/graphics/lowlevel/ortho.rs @@ -2,7 +2,7 @@ use cgmath::{Matrix4, Ortho}; use wgpu::util::DeviceExt; use wgpu::{ BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, - ShaderStage, + ShaderStages, }; // orthographic projection is used to transform pixel coords to the coordinate system used by wgpu @@ -45,7 +45,7 @@ pub fn update_ortho_buffer( let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Ortho uniform buffer"), contents: bytemuck::cast_slice(&[new_uniforms]), - usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_SRC, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC, }); // get a command encoder for the current frame @@ -83,14 +83,14 @@ pub fn init_ortho( let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { label: Some("Ortho uniform buffer"), contents: bytemuck::cast_slice(&[uniforms]), - usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }); // bind groups consist of extra resources that are provided to the shaders let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { binding: 0, - visibility: ShaderStage::VERTEX, + visibility: ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, diff --git a/editor/src/graphics/lowlevel/pipelines.rs b/editor/src/graphics/lowlevel/pipelines.rs index 810766ccfd..5bc768a33f 100644 --- a/editor/src/graphics/lowlevel/pipelines.rs +++ b/editor/src/graphics/lowlevel/pipelines.rs @@ -9,9 +9,9 @@ pub struct RectResources { pub fn make_rect_pipeline( gpu_device: &wgpu::Device, - swap_chain_descr: &wgpu::SwapChainDescriptor, + surface_config: &wgpu::SurfaceConfiguration, ) -> RectResources { - let ortho = init_ortho(swap_chain_descr.width, swap_chain_descr.height, gpu_device); + let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device); let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { bind_group_layouts: &[&ortho.bind_group_layout], @@ -21,11 +21,10 @@ pub fn make_rect_pipeline( let pipeline = create_render_pipeline( gpu_device, &pipeline_layout, - swap_chain_descr.format, + surface_config.format, &wgpu::ShaderModuleDescriptor { label: None, source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))), - flags: wgpu::ShaderFlags::all(), }, ); @@ -61,7 +60,7 @@ pub fn create_render_pipeline( }, alpha: wgpu::BlendComponent::REPLACE, }), - write_mask: wgpu::ColorWrite::ALL, + write_mask: wgpu::ColorWrites::ALL, }], }), primitive: wgpu::PrimitiveState::default(), diff --git a/editor/src/graphics/lowlevel/vertex.rs b/editor/src/graphics/lowlevel/vertex.rs index 64cf9fac1f..f17a840bb2 100644 --- a/editor/src/graphics/lowlevel/vertex.rs +++ b/editor/src/graphics/lowlevel/vertex.rs @@ -18,7 +18,7 @@ impl Vertex { pub const SIZE: wgpu::BufferAddress = std::mem::size_of::() as wgpu::BufferAddress; pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { array_stride: Self::SIZE, - step_mode: wgpu::InputStepMode::Vertex, + step_mode: wgpu::VertexStepMode::Vertex, attributes: &[ // position wgpu::VertexAttribute { From 59831f2e8505906c47dedcac2a6700e455d996a9 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Wed, 22 Sep 2021 21:02:43 +0200 Subject: [PATCH 11/96] more refactoring for seperate ast crate --- Cargo.lock | 19 + Cargo.toml | 1 + ast/Cargo.toml | 24 + ast/src/ast_error.rs | 30 + ast/src/canonicalize/canonicalize.rs | 289 ++++ ast/src/canonicalize/mod.rs | 2 + ast/src/canonicalize/module.rs | 319 ++++ ast/src/constrain.rs | 1746 ++++++++++++++++++++ ast/src/lang/core/ast.rs | 31 + ast/src/lang/core/def/def.rs | 1437 +++++++++++++++++ ast/src/lang/core/def/def2.rs | 15 + ast/src/lang/core/def/def_to_def2.rs | 75 + ast/src/lang/core/def/mod.rs | 3 + ast/src/lang/core/expr/expr2.rs | 235 +++ ast/src/lang/core/expr/expr2_to_string.rs | 136 ++ ast/src/lang/core/expr/expr_to_expr2.rs | 694 ++++++++ ast/src/lang/core/expr/introduced_vars.rs | 52 + ast/src/lang/core/expr/mod.rs | 5 + ast/src/lang/core/expr/output.rs | 28 + ast/src/lang/core/fun_def.rs | 56 + ast/src/lang/core/header.rs | 12 + ast/src/lang/core/mod.rs | 8 + ast/src/lang/core/pattern.rs | 626 ++++++++ ast/src/lang/core/types.rs | 867 ++++++++++ ast/src/lang/core/val_def.rs | 91 ++ ast/src/lang/env.rs | 168 ++ ast/src/lang/mod.rs | 4 + ast/src/lang/rigids.rs | 80 + ast/src/lang/scope.rs | 330 ++++ ast/src/lib.rs | 5 + ast/src/mod.rs | 14 + ast/src/parse/parse_ast.rs | 34 + ast/src/parse/parse_expr.rs | 0 ast/src/parse/parse_header.rs | 11 + ast/src/pool/mod.rs | 4 + ast/src/pool/pool.rs | 228 +++ ast/src/pool/pool_str.rs | 88 ++ ast/src/pool/pool_vec.rs | 324 ++++ ast/src/pool/shallow_clone.rs | 33 + ast/src/roc_file.rs | 133 ++ ast/src/solve_type.rs | 1752 +++++++++++++++++++++ editor/Cargo.toml | 1 - editor/src/lang/pool.rs | 24 +- 43 files changed, 10010 insertions(+), 24 deletions(-) create mode 100644 ast/Cargo.toml create mode 100644 ast/src/ast_error.rs create mode 100644 ast/src/canonicalize/canonicalize.rs create mode 100644 ast/src/canonicalize/mod.rs create mode 100644 ast/src/canonicalize/module.rs create mode 100644 ast/src/constrain.rs create mode 100644 ast/src/lang/core/ast.rs create mode 100644 ast/src/lang/core/def/def.rs create mode 100644 ast/src/lang/core/def/def2.rs create mode 100644 ast/src/lang/core/def/def_to_def2.rs create mode 100644 ast/src/lang/core/def/mod.rs create mode 100644 ast/src/lang/core/expr/expr2.rs create mode 100644 ast/src/lang/core/expr/expr2_to_string.rs create mode 100644 ast/src/lang/core/expr/expr_to_expr2.rs create mode 100644 ast/src/lang/core/expr/introduced_vars.rs create mode 100644 ast/src/lang/core/expr/mod.rs create mode 100644 ast/src/lang/core/expr/output.rs create mode 100644 ast/src/lang/core/fun_def.rs create mode 100644 ast/src/lang/core/header.rs create mode 100644 ast/src/lang/core/mod.rs create mode 100644 ast/src/lang/core/pattern.rs create mode 100644 ast/src/lang/core/types.rs create mode 100644 ast/src/lang/core/val_def.rs create mode 100644 ast/src/lang/env.rs create mode 100644 ast/src/lang/mod.rs create mode 100644 ast/src/lang/rigids.rs create mode 100644 ast/src/lang/scope.rs create mode 100644 ast/src/lib.rs create mode 100644 ast/src/mod.rs create mode 100644 ast/src/parse/parse_ast.rs create mode 100644 ast/src/parse/parse_expr.rs create mode 100644 ast/src/parse/parse_header.rs create mode 100644 ast/src/pool/mod.rs create mode 100644 ast/src/pool/pool.rs create mode 100644 ast/src/pool/pool_str.rs create mode 100644 ast/src/pool/pool_vec.rs create mode 100644 ast/src/pool/shallow_clone.rs create mode 100644 ast/src/roc_file.rs create mode 100644 ast/src/solve_type.rs diff --git a/Cargo.lock b/Cargo.lock index 1a03a50a6f..a7d51f68b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3387,6 +3387,25 @@ dependencies = [ "libc", ] +[[package]] +name = "roc_ast" +version = "0.1.0" +dependencies = [ + "arraystring", + "bumpalo", + "libc", + "page_size", + "roc_can", + "roc_collections", + "roc_module", + "roc_parse", + "roc_problem", + "roc_region", + "roc_types", + "snafu", + "ven_graph", +] + [[package]] name = "roc_build" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7363ba8600..e2864e9243 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ members = [ "vendor/pathfinding", "vendor/pretty", "editor", + "ast", "cli", "cli/cli_utils", "roc_std", diff --git a/ast/Cargo.toml b/ast/Cargo.toml new file mode 100644 index 0000000000..8b03f69880 --- /dev/null +++ b/ast/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "roc_ast" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "AST as used by the editor and docs. In contrast to the compiler, these types do not keep track of a location in a file." + +[dependencies] +roc_can = { path = "../compiler/can" } +roc_collections = { path = "../compiler/collections" } +roc_region = { path = "../compiler/region" } +roc_module = { path = "../compiler/module" } +roc_parse = { path = "../compiler/parse" } +roc_problem = { path = "../compiler/problem" } +roc_types = { path = "../compiler/types" } +arraystring = "0.3.0" +bumpalo = { version = "3.6.1", features = ["collections"] } +libc = "0.2" +page_size = "0.4" +snafu = { version = "0.6", features = ["backtraces"] } +ven_graph = { path = "../vendor/pathfinding" } + +[dev-dependencies] \ No newline at end of file diff --git a/ast/src/ast_error.rs b/ast/src/ast_error.rs new file mode 100644 index 0000000000..b18970e852 --- /dev/null +++ b/ast/src/ast_error.rs @@ -0,0 +1,30 @@ + + +use snafu::{Backtrace, Snafu}; + +use crate::lang::core::ast::ASTNodeId; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum ASTError { + #[snafu(display( + "ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .", + ast_node_id + ))] + ASTNodeIdWithoutExprId { + ast_node_id: ASTNodeId, + backtrace: Backtrace, + }, + #[snafu(display( + "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", + required_pattern2, + encountered_pattern2, + ))] + UnexpectedPattern2Variant { + required_pattern2: String, + encountered_pattern2: String, + backtrace: Backtrace, + }, +} + +pub type ASTResult = std::result::Result; \ No newline at end of file diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs new file mode 100644 index 0000000000..1e74257bc5 --- /dev/null +++ b/ast/src/canonicalize/canonicalize.rs @@ -0,0 +1,289 @@ + +use roc_can::{env::Env, expr::Output, scope::Scope}; +use roc_collections::all::MutMap; +use roc_problem::can::{Problem}; +use roc_region::all::{Located, Region}; +use roc_types::{subs::Variable}; + +use crate::{lang::core::{def::def::References, expr::expr2::{Expr2, ExprId, RecordField, WhenBranch}}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; + +enum CanonicalizeRecordProblem { + InvalidOptionalValue { + field_name: PoolStr, + field_region: Region, + record_region: Region, + }, +} + +enum FieldVar { + VarAndExprId(Variable, ExprId), + OnlyVar(Variable), +} + +fn canonicalize_fields<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + fields: &'a [Located>>], +) -> Result<(PoolVec, Output), CanonicalizeRecordProblem> { + let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); + let mut output = Output::default(); + + for loc_field in fields.iter() { + match canonicalize_field(env, scope, &loc_field.value) { + Ok(can_field) => { + match can_field { + CanonicalField::LabelAndValue { + label, + value_expr, + value_output, + var, + } => { + let expr_id = env.pool.add(value_expr); + + let replaced = + can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id)); + + if let Some(_old) = replaced { + // env.problems.push(Problem::DuplicateRecordFieldValue { + // field_name: label, + // field_region: loc_field.region, + // record_region: region, + // replaced_region: old.region, + // }); + todo!() + } + + output.references.union_mut(value_output.references); + } + CanonicalField::InvalidLabelOnly { label, var } => { + let replaced = can_fields.insert(label, FieldVar::OnlyVar(var)); + + if let Some(_old) = replaced { + todo!() + } + } + } + } + + Err(CanonicalizeFieldProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + }) => { + // env.problem(Problem::InvalidOptionalValue { + // field_name: field_name.clone(), + // field_region, + // record_region: region, + // }); + // return Err(CanonicalizeRecordProblem::InvalidOptionalValue { + // field_name, + // field_region, + // record_region: region, + // }); + todo!() + } + } + } + + let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool); + + for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) { + let name = PoolStr::new(string, env.pool); + + match field_var { + FieldVar::VarAndExprId(var, expr_id) => { + env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); + } + FieldVar::OnlyVar(var) => { + env.pool[node_id] = RecordField::InvalidLabelOnly(name, var); + } // TODO RecordField::LabelOnly + } + } + + Ok((pool_vec, output)) +} + +enum CanonicalizeFieldProblem { + InvalidOptionalValue { + field_name: PoolStr, + field_region: Region, + }, +} +enum CanonicalField<'a> { + LabelAndValue { + label: &'a str, + value_expr: Expr2, + value_output: Output, + var: Variable, + }, + InvalidLabelOnly { + label: &'a str, + var: Variable, + }, // TODO make ValidLabelOnly +} +fn canonicalize_field<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>, +) -> Result, CanonicalizeFieldProblem> { + use roc_parse::ast::AssignedField::*; + + match field { + // Both a label and a value, e.g. `{ name: "blah" }` + RequiredValue(label, _, loc_expr) => { + let field_var = env.var_store.fresh(); + let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); + + Ok(CanonicalField::LabelAndValue { + label: label.value, + value_expr: loc_can_expr, + value_output: output, + var: field_var, + }) + } + + OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { + field_name: PoolStr::new(label.value, env.pool), + field_region: Region::span_across(&label.region, &loc_expr.region), + }), + + // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) + LabelOnly(label) => { + let field_var = env.var_store.fresh(); + // TODO return ValidLabel if label points to in scope variable + Ok(CanonicalField::InvalidLabelOnly { + label: label.value, + var: field_var, + }) + } + + SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => { + canonicalize_field(env, scope, sub_field) + } + + Malformed(_string) => { + panic!("TODO canonicalize malformed record field"); + } + } +} + +#[inline(always)] +fn canonicalize_when_branch<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + branch: &'a roc_parse::ast::WhenBranch<'a>, + output: &mut Output, +) -> (WhenBranch, References) { + let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool); + + let original_scope = scope; + let mut scope = original_scope.shallow_clone(); + + // TODO report symbols not bound in all patterns + for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) { + let (new_output, can_pattern) = to_pattern2( + env, + &mut scope, + roc_parse::pattern::PatternType::WhenBranch, + &loc_pattern.value, + loc_pattern.region, + ); + + output.union(new_output); + + env.set_region(node_id, loc_pattern.region); + env.pool[node_id] = can_pattern; + } + + let (value, mut branch_output) = + to_expr2(env, &mut scope, &branch.value.value, branch.value.region); + let value_id = env.pool.add(value); + env.set_region(value_id, branch.value.region); + + let guard = match &branch.guard { + None => None, + Some(loc_expr) => { + let (can_guard, guard_branch_output) = + to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region); + + let expr_id = env.pool.add(can_guard); + env.set_region(expr_id, loc_expr.region); + + branch_output.union(guard_branch_output); + Some(expr_id) + } + }; + + // Now that we've collected all the references for this branch, check to see if + // any of the new idents it defined were unused. If any were, report it. + for (symbol, region) in scope.symbols() { + let symbol = symbol; + + if !output.references.has_lookup(symbol) + && !branch_output.references.has_lookup(symbol) + && !original_scope.contains_symbol(symbol) + { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + let references = branch_output.references.clone(); + output.union(branch_output); + + ( + WhenBranch { + patterns, + body: value_id, + guard, + }, + references, + ) +} + +fn canonicalize_lookup( + env: &mut Env<'_>, + scope: &mut Scope, + module_name: &str, + ident: &str, + region: Region, +) -> (Expr2, Output) { + use Expr2::*; + + let mut output = Output::default(); + let can_expr = if module_name.is_empty() { + // Since module_name was empty, this is an unqualified var. + // Look it up in scope! + match scope.lookup(&(*ident).into(), region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(problem) => { + env.problem(Problem::RuntimeError(problem.clone())); + + RuntimeError() + } + } + } else { + // Since module_name was nonempty, this is a qualified var. + // Look it up in the env! + match env.qualified_lookup(module_name, ident, region) { + Ok(symbol) => { + output.references.lookups.insert(symbol); + + Var(symbol) + } + Err(problem) => { + // Either the module wasn't imported, or + // it was imported but it doesn't expose this ident. + env.problem(Problem::RuntimeError(problem.clone())); + + RuntimeError() + } + } + }; + + // If it's valid, this ident should be in scope already. + + (can_expr, output) +} \ No newline at end of file diff --git a/ast/src/canonicalize/mod.rs b/ast/src/canonicalize/mod.rs new file mode 100644 index 0000000000..45b2084adf --- /dev/null +++ b/ast/src/canonicalize/mod.rs @@ -0,0 +1,2 @@ +pub mod canonicalize; +pub mod module; \ No newline at end of file diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalize/module.rs new file mode 100644 index 0000000000..28d37e18cb --- /dev/null +++ b/ast/src/canonicalize/module.rs @@ -0,0 +1,319 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] +use bumpalo::Bump; +use roc_can::operator::desugar_def; +use roc_can::scope::Scope; +use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_module::ident::Ident; +use roc_module::ident::Lowercase; +use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_parse::ast; +use roc_parse::pattern::PatternType; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore, Variable}; + +use crate::env::Env; +use crate::expr::output::Output; +use crate::lang::expr::output::Output; +use crate::pool::pool::NodeId; +use crate::pool::pool::Pool; +use crate::pool::pool_vec::PoolVec; +use crate::types::Alias; + +pub struct ModuleOutput { + pub aliases: MutMap>, + pub rigid_variables: MutMap, + pub declarations: Vec, + pub exposed_imports: MutMap, + pub lookups: Vec<(Symbol, Variable, Region)>, + pub problems: Vec, + pub ident_ids: IdentIds, + pub references: MutSet, +} + +// TODO trim these down +#[allow(clippy::too_many_arguments)] +pub fn canonicalize_module_defs<'a>( + arena: &Bump, + loc_defs: &'a [Located>], + home: ModuleId, + module_ids: &ModuleIds, + exposed_ident_ids: IdentIds, + dep_idents: MutMap, + aliases: MutMap, + exposed_imports: MutMap, + mut exposed_symbols: MutSet, + var_store: &mut VarStore, +) -> Result { + let mut pool = Pool::with_capacity(1 << 10); + let mut can_exposed_imports = MutMap::default(); + let mut scope = Scope::new(home, &mut pool, var_store); + let num_deps = dep_idents.len(); + + for (name, alias) in aliases.into_iter() { + let vars = PoolVec::with_capacity(alias.targs.len() as u32, &mut pool); + + for (node_id, targ_id) in vars.iter_node_ids().zip(alias.targs.iter_node_ids()) { + let (poolstr, var) = &pool[targ_id]; + pool[node_id] = (poolstr.shallow_clone(), *var); + } + scope.add_alias(&mut pool, name, vars, alias.actual); + } + + // Desugar operators (convert them to Apply calls, taking into account + // operator precedence and associativity rules), before doing other canonicalization. + // + // If we did this *during* canonicalization, then each time we + // visited a BinOp node we'd recursively try to apply this to each of its nested + // operators, and then again on *their* nested operators, ultimately applying the + // rules multiple times unnecessarily. + let mut desugared = + bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena); + + for loc_def in loc_defs.iter() { + desugared.push(&*arena.alloc(Located { + value: desugar_def(arena, &loc_def.value), + region: loc_def.region, + })); + } + + let mut env = Env::new( + home, + arena, + &mut pool, + var_store, + dep_idents, + module_ids, + exposed_ident_ids, + ); + let mut lookups = Vec::with_capacity(num_deps); + let rigid_variables = MutMap::default(); + + // Exposed values are treated like defs that appear before any others, e.g. + // + // imports [ Foo.{ bar, baz } ] + // + // ...is basically the same as if we'd added these extra defs at the start of the module: + // + // bar = Foo.bar + // baz = Foo.baz + // + // Here we essentially add those "defs" to "the beginning of the module" + // by canonicalizing them right before we canonicalize the actual ast::Def nodes. + for (ident, (symbol, region)) in exposed_imports { + let first_char = ident.as_inline_str().chars().next().unwrap(); + + if first_char.is_lowercase() { + // this is a value definition + let expr_var = env.var_store.fresh(); + + match scope.import(ident, symbol, region) { + Ok(()) => { + // Add an entry to exposed_imports using the current module's name + // as the key; e.g. if this is the Foo module and we have + // exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when + // anything references `baz` in this Foo module, it will resolve to Bar.baz. + can_exposed_imports.insert(symbol, expr_var); + + // This will be used during constraint generation, + // to add the usual Lookup constraint as if this were a normal def. + lookups.push((symbol, expr_var, region)); + } + Err((_shadowed_symbol, _region)) => { + panic!("TODO gracefully handle shadowing in imports.") + } + } + } else { + // This is a type alias + + // the should already be added to the scope when this module is canonicalized + debug_assert!(scope.contains_alias(symbol)); + } + } + + let (defs, _scope, output, symbols_introduced) = canonicalize_defs( + &mut env, + Output::default(), + &scope, + &desugared, + PatternType::TopLevelDef, + ); + + // See if any of the new idents we defined went unused. + // If any were unused and also not exposed, report it. + for (symbol, region) in symbols_introduced { + if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + // TODO register rigids + // for (var, lowercase) in output.introduced_variables.name_by_var.clone() { + // rigid_variables.insert(var, lowercase); + // } + + let mut references = MutSet::default(); + + // Gather up all the symbols that were referenced across all the defs' lookups. + for symbol in output.references.lookups.iter() { + references.insert(*symbol); + } + + // Gather up all the symbols that were referenced across all the defs' calls. + for symbol in output.references.calls.iter() { + references.insert(*symbol); + } + + // Gather up all the symbols that were referenced from other modules. + for symbol in env.qualified_lookups.iter() { + references.insert(*symbol); + } + + // NOTE previously we inserted builtin defs into the list of defs here + // this is now done later, in file.rs. + + match sort_can_defs(&mut env, defs, Output::default()) { + (Ok(mut declarations), output) => { + use Declaration::*; + + for decl in declarations.iter() { + match decl { + Declare(def) => { + for symbol in def.symbols(env.pool) { + if exposed_symbols.contains(&symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + } + } + } + DeclareRec(defs) => { + for def in defs { + for symbol in def.symbols(env.pool) { + if exposed_symbols.contains(&symbol) { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + } + } + } + } + + InvalidCycle(identifiers, _) => { + panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers); + } + Builtin(def) => { + // Builtins cannot be exposed in module declarations. + // This should never happen! + debug_assert!(def + .symbols(env.pool) + .iter() + .all(|symbol| !exposed_symbols.contains(symbol))); + } + } + } + + let mut aliases = MutMap::default(); + + for (symbol, alias) in output.aliases { + // Remove this from exposed_symbols, + // so that at the end of the process, + // we can see if there were any + // exposed symbols which did not have + // corresponding defs. + exposed_symbols.remove(&symbol); + + aliases.insert(symbol, alias); + } + + // By this point, all exposed symbols should have been removed from + // exposed_symbols and added to exposed_vars_by_symbol. If any were + // not, that means they were declared as exposed but there was + // no actual declaration with that name! + for symbol in exposed_symbols { + env.problem(Problem::ExposedButNotDefined(symbol)); + + // In case this exposed value is referenced by other modules, + // create a decl for it whose implementation is a runtime error. + let mut pattern_vars = SendMap::default(); + pattern_vars.insert(symbol, env.var_store.fresh()); + + let runtime_error = RuntimeError::ExposedButNotDefined(symbol); + + let value_def = { + let pattern_id = env.pool.add(Pattern2::Identifier(symbol)); + let expr_id = env.pool.add(Expr2::RuntimeError()); + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var: env.var_store.fresh(), + } + }; + + let def = Def::Value(value_def); + + declarations.push(Declaration::Declare(def)); + } + + // Incorporate any remaining output.lookups entries into references. + for symbol in output.references.lookups { + references.insert(symbol); + } + + // Incorporate any remaining output.calls entries into references. + for symbol in output.references.calls { + references.insert(symbol); + } + + // Gather up all the symbols that were referenced from other modules. + for symbol in env.qualified_lookups.iter() { + references.insert(*symbol); + } + + // TODO find captured variables + // for declaration in declarations.iter_mut() { + // match declaration { + // Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()), + // DeclareRec(defs) => { + // fix_values_captured_in_closure_defs(defs, &mut MutSet::default()) + // } + // InvalidCycle(_, _) | Builtin(_) => {} + // } + // } + + // TODO this loops over all symbols in the module, we can speed it up by having an + // iterator over all builtin symbols + + // TODO move over the builtins + // for symbol in references.iter() { + // if symbol.is_builtin() { + // // this can fail when the symbol is for builtin types, or has no implementation yet + // if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) { + // declarations.push(Declaration::Builtin(def)); + // } + // } + // } + + Ok(ModuleOutput { + aliases, + rigid_variables, + declarations, + references, + exposed_imports: can_exposed_imports, + problems: vec![], // TODO env.problems, + lookups, + ident_ids: env.ident_ids, + }) + } + (Err(runtime_error), _) => Err(runtime_error), + } +} diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs new file mode 100644 index 0000000000..77d4edb0cd --- /dev/null +++ b/ast/src/constrain.rs @@ -0,0 +1,1746 @@ +use bumpalo::{collections::Vec as BumpVec, Bump}; + +use crate::lang::{ + ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch}, + expr::Env, + pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, + pool::{Pool, PoolStr, PoolVec, ShallowClone}, + types::{Type2, TypeId}, +}; + +use roc_can::expected::{Expected, PExpected}; +use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; +use roc_module::{ + ident::{Lowercase, TagName}, + symbol::Symbol, +}; +use roc_region::all::Region; +use roc_types::{ + subs::Variable, + types::{self, AnnotationSource, PReason, PatternCategory}, + types::{Category, Reason}, +}; + +#[derive(Debug)] +pub enum Constraint<'a> { + Eq(Type2, Expected, Category, Region), + // Store(Type, Variable, &'static str, u32), + Lookup(Symbol, Expected, Region), + Pattern(Region, PatternCategory, Type2, PExpected), + And(BumpVec<'a, Constraint<'a>>), + Let(&'a LetConstraint<'a>), + // SaveTheEnvironment, + True, // Used for things that always unify, e.g. blanks and runtime errors +} + +#[derive(Debug)] +pub struct LetConstraint<'a> { + pub rigid_vars: BumpVec<'a, Variable>, + pub flex_vars: BumpVec<'a, Variable>, + pub def_types: BumpMap, + pub defs_constraint: Constraint<'a>, + pub ret_constraint: Constraint<'a>, +} + +pub fn constrain_expr<'a>( + arena: &'a Bump, + env: &mut Env, + expr: &Expr2, + expected: Expected, + region: Region, +) -> Constraint<'a> { + use Constraint::*; + + match expr { + Expr2::Blank | Expr2::RuntimeError() | Expr2::InvalidLookup(_) => True, + Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, region), + Expr2::SmallStr(_) => Eq(str_type(env.pool), expected, Category::Str, region), + Expr2::Var(symbol) => Lookup(*symbol, expected, region), + Expr2::EmptyRecord => constrain_empty_record(expected, region), + Expr2::SmallInt { var, .. } | Expr2::I128 { var, .. } | Expr2::U128 { var, .. } => { + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + + flex_vars.push(*var); + + let precision_var = env.var_store.fresh(); + + let range_type = Type2::Variable(precision_var); + + let range_type_id = env.pool.add(range_type); + + exists( + arena, + flex_vars, + Eq( + num_num(env.pool, range_type_id), + expected, + Category::Num, + region, + ), + ) + } + Expr2::Float { var, .. } => { + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + let num_type = Type2::Variable(*var); + + flex_vars.push(*var); + + let precision_var = env.var_store.fresh(); + + let range_type = Type2::Variable(precision_var); + + let range_type_id = env.pool.add(range_type); + + and_constraints.push(Eq( + num_type.shallow_clone(), + Expected::ForReason( + Reason::FloatLiteral, + num_float(env.pool, range_type_id), + region, + ), + Category::Int, + region, + )); + + and_constraints.push(Eq(num_type, expected, Category::Float, region)); + + let defs_constraint = And(and_constraints); + + exists(arena, flex_vars, defs_constraint) + } + Expr2::List { + elem_var, elems, .. + } => { + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + + flex_vars.push(*elem_var); + + if elems.is_empty() { + exists( + arena, + flex_vars, + Eq( + empty_list_type(env.pool, *elem_var), + expected, + Category::List, + region, + ), + ) + } else { + let mut constraints = BumpVec::with_capacity_in(1 + elems.len(), arena); + + let list_elem_type = Type2::Variable(*elem_var); + + let indexed_node_ids: Vec<(usize, ExprId)> = + elems.iter(env.pool).copied().enumerate().collect(); + + for (index, elem_node_id) in indexed_node_ids { + let elem_expr = env.pool.get(elem_node_id); + + let elem_expected = Expected::ForReason( + Reason::ElemInList { + index: Index::zero_based(index), + }, + list_elem_type.shallow_clone(), + region, + ); + + let constraint = constrain_expr(arena, env, elem_expr, elem_expected, region); + + constraints.push(constraint); + } + + constraints.push(Eq( + list_type(env.pool, list_elem_type), + expected, + Category::List, + region, + )); + + exists(arena, flex_vars, And(constraints)) + } + } + Expr2::Record { fields, record_var } => { + if fields.is_empty() { + constrain_empty_record(expected, region) + } else { + let field_types = PoolVec::with_capacity(fields.len() as u32, env.pool); + + let mut field_vars = BumpVec::with_capacity_in(fields.len(), arena); + + // Constraints need capacity for each field + // + 1 for the record itself + 1 for record var + let mut constraints = BumpVec::with_capacity_in(2 + fields.len(), arena); + + for (record_field_node_id, field_type_node_id) in + fields.iter_node_ids().zip(field_types.iter_node_ids()) + { + let record_field = env.pool.get(record_field_node_id); + + match record_field { + RecordField::LabeledValue(pool_str, var, node_id) => { + let expr = env.pool.get(*node_id); + + let (field_type, field_con) = constrain_field(arena, env, *var, expr); + + field_vars.push(*var); + + let field_type_id = env.pool.add(field_type); + + env.pool[field_type_node_id] = + (*pool_str, types::RecordField::Required(field_type_id)); + + constraints.push(field_con); + } + e => todo!("{:?}", e), + } + } + + let record_type = Type2::Record(field_types, env.pool.add(Type2::EmptyRec)); + + let record_con = Eq( + record_type, + expected.shallow_clone(), + Category::Record, + region, + ); + + constraints.push(record_con); + + // variable to store in the AST + let stored_con = Eq( + Type2::Variable(*record_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + field_vars.push(*record_var); + constraints.push(stored_con); + + exists(arena, field_vars, And(constraints)) + } + } + Expr2::GlobalTag { + variant_var, + ext_var, + name, + arguments, + } => { + let tag_name = TagName::Global(name.as_str(env.pool).into()); + + constrain_tag( + arena, + env, + expected, + region, + tag_name, + arguments, + *ext_var, + *variant_var, + ) + } + Expr2::PrivateTag { + name, + arguments, + ext_var, + variant_var, + } => { + let tag_name = TagName::Private(*name); + + constrain_tag( + arena, + env, + expected, + region, + tag_name, + arguments, + *ext_var, + *variant_var, + ) + } + Expr2::Call { + args, + expr_var, + expr: expr_node_id, + closure_var, + fn_var, + .. + } => { + // The expression that evaluates to the function being called, e.g. `foo` in + // (foo) bar baz + let call_expr = env.pool.get(*expr_node_id); + + let opt_symbol = if let Expr2::Var(symbol) = call_expr { + Some(*symbol) + } else { + None + }; + + let fn_type = Type2::Variable(*fn_var); + let fn_region = region; + let fn_expected = Expected::NoExpectation(fn_type.shallow_clone()); + + let fn_reason = Reason::FnCall { + name: opt_symbol, + arity: args.len() as u8, + }; + + let fn_con = constrain_expr(arena, env, call_expr, fn_expected, region); + + // The function's return type + // TODO: don't use expr_var? + let ret_type = Type2::Variable(*expr_var); + + // type of values captured in the closure + let closure_type = Type2::Variable(*closure_var); + + // This will be used in the occurs check + let mut vars = BumpVec::with_capacity_in(2 + args.len(), arena); + + vars.push(*fn_var); + // TODO: don't use expr_var? + vars.push(*expr_var); + vars.push(*closure_var); + + let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); + let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); + + for (index, arg_node_id) in args.iter_node_ids().enumerate() { + let (arg_var, arg) = env.pool.get(arg_node_id); + let arg_expr = env.pool.get(*arg); + + let region = region; + let arg_type = Type2::Variable(*arg_var); + + let reason = Reason::FnArg { + name: opt_symbol, + arg_index: Index::zero_based(index), + }; + + let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region); + + let arg_con = constrain_expr(arena, env, arg_expr, expected_arg, region); + + vars.push(*arg_var); + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let expected_fn_type = Expected::ForReason( + fn_reason, + Type2::Function( + PoolVec::new(arg_types.into_iter(), env.pool), + env.pool.add(closure_type), + env.pool.add(ret_type.shallow_clone()), + ), + region, + ); + + let category = Category::CallResult(opt_symbol); + + let mut and_constraints = BumpVec::with_capacity_in(4, arena); + + and_constraints.push(fn_con); + and_constraints.push(Eq(fn_type, expected_fn_type, category.clone(), fn_region)); + and_constraints.push(And(arg_cons)); + and_constraints.push(Eq(ret_type, expected, category, region)); + + exists(arena, vars, And(and_constraints)) + } + Expr2::Accessor { + function_var, + closure_var, + field, + record_var, + ext_var, + field_var, + } => { + let ext_var = *ext_var; + let ext_type = Type2::Variable(ext_var); + + let field_var = *field_var; + let field_type = Type2::Variable(field_var); + + let record_field = + types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); + + let record_type = Type2::Record( + PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), + env.pool.add(ext_type), + ); + + let category = Category::Accessor(field.as_str(env.pool).into()); + + let record_expected = Expected::NoExpectation(record_type.shallow_clone()); + let record_con = Eq( + Type2::Variable(*record_var), + record_expected, + category.clone(), + region, + ); + + let function_type = Type2::Function( + PoolVec::new(vec![record_type].into_iter(), env.pool), + env.pool.add(Type2::Variable(*closure_var)), + env.pool.add(field_type), + ); + + let mut flex_vars = BumpVec::with_capacity_in(5, arena); + + flex_vars.push(*record_var); + flex_vars.push(*function_var); + flex_vars.push(*closure_var); + flex_vars.push(field_var); + flex_vars.push(ext_var); + + let mut and_constraints = BumpVec::with_capacity_in(3, arena); + + and_constraints.push(Eq( + function_type.shallow_clone(), + expected, + category.clone(), + region, + )); + + and_constraints.push(Eq( + function_type, + Expected::NoExpectation(Type2::Variable(*function_var)), + category, + region, + )); + + and_constraints.push(record_con); + + exists(arena, flex_vars, And(and_constraints)) + } + Expr2::Access { + expr: expr_id, + field, + field_var, + record_var, + ext_var, + } => { + let ext_type = Type2::Variable(*ext_var); + + let field_type = Type2::Variable(*field_var); + + let record_field = + types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); + + let record_type = Type2::Record( + PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), + env.pool.add(ext_type), + ); + + let record_expected = Expected::NoExpectation(record_type); + + let category = Category::Access(field.as_str(env.pool).into()); + + let record_con = Eq( + Type2::Variable(*record_var), + record_expected.shallow_clone(), + category.clone(), + region, + ); + + let access_expr = env.pool.get(*expr_id); + + let constraint = constrain_expr(arena, env, access_expr, record_expected, region); + + let mut flex_vars = BumpVec::with_capacity_in(3, arena); + + flex_vars.push(*record_var); + flex_vars.push(*field_var); + flex_vars.push(*ext_var); + + let mut and_constraints = BumpVec::with_capacity_in(3, arena); + + and_constraints.push(constraint); + and_constraints.push(Eq(field_type, expected, category, region)); + and_constraints.push(record_con); + + exists(arena, flex_vars, And(and_constraints)) + } + Expr2::If { + cond_var, + expr_var, + branches, + final_else, + } => { + let expect_bool = |region| { + let bool_type = Type2::Variable(Variable::BOOL); + Expected::ForReason(Reason::IfCondition, bool_type, region) + }; + + let mut branch_cons = BumpVec::with_capacity_in(2 * branches.len() + 3, arena); + + // TODO why does this cond var exist? is it for error messages? + // let first_cond_region = branches[0].0.region; + let cond_var_is_bool_con = Eq( + Type2::Variable(*cond_var), + expect_bool(region), + Category::If, + region, + ); + + branch_cons.push(cond_var_is_bool_con); + + let final_else_expr = env.pool.get(*final_else); + + let mut flex_vars = BumpVec::with_capacity_in(2, arena); + + flex_vars.push(*cond_var); + flex_vars.push(*expr_var); + + match expected { + Expected::FromAnnotation(name, arity, _, tipe) => { + let num_branches = branches.len() + 1; + + for (index, branch_id) in branches.iter_node_ids().enumerate() { + let (cond_id, body_id) = env.pool.get(branch_id); + + let cond = env.pool.get(*cond_id); + let body = env.pool.get(*body_id); + + let cond_con = + constrain_expr(arena, env, cond, expect_bool(region), region); + + let then_con = constrain_expr( + arena, + env, + body, + Expected::FromAnnotation( + name.clone(), + arity, + AnnotationSource::TypedIfBranch { + index: Index::zero_based(index), + num_branches, + }, + tipe.shallow_clone(), + ), + region, + ); + + branch_cons.push(cond_con); + branch_cons.push(then_con); + } + + let else_con = constrain_expr( + arena, + env, + final_else_expr, + Expected::FromAnnotation( + name, + arity, + AnnotationSource::TypedIfBranch { + index: Index::zero_based(branches.len()), + num_branches, + }, + tipe.shallow_clone(), + ), + region, + ); + + let ast_con = Eq( + Type2::Variable(*expr_var), + Expected::NoExpectation(tipe), + Category::Storage(std::file!(), std::line!()), + region, + ); + + branch_cons.push(ast_con); + branch_cons.push(else_con); + + exists(arena, flex_vars, And(branch_cons)) + } + _ => { + for (index, branch_id) in branches.iter_node_ids().enumerate() { + let (cond_id, body_id) = env.pool.get(branch_id); + + let cond = env.pool.get(*cond_id); + let body = env.pool.get(*body_id); + + let cond_con = + constrain_expr(arena, env, cond, expect_bool(region), region); + + let then_con = constrain_expr( + arena, + env, + body, + Expected::ForReason( + Reason::IfBranch { + index: Index::zero_based(index), + total_branches: branches.len(), + }, + Type2::Variable(*expr_var), + // should be from body + region, + ), + region, + ); + + branch_cons.push(cond_con); + branch_cons.push(then_con); + } + + let else_con = constrain_expr( + arena, + env, + final_else_expr, + Expected::ForReason( + Reason::IfBranch { + index: Index::zero_based(branches.len()), + total_branches: branches.len() + 1, + }, + Type2::Variable(*expr_var), + // should come from final_else + region, + ), + region, + ); + + branch_cons.push(Eq( + Type2::Variable(*expr_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + )); + + branch_cons.push(else_con); + + exists(arena, flex_vars, And(branch_cons)) + } + } + } + Expr2::When { + cond_var, + expr_var, + cond: cond_id, + branches, + } => { + // Infer the condition expression's type. + let cond_type = Type2::Variable(*cond_var); + + let cond = env.pool.get(*cond_id); + + let expr_con = constrain_expr( + arena, + env, + cond, + Expected::NoExpectation(cond_type.shallow_clone()), + region, + ); + + let mut constraints = BumpVec::with_capacity_in(branches.len() + 1, arena); + + constraints.push(expr_con); + + let mut flex_vars = BumpVec::with_capacity_in(2, arena); + + flex_vars.push(*cond_var); + flex_vars.push(*expr_var); + + match &expected { + Expected::FromAnnotation(name, arity, _, _typ) => { + // NOTE deviation from elm. + // + // in elm, `_typ` is used, but because we have this `expr_var` too + // and need to constrain it, this is what works and gives better error messages + let typ = Type2::Variable(*expr_var); + + for (index, when_branch_id) in branches.iter_node_ids().enumerate() { + let when_branch = env.pool.get(when_branch_id); + + let pattern_region = region; + // let pattern_region = Region::across_all( + // when_branch.patterns.iter(env.pool).map(|v| &v.region), + // ); + + let branch_con = constrain_when_branch( + arena, + env, + // TODO: when_branch.value.region, + region, + when_branch, + PExpected::ForReason( + PReason::WhenMatch { + index: Index::zero_based(index), + }, + cond_type.shallow_clone(), + pattern_region, + ), + Expected::FromAnnotation( + name.clone(), + *arity, + AnnotationSource::TypedWhenBranch { + index: Index::zero_based(index), + }, + typ.shallow_clone(), + ), + ); + + constraints.push(branch_con); + } + + constraints.push(Eq(typ, expected, Category::When, region)); + + return exists(arena, flex_vars, And(constraints)); + } + + _ => { + let branch_type = Type2::Variable(*expr_var); + let mut branch_cons = BumpVec::with_capacity_in(branches.len(), arena); + + for (index, when_branch_id) in branches.iter_node_ids().enumerate() { + let when_branch = env.pool.get(when_branch_id); + + let pattern_region = region; + // let pattern_region = + // Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + + let branch_con = constrain_when_branch( + arena, + env, + region, + when_branch, + PExpected::ForReason( + PReason::WhenMatch { + index: Index::zero_based(index), + }, + cond_type.shallow_clone(), + pattern_region, + ), + Expected::ForReason( + Reason::WhenBranch { + index: Index::zero_based(index), + }, + branch_type.shallow_clone(), + // TODO: when_branch.value.region, + region, + ), + ); + + branch_cons.push(branch_con); + } + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(And(branch_cons)); + and_constraints.push(Eq(branch_type, expected, Category::When, region)); + + constraints.push(And(and_constraints)); + } + } + + // exhautiveness checking happens when converting to mono::Expr + exists(arena, flex_vars, And(constraints)) + } + Expr2::LetValue { + def_id, + body_id, + body_var, + } => { + let value_def = env.pool.get(*def_id); + let body = env.pool.get(*body_id); + + let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region); + + match value_def { + ValueDef::WithAnnotation { .. } => todo!("implement {:?}", value_def), + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + let pattern = env.pool.get(*pattern_id); + + let mut flex_vars = BumpVec::with_capacity_in(1, arena); + flex_vars.push(*body_var); + + let expr_type = Type2::Variable(*expr_var); + + let pattern_expected = PExpected::NoExpectation(expr_type.shallow_clone()); + let mut state = PatternState2 { + headers: BumpMap::new_in(arena), + vars: BumpVec::with_capacity_in(1, arena), + constraints: BumpVec::with_capacity_in(1, arena), + }; + + constrain_pattern(arena, env, pattern, region, pattern_expected, &mut state); + state.vars.push(*expr_var); + + let def_expr = env.pool.get(*expr_id); + + let constrained_def = Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), // always empty + flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments + def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments! + defs_constraint: And(state.constraints), + ret_constraint: constrain_expr( + arena, + env, + def_expr, + Expected::NoExpectation(expr_type), + region, + ), + })), + ret_constraint: body_con, + })); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(constrained_def); + and_constraints.push(Eq( + Type2::Variable(*body_var), + expected, + Category::Storage(std::file!(), std::line!()), + // TODO: needs to be ret region + region, + )); + + exists(arena, flex_vars, And(and_constraints)) + } + } + } + Expr2::Update { + symbol, + updates, + ext_var, + record_var, + } => { + let field_types = PoolVec::with_capacity(updates.len() as u32, env.pool); + let mut flex_vars = BumpVec::with_capacity_in(updates.len() + 2, arena); + let mut cons = BumpVec::with_capacity_in(updates.len() + 1, arena); + let mut record_key_updates = SendMap::default(); + + for (record_field_id, field_type_node_id) in + updates.iter_node_ids().zip(field_types.iter_node_ids()) + { + let record_field = env.pool.get(record_field_id); + + match record_field { + RecordField::LabeledValue(pool_str, var, node_id) => { + let expr = env.pool.get(*node_id); + + let (field_type, field_con) = constrain_field_update( + arena, + env, + *var, + pool_str.as_str(env.pool).into(), + expr, + ); + + let field_type_id = env.pool.add(field_type); + + env.pool[field_type_node_id] = + (*pool_str, types::RecordField::Required(field_type_id)); + + record_key_updates.insert(pool_str.as_str(env.pool).into(), Region::zero()); + + flex_vars.push(*var); + cons.push(field_con); + } + e => todo!("{:?}", e), + } + } + + let fields_type = Type2::Record(field_types, env.pool.add(Type2::Variable(*ext_var))); + let record_type = Type2::Variable(*record_var); + + // NOTE from elm compiler: fields_type is separate so that Error propagates better + let fields_con = Eq( + record_type.shallow_clone(), + Expected::NoExpectation(fields_type), + Category::Record, + region, + ); + let record_con = Eq( + record_type.shallow_clone(), + expected, + Category::Record, + region, + ); + + flex_vars.push(*record_var); + flex_vars.push(*ext_var); + + let con = Lookup( + *symbol, + Expected::ForReason( + Reason::RecordUpdateKeys(*symbol, record_key_updates), + record_type, + region, + ), + region, + ); + + // ensure constraints are solved in this order, gives better errors + cons.insert(0, fields_con); + cons.insert(1, con); + cons.insert(2, record_con); + + exists(arena, flex_vars, And(cons)) + } + + Expr2::RunLowLevel { op, args, ret_var } => { + // This is a modified version of what we do for function calls. + + // The operation's return type + let ret_type = Type2::Variable(*ret_var); + + // This will be used in the occurs check + let mut vars = BumpVec::with_capacity_in(1 + args.len(), arena); + + vars.push(*ret_var); + + let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); + let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); + + for (index, node_id) in args.iter_node_ids().enumerate() { + let (arg_var, arg_id) = env.pool.get(node_id); + + vars.push(*arg_var); + + let arg_type = Type2::Variable(*arg_var); + + let reason = Reason::LowLevelOpArg { + op: *op, + arg_index: Index::zero_based(index), + }; + let expected_arg = + Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); + let arg = env.pool.get(*arg_id); + + let arg_con = constrain_expr(arena, env, arg, expected_arg, Region::zero()); + + arg_types.push(arg_type); + arg_cons.push(arg_con); + } + + let category = Category::LowLevelOpResult(*op); + + let mut and_constraints = BumpVec::with_capacity_in(2, arena); + + and_constraints.push(And(arg_cons)); + and_constraints.push(Eq(ret_type, expected, category, region)); + + exists(arena, vars, And(and_constraints)) + } + Expr2::Closure { + args, + name, + body: body_id, + function_type: fn_var, + extra, + .. + } => { + // NOTE defs are treated somewhere else! + let body = env.pool.get(*body_id); + + let ClosureExtra { + captured_symbols, + return_type: ret_var, + closure_type: closure_var, + closure_ext_var, + } = env.pool.get(*extra); + + let closure_type = Type2::Variable(*closure_var); + let return_type = Type2::Variable(*ret_var); + + let (mut vars, pattern_state, function_type) = + constrain_untyped_args(arena, env, args, closure_type, return_type.shallow_clone()); + + vars.push(*ret_var); + vars.push(*closure_var); + vars.push(*closure_ext_var); + vars.push(*fn_var); + + let expected_body_type = Expected::NoExpectation(return_type); + // Region here should come from body expr + let ret_constraint = constrain_expr(arena, env, body, expected_body_type, region); + + let captured_symbols_as_vec = captured_symbols + .iter(env.pool) + .copied() + .collect::>(); + + // make sure the captured symbols are sorted! + debug_assert_eq!(captured_symbols_as_vec, { + let mut copy: Vec<(Symbol, Variable)> = captured_symbols_as_vec.clone(); + + copy.sort(); + + copy + }); + + let closure_constraint = constrain_closure_size( + arena, + env, + *name, + region, + captured_symbols, + *closure_var, + *closure_ext_var, + &mut vars, + ); + + let mut and_constraints = BumpVec::with_capacity_in(4, arena); + + and_constraints.push(Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: pattern_state.vars, + def_types: pattern_state.headers, + defs_constraint: And(pattern_state.constraints), + ret_constraint, + }))); + + // "the closure's type is equal to expected type" + and_constraints.push(Eq( + function_type.shallow_clone(), + expected, + Category::Lambda, + region, + )); + + // "fn_var is equal to the closure's type" - fn_var is used in code gen + and_constraints.push(Eq( + Type2::Variable(*fn_var), + Expected::NoExpectation(function_type), + Category::Storage(std::file!(), std::line!()), + region, + )); + + and_constraints.push(closure_constraint); + + exists(arena, vars, And(and_constraints)) + } + Expr2::LetRec { .. } => todo!(), + Expr2::LetFunction { .. } => todo!(), + } +} + +fn exists<'a>( + arena: &'a Bump, + flex_vars: BumpVec<'a, Variable>, + defs_constraint: Constraint<'a>, +) -> Constraint<'a> { + Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars, + def_types: BumpMap::new_in(arena), + defs_constraint, + ret_constraint: Constraint::True, + })) +} + +#[allow(clippy::too_many_arguments)] +fn constrain_tag<'a>( + arena: &'a Bump, + env: &mut Env, + expected: Expected, + region: Region, + tag_name: TagName, + arguments: &PoolVec<(Variable, ExprId)>, + ext_var: Variable, + variant_var: Variable, +) -> Constraint<'a> { + use Constraint::*; + + let mut flex_vars = BumpVec::with_capacity_in(arguments.len(), arena); + let types = PoolVec::with_capacity(arguments.len() as u32, env.pool); + let mut arg_cons = BumpVec::with_capacity_in(arguments.len(), arena); + + for (argument_node_id, type_node_id) in arguments.iter_node_ids().zip(types.iter_node_ids()) { + let (var, expr_node_id) = env.pool.get(argument_node_id); + + let argument_expr = env.pool.get(*expr_node_id); + + let arg_con = constrain_expr( + arena, + env, + argument_expr, + Expected::NoExpectation(Type2::Variable(*var)), + region, + ); + + arg_cons.push(arg_con); + flex_vars.push(*var); + + env.pool[type_node_id] = Type2::Variable(*var); + } + + let union_con = Eq( + Type2::TagUnion( + PoolVec::new(std::iter::once((tag_name.clone(), types)), env.pool), + env.pool.add(Type2::Variable(ext_var)), + ), + expected.shallow_clone(), + Category::TagApply { + tag_name, + args_count: arguments.len(), + }, + region, + ); + + let ast_con = Eq( + Type2::Variable(variant_var), + expected, + Category::Storage(std::file!(), std::line!()), + region, + ); + + flex_vars.push(variant_var); + flex_vars.push(ext_var); + + arg_cons.push(union_con); + arg_cons.push(ast_con); + + exists(arena, flex_vars, And(arg_cons)) +} + +fn constrain_field<'a>( + arena: &'a Bump, + env: &mut Env, + field_var: Variable, + expr: &Expr2, +) -> (Type2, Constraint<'a>) { + let field_type = Type2::Variable(field_var); + let field_expected = Expected::NoExpectation(field_type.shallow_clone()); + let constraint = constrain_expr(arena, env, expr, field_expected, Region::zero()); + + (field_type, constraint) +} + +#[inline(always)] +fn constrain_field_update<'a>( + arena: &'a Bump, + env: &mut Env, + field_var: Variable, + field: Lowercase, + expr: &Expr2, +) -> (Type2, Constraint<'a>) { + let field_type = Type2::Variable(field_var); + let reason = Reason::RecordUpdateValue(field); + let field_expected = Expected::ForReason(reason, field_type.shallow_clone(), Region::zero()); + let con = constrain_expr(arena, env, expr, field_expected, Region::zero()); + + (field_type, con) +} + +fn constrain_empty_record<'a>(expected: Expected, region: Region) -> Constraint<'a> { + Constraint::Eq(Type2::EmptyRec, expected, Category::Record, region) +} + +#[inline(always)] +fn constrain_when_branch<'a>( + arena: &'a Bump, + env: &mut Env, + region: Region, + when_branch: &WhenBranch, + pattern_expected: PExpected, + expr_expected: Expected, +) -> Constraint<'a> { + let when_expr = env.pool.get(when_branch.body); + + let ret_constraint = constrain_expr(arena, env, when_expr, expr_expected, region); + + let mut state = PatternState2 { + headers: BumpMap::new_in(arena), + vars: BumpVec::with_capacity_in(1, arena), + constraints: BumpVec::with_capacity_in(1, arena), + }; + + // TODO investigate for error messages, is it better to unify all branches with a variable, + // then unify that variable with the expectation? + for pattern_id in when_branch.patterns.iter_node_ids() { + let pattern = env.pool.get(pattern_id); + + constrain_pattern( + arena, + env, + pattern, + // loc_pattern.region, + region, + pattern_expected.shallow_clone(), + &mut state, + ); + } + + if let Some(guard_id) = &when_branch.guard { + let guard = env.pool.get(*guard_id); + + let guard_constraint = constrain_expr( + arena, + env, + guard, + Expected::ForReason( + Reason::WhenGuard, + Type2::Variable(Variable::BOOL), + // TODO: loc_guard.region, + region, + ), + region, + ); + + // must introduce the headers from the pattern before constraining the guard + Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Constraint::And(state.constraints), + ret_constraint: Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: BumpVec::new_in(arena), + def_types: BumpMap::new_in(arena), + defs_constraint: guard_constraint, + ret_constraint, + })), + })) + } else { + Constraint::Let(arena.alloc(LetConstraint { + rigid_vars: BumpVec::new_in(arena), + flex_vars: state.vars, + def_types: state.headers, + defs_constraint: Constraint::And(state.constraints), + ret_constraint, + })) + } +} + +/// This accepts PatternState (rather than returning it) so that the caller can +/// initialize the Vecs in PatternState using with_capacity +/// based on its knowledge of their lengths. +pub fn constrain_pattern<'a>( + arena: &'a Bump, + env: &mut Env, + pattern: &Pattern2, + region: Region, + expected: PExpected, + state: &mut PatternState2<'a>, +) { + use Pattern2::*; + + match pattern { + Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => { + // Neither the _ pattern nor erroneous ones add any constraints. + } + + Identifier(symbol) => { + state.headers.insert(*symbol, expected.get_type()); + } + + NumLiteral(var, _) => { + state.vars.push(*var); + + let type_id = env.pool.add(Type2::Variable(*var)); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Num, + num_num(env.pool, type_id), + expected, + )); + } + + IntLiteral(_int_val) => { + let precision_var = env.var_store.fresh(); + + let range = env.add(Type2::Variable(precision_var), region); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Int, + num_int(env.pool, range), + expected, + )); + } + + FloatLiteral(_float_val) => { + let precision_var = env.var_store.fresh(); + + let range = env.add(Type2::Variable(precision_var), region); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Float, + num_float(env.pool, range), + expected, + )); + } + + StrLiteral(_) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Str, + str_type(env.pool), + expected, + )); + } + + RecordDestructure { + whole_var, + ext_var, + destructs, + } => { + state.vars.push(*whole_var); + state.vars.push(*ext_var); + let ext_type = Type2::Variable(*ext_var); + + let mut field_types = Vec::new(); + + for destruct_id in destructs.iter_node_ids() { + let RecordDestruct { + var, + label, + symbol, + typ, + } = env.pool.get(destruct_id); + + let pat_type = Type2::Variable(*var); + let expected = PExpected::NoExpectation(pat_type.shallow_clone()); + + if !state.headers.contains_key(symbol) { + state.headers.insert(*symbol, pat_type.shallow_clone()); + } + + let destruct_type = env.pool.get(*typ); + + let field_type = match destruct_type { + DestructType::Guard(guard_var, guard_id) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::PatternGuard, + Type2::Variable(*guard_var), + PExpected::ForReason( + PReason::PatternGuard, + pat_type.shallow_clone(), + // TODO: region should be from guard_id + region, + ), + )); + + state.vars.push(*guard_var); + + let guard = env.pool.get(*guard_id); + + // TODO: region should be from guard_id + constrain_pattern(arena, env, guard, region, expected, state); + + types::RecordField::Demanded(env.pool.add(pat_type)) + } + DestructType::Optional(expr_var, expr_id) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::PatternDefault, + Type2::Variable(*expr_var), + PExpected::ForReason( + PReason::OptionalField, + pat_type.shallow_clone(), + // TODO: region should be from expr_id + region, + ), + )); + + state.vars.push(*expr_var); + + let expr_expected = Expected::ForReason( + Reason::RecordDefaultField(label.as_str(env.pool).into()), + pat_type.shallow_clone(), + // TODO: region should be from expr_id + region, + ); + + let expr = env.pool.get(*expr_id); + + // TODO: region should be from expr_id + let expr_con = constrain_expr(arena, env, expr, expr_expected, region); + + state.constraints.push(expr_con); + + types::RecordField::Optional(env.pool.add(pat_type)) + } + DestructType::Required => { + // No extra constraints necessary. + types::RecordField::Demanded(env.pool.add(pat_type)) + } + }; + + field_types.push((*label, field_type)); + + state.vars.push(*var); + } + + let record_type = Type2::Record( + PoolVec::new(field_types.into_iter(), env.pool), + env.pool.add(ext_type), + ); + + let whole_con = Constraint::Eq( + Type2::Variable(*whole_var), + Expected::NoExpectation(record_type), + Category::Storage(std::file!(), std::line!()), + region, + ); + + let record_con = Constraint::Pattern( + region, + PatternCategory::Record, + Type2::Variable(*whole_var), + expected, + ); + + state.constraints.push(whole_con); + state.constraints.push(record_con); + } + GlobalTag { + whole_var, + ext_var, + tag_name: name, + arguments, + } => { + let tag_name = TagName::Global(name.as_str(env.pool).into()); + + constrain_tag_pattern( + arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, + ); + } + PrivateTag { + whole_var, + ext_var, + tag_name: name, + arguments, + } => { + let tag_name = TagName::Private(*name); + + constrain_tag_pattern( + arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, + ); + } + } +} + +#[allow(clippy::too_many_arguments)] +fn constrain_tag_pattern<'a>( + arena: &'a Bump, + env: &mut Env, + region: Region, + expected: PExpected, + state: &mut PatternState2<'a>, + whole_var: Variable, + ext_var: Variable, + arguments: &PoolVec<(Variable, PatternId)>, + tag_name: TagName, +) { + let mut argument_types = Vec::with_capacity(arguments.len()); + + for (index, arg_id) in arguments.iter_node_ids().enumerate() { + let (pattern_var, pattern_id) = env.pool.get(arg_id); + let pattern = env.pool.get(*pattern_id); + + state.vars.push(*pattern_var); + + let pattern_type = Type2::Variable(*pattern_var); + argument_types.push(pattern_type.shallow_clone()); + + let expected = PExpected::ForReason( + PReason::TagArg { + tag_name: tag_name.clone(), + index: Index::zero_based(index), + }, + pattern_type, + region, + ); + + // TODO region should come from pattern + constrain_pattern(arena, env, pattern, region, expected, state); + } + + let whole_con = Constraint::Eq( + Type2::Variable(whole_var), + Expected::NoExpectation(Type2::TagUnion( + PoolVec::new( + vec![( + tag_name.clone(), + PoolVec::new(argument_types.into_iter(), env.pool), + )] + .into_iter(), + env.pool, + ), + env.pool.add(Type2::Variable(ext_var)), + )), + Category::Storage(std::file!(), std::line!()), + region, + ); + + let tag_con = Constraint::Pattern( + region, + PatternCategory::Ctor(tag_name), + Type2::Variable(whole_var), + expected, + ); + + state.vars.push(whole_var); + state.vars.push(ext_var); + state.constraints.push(whole_con); + state.constraints.push(tag_con); +} + +fn constrain_untyped_args<'a>( + arena: &'a Bump, + env: &mut Env, + arguments: &PoolVec<(Variable, PatternId)>, + closure_type: Type2, + return_type: Type2, +) -> (BumpVec<'a, Variable>, PatternState2<'a>, Type2) { + let mut vars = BumpVec::with_capacity_in(arguments.len(), arena); + + let pattern_types = PoolVec::with_capacity(arguments.len() as u32, env.pool); + + let mut pattern_state = PatternState2 { + headers: BumpMap::new_in(arena), + vars: BumpVec::with_capacity_in(1, arena), + constraints: BumpVec::with_capacity_in(1, arena), + }; + + for (arg_node_id, pattern_type_id) in + arguments.iter_node_ids().zip(pattern_types.iter_node_ids()) + { + let (pattern_var, pattern_id) = env.pool.get(arg_node_id); + let pattern = env.pool.get(*pattern_id); + + let pattern_type = Type2::Variable(*pattern_var); + let pattern_expected = PExpected::NoExpectation(pattern_type.shallow_clone()); + + env.pool[pattern_type_id] = pattern_type; + + constrain_pattern( + arena, + env, + pattern, + // TODO needs to come from pattern + Region::zero(), + pattern_expected, + &mut pattern_state, + ); + + vars.push(*pattern_var); + } + + let function_type = Type2::Function( + pattern_types, + env.pool.add(closure_type), + env.pool.add(return_type), + ); + + (vars, pattern_state, function_type) +} + +#[allow(clippy::too_many_arguments)] +fn constrain_closure_size<'a>( + arena: &'a Bump, + env: &mut Env, + name: Symbol, + region: Region, + captured_symbols: &PoolVec<(Symbol, Variable)>, + closure_var: Variable, + closure_ext_var: Variable, + variables: &mut BumpVec<'a, Variable>, +) -> Constraint<'a> { + use Constraint::*; + + debug_assert!(variables.iter().any(|s| *s == closure_var)); + debug_assert!(variables.iter().any(|s| *s == closure_ext_var)); + + let tag_arguments = PoolVec::with_capacity(captured_symbols.len() as u32, env.pool); + let mut captured_symbols_constraints = BumpVec::with_capacity_in(captured_symbols.len(), arena); + + for (captured_symbol_id, tag_arg_id) in captured_symbols + .iter_node_ids() + .zip(tag_arguments.iter_node_ids()) + { + let (symbol, var) = env.pool.get(captured_symbol_id); + + // make sure the variable is registered + variables.push(*var); + + let tag_arg_type = Type2::Variable(*var); + + // this symbol is captured, so it must be part of the closure type + env.pool[tag_arg_id] = tag_arg_type.shallow_clone(); + + // make the variable equal to the looked-up type of symbol + captured_symbols_constraints.push(Lookup( + *symbol, + Expected::NoExpectation(tag_arg_type), + Region::zero(), + )); + } + + let tag_name = TagName::Closure(name); + let closure_type = Type2::TagUnion( + PoolVec::new(vec![(tag_name, tag_arguments)].into_iter(), env.pool), + env.pool.add(Type2::Variable(closure_ext_var)), + ); + + let finalizer = Eq( + Type2::Variable(closure_var), + Expected::NoExpectation(closure_type), + Category::ClosureSize, + region, + ); + + captured_symbols_constraints.push(finalizer); + + And(captured_symbols_constraints) +} + +#[inline(always)] +fn builtin_type(symbol: Symbol, args: PoolVec) -> Type2 { + Type2::Apply(symbol, args) +} + +#[inline(always)] +fn str_type(pool: &mut Pool) -> Type2 { + builtin_type(Symbol::STR_STR, PoolVec::empty(pool)) +} + +#[inline(always)] +fn empty_list_type(pool: &mut Pool, var: Variable) -> Type2 { + list_type(pool, Type2::Variable(var)) +} + +#[inline(always)] +fn list_type(pool: &mut Pool, typ: Type2) -> Type2 { + builtin_type(Symbol::LIST_LIST, PoolVec::new(vec![typ].into_iter(), pool)) +} + +#[inline(always)] +fn num_float(pool: &mut Pool, range: TypeId) -> Type2 { + let num_floatingpoint_type = num_floatingpoint(pool, range); + let num_floatingpoint_id = pool.add(num_floatingpoint_type); + + let num_num_type = num_num(pool, num_floatingpoint_id); + let num_num_id = pool.add(num_num_type); + + Type2::Alias( + Symbol::NUM_FLOAT, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + num_num_id, + ) +} + +#[inline(always)] +fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 { + let range_type = pool.get(range); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_FLOATINGPOINT, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + pool.add(alias_content), + ) +} + +#[inline(always)] +fn num_int(pool: &mut Pool, range: TypeId) -> Type2 { + let num_integer_type = _num_integer(pool, range); + let num_integer_id = pool.add(num_integer_type); + + let num_num_type = num_num(pool, num_integer_id); + let num_num_id = pool.add(num_num_type); + + Type2::Alias( + Symbol::NUM_INT, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + num_num_id, + ) +} + +#[inline(always)] +fn _num_signed64(pool: &mut Pool) -> Type2 { + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_SIGNED64), + PoolVec::empty(pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_SIGNED64, + PoolVec::empty(pool), + pool.add(alias_content), + ) +} + +#[inline(always)] +fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { + let range_type = pool.get(range); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_INTEGER), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_INTEGER, + PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), + pool.add(alias_content), + ) +} + +#[inline(always)] +fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { + let range_type = pool.get(type_id); + + let alias_content = Type2::TagUnion( + PoolVec::new( + vec![( + TagName::Private(Symbol::NUM_AT_NUM), + PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), + )] + .into_iter(), + pool, + ), + pool.add(Type2::EmptyTagUnion), + ); + + Type2::Alias( + Symbol::NUM_NUM, + PoolVec::new( + vec![(PoolStr::new("range", pool), type_id)].into_iter(), + pool, + ), + pool.add(alias_content), + ) +} diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs new file mode 100644 index 0000000000..2cf25730b9 --- /dev/null +++ b/ast/src/lang/core/ast.rs @@ -0,0 +1,31 @@ +use crate::ast_error::{ASTResult, ASTNodeIdWithoutExprId}; + +use super::{def::def2::DefId, expr::expr2::ExprId, header::AppHeader}; + +#[derive(Debug)] +pub struct AST { + pub header: AppHeader, + pub def_ids: Vec, +} + +#[derive(Debug, PartialEq, Copy, Clone)] +pub enum ASTNodeId { + ADefId(DefId), + AExprId(ExprId), +} + +impl ASTNodeId { + pub fn to_expr_id(&self) -> ASTResult { + match self { + ASTNodeId::AExprId(expr_id) => Ok(*expr_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } + + pub fn to_def_id(&self) -> ASTResult { + match self { + ASTNodeId::ADefId(def_id) => Ok(*def_id), + _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs new file mode 100644 index 0000000000..02c9e94ff9 --- /dev/null +++ b/ast/src/lang/core/def/def.rs @@ -0,0 +1,1437 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] +// use crate::annotation::canonicalize_annotation; +// use crate::annotation::IntroducedVariables; +// use crate::env::Env; +// use crate::expr::Expr::{self, *}; +// use crate::expr::{ +// canonicalize_expr, local_successors, references_from_call, references_from_local, Output, +// Recursive, +// }; +// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; +// use crate::procedure::References; +use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap}; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_parse::ast; +use roc_parse::pattern::PatternType; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore, Variable}; +use std::collections::HashMap; +use std::fmt::Debug; +use ven_graph::{strongly_connected_components, topological_sort_into_groups}; + +use crate::{lang::{core::{expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output}, fun_def::FunctionDef, pattern::{self, Pattern2, PatternId, symbols_from_pattern, to_pattern_id}, types::{Alias, Annotation2, Signature, Type2, TypeId, to_annotation2}, val_def::ValueDef}, env::Env, rigids::Rigids, scope::Scope}, pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}}; + +#[derive(Debug)] +pub enum Def { + AnnotationOnly { rigids: Rigids, annotation: TypeId }, + Value(ValueDef), + Function(FunctionDef), +} + +impl Def { + pub fn symbols(&self, pool: &Pool) -> MutSet { + let mut output = MutSet::default(); + + match self { + Def::AnnotationOnly { .. } => todo!("lost pattern information here ... "), + Def::Value(value_def) => match value_def { + ValueDef::WithAnnotation { pattern_id, .. } + | ValueDef::NoAnnotation { pattern_id, .. } => { + let pattern2 = &pool[*pattern_id]; + output.extend(symbols_from_pattern(pool, pattern2)); + } + }, + Def::Function(function_def) => match function_def { + FunctionDef::NoAnnotation { name, .. } + | FunctionDef::WithAnnotation { name, .. } => { + output.insert(*name); + } + }, + } + + output + } +} + +impl ShallowClone for Def { + fn shallow_clone(&self) -> Self { + match self { + Self::AnnotationOnly { rigids, annotation } => Self::AnnotationOnly { + rigids: rigids.shallow_clone(), + annotation: *annotation, + }, + Self::Value(def) => Self::Value(def.shallow_clone()), + Self::Function(def) => Self::Function(def.shallow_clone()), + } + } +} + +/// A Def that has had patterns and type annnotations canonicalized, +/// but no Expr canonicalization has happened yet. Also, it has had spaces +/// and nesting resolved, and knows whether annotations are standalone or not. +#[derive(Debug)] +pub enum PendingDef<'a> { + /// A standalone annotation with no body + AnnotationOnly( + &'a Located>, + PatternId, + &'a Located>, + ), + /// A body with no type annotation + Body( + &'a Located>, + PatternId, + &'a Located>, + ), + /// A body with a type annotation + TypedBody( + &'a Located>, + PatternId, + &'a Located>, + &'a Located>, + ), + + /// A type alias, e.g. `Ints : List Int` + Alias { + name: Located, + vars: Vec>, + ann: &'a Located>, + }, + + /// An invalid alias, that is ignored in the rest of the pipeline + /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` + /// with an incorrect pattern + InvalidAlias, +} + +fn to_pending_def<'a>( + env: &mut Env<'a>, + def: &'a ast::Def<'a>, + scope: &mut Scope, + pattern_type: PatternType, +) -> Option<(Output, PendingDef<'a>)> { + use roc_parse::ast::Def::*; + + match def { + Annotation(loc_pattern, loc_ann) => { + // This takes care of checking for shadowing and adding idents to scope. + let (output, loc_can_pattern) = pattern::to_pattern_id( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + Some(( + output, + PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), + )) + } + Body(loc_pattern, loc_expr) => { + // This takes care of checking for shadowing and adding idents to scope. + let (output, loc_can_pattern) = pattern::to_pattern_id( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + Some(( + output, + PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr), + )) + } + + AnnotatedBody { + ann_pattern, + ann_type, + comment: _, + body_pattern, + body_expr, + } => { + if ann_pattern.value.equivalent(&body_pattern.value) { + // NOTE: Pick the body pattern, picking the annotation one is + // incorrect in the presence of optional record fields! + // + // { x, y } : { x : Int, y ? Bool }* + // { x, y ? False } = rec + Some(pending_typed_body( + env, + body_pattern, + ann_type, + body_expr, + scope, + pattern_type, + )) + } else { + // the pattern of the annotation does not match the pattern of the body direc + env.problem(Problem::SignatureDefMismatch { + annotation_pattern: ann_pattern.region, + def_pattern: body_pattern.region, + }); + + // TODO: Should we instead build some PendingDef::InvalidAnnotatedBody ? This would + // remove the `Option` on this function (and be probably more reliable for further + // problem/error reporting) + None + } + } + + roc_parse::ast::Def::Alias { name, vars, ann } => { + let region = Region::span_across(&name.region, &ann.region); + + match scope.introduce( + name.value.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); + + for loc_var in vars.iter() { + match loc_var.value { + ast::Pattern::Identifier(name) + if name.chars().next().unwrap().is_lowercase() => + { + let lowercase = Lowercase::from(name); + can_rigids.push(Located { + value: lowercase, + region: loc_var.region, + }); + } + _ => { + // any other pattern in this position is a syntax error. + env.problem(Problem::InvalidAliasRigid { + alias_name: symbol, + region: loc_var.region, + }); + + return Some((Output::default(), PendingDef::InvalidAlias)); + } + } + } + + Some(( + Output::default(), + PendingDef::Alias { + name: Located { + region: name.region, + value: symbol, + }, + vars: can_rigids, + ann, + }, + )) + } + + Err((original_region, loc_shadowed_symbol)) => { + env.problem(Problem::ShadowingInAnnotation { + original_region, + shadow: loc_shadowed_symbol, + }); + + Some((Output::default(), PendingDef::InvalidAlias)) + } + } + } + + Expect(_) => todo!(), + + SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { + to_pending_def(env, sub_def, scope, pattern_type) + } + + NotYetImplemented(s) => todo!("{}", s), + } +} + +fn pending_typed_body<'a>( + env: &mut Env<'a>, + loc_pattern: &'a Located>, + loc_ann: &'a Located>, + loc_expr: &'a Located>, + scope: &mut Scope, + pattern_type: PatternType, +) -> (Output, PendingDef<'a>) { + // This takes care of checking for shadowing and adding idents to scope. + let (output, loc_can_pattern) = to_pattern_id( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + ( + output, + PendingDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr), + ) +} + +fn from_pending_alias<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + name: Located, + vars: Vec>, + ann: &'a Located>, + mut output: Output, +) -> Output { + let symbol = name.value; + + match to_annotation2(env, scope, &ann.value, ann.region) { + Annotation2::Erroneous(_) => todo!(), + Annotation2::Annotation { + named_rigids, + unnamed_rigids, + symbols, + signature, + } => { + // Record all the annotation's references in output.references.lookups + + for symbol in symbols { + output.references.lookups.insert(symbol); + output.references.referenced_aliases.insert(symbol); + } + + for loc_lowercase in vars { + if !named_rigids.contains_key(loc_lowercase.value.as_str()) { + env.problem(Problem::PhantomTypeArgument { + alias: symbol, + variable_region: loc_lowercase.region, + variable_name: loc_lowercase.value.clone(), + }); + } + } + + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); + + let annotation = match signature { + Signature::Value { annotation } => annotation, + Signature::Function { + arguments, + closure_type_id, + return_type_id, + } => Type2::Function(arguments, closure_type_id, return_type_id), + Signature::FunctionWithAliases { annotation, .. } => annotation, + }; + + if annotation.contains_symbol(env.pool, symbol) { + // the alias is recursive. If it's a tag union, we attempt to fix this + if let Type2::TagUnion(tags, ext) = annotation { + // re-canonicalize the alias with the alias already in scope + let rec_var = env.var_store.fresh(); + let rec_type_union = Type2::RecursiveTagUnion(rec_var, tags, ext); + + // NOTE this only looks at the symbol, and just assumes that the + // recursion is not polymorphic + rec_type_union.substitute_alias(env.pool, symbol, Type2::Variable(rec_var)); + + let annotation_id = env.add(rec_type_union, ann.region); + let named = rigids.named(env.pool); + + scope.add_alias(env.pool, symbol, named, annotation_id); + } else { + env.problem(Problem::CyclicAlias(symbol, name.region, vec![])); + return output; + } + } else { + let annotation_id = env.add(annotation, ann.region); + let named = rigids.named(env.pool); + + scope.add_alias(env.pool, symbol, named, annotation_id); + } + + output + } + } +} + +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +#[allow(clippy::cognitive_complexity)] +fn canonicalize_pending_def<'a>( + env: &mut Env<'a>, + pending_def: PendingDef<'a>, + mut output: Output, + scope: &mut Scope, + can_defs_by_symbol: &mut MutMap, + refs_by_symbol: &mut MutMap, + aliases: &mut MutMap, +) -> Output { + use PendingDef::*; + + // Make types for the body expr, even if we won't end up having a body. + let expr_var = env.var_store.fresh(); + + match pending_def { + AnnotationOnly(_, loc_can_pattern, loc_ann) => { + // annotation sans body cannot introduce new rigids that are visible in other annotations + // but the rigids can show up in type error messages, so still register them + + match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { + Annotation2::Erroneous(_) => todo!(), + Annotation2::Annotation { + named_rigids, + unnamed_rigids, + symbols, + signature, + } => { + // Record all the annotation's references in output.references.lookups + + for symbol in symbols { + output.references.lookups.insert(symbol); + output.references.referenced_aliases.insert(symbol); + } + + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); + + let annotation = match signature { + Signature::Value { annotation } => annotation, + Signature::Function { + arguments, + closure_type_id, + return_type_id, + } => Type2::Function(arguments, closure_type_id, return_type_id), + Signature::FunctionWithAliases { annotation, .. } => annotation, + }; + let annotation = env.add(annotation, loc_ann.region); + + let def = Def::AnnotationOnly { rigids, annotation }; + + for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { + can_defs_by_symbol.insert(symbol, def.shallow_clone()); + } + + output + } + } + } + + PendingDef::Alias { name, ann, vars } => { + from_pending_alias(env, scope, name, vars, ann, output) + } + + InvalidAlias => { + // invalid aliases (shadowed, incorrect patterns ) + todo!() + } + + TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { + match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { + Annotation2::Erroneous(_) => todo!(), + Annotation2::Annotation { + named_rigids, + unnamed_rigids, + symbols, + signature, + } => { + // Record all the annotation's references in output.references.lookups + + for symbol in symbols { + output.references.lookups.insert(symbol); + output.references.referenced_aliases.insert(symbol); + } + + let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); + + // bookkeeping for tail-call detection. If we're assigning to an + // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. + let outer_identifier = env.tailcallable_symbol; + + if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { + env.tailcallable_symbol = Some(*defined_symbol); + }; + + // regiser the name of this closure, to make sure the closure won't capture it's own name + if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = + (&env.pool[loc_can_pattern], &loc_expr.value) + { + env.closure_name_symbol = Some(*defined_symbol); + }; + + let (loc_can_expr, can_output) = + to_expr2(env, scope, &loc_expr.value, loc_expr.region); + + output.references.union_mut(can_output.references.clone()); + + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_identifier; + + // First, make sure we are actually assigning an identifier instead of (for example) a tag. + // + // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, + // which also implies it's not a self tail call! + // + // Only defs of the form (foo = ...) can be closure declarations or self tail calls. + match loc_can_expr { + Expr2::Closure { + args: closure_args, + body, + extra, + name: closure_symbol, + .. + } => { + let symbol = match env.pool[loc_can_pattern] { + Pattern2::Identifier(ref s) => *s, + _ => todo!( + r"this is an error; functions must be bound with an identifier pattern!" + ), + }; + + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. + let references = + env.closures.remove(&closure_symbol).unwrap_or_else(|| { + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + }); + + // TODO should we re-insert this function into env.closures? + env.closures.insert(symbol, references); + + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.lookups.remove(&symbol); + }); + + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + let refs = References::new(); + + let arguments: PoolVec<(PatternId, Type2)> = + PoolVec::with_capacity(closure_args.len() as u32, env.pool); + + let return_type: TypeId; + + let annotation = match signature { + Signature::Value { .. } => { + todo!("type annotation says 0 arguments, but it's a function") + } + Signature::Function { + arguments: type_arguments, + closure_type_id, + return_type_id, + } + | Signature::FunctionWithAliases { + arguments: type_arguments, + closure_type_id, + return_type_id, + .. + } => { + if arguments.len() != type_arguments.len() { + panic!("argument number mismatch"); + } + + let it: Vec<_> = closure_args + .iter(env.pool) + .map(|(x, y)| (*x, *y)) + .zip( + type_arguments + .iter(env.pool) + .map(|t| t.shallow_clone()), + ) + .collect(); + + for (node_id, ((_, pattern_id), typ)) in + arguments.iter_node_ids().zip(it.into_iter()) + { + env.pool[node_id] = (pattern_id, typ); + } + + return_type = return_type_id; + } + }; + + let function_def = FunctionDef::WithAnnotation { + name: symbol, + arguments, + rigids: env.pool.add(rigids), + return_type, + body, + }; + + let def = Def::Function(function_def); + + can_defs_by_symbol.insert(symbol, def); + + output + } + + _ => { + let annotation = match signature { + Signature::Value { annotation } => annotation, + Signature::Function { + arguments, + closure_type_id, + return_type_id, + } => Type2::Function(arguments, closure_type_id, return_type_id), + Signature::FunctionWithAliases { annotation, .. } => annotation, + }; + let annotation = env.add(annotation, loc_ann.region); + + let value_def = ValueDef::WithAnnotation { + pattern_id: loc_can_pattern, + expr_id: env.pool.add(loc_can_expr), + type_id: annotation, + rigids: rigids, + expr_var: env.var_store.fresh(), + }; + + let def = Def::Value(value_def); + + for symbol in + symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) + { + can_defs_by_symbol.insert(symbol, def.shallow_clone()); + } + + for (_, (symbol, region)) in scope.idents() { + // if !vars_by_symbol.contains_key(&symbol) { + // continue; + // } + let refs = can_output.references.clone(); + refs_by_symbol.insert(*symbol, (*region, refs)); + } + + output + } + } + } + } + } + + Body(loc_pattern, loc_can_pattern, loc_expr) => { + // bookkeeping for tail-call detection. If we're assigning to an + // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. + let outer_identifier = env.tailcallable_symbol; + + if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { + env.tailcallable_symbol = Some(*defined_symbol); + }; + + // regiser the name of this closure, to make sure the closure won't capture it's own name + if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = + (&env.pool[loc_can_pattern], &loc_expr.value) + { + env.closure_name_symbol = Some(*defined_symbol); + }; + + let (loc_can_expr, can_output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); + + output.references.union_mut(can_output.references.clone()); + + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_identifier; + + // First, make sure we are actually assigning an identifier instead of (for example) a tag. + // + // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, + // which also implies it's not a self tail call! + // + // Only defs of the form (foo = ...) can be closure declarations or self tail calls. + match loc_can_expr { + Expr2::Closure { + args: closure_args, + body, + extra, + name: closure_symbol, + .. + } => { + let symbol = match env.pool[loc_can_pattern] { + Pattern2::Identifier(ref s) => *s, + _ => todo!( + r"this is an error; functions must be bound with an identifier pattern!" + ), + }; + + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. + let references = + env.closures.remove(&closure_symbol).unwrap_or_else(|| { + panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) + }); + + // TODO should we re-insert this function into env.closures? + env.closures.insert(symbol, references); + + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.lookups.remove(&symbol); + }); + + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + let refs = References::new(); + + let arguments: PoolVec<(PatternId, Variable)> = + PoolVec::with_capacity(closure_args.len() as u32, env.pool); + + let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect(); + + for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter()) + { + env.pool[node_id] = (pattern_id, env.var_store.fresh()); + } + + let function_def = FunctionDef::NoAnnotation { + name: symbol, + arguments, + return_var: env.var_store.fresh(), + body, + }; + + let def = Def::Function(function_def); + + can_defs_by_symbol.insert(symbol, def); + + output + } + + _ => { + let value_def = ValueDef::NoAnnotation { + pattern_id: loc_can_pattern, + expr_id: env.pool.add(loc_can_expr), + expr_var: env.var_store.fresh(), + }; + + let def = Def::Value(value_def); + + for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { + can_defs_by_symbol.insert(symbol, def.shallow_clone()); + } + + for (_, (symbol, region)) in scope.idents() { + // if !vars_by_symbol.contains_key(&symbol) { + // continue; + // } + let refs = can_output.references.clone(); + refs_by_symbol.insert(*symbol, (*region, refs)); + } + + output + } + } + } + } +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct References { + pub bound_symbols: MutSet, + pub lookups: MutSet, + pub referenced_aliases: MutSet, + pub calls: MutSet, +} + +impl References { + pub fn new() -> References { + Self::default() + } + + pub fn union_mut(&mut self, other: References) { + self.lookups.extend(other.lookups); + self.calls.extend(other.calls); + self.bound_symbols.extend(other.bound_symbols); + self.referenced_aliases.extend(other.referenced_aliases); + } + + pub fn has_lookup(&self, symbol: Symbol) -> bool { + self.lookups.contains(&symbol) + } +} + +#[derive(Debug)] +pub struct CanDefs { + pub refs_by_symbol: MutMap, + pub can_defs_by_symbol: MutMap, + pub aliases: MutMap, +} + +#[inline(always)] +pub fn canonicalize_defs<'a>( + env: &mut Env<'a>, + mut output: Output, + original_scope: &Scope, + loc_defs: &'a [&'a Located>], + pattern_type: PatternType, +) -> (CanDefs, Scope, Output, MutMap) { + // Canonicalizing defs while detecting shadowing involves a multi-step process: + // + // 1. Go through each of the patterns. + // 2. For each identifier pattern, get the scope.symbol() for the ident. (That symbol will use the home module for its module.) + // 3. If that symbol is already in scope, then we're about to shadow it. Error! + // 4. Otherwise, add it to the scope immediately, so we can detect shadowing within the same + // pattern (e.g. (Foo a a) = ...) + // 5. Add this canonicalized pattern and its corresponding ast::Expr to pending_exprs. + // 5. Once every pattern has been processed and added to scope, go back and canonicalize the exprs from + // pending_exprs, this time building up a canonical def for each one. + // + // This way, whenever any expr is doing lookups, it knows everything that's in scope - + // even defs that appear after it in the source. + // + // This naturally handles recursion too, because a given expr which refers + // to itself won't be processed until after its def has been added to scope. + + // Record both the original and final idents from the scope, + // so we can diff them while detecting unused defs. + let mut scope = original_scope.shallow_clone(); + let num_defs = loc_defs.len(); + let mut refs_by_symbol = MutMap::default(); + let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); + let mut pending = Vec::with_capacity(num_defs); // TODO bump allocate this! + + // Canonicalize all the patterns, record shadowing problems, and store + // the ast::Expr values in pending_exprs for further canonicalization + // once we've finished assembling the entire scope. + for loc_def in loc_defs { + match to_pending_def(env, &loc_def.value, &mut scope, pattern_type) { + None => (), + Some((new_output, pending_def)) => { + // store the top-level defs, used to ensure that closures won't capture them + if let PatternType::TopLevelDef = pattern_type { + match &pending_def { + PendingDef::AnnotationOnly(_, loc_can_pattern, _) + | PendingDef::Body(_, loc_can_pattern, _) + | PendingDef::TypedBody(_, loc_can_pattern, _, _) => { + env.top_level_symbols.extend(symbols_from_pattern( + env.pool, + env.pool.get(*loc_can_pattern), + )) + } + PendingDef::Alias { .. } | PendingDef::InvalidAlias => {} + } + } + // Record the ast::Expr for later. We'll do another pass through these + // once we have the entire scope assembled. If we were to canonicalize + // the exprs right now, they wouldn't have symbols in scope from defs + // that get would have gotten added later in the defs list! + pending.push(pending_def); + output.union(new_output); + } + } + } + + if cfg!(debug_assertions) { + env.home.register_debug_idents(&env.ident_ids); + } + + // TODO what to do here? aliases are already in the scope! + let mut aliases = MutMap::default(); + let mut value_defs = Vec::new(); + + for pending_def in pending.into_iter() { + match pending_def { + PendingDef::Alias { name, vars, ann } => { + output = from_pending_alias(env, &mut scope, name, vars, ann, output); + } + other => value_defs.push(other), + } + } + + // TODO + // correct_mutual_recursive_type_alias(env, &mut aliases, var_store); + + // Now that we have the scope completely assembled, and shadowing resolved, + // we're ready to canonicalize any body exprs. + for pending_def in value_defs.into_iter() { + output = canonicalize_pending_def( + env, + pending_def, + output, + &mut scope, + &mut can_defs_by_symbol, + &mut refs_by_symbol, + &mut aliases, + ); + + // TODO we should do something with these references; they include + // things like type annotations. + } + + // Determine which idents we introduced in the course of this process. + let mut symbols_introduced = MutMap::default(); + + for (symbol, region) in scope.symbols() { + if !original_scope.contains_symbol(symbol) { + symbols_introduced.insert(symbol, region); + } + } + + // This returns both the defs info as well as the new scope. + // + // We have to return the new scope because we added defs to it + // (and those lookups shouldn't fail later, e.g. when canonicalizing + // the return expr), but we didn't want to mutate the original scope + // directly because we wanted to keep a clone of it around to diff + // when looking for unused idents. + // + // We have to return the scope separately from the defs, because the + // defs need to get moved later. + ( + CanDefs { + refs_by_symbol, + can_defs_by_symbol, + aliases, + }, + scope, + output, + symbols_introduced, + ) +} + +// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum Declaration { + Declare(Def), + DeclareRec(Vec), + Builtin(Def), + InvalidCycle(Vec, Vec<(Region /* pattern */, Region /* expr */)>), +} + +impl Declaration { + pub fn def_count(&self) -> usize { + use Declaration::*; + match self { + Declare(_) => 1, + DeclareRec(defs) => defs.len(), + InvalidCycle(_, _) => 0, + Builtin(_) => 0, + } + } +} + +#[inline(always)] +pub fn sort_can_defs( + env: &mut Env<'_>, + defs: CanDefs, + mut output: Output, +) -> (Result, RuntimeError>, Output) { + let CanDefs { + refs_by_symbol, + can_defs_by_symbol, + aliases, + } = defs; + + // for (symbol, alias) in aliases.into_iter() { + // output.aliases.insert(symbol, alias); + // } + + // Determine the full set of references by traversing the graph. + let mut visited_symbols = MutSet::default(); + let returned_lookups = MutSet::clone(&output.references.lookups); + + // Start with the return expression's referenced locals. They're the only ones that count! + // + // If I have two defs which reference each other, but neither of them is referenced + // in the return expression, I don't want either of them (or their references) to end up + // in the final output.references. They were unused, and so were their references! + // + // The reason we need a graph here is so we don't overlook transitive dependencies. + // For example, if I have `a = b + 1` and the def returns `a + 1`, then the + // def as a whole references both `a` *and* `b`, even though it doesn't + // directly mention `b` - because `a` depends on `b`. If we didn't traverse a graph here, + // we'd erroneously give a warning that `b` was unused since it wasn't directly referenced. + for symbol in returned_lookups.into_iter() { + // We only care about local symbols in this analysis. + if symbol.module_id() == env.home { + // Traverse the graph and look up *all* the references for this local symbol. + let refs = + references_from_local(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); + + output.references.union_mut(refs); + } + } + + for symbol in output.references.calls.clone() { + // Traverse the graph and look up *all* the references for this call. + // Reuse the same visited_symbols as before; if we already visited it, + // we won't learn anything new from visiting it again! + let refs = + references_from_call(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); + + output.references.union_mut(refs); + } + + let mut defined_symbols: Vec = Vec::new(); + let mut defined_symbols_set: MutSet = MutSet::default(); + + for symbol in can_defs_by_symbol.keys().into_iter() { + defined_symbols.push(*symbol); + defined_symbols_set.insert(*symbol); + } + + // Use topological sort to reorder the defs based on their dependencies to one another. + // This way, during code gen, no def will refer to a value that hasn't been initialized yet. + // As a bonus, the topological sort also reveals any cycles between the defs, allowing + // us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually + // recursive definitions. + + // All successors that occur in the body of a symbol. + let all_successors_without_self = |symbol: &Symbol| -> MutSet { + // This may not be in refs_by_symbol. For example, the `f` in `f x` here: + // + // f = \z -> z + // + // (\x -> + // a = f x + // x + // ) + // + // It's not part of the current defs (the one with `a = f x`); rather, + // it's in the enclosing scope. It's still referenced though, so successors + // will receive it as an argument! + match refs_by_symbol.get(symbol) { + Some((_, references)) => { + // We can only sort the symbols at the current level. That is safe because + // symbols defined at higher levels cannot refer to symbols at lower levels. + // Therefore they can never form a cycle! + // + // In the above example, `f` cannot reference `a`, and in the closure + // a call to `f` cannot cycle back to `a`. + let mut loc_succ = local_successors(&references, &env.closures); + + // if the current symbol is a closure, peek into its body + if let Some(References { lookups, .. }) = env.closures.get(symbol) { + let home = env.home; + + for lookup in lookups { + if lookup != symbol && lookup.module_id() == home { + // DO NOT register a self-call behind a lambda! + // + // We allow `boom = \_ -> boom {}`, but not `x = x` + loc_succ.insert(*lookup); + } + } + } + + // remove anything that is not defined in the current block + loc_succ.retain(|key| defined_symbols_set.contains(key)); + + loc_succ + } + None => MutSet::default(), + } + }; + + // All successors that occur in the body of a symbol, including the symbol itself + // This is required to determine whether a symbol is recursive. Recursive symbols + // (that are not faulty) always need a DeclareRec, even if there is just one symbol in the + // group + let mut all_successors_with_self = |symbol: &Symbol| -> MutSet { + // This may not be in refs_by_symbol. For example, the `f` in `f x` here: + // + // f = \z -> z + // + // (\x -> + // a = f x + // x + // ) + // + // It's not part of the current defs (the one with `a = f x`); rather, + // it's in the enclosing scope. It's still referenced though, so successors + // will receive it as an argument! + match refs_by_symbol.get(symbol) { + Some((_, references)) => { + // We can only sort the symbols at the current level. That is safe because + // symbols defined at higher levels cannot refer to symbols at lower levels. + // Therefore they can never form a cycle! + // + // In the above example, `f` cannot reference `a`, and in the closure + // a call to `f` cannot cycle back to `a`. + let mut loc_succ = local_successors(&references, &env.closures); + + // if the current symbol is a closure, peek into its body + if let Some(References { lookups, .. }) = env.closures.get(symbol) { + for lookup in lookups { + loc_succ.insert(*lookup); + } + } + + // remove anything that is not defined in the current block + loc_succ.retain(|key| defined_symbols_set.contains(key)); + + loc_succ + } + None => MutSet::default(), + } + }; + + // If a symbol is a direct successor of itself, there is an invalid cycle. + // The difference with the function above is that this one does not look behind lambdas, + // but does consider direct self-recursion. + let direct_successors = |symbol: &Symbol| -> MutSet { + match refs_by_symbol.get(symbol) { + Some((_, references)) => { + let mut loc_succ = local_successors(&references, &env.closures); + + // NOTE: if the symbol is a closure we DONT look into its body + + // remove anything that is not defined in the current block + loc_succ.retain(|key| defined_symbols_set.contains(key)); + + // NOTE: direct recursion does matter here: `x = x` is invalid recursion! + + loc_succ + } + None => MutSet::default(), + } + }; + + // TODO also do the same `addDirects` check elm/compiler does, so we can + // report an error if a recursive definition can't possibly terminate! + match ven_graph::topological_sort_into_groups( + defined_symbols.as_slice(), + all_successors_without_self, + ) { + Ok(groups) => { + let mut declarations = Vec::new(); + + // groups are in reversed order + let mut can_defs_by_symbol = can_defs_by_symbol; + let cdbs = &mut can_defs_by_symbol; + for group in groups.into_iter().rev() { + group_to_declaration( + &group, + &env.closures, + &mut all_successors_with_self, + cdbs, + &mut declarations, + ); + } + + (Ok(declarations), output) + } + Err((mut groups, nodes_in_cycle)) => { + let mut declarations = Vec::new(); + let problems = Vec::new(); + + // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, + // and in general it's impossible to decide whether it is. So we use a crude heuristic: + // + // Definitions where the cycle occurs behind a lambda are OK + // + // boom = \_ -> boom {} + // + // But otherwise we report an error, e.g. + // + // foo = if b then foo else bar + + for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) + { + // check whether the cycle is faulty, which is when it has + // a direct successor in the current cycle. This catches things like: + // + // x = x + // + // or + // + // p = q + // q = p + let is_invalid_cycle = match cycle.get(0) { + Some(symbol) => { + let mut succs = direct_successors(symbol); + + succs.retain(|key| cycle.contains(key)); + + !succs.is_empty() + } + None => false, + }; + + if is_invalid_cycle { + // We want to show the entire cycle in the error message, so expand it out. + let mut loc_symbols = Vec::new(); + + for symbol in cycle { + match refs_by_symbol.get(&symbol) { + None => unreachable!( + r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, + symbol, refs_by_symbol + ), + Some((region, _)) => { + loc_symbols.push(Located::at(*region, symbol)); + } + } + } + + // TODO we don't store those regions any more! + // let regions = Vec::with_capacity(can_defs_by_symbol.len()); + // for def in can_defs_by_symbol.values() { + // regions.push((def.loc_pattern.region, def.loc_expr.region)); + // } + // + // // Sort them by line number to make the report more helpful. + // loc_symbols.sort(); + // regions.sort(); + + // let symbols_in_cycle: Vec = + // loc_symbols.into_iter().map(|s| s.value).collect(); + // + // problems.push(Problem::RuntimeError(RuntimeError::CircularDef( + // symbols_in_cycle.clone(), + // regions.clone(), + // ))); + // + // declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions)); + panic!("Invalid Cycle"); + } else { + // slightly inefficient, because we know this becomes exactly one DeclareRec already + groups.push(cycle); + } + } + + // now we have a collection of groups whose dependencies are not cyclic. + // They are however not yet topologically sorted. Here we have to get a bit + // creative to get all the definitions in the correct sorted order. + + let mut group_ids = Vec::with_capacity(groups.len()); + let mut symbol_to_group_index = MutMap::default(); + for (i, group) in groups.iter().enumerate() { + for symbol in group { + symbol_to_group_index.insert(*symbol, i); + } + + group_ids.push(i); + } + + let successors_of_group = |group_id: &usize| { + let mut result = MutSet::default(); + + // for each symbol in this group + for symbol in &groups[*group_id] { + // find its successors + for succ in all_successors_without_self(symbol) { + // and add its group to the result + result.insert(symbol_to_group_index[&succ]); + } + } + + // don't introduce any cycles to self + result.remove(group_id); + + result + }; + + match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { + Ok(sorted_group_ids) => { + let mut can_defs_by_symbol = can_defs_by_symbol; + let cdbs = &mut can_defs_by_symbol; + for sorted_group in sorted_group_ids.iter().rev() { + for group_id in sorted_group.iter().rev() { + let group = &groups[*group_id]; + + group_to_declaration( + group, + &env.closures, + &mut all_successors_with_self, + cdbs, + &mut declarations, + ); + } + } + } + Err(_) => unreachable!("there should be no cycles now!"), + } + + for problem in problems { + env.problem(problem); + } + + (Ok(declarations), output) + } + } +} + +pub fn references_from_local<'a, T>( + defined_symbol: Symbol, + visited: &'a mut MutSet, + refs_by_def: &'a MutMap, + closures: &'a MutMap, +) -> References +where + T: Debug, +{ + let mut answer: References = References::new(); + + match refs_by_def.get(&defined_symbol) { + Some((_, refs)) => { + visited.insert(defined_symbol); + + for local in refs.lookups.iter() { + if !visited.contains(&local) { + let other_refs: References = + references_from_local(*local, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.lookups.insert(*local); + } + + for call in refs.calls.iter() { + if !visited.contains(&call) { + let other_refs = references_from_call(*call, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.calls.insert(*call); + } + + answer + } + None => answer, + } +} + +pub fn references_from_call<'a, T>( + call_symbol: Symbol, + visited: &'a mut MutSet, + refs_by_def: &'a MutMap, + closures: &'a MutMap, +) -> References +where + T: Debug, +{ + match closures.get(&call_symbol) { + Some(references) => { + let mut answer = references.clone(); + + visited.insert(call_symbol); + + for closed_over_local in references.lookups.iter() { + if !visited.contains(&closed_over_local) { + let other_refs = + references_from_local(*closed_over_local, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.lookups.insert(*closed_over_local); + } + + for call in references.calls.iter() { + if !visited.contains(&call) { + let other_refs = references_from_call(*call, visited, refs_by_def, closures); + + answer.union_mut(other_refs); + } + + answer.calls.insert(*call); + } + + answer + } + None => { + // If the call symbol was not in the closure map, that means we're calling a non-function and + // will get a type mismatch later. For now, assume no references as a result of the "call." + References::new() + } + } +} + +fn local_successors( + references: &References, + closures: &MutMap, +) -> MutSet { + let mut answer = references.lookups.clone(); + + for call_symbol in references.calls.iter() { + answer.extend(call_successors(*call_symbol, closures)); + } + + answer +} + +fn call_successors<'a>( + call_symbol: Symbol, + closures: &'a MutMap, +) -> MutSet { + let mut answer = MutSet::default(); + let mut seen = MutSet::default(); + let mut queue = vec![call_symbol]; + + while let Some(symbol) = queue.pop() { + if seen.contains(&symbol) { + continue; + } + + if let Some(references) = closures.get(&symbol) { + answer.extend(references.lookups.iter().copied()); + queue.extend(references.calls.iter().copied()); + + seen.insert(symbol); + } + } + + answer +} + +fn group_to_declaration( + group: &[Symbol], + closures: &MutMap, + successors: &mut dyn FnMut(&Symbol) -> MutSet, + can_defs_by_symbol: &mut MutMap, + declarations: &mut Vec, +) { + use Declaration::*; + + // We want only successors in the current group, otherwise definitions get duplicated + let filtered_successors = |symbol: &Symbol| -> MutSet { + let mut result = successors(symbol); + + result.retain(|key| group.contains(key)); + result + }; + + // TODO fix this + // Patterns like + // + // { x, y } = someDef + // + // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), + // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key + // for a definition, so every definition is only inserted (thus typechecked and emitted) once + // let mut seen_pattern_regions: MutSet = MutSet::default(); + + for cycle in strongly_connected_components(&group, filtered_successors) { + if cycle.len() == 1 { + let symbol = &cycle[0]; + + if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { + // Determine recursivity of closures that are not tail-recursive + + let is_recursive = successors(&symbol).contains(&symbol); + + if is_recursive { + declarations.push(DeclareRec(vec![can_def])); + } else { + declarations.push(Declare(can_def)); + } + } + } else { + let mut can_defs = Vec::new(); + + // Topological sort gives us the reverse of the sorting we want! + for symbol in cycle.into_iter().rev() { + if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { + can_defs.push(can_def); + } + } + + declarations.push(DeclareRec(can_defs)); + } + } +} diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs new file mode 100644 index 0000000000..578a831d62 --- /dev/null +++ b/ast/src/lang/core/def/def2.rs @@ -0,0 +1,15 @@ +use crate::{lang::core::{expr::expr2::Expr2, pattern::Pattern2}, pool::pool::NodeId}; + + +// A top level definition, not inside a function. For example: `main = "Hello, world!"` +#[derive(Debug)] +pub enum Def2 { + // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" + ValueDef { + identifier_id: NodeId, + expr_id: NodeId, + }, + Blank, +} + +pub type DefId = NodeId; \ No newline at end of file diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs new file mode 100644 index 0000000000..377eaa37ab --- /dev/null +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -0,0 +1,75 @@ +use bumpalo::Bump; +use bumpalo::collections::Vec as BumpVec; +use roc_parse::pattern::PatternType; +use roc_region::all::Region; + +use crate::lang::{core::pattern::to_pattern2, env::Env, scope::Scope}; + +use super::def2::Def2; + +pub fn defs_to_defs2<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_defs: &'a BumpVec>>, + region: Region, +) -> Vec { + + parsed_defs + .iter() + .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) + .collect() +} + +pub fn to_def2_from_def<'a>( + arena: &'a Bump, + env: &mut Env<'a>, + scope: &mut Scope, + parsed_def: &'a roc_parse::ast::Def<'a>, + region: Region, +) -> Def2 { + use roc_parse::ast::Def::*; + + match parsed_def { + SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), + Body(&loc_pattern, &loc_expr) => { + // TODO loc_pattern use identifier + let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; + let expr_id = env.pool.add(expr2); + + use roc_parse::ast::Pattern::*; + + match loc_pattern.value { + Identifier(_) => { + let (_, pattern2) = to_pattern2( + env, + scope, + PatternType::TopLevelDef, + &loc_pattern.value, + region, + ); + let pattern_id = env.pool.add(pattern2); + + // TODO support with annotation + Def2::ValueDef { + identifier_id: pattern_id, + expr_id, + } + } + other => { + unimplemented!( + "I don't yet know how to convert the pattern {:?} into an expr2", + other + ) + } + } + } + other => { + unimplemented!( + "I don't know how to make an expr2 from this def yet: {:?}", + other + ) + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/def/mod.rs b/ast/src/lang/core/def/mod.rs new file mode 100644 index 0000000000..0f62252832 --- /dev/null +++ b/ast/src/lang/core/def/mod.rs @@ -0,0 +1,3 @@ +mod def_to_def2; +pub mod def; +pub mod def2; \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs new file mode 100644 index 0000000000..6b5b5b8328 --- /dev/null +++ b/ast/src/lang/core/expr/expr2.rs @@ -0,0 +1,235 @@ +use arraystring::{typenum::U30, ArrayString}; +use roc_types::subs::Variable; + +use crate::{lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}}; +use roc_can::expr::Recursive; +use roc_module::symbol::Symbol; +use roc_module::low_level::LowLevel; +use roc_module::operator::CalledVia; + +pub type ArrString = ArrayString; + +// TODO make the inner types private? +pub type ExprId = NodeId; + +/// An Expr that fits in 32B. +/// It has a 1B discriminant and variants which hold payloads of at most 31B. +#[derive(Debug)] +pub enum Expr2 { + /// A negative number literal without a dot + SmallInt { + number: IntVal, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + // TODO(rvcas): rename this eventually + /// A large (over 64-bit) negative number literal without a dot. + /// This variant can't use IntVal because if IntVal stored 128-bit + /// integers, it would be 32B on its own because of alignment. + I128 { + number: i128, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + // TODO(rvcas): rename this eventually + /// A large (over 64-bit) nonnegative number literal without a dot + /// This variant can't use IntVal because if IntVal stored 128-bit + /// integers, it would be 32B on its own because of alignment. + U128 { + number: u128, // 16B + var: Variable, // 4B + style: IntStyle, // 1B + text: PoolStr, // 8B + }, + /// A floating-point literal (with a dot) + Float { + number: FloatVal, // 16B + var: Variable, // 4B + text: PoolStr, // 8B + }, + /// string literals of length up to 30B + SmallStr(ArrString), // 31B + /// string literals of length 31B or more + Str(PoolStr), // 8B + // Lookups + Var(Symbol), // 8B + InvalidLookup(PoolStr), // 8B + + List { + elem_var: Variable, // 4B + elems: PoolVec, // 8B + }, + If { + cond_var: Variable, // 4B + expr_var: Variable, // 4B + branches: PoolVec<(ExprId, ExprId)>, // 8B + final_else: ExprId, // 4B + }, + When { + cond_var: Variable, // 4B + expr_var: Variable, // 4B + branches: PoolVec, // 8B + cond: ExprId, // 4B + }, + LetRec { + defs: PoolVec, // 8B + body_var: Variable, // 8B + body_id: ExprId, // 4B + }, + LetFunction { + def_id: NodeId, // 4B + body_var: Variable, // 8B + body_id: ExprId, // 4B + }, + LetValue { + def_id: NodeId, // 4B + body_id: ExprId, // 4B + body_var: Variable, // 4B + }, + Call { + args: PoolVec<(Variable, ExprId)>, // 8B + expr: ExprId, // 4B + expr_var: Variable, // 4B + fn_var: Variable, // 4B + closure_var: Variable, // 4B + called_via: CalledVia, // 2B + }, + RunLowLevel { + op: LowLevel, // 1B + args: PoolVec<(Variable, ExprId)>, // 8B + ret_var: Variable, // 4B + }, + Closure { + args: PoolVec<(Variable, NodeId)>, // 8B + name: Symbol, // 8B + body: ExprId, // 4B + function_type: Variable, // 4B + recursive: Recursive, // 1B + extra: NodeId, // 4B + }, + // Product Types + Record { + record_var: Variable, // 4B + fields: PoolVec, // 8B + }, + /// Empty record constant + EmptyRecord, + /// Look up exactly one field on a record, e.g. (expr).foo. + Access { + field: PoolStr, // 4B + expr: ExprId, // 4B + record_var: Variable, // 4B + ext_var: Variable, // 4B + field_var: Variable, // 4B + }, + + /// field accessor as a function, e.g. (.foo) expr + Accessor { + function_var: Variable, // 4B + closure_var: Variable, // 4B + field: PoolStr, // 4B + record_var: Variable, // 4B + ext_var: Variable, // 4B + field_var: Variable, // 4B + }, + Update { + symbol: Symbol, // 8B + updates: PoolVec, // 8B + record_var: Variable, // 4B + ext_var: Variable, // 4B + }, + + // Sum Types + GlobalTag { + name: PoolStr, // 4B + variant_var: Variable, // 4B + ext_var: Variable, // 4B + arguments: PoolVec<(Variable, ExprId)>, // 8B + }, + PrivateTag { + name: Symbol, // 8B + variant_var: Variable, // 4B + ext_var: Variable, // 4B + arguments: PoolVec<(Variable, ExprId)>, // 8B + }, + Blank, // Rendered as empty box in editor + + // Compiles, but will crash if reached + RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Problem { + RanOutOfNodeIds, +} + +pub type Res = Result; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntStyle { + Decimal, + Octal, + Hex, + Binary, +} + +impl IntStyle { + pub fn from_base(base: roc_parse::ast::Base) -> Self { + use roc_parse::ast::Base; + match base { + Base::Decimal => Self::Decimal, + Base::Octal => Self::Octal, + Base::Hex => Self::Hex, + Base::Binary => Self::Binary, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IntVal { + I64(i64), + U64(u64), + I32(i32), + U32(u32), + I16(i16), + U16(u16), + I8(i8), + U8(u8), +} + +#[test] +fn size_of_intval() { + assert_eq!(std::mem::size_of::(), 16); +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum FloatVal { + F64(f64), + F32(f32), +} + +#[derive(Debug)] +pub enum RecordField { + InvalidLabelOnly(PoolStr, Variable), + LabelOnly(PoolStr, Variable, Symbol), + LabeledValue(PoolStr, Variable, ExprId), +} + +#[derive(Debug)] +pub struct WhenBranch { + pub patterns: PoolVec, // 4B + pub body: ExprId, // 3B + pub guard: Option, // 4B +} + +/// This is overflow data from a Closure variant, which needs to store +/// more than 32B of total data +#[derive(Debug)] +pub struct ClosureExtra { + pub return_type: Variable, // 4B + pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B + pub closure_type: Variable, // 4B + pub closure_ext_var: Variable, // 4B +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs new file mode 100644 index 0000000000..7ab0ec4650 --- /dev/null +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -0,0 +1,136 @@ +use crate::{lang::core::{expr::expr2::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; + +use roc_types::subs::Variable; +use super::expr2::{Expr2, ExprId}; + +pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { + let mut full_string = String::new(); + let expr2 = pool.get(node_id); + + expr2_to_string_helper(expr2, 0, pool, &mut full_string); + + full_string +} + +fn get_spacing(indent_level: usize) -> String { + std::iter::repeat(" ") + .take(indent_level) + .collect::>() + .join("") +} + +fn expr2_to_string_helper( + expr2: &Expr2, + indent_level: usize, + pool: &Pool, + out_string: &mut String, +) { + out_string.push_str(&get_spacing(indent_level)); + + match expr2 { + Expr2::SmallStr(arr_string) => out_string.push_str(&format!( + "{}{}{}", + "SmallStr(\"", + arr_string.as_str(), + "\")", + )), + Expr2::Str(pool_str) => { + out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",)) + } + Expr2::Blank => out_string.push_str("Blank"), + Expr2::EmptyRecord => out_string.push_str("EmptyRecord"), + Expr2::Record { record_var, fields } => { + out_string.push_str("Record:\n"); + out_string.push_str(&var_to_string(record_var, indent_level + 1)); + + out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1))); + + let mut first_child = true; + + for field in fields.iter(pool) { + if !first_child { + out_string.push_str(", ") + } else { + first_child = false; + } + + match field { + RecordField::InvalidLabelOnly(pool_str, var) => { + out_string.push_str(&format!( + "{}({}, Var({:?})", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + )); + } + RecordField::LabelOnly(pool_str, var, symbol) => { + out_string.push_str(&format!( + "{}({}, Var({:?}), Symbol({:?})", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + symbol + )); + } + RecordField::LabeledValue(pool_str, var, val_node_id) => { + out_string.push_str(&format!( + "{}({}, Var({:?}), Expr2(\n", + get_spacing(indent_level + 2), + pool_str.as_str(pool), + var, + )); + + let val_expr2 = pool.get(*val_node_id); + expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string); + out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2))); + } + } + } + + out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); + } + Expr2::List { elem_var, elems } => { + out_string.push_str("List:\n"); + out_string.push_str(&var_to_string(elem_var, indent_level + 1)); + out_string.push_str(&format!("{}elems: [\n", get_spacing(indent_level + 1))); + + let mut first_elt = true; + + for elem_expr2_id in elems.iter(pool) { + if !first_elt { + out_string.push_str(", ") + } else { + first_elt = false; + } + + let elem_expr2 = pool.get(*elem_expr2_id); + + expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string) + } + + out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); + } + Expr2::InvalidLookup(pool_str) => { + out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool))); + } + Expr2::SmallInt { text, .. } => { + out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); + } + Expr2::LetValue { + def_id, body_id, .. + } => { + out_string.push_str(&format!( + "LetValue(def_id: >>{:?}), body_id: >>{:?})", + value_def_to_string(pool.get(*def_id), pool), + pool.get(*body_id) + )); + } + other => todo!("Implement for {:?}", other), + } + + out_string.push('\n'); +} + +fn var_to_string(some_var: &Variable, indent_level: usize) -> String { + format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs new file mode 100644 index 0000000000..0dc093ee91 --- /dev/null +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -0,0 +1,694 @@ +use bumpalo::Bump; +use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_can::operator::desugar_expr; +use roc_parse::{ast::Expr, pattern::PatternType}; +use roc_problem::can::{Problem, RuntimeError}; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; + +use crate::lang::core::pattern::flatten_str_literal; +use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; + +use super::{expr2::Expr2, output::Output}; + +pub fn loc_expr_to_expr2<'a>( + arena: &'a Bump, + loc_expr: Located>, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> (Expr2, Output) { + let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); + + to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) +} + +const ZERO: Region = Region::zero(); + +pub fn to_expr2<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + parse_expr: &'a roc_parse::ast::Expr<'a>, + region: Region, +) -> (Expr2, self::Output) { + use roc_parse::ast::Expr::*; + + match parse_expr { + Float(string) => { + match finish_parsing_float(string) { + Ok(float) => { + let expr = Expr2::Float { + number: FloatVal::F64(float), + var: env.var_store.fresh(), + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + Num(string) => { + match finish_parsing_int(string) { + Ok(int) => { + let expr = Expr2::SmallInt { + number: IntVal::I64(int), + var: env.var_store.fresh(), + // TODO non-hardcode + style: IntStyle::Decimal, + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidInt( + error, + roc_parse::ast::Base::Decimal, + ZERO, + raw.into(), + ); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + NonBase10Int { + string, + base, + is_negative, + } => { + match finish_parsing_base(string, *base, *is_negative) { + Ok(int) => { + let expr = Expr2::SmallInt { + number: IntVal::I64(int), + var: env.var_store.fresh(), + // TODO non-hardcode + style: IntStyle::from_base(*base), + text: PoolStr::new(string, &mut env.pool), + }; + + (expr, Output::default()) + } + Err((raw, error)) => { + // emit runtime error + let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + // + // Expr::RuntimeError(runtime_error) + todo!() + } + } + } + + Str(literal) => flatten_str_literal(env, scope, &literal), + + List { items, .. } => { + let mut output = Output::default(); + let output_ref = &mut output; + + let elems: PoolVec = PoolVec::with_capacity(items.len() as u32, env.pool); + + for (node_id, item) in elems.iter_node_ids().zip(items.iter()) { + let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region); + + output_ref.union(sub_output); + + let expr_id = env.pool.add(expr); + env.pool[node_id] = expr_id; + } + + let expr = Expr2::List { + elem_var: env.var_store.fresh(), + elems, + }; + + (expr, output) + } + + GlobalTag(tag) => { + // a global tag without any arguments + ( + Expr2::GlobalTag { + name: PoolStr::new(tag, env.pool), + variant_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + arguments: PoolVec::empty(env.pool), + }, + Output::default(), + ) + } + PrivateTag(name) => { + // a private tag without any arguments + let ident_id = env.ident_ids.get_or_insert(&(*name).into()); + let name = Symbol::new(env.home, ident_id); + ( + Expr2::PrivateTag { + name, + variant_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + arguments: PoolVec::empty(env.pool), + }, + Output::default(), + ) + } + + RecordUpdate { + fields, + update: loc_update, + final_comments: _, + } => { + let (can_update, update_out) = + to_expr2(env, scope, &loc_update.value, loc_update.region); + + if let Expr2::Var(symbol) = &can_update { + match canonicalize_fields(env, scope, fields) { + Ok((can_fields, mut output)) => { + output.references.union_mut(update_out.references); + + let answer = Expr2::Update { + record_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + symbol: *symbol, + updates: can_fields, + }; + + (answer, output) + } + Err(CanonicalizeRecordProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + record_region: _, + }) => { + // let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue { + // field_name, + // field_region, + // record_region, + // }; + // + // env.problem(Problem::RuntimeError(runtime_error)); + + todo!() + } + } + } else { + // only (optionally qualified) variables can be updated, not arbitrary expressions + + // let error = roc_problem::can::RuntimeError::InvalidRecordUpdate { + // region: can_update.region, + // }; + // + // let answer = Expr::RuntimeError(error.clone()); + // + // env.problems.push(Problem::RuntimeError(error)); + // + // (answer, Output::default()) + todo!() + } + } + + Record { + fields, + final_comments: _, + } => { + if fields.is_empty() { + (Expr2::EmptyRecord, Output::default()) + } else { + match canonicalize_fields(env, scope, fields) { + Ok((can_fields, output)) => ( + Expr2::Record { + record_var: env.var_store.fresh(), + fields: can_fields, + }, + output, + ), + Err(CanonicalizeRecordProblem::InvalidOptionalValue { + field_name: _, + field_region: _, + record_region: _, + }) => { + // let runtime_error = RuntimeError::InvalidOptionalValue { + // field_name, + // field_region, + // record_region, + // }; + // + // env.problem(runtime_error); + // ( + // Expr::RuntimeError( + // ), + // Output::default(), + // + // ) + todo!() + } + } + } + } + + Access(record_expr, field) => { + // TODO + let region = ZERO; + let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region); + + ( + Expr2::Access { + record_var: env.var_store.fresh(), + field_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + expr: record_expr_id, + field: PoolStr::new(field, env.pool), + }, + output, + ) + } + + AccessorFunction(field) => ( + Expr2::Accessor { + function_var: env.var_store.fresh(), + record_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + field_var: env.var_store.fresh(), + field: PoolStr::new(field, env.pool), + }, + Output::default(), + ), + + If(branches, final_else) => { + let mut new_branches = Vec::with_capacity(branches.len()); + let mut output = Output::default(); + + for (condition, then_branch) in branches.iter() { + let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region); + + let (then_expr, then_output) = + to_expr2(env, scope, &then_branch.value, then_branch.region); + + output.references.union_mut(cond_output.references); + output.references.union_mut(then_output.references); + + new_branches.push((env.pool.add(cond), env.pool.add(then_expr))); + } + + let (else_expr, else_output) = + to_expr2(env, scope, &final_else.value, final_else.region); + + output.references.union_mut(else_output.references); + + let expr = Expr2::If { + cond_var: env.var_store.fresh(), + expr_var: env.var_store.fresh(), + branches: PoolVec::new(new_branches.into_iter(), env.pool), + final_else: env.pool.add(else_expr), + }; + + (expr, output) + } + + When(loc_cond, branches) => { + // Infer the condition expression's type. + let cond_var = env.var_store.fresh(); + let (can_cond, mut output) = to_expr2(env, scope, &loc_cond.value, loc_cond.region); + + // the condition can never be a tail-call + output.tail_call = None; + + let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool); + + for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) { + let (can_when_branch, branch_references) = + canonicalize_when_branch(env, scope, *branch, &mut output); + + output.references.union_mut(branch_references); + + env.pool[node_id] = can_when_branch; + } + + // A "when" with no branches is a runtime error, but it will mess things up + // if code gen mistakenly thinks this is a tail call just because its condition + // happened to be one. (The condition gave us our initial output value.) + if branches.is_empty() { + output.tail_call = None; + } + + // Incorporate all three expressions into a combined Output value. + let expr = Expr2::When { + expr_var: env.var_store.fresh(), + cond_var, + cond: env.pool.add(can_cond), + branches: can_branches, + }; + + (expr, output) + } + + Closure(loc_arg_patterns, loc_body_expr) => { + // The globally unique symbol that will refer to this closure once it gets converted + // into a top-level procedure for code gen. + // + // In the Foo module, this will look something like Foo.$1 or Foo.$2. + let symbol = env + .closure_name_symbol + .unwrap_or_else(|| env.gen_unique_symbol()); + env.closure_name_symbol = None; + + // The body expression gets a new scope for canonicalization. + // Shadow `scope` to make sure we don't accidentally use the original one for the + // rest of this block, but keep the original around for later diffing. + let original_scope = scope; + let mut scope = original_scope.shallow_clone(); + let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool); + let mut output = Output::default(); + + let mut bound_by_argument_patterns = MutSet::default(); + + for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) { + let (new_output, can_arg) = to_pattern2( + env, + &mut scope, + roc_parse::pattern::PatternType::FunctionArg, + &loc_pattern.value, + loc_pattern.region, + ); + + bound_by_argument_patterns + .extend(new_output.references.bound_symbols.iter().copied()); + + output.union(new_output); + + let pattern_id = env.add(can_arg, loc_pattern.region); + env.pool[node_id] = (env.var_store.fresh(), pattern_id); + } + + let (body_expr, new_output) = + to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region); + + let mut captured_symbols: MutSet = + new_output.references.lookups.iter().copied().collect(); + + // filter out the closure's name itself + captured_symbols.remove(&symbol); + + // symbols bound either in this pattern or deeper down are not captured! + captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); + captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); + + // filter out top-level symbols + // those will be globally available, and don't need to be captured + captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); + + // filter out imported symbols + // those will be globally available, and don't need to be captured + captured_symbols.retain(|s| s.module_id() == env.home); + + // TODO any Closure that has an empty `captured_symbols` list could be excluded! + + output.union(new_output); + + // filter out aliases + captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s)); + + // filter out functions that don't close over anything + captured_symbols.retain(|s| !output.non_closures.contains(s)); + + // Now that we've collected all the references, check to see if any of the args we defined + // went unreferenced. If any did, report them as unused arguments. + for (sub_symbol, region) in scope.symbols() { + if !original_scope.contains_symbol(sub_symbol) { + if !output.references.has_lookup(sub_symbol) { + // The body never referenced this argument we declared. It's an unused argument! + env.problem(Problem::UnusedArgument(symbol, sub_symbol, region)); + } + + // We shouldn't ultimately count arguments as referenced locals. Otherwise, + // we end up with weird conclusions like the expression (\x -> x + 1) + // references the (nonexistant) local variable x! + output.references.lookups.remove(&sub_symbol); + } + } + + env.register_closure(symbol, output.references.clone()); + + let mut captured_symbols: Vec<_> = captured_symbols + .into_iter() + .map(|s| (s, env.var_store.fresh())) + .collect(); + + // sort symbols, so we know the order in which they're stored in the closure record + captured_symbols.sort(); + + // store that this function doesn't capture anything. It will be promoted to a + // top-level function, and does not need to be captured by other surrounding functions. + if captured_symbols.is_empty() { + output.non_closures.insert(symbol); + } + + let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool); + + let extra = ClosureExtra { + return_type: env.var_store.fresh(), // 4B + captured_symbols, // 8B + closure_type: env.var_store.fresh(), // 4B + closure_ext_var: env.var_store.fresh(), // 4B + }; + + ( + Expr2::Closure { + function_type: env.var_store.fresh(), + name: symbol, + recursive: Recursive::NotRecursive, + args: can_args, + body: env.add(body_expr, loc_body_expr.region), + extra: env.pool.add(extra), + }, + output, + ) + } + + Apply(loc_fn, loc_args, application_style) => { + // The expression that evaluates to the function being called, e.g. `foo` in + // (foo) bar baz + let fn_region = loc_fn.region; + + // Canonicalize the function expression and its arguments + let (fn_expr, mut output) = to_expr2(env, scope, &loc_fn.value, fn_region); + + // The function's return type + let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool); + + for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) { + let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region); + + env.pool[node_id] = (env.var_store.fresh(), arg_expr_id); + + output.references.union_mut(arg_out.references); + } + + // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. + output.tail_call = None; + + let expr = match fn_expr { + Expr2::Var(ref symbol) => { + output.references.calls.insert(*symbol); + + // we're tail-calling a symbol by name, check if it's the tail-callable symbol + output.tail_call = match &env.tailcallable_symbol { + Some(tc_sym) if *tc_sym == *symbol => Some(*symbol), + Some(_) | None => None, + }; + + // IDEA: Expr2::CallByName? + let fn_expr_id = env.add(fn_expr, fn_region); + Expr2::Call { + args, + expr: fn_expr_id, + expr_var: env.var_store.fresh(), + fn_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + called_via: *application_style, + } + } + Expr2::RuntimeError() => { + // We can't call a runtime error; bail out by propagating it! + return (fn_expr, output); + } + Expr2::GlobalTag { + variant_var, + ext_var, + name, + .. + } => Expr2::GlobalTag { + variant_var, + ext_var, + name, + arguments: args, + }, + Expr2::PrivateTag { + variant_var, + ext_var, + name, + .. + } => Expr2::PrivateTag { + variant_var, + ext_var, + name, + arguments: args, + }, + _ => { + // This could be something like ((if True then fn1 else fn2) arg1 arg2). + let fn_expr_id = env.add(fn_expr, fn_region); + Expr2::Call { + args, + expr: fn_expr_id, + expr_var: env.var_store.fresh(), + fn_var: env.var_store.fresh(), + closure_var: env.var_store.fresh(), + called_via: *application_style, + } + } + }; + + (expr, output) + } + + Defs(loc_defs, loc_ret) => { + let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( + env, + Output::default(), + &scope, + loc_defs, + PatternType::DefExpr, + ); + + // The def as a whole is a tail call iff its return expression is a tail call. + // Use its output as a starting point because its tail_call already has the right answer! + let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region); + + output + .introduced_variables + .union(&defs_output.introduced_variables); + + output.references.union_mut(defs_output.references); + + // Now that we've collected all the references, check to see if any of the new idents + // we defined went unused by the return expression. If any were unused, report it. + for (symbol, region) in symbols_introduced { + if !output.references.has_lookup(symbol) { + env.problem(Problem::UnusedDef(symbol, region)); + } + } + + let (can_defs, output) = sort_can_defs(env, unsorted, output); + + match can_defs { + Ok(decls) => { + let mut expr = ret_expr; + + for declaration in decls.into_iter().rev() { + expr = decl_to_let(env.pool, env.var_store, declaration, expr); + } + + (expr, output) + } + Err(_err) => { + // TODO: fix this to be something from Expr2 + // (RuntimeError(err), output) + todo!() + } + } + } + + PrecedenceConflict { .. } => { + // use roc_problem::can::RuntimeError::*; + // + // let problem = PrecedenceProblem::BothNonAssociative( + // *whole_region, + // binop1.clone(), + // binop2.clone(), + // ); + // + // env.problem(Problem::PrecedenceProblem(problem.clone())); + // + // ( + // RuntimeError(InvalidPrecedence(problem, region)), + // Output::default(), + // ) + todo!() + } + MalformedClosure => { + // use roc_problem::can::RuntimeError::*; + // (RuntimeError(MalformedClosure(region)), Output::default()) + todo!() + } + MalformedIdent(_name, _problem) => { + // use roc_problem::can::RuntimeError::*; + // + // let problem = MalformedIdentifier((*name).into(), region); + // env.problem(Problem::RuntimeError(problem.clone())); + // + // (RuntimeError(problem), Output::default()) + todo!() + } + Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), + + // Below this point, we shouln't see any of these nodes anymore because + // operator desugaring should have removed them! + bad_expr @ ParensAround(_) => { + panic!( + "A ParensAround did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ SpaceBefore(_, _) => { + panic!( + "A SpaceBefore did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ SpaceAfter(_, _) => { + panic!( + "A SpaceAfter did not get removed during operator desugaring somehow: {:#?}", + bad_expr + ); + } + bad_expr @ BinOps { .. } => { + panic!( + "A binary operator chain did not get desugared somehow: {:#?}", + bad_expr + ); + } + bad_expr @ UnaryOp(_, _) => { + panic!( + "A unary operator did not get desugared somehow: {:#?}", + bad_expr + ); + } + + rest => todo!("not yet implemented {:?}", rest), + } +} + +pub fn to_expr_id<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + parse_expr: &'a roc_parse::ast::Expr<'a>, + region: Region, +) -> (ExprId, Output) { + let (expr, output) = to_expr2(env, scope, parse_expr, region); + + (env.add(expr, region), output) +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/introduced_vars.rs b/ast/src/lang/core/expr/introduced_vars.rs new file mode 100644 index 0000000000..7a8db4c3fa --- /dev/null +++ b/ast/src/lang/core/expr/introduced_vars.rs @@ -0,0 +1,52 @@ + +use roc_types::subs::{Variable}; +use roc_collections::all::{MutMap}; +use roc_module::ident::{Lowercase}; +use roc_module::symbol::{Symbol}; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct IntroducedVariables { + // Rigids must be unique within a type annoation. + // E.g. in `identity : a -> a`, there should only be one + // variable (a rigid one, with name "a"). + // Hence `rigids : Map` + // + // But then between annotations, the same name can occur multiple times, + // but a variable can only have one name. Therefore + // `ftv : Map`. + pub wildcards: Vec, + pub var_by_name: MutMap, + pub name_by_var: MutMap, + pub host_exposed_aliases: MutMap, +} + +impl IntroducedVariables { + pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + self.var_by_name.insert(name.clone(), var); + self.name_by_var.insert(var, name); + } + + pub fn insert_wildcard(&mut self, var: Variable) { + self.wildcards.push(var); + } + + pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { + self.host_exposed_aliases.insert(symbol, var); + } + + pub fn union(&mut self, other: &Self) { + self.wildcards.extend(other.wildcards.iter().cloned()); + self.var_by_name.extend(other.var_by_name.clone()); + self.name_by_var.extend(other.name_by_var.clone()); + self.host_exposed_aliases + .extend(other.host_exposed_aliases.clone()); + } + + pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { + self.var_by_name.get(name) + } + + pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { + self.name_by_var.get(&var) + } +} \ No newline at end of file diff --git a/ast/src/lang/core/expr/mod.rs b/ast/src/lang/core/expr/mod.rs new file mode 100644 index 0000000000..186f65c9c0 --- /dev/null +++ b/ast/src/lang/core/expr/mod.rs @@ -0,0 +1,5 @@ +pub mod expr2; +pub mod expr2_to_string; +pub (crate) mod output; +mod introduced_vars; +pub (crate) mod expr_to_expr2; diff --git a/ast/src/lang/core/expr/output.rs b/ast/src/lang/core/expr/output.rs new file mode 100644 index 0000000000..3c84e9a618 --- /dev/null +++ b/ast/src/lang/core/expr/output.rs @@ -0,0 +1,28 @@ +use crate::{lang::core::{def::def::References, types::{Alias}}, pool::pool::NodeId}; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::symbol::Symbol; + +use super::introduced_vars::IntroducedVariables; + +#[derive(Clone, Default, Debug, PartialEq)] +pub struct Output { + pub references: References, + pub tail_call: Option, + pub introduced_variables: IntroducedVariables, + pub aliases: MutMap>, + pub non_closures: MutSet, +} + +impl Output { + pub fn union(&mut self, other: Self) { + self.references.union_mut(other.references); + + if let (None, Some(later)) = (self.tail_call, other.tail_call) { + self.tail_call = Some(later); + } + + self.aliases.extend(other.aliases); + self.non_closures.extend(other.non_closures); + } +} + \ No newline at end of file diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs new file mode 100644 index 0000000000..ea67f75082 --- /dev/null +++ b/ast/src/lang/core/fun_def.rs @@ -0,0 +1,56 @@ + + +use crate::{lang::rigids::Rigids, pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use roc_module::symbol::Symbol; +use roc_types::subs::Variable; + +use super::{expr::expr2::ExprId, pattern::PatternId, types::{Type2, TypeId}}; + +#[derive(Debug)] +pub enum FunctionDef { + WithAnnotation { + name: Symbol, // 8B + arguments: PoolVec<(PatternId, Type2)>, // 8B + rigids: NodeId, // 4B + return_type: TypeId, // 4B + body: ExprId, // 4B + }, + NoAnnotation { + name: Symbol, // 8B + arguments: PoolVec<(PatternId, Variable)>, // 8B + return_var: Variable, // 4B + body: ExprId, // 4B + }, +} + +impl ShallowClone for FunctionDef { + fn shallow_clone(&self) -> Self { + match self { + Self::WithAnnotation { + name, + arguments, + rigids, + return_type, + body, + } => Self::WithAnnotation { + name: *name, + arguments: arguments.shallow_clone(), + rigids: *rigids, + return_type: *return_type, + body: *body, + }, + + Self::NoAnnotation { + name, + arguments, + return_var, + body, + } => Self::NoAnnotation { + name: *name, + arguments: arguments.shallow_clone(), + return_var: *return_var, + body: *body, + }, + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/header.rs b/ast/src/lang/core/header.rs new file mode 100644 index 0000000000..60c359af0e --- /dev/null +++ b/ast/src/lang/core/header.rs @@ -0,0 +1,12 @@ + +use super::expr::expr2::ExprId; + + +#[derive(Debug)] +pub struct AppHeader { + pub app_name: String, + pub packages_base: String, + pub imports: Vec, + pub provides: Vec, + pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId +} \ No newline at end of file diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs new file mode 100644 index 0000000000..7347e5a924 --- /dev/null +++ b/ast/src/lang/core/mod.rs @@ -0,0 +1,8 @@ +pub mod def; +pub mod expr; +pub mod header; +pub mod ast; +mod val_def; +mod fun_def; +mod pattern; +pub mod types; \ No newline at end of file diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs new file mode 100644 index 0000000000..ddab1bef5a --- /dev/null +++ b/ast/src/lang/core/pattern.rs @@ -0,0 +1,626 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use bumpalo::collections::Vec as BumpVec; +use roc_can::expr::unescape_char; +use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; +use roc_collections::all::BumpMap; +use roc_module::symbol::{Interns, Symbol}; +use roc_parse::ast::{StrLiteral, StrSegment}; +use roc_parse::pattern::PatternType; +use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; +use roc_region::all::Region; +use roc_types::subs::Variable; + +use crate::constrain::Constraint; +use crate::lang::core::expr::expr_to_expr2::to_expr_id; +use crate::lang::env::Env; +use crate::lang::scope::Scope; +use crate::pool::pool::{NodeId, Pool}; +use crate::pool::pool_str::PoolStr; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; +use crate::ast_error::{ASTResult, UnexpectedPattern2Variant}; + +use super::expr::expr2::{ExprId, FloatVal, IntVal}; +use super::expr::output::Output; +use super::types::Type2; + +pub type PatternId = NodeId; + +#[derive(Debug)] +pub enum Pattern2 { + Identifier(Symbol), // 8B + NumLiteral(Variable, i64), // 4B + 8B + IntLiteral(IntVal), // 16B + FloatLiteral(FloatVal), // 16B + StrLiteral(PoolStr), // 8B + Underscore, // 0B + GlobalTag { + whole_var: Variable, // 4B + ext_var: Variable, // 4B + tag_name: PoolStr, // 8B + arguments: PoolVec<(Variable, PatternId)>, // 8B + }, + PrivateTag { + whole_var: Variable, // 4B + ext_var: Variable, // 4B + tag_name: Symbol, // 8B + arguments: PoolVec<(Variable, PatternId)>, // 8B + }, + RecordDestructure { + whole_var: Variable, // 4B + ext_var: Variable, // 4B + destructs: PoolVec, // 8B + }, + + // Runtime Exceptions + // TODO: figure out how to better handle regions + // to keep this member under 32. With 2 Regions + // it ends up at size 40 + Shadowed { + shadowed_ident: PoolStr, + // definition: Region, + // shadowed_at: Region, + }, + + /// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! + UnsupportedPattern(Region), + // parse error patterns + MalformedPattern(MalformedPatternProblem, Region), +} + +impl ShallowClone for Pattern2 { + fn shallow_clone(&self) -> Self { + todo!() + } +} + +#[derive(Debug)] +pub struct PatternState2<'a> { + pub headers: BumpMap, + pub vars: BumpVec<'a, Variable>, + pub constraints: BumpVec<'a, Constraint<'a>>, +} + +#[derive(Debug)] +pub struct RecordDestruct { + pub var: Variable, // 4B + pub label: PoolStr, // 8B + pub symbol: Symbol, // 8B + pub typ: NodeId, // 4B +} + +#[derive(Clone, Debug)] +pub enum DestructType { + Required, + Optional(Variable, ExprId), // 4B + 4B + Guard(Variable, PatternId), // 4B + 4B +} + +pub fn as_pattern_id<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + pattern_id: PatternId, + pattern_type: PatternType, + pattern: &roc_parse::ast::Pattern<'a>, + region: Region, +) -> Output { + let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); + + env.pool[pattern_id] = can_pattern; + env.set_region(pattern_id, region); + + output +} + +pub fn to_pattern_id<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + pattern_type: PatternType, + pattern: &roc_parse::ast::Pattern<'a>, + region: Region, +) -> (Output, PatternId) { + let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); + + let pattern_id = env.pool.add(can_pattern); + env.set_region(pattern_id, region); + + (output, pattern_id) +} + +pub fn to_pattern2<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + pattern_type: PatternType, + pattern: &roc_parse::ast::Pattern<'a>, + region: Region, +) -> (Output, Pattern2) { + use roc_parse::ast::Pattern::*; + use PatternType::*; + + let mut output = Output::default(); + let can_pattern = match pattern { + Identifier(name) => match scope.introduce( + (*name).into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + output.references.bound_symbols.insert(symbol); + + Pattern2::Identifier(symbol) + } + Err((original_region, shadow)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + })); + + let name: &str = shadow.value.as_ref(); + + Pattern2::Shadowed { + shadowed_ident: PoolStr::new(name, env.pool), + } + } + }, + + QualifiedIdentifier { .. } => { + let problem = MalformedPatternProblem::QualifiedIdentifier; + malformed_pattern(env, problem, region) + } + + Underscore(_) => match pattern_type { + WhenBranch | FunctionArg => Pattern2::Underscore, + TopLevelDef | DefExpr => underscore_in_def(env, region), + }, + + FloatLiteral(ref string) => match pattern_type { + WhenBranch => match finish_parsing_float(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedFloat; + malformed_pattern(env, problem, region) + } + Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)), + }, + ptype => unsupported_pattern(env, ptype, region), + }, + + NumLiteral(string) => match pattern_type { + WhenBranch => match finish_parsing_int(string) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedInt; + malformed_pattern(env, problem, region) + } + Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), + }, + ptype => unsupported_pattern(env, ptype, region), + }, + + NonBase10Literal { + string, + base, + is_negative, + } => match pattern_type { + WhenBranch => match finish_parsing_base(string, *base, *is_negative) { + Err(_error) => { + let problem = MalformedPatternProblem::MalformedBase(*base); + malformed_pattern(env, problem, region) + } + Ok(int) => { + if *is_negative { + Pattern2::IntLiteral(IntVal::I64(-int)) + } else { + Pattern2::IntLiteral(IntVal::I64(int)) + } + } + }, + ptype => unsupported_pattern(env, ptype, region), + }, + + StrLiteral(literal) => match pattern_type { + WhenBranch => flatten_str_literal(env.pool, literal), + ptype => unsupported_pattern(env, ptype, region), + }, + + GlobalTag(name) => { + // Canonicalize the tag's name. + Pattern2::GlobalTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: PoolStr::new(name, env.pool), + arguments: PoolVec::empty(env.pool), + } + } + PrivateTag(name) => { + let ident_id = env.ident_ids.get_or_insert(&(*name).into()); + + // Canonicalize the tag's name. + Pattern2::PrivateTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: Symbol::new(env.home, ident_id), + arguments: PoolVec::empty(env.pool), + } + } + + Apply(tag, patterns) => { + let can_patterns = PoolVec::with_capacity(patterns.len() as u32, env.pool); + for (loc_pattern, node_id) in (*patterns).iter().zip(can_patterns.iter_node_ids()) { + let (new_output, can_pattern) = to_pattern2( + env, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + output.union(new_output); + + let can_pattern_id = env.pool.add(can_pattern); + + env.pool[node_id] = (env.var_store.fresh(), can_pattern_id); + } + + match tag.value { + GlobalTag(name) => Pattern2::GlobalTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: PoolStr::new(name, env.pool), + arguments: can_patterns, + }, + PrivateTag(name) => { + let ident_id = env.ident_ids.get_or_insert(&name.into()); + + Pattern2::PrivateTag { + whole_var: env.var_store.fresh(), + ext_var: env.var_store.fresh(), + tag_name: Symbol::new(env.home, ident_id), + arguments: can_patterns, + } + } + _ => unreachable!("Other patterns cannot be applied"), + } + } + + RecordDestructure(patterns) => { + let ext_var = env.var_store.fresh(); + let whole_var = env.var_store.fresh(); + let destructs = PoolVec::with_capacity(patterns.len() as u32, env.pool); + let opt_erroneous = None; + + for (node_id, loc_pattern) in destructs.iter_node_ids().zip((*patterns).iter()) { + match loc_pattern.value { + Identifier(label) => { + match scope.introduce( + label.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + output.references.bound_symbols.insert(symbol); + + let destruct = RecordDestruct { + var: env.var_store.fresh(), + label: PoolStr::new(label, env.pool), + symbol, + typ: env.pool.add(DestructType::Required), + }; + + env.pool[node_id] = destruct; + env.set_region(node_id, loc_pattern.region); + } + Err((original_region, shadow)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + })); + + // let shadowed = Pattern2::Shadowed { + // definition: original_region, + // shadowed_at: loc_pattern.region, + // shadowed_ident: shadow.value, + // }; + + // No matter what the other patterns + // are, we're definitely shadowed and will + // get a runtime exception as soon as we + // encounter the first bad pattern. + // opt_erroneous = Some(); + // env.pool[node_id] = sha; + // env.set_region(node_id, loc_pattern.region); + todo!("we must both report/store the problem, but also not lose any information") + } + }; + } + + RequiredField(label, loc_guard) => { + // a guard does not introduce the label into scope! + let symbol = scope.ignore(label.into(), &mut env.ident_ids); + let (new_output, can_guard) = to_pattern_id( + env, + scope, + pattern_type, + &loc_guard.value, + loc_guard.region, + ); + + let destruct = RecordDestruct { + var: env.var_store.fresh(), + label: PoolStr::new(label, env.pool), + symbol, + typ: env + .pool + .add(DestructType::Guard(env.var_store.fresh(), can_guard)), + }; + + output.union(new_output); + + env.pool[node_id] = destruct; + env.set_region(node_id, loc_pattern.region); + } + OptionalField(label, loc_default) => { + // an optional DOES introduce the label into scope! + match scope.introduce( + label.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + let (can_default, expr_output) = + to_expr_id(env, scope, &loc_default.value, loc_default.region); + + // an optional field binds the symbol! + output.references.bound_symbols.insert(symbol); + + output.union(expr_output); + + let destruct = RecordDestruct { + var: env.var_store.fresh(), + label: PoolStr::new(label, env.pool), + symbol, + typ: env.pool.add(DestructType::Optional( + env.var_store.fresh(), + can_default, + )), + }; + + env.pool[node_id] = destruct; + env.set_region(node_id, loc_pattern.region); + } + Err((original_region, shadow)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + })); + + // No matter what the other patterns + // are, we're definitely shadowed and will + // get a runtime exception as soon as we + // encounter the first bad pattern. + // opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); + todo!("must report problem but also not loose any information") + } + }; + } + _ => unreachable!("Any other pattern should have given a parse error"), + } + } + + // If we encountered an erroneous pattern (e.g. one with shadowing), + // use the resulting RuntimeError. Otherwise, return a successful record destructure. + opt_erroneous.unwrap_or(Pattern2::RecordDestructure { + whole_var, + ext_var, + destructs, + }) + } + + RequiredField(_name, _loc_pattern) => { + unreachable!("should have been handled in RecordDestructure"); + } + OptionalField(_name, _loc_pattern) => { + unreachable!("should have been handled in RecordDestructure"); + } + + Malformed(_str) => { + let problem = MalformedPatternProblem::Unknown; + malformed_pattern(env, problem, region) + } + + MalformedIdent(_str, bad_ident) => { + let problem = MalformedPatternProblem::BadIdent(*bad_ident); + malformed_pattern(env, problem, region) + } + + SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { + return to_pattern2(env, scope, pattern_type, sub_pattern, region) + } + }; + + (output, can_pattern) +} + +pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { + use Pattern2::*; + let mut symbols = Vec::new(); + let mut stack = vec![initial]; + + while let Some(pattern) = stack.pop() { + match pattern { + Identifier(symbol) => { + symbols.push(*symbol); + } + + GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + for (_, pat_id) in arguments.iter(pool) { + let pat = pool.get(*pat_id); + stack.push(pat); + } + } + + RecordDestructure { destructs, .. } => { + for destruct in destructs.iter(pool) { + let destruct_type = pool.get(destruct.typ); + + if let DestructType::Guard(_, subpattern_id) = &destruct_type { + let subpattern = pool.get(*subpattern_id); + stack.push(subpattern); + } else { + symbols.push(destruct.symbol); + } + } + } + + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | MalformedPattern(_, _) + | Shadowed { .. } + | UnsupportedPattern(_) => {} + } + } + + symbols +} + +pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult { + match pattern { + Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), + other => UnexpectedPattern2Variant { + required_pattern2: "Identifier".to_string(), + encountered_pattern2: format!("{:?}", other), + } + .fail()?, + } +} + +pub fn symbols_and_variables_from_pattern( + pool: &Pool, + initial: &Pattern2, + initial_var: Variable, +) -> Vec<(Symbol, Variable)> { + use Pattern2::*; + let mut symbols = Vec::new(); + let mut stack = vec![(initial_var, initial)]; + + while let Some((variable, pattern)) = stack.pop() { + match pattern { + Identifier(symbol) => { + symbols.push((*symbol, variable)); + } + + GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { + for (var, pat_id) in arguments.iter(pool) { + let pat = pool.get(*pat_id); + stack.push((*var, pat)); + } + } + + RecordDestructure { destructs, .. } => { + for destruct in destructs.iter(pool) { + let destruct_type = pool.get(destruct.typ); + + if let DestructType::Guard(_, subpattern_id) = &destruct_type { + let subpattern = pool.get(*subpattern_id); + stack.push((destruct.var, subpattern)); + } else { + symbols.push((destruct.symbol, destruct.var)); + } + } + } + + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | MalformedPattern(_, _) + | Shadowed { .. } + | UnsupportedPattern(_) => {} + } + } + + symbols +} + +/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't +/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern. +fn unsupported_pattern<'a>( + env: &mut Env<'a>, + pattern_type: PatternType, + region: Region, +) -> Pattern2 { + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::Unsupported(pattern_type), + region, + )); + + Pattern2::UnsupportedPattern(region) +} + +fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { + use roc_problem::can::BadPattern; + env.problem(Problem::UnsupportedPattern( + BadPattern::UnderscoreInDef, + region, + )); + + Pattern2::UnsupportedPattern(region) +} + +pub (crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { + use roc_parse::ast::StrLiteral::*; + + match literal { + PlainLine(str_slice) => Pattern2::StrLiteral(PoolStr::new(str_slice, pool)), + Line(segments) => flatten_str_lines(pool, &[segments]), + Block(lines) => flatten_str_lines(pool, lines), + } +} + +pub (crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { + use StrSegment::*; + + let mut buf = String::new(); + + for line in lines { + for segment in line.iter() { + match segment { + Plaintext(string) => { + buf.push_str(string); + } + Unicode(loc_digits) => { + todo!("parse unicode digits {:?}", loc_digits); + } + Interpolated(loc_expr) => { + return Pattern2::UnsupportedPattern(loc_expr.region); + } + EscapedChar(escaped) => buf.push(unescape_char(escaped)), + } + } + } + + Pattern2::StrLiteral(PoolStr::new(&buf, pool)) +} + +/// When we detect a malformed pattern like `3.X` or `0b5`, +/// report it to Env and return an UnsupportedPattern runtime error pattern. +fn malformed_pattern<'a>( + env: &mut Env<'a>, + problem: MalformedPatternProblem, + region: Region, +) -> Pattern2 { + env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( + problem, region, + ))); + + Pattern2::MalformedPattern(problem, region) +} diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs new file mode 100644 index 0000000000..8279cd04e5 --- /dev/null +++ b/ast/src/lang/core/types.rs @@ -0,0 +1,867 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] +// use roc_can::expr::Output; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, TagName}; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; +use roc_types::types::{Problem, RecordField}; +use roc_types::{subs::Variable, types::ErrorType}; + +use crate::lang::env::Env; +use crate::lang::scope::Scope; +use crate::pool::pool::{NodeId, Pool}; +use crate::pool::pool_str::PoolStr; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; + +pub type TypeId = NodeId; + +#[derive(Debug)] +pub enum Type2 { + Variable(Variable), + + Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B + AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B + + // 32B + HostExposedAlias { + name: Symbol, // 8B + arguments: PoolVec<(PoolStr, TypeId)>, // 12B + actual_var: Variable, // 4B + actual: TypeId, // 4B + }, + EmptyTagUnion, + TagUnion(PoolVec<(TagName, PoolVec)>, TypeId), // 16B = 12B + 4B + RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec)>, TypeId), // 20B = 4B + 12B + 4B + + EmptyRec, + Record(PoolVec<(PoolStr, RecordField)>, TypeId), // 16B = 12B + 4B + + Function(PoolVec, TypeId, TypeId), // 20B = 12B + 4B + 4B + Apply(Symbol, PoolVec), // 20B = 8B + 12B + + Erroneous(Problem2), +} + +#[derive(Debug)] +pub enum Problem2 { + CanonicalizationProblem, + CircularType(Symbol, NodeId), // 12B = 8B + 4B + CyclicAlias(Symbol, PoolVec), // 20B = 8B + 12B + UnrecognizedIdent(PoolStr), // 8B + Shadowed(Located), + BadTypeArguments { + symbol: Symbol, // 8B + type_got: u8, // 1B + alias_needs: u8, // 1B + }, + InvalidModule, + SolvedTypeError, +} + +impl ShallowClone for Type2 { + fn shallow_clone(&self) -> Self { + match self { + Self::Variable(var) => Self::Variable(*var), + Self::Alias(symbol, args, alias_type_id) => { + Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone()) + } + Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()), + Self::Function(args, closure_type_id, ret_type_id) => Self::Function( + args.shallow_clone(), + closure_type_id.clone(), + ret_type_id.clone(), + ), + rest => todo!("{:?}", rest), + } + } +} + +impl Type2 { + fn substitute(_pool: &mut Pool, _subs: &MutMap, _type_id: TypeId) { + todo!() + } + + pub fn variables(&self, pool: &mut Pool) -> MutSet { + use Type2::*; + + let mut stack = vec![self]; + let mut result = MutSet::default(); + + while let Some(this) = stack.pop() { + match this { + Variable(v) => { + result.insert(*v); + } + Alias(_, _, actual) | AsAlias(_, _, actual) => { + stack.push(pool.get(*actual)); + } + HostExposedAlias { + actual_var, actual, .. + } => { + result.insert(*actual_var); + stack.push(pool.get(*actual)); + } + EmptyTagUnion | EmptyRec | Erroneous(_) => {} + TagUnion(tags, ext) => { + for (_, args) in tags.iter(pool) { + stack.extend(args.iter(pool)); + } + stack.push(pool.get(*ext)); + } + RecursiveTagUnion(rec, tags, ext) => { + for (_, args) in tags.iter(pool) { + stack.extend(args.iter(pool)); + } + stack.push(pool.get(*ext)); + result.insert(*rec); + } + Record(fields, ext) => { + for (_, field) in fields.iter(pool) { + stack.push(pool.get(*field.as_inner())); + } + stack.push(pool.get(*ext)); + } + Function(args, closure, result) => { + stack.extend(args.iter(pool)); + stack.push(pool.get(*closure)); + stack.push(pool.get(*result)); + } + Apply(_, args) => { + stack.extend(args.iter(pool)); + } + } + } + + result + } + + pub fn contains_symbol(&self, _pool: &mut Pool, _needle: Symbol) -> bool { + todo!() + } + + pub fn substitute_alias(&self, _pool: &mut Pool, _needle: Symbol, _actual: Self) { + todo!() + } +} + +impl NodeId { + pub fn variables(&self, _pool: &mut Pool) -> MutSet { + todo!() + } +} + +/// A temporary data structure to return a bunch of values to Def construction +pub enum Signature { + FunctionWithAliases { + annotation: Type2, + arguments: PoolVec, + closure_type_id: TypeId, + return_type_id: TypeId, + }, + Function { + arguments: PoolVec, + closure_type_id: TypeId, + return_type_id: TypeId, + }, + Value { + annotation: Type2, + }, +} + +pub enum Annotation2<'a> { + Annotation { + named_rigids: MutMap<&'a str, Variable>, + unnamed_rigids: MutSet, + symbols: MutSet, + signature: Signature, + }, + Erroneous(roc_types::types::Problem), +} + +pub fn to_annotation2<'a>( + env: &mut Env, + scope: &mut Scope, + annotation: &'a roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) -> Annotation2<'a> { + let mut references = References::default(); + + let annotation = to_type2(env, scope, &mut references, annotation, region); + + // we dealias until we hit a non-alias, then we either hit a function type (and produce a + // function annotation) or anything else (and produce a value annotation) + match annotation { + Type2::Function(arguments, closure_type_id, return_type_id) => { + let References { + named, + unnamed, + symbols, + .. + } = references; + + let signature = Signature::Function { + arguments, + closure_type_id, + return_type_id, + }; + + Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + } + } + Type2::Alias(_, _, _) => { + // most of the time, the annotation is not an alias, so this case is comparatively + // less efficient + shallow_dealias(env, references, annotation) + } + _ => { + let References { + named, + unnamed, + symbols, + .. + } = references; + + let signature = Signature::Value { annotation }; + + Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + } + } + } +} + +fn shallow_dealias<'a>( + env: &mut Env, + references: References<'a>, + annotation: Type2, +) -> Annotation2<'a> { + let References { + named, + unnamed, + symbols, + .. + } = references; + let mut inner = &annotation; + + loop { + match inner { + Type2::Alias(_, _, actual) => { + inner = env.pool.get(*actual); + } + Type2::Function(arguments, closure_type_id, return_type_id) => { + let signature = Signature::FunctionWithAliases { + arguments: arguments.shallow_clone(), + closure_type_id: *closure_type_id, + return_type_id: *return_type_id, + annotation, + }; + + return Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + }; + } + _ => { + let signature = Signature::Value { annotation }; + + return Annotation2::Annotation { + named_rigids: named, + unnamed_rigids: unnamed, + symbols, + signature, + }; + } + } + } +} + +#[derive(Default)] +pub struct References<'a> { + named: MutMap<&'a str, Variable>, + unnamed: MutSet, + hidden: MutSet, + symbols: MutSet, +} + +pub fn to_type_id<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + annotation: &roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) -> TypeId { + let type2 = to_type2(env, scope, rigids, annotation, region); + + env.add(type2, region) +} + +pub fn as_type_id<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + type_id: TypeId, + annotation: &roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) { + let type2 = to_type2(env, scope, rigids, annotation, region); + + env.pool[type_id] = type2; + env.set_region(type_id, region); +} + +pub fn to_type2<'a>( + env: &mut Env, + scope: &mut Scope, + references: &mut References<'a>, + annotation: &roc_parse::ast::TypeAnnotation<'a>, + region: Region, +) -> Type2 { + use roc_parse::ast::TypeAnnotation::*; + + match annotation { + Apply(module_name, ident, targs) => { + match to_type_apply(env, scope, references, module_name, ident, targs, region) { + TypeApply::Apply(symbol, args) => { + references.symbols.insert(symbol); + Type2::Apply(symbol, args) + } + TypeApply::Alias(symbol, args, actual) => { + references.symbols.insert(symbol); + Type2::Alias(symbol, args, actual) + } + TypeApply::Erroneous(_problem) => { + // Type2::Erroneous(problem) + todo!() + } + } + } + Function(argument_types, return_type) => { + let arguments = PoolVec::with_capacity(argument_types.len() as u32, env.pool); + + for (type_id, loc_arg) in arguments.iter_node_ids().zip(argument_types.iter()) { + as_type_id( + env, + scope, + references, + type_id, + &loc_arg.value, + loc_arg.region, + ); + } + + let return_type_id = to_type_id( + env, + scope, + references, + &return_type.value, + return_type.region, + ); + + let closure_type = Type2::Variable(env.var_store.fresh()); + let closure_type_id = env.pool.add(closure_type); + + Type2::Function(arguments, closure_type_id, return_type_id) + } + BoundVariable(v) => { + // a rigid type variable + match references.named.get(v) { + Some(var) => Type2::Variable(*var), + None => { + let var = env.var_store.fresh(); + + references.named.insert(v, var); + + Type2::Variable(var) + } + } + } + Wildcard | Malformed(_) => { + let var = env.var_store.fresh(); + + references.unnamed.insert(var); + + Type2::Variable(var) + } + Record { fields, ext, .. } => { + let field_types_map = can_assigned_fields(env, scope, references, fields, region); + + let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool); + + for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) { + let poolstr = PoolStr::new(label, env.pool); + + let rec_field = match field { + RecordField::Optional(_) => { + let field_id = env.pool.add(field.into_inner()); + RecordField::Optional(field_id) + } + RecordField::Demanded(_) => { + let field_id = env.pool.add(field.into_inner()); + RecordField::Demanded(field_id) + } + RecordField::Required(_) => { + let field_id = env.pool.add(field.into_inner()); + RecordField::Required(field_id) + } + }; + env.pool[node_id] = (poolstr, rec_field); + } + + let ext_type = match ext { + Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), + None => env.add(Type2::EmptyRec, region), + }; + + Type2::Record(field_types, ext_type) + } + TagUnion { tags, ext, .. } => { + let tag_types_vec = can_tags(env, scope, references, tags, region); + + let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool); + + for (node_id, (tag_name, field)) in tag_types.iter_node_ids().zip(tag_types_vec) { + env.pool[node_id] = (tag_name, field); + } + + let ext_type = match ext { + Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), + None => env.add(Type2::EmptyTagUnion, region), + }; + + Type2::TagUnion(tag_types, ext_type) + } + As(loc_inner, _spaces, loc_as) => { + // e.g. `{ x : Int, y : Int } as Point }` + match loc_as.value { + Apply(module_name, ident, loc_vars) if module_name.is_empty() => { + let symbol = match scope.introduce( + ident.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => symbol, + + Err((_original_region, _shadow)) => { + // let problem = Problem2::Shadowed(original_region, shadow.clone()); + + // env.problem(roc_problem::can::Problem::ShadowingInAnnotation { + // original_region, + // shadow, + // }); + + // return Type2::Erroneous(problem); + todo!(); + } + }; + + let inner_type = to_type2(env, scope, references, &loc_inner.value, region); + let vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); + + let lowercase_vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); + + for ((loc_var, named_id), var_id) in loc_vars + .iter() + .zip(lowercase_vars.iter_node_ids()) + .zip(vars.iter_node_ids()) + { + match loc_var.value { + BoundVariable(ident) => { + let var_name = ident; + + if let Some(var) = references.named.get(&var_name) { + let poolstr = PoolStr::new(var_name, env.pool); + + let type_id = env.pool.add(Type2::Variable(*var)); + env.pool[var_id] = (poolstr.shallow_clone(), type_id); + + env.pool[named_id] = (poolstr, *var); + env.set_region(named_id, loc_var.region); + } else { + let var = env.var_store.fresh(); + + references.named.insert(var_name.clone(), var); + let poolstr = PoolStr::new(var_name, env.pool); + + let type_id = env.pool.add(Type2::Variable(var)); + env.pool[var_id] = (poolstr.shallow_clone(), type_id); + + env.pool[named_id] = (poolstr, var); + env.set_region(named_id, loc_var.region); + } + } + _ => { + // If anything other than a lowercase identifier + // appears here, the whole annotation is invalid. + return Type2::Erroneous(Problem2::CanonicalizationProblem); + } + } + } + + let alias_actual = inner_type; + // TODO instantiate recursive tag union + // let alias_actual = if let Type2::TagUnion(tags, ext) = inner_type { + // let rec_var = env.var_store.fresh(); + // + // let mut new_tags = Vec::with_capacity(tags.len()); + // for (tag_name, args) in tags { + // let mut new_args = Vec::with_capacity(args.len()); + // for arg in args { + // let mut new_arg = arg.clone(); + // new_arg.substitute_alias(symbol, &Type2::Variable(rec_var)); + // new_args.push(new_arg); + // } + // new_tags.push((tag_name.clone(), new_args)); + // } + // Type2::RecursiveTagUnion(rec_var, new_tags, ext) + // } else { + // inner_type + // }; + + let mut hidden_variables = MutSet::default(); + hidden_variables.extend(alias_actual.variables(env.pool)); + + for (_, var) in lowercase_vars.iter(env.pool) { + hidden_variables.remove(var); + } + + let alias_actual_id = env.pool.add(alias_actual); + scope.add_alias(env.pool, symbol, lowercase_vars, alias_actual_id); + + let alias = scope.lookup_alias(symbol).unwrap(); + // local_aliases.insert(symbol, alias.clone()); + + // TODO host-exposed + // if vars.is_empty() && env.home == symbol.module_id() { + // let actual_var = env.var_store.fresh(); + // rigids.host_exposed.insert(symbol, actual_var); + // Type::HostExposedAlias { + // name: symbol, + // arguments: vars, + // actual: Box::new(alias.typ.clone()), + // actual_var, + // } + // } else { + // Type::Alias(symbol, vars, Box::new(alias.typ.clone())) + // } + Type2::AsAlias(symbol, vars, alias.actual) + } + _ => { + // This is a syntactically invalid type alias. + Type2::Erroneous(Problem2::CanonicalizationProblem) + } + } + } + SpaceBefore(nested, _) | SpaceAfter(nested, _) => { + to_type2(env, scope, references, nested, region) + } + } +} + +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +fn can_assigned_fields<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + fields: &&[Located>>], + region: Region, +) -> MutMap<&'a str, RecordField> { + use roc_parse::ast::AssignedField::*; + use roc_types::types::RecordField::*; + + // SendMap doesn't have a `with_capacity` + let mut field_types = MutMap::default(); + + // field names we've seen so far in this record + let mut seen = std::collections::HashMap::with_capacity(fields.len()); + + 'outer: for loc_field in fields.iter() { + let mut field = &loc_field.value; + + // use this inner loop to unwrap the SpaceAfter/SpaceBefore + // when we find the name of this field, break out of the loop + // with that value, so we can check whether the field name is + // a duplicate + let new_name = 'inner: loop { + match field { + RequiredValue(field_name, _, annotation) => { + let field_type = + to_type2(env, scope, rigids, &annotation.value, annotation.region); + + let label = field_name.value; + field_types.insert(label, Required(field_type)); + + break 'inner label; + } + OptionalValue(field_name, _, annotation) => { + let field_type = + to_type2(env, scope, rigids, &annotation.value, annotation.region); + + let label = field_name.value; + field_types.insert(label.clone(), Optional(field_type)); + + break 'inner label; + } + LabelOnly(loc_field_name) => { + // Interpret { a, b } as { a : a, b : b } + let field_name = loc_field_name.value; + let field_type = { + if let Some(var) = rigids.named.get(&field_name) { + Type2::Variable(*var) + } else { + let field_var = env.var_store.fresh(); + rigids.named.insert(field_name, field_var); + Type2::Variable(field_var) + } + }; + + field_types.insert(field_name.clone(), Required(field_type)); + + break 'inner field_name; + } + SpaceBefore(nested, _) | SpaceAfter(nested, _) => { + // check the nested field instead + field = nested; + continue 'inner; + } + Malformed(_) => { + // TODO report this? + // completely skip this element, advance to the next tag + continue 'outer; + } + } + }; + + // ensure that the new name is not already in this record: + // note that the right-most tag wins when there are two with the same name + if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) { + env.problem(roc_problem::can::Problem::DuplicateRecordFieldType { + field_name: new_name.into(), + record_region: region, + field_region: loc_field.region, + replaced_region, + }); + } + } + + field_types +} + +fn can_tags<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + tags: &'a [Located>], + region: Region, +) -> Vec<(TagName, PoolVec)> { + use roc_parse::ast::Tag; + let mut tag_types = Vec::with_capacity(tags.len()); + + // tag names we've seen so far in this tag union + let mut seen = std::collections::HashMap::with_capacity(tags.len()); + + 'outer: for loc_tag in tags.iter() { + let mut tag = &loc_tag.value; + + // use this inner loop to unwrap the SpaceAfter/SpaceBefore + // when we find the name of this tag, break out of the loop + // with that value, so we can check whether the tag name is + // a duplicate + let new_name = 'inner: loop { + match tag { + Tag::Global { name, args } => { + let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); + + for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { + as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); + } + + let tag_name = TagName::Global(name.value.into()); + tag_types.push((tag_name.clone(), arg_types)); + + break 'inner tag_name; + } + Tag::Private { name, args } => { + let ident_id = env.ident_ids.get_or_insert(&name.value.into()); + let symbol = Symbol::new(env.home, ident_id); + + let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); + + for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { + as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); + } + + let tag_name = TagName::Private(symbol); + tag_types.push((tag_name.clone(), arg_types)); + + break 'inner tag_name; + } + Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => { + // check the nested tag instead + tag = nested; + continue 'inner; + } + Tag::Malformed(_) => { + // TODO report this? + // completely skip this element, advance to the next tag + continue 'outer; + } + } + }; + + // ensure that the new name is not already in this tag union: + // note that the right-most tag wins when there are two with the same name + if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) { + env.problem(roc_problem::can::Problem::DuplicateTag { + tag_region: loc_tag.region, + tag_union_region: region, + replaced_region, + tag_name: new_name, + }); + } + } + + tag_types +} + +enum TypeApply { + Apply(Symbol, PoolVec), + Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), + Erroneous(roc_types::types::Problem), +} + +#[inline(always)] +fn to_type_apply<'a>( + env: &mut Env, + scope: &mut Scope, + rigids: &mut References<'a>, + module_name: &str, + ident: &str, + type_arguments: &[Located>], + region: Region, +) -> TypeApply { + let symbol = if module_name.is_empty() { + // Since module_name was empty, this is an unqualified type. + // Look it up in scope! + let ident: Ident = (*ident).into(); + + match scope.lookup(&ident, region) { + Ok(symbol) => symbol, + Err(problem) => { + env.problem(roc_problem::can::Problem::RuntimeError(problem)); + + return TypeApply::Erroneous(Problem::UnrecognizedIdent(ident.into())); + } + } + } else { + match env.qualified_lookup(module_name, ident, region) { + Ok(symbol) => symbol, + Err(problem) => { + // Either the module wasn't imported, or + // it was imported but it doesn't expose this ident. + env.problem(roc_problem::can::Problem::RuntimeError(problem)); + + return TypeApply::Erroneous(Problem::UnrecognizedIdent((*ident).into())); + } + } + }; + + let argument_type_ids = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); + + for (type_id, loc_arg) in argument_type_ids.iter_node_ids().zip(type_arguments.iter()) { + as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); + } + + let args = type_arguments; + let opt_alias = scope.lookup_alias(symbol); + match opt_alias { + Some(ref alias) => { + // use a known alias + let actual = alias.actual; + let mut substitutions: MutMap = MutMap::default(); + + if alias.targs.len() != args.len() { + let error = TypeApply::Erroneous(Problem::BadTypeArguments { + symbol, + region, + alias_needs: alias.targs.len() as u8, + type_got: args.len() as u8, + }); + return error; + } + + let arguments = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); + + let it = arguments.iter_node_ids().zip( + argument_type_ids + .iter_node_ids() + .zip(alias.targs.iter_node_ids()), + ); + + for (node_id, (type_id, loc_var_id)) in it { + let loc_var = &env.pool[loc_var_id]; + let name = loc_var.0.shallow_clone(); + let var = loc_var.1; + + env.pool[node_id] = (name, type_id); + + substitutions.insert(var, type_id); + } + + // make sure the recursion variable is freshly instantiated + // have to allocate these outside of the if for lifetime reasons... + let new = env.var_store.fresh(); + let fresh = env.pool.add(Type2::Variable(new)); + if let Type2::RecursiveTagUnion(rvar, ref tags, ext) = &mut env.pool[actual] { + substitutions.insert(*rvar, fresh); + + env.pool[actual] = Type2::RecursiveTagUnion(new, tags.shallow_clone(), *ext); + } + + // make sure hidden variables are freshly instantiated + for var_id in alias.hidden_variables.iter_node_ids() { + let var = env.pool[var_id]; + let fresh = env.pool.add(Type2::Variable(env.var_store.fresh())); + substitutions.insert(var, fresh); + } + + // instantiate variables + Type2::substitute(env.pool, &substitutions, actual); + + TypeApply::Alias(symbol, arguments, actual) + } + None => TypeApply::Apply(symbol, argument_type_ids), + } +} + +#[derive(Debug)] +pub struct Alias { + pub targs: PoolVec<(PoolStr, Variable)>, + pub actual: TypeId, + + /// hidden type variables, like the closure variable in `a -> b` + pub hidden_variables: PoolVec, +} + +impl ShallowClone for Alias { + fn shallow_clone(&self) -> Self { + Self { + targs: self.targs.shallow_clone(), + hidden_variables: self.hidden_variables.shallow_clone(), + actual: self.actual, + } + } +} diff --git a/ast/src/lang/core/val_def.rs b/ast/src/lang/core/val_def.rs new file mode 100644 index 0000000000..c97df3f810 --- /dev/null +++ b/ast/src/lang/core/val_def.rs @@ -0,0 +1,91 @@ +use crate::{lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, pool::{pool::{NodeId, Pool}, shallow_clone::ShallowClone}}; +use roc_types::subs::{Variable}; + +use super::{expr::expr2::ExprId, pattern::{Pattern2, PatternId}, types::TypeId}; + +#[derive(Debug)] +pub enum ValueDef { + WithAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + type_id: TypeId, + rigids: Rigids, + expr_var: Variable, // 4B + }, + NoAnnotation { + pattern_id: PatternId, // 4B + expr_id: ExprId, // 4B + expr_var: Variable, // 4B + }, +} + +impl ShallowClone for ValueDef { + fn shallow_clone(&self) -> Self { + match self { + Self::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => Self::WithAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + type_id: *type_id, + rigids: rigids.shallow_clone(), + expr_var: *expr_var, + }, + Self::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => Self::NoAnnotation { + pattern_id: *pattern_id, + expr_id: *expr_id, + expr_var: *expr_var, + }, + } + } +} + +impl ValueDef { + pub fn get_expr_id(&self) -> ExprId { + match self { + ValueDef::WithAnnotation { expr_id, .. } => *expr_id, + ValueDef::NoAnnotation { expr_id, .. } => *expr_id, + } + } + + pub fn get_pattern_id(&self) -> NodeId { + match self { + ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, + ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, + } + } +} + +pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { + match val_def { + ValueDef::WithAnnotation { + pattern_id, + expr_id, + type_id, + rigids, + expr_var, + } => { + format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) + } + ValueDef::NoAnnotation { + pattern_id, + expr_id, + expr_var, + } => { + format!( + "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", + pool.get(*pattern_id), + expr2_to_string(*expr_id, pool), + expr_var + ) + } + } +} \ No newline at end of file diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs new file mode 100644 index 0000000000..5a6d212fcf --- /dev/null +++ b/ast/src/lang/env.rs @@ -0,0 +1,168 @@ +use bumpalo::{collections::Vec as BumpVec, Bump}; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, ModuleName}; +use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; +use roc_problem::can::{Problem, RuntimeError}; +use roc_region::all::{Located, Region}; +use roc_types::subs::{VarStore}; + +use crate::pool::pool::{NodeId, Pool}; + +use super::core::def::def::References; + +#[derive(Debug)] +pub struct Env<'a> { + pub home: ModuleId, + pub var_store: &'a mut VarStore, + pub pool: &'a mut Pool, + pub arena: &'a Bump, + + pub problems: BumpVec<'a, Problem>, + + pub dep_idents: MutMap, + pub module_ids: &'a ModuleIds, + pub ident_ids: IdentIds, + pub exposed_ident_ids: IdentIds, + + pub closures: MutMap, + /// Symbols which were referenced by qualified lookups. + pub qualified_lookups: MutSet, + + pub top_level_symbols: MutSet, + + pub closure_name_symbol: Option, + pub tailcallable_symbol: Option, +} + +impl<'a> Env<'a> { + pub fn new( + home: ModuleId, + arena: &'a Bump, + pool: &'a mut Pool, + var_store: &'a mut VarStore, + dep_idents: MutMap, + module_ids: &'a ModuleIds, + exposed_ident_ids: IdentIds, + ) -> Env<'a> { + Env { + home, + arena, + pool, + problems: BumpVec::new_in(arena), + var_store, + dep_idents, + module_ids, + ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later + exposed_ident_ids, + closures: MutMap::default(), + qualified_lookups: MutSet::default(), + tailcallable_symbol: None, + closure_name_symbol: None, + top_level_symbols: MutSet::default(), + } + } + + pub fn add(&mut self, item: T, region: Region) -> NodeId { + let id = self.pool.add(item); + self.set_region(id, region); + + id + } + + pub fn problem(&mut self, problem: Problem) { + self.problems.push(problem); + } + + pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { + dbg!("Don't Forget to set the region eventually"); + } + + pub fn register_closure(&mut self, symbol: Symbol, references: References) { + self.closures.insert(symbol, references); + } + + /// Generates a unique, new symbol like "$1" or "$5", + /// using the home module as the module_id. + /// + /// This is used, for example, during canonicalization of an Expr::Closure + /// to generate a unique symbol to refer to that closure. + pub fn gen_unique_symbol(&mut self) -> Symbol { + let ident_id = self.ident_ids.gen_unique(); + + Symbol::new(self.home, ident_id) + } + + /// Returns Err if the symbol resolved, but it was not exposed by the given module + pub fn qualified_lookup( + &mut self, + module_name: &str, + ident: &str, + region: Region, + ) -> Result { + debug_assert!( + !module_name.is_empty(), + "Called env.qualified_lookup with an unqualified ident: {:?}", + ident + ); + + let module_name: ModuleName = module_name.into(); + + match self.module_ids.get_id(&module_name) { + Some(&module_id) => { + let ident: Ident = ident.into(); + + // You can do qualified lookups on your own module, e.g. + // if I'm in the Foo module, I can do a `Foo.bar` lookup. + if module_id == self.home { + match self.ident_ids.get_id(&ident) { + Some(ident_id) => { + let symbol = Symbol::new(module_id, *ident_id); + + self.qualified_lookups.insert(symbol); + + Ok(symbol) + } + None => Err(RuntimeError::LookupNotInScope( + Located { + value: ident, + region, + }, + self.ident_ids + .idents() + .map(|(_, string)| string.as_ref().into()) + .collect(), + )), + } + } else { + match self + .dep_idents + .get(&module_id) + .and_then(|exposed_ids| exposed_ids.get_id(&ident)) + { + Some(ident_id) => { + let symbol = Symbol::new(module_id, *ident_id); + + self.qualified_lookups.insert(symbol); + + Ok(symbol) + } + None => Err(RuntimeError::ValueNotExposed { + module_name: ModuleName::from(module_name), + ident, + region, + }), + } + } + } + None => Err(RuntimeError::ModuleNotImported { + module_name, + imported_modules: self + .module_ids + .available_modules() + .map(|string| string.as_ref().into()) + .collect(), + region, + }), + } + } +} \ No newline at end of file diff --git a/ast/src/lang/mod.rs b/ast/src/lang/mod.rs new file mode 100644 index 0000000000..9437f39470 --- /dev/null +++ b/ast/src/lang/mod.rs @@ -0,0 +1,4 @@ +pub mod core; +mod scope; +mod rigids; +mod env; diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs new file mode 100644 index 0000000000..94bca10038 --- /dev/null +++ b/ast/src/lang/rigids.rs @@ -0,0 +1,80 @@ +use std::{collections::{HashMap, HashSet}, hash::BuildHasherDefault}; + +use crate::pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}; +use roc_collections::all::WyHash; +use roc_types::subs::{Variable}; + +#[derive(Debug)] +pub struct Rigids { + pub names: PoolVec<(Option, Variable)>, // 8B + padding: [u8; 1], +} + +#[allow(clippy::needless_collect)] +impl Rigids { + pub fn new( + named: HashMap<&str, Variable, BuildHasherDefault>, + unnamed: HashSet>, + pool: &mut Pool, + ) -> Self { + let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool); + + let mut temp_names = Vec::new(); + + temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); + + temp_names.extend(unnamed.iter().map(|var| (None, *var))); + + for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) { + let poolstr = opt_name.map(|name| PoolStr::new(name, pool)); + + pool[node_id] = (poolstr, variable); + } + + Self { + names, + padding: Default::default(), + } + } + + pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> { + let named = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + if let Some(pool_str) = opt_pool_str { + Some((*pool_str, *var)) + } else { + None + } + }) + .collect::>(); + + PoolVec::new(named.into_iter(), pool) + } + + pub fn unnamed(&self, pool: &mut Pool) -> PoolVec { + let unnamed = self + .names + .iter(pool) + .filter_map(|(opt_pool_str, var)| { + if opt_pool_str.is_none() { + Some(*var) + } else { + None + } + }) + .collect::>(); + + PoolVec::new(unnamed.into_iter(), pool) + } +} + +impl ShallowClone for Rigids { + fn shallow_clone(&self) -> Self { + Self { + names: self.names.shallow_clone(), + padding: self.padding, + } + } +} diff --git a/ast/src/lang/scope.rs b/ast/src/lang/scope.rs new file mode 100644 index 0000000000..fa8cc7e9d2 --- /dev/null +++ b/ast/src/lang/scope.rs @@ -0,0 +1,330 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +#![allow(unused_imports)] + +use crate::pool::pool::Pool; +use crate::pool::pool_str::PoolStr; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, Lowercase}; +use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_problem::can::RuntimeError; +use roc_region::all::{Located, Region}; +use roc_types::{ + builtin_aliases, + solved_types::{BuiltinAlias, FreeVars, SolvedType}, + subs::{VarId, VarStore, Variable}, +}; + +use super::core::types::{Alias, Type2, TypeId}; + +fn solved_type_to_type_id( + pool: &mut Pool, + solved_type: &SolvedType, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> TypeId { + let typ2 = to_type2(pool, solved_type, free_vars, var_store); + + pool.add(typ2) +} + +fn to_type2( + pool: &mut Pool, + solved_type: &SolvedType, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> Type2 { + match solved_type { + SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual) => { + let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); + + for (type_variable_node_id, (lowercase, solved_arg)) in type_variables + .iter_node_ids() + .zip(solved_type_variables.iter()) + { + let typ2 = to_type2(pool, solved_arg, free_vars, var_store); + + let node = pool.add(typ2); + + pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node); + } + + let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store); + + let actual = pool.add(actual_typ2); + + let typ2 = Type2::Alias(*symbol, type_variables, actual); + + typ2 + } + SolvedType::TagUnion(tags, ext) => { + let new_tags = PoolVec::with_capacity(tags.len() as u32, pool); + + for (tag_node_id, (tag_name, args)) in new_tags.iter_node_ids().zip(tags.iter()) { + let new_args: PoolVec = PoolVec::with_capacity(args.len() as u32, pool); + + for (arg_node_id, arg) in new_args.iter_node_ids().zip(args.iter()) { + let node = to_type2(pool, arg, free_vars, var_store); + + pool[arg_node_id] = node; + } + + pool[tag_node_id] = (tag_name.clone(), new_args); + } + + let actual_typ2 = to_type2(pool, ext, free_vars, var_store); + + let actual = pool.add(actual_typ2); + + let typ2 = Type2::TagUnion(new_tags, actual); + + typ2 + } + SolvedType::Flex(var_id) => { + Type2::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)) + } + SolvedType::EmptyTagUnion => Type2::EmptyTagUnion, + rest => todo!("{:?}", rest), + } +} + +fn var_id_to_flex_var( + var_id: VarId, + free_vars: &mut FreeVars, + var_store: &mut VarStore, +) -> Variable { + if let Some(var) = free_vars.unnamed_vars.get(&var_id) { + *var + } else { + let var = var_store.fresh(); + free_vars.unnamed_vars.insert(var_id, var); + + var + } +} + +#[derive(Debug)] +pub struct Scope { + /// All the identifiers in scope, mapped to were they were defined and + /// the Symbol they resolve to. + idents: MutMap, + + /// A cache of all the symbols in scope. This makes lookups much + /// faster when checking for unused defs and unused arguments. + symbols: MutMap, + + /// The type aliases currently in scope + aliases: MutMap, + + /// The current module being processed. This will be used to turn + /// unqualified idents into Symbols. + home: ModuleId, +} + +impl Scope { + pub fn new(home: ModuleId, pool: &mut Pool, var_store: &mut VarStore) -> Scope { + let solved_aliases = builtin_aliases::aliases(); + let mut aliases = MutMap::default(); + + for (symbol, builtin_alias) in solved_aliases { + // let BuiltinAlias { region, vars, typ } = builtin_alias; + let BuiltinAlias { vars, typ, .. } = builtin_alias; + + let mut free_vars = FreeVars::default(); + + // roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); + let actual = solved_type_to_type_id(pool, &typ, &mut free_vars, var_store); + + // make sure to sort these variables to make them line up with the type arguments + let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); + type_variables.sort(); + + debug_assert_eq!(vars.len(), type_variables.len()); + let variables = PoolVec::with_capacity(vars.len() as u32, pool); + + let it = variables + .iter_node_ids() + .zip(vars.iter()) + .zip(type_variables); + for ((node_id, loc_name), (_, var)) in it { + // TODO region is ignored, but "fake" anyway. How to resolve? + let name = PoolStr::new(loc_name.value.as_str(), pool); + pool[node_id] = (name, var); + } + + let alias = Alias { + actual, + /// We know that builtin aliases have no hiddden variables (e.g. in closures) + hidden_variables: PoolVec::empty(pool), + targs: variables, + }; + + aliases.insert(symbol, alias); + } + + let idents = Symbol::default_in_scope(); + let idents: MutMap<_, _> = idents.into_iter().collect(); + + Scope { + home, + idents, + symbols: MutMap::default(), + aliases, + } + } + + pub fn idents(&self) -> impl Iterator { + self.idents.iter() + } + + pub fn symbols(&self) -> impl Iterator + '_ { + self.symbols.iter().map(|(x, y)| (*x, *y)) + } + + pub fn contains_ident(&self, ident: &Ident) -> bool { + self.idents.contains_key(ident) + } + + pub fn contains_symbol(&self, symbol: Symbol) -> bool { + self.symbols.contains_key(&symbol) + } + + pub fn num_idents(&self) -> usize { + self.idents.len() + } + + pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result { + match self.idents.get(ident) { + Some((symbol, _)) => Ok(*symbol), + None => Err(RuntimeError::LookupNotInScope( + Located { + region, + value: ident.clone().into(), + }, + self.idents.keys().map(|v| v.as_ref().into()).collect(), + )), + } + } + + pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> { + self.aliases.get(&symbol) + } + + /// Introduce a new ident to scope. + /// + /// Returns Err if this would shadow an existing ident, including the + /// Symbol and Region of the ident we already had in scope under that name. + pub fn introduce( + &mut self, + ident: Ident, + exposed_ident_ids: &IdentIds, + all_ident_ids: &mut IdentIds, + region: Region, + ) -> Result)> { + match self.idents.get(&ident) { + Some((_, original_region)) => { + let shadow = Located { + value: ident, + region, + }; + + Err((*original_region, shadow)) + } + None => { + // If this IdentId was already added previously + // when the value was exposed in the module header, + // use that existing IdentId. Otherwise, create a fresh one. + let ident_id = match exposed_ident_ids.get_id(&ident) { + Some(ident_id) => *ident_id, + None => all_ident_ids.add(ident.clone().into()), + }; + + let symbol = Symbol::new(self.home, ident_id); + + self.symbols.insert(symbol, region); + self.idents.insert(ident, (symbol, region)); + + Ok(symbol) + } + } + } + + /// Ignore an identifier. + /// + /// Used for record guards like { x: Just _ } + pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { + let ident_id = all_ident_ids.add(ident.into()); + Symbol::new(self.home, ident_id) + } + + /// Import a Symbol from another module into this module's top-level scope. + /// + /// Returns Err if this would shadow an existing ident, including the + /// Symbol and Region of the ident we already had in scope under that name. + pub fn import( + &mut self, + ident: Ident, + symbol: Symbol, + region: Region, + ) -> Result<(), (Symbol, Region)> { + match self.idents.get(&ident) { + Some(shadowed) => Err(*shadowed), + None => { + self.symbols.insert(symbol, region); + self.idents.insert(ident, (symbol, region)); + + Ok(()) + } + } + } + + pub fn add_alias( + &mut self, + pool: &mut Pool, + name: Symbol, + vars: PoolVec<(PoolStr, Variable)>, + typ: TypeId, + ) { + let mut hidden_variables = MutSet::default(); + hidden_variables.extend(typ.variables(pool)); + + for loc_var in vars.iter(pool) { + hidden_variables.remove(&loc_var.1); + } + + let hidden_variables_vec = PoolVec::with_capacity(hidden_variables.len() as u32, pool); + + for (node_id, var) in hidden_variables_vec.iter_node_ids().zip(hidden_variables) { + pool[node_id] = var; + } + + let alias = Alias { + targs: vars, + hidden_variables: hidden_variables_vec, + actual: typ, + }; + + self.aliases.insert(name, alias); + } + + pub fn contains_alias(&mut self, name: Symbol) -> bool { + self.aliases.contains_key(&name) + } +} + +impl ShallowClone for Scope { + fn shallow_clone(&self) -> Self { + Self { + idents: self.idents.clone(), + symbols: self.symbols.clone(), + aliases: self + .aliases + .iter() + .map(|(s, a)| (*s, a.shallow_clone())) + .collect(), + home: self.home, + } + } +} diff --git a/ast/src/lib.rs b/ast/src/lib.rs new file mode 100644 index 0000000000..84c6ff305b --- /dev/null +++ b/ast/src/lib.rs @@ -0,0 +1,5 @@ +pub mod lang; +pub mod pool; +mod constrain; +mod canonicalize; +mod ast_error; \ No newline at end of file diff --git a/ast/src/mod.rs b/ast/src/mod.rs new file mode 100644 index 0000000000..addb2cebaf --- /dev/null +++ b/ast/src/mod.rs @@ -0,0 +1,14 @@ +pub mod ast; +mod constrain; +pub mod lang; +mod module; +pub mod parse; +mod pattern; +pub mod pool; +pub mod roc_file; +mod scope; +mod solve; +mod types; +mod rigids; +mod canonicalize; +mod ast_error; \ No newline at end of file diff --git a/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs new file mode 100644 index 0000000000..2f99b8a071 --- /dev/null +++ b/ast/src/parse/parse_ast.rs @@ -0,0 +1,34 @@ + + +pub fn parse_from_string<'a>( + code_str: &'a str, + env: &mut Env<'a>, + ast_arena: &'a Bump, +) -> Result> { + let blank_line_indx = code_str + .find("\n\n") + .expect("I was expecting a double newline to split header and rest of code."); + + let header_str = &code_str[0..blank_line_indx]; + let tail_str = &code_str[blank_line_indx..]; + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + let region = Region::new(0, 0, 0, 0); + + let mut def_ids = Vec::::new(); + + let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; + + for def2 in def2_vec { + let def_id = env.pool.add(def2); + + def_ids.push(def_id); + } + + let ast_node_id = env.pool.add(Expr2::Blank); + + Ok(AST { + header: AppHeader::parse_from_string(header_str, ast_node_id), + def_ids, + }) +} \ No newline at end of file diff --git a/ast/src/parse/parse_expr.rs b/ast/src/parse/parse_expr.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ast/src/parse/parse_header.rs b/ast/src/parse/parse_header.rs new file mode 100644 index 0000000000..0b9f2016f9 --- /dev/null +++ b/ast/src/parse/parse_header.rs @@ -0,0 +1,11 @@ + +// TODO don't use mock struct and actually parse string +pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { + AppHeader { + app_name: "\"untitled-app\"".to_owned(), + packages_base: "\"platform\"".to_owned(), + imports: vec![], + provides: vec!["main".to_owned()], + ast_node_id, + } +} \ No newline at end of file diff --git a/ast/src/pool/mod.rs b/ast/src/pool/mod.rs new file mode 100644 index 0000000000..1b0cd90832 --- /dev/null +++ b/ast/src/pool/mod.rs @@ -0,0 +1,4 @@ +pub mod pool; +pub mod pool_str; +pub mod pool_vec; +pub mod shallow_clone; \ No newline at end of file diff --git a/ast/src/pool/pool.rs b/ast/src/pool/pool.rs new file mode 100644 index 0000000000..8663ca4bcb --- /dev/null +++ b/ast/src/pool/pool.rs @@ -0,0 +1,228 @@ +/// A memory pool of 32-byte nodes. The node value 0 is reserved for the pool's +/// use, and valid nodes may never have that value. +/// +/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes +/// into one page at a time, and when it runs out, it uses mmap to reserve an +/// anonymous memory page in which to store nodes. +/// +/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a +/// particular node by its NodeId, which is an opaque wrapper around a pointer. +/// +/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. +/// This is important for performance. +use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; +use std::any::type_name; +use std::marker::PhantomData; +use std::mem::size_of; +use std::ptr::null; + +pub const NODE_BYTES: usize = 32; + +// Each page has 128 slots. Each slot holds one 32B node +// This means each page is 4096B, which is the size of a memory page +// on typical systems where the compiler will be run. +// +// Nice things about this system include: +// * Allocating a new page is as simple as asking the OS for a memory page. +// * Since each node is 32B, each node's memory address will be a multiple of 16. +// * Thanks to the free lists and our consistent chunk sizes, we should +// end up with very little fragmentation. +// * Finding a slot for a given node should be very fast: see if the relevant +// free list has any openings; if not, try the next size up. +// +// Less nice things include: +// * This system makes it very hard to ever give a page back to the OS. +// We could try doing the Mesh Allocator strategy: whenever we allocate +// something, assign it to a random slot in the page, and then periodically +// try to merge two pages into one (by locking and remapping them in the OS) +// and then returning the redundant physical page back to the OS. This should +// work in theory, but is pretty complicated, and we'd need to schedule it. +// Keep in mind that we can't use the Mesh Allocator itself because it returns +// usize pointers, which would be too big for us to have 16B nodes. +// On the plus side, we could be okay with higher memory usage early on, +// and then later use the Mesh strategy to reduce long-running memory usage. +// +// With this system, we can allocate up to 4B nodes. If we wanted to keep +// a generational index in there, like https://crates.io/crates/sharded-slab +// does, we could use some of the 32 bits for that. For example, if we wanted +// to have a 5-bit generational index (supporting up to 32 generations), then +// we would have 27 bits remaining, meaning we could only support at most +// 134M nodes. Since the editor has a separate Pool for each module, is that +// enough for any single module we'll encounter in practice? Probably, and +// especially if we allocate super large collection literals on the heap instead +// of in the pool. +// +// Another possible design is to try to catch reuse bugs using an "ASan" like +// approach: in development builds, whenever we "free" a particular slot, we +// can add it to a dev-build-only "freed nodes" list and don't hand it back +// out (so, we leak the memory.) Then we can (again, in development builds only) +// check to see if we're about to store something in zeroed-out memory; if so, check +// to see if it was + +#[derive(Debug, Eq)] +pub (crate) struct NodeId { + pub (super) index: u32, + _phantom: PhantomData, +} + +impl Clone for NodeId { + fn clone(&self) -> Self { + NodeId { + index: self.index, + _phantom: PhantomData::default(), + } + } +} + +impl PartialEq for NodeId { + fn eq(&self, other: &Self) -> bool { + self.index == other.index + } +} + +impl Copy for NodeId {} + +#[derive(Debug)] +pub struct Pool { + pub (super) nodes: *mut [u8; NODE_BYTES], + num_nodes: u32, + capacity: u32, + // free_1node_slots: Vec>, +} + +impl Pool { + pub fn with_capacity(nodes: u32) -> Self { + // round up number of nodes requested to nearest page size in bytes + let bytes_per_page = page_size::get(); + let node_bytes = NODE_BYTES * nodes as usize; + let leftover = node_bytes % bytes_per_page; + let bytes_to_mmap = if leftover == 0 { + node_bytes + } else { + node_bytes + bytes_per_page - leftover + }; + + let nodes = unsafe { + // mmap anonymous memory pages - that is, contiguous virtual memory + // addresses from the OS which will be lazily translated into + // physical memory one 4096-byte page at a time, once we actually + // try to read or write in that page's address range. + libc::mmap( + null::() as *mut c_void, + bytes_to_mmap, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + 0, + 0, + ) + } as *mut [u8; NODE_BYTES]; + + // This is our actual capacity, in nodes. + // It might be higher than the requested capacity due to rounding up + // to nearest page size. + let capacity = (bytes_to_mmap / NODE_BYTES) as u32; + + Pool { + nodes, + num_nodes: 0, + capacity, + } + } + + pub fn add(&mut self, node: T) -> NodeId { + // It's only safe to store this if T fits in S. + debug_assert!( + size_of::() <= NODE_BYTES, + "{} has a size of {}, but it needs to be at most {}", + type_name::(), + size_of::(), + NODE_BYTES + ); + + let node_id = self.reserve(1); + let node_ptr = unsafe { self.nodes.offset(node_id.index as isize) } as *mut T; + + unsafe { *node_ptr = node }; + + node_id + } + + /// Reserves the given number of contiguous node slots, and returns + /// the NodeId of the first one. We only allow reserving 2^32 in a row. + pub (super) fn reserve(&mut self, nodes: u32) -> NodeId { + // TODO once we have a free list, look in there for an open slot first! + let index = self.num_nodes; + + if index < self.capacity { + self.num_nodes = index + nodes; + + NodeId { + index, + _phantom: PhantomData::default(), + } + } else { + todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows."); + } + } + + pub fn get<'a, 'b, T>(&'a self, node_id: NodeId) -> &'b T { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *const T; + + &*node_ptr + } + } + + pub fn get_mut(&mut self, node_id: NodeId) -> &mut T { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; + + &mut *node_ptr + } + } + + pub fn set(&mut self, node_id: NodeId, element: T) { + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; + + *node_ptr = element; + } + } + + // A node is available iff its bytes are all zeroes + #[allow(dead_code)] + fn is_available(&self, node_id: NodeId) -> bool { + debug_assert_eq!(size_of::(), NODE_BYTES); + + unsafe { + let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES]; + + *node_ptr == [0; NODE_BYTES] + } + } +} + +impl std::ops::Index> for Pool { + type Output = T; + + fn index(&self, node_id: NodeId) -> &Self::Output { + self.get(node_id) + } +} + +impl std::ops::IndexMut> for Pool { + fn index_mut(&mut self, node_id: NodeId) -> &mut Self::Output { + self.get_mut(node_id) + } +} + +impl Drop for Pool { + fn drop(&mut self) { + unsafe { + libc::munmap( + self.nodes as *mut c_void, + NODE_BYTES * self.capacity as usize, + ); + } + } +} \ No newline at end of file diff --git a/ast/src/pool/pool_str.rs b/ast/src/pool/pool_str.rs new file mode 100644 index 0000000000..02a9d47603 --- /dev/null +++ b/ast/src/pool/pool_str.rs @@ -0,0 +1,88 @@ + +use super::pool::{NodeId, Pool, NODE_BYTES}; +use super::shallow_clone::ShallowClone; +use libc::{c_void}; +use std::marker::PhantomData; +use std::mem::size_of; + + +/// A string containing at most 2^32 pool-allocated bytes. +#[derive(Debug, Copy, Clone)] +pub struct PoolStr { + first_node_id: NodeId<()>, + len: u32, +} + +#[test] +fn pool_str_size() { + assert_eq!(size_of::(), 8); +} + +impl PoolStr { + pub fn new(string: &str, pool: &mut Pool) -> Self { + debug_assert!(string.len() <= u32::MAX as usize); + + let chars_per_node = NODE_BYTES / size_of::(); + + let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32; + + if number_of_nodes > 0 { + let first_node_id = pool.reserve(number_of_nodes); + let index = first_node_id.index as isize; + let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void; + + unsafe { + libc::memcpy( + next_node_ptr, + string.as_ptr() as *const c_void, + string.len(), + ); + } + + PoolStr { + first_node_id, + len: string.len() as u32, + } + } else { + PoolStr { + first_node_id: NodeId { + index: 0, + _phantom: PhantomData::default(), + }, + len: 0, + } + } + } + + pub fn as_str(&self, pool: &Pool) -> &str { + unsafe { + let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8; + + let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize); + + std::str::from_utf8_unchecked(&node_slice[0..self.len as usize]) + } + } + + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, pool: &Pool) -> usize { + let contents = self.as_str(pool); + + contents.len() + } + + pub fn is_empty(&self, pool: &Pool) -> bool { + self.len(pool) == 0 + } +} + +impl ShallowClone for PoolStr { + fn shallow_clone(&self) -> Self { + // Question: should this fully clone, or is a shallow copy + // (and the aliasing it entails) OK? + Self { + first_node_id: self.first_node_id, + len: self.len, + } + } +} \ No newline at end of file diff --git a/ast/src/pool/pool_vec.rs b/ast/src/pool/pool_vec.rs new file mode 100644 index 0000000000..3da278e485 --- /dev/null +++ b/ast/src/pool/pool_vec.rs @@ -0,0 +1,324 @@ + +use super::pool::{NodeId, Pool, NODE_BYTES}; +use super::shallow_clone::ShallowClone; +use libc::{c_void}; +use std::marker::PhantomData; +use std::mem::size_of; +use std::any::type_name; +use std::cmp::Ordering; + +/// An array of at most 2^32 pool-allocated nodes. +#[derive(Debug)] +pub struct PoolVec { + first_node_id: NodeId, + len: u32, +} + +#[test] +fn pool_vec_size() { + assert_eq!(size_of::>(), 8); +} + +impl<'a, T: 'a + Sized> PoolVec { + pub fn empty(pool: &mut Pool) -> Self { + Self::new(std::iter::empty(), pool) + } + + pub fn with_capacity(len: u32, pool: &mut Pool) -> Self { + debug_assert!( + size_of::() <= NODE_BYTES, + "{} has a size of {}", + type_name::(), + size_of::() + ); + + if len == 0 { + Self::empty(pool) + } else { + let first_node_id = pool.reserve(len); + + PoolVec { first_node_id, len } + } + } + + pub fn len(&self) -> usize { + self.len as usize + } + + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn new>(nodes: I, pool: &mut Pool) -> Self { + debug_assert!(nodes.len() <= u32::MAX as usize); + debug_assert!(size_of::() <= NODE_BYTES); + + let len = nodes.len() as u32; + + if len > 0 { + let first_node_id = pool.reserve(len); + let index = first_node_id.index as isize; + let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T; + + for (indx_inc, node) in nodes.enumerate() { + unsafe { + *next_node_ptr = node; + + next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T; + } + } + + PoolVec { first_node_id, len } + } else { + PoolVec { + first_node_id: NodeId { + index: 0, + _phantom: PhantomData::default(), + }, + len: 0, + } + } + } + + pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator { + self.pool_list_iter(pool) + } + + pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator { + self.pool_list_iter_mut(pool) + } + + pub fn iter_node_ids(&self) -> impl ExactSizeIterator> { + self.pool_list_iter_node_ids() + } + + /// Private version of into_iter which exposes the implementation detail + /// of PoolVecIter. We don't want that struct to be public, but we + /// actually do want to have this separate function for code reuse + /// in the iterator's next() method. + #[inline(always)] + fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> { + PoolVecIter { + pool, + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + #[inline(always)] + fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> { + PoolVecIterMut { + pool, + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + #[inline(always)] + fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds { + PoolVecIterNodeIds { + current_node_id: self.first_node_id, + len_remaining: self.len, + } + } + + pub fn free(self, pool: &'a mut Pool) { + // zero out the memory + unsafe { + let index = self.first_node_id.index as isize; + let node_ptr = pool.nodes.offset(index) as *mut c_void; + let bytes = self.len as usize * NODE_BYTES; + + libc::memset(node_ptr, 0, bytes); + } + + // TODO insert it into the pool's free list + } +} + +impl ShallowClone for PoolVec { + fn shallow_clone(&self) -> Self { + // Question: should this fully clone, or is a shallow copy + // (and the aliasing it entails) OK? + Self { + first_node_id: self.first_node_id, + len: self.len, + } + } +} + +struct PoolVecIter<'a, T> { + pool: &'a Pool, + current_node_id: NodeId, + len_remaining: u32, +} + +impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T> +where + T: 'a, +{ + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl<'a, T> Iterator for PoolVecIter<'a, T> +where + T: 'a, +{ + type Item = &'a T; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(unsafe { &*node_ptr }) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; + + Some(unsafe { &*node_ptr }) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +struct PoolVecIterMut<'a, T> { + pool: &'a Pool, + current_node_id: NodeId, + len_remaining: u32, +} + +impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T> +where + T: 'a, +{ + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl<'a, T> Iterator for PoolVecIterMut<'a, T> +where + T: 'a, +{ + type Item = &'a mut T; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(unsafe { &mut *node_ptr }) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + let index = self.current_node_id.index; + let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; + + Some(unsafe { &mut *node_ptr }) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +struct PoolVecIterNodeIds { + current_node_id: NodeId, + len_remaining: u32, +} + +impl ExactSizeIterator for PoolVecIterNodeIds { + fn len(&self) -> usize { + self.len_remaining as usize + } +} + +impl Iterator for PoolVecIterNodeIds { + type Item = NodeId; + + fn next(&mut self) -> Option { + let len_remaining = self.len_remaining; + + match len_remaining.cmp(&1) { + Ordering::Greater => { + // Get the current node + let current = self.current_node_id; + let index = current.index; + + // Advance the node pointer to the next node in the current page + self.current_node_id = NodeId { + index: index + 1, + _phantom: PhantomData::default(), + }; + self.len_remaining = len_remaining - 1; + + Some(current) + } + Ordering::Equal => { + self.len_remaining = 0; + + // Don't advance the node pointer's node, because that might + // advance past the end of the page! + + Some(self.current_node_id) + } + Ordering::Less => { + // len_remaining was 0 + None + } + } + } +} + +#[test] +fn pool_vec_iter_test() { + let expected_vec: Vec = vec![2, 4, 8, 16]; + + let mut test_pool = Pool::with_capacity(1024); + let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool); + + let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); + + assert_eq!(current_vec, expected_vec); +} \ No newline at end of file diff --git a/ast/src/pool/shallow_clone.rs b/ast/src/pool/shallow_clone.rs new file mode 100644 index 0000000000..cf7c24f8f0 --- /dev/null +++ b/ast/src/pool/shallow_clone.rs @@ -0,0 +1,33 @@ + +use roc_can::expected::Expected; +use roc_can::expected::PExpected; + +/// Clones the outer node, but does not clone any nodeids +pub trait ShallowClone { + fn shallow_clone(&self) -> Self; +} + +impl ShallowClone for Expected { + fn shallow_clone(&self) -> Self { + use Expected::*; + + match self { + NoExpectation(t) => NoExpectation(t.shallow_clone()), + ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), + FromAnnotation(loc_pat, n, source, t) => { + FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone()) + } + } + } +} + +impl ShallowClone for PExpected { + fn shallow_clone(&self) -> Self { + use PExpected::*; + + match self { + NoExpectation(t) => NoExpectation(t.shallow_clone()), + ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), + } + } +} diff --git a/ast/src/roc_file.rs b/ast/src/roc_file.rs new file mode 100644 index 0000000000..5379e9097a --- /dev/null +++ b/ast/src/roc_file.rs @@ -0,0 +1,133 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; +use roc_fmt::def::fmt_def; +use roc_fmt::module::fmt_module; +use roc_parse::ast::{Def, Module}; +use roc_parse::module::module_defs; +use roc_parse::parser; +use roc_parse::parser::{Parser, SyntaxError}; +use roc_region::all::Located; +use std::ffi::OsStr; +use std::path::Path; +use std::{fs, io}; + +#[derive(Debug)] +pub struct File<'a> { + path: &'a Path, + module_header: Module<'a>, + content: Vec<'a, Located>>, +} + +#[derive(Debug)] +pub enum ReadError<'a> { + Read(std::io::Error), + ParseDefs(SyntaxError<'a>), + ParseHeader(SyntaxError<'a>), + DoesntHaveRocExtension, +} + +impl<'a> File<'a> { + pub fn read(path: &'a Path, arena: &'a Bump) -> Result, ReadError<'a>> { + if path.extension() != Some(OsStr::new("roc")) { + return Err(ReadError::DoesntHaveRocExtension); + } + + let bytes = fs::read(path).map_err(ReadError::Read)?; + + let allocation = arena.alloc(bytes); + + let module_parse_state = parser::State::new(allocation); + let parsed_module = roc_parse::module::parse_header(arena, module_parse_state); + + match parsed_module { + Ok((module, state)) => { + let parsed_defs = module_defs().parse(arena, state); + + match parsed_defs { + Ok((_, defs, _)) => Ok(File { + path, + module_header: module, + content: defs, + }), + Err((_, error, _)) => Err(ReadError::ParseDefs(error)), + } + } + Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))), + } + } + + pub fn fmt(&self) -> String { + let arena = Bump::new(); + let mut formatted_file = String::new(); + + let mut module_header_buf = bumpalo::collections::String::new_in(&arena); + fmt_module(&mut module_header_buf, &self.module_header); + + formatted_file.push_str(module_header_buf.as_str()); + + for def in &self.content { + let mut def_buf = bumpalo::collections::String::new_in(&arena); + + fmt_def(&mut def_buf, &def.value, 0); + + formatted_file.push_str(def_buf.as_str()); + } + + formatted_file + } + + pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> { + let formatted_file = self.fmt(); + + fs::write(write_path, formatted_file) + } + + pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> { + self.fmt_then_write_to( + self.path + .with_file_name(new_name) + .with_extension("roc") + .as_path(), + ) + } + + pub fn fmt_then_write(&self) -> io::Result<()> { + self.fmt_then_write_to(self.path) + } +} + +#[cfg(test)] +mod test_file { + use crate::lang::roc_file; + use bumpalo::Bump; + use std::path::Path; + + #[test] + fn read_and_fmt_simple_roc_module() { + let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc"); + + let arena = Bump::new(); + + let file = roc_file::File::read(simple_module_path, &arena) + .expect("Could not read SimpleUnformatted.roc in test_file test"); + + assert_eq!( + file.fmt(), + indoc!( + r#" + interface Simple + exposes [ + v, x + ] + imports [] + + v : Str + + v = "Value!" + + x : Int + x = 4"# + ) + ); + } +} diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs new file mode 100644 index 0000000000..68b29c7635 --- /dev/null +++ b/ast/src/solve_type.rs @@ -0,0 +1,1752 @@ +#![allow(clippy::all)] +#![allow(dead_code)] +use crate::lang::constrain::Constraint::{self, *}; +use crate::lang::pool::{Pool, PoolVec, ShallowClone}; +use crate::lang::types::Type2; +use bumpalo::Bump; +use roc_can::expected::{Expected, PExpected}; +use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; +use roc_module::ident::TagName; +use roc_module::symbol::Symbol; +use roc_region::all::{Located, Region}; +use roc_types::solved_types::Solved; +use roc_types::subs::{ + AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, + SubsSlice, UnionTags, Variable, VariableSubsSlice, +}; +use roc_types::types::{ + gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField, +}; +use roc_unify::unify::unify; +use roc_unify::unify::Unified::*; + +// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed +// https://github.com/elm/compiler +// Thank you, Evan! + +// A lot of energy was put into making type inference fast. That means it's pretty intimidating. +// +// Fundamentally, type inference assigns very general types based on syntax, and then tries to +// make all the pieces fit together. For instance when writing +// +// > f x +// +// We know that `f` is a function, and thus must have some type `a -> b`. +// `x` is just a variable, that gets the type `c` +// +// Next comes constraint generation. For `f x` to be well-typed, +// it must be the case that `c = a`, So a constraint `Eq(c, a)` is generated. +// But `Eq` is a bit special: `c` does not need to equal `a` exactly, but they need to be equivalent. +// This allows for instance the use of aliases. `c` could be an alias, and so looks different from +// `a`, but they still represent the same type. +// +// Then we get to solving, which happens in this file. +// +// When we hit an `Eq` constraint, then we check whether the two involved types are in fact +// equivalent using unification, and when they are, we can substitute one for the other. +// +// When all constraints are processed, and no unification errors have occurred, then the program +// is type-correct. Otherwise the errors are reported. +// +// Now, coming back to efficiency, this type checker uses *ranks* to optimize +// The rank tracks the number of let-bindings a variable is "under". Top-level definitions +// have rank 1. A let in a top-level definition gets rank 2, and so on. +// +// This has to do with generalization of type variables. This is described here +// +// http://okmij.org/ftp/ML/generalization.html#levels +// +// The problem is that when doing inference naively, this program would fail to typecheck +// +// f = +// id = \x -> x +// +// { a: id 1, b: id "foo" } +// +// Because `id` is applied to an integer, the type `Int -> Int` is inferred, which then gives a +// type error for `id "foo"`. +// +// Thus instead the inferred type for `id` is generalized (see the `generalize` function) to `a -> a`. +// Ranks are used to limit the number of type variables considered for generalization. Only those inside +// of the let (so those used in inferring the type of `\x -> x`) are considered. + +#[derive(PartialEq, Debug, Clone)] +pub enum TypeError { + BadExpr(Region, Category, ErrorType, Expected), + BadPattern(Region, PatternCategory, ErrorType, PExpected), + CircularType(Region, Symbol, ErrorType), + BadType(roc_types::types::Problem), + UnexposedLookup(Symbol), +} + +#[derive(Clone, Debug, Default)] +pub struct Env { + pub vars_by_symbol: MutMap, + pub aliases: MutMap, +} + +const DEFAULT_POOLS: usize = 8; + +#[derive(Clone, Debug)] +struct Pools(Vec>); + +impl Default for Pools { + fn default() -> Self { + Pools::new(DEFAULT_POOLS) + } +} + +impl Pools { + pub fn new(num_pools: usize) -> Self { + Pools(vec![Vec::new(); num_pools]) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { + self.0 + .get_mut(rank.into_usize()) + .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) + } + + pub fn get(&self, rank: Rank) -> &Vec { + self.0 + .get(rank.into_usize()) + .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) + } + + pub fn iter(&self) -> std::slice::Iter<'_, Vec> { + self.0.iter() + } + + pub fn split_last(&self) -> (&Vec, &[Vec]) { + self.0 + .split_last() + .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")) + } + + pub fn extend_to(&mut self, n: usize) { + for _ in self.len()..n { + self.0.push(Vec::new()); + } + } +} + +#[derive(Clone)] +struct State { + env: Env, + mark: Mark, +} + +pub fn run<'a>( + arena: &'a Bump, + mempool: &mut Pool, + env: &Env, + problems: &mut Vec, + mut subs: Subs, + constraint: &Constraint, +) -> (Solved, Env) { + let env = run_in_place(arena, mempool, env, problems, &mut subs, constraint); + + (Solved(subs), env) +} + +/// Modify an existing subs in-place instead +pub fn run_in_place<'a>( + arena: &'a Bump, + mempool: &mut Pool, + env: &Env, + problems: &mut Vec, + subs: &mut Subs, + constraint: &Constraint, +) -> Env { + let mut pools = Pools::default(); + let state = State { + env: env.clone(), + mark: Mark::NONE.next(), + }; + let rank = Rank::toplevel(); + let state = solve( + arena, + mempool, + env, + state, + rank, + &mut pools, + problems, + &mut MutMap::default(), + subs, + constraint, + ); + + state.env +} + +#[allow(clippy::too_many_arguments)] +fn solve<'a>( + arena: &'a Bump, + mempool: &mut Pool, + env: &Env, + state: State, + rank: Rank, + pools: &mut Pools, + problems: &mut Vec, + cached_aliases: &mut MutMap, + subs: &mut Subs, + constraint: &Constraint, +) -> State { + match constraint { + True => state, + // SaveTheEnvironment => { + // // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment + // let mut copy = state; + // + // copy.env = env.clone(); + // + // copy + // } + Eq(typ, expectation, category, region) => { + let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); + let expected = type_to_var( + arena, + mempool, + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + category.clone(), + actual_type, + expectation.replace_ref(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + // Store(source, target, _filename, _linenr) => { + // // a special version of Eq that is used to store types in the AST. + // // IT DOES NOT REPORT ERRORS! + // let actual = type_to_var(subs, rank, pools, cached_aliases, source); + // let target = *target; + // + // match unify(subs, actual, target) { + // Success(vars) => { + // introduce(subs, rank, pools, &vars); + // + // state + // } + // Failure(vars, _actual_type, _expected_type) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // BadType(vars, _problem) => { + // introduce(subs, rank, pools, &vars); + // + // // ERROR NOT REPORTED + // + // state + // } + // } + // } + Lookup(symbol, expectation, region) => { + match env.vars_by_symbol.get(&symbol) { + Some(var) => { + // Deep copy the vars associated with this symbol before unifying them. + // Otherwise, suppose we have this: + // + // identity = \a -> a + // + // x = identity 5 + // + // When we call (identity 5), it's important that we not unify + // on identity's original vars. If we do, the type of `identity` will be + // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; + // the type of `identity` is more general than that! + // + // Instead, we want to unify on a *copy* of its vars. If the copy unifies + // successfully (in this case, to `Int -> Int`), we can use that to + // infer the type of this lookup (in this case, `Int`) without ever + // having mutated the original. + // + // If this Lookup is targeting a value in another module, + // then we copy from that module's Subs into our own. If the value + // is being looked up in this module, then we use our Subs as both + // the source and destination. + let actual = deep_copy_var(subs, rank, pools, *var); + + let expected = type_to_var( + arena, + mempool, + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadExpr( + *region, + Category::Lookup(*symbol), + actual_type, + expectation.shallow_clone().replace(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + None => { + problems.push(TypeError::UnexposedLookup(*symbol)); + + state + } + } + } + And(sub_constraints) => { + let mut state = state; + + for sub_constraint in sub_constraints.iter() { + state = solve( + arena, + mempool, + env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + sub_constraint, + ); + } + + state + } + Pattern(region, category, typ, expectation) => { + let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); + let expected = type_to_var( + arena, + mempool, + subs, + rank, + pools, + cached_aliases, + expectation.get_type_ref(), + ); + + match unify(subs, actual, expected) { + Success(vars) => { + introduce(subs, rank, pools, &vars); + + state + } + Failure(vars, actual_type, expected_type) => { + introduce(subs, rank, pools, &vars); + + let problem = TypeError::BadPattern( + *region, + category.clone(), + actual_type, + expectation.shallow_clone().replace(expected_type), + ); + + problems.push(problem); + + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + + state + } + } + } + Let(let_con) => { + match &let_con.ret_constraint { + True if let_con.rigid_vars.is_empty() => { + introduce(subs, rank, pools, &let_con.flex_vars); + + // If the return expression is guaranteed to solve, + // solve the assignments themselves and move on. + solve( + arena, + mempool, + &env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ) + } + ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { + let state = solve( + arena, + mempool, + env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ); + + // Add a variable for each def to new_vars_by_env. + let mut local_def_vars = BumpMap::new_in(arena); + + for (symbol, typ) in let_con.def_types.iter() { + let var = + type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); + + // TODO: region should come from typ + local_def_vars.insert( + *symbol, + Located { + value: var, + region: Region::zero(), + }, + ); + } + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + if !new_env.vars_by_symbol.contains_key(&symbol) { + new_env.vars_by_symbol.insert(*symbol, loc_var.value); + } + } + + let new_state = solve( + arena, + mempool, + &new_env, + state, + rank, + pools, + problems, + cached_aliases, + subs, + ret_con, + ); + + for (symbol, loc_var) in local_def_vars { + check_for_infinite_type(subs, problems, symbol, loc_var); + } + + new_state + } + ret_con => { + let rigid_vars = &let_con.rigid_vars; + let flex_vars = &let_con.flex_vars; + + // work in the next pool to localize header + let next_rank = rank.next(); + + // introduce variables + for &var in rigid_vars.iter().chain(flex_vars.iter()) { + subs.set_rank(var, next_rank); + } + + // determine the next pool + let next_pools; + if next_rank.into_usize() < pools.len() { + next_pools = pools + } else { + // we should be off by one at this point + debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); + pools.extend_to(next_rank.into_usize()); + next_pools = pools; + } + + let pool: &mut Vec = next_pools.get_mut(next_rank); + + // Replace the contents of this pool with rigid_vars and flex_vars + pool.clear(); + pool.reserve(rigid_vars.len() + flex_vars.len()); + pool.extend(rigid_vars.iter()); + pool.extend(flex_vars.iter()); + + // run solver in next pool + + // Add a variable for each def to local_def_vars. + let mut local_def_vars = BumpMap::new_in(arena); + + for (symbol, typ) in let_con.def_types.iter() { + let var = type_to_var( + arena, + mempool, + subs, + next_rank, + next_pools, + cached_aliases, + typ, + ); + + // TODO: region should come from type + local_def_vars.insert( + *symbol, + Located { + value: var, + region: Region::zero(), + }, + ); + } + + // Solve the assignments' constraints first. + let State { + env: saved_env, + mark, + } = solve( + arena, + mempool, + &env, + state, + next_rank, + next_pools, + problems, + cached_aliases, + subs, + &let_con.defs_constraint, + ); + + let young_mark = mark; + let visit_mark = young_mark.next(); + let final_mark = visit_mark.next(); + + debug_assert_eq!( + { + let offenders = next_pools + .get(next_rank) + .iter() + .filter(|var| { + let current_rank = + subs.get_rank(roc_types::subs::Variable::clone(var)); + + current_rank.into_usize() > next_rank.into_usize() + }) + .collect::>(); + + let result = offenders.len(); + + if result > 0 { + dbg!(&subs, &offenders, &let_con.def_types); + } + + result + }, + 0 + ); + + // pop pool + generalize(subs, young_mark, visit_mark, next_rank, next_pools); + + next_pools.get_mut(next_rank).clear(); + + // check that things went well + debug_assert!({ + // NOTE the `subs.redundant` check is added for the uniqueness + // inference, and does not come from elm. It's unclear whether this is + // a bug with uniqueness inference (something is redundant that + // shouldn't be) or that it just never came up in elm. + let failing: Vec<_> = rigid_vars + .iter() + .filter(|&var| { + !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE + }) + .collect(); + + if !failing.is_empty() { + println!("Rigids {:?}", &rigid_vars); + println!("Failing {:?}", failing); + } + + failing.is_empty() + }); + + let mut new_env = env.clone(); + for (symbol, loc_var) in local_def_vars.iter() { + // when there are duplicates, keep the one from `env` + if !new_env.vars_by_symbol.contains_key(&symbol) { + new_env.vars_by_symbol.insert(*symbol, loc_var.value); + } + } + + // Note that this vars_by_symbol is the one returned by the + // previous call to solve() + let temp_state = State { + env: saved_env, + mark: final_mark, + }; + + // Now solve the body, using the new vars_by_symbol which includes + // the assignments' name-to-variable mappings. + let new_state = solve( + arena, + mempool, + &new_env, + temp_state, + rank, + next_pools, + problems, + cached_aliases, + subs, + &ret_con, + ); + + for (symbol, loc_var) in local_def_vars { + check_for_infinite_type(subs, problems, symbol, loc_var); + } + + new_state + } + } + } // _ => todo!("implement {:?}", constraint), + } +} + +fn type_to_var<'a>( + arena: &'a Bump, + mempool: &mut Pool, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + typ: &Type2, +) -> Variable { + type_to_variable(arena, mempool, subs, rank, pools, cached, typ) +} + +/// Abusing existing functions for our purposes +/// this is to put a solved type back into subs +pub fn insert_type_into_subs<'a>( + arena: &'a Bump, + mempool: &mut Pool, + subs: &mut Subs, + typ: &Type2, +) -> Variable { + let rank = Rank::NONE; + let mut pools = Pools::default(); + let mut cached = MutMap::default(); + + type_to_variable(arena, mempool, subs, rank, &mut pools, &mut cached, typ) +} + +fn type_to_variable<'a>( + arena: &'a Bump, + mempool: &Pool, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + typ: &Type2, +) -> Variable { + use Type2::*; + + match typ { + Variable(var) => *var, + Apply(symbol, args) => { + let mut arg_vars = Vec::with_capacity(args.len()); + for var_id in args.iter_node_ids() { + let arg = mempool.get(var_id); + arg_vars.push(type_to_variable( + arena, mempool, subs, rank, pools, cached, arg, + )) + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, arg_vars); + let flat_type = FlatType::Apply(*symbol, arg_vars); + let content = Content::Structure(flat_type); + + register(subs, rank, pools, content) + } + + EmptyRec => roc_types::subs::Variable::EMPTY_RECORD, + EmptyTagUnion => roc_types::subs::Variable::EMPTY_TAG_UNION, + + Record(fields, ext_id) => { + let mut field_vars = Vec::new(); + + for node_id in fields.iter_node_ids() { + use RecordField::*; + + let (field, field_type) = mempool.get(node_id); + + let field_var = match field_type { + Required(type_id) => Required(type_to_variable( + arena, + mempool, + subs, + rank, + pools, + cached, + mempool.get(*type_id), + )), + Optional(type_id) => Optional(type_to_variable( + arena, + mempool, + subs, + rank, + pools, + cached, + mempool.get(*type_id), + )), + Demanded(type_id) => Demanded(type_to_variable( + arena, + mempool, + subs, + rank, + pools, + cached, + mempool.get(*type_id), + )), + }; + + field_vars.push((field.as_str(mempool).into(), field_var)); + } + + let ext = mempool.get(*ext_id); + let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); + + let (it, new_ext_var) = + gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); + + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + field_vars.extend(it); + field_vars.sort_unstable_by(RecordFields::compare); + + let record_fields = RecordFields::insert_into_subs(subs, field_vars); + + let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); + + register(subs, rank, pools, content) + } + + Alias(symbol, args, alias_type_id) => { + // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! + // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) + // different variables (once for each occurrence). The recursion restriction is required + // for uniqueness types only: recursive aliases "introduce" an unbound uniqueness + // attribute in the body, when + // + // Peano : [ S Peano, Z ] + // + // becomes + // + // Peano : [ S (Attr u Peano), Z ] + // + // This `u` variable can be different between lists, so giving just one variable to + // this type is incorrect. + // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable + + let alias_type = mempool.get(*alias_type_id); + let is_recursive = false; // alias_type.is_recursive(); + let no_args = args.is_empty(); + /* + if no_args && !is_recursive { + if let Some(var) = cached.get(symbol) { + return *var; + } + } + */ + + let mut arg_vars = Vec::with_capacity(args.len()); + let mut new_aliases = BumpMap::new_in(arena); + + for (arg, arg_type_id) in args.iter(mempool) { + let arg_type = mempool.get(*arg_type_id); + + let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); + + let arg_str = arg.as_str(mempool); + + arg_vars.push((roc_module::ident::Lowercase::from(arg_str), arg_var)); + new_aliases.insert(arg_str, arg_var); + } + + let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []); + + let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type); + let content = Content::Alias(*symbol, arg_vars, alias_var); + + let result = register(subs, rank, pools, content); + + if no_args && !is_recursive { + // cached.insert(*symbol, result); + } + + result + } + TagUnion(tags, ext_id) => { + let ext = mempool.get(*ext_id); + + let (union_tags, ext) = + type_to_union_tags(arena, mempool, subs, rank, pools, cached, tags, ext); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + register(subs, rank, pools, content) + } + // This case is important for the rank of boolean variables + Function(arg_vars, closure_type_id, ret_type_id) => { + let closure_type = mempool.get(*closure_type_id); + let ret_type = mempool.get(*ret_type_id); + + let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); + + for var_id in arg_vars.iter_node_ids() { + let arg = mempool.get(var_id); + let var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg); + new_arg_vars.push(var) + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + let ret_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ret_type); + let closure_var = + type_to_variable(arena, mempool, subs, rank, pools, cached, closure_type); + + let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); + + register(subs, rank, pools, content) + } + other => todo!("not implemented {:?}", &other), + // RecursiveTagUnion(rec_var, tags, ext) => { + // let mut tag_vars = MutMap::default(); + // + // for (tag, tag_argument_types) in tags { + // let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); + // + // for arg_type in tag_argument_types { + // tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); + // } + // + // tag_vars.insert(tag.clone(), tag_argument_vars); + // } + // + // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); + // let mut ext_tag_vec = Vec::new(); + // let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( + // subs, + // temp_ext_var, + // &mut ext_tag_vec, + // ) { + // Ok(()) => Variable::EMPTY_TAG_UNION, + // Err((new, _)) => new, + // }; + // tag_vars.extend(ext_tag_vec.into_iter()); + // + // let content = + // Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var)); + // + // let tag_union_var = register(subs, rank, pools, content); + // + // subs.set_content( + // *rec_var, + // Content::RecursionVar { + // opt_name: None, + // structure: tag_union_var, + // }, + // ); + // + // tag_union_var + // } + // HostExposedAlias { + // name: symbol, + // arguments: args, + // actual: alias_type, + // actual_var, + // .. + // } => { + // let mut arg_vars = Vec::with_capacity(args.len()); + // let mut new_aliases = ImMap::default(); + // + // for (arg, arg_type) in args { + // let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); + // + // arg_vars.push((arg.clone(), arg_var)); + // new_aliases.insert(arg.clone(), arg_var); + // } + // + // let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); + // + // // unify the actual_var with the result var + // // this can be used to access the type of the actual_var + // // to determine its layout later + // // subs.set_content(*actual_var, descriptor.content); + // + // //subs.set(*actual_var, descriptor.clone()); + // let content = Content::Alias(*symbol, arg_vars, alias_var); + // + // let result = register(subs, rank, pools, content); + // + // // We only want to unify the actual_var with the alias once + // // if it's already redirected (and therefore, redundant) + // // don't do it again + // if !subs.redundant(*actual_var) { + // let descriptor = subs.get(result); + // subs.union(result, *actual_var, descriptor); + // } + // + // result + // } + // Erroneous(problem) => { + // let content = Content::Structure(FlatType::Erroneous(problem.clone())); + // + // register(subs, rank, pools, content) + // } + } +} + +fn type_to_union_tags<'a>( + arena: &'a Bump, + mempool: &Pool, + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + cached: &mut MutMap, + tags: &PoolVec<(TagName, PoolVec)>, + ext: &Type2, +) -> (UnionTags, Variable) { + let mut tag_vars = Vec::with_capacity(tags.len()); + + let mut tag_argument_vars = Vec::new(); + for id in tags.iter_node_ids() { + let (tag, tag_argument_types) = mempool.get(id); + for arg_id in tag_argument_types.iter_node_ids() { + let arg_type = mempool.get(arg_id); + let new_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); + tag_argument_vars.push(new_var); + } + + let new_slice = VariableSubsSlice::insert_into_subs(subs, tag_argument_vars.drain(..)); + + tag_vars.push((tag.clone(), new_slice)); + } + + let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); + + let ext = { + let (it, ext) = + roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); + + tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); + tag_vars.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + + // deduplicate, keeping the right-most occurrence of a tag name + let mut i = 0; + + while i < tag_vars.len() { + match (tag_vars.get(i), tag_vars.get(i + 1)) { + (Some((t1, _)), Some((t2, _))) => { + if t1 == t2 { + tag_vars.remove(i); + } else { + i += 1; + } + } + _ => break, + } + } + + ext + }; + + (UnionTags::insert_slices_into_subs(subs, tag_vars), ext) +} + +fn check_for_infinite_type( + subs: &mut Subs, + problems: &mut Vec, + symbol: Symbol, + loc_var: Located, +) { + let var = loc_var.value; + + while let Err((recursive, _chain)) = subs.occurs(var) { + let description = subs.get(recursive); + let content = description.content; + + // try to make a tag union recursive, see if that helps + match content { + Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + let rec_var = subs.fresh_unnamed_flex_var(); + subs.set_rank(rec_var, description.rank); + subs.set_content( + rec_var, + Content::RecursionVar { + opt_name: None, + structure: recursive, + }, + ); + + let mut new_tags = Vec::with_capacity(tags.len()); + + for (name_index, slice_index) in tags.iter_all() { + let slice = subs[slice_index]; + + let mut new_vars = Vec::new(); + for var_index in slice { + let var = subs[var_index]; + new_vars.push(subs.explicit_substitute(recursive, rec_var, var)); + } + + new_tags.push((subs[name_index].clone(), new_vars)); + } + + let new_ext_var = subs.explicit_substitute(recursive, rec_var, ext_var); + + let new_tags = UnionTags::insert_into_subs(subs, new_tags); + + let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext_var); + + subs.set_content(recursive, Content::Structure(flat_type)); + } + + _other => circular_error(subs, problems, symbol, &loc_var), + } + } +} + +fn circular_error( + subs: &mut Subs, + problems: &mut Vec, + symbol: Symbol, + loc_var: &Located, +) { + let var = loc_var.value; + let (error_type, _) = subs.var_to_error_type(var); + let problem = TypeError::CircularType(loc_var.region, symbol, error_type); + + subs.set_content(var, Content::Error); + + problems.push(problem); +} + +fn generalize( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + young_rank: Rank, + pools: &mut Pools, +) { + let young_vars = pools.get(young_rank); + let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars); + + // Get the ranks right for each entry. + // Start at low ranks so we only have to pass over the information once. + for (index, table) in rank_table.iter().enumerate() { + for &var in table.iter() { + adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var); + } + } + + let (last_pool, all_but_last_pool) = rank_table.split_last(); + + // For variables that have rank lowerer than young_rank, register them in + // the appropriate old pool if they are not redundant. + for vars in all_but_last_pool { + for &var in vars { + if !subs.redundant(var) { + let rank = subs.get_rank(var); + + pools.get_mut(rank).push(var); + } + } + } + + // For variables with rank young_rank, if rank < young_rank: register in old pool, + // otherwise generalize + for &var in last_pool { + if !subs.redundant(var) { + let desc_rank = subs.get_rank(var); + + if desc_rank < young_rank { + pools.get_mut(desc_rank).push(var); + } else { + subs.set_rank(var, Rank::NONE); + } + } + } +} + +fn pool_to_rank_table( + subs: &mut Subs, + young_mark: Mark, + young_rank: Rank, + young_vars: &[Variable], +) -> Pools { + let mut pools = Pools::new(young_rank.into_usize() + 1); + + // Sort the variables into buckets by rank. + for &var in young_vars.iter() { + let rank = subs.get_rank(var); + subs.set_mark(var, young_mark); + + debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); + pools.get_mut(rank).push(var); + } + + pools +} + +/// Adjust variable ranks such that ranks never increase as you move deeper. +/// This way the outermost rank is representative of the entire structure. +fn adjust_rank( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + group_rank: Rank, + var: Variable, +) -> Rank { + let desc = subs.get(var); + + if desc.mark == young_mark { + let Descriptor { + content, + rank: _, + mark: _, + copy, + } = desc; + + // Mark the variable as visited before adjusting content, as it may be cyclic. + subs.set_mark(var, visit_mark); + + let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content); + + subs.set( + var, + Descriptor { + content, + rank: max_rank, + mark: visit_mark, + copy, + }, + ); + + max_rank + } else if desc.mark == visit_mark { + // nothing changes + desc.rank + } else { + let mut desc = desc; + + let min_rank = group_rank.min(desc.rank); + + // TODO from elm-compiler: how can min_rank ever be group_rank? + desc.rank = min_rank; + desc.mark = visit_mark; + + subs.set(var, desc); + + min_rank + } +} + +fn adjust_rank_content( + subs: &mut Subs, + young_mark: Mark, + visit_mark: Mark, + group_rank: Rank, + content: &Content, +) -> Rank { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + match content { + FlexVar(_) | RigidVar(_) | Error => group_rank, + + RecursionVar { .. } => group_rank, + + Structure(flat_type) => { + match flat_type { + Apply(_, args) => { + let mut rank = Rank::toplevel(); + + for var_index in args.into_iter() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + rank + } + + Func(arg_vars, closure_var, ret_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ret_var); + + // TODO investigate further. + // + // My theory is that because the closure_var contains variables already + // contained in the signature only, it does not need to be part of the rank + // calculuation + if true { + rank = rank.max(adjust_rank( + subs, + young_mark, + visit_mark, + group_rank, + *closure_var, + )); + } + + for index in arg_vars.into_iter() { + let var = subs[index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + rank + } + + EmptyRecord => { + // from elm-compiler: THEORY: an empty record never needs to get generalized + Rank::toplevel() + } + + EmptyTagUnion => Rank::toplevel(), + + Record(fields, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for index in fields.iter_variables() { + let var = subs[index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + rank + } + + TagUnion(tags, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + } + + rank + } + + FunctionOrTagUnion(_, _, ext_var) => { + adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var) + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); + + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + rank = rank + .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + } + + // THEORY: the recursion var has the same rank as the tag union itself + // all types it uses are also in the tags already, so it cannot influence the + // rank + debug_assert!( + rank >= adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var) + ); + + rank + } + + Erroneous(_) => group_rank, + } + } + + Alias(_, args, real_var) => { + let mut rank = Rank::toplevel(); + + for var_index in args.variables() { + let var = subs[var_index]; + rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + } + + // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() + // this theory is not true in Roc! aliases of function types capture the closure var + rank = rank.max(adjust_rank( + subs, young_mark, visit_mark, group_rank, *real_var, + )); + + rank + } + } +} + +/// Introduce some variables to Pools at the given rank. +/// Also, set each of their ranks in Subs to be the given rank. +fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { + let pool: &mut Vec = pools.get_mut(rank); + + for &var in vars.iter() { + subs.set_rank(var, rank); + } + + pool.extend(vars); +} + +/// Function that converts rigids variables to flex variables +/// this is used during the monomorphization process +pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { + let rank = Rank::NONE; + let mut pools = Pools::default(); + + instantiate_rigids_help(subs, rank, &mut pools, var); +} + +fn instantiate_rigids_help( + subs: &mut Subs, + max_rank: Rank, + pools: &mut Pools, + var: Variable, +) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + let desc = subs.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + return copy; + } + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let content = desc.content; + let copy = var; + + pools.get_mut(max_rank).push(copy); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + subs.set( + var, + Descriptor { + content: content.clone(), + rank: desc.rank, + mark: Mark::NONE, + copy: copy.into(), + }, + ); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match content { + Structure(flat_type) => { + match flat_type { + Apply(_, args) => { + for var_index in args.into_iter() { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + Func(arg_vars, closure_var, ret_var) => { + instantiate_rigids_help(subs, max_rank, pools, ret_var); + instantiate_rigids_help(subs, max_rank, pools, closure_var); + + for index in arg_vars.into_iter() { + let var = subs[index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + EmptyRecord | EmptyTagUnion | Erroneous(_) => {} + + Record(fields, ext_var) => { + for index in fields.iter_variables() { + let var = subs[index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + + TagUnion(tags, ext_var) => { + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + + FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + instantiate_rigids_help(subs, max_rank, pools, rec_var); + + for (_, index) in tags.iter_all() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + } + + instantiate_rigids_help(subs, max_rank, pools, ext_var); + } + }; + } + + FlexVar(_) | Error => {} + + RecursionVar { structure, .. } => { + instantiate_rigids_help(subs, max_rank, pools, structure); + } + + RigidVar(name) => { + // what it's all about: convert the rigid var into a flex var + subs.set(copy, make_descriptor(FlexVar(Some(name)))); + } + + Alias(_, args, real_type_var) => { + for var_index in args.variables() { + let var = subs[var_index]; + instantiate_rigids_help(subs, max_rank, pools, var); + } + + instantiate_rigids_help(subs, max_rank, pools, real_type_var); + } + } + + var +} + +fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { + let copy = deep_copy_var_help(subs, rank, pools, var); + + subs.restore(var); + + copy +} + +fn deep_copy_var_help( + subs: &mut Subs, + max_rank: Rank, + pools: &mut Pools, + var: Variable, +) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + let desc = subs.get_without_compacting(var); + + if let Some(copy) = desc.copy.into_variable() { + return copy; + } else if desc.rank != Rank::NONE { + return var; + } + + let make_descriptor = |content| Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let content = desc.content; + let copy = subs.fresh(make_descriptor(content.clone())); + + pools.get_mut(max_rank).push(copy); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + subs.set( + var, + Descriptor { + content: content.clone(), + rank: desc.rank, + mark: Mark::NONE, + copy: copy.into(), + }, + ); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match content { + Structure(flat_type) => { + let new_flat_type = match flat_type { + Apply(symbol, args) => { + let mut new_arg_vars = Vec::with_capacity(args.len()); + + for index in args.into_iter() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + new_arg_vars.push(copy_var); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + Apply(symbol, arg_vars) + } + + Func(arg_vars, closure_var, ret_var) => { + let new_ret_var = deep_copy_var_help(subs, max_rank, pools, ret_var); + let new_closure_var = deep_copy_var_help(subs, max_rank, pools, closure_var); + + let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); + + for index in arg_vars.into_iter() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + new_arg_vars.push(copy_var); + } + + let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); + + Func(arg_vars, new_closure_var, new_ret_var) + } + + same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, + + Record(fields, ext_var) => { + let record_fields = { + let mut new_vars = Vec::with_capacity(fields.len()); + + for index in fields.iter_variables() { + let var = subs[index]; + let copy_var = deep_copy_var_help(subs, max_rank, pools, var); + + new_vars.push(copy_var); + } + + let field_names_start = subs.field_names.len() as u32; + let variables_start = subs.variables.len() as u32; + let field_types_start = subs.record_fields.len() as u32; + + let mut length = 0; + + for ((i1, _, i3), var) in fields.iter_all().zip(new_vars) { + let record_field = subs[i3].map(|_| var); + + subs.field_names.push(subs[i1].clone()); + subs.record_fields.push(record_field.map(|_| ())); + subs.variables.push(*record_field.as_inner()); + + length += 1; + } + + RecordFields { + length, + field_names_start, + variables_start, + field_types_start, + } + }; + + Record( + record_fields, + deep_copy_var_help(subs, max_rank, pools, ext_var), + ) + } + + TagUnion(tags, ext_var) => { + let mut new_variable_slices = Vec::with_capacity(tags.len()); + + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); + } + + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + TagUnion(union_tags, new_ext) + } + + FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( + tag_name, + symbol, + deep_copy_var_help(subs, max_rank, pools, ext_var), + ), + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let mut new_variable_slices = Vec::with_capacity(tags.len()); + + let mut new_variables = Vec::new(); + for index in tags.variables() { + let slice = subs[index]; + for var_index in slice { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_variables.push(new_var); + } + + let new_slice = + VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); + + new_variable_slices.push(new_slice); + } + + let new_variables = { + let start = subs.variable_slices.len() as u32; + let length = new_variable_slices.len() as u16; + subs.variable_slices.extend(new_variable_slices); + + SubsSlice::new(start, length) + }; + + let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); + + let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); + let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); + FlatType::RecursiveTagUnion(new_rec_var, union_tags, new_ext) + } + }; + + subs.set(copy, make_descriptor(Structure(new_flat_type))); + + copy + } + + FlexVar(_) | Error => copy, + + RecursionVar { + opt_name, + structure, + } => { + let new_structure = deep_copy_var_help(subs, max_rank, pools, structure); + + subs.set( + copy, + make_descriptor(RecursionVar { + opt_name, + structure: new_structure, + }), + ); + + copy + } + + RigidVar(name) => { + subs.set(copy, make_descriptor(FlexVar(Some(name)))); + + copy + } + + Alias(symbol, mut args, real_type_var) => { + let mut new_args = Vec::with_capacity(args.variables().len()); + + for var_index in args.variables() { + let var = subs[var_index]; + let new_var = deep_copy_var_help(subs, max_rank, pools, var); + new_args.push(new_var); + } + + args.replace_variables(subs, new_args); + + let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); + let new_content = Alias(symbol, args, new_real_type_var); + + subs.set(copy, make_descriptor(new_content)); + + copy + } + } +} + +fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { + let var = subs.fresh(Descriptor { + content, + rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }); + + pools.get_mut(rank).push(var); + + var +} diff --git a/editor/Cargo.toml b/editor/Cargo.toml index f9207b2e38..40ebc820bd 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -5,7 +5,6 @@ authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" description = "An editor for Roc" -exclude = ["src/shaders/*.spv"] [dependencies] roc_collections = { path = "../compiler/collections" } diff --git a/editor/src/lang/pool.rs b/editor/src/lang/pool.rs index 2504cfcffc..ac0fd67206 100644 --- a/editor/src/lang/pool.rs +++ b/editor/src/lang/pool.rs @@ -26,29 +26,7 @@ pub const NODE_BYTES: usize = 32; // on typical systems where the compiler will be run. // // Nice things about this system include: -// * Allocating a new page is as simple as asking the OS for a memory page. -// * Since each node is 32B, each node's memory address will be a multiple of 16. -// * Thanks to the free lists and our consistent chunk sizes, we should -// end up with very little fragmentation. -// * Finding a slot for a given node should be very fast: see if the relevant -// free list has any openings; if not, try the next size up. -// -// Less nice things include: -// * This system makes it very hard to ever give a page back to the OS. -// We could try doing the Mesh Allocator strategy: whenever we allocate -// something, assign it to a random slot in the page, and then periodically -// try to merge two pages into one (by locking and remapping them in the OS) -// and then returning the redundant physical page back to the OS. This should -// work in theory, but is pretty complicated, and we'd need to schedule it. -// Keep in mind that we can't use the Mesh Allocator itself because it returns -// usize pointers, which would be too big for us to have 16B nodes. -// On the plus side, we could be okay with higher memory usage early on, -// and then later use the Mesh strategy to reduce long-running memory usage. -// -// With this system, we can allocate up to 4B nodes. If we wanted to keep -// a generational index in there, like https://crates.io/crates/sharded-slab -// does, we could use some of the 32 bits for that. For example, if we wanted -// to have a 5-bit generational index (supporting up to 32 generations), then +// * Allocating a new pagShallowCloneal index (supporting up to 32 generations), then // we would have 27 bits remaining, meaning we could only support at most // 134M nodes. Since the editor has a separate Pool for each module, is that // enough for any single module we'll encounter in practice? Probably, and From f3fae8ea6107e9bc30cafade644c81f8533bf9e0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 23 Sep 2021 23:17:31 +0200 Subject: [PATCH 12/96] alias analysis static list --- compiler/mono/src/alias_analysis.rs | 41 +++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index f8e5dddb71..bff15284a7 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -16,6 +16,7 @@ use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; pub const MOD_APP: ModName = ModName(b"UserApp"); pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes()); +pub const STATIC_LIST_NAME: ConstName = ConstName(b"THIS IS A STATIC LIST"); const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; @@ -128,6 +129,22 @@ where }; m.add_const(STATIC_STR_NAME, static_str_def)?; + // a const that models all static lists + let static_list_def = { + let mut cbuilder = ConstDefBuilder::new(); + let block = cbuilder.add_block(); + let cell = cbuilder.add_new_heap_cell(block)?; + + let unit_type = cbuilder.add_tuple_type(&[])?; + let bag = cbuilder.add_empty_bag(block, unit_type)?; + let value_id = cbuilder.add_make_tuple(block, &[cell, bag])?; + let root = BlockExpr(block, value_id); + let list_type_id = static_list_type(&mut cbuilder)?; + + cbuilder.build(list_type_id, root)? + }; + m.add_const(STATIC_LIST_NAME, static_list_def)?; + // the entry point wrapper let roc_main_bytes = func_name_bytes_help( entry_point.symbol, @@ -1117,9 +1134,11 @@ fn expr_spec<'a>( let list = new_list(builder, block, type_id)?; let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let mut all_constants = true; for element in elems.iter() { let value_id = if let ListLiteralElement::Symbol(symbol) = element { + all_constants = false; env.symbols[symbol] } else { builder.add_make_tuple(block, &[]).unwrap() @@ -1128,9 +1147,13 @@ fn expr_spec<'a>( bag = builder.add_bag_insert(block, bag, value_id)?; } - let cell = builder.add_new_heap_cell(block)?; + if all_constants { + new_static_list(builder, block) + } else { + let cell = builder.add_new_heap_cell(block)?; - builder.add_make_tuple(block, &[cell, bag]) + builder.add_make_tuple(block, &[cell, bag]) + } } EmptyArray => { @@ -1296,6 +1319,14 @@ fn str_type(builder: &mut TC) -> Result { builder.add_tuple_type(&[cell_id]) } +fn static_list_type(builder: &mut TC) -> Result { + let unit_type = builder.add_tuple_type(&[])?; + let cell = builder.add_heap_cell_type(); + let bag = builder.add_bag_type(unit_type)?; + + builder.add_tuple_type(&[cell, bag]) +} + // const OK_TAG_ID: u8 = 1u8; // const ERR_TAG_ID: u8 = 0u8; @@ -1329,6 +1360,12 @@ fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result Result { + let module = MOD_APP; + + builder.add_const_ref(block, module, STATIC_LIST_NAME) +} + fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result { // we model all our numbers as unit values builder.add_make_tuple(block, &[]) From 45774df2db28cbe74d09a0734df273dc73552393 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 10:00:33 +0200 Subject: [PATCH 13/96] fix hello-web platform --- examples/hello-web/platform/host.js | 6 ++++++ examples/hello-web/platform/host.zig | 12 ++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/hello-web/platform/host.js b/examples/hello-web/platform/host.js index 0d953eabff..a90ff4187b 100644 --- a/examples/hello-web/platform/host.js +++ b/examples/hello-web/platform/host.js @@ -11,6 +11,12 @@ async function roc_web_platform_run(wasm_filename, callback) { const importObj = { wasi_snapshot_preview1: { + proc_exit: (code) => { + if (code !== 0) { + console.error(`Exited with code ${code}`); + } + exit_code = code; + }, roc_panic: (_pointer, _tag_id) => { throw 'Roc panicked!'; } diff --git a/examples/hello-web/platform/host.zig b/examples/hello-web/platform/host.zig index 5d588d6912..2623097836 100644 --- a/examples/hello-web/platform/host.zig +++ b/examples/hello-web/platform/host.zig @@ -48,25 +48,21 @@ export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { const mem = std.mem; const Allocator = mem.Allocator; -extern fn roc__mainForHost_1_exposed(*RocCallResult) void; - -const RocCallResult = extern struct { flag: u64, content: RocStr }; +extern fn roc__mainForHost_1_exposed(*RocStr) void; const Unit = extern struct {}; extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void; pub fn main() u8 { - // make space for the result - var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() }; - // actually call roc to populate the callresult + var callresult = RocStr.empty(); roc__mainForHost_1_exposed(&callresult); // display the result using JavaScript - js_display_roc_string(callresult.content.str_bytes, callresult.content.str_len); + js_display_roc_string(callresult.str_bytes, callresult.str_len); - callresult.content.deinit(); + callresult.deinit(); return 0; } From 34a25408c3f4c56d0622b2805f5145a1e4500dd9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 14:27:38 +0200 Subject: [PATCH 14/96] fix issue --- cli/tests/repl_eval.rs | 5 + compiler/gen_llvm/src/llvm/build.rs | 204 ++++++++++++++++++++++------ 2 files changed, 168 insertions(+), 41 deletions(-) diff --git a/cli/tests/repl_eval.rs b/cli/tests/repl_eval.rs index 87d53837a8..bfb04ae0e0 100644 --- a/cli/tests/repl_eval.rs +++ b/cli/tests/repl_eval.rs @@ -502,6 +502,11 @@ mod repl_eval { expect_success("\\x -> x", " : a -> a"); } + #[test] + fn sum_lambda() { + expect_success("\\x, y -> x + y", " : Num a, Num a -> Num a"); + } + #[test] fn stdlib_function() { expect_success("Num.abs", " : Num a -> Num a"); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 4864b04fcb..99c83a2126 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -708,7 +708,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>( env, main_fn_name, roc_main_fn, - &[], + top_level.arguments, top_level.result, main_fn_name, ); @@ -3210,6 +3210,141 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>( c_function } +fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + ident_string: &str, + roc_function: FunctionValue<'ctx>, + arguments: &[Layout<'a>], + c_function_name: &str, +) -> FunctionValue<'ctx> { + let context = env.context; + + // a tagged union to indicate to the test loader that a panic occured. + // especially when running 32-bit binaries on a 64-bit machine, there + // does not seem to be a smarter solution + let wrapper_return_type = context.struct_type( + &[ + context.i64_type().into(), + roc_function.get_type().get_return_type().unwrap(), + ], + false, + ); + + let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena); + for layout in arguments { + cc_argument_types.push(to_cc_type(env, layout)); + } + + // STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it + let mut argument_types = cc_argument_types; + let return_type = wrapper_return_type; + + let c_function_type = { + let output_type = return_type.ptr_type(AddressSpace::Generic); + argument_types.push(output_type.into()); + env.context.void_type().fn_type(&argument_types, false) + }; + + let c_function = add_func( + env.module, + c_function_name, + c_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(c_function_name); + c_function.set_subprogram(subprogram); + + // STEP 2: build the exposed function's body + let builder = env.builder; + let context = env.context; + + let entry = context.append_basic_block(c_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, c_function); + + // drop the final argument, which is the pointer we write the result into + let args_vector = c_function.get_params(); + let mut args = args_vector.as_slice(); + let args_length = args.len(); + + args = &args[..args.len() - 1]; + + let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); + + let it = args.iter().zip(roc_function.get_type().get_param_types()); + for (arg, fastcc_type) in it { + let arg_type = arg.get_type(); + if arg_type == fastcc_type { + // the C and Fast calling conventions agree + arguments_for_call.push(*arg); + } else { + let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type"); + arguments_for_call.push(cast); + } + } + + let arguments_for_call = &arguments_for_call.into_bump_slice(); + + let call_result = { + let roc_wrapper_function = make_exception_catcher(env, roc_function); + debug_assert_eq!( + arguments_for_call.len(), + roc_wrapper_function.get_params().len() + ); + + builder.position_at_end(entry); + + let call_wrapped = builder.build_call( + roc_wrapper_function, + arguments_for_call, + "call_wrapped_function", + ); + call_wrapped.set_call_convention(FAST_CALL_CONV); + + call_wrapped.try_as_basic_value().left().unwrap() + }; + + let output_arg_index = args_length - 1; + + let output_arg = c_function + .get_nth_param(output_arg_index as u32) + .unwrap() + .into_pointer_value(); + + builder.build_store(output_arg, call_result); + builder.build_return(None); + + // STEP 3: build a {} -> u64 function that gives the size of the return type + let size_function_type = env.context.i64_type().fn_type(&[], false); + let size_function_name: String = format!("roc__{}_size", ident_string); + + let size_function = add_func( + env.module, + size_function_name.as_str(), + size_function_type, + Linkage::External, + C_CALL_CONV, + ); + + let subprogram = env.new_subprogram(&size_function_name); + size_function.set_subprogram(subprogram); + + let entry = context.append_basic_block(size_function, "entry"); + + builder.position_at_end(entry); + + debug_info_init!(env, size_function); + + let size: BasicValueEnum = return_type.size_of().unwrap().into(); + builder.build_return(Some(&size)); + + c_function +} + fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, ident_string: &str, @@ -3220,16 +3355,24 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( ) -> FunctionValue<'ctx> { let context = env.context; - // a generic version that writes the result into a passed *u8 pointer - if !env.is_gen_test { - expose_function_to_host_help_c_abi_generic( + if env.is_gen_test { + return expose_function_to_host_help_c_abi_gen_test( env, + ident_string, roc_function, arguments, - &format!("{}_generic", c_function_name), + c_function_name, ); } + // a generic version that writes the result into a passed *u8 pointer + expose_function_to_host_help_c_abi_generic( + env, + roc_function, + arguments, + &format!("{}_generic", c_function_name), + ); + let wrapper_return_type = if env.is_gen_test { context .struct_type( @@ -3256,11 +3399,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let cc_return = to_cc_return(env, &return_layout); let c_function_type = match cc_return { - CCReturn::Void if !env.is_gen_test => { - env.context.void_type().fn_type(&argument_types, false) - } - CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false), - _ => { + CCReturn::Void => env.context.void_type().fn_type(&argument_types, false), + CCReturn::Return => return_type.fn_type(&argument_types, false), + CCReturn::ByPointer => { let output_type = return_type.ptr_type(AddressSpace::Generic); argument_types.push(output_type.into()); env.context.void_type().fn_type(&argument_types, false) @@ -3294,13 +3435,13 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let args_length = args.len(); match cc_return { - CCReturn::Return if !env.is_gen_test => { + CCReturn::Return => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - CCReturn::Void if !env.is_gen_test => { + CCReturn::Void => { debug_assert_eq!(args.len(), roc_function.get_params().len()); } - _ => { + CCReturn::ByPointer => { args = &args[..args.len() - 1]; debug_assert_eq!(args.len(), roc_function.get_params().len()); } @@ -3323,44 +3464,25 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( let arguments_for_call = &arguments_for_call.into_bump_slice(); let call_result = { - if env.is_gen_test { - let roc_wrapper_function = make_exception_catcher(env, roc_function); - debug_assert_eq!( - arguments_for_call.len(), - roc_wrapper_function.get_params().len() - ); + let call_unwrapped = + builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function"); + call_unwrapped.set_call_convention(FAST_CALL_CONV); - builder.position_at_end(entry); + let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); - let call_wrapped = builder.build_call( - roc_wrapper_function, - arguments_for_call, - "call_wrapped_function", - ); - call_wrapped.set_call_convention(FAST_CALL_CONV); - - call_wrapped.try_as_basic_value().left().unwrap() - } else { - let call_unwrapped = - builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function"); - call_unwrapped.set_call_convention(FAST_CALL_CONV); - - let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); - - // make_good_roc_result(env, call_unwrapped_result) - call_unwrapped_result - } + // make_good_roc_result(env, call_unwrapped_result) + call_unwrapped_result }; match cc_return { - CCReturn::Void if !env.is_gen_test => { + CCReturn::Void => { // TODO return empty struct here? builder.build_return(None); } - CCReturn::Return if !env.is_gen_test => { + CCReturn::Return => { builder.build_return(Some(&call_result)); } - _ => { + CCReturn::ByPointer => { let output_arg_index = args_length - 1; let output_arg = c_function From 43fc0f87ad2a63eecff77813716a5a4dd46ab839 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Sep 2021 15:14:34 +0200 Subject: [PATCH 15/96] fixed many imports after refactoring --- ast/src/canonicalize/canonicalize.rs | 12 +- ast/src/canonicalize/module.rs | 16 ++- ast/src/constrain.rs | 10 +- ast/src/lang/core/declaration.rs | 62 +++++++++ ast/src/lang/core/def/def_to_def2.rs | 2 +- ast/src/lang/core/expr/expr_to_expr2.rs | 11 +- ast/src/lang/core/mod.rs | 8 +- ast/src/lang/core/str.rs | 164 ++++++++++++++++++++++++ ast/src/lang/mod.rs | 4 +- 9 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 ast/src/lang/core/declaration.rs create mode 100644 ast/src/lang/core/str.rs diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index 1e74257bc5..c8bddb58fd 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -1,13 +1,13 @@ -use roc_can::{env::Env, expr::Output, scope::Scope}; + use roc_collections::all::MutMap; use roc_problem::can::{Problem}; use roc_region::all::{Located, Region}; use roc_types::{subs::Variable}; -use crate::{lang::core::{def::def::References, expr::expr2::{Expr2, ExprId, RecordField, WhenBranch}}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, RecordField, WhenBranch}, expr_to_expr2::to_expr2, output::Output}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; -enum CanonicalizeRecordProblem { +pub (crate) enum CanonicalizeRecordProblem { InvalidOptionalValue { field_name: PoolStr, field_region: Region, @@ -20,7 +20,7 @@ enum FieldVar { OnlyVar(Variable), } -fn canonicalize_fields<'a>( +pub fn canonicalize_fields<'a>( env: &mut Env<'a>, scope: &mut Scope, fields: &'a [Located>>], @@ -167,7 +167,7 @@ fn canonicalize_field<'a>( } #[inline(always)] -fn canonicalize_when_branch<'a>( +pub (crate) fn canonicalize_when_branch<'a>( env: &mut Env<'a>, scope: &mut Scope, branch: &'a roc_parse::ast::WhenBranch<'a>, @@ -239,7 +239,7 @@ fn canonicalize_when_branch<'a>( ) } -fn canonicalize_lookup( +pub (crate) fn canonicalize_lookup( env: &mut Env<'_>, scope: &mut Scope, module_name: &str, diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalize/module.rs index 28d37e18cb..0c1ad7b089 100644 --- a/ast/src/canonicalize/module.rs +++ b/ast/src/canonicalize/module.rs @@ -4,7 +4,6 @@ #![allow(unused_variables)] use bumpalo::Bump; use roc_can::operator::desugar_def; -use roc_can::scope::Scope; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::Ident; use roc_module::ident::Lowercase; @@ -15,13 +14,20 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; -use crate::env::Env; -use crate::expr::output::Output; -use crate::lang::expr::output::Output; +use crate::lang::core::def::def::Def; +use crate::lang::core::def::def::{sort_can_defs, Declaration}; +use crate::lang::core::expr::expr2::Expr2; +use crate::lang::core::expr::output::Output; +use crate::lang::core::pattern::Pattern2; +use crate::lang::core::types::Alias; +use crate::lang::core::val_def::ValueDef; +use crate::lang::env::Env; +use crate::lang::scope::Scope; use crate::pool::pool::NodeId; use crate::pool::pool::Pool; use crate::pool::pool_vec::PoolVec; -use crate::types::Alias; +use crate::pool::shallow_clone::ShallowClone; +use crate::lang::core::def::def::canonicalize_defs; pub struct ModuleOutput { pub aliases: MutMap>, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 77d4edb0cd..b0e3f8fa5d 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1,13 +1,5 @@ use bumpalo::{collections::Vec as BumpVec, Bump}; -use crate::lang::{ - ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch}, - expr::Env, - pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, - pool::{Pool, PoolStr, PoolVec, ShallowClone}, - types::{Type2, TypeId}, -}; - use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; use roc_module::{ @@ -21,6 +13,8 @@ use roc_types::{ types::{Category, Reason}, }; +use crate::{lang::{core::{expr::expr2::{ClosureExtra, Expr2, ExprId, RecordField, WhenBranch}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; + #[derive(Debug)] pub enum Constraint<'a> { Eq(Type2, Expected, Category, Region), diff --git a/ast/src/lang/core/declaration.rs b/ast/src/lang/core/declaration.rs new file mode 100644 index 0000000000..207886bece --- /dev/null +++ b/ast/src/lang/core/declaration.rs @@ -0,0 +1,62 @@ +use roc_types::subs::VarStore; + +use crate::{lang::core::{def::def::Def, expr::expr2::Expr2}, pool::{pool::Pool, pool_vec::PoolVec}}; + +use super::def::def::Declaration; + +pub(crate) fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { + match decl { + Declaration::Declare(def) => match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Value(value_def) => { + let def_id = pool.add(value_def); + + let body_id = pool.add(ret); + + Expr2::LetValue { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + Def::Function(function_def) => { + let def_id = pool.add(function_def); + let body_id = pool.add(ret); + + Expr2::LetFunction { + def_id, + body_id, + body_var: var_store.fresh(), + } + } + }, + Declaration::DeclareRec(defs) => { + let mut function_defs = vec![]; + + for def in defs { + match def { + Def::AnnotationOnly { .. } => todo!(), + Def::Function(function_def) => function_defs.push(function_def), + Def::Value(_) => unreachable!(), + } + } + + let body_id = pool.add(ret); + + Expr2::LetRec { + defs: PoolVec::new(function_defs.into_iter(), pool), + body_var: var_store.fresh(), + body_id, + } + } + Declaration::InvalidCycle(_entries, _) => { + // TODO: replace with something from Expr2 + // Expr::RuntimeError(RuntimeError::CircularDef(entries)) + todo!() + } + Declaration::Builtin(_) => { + // Builtins should only be added to top-level decls, not to let-exprs! + unreachable!() + } + } +} \ No newline at end of file diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs index 377eaa37ab..a30a65e3c6 100644 --- a/ast/src/lang/core/def/def_to_def2.rs +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -3,7 +3,7 @@ use bumpalo::collections::Vec as BumpVec; use roc_parse::pattern::PatternType; use roc_region::all::Region; -use crate::lang::{core::pattern::to_pattern2, env::Env, scope::Scope}; +use crate::lang::{core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, env::Env, scope::Scope}; use super::def2::Def2; diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 0dc093ee91..9a5a67f9a6 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -1,14 +1,21 @@ use bumpalo::Bump; +use roc_can::expr::Recursive; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::operator::desugar_expr; +use roc_collections::all::MutSet; use roc_parse::{ast::Expr, pattern::PatternType}; use roc_problem::can::{Problem, RuntimeError}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; -use crate::lang::core::pattern::flatten_str_literal; +use crate::lang::core::declaration::decl_to_let; +use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs}; +use crate::lang::core::expr::expr2::ClosureExtra; +use crate::lang::core::pattern::to_pattern2; +use crate::lang::core::str::flatten_str_literal; +use crate::pool::shallow_clone::ShallowClone; use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; - +use crate::canonicalize::canonicalize::{CanonicalizeRecordProblem, canonicalize_fields, canonicalize_lookup, canonicalize_when_branch}; use super::{expr2::Expr2, output::Output}; pub fn loc_expr_to_expr2<'a>( diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs index 7347e5a924..2ee105850f 100644 --- a/ast/src/lang/core/mod.rs +++ b/ast/src/lang/core/mod.rs @@ -2,7 +2,9 @@ pub mod def; pub mod expr; pub mod header; pub mod ast; -mod val_def; +pub mod val_def; mod fun_def; -mod pattern; -pub mod types; \ No newline at end of file +pub mod pattern; +pub mod types; +mod str; +mod declaration; \ No newline at end of file diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs new file mode 100644 index 0000000000..bc203c0753 --- /dev/null +++ b/ast/src/lang/core/str.rs @@ -0,0 +1,164 @@ +use roc_module::{operator::CalledVia, symbol::Symbol}; +use roc_parse::ast::StrLiteral; + +use crate::{lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; + +use super::expr::{expr2::Expr2, output::Output}; + +pub (crate) fn flatten_str_literal<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + literal: &StrLiteral<'a>, +) -> (Expr2, Output) { + use roc_parse::ast::StrLiteral::*; + + match literal { + PlainLine(str_slice) => { + // TODO use smallstr + let expr = Expr2::Str(PoolStr::new(str_slice, &mut env.pool)); + + (expr, Output::default()) + } + Line(segments) => flatten_str_lines(env, scope, &[segments]), + Block(lines) => flatten_str_lines(env, scope, lines), + } +} + +enum StrSegment { + Interpolation(Expr2), + Plaintext(PoolStr), +} + +fn flatten_str_lines<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + lines: &[&[roc_parse::ast::StrSegment<'a>]], +) -> (Expr2, Output) { + use roc_parse::ast::StrSegment::*; + + let mut buf = String::new(); + let mut segments = Vec::new(); + let mut output = Output::default(); + + for line in lines { + for segment in line.iter() { + match segment { + Plaintext(string) => { + buf.push_str(string); + } + Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) { + Ok(code_pt) => match std::char::from_u32(code_pt) { + Some(ch) => { + buf.push(ch); + } + None => { + // env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt( + // loc_hex_digits.region, + // )), + // output, + // ); + todo!() + } + }, + Err(_) => { + // env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidHexadecimal( + // loc_hex_digits.region, + // )), + // output, + // ); + todo!() + } + }, + Interpolated(loc_expr) => { + if roc_can::expr::is_valid_interpolation(loc_expr.value) { + // Interpolations desugar to Str.concat calls + output.references.calls.insert(Symbol::STR_CONCAT); + + if !buf.is_empty() { + segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); + + buf = String::new(); + } + + let (loc_expr, new_output) = + to_expr2(env, scope, loc_expr.value, loc_expr.region); + + output.union(new_output); + + segments.push(StrSegment::Interpolation(loc_expr)); + } else { + // env.problem(Problem::InvalidInterpolation(loc_expr.region)); + // + // return ( + // Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)), + // output, + // ); + todo!() + } + } + EscapedChar(escaped) => buf.push(roc_can::expr::unescape_char(escaped)), + } + } + } + + if !buf.is_empty() { + segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); + } + + (desugar_str_segments(env, segments), output) +} + +/// Resolve string interpolations by desugaring a sequence of StrSegments +/// into nested calls to Str.concat +fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Expr2 { + use StrSegment::*; + + let pool = &mut env.pool; + let var_store = &mut env.var_store; + + let mut iter = segments.into_iter().rev(); + let mut expr = match iter.next() { + Some(Plaintext(pool_str)) => Expr2::Str(pool_str), + Some(Interpolation(expr_id)) => expr_id, + None => { + // No segments? Empty string! + + let pool_str = PoolStr::new("", pool); + Expr2::Str(pool_str) + } + }; + + for seg in iter { + let new_expr = match seg { + Plaintext(string) => Expr2::Str(string), + Interpolation(expr_id) => expr_id, + }; + + let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT)); + + let args = vec![ + (var_store.fresh(), pool.add(new_expr)), + (var_store.fresh(), pool.add(expr)), + ]; + let args = PoolVec::new(args.into_iter(), pool); + + let new_call = Expr2::Call { + args, + expr: concat_expr_id, + expr_var: var_store.fresh(), + fn_var: var_store.fresh(), + closure_var: var_store.fresh(), + called_via: CalledVia::Space, + }; + + expr = new_call + } + + expr +} \ No newline at end of file diff --git a/ast/src/lang/mod.rs b/ast/src/lang/mod.rs index 9437f39470..b6a8753591 100644 --- a/ast/src/lang/mod.rs +++ b/ast/src/lang/mod.rs @@ -1,4 +1,4 @@ pub mod core; -mod scope; +pub mod scope; mod rigids; -mod env; +pub mod env; From f43c10373f0b42c11261299dc56fde96a0879f32 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 15:41:00 +0200 Subject: [PATCH 16/96] represent empty closure as unit (not void) --- compiler/mono/src/layout.rs | 2 +- compiler/test_gen/src/gen_list.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 959d679cb3..507bcfaf7e 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -599,7 +599,7 @@ impl<'a> LambdaSet<'a> { // this can happen when there is a type error somewhere Ok(LambdaSet { set: &[], - representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))), + representation: arena.alloc(Layout::Struct(&[])), }) } _ => panic!("called LambdaSet.from_var on invalid input"), diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 582b9acceb..758603f2c1 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -2031,3 +2031,31 @@ fn map_with_index_multi_record() { RocList<((), ())> ); } + +#[test] +fn empty_list_of_function_type() { + // see https://github.com/rtfeldman/roc/issues/1732 + assert_evals_to!( + indoc!( + r#" + myList : List (Str -> Str) + myList = [] + + myClosure : Str -> Str + myClosure = \_ -> "bar" + + choose = + if False then + myList + else + [ myClosure ] + + when List.get choose 0 is + Ok f -> f "foo" + Err _ -> "bad!" + "# + ), + RocStr::from_slice(b"bar"), + RocStr + ); +} From ea62f15ac60a573f94299143e4390861d65ee38a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Sep 2021 15:41:45 +0200 Subject: [PATCH 17/96] no more errors --- ast/src/canonicalize/canonicalize.rs | 2 +- ast/src/lang/core/def/def_to_def2.rs | 21 ++++++++++++++++++++- ast/src/lang/core/expr/expr_to_expr2.rs | 14 ++++++++++++++ ast/src/mod.rs | 2 +- ast/src/pool/pool.rs | 4 ++-- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index c8bddb58fd..72d9e83846 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -20,7 +20,7 @@ enum FieldVar { OnlyVar(Variable), } -pub fn canonicalize_fields<'a>( +pub (crate) fn canonicalize_fields<'a>( env: &mut Env<'a>, scope: &mut Scope, fields: &'a [Located>>], diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs index a30a65e3c6..b35b7e3890 100644 --- a/ast/src/lang/core/def/def_to_def2.rs +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -1,6 +1,6 @@ use bumpalo::Bump; use bumpalo::collections::Vec as BumpVec; -use roc_parse::pattern::PatternType; +use roc_parse::{parser::SyntaxError, pattern::PatternType}; use roc_region::all::Region; use crate::lang::{core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, env::Env, scope::Scope}; @@ -72,4 +72,23 @@ pub fn to_def2_from_def<'a>( ) } } +} + +pub fn str_to_def2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> Result, SyntaxError<'a>> { + match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { + Ok(vec_loc_def) => Ok(defs_to_defs2( + arena, + env, + scope, + arena.alloc(vec_loc_def), + region, + )), + Err(fail) => Err(fail), + } } \ No newline at end of file diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 9a5a67f9a6..4e277d590c 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -3,6 +3,7 @@ use roc_can::expr::Recursive; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::operator::desugar_expr; use roc_collections::all::MutSet; +use roc_parse::parser::SyntaxError; use roc_parse::{ast::Expr, pattern::PatternType}; use roc_problem::can::{Problem, RuntimeError}; use roc_module::symbol::Symbol; @@ -18,6 +19,19 @@ use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env: use crate::canonicalize::canonicalize::{CanonicalizeRecordProblem, canonicalize_fields, canonicalize_lookup, canonicalize_when_branch}; use super::{expr2::Expr2, output::Output}; +pub fn str_to_expr2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, +) -> Result<(Expr2, self::Output), SyntaxError<'a>> { + match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { + Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), + Err(fail) => Err(fail), + } +} + pub fn loc_expr_to_expr2<'a>( arena: &'a Bump, loc_expr: Located>, diff --git a/ast/src/mod.rs b/ast/src/mod.rs index addb2cebaf..7aed5d1c66 100644 --- a/ast/src/mod.rs +++ b/ast/src/mod.rs @@ -1,5 +1,5 @@ pub mod ast; -mod constrain; +pub mod constrain; pub mod lang; mod module; pub mod parse; diff --git a/ast/src/pool/pool.rs b/ast/src/pool/pool.rs index 8663ca4bcb..97ec3ca7af 100644 --- a/ast/src/pool/pool.rs +++ b/ast/src/pool/pool.rs @@ -60,9 +60,9 @@ pub const NODE_BYTES: usize = 32; // to see if it was #[derive(Debug, Eq)] -pub (crate) struct NodeId { +pub struct NodeId { pub (super) index: u32, - _phantom: PhantomData, + pub (super) _phantom: PhantomData, } impl Clone for NodeId { From b7f26baf9566be5e4e060f8873e197ee10476fa0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 15:44:09 +0200 Subject: [PATCH 18/96] add mono test --- .../generated/empty_list_of_function_type.txt | 43 +++++++++++++++++++ compiler/test_mono/src/lib.rs | 27 ++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 compiler/test_mono/generated/empty_list_of_function_type.txt diff --git a/compiler/test_mono/generated/empty_list_of_function_type.txt b/compiler/test_mono/generated/empty_list_of_function_type.txt new file mode 100644 index 0000000000..89aa3d309b --- /dev/null +++ b/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -0,0 +1,43 @@ +procedure List.3 (#Attr.2, #Attr.3): + let Test.20 = lowlevel ListLen #Attr.2; + let Test.17 = lowlevel NumLt #Attr.3 Test.20; + if Test.17 then + let Test.19 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; + let Test.18 = Ok Test.19; + ret Test.18; + else + let Test.16 = Struct {}; + let Test.15 = Err Test.16; + ret Test.15; + +procedure Test.2 (Test.6): + let Test.24 = "bar"; + ret Test.24; + +procedure Test.0 (): + let Test.1 = Array []; + joinpoint Test.22 Test.3: + let Test.14 = 0i64; + let Test.7 = CallByName List.3 Test.3 Test.14; + dec Test.3; + let Test.11 = 1i64; + let Test.12 = GetTagId Test.7; + let Test.13 = lowlevel Eq Test.11 Test.12; + if Test.13 then + let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.7; + let Test.9 = "foo"; + let Test.8 = CallByName Test.2 Test.9; + dec Test.9; + ret Test.8; + else + let Test.10 = "bad!"; + ret Test.10; + in + let Test.25 = false; + if Test.25 then + jump Test.22 Test.1; + else + dec Test.1; + let Test.23 = Struct {}; + let Test.21 = Array [Test.23]; + jump Test.22 Test.21; diff --git a/compiler/test_mono/src/lib.rs b/compiler/test_mono/src/lib.rs index 2d05f9cd16..cbcdbed066 100644 --- a/compiler/test_mono/src/lib.rs +++ b/compiler/test_mono/src/lib.rs @@ -1084,6 +1084,33 @@ fn specialize_lowlevel() { ) } +#[mono_test] +fn empty_list_of_function_type() { + // see https://github.com/rtfeldman/roc/issues/1732 + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + main = + myList : List (Str -> Str) + myList = [] + + myClosure : Str -> Str + myClosure = \_ -> "bar" + + choose = + if False then + myList + else + [ myClosure ] + + when List.get choose 0 is + Ok f -> f "foo" + Err _ -> "bad!" + "# + ) +} + // #[ignore] // #[mono_test] // fn static_str_closure() { From c11d89c4bf1dad25d3e3c664937425b69517fdc0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 15:47:53 +0200 Subject: [PATCH 19/96] fix typo --- compiler/gen_llvm/src/llvm/build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 99c83a2126..40c80e4157 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3219,7 +3219,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>( ) -> FunctionValue<'ctx> { let context = env.context; - // a tagged union to indicate to the test loader that a panic occured. + // a tagged union to indicate to the test loader that a panic occurred. // especially when running 32-bit binaries on a 64-bit machine, there // does not seem to be a smarter solution let wrapper_return_type = context.struct_type( From 2f24067267668afdc9da0a51421ede89c66e4539 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 14:41:44 -0700 Subject: [PATCH 20/96] Add small string support to the dev backend --- compiler/gen_dev/src/generic64/mod.rs | 64 +- compiler/gen_dev/tests/dev_num.rs | 6 - compiler/gen_dev/tests/dev_str.rs | 954 ++++++++++++++++++++++++++ 3 files changed, 1017 insertions(+), 7 deletions(-) create mode 100644 compiler/gen_dev/tests/dev_str.rs diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index 9f98f512be..ca26a61f5f 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -9,7 +9,7 @@ use std::marker::PhantomData; pub mod aarch64; pub mod x86_64; -const PTR_SIZE: u32 = 64; +const PTR_SIZE: u32 = 8; pub trait CallConv { const GENERAL_PARAM_REGS: &'static [GeneralReg]; @@ -489,6 +489,25 @@ impl< ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); Ok(()) } + Layout::Builtin(Builtin::Str) => { + if CC::returns_via_arg_pointer(ret_layout)? { + // This will happen on windows, return via pointer here. + Err("FnCall: Returning strings via pointer not yet implemented".to_string()) + } else { + let offset = self.claim_stack_size(16)?; + self.symbol_storage_map.insert( + *dst, + SymbolStorage::Base { + offset, + size: 16, + owned: true, + }, + ); + ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); + ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); + Ok(()) + } + } x => Err(format!( "FnCall: receiving return type, {:?}, is not yet implemented", x @@ -893,6 +912,35 @@ impl< ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val); Ok(()) } + Literal::Str(x) if x.len() < 16 => { + // Load small string. + let reg = self.get_tmp_general_reg()?; + + let offset = self.claim_stack_size(16)?; + self.symbol_storage_map.insert( + *sym, + SymbolStorage::Base { + offset, + size: 16, + owned: true, + }, + ); + let mut bytes = [0; 16]; + bytes[..x.len()].copy_from_slice(x.as_bytes()); + bytes[15] = (x.len() as u8) | 0b1000_0000; + + let mut num_bytes = [0; 8]; + num_bytes.copy_from_slice(&bytes[..8]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(&mut self.buf, reg, num); + ASM::mov_base32_reg64(&mut self.buf, offset, reg); + + num_bytes.copy_from_slice(&bytes[8..]); + let num = i64::from_ne_bytes(num_bytes); + ASM::mov_reg64_imm64(&mut self.buf, reg, num); + ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg); + Ok(()) + } x => Err(format!("loading literal, {:?}, is not yet implemented", x)), } } @@ -1012,6 +1060,20 @@ impl< Layout::Builtin(Builtin::Float64) => { ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); } + Layout::Builtin(Builtin::Str) => { + if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { + // This will happen on windows, return via pointer here. + Err("Returning strings via pointer not yet implemented".to_string()) + } else { + ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); + ASM::mov_reg64_base32( + &mut self.buf, + CC::GENERAL_RETURN_REGS[1], + *offset + 8, + ); + Ok(()) + } + } Layout::Struct(field_layouts) => { let (offset, size) = (*offset, *size); // Nothing to do for empty struct diff --git a/compiler/gen_dev/tests/dev_num.rs b/compiler/gen_dev/tests/dev_num.rs index c223acbcdf..c82d2ca23f 100644 --- a/compiler/gen_dev/tests/dev_num.rs +++ b/compiler/gen_dev/tests/dev_num.rs @@ -1,12 +1,6 @@ -#[macro_use] -extern crate pretty_assertions; - #[macro_use] extern crate indoc; -extern crate bumpalo; -extern crate libc; - #[macro_use] mod helpers; diff --git a/compiler/gen_dev/tests/dev_str.rs b/compiler/gen_dev/tests/dev_str.rs new file mode 100644 index 0000000000..c03240a089 --- /dev/null +++ b/compiler/gen_dev/tests/dev_str.rs @@ -0,0 +1,954 @@ +// use roc_std::{RocList, RocStr}; +#[macro_use] +extern crate indoc; + +#[macro_use] +mod helpers; + +#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] +mod dev_str { + // #[test] + // fn str_split_bigger_delimiter_small_str() { + // assert_evals_to!( + // indoc!( + // r#" + // List.len (Str.split "hello" "JJJJ there") + // "# + // ), + // 1, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // when List.first (Str.split "JJJ" "JJJJ there") is + // Ok str -> + // Str.countGraphemes str + + // _ -> + // -1 + + // "# + // ), + // 3, + // i64 + // ); + // } + + // #[test] + // fn str_split_str_concat_repeated() { + // assert_evals_to!( + // indoc!( + // r#" + // when List.first (Str.split "JJJJJ" "JJJJ there") is + // Ok str -> + // str + // |> Str.concat str + // |> Str.concat str + // |> Str.concat str + // |> Str.concat str + + // _ -> + // "Not Str!" + + // "# + // ), + // RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"), + // RocStr + // ); + // } + + // #[test] + // fn str_split_small_str_bigger_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // when + // List.first + // (Str.split "JJJ" "0123456789abcdefghi") + // is + // Ok str -> str + // _ -> "" + // "# + // ), + // RocStr::from_slice(b"JJJ"), + // RocStr + // ); + // } + + // #[test] + // fn str_split_big_str_small_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "01234567789abcdefghi?01234567789abcdefghi" "?" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"01234567789abcdefghi"), + // RocStr::from_slice(b"01234567789abcdefghi") + // ]), + // RocList + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"01234567789abcdefghi "), + // RocStr::from_slice(b" 01234567789abcdefghi") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_small_str_small_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "J!J!J" "!" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"J"), + // RocStr::from_slice(b"J"), + // RocStr::from_slice(b"J") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_bigger_delimiter_big_strs() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "string to split is shorter" + // "than the delimiter which happens to be very very long" + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]), + // RocList + // ); + // } + + // #[test] + // fn str_split_empty_strs() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "" "" + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"")]), + // RocList + // ); + // } + + // #[test] + // fn str_split_minimal_example() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split "a," "," + // "# + // ), + // RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]), + // RocList + // ) + // } + + // #[test] + // fn str_split_small_str_big_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + // "---- ---- ---- ---- ----" + // |> List.len + // "# + // ), + // 3, + // i64 + // ); + + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "1---- ---- ---- ---- ----2---- ---- ---- ---- ----" + // "---- ---- ---- ---- ----" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"1"), + // RocStr::from_slice(b"2"), + // RocStr::from_slice(b"") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_split_small_str_20_char_delimiter() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.split + // "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |" + // "|-- -- -- -- -- -- |" + // "# + // ), + // RocList::from_slice(&[ + // RocStr::from_slice(b"3"), + // RocStr::from_slice(b"4"), + // RocStr::from_slice(b"") + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_concat_big_to_big() { + // assert_evals_to!( + // indoc!( + // r#" + // Str.concat + // "First string that is fairly long. Longer strings make for different errors. " + // "Second string that is also fairly long. Two long strings test things that might not appear with short strings." + // "# + // ), + // RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."), + // RocStr + // ); + // } + + #[test] + fn small_str_literal() { + assert_evals_to!( + "\"JJJJJJJJJJJJJJJ\"", + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } + + // #[test] + // fn small_str_zeroed_literal() { + // // Verifies that we zero out unused bytes in the string. + // // This is important so that string equality tests don't randomly + // // fail due to unused memory being there! + // assert_llvm_evals_to!( + // "\"J\"", + // [ + // 0x4a, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0x00, + // 0b1000_0001 + // ], + // [u8; 16] + // ); + // } + + // #[test] + // fn small_str_concat_empty_first_arg() { + // assert_llvm_evals_to!( + // r#"Str.concat "" "JJJJJJJJJJJJJJJ""#, + // [ + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0b1000_1111 + // ], + // [u8; 16] + // ); + // } + + // #[test] + // fn small_str_concat_empty_second_arg() { + // assert_llvm_evals_to!( + // r#"Str.concat "JJJJJJJJJJJJJJJ" """#, + // [ + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0b1000_1111 + // ], + // [u8; 16] + // ); + // } + + // #[test] + // fn small_str_concat_small_to_big() { + // assert_evals_to!( + // r#"Str.concat "abc" " this is longer than 15 chars""#, + // RocStr::from_slice(b"abc this is longer than 15 chars"), + // RocStr + // ); + // } + + // #[test] + // fn small_str_concat_small_to_small_staying_small() { + // assert_llvm_evals_to!( + // r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, + // [ + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0x4a, + // 0b1000_1111 + // ], + // [u8; 16] + // ); + // } + + // #[test] + // fn small_str_concat_small_to_small_overflow_to_big() { + // assert_evals_to!( + // r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#, + // RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"), + // RocStr + // ); + // } + + // #[test] + // fn str_concat_empty() { + // assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr); + // } + + // #[test] + // fn small_str_is_empty() { + // assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool); + // } + + // #[test] + // fn big_str_is_empty() { + // assert_evals_to!( + // r#"Str.isEmpty "this is more than 15 chars long""#, + // false, + // bool + // ); + // } + + // #[test] + // fn empty_str_is_empty() { + // assert_evals_to!(r#"Str.isEmpty """#, true, bool); + // } + + // #[test] + // fn str_starts_with() { + // assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool); + // assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool); + // assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool); + // } + + // #[test] + // fn str_starts_with_code_point() { + // assert_evals_to!( + // &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32), + // true, + // bool + // ); + // assert_evals_to!( + // &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32), + // false, + // bool + // ); + // } + + // #[test] + // fn str_ends_with() { + // assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool); + // assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool); + // assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool); + // } + + // #[test] + // fn str_count_graphemes_small_str() { + // assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize); + // } + + // #[test] + // fn str_count_graphemes_three_js() { + // assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize); + // } + + // #[test] + // fn str_count_graphemes_big_str() { + // assert_evals_to!( + // r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#, + // 45, + // usize + // ); + // } + + // #[test] + // fn str_starts_with_same_big_str() { + // assert_evals_to!( + // r#"Str.startsWith "123456789123456789" "123456789123456789""#, + // true, + // bool + // ); + // } + + // #[test] + // fn str_starts_with_different_big_str() { + // assert_evals_to!( + // r#"Str.startsWith "12345678912345678910" "123456789123456789""#, + // true, + // bool + // ); + // } + + // #[test] + // fn str_starts_with_same_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool); + // } + + // #[test] + // fn str_starts_with_different_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool); + // } + // #[test] + // fn str_starts_with_false_small_str() { + // assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool); + // } + + // #[test] + // fn str_from_int() { + // assert_evals_to!( + // r#"Str.fromInt 1234"#, + // roc_std::RocStr::from_slice("1234".as_bytes()), + // roc_std::RocStr + // ); + // assert_evals_to!( + // r#"Str.fromInt 0"#, + // roc_std::RocStr::from_slice("0".as_bytes()), + // roc_std::RocStr + // ); + // assert_evals_to!( + // r#"Str.fromInt -1"#, + // roc_std::RocStr::from_slice("-1".as_bytes()), + // roc_std::RocStr + // ); + + // let max = format!("{}", i64::MAX); + // assert_evals_to!( + // r#"Str.fromInt Num.maxInt"#, + // RocStr::from_slice(max.as_bytes()), + // RocStr + // ); + + // let min = format!("{}", i64::MIN); + // assert_evals_to!( + // r#"Str.fromInt Num.minInt"#, + // RocStr::from_slice(min.as_bytes()), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_ascii() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_ascii() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("abc~".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_unicode() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("∆".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_unicode() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("∆œ¬".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_single_grapheme() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_many_grapheme() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_pass_all() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is + // Ok val -> val + // Err _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("💖b∆".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_invalid_start_byte() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is + // Err (BadUtf8 InvalidStartByte byteIndex) -> + // if byteIndex == 2 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_unexpected_end_of_sequence() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is + // Err (BadUtf8 UnexpectedEndOfSequence byteIndex) -> + // if byteIndex == 3 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_expected_continuation() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is + // Err (BadUtf8 ExpectedContinuation byteIndex) -> + // if byteIndex == 3 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_overlong_encoding() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is + // Err (BadUtf8 OverlongEncoding byteIndex) -> + // if byteIndex == 1 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_codepoint_too_large() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is + // Err (BadUtf8 CodepointTooLarge byteIndex) -> + // if byteIndex == 1 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_fail_surrogate_half() { + // assert_evals_to!( + // indoc!( + // r#" + // when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is + // Err (BadUtf8 EncodesSurrogateHalf byteIndex) -> + // if byteIndex == 2 then + // "a" + // else + // "b" + // _ -> "" + // "# + // ), + // roc_std::RocStr::from_slice("a".as_bytes()), + // roc_std::RocStr + // ); + // } + + // #[test] + // fn str_equality() { + // assert_evals_to!(r#""a" == "a""#, true, bool); + // assert_evals_to!( + // r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#, + // true, + // bool + // ); + // assert_evals_to!(r#""a" != "b""#, true, bool); + // assert_evals_to!(r#""a" == "b""#, false, bool); + // } + + // #[test] + // fn str_clone() { + // use roc_std::RocStr; + // let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes()); + // let short = RocStr::from_slice("x".as_bytes()); + // let empty = RocStr::from_slice("".as_bytes()); + + // debug_assert_eq!(long.clone(), long); + // debug_assert_eq!(short.clone(), short); + // debug_assert_eq!(empty.clone(), empty); + // } + + // #[test] + // fn nested_recursive_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // Expr : [ Add Expr Expr, Val I64, Var I64 ] + + // expr : Expr + // expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1)) + + // printExpr : Expr -> Str + // printExpr = \e -> + // when e is + // Add a b -> + // "Add (" + // |> Str.concat (printExpr a) + // |> Str.concat ") (" + // |> Str.concat (printExpr b) + // |> Str.concat ")" + // Val v -> "Val " |> Str.concat (Str.fromInt v) + // Var v -> "Var " |> Str.concat (Str.fromInt v) + + // printExpr expr + // "# + // ), + // RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_small() { + // assert_evals_to!( + // r#"Str.joinWith ["1", "2"] ", " "#, + // RocStr::from("1, 2"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_big() { + // assert_evals_to!( + // r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#, + // RocStr::from("10000000, 2000000, 30000000"), + // RocStr + // ); + // } + + // #[test] + // fn str_join_comma_single() { + // assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr); + // } + + // #[test] + // fn str_from_float() { + // assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr); + // } + + // #[test] + // fn str_to_utf8() { + // assert_evals_to!( + // r#"Str.toUtf8 "hello""#, + // RocList::from_slice(&[104, 101, 108, 108, 111]), + // RocList + // ); + // assert_evals_to!( + // r#"Str.toUtf8 "this is a long string""#, + // RocList::from_slice(&[ + // 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116, + // 114, 105, 110, 103 + // ]), + // RocList + // ); + // } + + // #[test] + // fn str_from_utf8_range() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 5, start: 0 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("hello"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_slice() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 4, start: 1 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ello"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_slice_not_end() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { count: 3, start: 1 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ell"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_order_does_not_matter() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 1, count: 3 } is + // Ok utf8String -> utf8String + // _ -> "" + // "# + // ), + // RocStr::from("ell"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_out_of_bounds_start_value() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 7, count: 3 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_count_too_high() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 0, count: 6 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } + + // #[test] + // fn str_from_utf8_range_count_too_high_for_start() { + // assert_evals_to!( + // indoc!( + // r#" + // bytes = Str.toUtf8 "hello" + // when Str.fromUtf8Range bytes { start: 4, count: 3 } is + // Ok _ -> "" + // Err (BadUtf8 _ _) -> "" + // Err OutOfBounds -> "out of bounds" + // "# + // ), + // RocStr::from("out of bounds"), + // RocStr + // ); + // } +} From d1021d652dd0ba27b0b638bdffcff43949d7ea42 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 15:55:04 -0700 Subject: [PATCH 21/96] Add StrCat support and update hello-zig to support dev backend --- cli/src/build.rs | 11 +- compiler/gen_dev/src/generic64/aarch64.rs | 1 + compiler/gen_dev/src/generic64/mod.rs | 11 +- compiler/gen_dev/src/generic64/x86_64.rs | 76 ++++++++++- compiler/gen_dev/src/lib.rs | 23 +++- compiler/gen_dev/tests/dev_str.rs | 158 +++++++++++----------- 6 files changed, 186 insertions(+), 94 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index d0702ce3dd..85fc609717 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -3,6 +3,7 @@ use roc_build::{ link::{link, rebuild_host, LinkType}, program, }; +use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load::file::LoadingProblem; @@ -240,11 +241,19 @@ pub fn build_file<'a>( })?; BuildOutcome::NoProblems } else { + let mut inputs = vec![ + host_input_path.as_path().to_str().unwrap(), + app_o_file.to_str().unwrap(), + ]; + if matches!(opt_level, OptLevel::Development) { + inputs.push(bitcode::OBJ_PATH); + } + let (mut child, _) = // TODO use lld link( target, binary_path.clone(), - &[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], + &inputs, link_type ) .map_err(|_| { diff --git a/compiler/gen_dev/src/generic64/aarch64.rs b/compiler/gen_dev/src/generic64/aarch64.rs index 6149bff14c..32273fbf91 100644 --- a/compiler/gen_dev/src/generic64/aarch64.rs +++ b/compiler/gen_dev/src/generic64/aarch64.rs @@ -226,6 +226,7 @@ impl CallConv for AArch64Call { #[inline(always)] fn load_args<'a>( + _buf: &mut Vec<'a, u8>, _symbol_map: &mut MutMap>, _args: &'a [(Layout<'a>, Symbol)], _ret_layout: &Layout<'a>, diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index ca26a61f5f..eb5a79150e 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -48,6 +48,7 @@ pub trait CallConv { // load_args updates the symbol map to know where every arg is stored. fn load_args<'a>( + buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. @@ -422,7 +423,12 @@ impl< args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, ) -> Result<(), String> { - CC::load_args(&mut self.symbol_storage_map, args, ret_layout)?; + CC::load_args( + &mut self.buf, + &mut self.symbol_storage_map, + args, + ret_layout, + )?; // Update used and free regs. for (sym, storage) in &self.symbol_storage_map { match storage { @@ -1063,7 +1069,7 @@ impl< Layout::Builtin(Builtin::Str) => { if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) { // This will happen on windows, return via pointer here. - Err("Returning strings via pointer not yet implemented".to_string()) + return Err("Returning strings via pointer not yet implemented".to_string()); } else { ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); ASM::mov_reg64_base32( @@ -1071,7 +1077,6 @@ impl< CC::GENERAL_RETURN_REGS[1], *offset + 8, ); - Ok(()) } } Layout::Struct(field_layouts) => { diff --git a/compiler/gen_dev/src/generic64/x86_64.rs b/compiler/gen_dev/src/generic64/x86_64.rs index a39a828f8a..a30a1a4e9c 100644 --- a/compiler/gen_dev/src/generic64/x86_64.rs +++ b/compiler/gen_dev/src/generic64/x86_64.rs @@ -177,6 +177,7 @@ impl CallConv for X86_64SystemV { #[inline(always)] fn load_args<'a>( + buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, @@ -231,6 +232,29 @@ impl CallConv for X86_64SystemV { ); } } + Layout::Builtin(Builtin::Str) => { + if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { + // Load the value to the param reg. + let dst1 = Self::GENERAL_PARAM_REGS[general_i]; + let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1]; + base_offset += 16; + X86_64Assembler::mov_reg64_base32(buf, dst1, base_offset - 8); + X86_64Assembler::mov_reg64_base32(buf, dst2, base_offset); + symbol_map.insert( + *sym, + SymbolStorage::Base { + offset: base_offset, + size: 16, + owned: true, + }, + ); + general_i += 2; + } else { + return Err( + "loading strings args on the stack is not yet implemented".to_string() + ); + } + } Layout::Struct(&[]) => {} x => { return Err(format!( @@ -257,7 +281,7 @@ impl CallConv for X86_64SystemV { // For most return layouts we will do nothing. // In some cases, we need to put the return address as the first arg. match ret_layout { - Layout::Builtin(single_register_builtins!()) => {} + Layout::Builtin(single_register_builtins!() | Builtin::Str) => {} x => { return Err(format!( "receiving return type, {:?}, is not yet implemented", @@ -373,6 +397,32 @@ impl CallConv for X86_64SystemV { stack_offset += 8; } } + Layout::Builtin(Builtin::Str) => { + if general_i + 1 < Self::GENERAL_PARAM_REGS.len() { + // Load the value to the param reg. + let dst1 = Self::GENERAL_PARAM_REGS[general_i]; + let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1]; + match symbol_map + .get(&args[i]) + .ok_or("function argument does not reference any symbol")? + { + SymbolStorage::Base { offset, .. } => { + X86_64Assembler::mov_reg64_base32(buf, dst1, *offset); + X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8); + } + _ => { + return Err("Strings only support being loaded from base offsets" + .to_string()); + } + } + general_i += 2; + } else { + return Err( + "calling functions with strings on the stack is not yet implemented" + .to_string(), + ); + } + } Layout::Struct(&[]) => {} x => { return Err(format!( @@ -516,6 +566,7 @@ impl CallConv for X86_64WindowsFastcall { #[inline(always)] fn load_args<'a>( + _buf: &mut Vec<'a, u8>, symbol_map: &mut MutMap>, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>, @@ -535,9 +586,18 @@ impl CallConv for X86_64WindowsFastcall { Layout::Builtin(single_register_integers!()) => { symbol_map .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); + i += 1; } Layout::Builtin(single_register_floats!()) => { symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); + i += 1; + } + Layout::Builtin(Builtin::Str) => { + // I think this just needs to be passed on the stack, so not a huge deal. + return Err( + "Passing str args with Windows fast call not yet implemented." + .to_string(), + ); } Layout::Struct(&[]) => {} x => { @@ -547,7 +607,6 @@ impl CallConv for X86_64WindowsFastcall { )); } } - i += 1; } else { base_offset += match layout { Layout::Builtin(single_register_builtins!()) => 8, @@ -580,7 +639,6 @@ impl CallConv for X86_64WindowsFastcall { ret_layout: &Layout<'a>, ) -> Result { let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; - let mut reg_i = 0; // For most return layouts we will do nothing. // In some cases, we need to put the return address as the first arg. match ret_layout { @@ -597,7 +655,7 @@ impl CallConv for X86_64WindowsFastcall { Layout::Builtin(single_register_integers!()) => { if i < Self::GENERAL_PARAM_REGS.len() { // Load the value to the param reg. - let dst = Self::GENERAL_PARAM_REGS[reg_i]; + let dst = Self::GENERAL_PARAM_REGS[i]; match symbol_map .get(&args[i]) .ok_or("function argument does not reference any symbol")? @@ -615,7 +673,6 @@ impl CallConv for X86_64WindowsFastcall { ) } } - reg_i += 1; } else { // Load the value to the stack. match symbol_map @@ -651,7 +708,7 @@ impl CallConv for X86_64WindowsFastcall { Layout::Builtin(single_register_floats!()) => { if i < Self::FLOAT_PARAM_REGS.len() { // Load the value to the param reg. - let dst = Self::FLOAT_PARAM_REGS[reg_i]; + let dst = Self::FLOAT_PARAM_REGS[i]; match symbol_map .get(&args[i]) .ok_or("function argument does not reference any symbol")? @@ -668,7 +725,6 @@ impl CallConv for X86_64WindowsFastcall { return Err("Cannot load general symbol into FloatReg".to_string()) } } - reg_i += 1; } else { // Load the value to the stack. match symbol_map @@ -700,6 +756,12 @@ impl CallConv for X86_64WindowsFastcall { stack_offset += 8; } } + Layout::Builtin(Builtin::Str) => { + // I think this just needs to be passed on the stack, so not a huge deal. + return Err( + "Passing str args with Windows fast call not yet implemented.".to_string(), + ); + } Layout::Struct(&[]) => {} x => { return Err(format!( diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index 010fa066d3..5ef03a3a42 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -93,12 +93,8 @@ where for (layout, sym) in proc.args { self.set_layout_map(*sym, layout)?; } - // let start = std::time::Instant::now(); self.scan_ast(&proc.body); self.create_free_map(); - // let duration = start.elapsed(); - // println!("Time to calculate lifetimes: {:?}", duration); - // println!("{:?}", self.last_seen_map()); self.build_stmt(&proc.body, &proc.ret_layout)?; self.finalize() } @@ -119,6 +115,11 @@ where self.free_symbols(stmt)?; Ok(()) } + Stmt::Refcounting(_modify, following) => { + // TODO: actually deal with refcounting. For hello world, we just skipped it. + self.build_stmt(following, ret_layout)?; + Ok(()) + } Stmt::Switch { cond_symbol, cond_layout, @@ -298,6 +299,13 @@ where arg_layouts, ret_layout, ), + Symbol::STR_CONCAT => self.build_run_low_level( + sym, + &LowLevel::StrConcat, + arguments, + arg_layouts, + ret_layout, + ), x if x .module_string(&self.env().interns) .starts_with(ModuleName::APP) => @@ -470,6 +478,13 @@ where arg_layouts, ret_layout, ), + LowLevel::StrConcat => self.build_fn_call( + sym, + bitcode::STR_CONCAT.to_string(), + args, + arg_layouts, + ret_layout, + ), x => Err(format!("low level, {:?}. is not yet implemented", x)), } } diff --git a/compiler/gen_dev/tests/dev_str.rs b/compiler/gen_dev/tests/dev_str.rs index c03240a089..19da650a89 100644 --- a/compiler/gen_dev/tests/dev_str.rs +++ b/compiler/gen_dev/tests/dev_str.rs @@ -1,12 +1,12 @@ -// use roc_std::{RocList, RocStr}; -#[macro_use] -extern crate indoc; +// #[macro_use] +// extern crate indoc; #[macro_use] mod helpers; #[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))] mod dev_str { + // use roc_std::{RocList, RocStr}; // #[test] // fn str_split_bigger_delimiter_small_str() { // assert_evals_to!( @@ -261,7 +261,7 @@ mod dev_str { // // Verifies that we zero out unused bytes in the string. // // This is important so that string equality tests don't randomly // // fail due to unused memory being there! - // assert_llvm_evals_to!( + // assert_evals_to!( // "\"J\"", // [ // 0x4a, @@ -285,57 +285,57 @@ mod dev_str { // ); // } - // #[test] - // fn small_str_concat_empty_first_arg() { - // assert_llvm_evals_to!( - // r#"Str.concat "" "JJJJJJJJJJJJJJJ""#, - // [ - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0b1000_1111 - // ], - // [u8; 16] - // ); - // } + #[test] + fn small_str_concat_empty_first_arg() { + assert_evals_to!( + r#"Str.concat "" "JJJJJJJJJJJJJJJ""#, + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } - // #[test] - // fn small_str_concat_empty_second_arg() { - // assert_llvm_evals_to!( - // r#"Str.concat "JJJJJJJJJJJJJJJ" """#, - // [ - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0b1000_1111 - // ], - // [u8; 16] - // ); - // } + #[test] + fn small_str_concat_empty_second_arg() { + assert_evals_to!( + r#"Str.concat "JJJJJJJJJJJJJJJ" """#, + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } // #[test] // fn small_str_concat_small_to_big() { @@ -346,31 +346,31 @@ mod dev_str { // ); // } - // #[test] - // fn small_str_concat_small_to_small_staying_small() { - // assert_llvm_evals_to!( - // r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, - // [ - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0x4a, - // 0b1000_1111 - // ], - // [u8; 16] - // ); - // } + #[test] + fn small_str_concat_small_to_small_staying_small() { + assert_evals_to!( + r#"Str.concat "J" "JJJJJJJJJJJJJJ""#, + [ + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0x4a, + 0b1000_1111 + ], + [u8; 16] + ); + } // #[test] // fn small_str_concat_small_to_small_overflow_to_big() { From e546ec0c37137be31ab4e3c0c5f4ca3a0127fff8 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 18 Sep 2021 16:29:26 -0700 Subject: [PATCH 22/96] Add builtins to precompiled host --- compiler/build/src/link.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 40a8f84cbf..2d3517ee60 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -1,6 +1,7 @@ use crate::target::arch_str; #[cfg(feature = "llvm")] use libloading::{Error, Library}; +use roc_builtins::bitcode; #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; use std::collections::HashMap; @@ -93,7 +94,12 @@ pub fn build_zig_host_native( .env("PATH", env_path) .env("HOME", env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); + command.args(&[ + "build-exe", + "-fPIE", + shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, + ]); } else { command.args(&["build-obj", "-fPIC"]); } @@ -109,7 +115,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "--strip", // cross-compile? "-target", target, @@ -178,7 +183,12 @@ pub fn build_zig_host_native( .env("PATH", &env_path) .env("HOME", &env_home); if let Some(shared_lib_path) = shared_lib_path { - command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); + command.args(&[ + "build-exe", + "-fPIE", + shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, + ]); } else { command.args(&["build-obj", "-fPIC"]); } @@ -197,7 +207,6 @@ pub fn build_zig_host_native( // include libc "--library", "c", - "--strip", ]); if matches!(opt_level, OptLevel::Optimize) { command.args(&["-O", "ReleaseSafe"]); @@ -274,6 +283,7 @@ pub fn build_c_host_native( if let Some(shared_lib_path) = shared_lib_path { command.args(&[ shared_lib_path.to_str().unwrap(), + bitcode::OBJ_PATH, "-fPIE", "-pie", "-lm", From 95f29c4d5b369d132391caa0760e5b8003436d79 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 08:35:34 -0700 Subject: [PATCH 23/96] Remove stale comment about F16 --- compiler/gen_dev/src/generic64/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index eb5a79150e..008633c202 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -1513,8 +1513,6 @@ macro_rules! single_register_integers { #[macro_export] macro_rules! single_register_floats { () => { - // Float16 is explicitly ignored because it is not supported by must hardware and may require special exceptions. - // Builtin::Float16 | Builtin::Float32 | Builtin::Float64 }; } From c334e4d3e5738ce41896f90851374cde3c94a66d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 08:54:41 -0700 Subject: [PATCH 24/96] Export zig builtin version of __muloti4 as weak so it doesn't conflict if it already exists --- compiler/builtins/bitcode/src/main.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 69bf1ca5ac..8fe350f16d 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -164,7 +164,12 @@ test "" { // https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig // // Thank you Zig Contributors! -export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { + +// Export it as weak incase it is alreadly linked in by something else. +comptime { + @export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak }); +} +fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { // @setRuntimeSafety(std.builtin.is_test); const min = @bitCast(i128, @as(u128, 1 << (128 - 1))); From f9e2e3469ba25ca833d49fb0d6c3237ae4e6ea1f Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Fri, 24 Sep 2021 20:08:49 +0200 Subject: [PATCH 25/96] separated markup and util crates, no more errors --- Cargo.lock | 20 + Cargo.toml | 2 + ast/Cargo.toml | 2 +- ast/src/canonicalize/canonicalize.rs | 2 +- ast/src/constrain.rs | 2 +- ast/src/lang/core/expr/expr2.rs | 9 +- ast/src/lang/core/expr/expr2_to_string.rs | 2 +- ast/src/lang/core/expr/mod.rs | 1 + ast/src/lang/core/expr/record_field.rs | 52 ++ ast/src/lib.rs | 2 +- ast/src/mod.rs | 2 +- code_markup/Cargo.toml | 18 + code_markup/src/colors.rs | 22 + code_markup/src/lib.rs | 5 + code_markup/src/markup/attribute.rs | 123 +++ code_markup/src/markup/common_nodes.rs | 107 +++ code_markup/src/markup/mod.rs | 4 + code_markup/src/markup/nodes.rs | 880 ++++++++++++++++++++++ code_markup/src/markup/top_level_def.rs | 39 + code_markup/src/markup_error.rs | 56 ++ code_markup/src/slow_pool.rs | 76 ++ code_markup/src/syntax_highlight.rs | 48 ++ editor/src/editor/slow_pool.rs | 5 +- utils/Cargo.toml | 12 + utils/src/lib.rs | 96 +++ utils/src/util_error.rs | 36 + 26 files changed, 1609 insertions(+), 14 deletions(-) create mode 100644 ast/src/lang/core/expr/record_field.rs create mode 100644 code_markup/Cargo.toml create mode 100644 code_markup/src/colors.rs create mode 100644 code_markup/src/lib.rs create mode 100644 code_markup/src/markup/attribute.rs create mode 100644 code_markup/src/markup/common_nodes.rs create mode 100644 code_markup/src/markup/mod.rs create mode 100644 code_markup/src/markup/nodes.rs create mode 100644 code_markup/src/markup/top_level_def.rs create mode 100644 code_markup/src/markup_error.rs create mode 100644 code_markup/src/slow_pool.rs create mode 100644 code_markup/src/syntax_highlight.rs create mode 100644 utils/Cargo.toml create mode 100644 utils/src/lib.rs create mode 100644 utils/src/util_error.rs diff --git a/Cargo.lock b/Cargo.lock index a7d51f68b9..3fdcc1094b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3529,6 +3529,19 @@ dependencies = [ "wasmer-wasi", ] +[[package]] +name = "roc_code_markup" +version = "0.1.0" +dependencies = [ + "bumpalo", + "palette", + "roc_ast", + "roc_module", + "roc_utils", + "serde", + "snafu", +] + [[package]] name = "roc_collections" version = "0.1.0" @@ -3956,6 +3969,13 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_utils" +version = "0.1.0" +dependencies = [ + "snafu", +] + [[package]] name = "ropey" version = "1.3.1" diff --git a/Cargo.toml b/Cargo.toml index e2864e9243..a2fe43c612 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,9 @@ members = [ "ast", "cli", "cli/cli_utils", + "code_markup", "roc_std", + "utils", "docs", "linker", ] diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 8b03f69880..76d1658885 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" -description = "AST as used by the editor and docs. In contrast to the compiler, these types do not keep track of a location in a file." +description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file." [dependencies] roc_can = { path = "../compiler/can" } diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index 72d9e83846..459fb6c7d1 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -5,7 +5,7 @@ use roc_problem::can::{Problem}; use roc_region::all::{Located, Region}; use roc_types::{subs::Variable}; -use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, RecordField, WhenBranch}, expr_to_expr2::to_expr2, output::Output}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, WhenBranch}, expr_to_expr2::to_expr2, output::Output, record_field::RecordField}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; pub (crate) enum CanonicalizeRecordProblem { InvalidOptionalValue { diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index b0e3f8fa5d..07c7ba8480 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -13,7 +13,7 @@ use roc_types::{ types::{Category, Reason}, }; -use crate::{lang::{core::{expr::expr2::{ClosureExtra, Expr2, ExprId, RecordField, WhenBranch}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{lang::{core::{expr::{expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, record_field::RecordField}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; #[derive(Debug)] pub enum Constraint<'a> { diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 6b5b5b8328..83e263b3d2 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -7,6 +7,8 @@ use roc_module::symbol::Symbol; use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; +use super::record_field::RecordField; + pub type ArrString = ArrayString; // TODO make the inner types private? @@ -210,13 +212,6 @@ pub enum FloatVal { F32(f32), } -#[derive(Debug)] -pub enum RecordField { - InvalidLabelOnly(PoolStr, Variable), - LabelOnly(PoolStr, Variable, Symbol), - LabeledValue(PoolStr, Variable, ExprId), -} - #[derive(Debug)] pub struct WhenBranch { pub patterns: PoolVec, // 4B diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs index 7ab0ec4650..b9ec8a234b 100644 --- a/ast/src/lang/core/expr/expr2_to_string.rs +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -1,4 +1,4 @@ -use crate::{lang::core::{expr::expr2::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; +use crate::{lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; use roc_types::subs::Variable; use super::expr2::{Expr2, ExprId}; diff --git a/ast/src/lang/core/expr/mod.rs b/ast/src/lang/core/expr/mod.rs index 186f65c9c0..1a58685fc2 100644 --- a/ast/src/lang/core/expr/mod.rs +++ b/ast/src/lang/core/expr/mod.rs @@ -3,3 +3,4 @@ pub mod expr2_to_string; pub (crate) mod output; mod introduced_vars; pub (crate) mod expr_to_expr2; +pub mod record_field; diff --git a/ast/src/lang/core/expr/record_field.rs b/ast/src/lang/core/expr/record_field.rs new file mode 100644 index 0000000000..2db2fc6fe5 --- /dev/null +++ b/ast/src/lang/core/expr/record_field.rs @@ -0,0 +1,52 @@ + +use roc_types::subs::Variable; + +use crate::{pool::{pool_str::PoolStr}}; +use roc_module::symbol::Symbol; + +use super::expr2::ExprId; + +#[derive(Debug)] +pub enum RecordField { + InvalidLabelOnly(PoolStr, Variable), + LabelOnly(PoolStr, Variable, Symbol), + LabeledValue(PoolStr, Variable, ExprId), +} + +use RecordField::*; + +impl RecordField { + + + pub fn get_record_field_var(&self) -> &Variable { + match self { + InvalidLabelOnly(_, var) => var, + LabelOnly(_, var, _) => var, + LabeledValue(_, var, _) => var, + } + } + + pub fn get_record_field_pool_str(&self) -> &PoolStr { + match self { + InvalidLabelOnly(pool_str, _) => pool_str, + LabelOnly(pool_str, _, _) => pool_str, + LabeledValue(pool_str, _, _) => pool_str, + } + } + + pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr { + match self { + InvalidLabelOnly(pool_str, _) => pool_str, + LabelOnly(pool_str, _, _) => pool_str, + LabeledValue(pool_str, _, _) => pool_str, + } + } + + pub fn get_record_field_val_node_id(&self) -> Option { + match self { + InvalidLabelOnly(_, _) => None, + LabelOnly(_, _, _) => None, + LabeledValue(_, _, field_val_id) => Some(*field_val_id), + } + } +} \ No newline at end of file diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 84c6ff305b..02cf199e70 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -2,4 +2,4 @@ pub mod lang; pub mod pool; mod constrain; mod canonicalize; -mod ast_error; \ No newline at end of file +pub mod ast_error; \ No newline at end of file diff --git a/ast/src/mod.rs b/ast/src/mod.rs index 7aed5d1c66..273c344ee5 100644 --- a/ast/src/mod.rs +++ b/ast/src/mod.rs @@ -11,4 +11,4 @@ mod solve; mod types; mod rigids; mod canonicalize; -mod ast_error; \ No newline at end of file +pub mod ast_error; \ No newline at end of file diff --git a/code_markup/Cargo.toml b/code_markup/Cargo.toml new file mode 100644 index 0000000000..2583ee0006 --- /dev/null +++ b/code_markup/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "roc_code_markup" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Our own markup language for Roc code. Used by the editor and (soon) the docs." + +[dependencies] +roc_ast = { path = "../ast" } +roc_module = { path = "../compiler/module" } +roc_utils = { path = "../utils" } +serde = { version = "1.0.123", features = ["derive"] } +palette = "0.5" +snafu = { version = "0.6", features = ["backtraces"] } +bumpalo = { version = "3.2", features = ["collections"] } + +[dev-dependencies] \ No newline at end of file diff --git a/code_markup/src/colors.rs b/code_markup/src/colors.rs new file mode 100644 index 0000000000..93c6c452b6 --- /dev/null +++ b/code_markup/src/colors.rs @@ -0,0 +1,22 @@ +use palette::{Hsv, LinSrgb}; + +pub type RgbaTup = (f32, f32, f32, f32); +pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0); + +pub fn to_slice((r, g, b, a): RgbaTup) -> [f32; 4] { + [r, g, b, a] +} + +pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup { + from_hsba(hue, saturation, brightness, 1.0) +} + +pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup { + let rgb = LinSrgb::from(Hsv::new( + hue as f32, + (saturation as f32) / 100.0, + (brightness as f32) / 100.0, + )); + + (rgb.red, rgb.green, rgb.blue, alpha) +} diff --git a/code_markup/src/lib.rs b/code_markup/src/lib.rs new file mode 100644 index 0000000000..df6e4f24ed --- /dev/null +++ b/code_markup/src/lib.rs @@ -0,0 +1,5 @@ +pub mod markup; +pub mod markup_error; +pub mod slow_pool; +pub mod syntax_highlight; +pub mod colors; diff --git a/code_markup/src/markup/attribute.rs b/code_markup/src/markup/attribute.rs new file mode 100644 index 0000000000..31a575d35c --- /dev/null +++ b/code_markup/src/markup/attribute.rs @@ -0,0 +1,123 @@ +#![allow(dead_code)] +use snafu::ensure; + +use crate::markup_error::{MarkResult, CaretNotFound}; + +#[derive(Debug, Copy, Clone)] +pub struct Caret { + pub offset_col: usize, +} + +impl Caret { + pub fn new_attr(offset_col: usize) -> Attribute { + Attribute::Caret { + caret: Caret { offset_col }, + } + } +} +#[derive(Debug)] +pub struct SelectionStart { + offset_col: usize, +} +#[derive(Debug)] +pub struct SelectionEnd { + offset_col: usize, +} + +// Highlight is used for example when searching for a specific string to highlight all search results in the module +#[derive(Debug)] +pub struct HighlightStart { + offset_col: usize, +} +#[derive(Debug)] +pub struct HighlightEnd { + offset_col: usize, +} + +// Underline is used for warnings and errors +#[derive(Debug)] +pub struct UnderlineStart { + offset_col: usize, +} +#[derive(Debug)] +pub struct UnderlineEnd { + offset_col: usize, +} + +#[derive(Debug)] +pub enum Attribute { + // Rust does not yet support types for enum variants so we have to do it like this + Caret { caret: Caret }, + + SelectionStart { selection_start: SelectionStart }, + SelectionEnd { selection_end: SelectionEnd }, + + HighlightStart { highlight_start: HighlightStart }, + HighlightEnd { highlight_end: HighlightEnd }, + + UnderlineStart { underline_start: UnderlineStart }, + UnderlineEnd { underline_end: UnderlineEnd }, +} + +#[derive(Debug)] +pub struct Attributes { + pub all: Vec, +} + +impl Attributes { + pub fn new() -> Attributes { + Attributes { all: Vec::new() } + } + + pub fn add(&mut self, attr: Attribute) { + self.all.push(attr); + } + + pub fn add_caret(&mut self, offset_col: usize) { + self.all.push(Attribute::Caret { + caret: Caret { offset_col }, + }); + } + + pub fn get_mut_carets(&mut self) -> Vec<&mut Caret> { + let mut carets = Vec::new(); + + for attr in self.all.iter_mut() { + if let Attribute::Caret { caret } = attr { + carets.push(caret) + } + } + + carets + } + + pub fn get_carets(&self) -> Vec { + let mut carets = Vec::new(); + + for attr in self.all.iter() { + if let Attribute::Caret { caret } = attr { + carets.push(*caret) + } + } + + carets + } + + pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> MarkResult<()> { + let old_len = self.all.len(); + + self.all.retain(|attr| { + if let Attribute::Caret { caret } = attr { + caret.offset_col != offset_col + } else { + true + } + }); + + let new_len = self.all.len(); + + ensure!(old_len != new_len, CaretNotFound { node_id }); + + Ok(()) + } +} diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs new file mode 100644 index 0000000000..c44000534e --- /dev/null +++ b/code_markup/src/markup/common_nodes.rs @@ -0,0 +1,107 @@ + +use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId}; + +use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}; + +use super::{attribute::Attributes, nodes, nodes::MarkupNode}; + +pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::EQUALS.to_owned(), + ast_node_id, + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COMMA.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_blank_mn_w_nls( + ast_node_id: ASTNodeId, + parent_id_opt: Option, + nr_of_newlines: usize, +) -> MarkupNode { + MarkupNode::Blank { + ast_node_id, + syn_high_style: HighlightStyle::Blank, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: nr_of_newlines, + } +} + +pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::COLON.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Operator, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_ACCOLADE.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::LEFT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} + +pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { + MarkupNode::Text { + content: nodes::RIGHT_SQUARE_BR.to_owned(), + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::Bracket, + attributes: Attributes::new(), + parent_id_opt, + newlines_at_end: 0, + } +} diff --git a/code_markup/src/markup/mod.rs b/code_markup/src/markup/mod.rs new file mode 100644 index 0000000000..e3ce137f80 --- /dev/null +++ b/code_markup/src/markup/mod.rs @@ -0,0 +1,4 @@ +pub mod attribute; +pub mod common_nodes; +pub mod nodes; +pub mod top_level_def; diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs new file mode 100644 index 0000000000..6a08432891 --- /dev/null +++ b/code_markup/src/markup/nodes.rs @@ -0,0 +1,880 @@ +use crate::{markup::common_nodes::{new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn, new_right_square_mn}, markup_error::MarkResult, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; + +use super::{attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node}; +/*use crate::editor::ed_error::EdResult; +use crate::editor::ed_error::ExpectedTextNode; +use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; +use crate::editor::markup::common_nodes::new_blank_mn; +use crate::editor::markup::common_nodes::new_blank_mn_w_nls; +use crate::editor::markup::common_nodes::new_colon_mn; +use crate::editor::markup::common_nodes::new_comma_mn; +use crate::editor::markup::common_nodes::new_equals_mn; +use crate::editor::markup::common_nodes::new_left_accolade_mn; +use crate::editor::markup::common_nodes::new_left_square_mn; +use crate::editor::markup::common_nodes::new_right_accolade_mn; +use crate::editor::markup::common_nodes::new_right_square_mn; +use crate::editor::mvc::tld_value_update::tld_mark_node; +use crate::editor::slow_pool::MarkNodeId; +use crate::editor::slow_pool::SlowPool; +use crate::editor::syntax_highlight::HighlightStyle; +use crate::editor::util::index_of; +use crate::lang::ast::Def2; +use crate::lang::ast::DefId; +use crate::lang::ast::ExprId; +use crate::lang::ast::RecordField; +use crate::lang::ast::ValueDef; +use crate::lang::parse::ASTNodeId; +use crate::lang::parse::{AppHeader, AST}; +use crate::lang::pattern::get_identifier_string; +use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; +use crate::ui::util::slice_get;*/ +use bumpalo::Bump; +use roc_ast::{ast_error::ASTResult, lang::{core::{ast::{AST, ASTNodeId}, def::def2::{Def2, DefId}, expr::{expr2::{Expr2, ExprId}, record_field::RecordField}, header::AppHeader, pattern::get_identifier_string, val_def::ValueDef}, env::Env}, pool::pool_str::PoolStr}; +use roc_utils::{index_of, slice_get}; +use roc_module::symbol::Interns; +use std::fmt; +use crate::markup_error::{NestedNodeMissingChild, NestedNodeRequired, ExpectedTextNode}; + +#[derive(Debug)] +pub enum MarkupNode { + Nested { + ast_node_id: ASTNodeId, + children_ids: Vec, + parent_id_opt: Option, + newlines_at_end: usize, + }, + Text { + content: String, + ast_node_id: ASTNodeId, + syn_high_style: HighlightStyle, + attributes: Attributes, + parent_id_opt: Option, + newlines_at_end: usize, + }, + Blank { + ast_node_id: ASTNodeId, + attributes: Attributes, + syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank + parent_id_opt: Option, + newlines_at_end: usize, + }, +} + +impl MarkupNode { + pub fn get_ast_node_id(&self) -> ASTNodeId { + match self { + MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, + MarkupNode::Text { ast_node_id, .. } => *ast_node_id, + MarkupNode::Blank { ast_node_id, .. } => *ast_node_id, + } + } + + pub fn get_parent_id_opt(&self) -> Option { + match self { + MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt, + MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt, + MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt, + } + } + + pub fn get_children_ids(&self) -> Vec { + match self { + MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(), + MarkupNode::Text { .. } => vec![], + MarkupNode::Blank { .. } => vec![], + } + } + + pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec { + if let Some(parent_id) = self.get_parent_id_opt() { + let parent_node = mark_node_pool.get(parent_id); + + parent_node.get_children_ids() + } else { + vec![] + } + } + + // return (index of child in list of children, closest ast index of child corresponding to ast node) + pub fn get_child_indices( + &self, + child_id: MarkNodeId, + mark_node_pool: &SlowPool, + ) -> MarkResult<(usize, usize)> { + match self { + MarkupNode::Nested { children_ids, .. } => { + let mut mark_child_index_opt: Option = None; + let mut child_ids_with_ast: Vec = Vec::new(); + let self_ast_id = self.get_ast_node_id(); + + for (indx, &mark_child_id) in children_ids.iter().enumerate() { + if mark_child_id == child_id { + mark_child_index_opt = Some(indx); + } + + let child_mark_node = mark_node_pool.get(mark_child_id); + // a node that points to the same ast_node as the parent is a ',', '[', ']' + // those are not "real" ast children + if child_mark_node.get_ast_node_id() != self_ast_id { + child_ids_with_ast.push(mark_child_id) + } + } + + if let Some(child_index) = mark_child_index_opt { + if child_index == (children_ids.len() - 1) { + let ast_child_index = child_ids_with_ast.len(); + + Ok((child_index, ast_child_index)) + } else { + // we want to find the index of the closest ast mark node to child_index + let mut indices_in_mark = vec![]; + + for &c_id in child_ids_with_ast.iter() { + indices_in_mark.push(index_of(c_id, children_ids)?); + } + + let mut last_diff = usize::MAX; + let mut best_index = 0; + + for index in indices_in_mark.iter() { + let curr_diff = + isize::abs((*index as isize) - (child_index as isize)) as usize; + + if curr_diff >= last_diff { + break; + } else { + last_diff = curr_diff; + best_index = *index; + } + } + + let closest_ast_child = slice_get(best_index, children_ids)?; + + let closest_ast_child_index = + index_of(*closest_ast_child, &child_ids_with_ast)?; + + // +1 because we want to insert after ast_child + Ok((child_index, closest_ast_child_index + 1)) + } + } else { + NestedNodeMissingChild { + node_id: child_id, + children_ids: children_ids.clone(), + } + .fail() + } + } + _ => NestedNodeRequired { + node_type: self.node_type_as_string(), + } + .fail(), + } + } + + pub fn get_content(&self) -> String { + match self { + MarkupNode::Nested { .. } => "".to_owned(), + MarkupNode::Text { content, .. } => content.clone(), + MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), + } + } + + // gets content and adds newline from newline_at_end + pub fn get_full_content(&self) -> String { + let mut full_content = self.get_content(); + + for _ in 0..self.get_newlines_at_end() { + full_content.push('\n') + } + + full_content + } + + pub fn get_content_mut(&mut self) -> MarkResult<&mut String> { + match self { + MarkupNode::Nested { .. } => ExpectedTextNode { + function_name: "set_content".to_owned(), + node_type: self.node_type_as_string(), + } + .fail(), + MarkupNode::Text { content, .. } => Ok(content), + MarkupNode::Blank { .. } => ExpectedTextNode { + function_name: "set_content".to_owned(), + node_type: self.node_type_as_string(), + } + .fail(), + } + } + + pub fn is_all_alphanumeric(&self) -> bool { + self.get_content() + .chars() + .all(|chr| chr.is_ascii_alphanumeric()) + } + + pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> MarkResult<()> { + if let MarkupNode::Nested { children_ids, .. } = self { + children_ids.splice(index..index, vec![child_id]); + } else { + NestedNodeRequired { + node_type: self.node_type_as_string(), + } + .fail()?; + } + + Ok(()) + } + + pub fn node_type_as_string(&self) -> String { + let type_str = match self { + MarkupNode::Nested { .. } => "Nested", + MarkupNode::Text { .. } => "Text", + MarkupNode::Blank { .. } => "Blank", + }; + + type_str.to_owned() + } + + pub fn is_blank(&self) -> bool { + matches!(self, MarkupNode::Blank { .. }) + } + + pub fn is_nested(&self) -> bool { + matches!(self, MarkupNode::Nested { .. }) + } + + pub fn get_newlines_at_end(&self) -> usize { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end, + } + } + + pub fn add_newline_at_end(&mut self) { + match self { + MarkupNode::Nested { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Text { + newlines_at_end, .. + } => *newlines_at_end += 1, + MarkupNode::Blank { + newlines_at_end, .. + } => *newlines_at_end += 1, + } + } +} + +fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { + pool_str.as_str(env.pool).to_owned() +} + +pub const BLANK_PLACEHOLDER: &str = " "; +pub const LEFT_ACCOLADE: &str = "{ "; +pub const RIGHT_ACCOLADE: &str = " }"; +pub const LEFT_SQUARE_BR: &str = "[ "; +pub const RIGHT_SQUARE_BR: &str = " ]"; +pub const COLON: &str = ": "; +pub const COMMA: &str = ", "; +pub const STRING_QUOTES: &str = "\"\""; +pub const EQUALS: &str = " = "; + +fn new_markup_node( + text: String, + node_id: ASTNodeId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let node = MarkupNode::Text { + content: text, + ast_node_id: node_id, + syn_high_style: highlight_style, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(node) +} + +pub fn def2_to_markup<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + def2: &Def2, + def2_node_id: DefId, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult { + let ast_node_id = ASTNodeId::ADefId(def2_node_id); + + let mark_node_id = match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + let expr_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let tld_mn = tld_mark_node( + *identifier_id, + expr_mn_id, + ast_node_id, + mark_node_pool, + env, + interns, + )?; + + mark_node_pool.add(tld_mn) + } + Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), + }; + + Ok(mark_node_id) +} + +// make Markup Nodes: generate String representation, assign Highlighting Style +pub fn expr2_to_markup<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + expr2: &Expr2, + expr2_node_id: ExprId, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult { + let ast_node_id = ASTNodeId::AExprId(expr2_node_id); + + let mark_node_id = match expr2 { + Expr2::SmallInt { text, .. } + | Expr2::I128 { text, .. } + | Expr2::U128 { text, .. } + | Expr2::Float { text, .. } => { + let num_str = get_string(env, text); + + new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) + } + Expr2::Str(text) => new_markup_node( + "\"".to_owned() + text.as_str(env.pool) + "\"", + ast_node_id, + HighlightStyle::String, + mark_node_pool, + ), + Expr2::GlobalTag { name, .. } => new_markup_node( + get_string(env, name), + ast_node_id, + HighlightStyle::Type, + mark_node_pool, + ), + Expr2::Call { expr: expr_id, .. } => { + let expr = env.pool.get(*expr_id); + expr2_to_markup(arena, env, expr, *expr_id, mark_node_pool, interns)? + } + Expr2::Var(symbol) => { + //TODO make bump_format with arena + let text = format!("{:?}", symbol); + new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) + } + Expr2::List { elems, .. } => { + let mut children_ids = + vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; + + let indexed_node_ids: Vec<(usize, ExprId)> = + elems.iter(env.pool).copied().enumerate().collect(); + + for (idx, node_id) in indexed_node_ids.iter() { + let sub_expr2 = env.pool.get(*node_id); + + children_ids.push(expr2_to_markup( + arena, + env, + sub_expr2, + *node_id, + mark_node_pool, + interns, + )?); + + if idx + 1 < elems.len() { + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); + } + } + children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); + + let list_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(list_node) + } + Expr2::EmptyRecord => { + let children_ids = vec![ + mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), + mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), + ]; + + let record_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(record_node) + } + Expr2::Record { fields, .. } => { + let mut children_ids = + vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; + + for (idx, field_node_id) in fields.iter_node_ids().enumerate() { + let record_field = env.pool.get(field_node_id); + + let field_name = record_field.get_record_field_pool_str(); + + children_ids.push(new_markup_node( + field_name.as_str(env.pool).to_owned(), + ast_node_id, + HighlightStyle::RecordField, + mark_node_pool, + )); + + match record_field { + RecordField::InvalidLabelOnly(_, _) => (), + RecordField::LabelOnly(_, _, _) => (), + RecordField::LabeledValue(_, _, sub_expr2_node_id) => { + children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); + + let sub_expr2 = env.pool.get(*sub_expr2_node_id); + children_ids.push(expr2_to_markup( + arena, + env, + sub_expr2, + *sub_expr2_node_id, + mark_node_pool, + interns, + )?); + } + } + + if idx + 1 < fields.len() { + children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); + } + } + + children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); + + let record_node = MarkupNode::Nested { + ast_node_id, + children_ids, + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(record_node) + } + Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), + Expr2::LetValue { + def_id, + body_id: _, + body_var: _, + } => { + let pattern_id = env.pool.get(*def_id).get_pattern_id(); + + let pattern2 = env.pool.get(pattern_id); + + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let value_def = env.pool.get(*def_id); + + match value_def { + ValueDef::NoAnnotation { + pattern_id: _, + expr_id, + expr_var: _, + } => { + let body_mn_id = expr2_to_markup( + arena, + env, + env.pool.get(*expr_id), + *expr_id, + mark_node_pool, + interns, + )?; + + let body_mn = mark_node_pool.get_mut(body_mn_id); + body_mn.add_newline_at_end(); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + mark_node_pool.add(full_let_node) + } + other => { + unimplemented!( + "I don't know how to convert {:?} into a MarkupNode yet.", + other + ) + } + } + } + Expr2::RuntimeError() => new_markup_node( + "RunTimeError".to_string(), + ast_node_id, + HighlightStyle::Blank, + mark_node_pool, + ), + rest => todo!("implement expr2_to_markup for {:?}", rest), + }; + + Ok(mark_node_id) +} + +pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { + let node = mark_node_pool.get(markup_node_id); + + if let MarkupNode::Nested { + ast_node_id: _, + children_ids, + parent_id_opt: _, + newlines_at_end: _, + } = node + { + // need to clone because of borrowing issues + let children_ids_clone = children_ids.clone(); + + for child_id in children_ids_clone { + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); + } + } +} + +pub fn set_parent_for_all_helper( + markup_node_id: MarkNodeId, + parent_node_id: MarkNodeId, + mark_node_pool: &mut SlowPool, +) { + let node = mark_node_pool.get_mut(markup_node_id); + + match node { + MarkupNode::Nested { + children_ids, + parent_id_opt, + .. + } => { + *parent_id_opt = Some(parent_node_id); + + // need to clone because of borrowing issues + let children_ids_clone = children_ids.clone(); + + for child_id in children_ids_clone { + set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); + } + } + MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), + MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), + } +} + +fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: HighlightStyle::PackageRelated, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +fn header_val_mn( + content: String, + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> MarkNodeId { + let mark_node = MarkupNode::Text { + content, + ast_node_id: ASTNodeId::AExprId(expr_id), + syn_high_style: highlight_style, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + mark_node_pool.add(mark_node) +} + +pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { + let expr_id = app_header.ast_node_id; + let ast_node_id = ASTNodeId::AExprId(expr_id); + + let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); + + let app_name_node_id = header_val_mn( + app_header.app_name.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let full_app_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![app_node_id, app_name_node_id], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); + + let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); + + let pack_base_node_id = header_val_mn( + "base: ".to_owned(), + expr_id, + HighlightStyle::RecordField, + mark_node_pool, + ); + + let pack_val_node_id = header_val_mn( + app_header.packages_base.clone(), + expr_id, + HighlightStyle::String, + mark_node_pool, + ); + + let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); + + let full_packages_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + packages_node_id, + pack_left_acc_node_id, + pack_base_node_id, + pack_val_node_id, + pack_right_acc_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); + + let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut import_child_ids: Vec = add_header_mn_list( + &app_header.imports, + expr_id, + HighlightStyle::Import, + mark_node_pool, + ); + + let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; + + full_import_children.append(&mut import_child_ids); + full_import_children.push(imports_right_square_node_id); + + let full_import_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_import_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); + + let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); + + let mut provides_val_node_ids: Vec = add_header_mn_list( + &app_header.provides, + expr_id, + HighlightStyle::Provides, + mark_node_pool, + ); + + let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); + + let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); + + let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; + + full_provides_children.append(&mut provides_val_node_ids); + full_provides_children.push(provides_right_square_node_id); + full_provides_children.push(provides_end_node_id); + + let full_provides_node = MarkupNode::Nested { + ast_node_id, + children_ids: full_provides_children, + parent_id_opt: None, + newlines_at_end: 1, + }; + + let full_app_node_id = mark_node_pool.add(full_app_node); + let full_packages_node = mark_node_pool.add(full_packages_node); + let full_import_node_id = mark_node_pool.add(full_import_node); + let full_provides_node_id = mark_node_pool.add(full_provides_node); + + let header_mark_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![ + full_app_node_id, + full_packages_node, + full_import_node_id, + full_provides_node_id, + ], + parent_id_opt: None, + newlines_at_end: 1, + }; + + let header_mn_id = mark_node_pool.add(header_mark_node); + + set_parent_for_all(header_mn_id, mark_node_pool); + + header_mn_id +} + +// Used for provides and imports +fn add_header_mn_list( + str_vec: &[String], + expr_id: ExprId, + highlight_style: HighlightStyle, + mark_node_pool: &mut SlowPool, +) -> Vec { + let nr_of_elts = str_vec.len(); + + str_vec + .iter() + .enumerate() + .map(|(indx, provide_str)| { + let provide_str = header_val_mn( + provide_str.to_owned(), + expr_id, + highlight_style, + mark_node_pool, + ); + + if indx != nr_of_elts - 1 { + vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] + } else { + vec![provide_str] + } + }) + .flatten() + .collect() +} + +pub fn ast_to_mark_nodes<'a, 'b>( + arena: &'a Bump, + env: &mut Env<'b>, + ast: &AST, + mark_node_pool: &mut SlowPool, + interns: &Interns, +) -> ASTResult> { + let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; + + for &def_id in ast.def_ids.iter() { + let def2 = env.pool.get(def_id); + + let expr2_markup_id = def2_to_markup(arena, env, def2, def_id, mark_node_pool, interns)?; + + set_parent_for_all(expr2_markup_id, mark_node_pool); + + all_mark_node_ids.push(expr2_markup_id); + } + + Ok(all_mark_node_ids) +} + +impl fmt::Display for MarkupNode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} ({}, {})", + self.node_type_as_string(), + self.get_content(), + self.get_newlines_at_end() + ) + } +} + +pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { + let mut full_string = "\n(mark_node_tree)\n".to_owned(); + + let node = mark_node_pool.get(root_node_id); + + full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id)); + + tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); + + full_string +} + +fn tree_as_string_helper( + node: &MarkupNode, + level: usize, + tree_string: &mut String, + mark_node_pool: &SlowPool, +) { + for child_id in node.get_children_ids() { + let mut full_str = std::iter::repeat("|--- ") + .take(level) + .collect::>() + .join("") + .to_owned(); + + let child = mark_node_pool.get(child_id); + let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n"); + + full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id)); + + tree_string.push_str(&full_str); + + tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); + } +} + +// return to the the root parent_id of the current node +pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId { + let mut curr_mark_node_id = mark_node_id; + let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + + while let Some(curr_parent_id) = curr_parent_id_opt { + curr_mark_node_id = curr_parent_id; + curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); + } + + curr_mark_node_id +} diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs new file mode 100644 index 0000000000..78d086f3fe --- /dev/null +++ b/code_markup/src/markup/top_level_def.rs @@ -0,0 +1,39 @@ +use roc_ast::{ast_error::ASTResult, lang::{core::{ast::ASTNodeId, pattern::{PatternId, get_identifier_string}}, env::Env}}; +use roc_module::symbol::Interns; + +use crate::{markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; + + +pub fn tld_mark_node<'a>( + identifier_id: PatternId, + expr_mark_node_id: MarkNodeId, + ast_node_id: ASTNodeId, + mark_node_pool: &mut SlowPool, + env: &Env<'a>, + interns: &Interns, +) -> ASTResult { + let pattern2 = env.pool.get(identifier_id); + let val_name = get_identifier_string(pattern2, interns)?; + + let val_name_mn = MarkupNode::Text { + content: val_name, + ast_node_id, + syn_high_style: HighlightStyle::Variable, + attributes: Attributes::new(), + parent_id_opt: None, + newlines_at_end: 0, + }; + + let val_name_mn_id = mark_node_pool.add(val_name_mn); + + let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); + + let full_let_node = MarkupNode::Nested { + ast_node_id, + children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id], + parent_id_opt: None, + newlines_at_end: 2, + }; + + Ok(full_let_node) +} \ No newline at end of file diff --git a/code_markup/src/markup_error.rs b/code_markup/src/markup_error.rs new file mode 100644 index 0000000000..a3d3d2a680 --- /dev/null +++ b/code_markup/src/markup_error.rs @@ -0,0 +1,56 @@ + +use roc_utils::util_error::UtilError; +use snafu::{Backtrace, NoneError, Snafu, ResultExt}; + +use crate::slow_pool::MarkNodeId; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum MarkError { + #[snafu(display( + "CaretNotFound: No carets were found in the expected node with id {}", + node_id + ))] + CaretNotFound { + node_id: MarkNodeId, + backtrace: Backtrace, + }, + #[snafu(display( + "ExpectedTextNode: the function {} expected a Text node, got {} instead.", + function_name, + node_type + ))] + ExpectedTextNode { + function_name: String, + node_type: String, + backtrace: Backtrace, + }, + #[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))] + NestedNodeMissingChild { + node_id: MarkNodeId, + children_ids: Vec, + backtrace: Backtrace, + }, + #[snafu(display( + "NestedNodeRequired: required a Nested node at this position, node was a {}.", + node_type + ))] + NestedNodeRequired { + node_type: String, + backtrace: Backtrace, + }, + #[snafu(display("UIError: {}", msg))] + UtilErrorBacktrace { msg: String, backtrace: Backtrace }, +} + +pub type MarkResult = std::result::Result; + +impl From for MarkError { + fn from(util_err: UtilError) -> Self { + let msg = format!("{}", util_err); + + // hack to handle MarkError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(UtilErrorBacktrace { msg }).unwrap_err() + } +} \ No newline at end of file diff --git a/code_markup/src/slow_pool.rs b/code_markup/src/slow_pool.rs new file mode 100644 index 0000000000..df8335b570 --- /dev/null +++ b/code_markup/src/slow_pool.rs @@ -0,0 +1,76 @@ + +use std::fmt; + +use crate::markup::nodes::MarkupNode; + +pub type MarkNodeId = usize; + +#[derive(Debug)] +pub struct SlowPool { + nodes: Vec, +} + +impl SlowPool { + pub fn new() -> SlowPool { + SlowPool { nodes: Vec::new() } + } + + pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { + let id = self.nodes.len(); + + self.nodes.push(node); + + id + } + + pub fn get(&self, node_id: MarkNodeId) -> &MarkupNode { + // unwrap because Pool doesn't return Result either + self.nodes.get(node_id).unwrap() + } + + pub fn get_mut(&mut self, node_id: MarkNodeId) -> &mut MarkupNode { + // unwrap because Pool doesn't return Result either + self.nodes.get_mut(node_id).unwrap() + } + + pub fn replace_node(&mut self, node_id: MarkNodeId, new_node: MarkupNode) { + self.nodes[node_id] = new_node; + + // TODO delete children of old node, this requires SlowPool to be changed to + // make sure the indexes still make sense after removal/compaction + } +} + +impl fmt::Display for SlowPool { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\n\n(mark_node_pool)\n")?; + + for (index, node) in self.nodes.iter().enumerate() { + let ast_node_id_str = format!("{:?}", node.get_ast_node_id()); + let ast_node_id: String = ast_node_id_str + .chars() + .filter(|c| c.is_ascii_digit()) + .collect(); + + let mut child_str = String::new(); + + let node_children = node.get_children_ids(); + + if !node_children.is_empty() { + child_str = format!("children: {:?}", node_children); + } + + writeln!( + f, + "{}: {} ({}) ast_id {:?} {}", + index, + node.node_type_as_string(), + node.get_content(), + ast_node_id.parse::().unwrap(), + child_str + )?; + } + + Ok(()) + } +} diff --git a/code_markup/src/syntax_highlight.rs b/code_markup/src/syntax_highlight.rs new file mode 100644 index 0000000000..2f5fcf6865 --- /dev/null +++ b/code_markup/src/syntax_highlight.rs @@ -0,0 +1,48 @@ + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use crate::colors::{self, RgbaTup, from_hsb}; + +#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] +pub enum HighlightStyle { + Operator, // =+-<>... + String, + FunctionName, + Type, + Bracket, + Number, + PackageRelated, // app, packages, imports, exposes, provides... + Variable, + RecordField, + Import, + Provides, + Blank, +} + +pub fn default_highlight_map() -> HashMap { + use HighlightStyle::*; + + let mut highlight_map = HashMap::new(); + [ + (Operator, colors::WHITE), + (String, from_hsb(346, 65, 97)), + (FunctionName, colors::WHITE), + (Type, colors::WHITE), + (Bracket, from_hsb(347, 80, 100)), + (Number, from_hsb(185, 50, 75)), + (PackageRelated, colors::WHITE), + (Variable, colors::WHITE), + (RecordField, from_hsb(258, 50, 90)), + (Import, from_hsb(185, 50, 75)), + (Provides, from_hsb(185, 50, 75)), + (Blank, from_hsb(258, 50, 90)), + // comment from_hsb(285, 6, 47) or 186, 35, 40 + ] + .iter() + .for_each(|tup| { + highlight_map.insert(tup.0, tup.1); + }); + + highlight_map +} diff --git a/editor/src/editor/slow_pool.rs b/editor/src/editor/slow_pool.rs index 72dc43336c..01058e9668 100644 --- a/editor/src/editor/slow_pool.rs +++ b/editor/src/editor/slow_pool.rs @@ -1,6 +1,9 @@ -use crate::editor::markup::nodes::MarkupNode; + use std::fmt; +use super::markup::nodes::MarkupNode; + + pub type MarkNodeId = usize; #[derive(Debug)] diff --git a/utils/Cargo.toml b/utils/Cargo.toml new file mode 100644 index 0000000000..75c3f928a4 --- /dev/null +++ b/utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "roc_utils" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +description = "Utility functions used all over the code base." + +[dependencies] +snafu = { version = "0.6", features = ["backtraces"] } + +[dev-dependencies] \ No newline at end of file diff --git a/utils/src/lib.rs b/utils/src/lib.rs new file mode 100644 index 0000000000..d9402be3e5 --- /dev/null +++ b/utils/src/lib.rs @@ -0,0 +1,96 @@ + +use snafu::OptionExt; +use util_error::{UtilResult, KeyNotFound, IndexOfFailed, OutOfBounds}; +use std::{collections::HashMap, slice::SliceIndex}; + +pub mod util_error; + +// replace HashMap method that returns Option with one that returns Result and proper Error +pub fn map_get<'a, K: ::std::fmt::Debug + std::hash::Hash + std::cmp::Eq, V>( + hash_map: &'a HashMap, + key: &K, +) -> UtilResult<&'a V> { + let value = hash_map.get(key).context(KeyNotFound { + key_str: format!("{:?}", key), + })?; + + Ok(value) +} + +pub fn index_of(elt: T, slice: &[T]) -> UtilResult { + let index = slice + .iter() + .position(|slice_elt| *slice_elt == elt) + .with_context(|| { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailed { + elt_str, + collection_str, + } + })?; + + Ok(index) +} + +// replace slice method that return Option with one that return Result and proper Error +pub fn slice_get(index: usize, slice: &[T]) -> UtilResult<&>::Output> { + let elt_ref = slice.get(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: slice.len(), + })?; + + Ok(elt_ref) +} + +pub fn slice_get_mut( + index: usize, + slice: &mut [T], +) -> UtilResult<&mut >::Output> { + let slice_len = slice.len(); + + let elt_ref = slice.get_mut(index).context(OutOfBounds { + index, + collection_name: "Slice", + len: slice_len, + })?; + + Ok(elt_ref) +} + +// returns the index of the first occurrence of element and index of the last occurrence +pub fn first_last_index_of( + elt: T, + slice: &[T], +) -> UtilResult<(usize, usize)> { + let mut first_index_opt = None; + let mut last_index_opt = None; + + for (index, list_elt) in slice.iter().enumerate() { + if *list_elt == elt { + if first_index_opt.is_none() { + first_index_opt = Some(index); + last_index_opt = Some(index); + } else { + last_index_opt = Some(index) + } + } else if last_index_opt.is_some() { + break; + } + } + + if let (Some(first_index), Some(last_index)) = (first_index_opt, last_index_opt) { + Ok((first_index, last_index)) + } else { + let elt_str = format!("{:?}", elt); + let collection_str = format!("{:?}", slice); + + IndexOfFailed { + elt_str, + collection_str, + } + .fail() + } +} diff --git a/utils/src/util_error.rs b/utils/src/util_error.rs new file mode 100644 index 0000000000..fda0c1a565 --- /dev/null +++ b/utils/src/util_error.rs @@ -0,0 +1,36 @@ + +use snafu::{Backtrace, Snafu}; + +#[derive(Debug, Snafu)] +#[snafu(visibility(pub))] +pub enum UtilError { + #[snafu(display( + "IndexOfFailed: Element {} was not found in collection {}.", + elt_str, + collection_str + ))] + IndexOfFailed { + elt_str: String, + collection_str: String, + backtrace: Backtrace, + }, + #[snafu(display("KeyNotFound: key {} was not found in HashMap.", key_str,))] + KeyNotFound { + key_str: String, + backtrace: Backtrace, + }, + #[snafu(display( + "OutOfBounds: index {} was out of bounds for {} with length {}.", + index, + collection_name, + len + ))] + OutOfBounds { + index: usize, + collection_name: String, + len: usize, + backtrace: Backtrace, + }, +} + +pub type UtilResult = std::result::Result; \ No newline at end of file From 23e8f6c68788f30ba7da3854e9745ec26992e767 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 21:53:41 +0200 Subject: [PATCH 26/96] only introduce rigid once! --- compiler/constrain/src/expr.rs | 13 ++++- compiler/test_gen/src/gen_primitives.rs | 74 +++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 77b8a3c82c..5584779f03 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -9,7 +9,7 @@ use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; use roc_can::expr::{Field, WhenBranch}; use roc_can::pattern::Pattern; -use roc_collections::all::{ImMap, Index, SendMap}; +use roc_collections::all::{ImMap, Index, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; @@ -1438,13 +1438,15 @@ fn instantiate_rigids( annotation: &Type, introduced_vars: &IntroducedVariables, new_rigids: &mut Vec, - ftv: &mut ImMap, + ftv: &mut ImMap, // rigids defined before the current annotation loc_pattern: &Located, headers: &mut SendMap>, ) -> Type { let mut annotation = annotation.clone(); let mut rigid_substitution: ImMap = ImMap::default(); + let outside_rigids: MutSet = ftv.values().copied().collect(); + for (name, var) in introduced_vars.var_by_name.iter() { if let Some(existing_rigid) = ftv.get(name) { rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); @@ -1464,7 +1466,12 @@ fn instantiate_rigids( &Located::at(loc_pattern.region, annotation.clone()), ) { for (symbol, loc_type) in new_headers { - new_rigids.extend(loc_type.value.variables()); + for var in loc_type.value.variables() { + // a rigid is only new if this annotation is the first occurence of this rigid + if !outside_rigids.contains(&var) { + new_rigids.push(var); + } + } headers.insert(symbol, loc_type); } } diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index d183b95d0b..1de82fa5f7 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2906,3 +2906,77 @@ fn do_pass_bool_byte_closure_layout() { RocStr ); } + +#[test] +fn nested_rigid_list() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + foo : List a -> List a + foo = \list -> + p2 : List a + p2 = list + + p2 + + main = + when foo [] is + _ -> "hello world" + "# + ), + RocStr::from_slice(b"hello world"), + RocStr + ); +} + +#[test] +fn nested_rigid_alias() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + Identity a : [ @Identity a ] + + foo : Identity a -> Identity a + foo = \list -> + p2 : Identity a + p2 = list + + p2 + + main = + when foo (@Identity "foo") is + _ -> "hello world" + "# + ), + RocStr::from_slice(b"hello world"), + RocStr + ); +} + +#[test] +fn nested_rigid_tag_union() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + foo : [ @Identity a ] -> [ @Identity a ] + foo = \list -> + p2 : [ @Identity a ] + p2 = list + + p2 + + main = + when foo (@Identity "foo") is + _ -> "hello world" + "# + ), + RocStr::from_slice(b"hello world"), + RocStr + ); +} From 72194b87dfa27d83d49d1dfcb07830a45ab75f04 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 24 Sep 2021 21:58:25 +0200 Subject: [PATCH 27/96] fix typo --- compiler/constrain/src/expr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 5584779f03..4e920147d0 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1467,7 +1467,7 @@ fn instantiate_rigids( ) { for (symbol, loc_type) in new_headers { for var in loc_type.value.variables() { - // a rigid is only new if this annotation is the first occurence of this rigid + // a rigid is only new if this annotation is the first occurrence of this rigid if !outside_rigids.contains(&var) { new_rigids.push(var); } From eae8a2ea372576912d9bc866d5b96bb4763f00f3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 13:43:00 -0700 Subject: [PATCH 28/96] Add surgical linker to cli_run tests that don't use rust --- cli/tests/cli_run.rs | 28 ++++++++++++++++++++++++++-- examples/hello-rust/.gitignore | 2 +- examples/hello-rust/Hello.roc | 2 +- linker/README.md | 1 - linker/src/lib.rs | 1 + 5 files changed, 29 insertions(+), 5 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index aa7020700f..6b0f34780b 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -18,6 +18,13 @@ mod cli_run { #[cfg(not(debug_assertions))] use roc_collections::all::MutMap; + #[cfg(target_os = "linux")] + const TEST_SURGICAL_LINKER: bool = true; + + // Surgical linker currently only supports linux. + #[cfg(not(target_os = "linux"))] + const TEST_SURGICAL_LINKER: bool = false; + #[cfg(not(target_os = "macos"))] const ALLOW_VALGRIND: bool = true; @@ -136,7 +143,6 @@ mod cli_run { ); } } - /// This macro does two things. /// /// First, it generates and runs a separate test for each of the given @@ -184,6 +190,24 @@ mod cli_run { example.expected_ending, example.use_valgrind, ); + + // Also check with the surgical linker. + + if TEST_SURGICAL_LINKER { + if matches!(example.executable_filename, "echo" | "hello-rust") { + eprintln!("WARNING: skipping testing example {} with surgical linking because rust is currently not supported!", example.filename); + return; + } + + check_output_with_stdin( + &file_name, + example.stdin, + example.executable_filename, + &["--roc-linker"], + example.expected_ending, + example.use_valgrind, + ); + } } )* @@ -228,7 +252,7 @@ mod cli_run { }, hello_rust:"hello-rust" => Example { filename: "Hello.roc", - executable_filename: "hello-world", + executable_filename: "hello-rust", stdin: &[], expected_ending:"Hello, World!\n", use_valgrind: true, diff --git a/examples/hello-rust/.gitignore b/examples/hello-rust/.gitignore index 6b820fd903..8485821d7c 100644 --- a/examples/hello-rust/.gitignore +++ b/examples/hello-rust/.gitignore @@ -1 +1 @@ -hello-world +hello-rust diff --git a/examples/hello-rust/Hello.roc b/examples/hello-rust/Hello.roc index d78f48ff19..cd7092308d 100644 --- a/examples/hello-rust/Hello.roc +++ b/examples/hello-rust/Hello.roc @@ -1,4 +1,4 @@ -app "hello-world" +app "hello-rust" packages { base: "platform" } imports [] provides [ main ] to base diff --git a/linker/README.md b/linker/README.md index 0d1e2d77ee..d65d68e14b 100644 --- a/linker/README.md +++ b/linker/README.md @@ -31,7 +31,6 @@ This linker is run in 2 phases: preprocessing and surigical linking. ## TODO (In a lightly prioritized order) -- Run CLI tests and/or benchmarks with the Roc Linker. - Test with an executable completely generated by Cargo (It will hopefully work out of the box like zig). - Add Macho support - Honestly should be almost exactly the same code. diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 1a043fbce1..3fa53e1bbd 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -199,6 +199,7 @@ fn generate_dynamic_lib( // TODO properly generate this list. for name in &[ format!("roc__{}_1_exposed", sym), + format!("roc__{}_1_exposed_generic", sym), format!("roc__{}_1_Fx_caller", sym), format!("roc__{}_1_Fx_size", sym), format!("roc__{}_1_Fx_result_size", sym), From 411ed58eecaaa4d17ef4a5474b3c926c8b3fa48d Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 21:37:07 -0700 Subject: [PATCH 29/96] Fix rust host with surgical linking --- cli/tests/cli_run.rs | 5 - compiler/build/src/link.rs | 29 +- examples/cli/platform/Cargo.toml | 10 +- examples/cli/platform/build.rs | 4 + examples/cli/platform/host.c | 6 +- examples/cli/platform/src/lib.rs | 26 +- examples/cli/platform/src/main.rs | 64 +++ examples/hello-rust/platform/Cargo.toml | 9 +- examples/hello-rust/platform/build.rs | 4 + examples/hello-rust/platform/host.c | 11 +- examples/hello-rust/platform/src/lib.rs | 20 +- examples/hello-rust/platform/src/main.rs | 51 ++ linker/README.md | 2 +- linker/src/lib.rs | 87 ++- linker/src/metadata.rs | 8 +- ult = result ^ roc_memcpy_ptr | 651 +++++++++++++++++++++++ 16 files changed, 920 insertions(+), 67 deletions(-) create mode 100644 examples/cli/platform/build.rs create mode 100644 examples/cli/platform/src/main.rs create mode 100644 examples/hello-rust/platform/build.rs create mode 100644 examples/hello-rust/platform/src/main.rs create mode 100644 ult = result ^ roc_memcpy_ptr diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 6b0f34780b..718f9df4bf 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -194,11 +194,6 @@ mod cli_run { // Also check with the surgical linker. if TEST_SURGICAL_LINKER { - if matches!(example.executable_filename, "echo" | "hello-rust") { - eprintln!("WARNING: skipping testing example {} with surgical linking because rust is currently not supported!", example.filename); - return; - } - check_output_with_stdin( &file_name, example.stdin, diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 40a8f84cbf..4764493911 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -370,7 +370,7 @@ pub fn rebuild_host( } else if cargo_host_src.exists() { // Compile and link Cargo.toml, if it exists let cargo_dir = host_input_path.parent().unwrap(); - let libhost_dir = + let cargo_out_dir = cargo_dir .join("target") .join(if matches!(opt_level, OptLevel::Optimize) { @@ -378,30 +378,29 @@ pub fn rebuild_host( } else { "debug" }); - let libhost = libhost_dir.join("libhost.a"); let mut command = Command::new("cargo"); command.arg("build").current_dir(cargo_dir); if matches!(opt_level, OptLevel::Optimize) { command.arg("--release"); } + let source_file = if shared_lib_path.is_some() { + command.args(&["--bin", "host"]); + "src/main.rs" + } else { + command.arg("--lib"); + "src/lib.rs" + }; let output = command.output().unwrap(); - validate_output("src/lib.rs", "cargo build", output); + validate_output(source_file, "cargo build", output); - // Cargo hosts depend on a c wrapper for the api. Compile host.c as well. if shared_lib_path.is_some() { - // If compiling to executable, let c deal with linking as well. - let output = build_c_host_native( - &env_path, - &env_home, - host_dest_native.to_str().unwrap(), - &[c_host_src.to_str().unwrap(), libhost.to_str().unwrap()], - opt_level, - shared_lib_path, - ); - validate_output("host.c", "clang", output); + // For surgical linking, just copy the dynamically linked rust app. + std::fs::copy(cargo_out_dir.join("host"), host_dest_native).unwrap(); } else { + // Cargo hosts depend on a c wrapper for the api. Compile host.c as well. + let output = build_c_host_native( &env_path, &env_home, @@ -418,7 +417,7 @@ pub fn rebuild_host( .args(&[ "-r", "-L", - libhost_dir.to_str().unwrap(), + cargo_out_dir.to_str().unwrap(), c_host_dest.to_str().unwrap(), "-lhost", "-o", diff --git a/examples/cli/platform/Cargo.toml b/examples/cli/platform/Cargo.toml index ad2bc7c449..eba1dfa680 100644 --- a/examples/cli/platform/Cargo.toml +++ b/examples/cli/platform/Cargo.toml @@ -5,8 +5,16 @@ authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" +links = "app" + [lib] -crate-type = ["staticlib"] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" [dependencies] roc_std = { path = "../../../roc_std" } diff --git a/examples/cli/platform/build.rs b/examples/cli/platform/build.rs new file mode 100644 index 0000000000..73159e387c --- /dev/null +++ b/examples/cli/platform/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/examples/cli/platform/host.c b/examples/cli/platform/host.c index 0378c69589..645d900c8e 100644 --- a/examples/cli/platform/host.c +++ b/examples/cli/platform/host.c @@ -1,7 +1,3 @@ -#include - extern int rust_main(); -int main() { - return rust_main(); -} +int main() { return rust_main(); } diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index 2b24da5ff7..d313d6f424 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -27,12 +27,12 @@ extern "C" { } #[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { libc::malloc(size) } #[no_mangle] -pub unsafe fn roc_realloc( +pub unsafe extern "C" fn roc_realloc( c_ptr: *mut c_void, new_size: usize, _old_size: usize, @@ -42,12 +42,12 @@ pub unsafe fn roc_realloc( } #[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { libc::free(c_ptr) } #[no_mangle] -pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { match tag_id { 0 => { let slice = CStr::from_ptr(c_ptr as *const c_char); @@ -60,7 +60,17 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { } #[no_mangle] -pub fn rust_main() -> isize { +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[no_mangle] +pub extern "C" fn rust_main() -> isize { let size = unsafe { roc_main_size() } as usize; let layout = Layout::array::(size).unwrap(); @@ -81,7 +91,7 @@ pub fn rust_main() -> isize { 0 } -unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { +unsafe extern "C" fn call_the_closure(closure_data_ptr: *const u8) -> i64 { let size = size_Fx_result() as usize; let layout = Layout::array::(size).unwrap(); let buffer = std::alloc::alloc(layout) as *mut u8; @@ -99,7 +109,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { } #[no_mangle] -pub fn roc_fx_getLine() -> RocStr { +pub extern "C" fn roc_fx_getLine() -> RocStr { use std::io::{self, BufRead}; let stdin = io::stdin(); @@ -109,7 +119,7 @@ pub fn roc_fx_getLine() -> RocStr { } #[no_mangle] -pub fn roc_fx_putLine(line: RocStr) -> () { +pub extern "C" fn roc_fx_putLine(line: RocStr) -> () { let bytes = line.as_slice(); let string = unsafe { std::str::from_utf8_unchecked(bytes) }; println!("{}", string); diff --git a/examples/cli/platform/src/main.rs b/examples/cli/platform/src/main.rs new file mode 100644 index 0000000000..0d2ec6edb1 --- /dev/null +++ b/examples/cli/platform/src/main.rs @@ -0,0 +1,64 @@ +#![allow(non_snake_case)] + +use core::ffi::c_void; +use roc_std::RocStr; + +fn main() { + let mut result = host::rust_main(); + // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. + if result == 0x1234_5678_9ABC_DEF0 { + let roc_alloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, + ) + }; + let roc_realloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_realloc + as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, + ) + }; + let roc_dealloc_ptr: isize = unsafe { + std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_panic_ptr: isize = unsafe { + std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_memcpy_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memcpy + as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, + ) + }; + let roc_memset_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memset + as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, + ) + }; + let roc_fx_putLine_ptr: isize = unsafe { + std::mem::transmute(host::roc_fx_putLine as *const extern "C" fn(line: RocStr) -> ()) + }; + let roc_fx_getLine_ptr: isize = unsafe { + std::mem::transmute(host::roc_fx_getLine as *const extern "C" fn() -> RocStr) + }; + // I really want to use the equivalent of std::hint::black_box, but it is expirimental. + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + result = result ^ roc_fx_putLine_ptr; + result = result ^ roc_fx_getLine_ptr; + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + result = result ^ roc_fx_putLine_ptr; + result = result ^ roc_fx_getLine_ptr; + } + std::process::exit(result as i32); +} diff --git a/examples/hello-rust/platform/Cargo.toml b/examples/hello-rust/platform/Cargo.toml index ad2bc7c449..72f534c88e 100644 --- a/examples/hello-rust/platform/Cargo.toml +++ b/examples/hello-rust/platform/Cargo.toml @@ -4,9 +4,16 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2018" +links = "app" [lib] -crate-type = ["staticlib"] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" [dependencies] roc_std = { path = "../../../roc_std" } diff --git a/examples/hello-rust/platform/build.rs b/examples/hello-rust/platform/build.rs new file mode 100644 index 0000000000..73159e387c --- /dev/null +++ b/examples/hello-rust/platform/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/examples/hello-rust/platform/host.c b/examples/hello-rust/platform/host.c index 9b91965724..b9214bcf33 100644 --- a/examples/hello-rust/platform/host.c +++ b/examples/hello-rust/platform/host.c @@ -1,12 +1,3 @@ -#include -#include - extern int rust_main(); -int main() { return rust_main(); } - -void *roc_memcpy(void *dest, const void *src, size_t n) { - return memcpy(dest, src, n); -} - -void *roc_memset(void *str, int c, size_t n) { return memset(str, c, n); } \ No newline at end of file +int main() { return rust_main(); } \ No newline at end of file diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index 6a78b4db0c..d81192b939 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -12,12 +12,12 @@ extern "C" { } #[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { return libc::malloc(size); } #[no_mangle] -pub unsafe fn roc_realloc( +pub unsafe extern "C" fn roc_realloc( c_ptr: *mut c_void, new_size: usize, _old_size: usize, @@ -27,12 +27,12 @@ pub unsafe fn roc_realloc( } #[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { return libc::free(c_ptr); } #[no_mangle] -pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { match tag_id { 0 => { let slice = CStr::from_ptr(c_ptr as *const c_char); @@ -45,7 +45,17 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { } #[no_mangle] -pub fn rust_main() -> isize { +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} + +#[no_mangle] +pub extern "C" fn rust_main() -> isize { unsafe { let roc_str = roc_main(); diff --git a/examples/hello-rust/platform/src/main.rs b/examples/hello-rust/platform/src/main.rs new file mode 100644 index 0000000000..49cefd692f --- /dev/null +++ b/examples/hello-rust/platform/src/main.rs @@ -0,0 +1,51 @@ +use core::ffi::c_void; + +fn main() { + let mut result = host::rust_main(); + // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. + if result == 0x1234_5678_9ABC_DEF0 { + let roc_alloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, + ) + }; + let roc_realloc_ptr: isize = unsafe { + std::mem::transmute( + host::roc_realloc + as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, + ) + }; + let roc_dealloc_ptr: isize = unsafe { + std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_panic_ptr: isize = unsafe { + std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) + }; + let roc_memcpy_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memcpy + as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, + ) + }; + let roc_memset_ptr: isize = unsafe { + std::mem::transmute( + host::roc_memset + as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, + ) + }; + // I really want to use the equivalent of std::hint::black_box, but it is expirimental. + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + result = result ^ roc_alloc_ptr; + result = result ^ roc_realloc_ptr; + result = result ^ roc_dealloc_ptr; + result = result ^ roc_panic_ptr; + result = result ^ roc_memcpy_ptr; + result = result ^ roc_memset_ptr; + } + std::process::exit(result as i32); +} diff --git a/linker/README.md b/linker/README.md index d65d68e14b..150eef59d3 100644 --- a/linker/README.md +++ b/linker/README.md @@ -31,7 +31,7 @@ This linker is run in 2 phases: preprocessing and surigical linking. ## TODO (In a lightly prioritized order) -- Test with an executable completely generated by Cargo (It will hopefully work out of the box like zig). +- Try to make rust hosts nicer. Currently making it expose functions is a huge pain. - Add Macho support - Honestly should be almost exactly the same code. This means we likely need to do a lot of refactoring to minimize the duplicate code. diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 3fa53e1bbd..15ec09084a 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -28,6 +28,7 @@ use target_lexicon::Triple; use tempfile::Builder; mod metadata; +use metadata::VirtualOffset; pub const CMD_PREPROCESS: &str = "preprocess"; pub const CMD_SURGERY: &str = "surgery"; @@ -196,7 +197,6 @@ fn generate_dynamic_lib( let text_section = out_object.section_id(write::StandardSection::Text); for sym in exposed_to_host { - // TODO properly generate this list. for name in &[ format!("roc__{}_1_exposed", sym), format!("roc__{}_1_exposed_generic", sym), @@ -317,7 +317,9 @@ fn preprocess_impl( for sym in exec_obj.symbols().filter(|sym| { sym.is_definition() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_") }) { - let name = sym.name().unwrap().to_string(); + // remove potentially trailing "@version". + let name = sym.name().unwrap().split("@").next().unwrap().to_string(); + // special exceptions for memcpy and memset. if &name == "roc_memcpy" { md.roc_symbol_vaddresses @@ -368,9 +370,6 @@ fn preprocess_impl( println!("PLT File Offset: {:+x}", plt_offset); } - // TODO: it looks like we may need to support global data host relocations. - // Rust host look to be using them by default instead of the plt. - // I think this is due to first linking into a static lib and then linking to the c wrapper. let plt_relocs = (match exec_obj.dynamic_relocations() { Some(relocs) => relocs, None => { @@ -380,7 +379,7 @@ fn preprocess_impl( } }) .map(|(_, reloc)| reloc) - .filter(|reloc| reloc.kind() == RelocationKind::Elf(7)); + .filter(|reloc| matches!(reloc.kind(), RelocationKind::Elf(7))); let app_syms: Vec = exec_obj .dynamic_symbols() @@ -388,6 +387,28 @@ fn preprocess_impl( sym.is_undefined() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_") }) .collect(); + + let got_app_syms: Vec<(String, usize)> = (match exec_obj.dynamic_relocations() { + Some(relocs) => relocs, + None => { + println!("Executable never calls any application functions."); + println!("No work to do. Probably an invalid input."); + return Ok(-1); + } + }) + .map(|(_, reloc)| reloc) + .filter(|reloc| matches!(reloc.kind(), RelocationKind::Elf(6))) + .map(|reloc| { + for symbol in app_syms.iter() { + if reloc.target() == RelocationTarget::Symbol(symbol.index()) { + return Some((symbol.name().unwrap().to_string(), symbol.index().0)); + } + } + None + }) + .filter_map(|x| x) + .collect(); + for sym in app_syms.iter() { let name = sym.name().unwrap().to_string(); md.app_functions.push(name.clone()); @@ -408,6 +429,7 @@ fn preprocess_impl( if reloc.target() == RelocationTarget::Symbol(symbol.index()) { let func_address = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_address; let func_offset = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_offset; + println!("{}", symbol.name().unwrap().to_string()); app_func_addresses.insert(func_address, symbol.name().unwrap()); md.plt_addresses.insert( symbol.name().unwrap().to_string(), @@ -537,7 +559,7 @@ fn preprocess_impl( .unwrap() .push(metadata::SurgeryEntry { file_offset: offset, - virtual_offset: inst.next_ip(), + virtual_offset: VirtualOffset::Relative(inst.next_ip()), size: op_size, }); } @@ -879,7 +901,7 @@ fn preprocess_impl( sec_offset as usize + md.added_byte_count as usize, sec_size as usize / mem::size_of::>(), ); - for rel in relocations.iter_mut() { + for (i, rel) in relocations.iter_mut().enumerate() { let r_offset = rel.r_offset.get(NativeEndian); if virtual_shift_start <= r_offset { rel.r_offset = endian::U64::new(LittleEndian, r_offset + md.added_byte_count); @@ -891,6 +913,28 @@ fn preprocess_impl( .set(LittleEndian, r_addend + md.added_byte_count as i64); } } + // If the relocation goes to a roc function, we need to surgically link it and change it to relative. + let r_type = rel.r_type(NativeEndian, false); + if r_type == elf::R_X86_64_GLOB_DAT { + let r_sym = rel.r_sym(NativeEndian, false); + for (name, index) in got_app_syms.iter() { + if *index as u32 == r_sym { + rel.set_r_info(LittleEndian, false, 0, elf::R_X86_64_RELATIVE); + let addend_addr = sec_offset as usize + + i * mem::size_of::>() + // This 16 skips the first 2 fields and gets to the addend field. + + 16; + md.surgeries + .get_mut(name) + .unwrap() + .push(metadata::SurgeryEntry { + file_offset: addend_addr as u64, + virtual_offset: VirtualOffset::Absolute, + size: 8, + }); + } + } + } } } @@ -1462,7 +1506,7 @@ fn surgery_impl( let dynsym_offset = md.dynamic_symbol_table_section_offset + md.added_byte_count; for func_name in md.app_functions { - let virt_offset = match app_func_vaddr_map.get(&func_name) { + let func_virt_offset = match app_func_vaddr_map.get(&func_name) { Some(offset) => *offset as u64, None => { println!("Function, {}, was not defined by the app", &func_name); @@ -1472,7 +1516,7 @@ fn surgery_impl( if verbose { println!( "Updating calls to {} to the address: {:+x}", - &func_name, virt_offset + &func_name, func_virt_offset ); } @@ -1480,11 +1524,13 @@ fn surgery_impl( if verbose { println!("\tPerforming surgery: {:+x?}", s); } + let surgery_virt_offset = match s.virtual_offset { + VirtualOffset::Relative(vs) => (vs + md.added_byte_count) as i64, + VirtualOffset::Absolute => 0, + }; match s.size { 4 => { - let target = (virt_offset as i64 - - (s.virtual_offset + md.added_byte_count) as i64) - as i32; + let target = (func_virt_offset as i64 - surgery_virt_offset) as i32; if verbose { println!("\tTarget Jump: {:+x}", target); } @@ -1493,6 +1539,16 @@ fn surgery_impl( ..(s.file_offset + md.added_byte_count) as usize + 4] .copy_from_slice(&data); } + 8 => { + let target = func_virt_offset as i64 - surgery_virt_offset; + if verbose { + println!("\tTarget Jump: {:+x}", target); + } + let data = target.to_le_bytes(); + exec_mmap[(s.file_offset + md.added_byte_count) as usize + ..(s.file_offset + md.added_byte_count) as usize + 8] + .copy_from_slice(&data); + } x => { println!("Surgery size not yet supported: {}", x); return Ok(-1); @@ -1506,7 +1562,8 @@ fn surgery_impl( let plt_off = (*plt_off + md.added_byte_count) as usize; let plt_vaddr = *plt_vaddr + md.added_byte_count; let jmp_inst_len = 5; - let target = (virt_offset as i64 - (plt_vaddr as i64 + jmp_inst_len as i64)) as i32; + let target = + (func_virt_offset as i64 - (plt_vaddr as i64 + jmp_inst_len as i64)) as i32; if verbose { println!("\tPLT: {:+x}, {:+x}", plt_off, plt_vaddr); println!("\tTarget Jump: {:+x}", target); @@ -1525,7 +1582,7 @@ fn surgery_impl( dynsym_offset as usize + *i as usize * mem::size_of::>(), ); sym.st_shndx = endian::U16::new(LittleEndian, new_text_section_index as u16); - sym.st_value = endian::U64::new(LittleEndian, virt_offset as u64); + sym.st_value = endian::U64::new(LittleEndian, func_virt_offset as u64); sym.st_size = endian::U64::new( LittleEndian, match app_func_size_map.get(&func_name) { diff --git a/linker/src/metadata.rs b/linker/src/metadata.rs index 16f06232cd..f24bf9a626 100644 --- a/linker/src/metadata.rs +++ b/linker/src/metadata.rs @@ -1,10 +1,16 @@ use roc_collections::all::MutMap; use serde::{Deserialize, Serialize}; +#[derive(Serialize, Deserialize, PartialEq, Debug)] +pub enum VirtualOffset { + Absolute, + Relative(u64), +} + #[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct SurgeryEntry { pub file_offset: u64, - pub virtual_offset: u64, + pub virtual_offset: VirtualOffset, pub size: u8, } diff --git a/ult = result ^ roc_memcpy_ptr b/ult = result ^ roc_memcpy_ptr new file mode 100644 index 0000000000..88b26daf7c --- /dev/null +++ b/ult = result ^ roc_memcpy_ptr @@ -0,0 +1,651 @@ + +examples/hello-rust/platform/dynhost: file format elf64-x86-64 + +DYNAMIC RELOCATION RECORDS +OFFSET TYPE VALUE +000000000003f520 R_X86_64_RELATIVE *ABS*+0x00000000000112f0 +000000000003f528 R_X86_64_RELATIVE *ABS*+0x0000000000023b70 +000000000003f530 R_X86_64_RELATIVE *ABS*+0x0000000000011330 +000000000003f538 R_X86_64_RELATIVE *ABS*+0x00000000000113f0 +000000000003f550 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 +000000000003f558 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 +000000000003f560 R_X86_64_RELATIVE *ABS*+0x00000000000113e0 +000000000003f568 R_X86_64_RELATIVE *ABS*+0x0000000000006a10 +000000000003f580 R_X86_64_RELATIVE *ABS*+0x0000000000011560 +000000000003f598 R_X86_64_RELATIVE *ABS*+0x00000000000115f0 +000000000003f5a0 R_X86_64_RELATIVE *ABS*+0x0000000000011650 +000000000003f5b8 R_X86_64_RELATIVE *ABS*+0x0000000000011590 +000000000003f5c0 R_X86_64_RELATIVE *ABS*+0x0000000000011570 +000000000003f5c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f5e0 R_X86_64_RELATIVE *ABS*+0x0000000000015090 +000000000003f5e8 R_X86_64_RELATIVE *ABS*+0x0000000000014f80 +000000000003f5f0 R_X86_64_RELATIVE *ABS*+0x0000000000014f90 +000000000003f5f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f610 R_X86_64_RELATIVE *ABS*+0x0000000000015080 +000000000003f618 R_X86_64_RELATIVE *ABS*+0x0000000000014ea0 +000000000003f620 R_X86_64_RELATIVE *ABS*+0x0000000000014fe0 +000000000003f628 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f640 R_X86_64_RELATIVE *ABS*+0x0000000000015100 +000000000003f648 R_X86_64_RELATIVE *ABS*+0x0000000000014d80 +000000000003f650 R_X86_64_RELATIVE *ABS*+0x0000000000015030 +000000000003f658 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f670 R_X86_64_RELATIVE *ABS*+0x0000000000011870 +000000000003f678 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003f690 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f6a8 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f6c0 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f6d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f6f0 R_X86_64_RELATIVE *ABS*+0x00000000000116d0 +000000000003f6f8 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 +000000000003f710 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 +000000000003f728 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 +000000000003f740 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f758 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f770 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f788 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f7a0 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 +000000000003f7b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f7d0 R_X86_64_RELATIVE *ABS*+0x000000000001d480 +000000000003f7d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f7f0 R_X86_64_RELATIVE *ABS*+0x00000000000391b0 +000000000003f7f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f810 R_X86_64_RELATIVE *ABS*+0x0000000000039190 +000000000003f818 R_X86_64_RELATIVE *ABS*+0x0000000000012420 +000000000003f830 R_X86_64_RELATIVE *ABS*+0x00000000000239d0 +000000000003f838 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f850 R_X86_64_RELATIVE *ABS*+0x000000000003e1c0 +000000000003f858 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f870 R_X86_64_RELATIVE *ABS*+0x0000000000011700 +000000000003f878 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003f890 R_X86_64_RELATIVE *ABS*+0x0000000000011810 +000000000003f898 R_X86_64_RELATIVE *ABS*+0x0000000000007e14 +000000000003f8b0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f8c8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f8e0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f8f8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f910 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 +000000000003f928 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f940 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f958 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f970 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa +000000000003f988 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 +000000000003f9a0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003f9b8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003f9d0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003f9e8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa00 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa18 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa30 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 +000000000003fa48 R_X86_64_RELATIVE *ABS*+0x0000000000007fe0 +000000000003fa60 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003fa78 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003fa90 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003faa8 R_X86_64_RELATIVE *ABS*+0x000000000000805d +000000000003fac0 R_X86_64_RELATIVE *ABS*+0x0000000000008184 +000000000003fad0 R_X86_64_RELATIVE *ABS*+0x00000000000081a8 +000000000003fae0 R_X86_64_RELATIVE *ABS*+0x000000000000816e +000000000003faf8 R_X86_64_RELATIVE *ABS*+0x00000000000081ab +000000000003fb08 R_X86_64_RELATIVE *ABS*+0x00000000000081cc +000000000003fb20 R_X86_64_RELATIVE *ABS*+0x00000000000081f4 +000000000003fb30 R_X86_64_RELATIVE *ABS*+0x00000000000081cc +000000000003fb48 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003fb58 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003fb68 R_X86_64_RELATIVE *ABS*+0x0000000000008336 +000000000003fb78 R_X86_64_RELATIVE *ABS*+0x0000000000008341 +000000000003fb88 R_X86_64_RELATIVE *ABS*+0x0000000000008342 +000000000003fb98 R_X86_64_RELATIVE *ABS*+0x000000000000835e +000000000003fbb0 R_X86_64_RELATIVE *ABS*+0x000000000000837b +000000000003fbc8 R_X86_64_RELATIVE *ABS*+0x000000000000837b +000000000003fbe0 R_X86_64_RELATIVE *ABS*+0x0000000000008394 +000000000003fbf0 R_X86_64_RELATIVE *ABS*+0x0000000000013010 +000000000003fc08 R_X86_64_RELATIVE *ABS*+0x000000000001ef40 +000000000003fc10 R_X86_64_RELATIVE *ABS*+0x0000000000011910 +000000000003fc18 R_X86_64_RELATIVE *ABS*+0x0000000000011b40 +000000000003fc20 R_X86_64_RELATIVE *ABS*+0x0000000000013010 +000000000003fc38 R_X86_64_RELATIVE *ABS*+0x000000000001eda0 +000000000003fc40 R_X86_64_RELATIVE *ABS*+0x0000000000011a20 +000000000003fc48 R_X86_64_RELATIVE *ABS*+0x0000000000011b00 +000000000003fc50 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fc68 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fc80 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fc98 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fcb0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fcc8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fce0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fcf8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fd10 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 +000000000003fd28 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003fd40 R_X86_64_RELATIVE *ABS*+0x000000000001fe50 +000000000003fd48 R_X86_64_RELATIVE *ABS*+0x0000000000011c00 +000000000003fd50 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fd68 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fd80 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fd98 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fdb0 R_X86_64_RELATIVE *ABS*+0x00000000000083bc +000000000003fdc8 R_X86_64_RELATIVE *ABS*+0x000000000000843b +000000000003fdd8 R_X86_64_RELATIVE *ABS*+0x0000000000013090 +000000000003fdf0 R_X86_64_RELATIVE *ABS*+0x00000000000207d0 +000000000003fdf8 R_X86_64_RELATIVE *ABS*+0x0000000000011b80 +000000000003fe00 R_X86_64_RELATIVE *ABS*+0x000000000000844c +000000000003fe10 R_X86_64_RELATIVE *ABS*+0x00000000000083a4 +000000000003fe20 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +000000000003fe30 R_X86_64_RELATIVE *ABS*+0x00000000000084e3 +000000000003fe40 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fe58 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fe70 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fe88 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fea0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003feb8 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fed0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 +000000000003fee8 R_X86_64_RELATIVE *ABS*+0x000000000000856f +000000000003fef8 R_X86_64_RELATIVE *ABS*+0x0000000000008584 +000000000003ff08 R_X86_64_RELATIVE *ABS*+0x0000000000008585 +000000000003ff18 R_X86_64_RELATIVE *ABS*+0x00000000000085a2 +000000000003ff28 R_X86_64_RELATIVE *ABS*+0x00000000000085b7 +000000000003ff38 R_X86_64_RELATIVE *ABS*+0x00000000000085c5 +000000000003ff48 R_X86_64_RELATIVE *ABS*+0x00000000000085e1 +000000000003ff58 R_X86_64_RELATIVE *ABS*+0x0000000000008605 +000000000003ff70 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +000000000003ff88 R_X86_64_RELATIVE *ABS*+0x00000000000249d0 +000000000003ff90 R_X86_64_RELATIVE *ABS*+0x0000000000024a30 +000000000003ff98 R_X86_64_RELATIVE *ABS*+0x0000000000024a90 +000000000003ffa0 R_X86_64_RELATIVE *ABS*+0x0000000000024aa0 +000000000003ffa8 R_X86_64_RELATIVE *ABS*+0x000000000001e550 +000000000003ffb0 R_X86_64_RELATIVE *ABS*+0x000000000001e640 +000000000003ffb8 R_X86_64_RELATIVE *ABS*+0x000000000001ec50 +000000000003ffc8 R_X86_64_RELATIVE *ABS*+0x0000000000012190 +000000000003ffe0 R_X86_64_RELATIVE *ABS*+0x000000000001e2c0 +000000000003ffe8 R_X86_64_RELATIVE *ABS*+0x000000000001e330 +000000000003fff0 R_X86_64_RELATIVE *ABS*+0x000000000001e4c0 +000000000003fff8 R_X86_64_RELATIVE *ABS*+0x000000000001e540 +0000000000040000 R_X86_64_RELATIVE *ABS*+0x000000000001e4d0 +0000000000040008 R_X86_64_RELATIVE *ABS*+0x000000000001e890 +0000000000040010 R_X86_64_RELATIVE *ABS*+0x000000000001eb00 +0000000000040020 R_X86_64_RELATIVE *ABS*+0x0000000000008150 +0000000000040030 R_X86_64_RELATIVE *ABS*+0x0000000000008636 +0000000000040040 R_X86_64_RELATIVE *ABS*+0x0000000000008645 +0000000000040050 R_X86_64_RELATIVE *ABS*+0x0000000000008584 +0000000000040060 R_X86_64_RELATIVE *ABS*+0x0000000000008648 +0000000000040070 R_X86_64_RELATIVE *ABS*+0x0000000000008605 +0000000000040088 R_X86_64_RELATIVE *ABS*+0x0000000000008605 +00000000000400a0 R_X86_64_RELATIVE *ABS*+0x0000000000012d90 +00000000000400b8 R_X86_64_RELATIVE *ABS*+0x0000000000022690 +00000000000400c0 R_X86_64_RELATIVE *ABS*+0x0000000000022790 +00000000000400c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000400e0 R_X86_64_RELATIVE *ABS*+0x0000000000022830 +00000000000400e8 R_X86_64_RELATIVE *ABS*+0x0000000000022880 +00000000000400f0 R_X86_64_RELATIVE *ABS*+0x0000000000012190 +0000000000040108 R_X86_64_RELATIVE *ABS*+0x00000000000116e0 +0000000000040110 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +0000000000040128 R_X86_64_RELATIVE *ABS*+0x00000000000116f0 +0000000000040130 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +0000000000040148 R_X86_64_RELATIVE *ABS*+0x00000000000228f0 +0000000000040150 R_X86_64_RELATIVE *ABS*+0x0000000000022950 +0000000000040158 R_X86_64_RELATIVE *ABS*+0x0000000000008696 +0000000000040168 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +0000000000040178 R_X86_64_RELATIVE *ABS*+0x00000000000086c8 +0000000000040188 R_X86_64_RELATIVE *ABS*+0x00000000000086f9 +0000000000040198 R_X86_64_RELATIVE *ABS*+0x0000000000007c20 +00000000000401a8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000401c0 R_X86_64_RELATIVE *ABS*+0x0000000000020a40 +00000000000401c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c20 +00000000000401d0 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000401e8 R_X86_64_RELATIVE *ABS*+0x0000000000020810 +00000000000401f0 R_X86_64_RELATIVE *ABS*+0x0000000000011bf0 +00000000000401f8 R_X86_64_RELATIVE *ABS*+0x0000000000008724 +0000000000040210 R_X86_64_RELATIVE *ABS*+0x0000000000008758 +0000000000040220 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +0000000000040230 R_X86_64_RELATIVE *ABS*+0x0000000000008379 +0000000000040240 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 +0000000000040250 R_X86_64_RELATIVE *ABS*+0x00000000000087d0 +0000000000040260 R_X86_64_RELATIVE *ABS*+0x00000000000084da +0000000000040270 R_X86_64_RELATIVE *ABS*+0x0000000000006b40 +0000000000040280 R_X86_64_RELATIVE *ABS*+0x00000000000083a3 +0000000000040290 R_X86_64_RELATIVE *ABS*+0x0000000000008584 +00000000000402a0 R_X86_64_RELATIVE *ABS*+0x00000000000087fd +00000000000402b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 +00000000000402d0 R_X86_64_RELATIVE *ABS*+0x0000000000011780 +00000000000402d8 R_X86_64_RELATIVE *ABS*+0x000000000000884a +00000000000402f0 R_X86_64_RELATIVE *ABS*+0x000000000000887a +0000000000040308 R_X86_64_RELATIVE *ABS*+0x000000000000887a +0000000000040320 R_X86_64_RELATIVE *ABS*+0x00000000000088a6 +0000000000040330 R_X86_64_RELATIVE *ABS*+0x0000000000008902 +0000000000040348 R_X86_64_RELATIVE *ABS*+0x0000000000008902 +0000000000040360 R_X86_64_RELATIVE *ABS*+0x000000000000894d +0000000000040370 R_X86_64_RELATIVE *ABS*+0x0000000000008956 +0000000000040380 R_X86_64_RELATIVE *ABS*+0x0000000000008971 +0000000000040390 R_X86_64_RELATIVE *ABS*+0x000000000000897f +00000000000403a0 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 +00000000000403b8 R_X86_64_RELATIVE *ABS*+0x00000000000089d2 +00000000000403c8 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 +00000000000403e0 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 +00000000000403f8 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 +0000000000040410 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 +0000000000040428 R_X86_64_RELATIVE *ABS*+0x0000000000008a23 +0000000000040440 R_X86_64_RELATIVE *ABS*+0x0000000000008a96 +0000000000040458 R_X86_64_RELATIVE *ABS*+0x0000000000008abb +0000000000040470 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 +0000000000040488 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 +00000000000404a0 R_X86_64_RELATIVE *ABS*+0x0000000000008b2d +00000000000404b8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 +00000000000404d0 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 +00000000000404d8 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 +00000000000404e0 R_X86_64_RELATIVE *ABS*+0x000000000002d880 +00000000000404e8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 +0000000000040500 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 +0000000000040508 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 +0000000000040510 R_X86_64_RELATIVE *ABS*+0x000000000002d870 +0000000000040518 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 +0000000000040530 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 +0000000000040548 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040560 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040578 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040590 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000405f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040608 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040620 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040638 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040650 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040668 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040680 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040698 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000406f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040710 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040728 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040740 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040758 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040770 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040788 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407a0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407b8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407d0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000407e8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040800 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040818 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040830 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040848 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040860 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040878 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040890 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000408f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040908 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040920 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040938 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040950 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040968 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040980 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040998 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +00000000000409f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040a10 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 +0000000000040a28 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 +0000000000040a40 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 +0000000000040a58 R_X86_64_RELATIVE *ABS*+0x00000000000315f0 +0000000000040a70 R_X86_64_RELATIVE *ABS*+0x0000000000031590 +0000000000040a78 R_X86_64_RELATIVE *ABS*+0x000000000000904b +0000000000040a90 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040aa8 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040ac0 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040ad8 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040af0 R_X86_64_RELATIVE *ABS*+0x00000000000090db +0000000000040b08 R_X86_64_RELATIVE *ABS*+0x0000000000009260 +0000000000040b20 R_X86_64_RELATIVE *ABS*+0x0000000000009260 +0000000000040b38 R_X86_64_RELATIVE *ABS*+0x0000000000009260 +0000000000040b50 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040b68 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040b80 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040b98 R_X86_64_RELATIVE *ABS*+0x00000000000331f0 +0000000000040bb0 R_X86_64_RELATIVE *ABS*+0x000000000003d290 +0000000000040bb8 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040bd0 R_X86_64_RELATIVE *ABS*+0x000000000000960e +0000000000040be8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c00 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c18 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c30 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c48 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c60 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c78 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040c90 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040ca8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040cc0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040cd8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040cf0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d08 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d20 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d38 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d50 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d68 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d80 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040d98 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040db0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 +0000000000040dc8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040de0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040df8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e10 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e28 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e40 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e58 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e70 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040e88 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040ea0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040eb8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040ed0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040ee8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040f00 R_X86_64_RELATIVE *ABS*+0x0000000000009712 +0000000000040f18 R_X86_64_RELATIVE *ABS*+0x000000000000979a +0000000000040f28 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 +0000000000040f40 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 +0000000000040f58 R_X86_64_RELATIVE *ABS*+0x0000000000009828 +0000000000040f70 R_X86_64_RELATIVE *ABS*+0x0000000000009939 +0000000000040f80 R_X86_64_RELATIVE *ABS*+0x0000000000009975 +0000000000040f98 R_X86_64_RELATIVE *ABS*+0x0000000000009990 +0000000000040fa8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 +0000000000040fb8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000040fd0 R_X86_64_RELATIVE *ABS*+0x0000000000039180 +0000000000040fd8 R_X86_64_RELATIVE *ABS*+0x00000000000099e2 +0000000000040fe8 R_X86_64_RELATIVE *ABS*+0x00000000000099e3 +0000000000040ff8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 +0000000000041008 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 +0000000000041018 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 +0000000000041028 R_X86_64_RELATIVE *ABS*+0x0000000000007c80 +0000000000041038 R_X86_64_RELATIVE *ABS*+0x00000000000099e7 +0000000000041048 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 +0000000000041058 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d +0000000000041068 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f +0000000000041078 R_X86_64_RELATIVE *ABS*+0x0000000000009a3b +0000000000041088 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 +0000000000041098 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d +00000000000410a8 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f +00000000000410b8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +00000000000410c8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 +00000000000410d8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3f +00000000000410e8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041100 R_X86_64_RELATIVE *ABS*+0x0000000000039880 +0000000000041108 R_X86_64_RELATIVE *ABS*+0x000000000003a060 +0000000000041110 R_X86_64_RELATIVE *ABS*+0x000000000003a140 +0000000000041118 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 +0000000000041130 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 +0000000000041148 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041160 R_X86_64_RELATIVE *ABS*+0x000000000003deb0 +0000000000041168 R_X86_64_RELATIVE *ABS*+0x0000000000009a56 +0000000000041180 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041198 R_X86_64_RELATIVE *ABS*+0x000000000003a180 +00000000000411a0 R_X86_64_RELATIVE *ABS*+0x000000000003a190 +00000000000411a8 R_X86_64_RELATIVE *ABS*+0x000000000003a270 +00000000000411b0 R_X86_64_RELATIVE *ABS*+0x0000000000007c60 +00000000000411c8 R_X86_64_RELATIVE *ABS*+0x0000000000009b3b +00000000000411d8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d +00000000000411e8 R_X86_64_RELATIVE *ABS*+0x0000000000006c30 +00000000000411f8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d +0000000000041208 R_X86_64_RELATIVE *ABS*+0x0000000000009b6f +0000000000041218 R_X86_64_RELATIVE *ABS*+0x0000000000009b85 +0000000000041228 R_X86_64_RELATIVE *ABS*+0x0000000000009b92 +0000000000041238 R_X86_64_RELATIVE *ABS*+0x0000000000009ba7 +0000000000041248 R_X86_64_RELATIVE *ABS*+0x0000000000009a52 +0000000000041258 R_X86_64_RELATIVE *ABS*+0x0000000000009bfc +0000000000041270 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +0000000000041288 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412a0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412b8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +00000000000412e8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f +0000000000041300 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041318 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041330 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041348 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041360 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041378 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +0000000000041390 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +00000000000413a8 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e +00000000000413c0 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 +00000000000413d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d8e +00000000000413e0 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +00000000000413f0 R_X86_64_RELATIVE *ABS*+0x0000000000009da4 +0000000000041400 R_X86_64_RELATIVE *ABS*+0x0000000000007f18 +0000000000041410 R_X86_64_RELATIVE *ABS*+0x0000000000006b50 +0000000000041420 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +0000000000041430 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 +0000000000041440 R_X86_64_RELATIVE *ABS*+0x0000000000009db2 +0000000000041450 R_X86_64_RELATIVE *ABS*+0x0000000000008120 +0000000000041460 R_X86_64_RELATIVE *ABS*+0x0000000000009dd8 +0000000000041470 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e +0000000000041480 R_X86_64_RELATIVE *ABS*+0x0000000000009dde +0000000000041498 R_X86_64_RELATIVE *ABS*+0x0000000000009dde +00000000000414b0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a +00000000000414c8 R_X86_64_RELATIVE *ABS*+0x000000000000a34a +00000000000414e0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a +00000000000414f8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041510 R_X86_64_RELATIVE *ABS*+0x000000000003e130 +0000000000041518 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041530 R_X86_64_RELATIVE *ABS*+0x000000000003de00 +0000000000041538 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 +0000000000041550 R_X86_64_RELATIVE *ABS*+0x000000000003e030 +0000000000041780 R_X86_64_RELATIVE *ABS*+0x00000000000114b0 +0000000000041788 R_X86_64_RELATIVE *ABS*+0x0000000000011470 +0000000000041790 R_X86_64_RELATIVE *ABS*+0x0000000000011460 +0000000000041798 R_X86_64_RELATIVE *ABS*+0x0000000000011490 +00000000000417a0 R_X86_64_RELATIVE *ABS*+0x0000000000011480 +00000000000417a8 R_X86_64_RELATIVE *ABS*+0x00000000000114a0 +00000000000417b0 R_X86_64_RELATIVE *ABS*+0x000000000001fe20 +00000000000417b8 R_X86_64_RELATIVE *ABS*+0x0000000000022dc0 +00000000000417c0 R_X86_64_RELATIVE *ABS*+0x0000000000011690 +00000000000417f8 R_X86_64_RELATIVE *ABS*+0x0000000000011660 +0000000000041800 R_X86_64_RELATIVE *ABS*+0x0000000000011680 +0000000000041810 R_X86_64_RELATIVE *ABS*+0x0000000000039480 +0000000000041818 R_X86_64_RELATIVE *ABS*+0x000000000001fe40 +0000000000041820 R_X86_64_RELATIVE *ABS*+0x0000000000011400 +0000000000041828 R_X86_64_RELATIVE *ABS*+0x0000000000038ad0 +0000000000041830 R_X86_64_RELATIVE *ABS*+0x0000000000022970 +0000000000041838 R_X86_64_RELATIVE *ABS*+0x000000000003b1c0 +0000000000041840 R_X86_64_RELATIVE *ABS*+0x000000000003d660 +0000000000041848 R_X86_64_RELATIVE *ABS*+0x000000000003b1d0 +0000000000041850 R_X86_64_RELATIVE *ABS*+0x000000000003d700 +0000000000041858 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 +0000000000041860 R_X86_64_RELATIVE *ABS*+0x0000000000020d80 +0000000000041868 R_X86_64_RELATIVE *ABS*+0x000000000003b240 +0000000000041870 R_X86_64_RELATIVE *ABS*+0x000000000003a020 +0000000000041878 R_X86_64_RELATIVE *ABS*+0x000000000003a030 +0000000000041880 R_X86_64_RELATIVE *ABS*+0x000000000003d520 +0000000000041888 R_X86_64_RELATIVE *ABS*+0x000000000003d5c0 +0000000000041890 R_X86_64_RELATIVE *ABS*+0x000000000003da60 +0000000000041898 R_X86_64_RELATIVE *ABS*+0x000000000003d3e0 +00000000000418a0 R_X86_64_RELATIVE *ABS*+0x000000000003d480 +00000000000418a8 R_X86_64_RELATIVE *ABS*+0x000000000003d9d0 +00000000000418b0 R_X86_64_RELATIVE *ABS*+0x0000000000039490 +00000000000418b8 R_X86_64_RELATIVE *ABS*+0x000000000003b290 +00000000000418c0 R_X86_64_RELATIVE *ABS*+0x000000000003a310 +00000000000418c8 R_X86_64_RELATIVE *ABS*+0x0000000000011410 +00000000000418e0 R_X86_64_RELATIVE *ABS*+0x0000000000042f78 +00000000000418e8 R_X86_64_RELATIVE *ABS*+0x0000000000022450 +00000000000418f0 R_X86_64_RELATIVE *ABS*+0x00000000000204c0 +0000000000041910 R_X86_64_RELATIVE *ABS*+0x000000000003b8d0 +0000000000041918 R_X86_64_RELATIVE *ABS*+0x000000000003bcd0 +0000000000041920 R_X86_64_RELATIVE *ABS*+0x0000000000039570 +0000000000041928 R_X86_64_RELATIVE *ABS*+0x000000000003cb90 +0000000000041930 R_X86_64_RELATIVE *ABS*+0x000000000003b850 +0000000000041938 R_X86_64_RELATIVE *ABS*+0x000000000003b950 +0000000000041940 R_X86_64_RELATIVE *ABS*+0x0000000000039520 +0000000000041948 R_X86_64_RELATIVE *ABS*+0x0000000000039630 +0000000000041950 R_X86_64_RELATIVE *ABS*+0x000000000003b1b0 +0000000000041960 R_X86_64_RELATIVE *ABS*+0x0000000000011420 +0000000000041968 R_X86_64_RELATIVE *ABS*+0x0000000000038c90 +0000000000041970 R_X86_64_RELATIVE *ABS*+0x00000000000331c0 +0000000000041978 R_X86_64_RELATIVE *ABS*+0x0000000000033190 +0000000000041980 R_X86_64_RELATIVE *ABS*+0x0000000000038cb0 +0000000000041988 R_X86_64_RELATIVE *ABS*+0x0000000000031480 +0000000000041990 R_X86_64_RELATIVE *ABS*+0x0000000000033150 +0000000000041998 R_X86_64_RELATIVE *ABS*+0x000000000003b1e0 +00000000000419a0 R_X86_64_RELATIVE *ABS*+0x0000000000039ce0 +00000000000419a8 R_X86_64_RELATIVE *ABS*+0x0000000000024ab0 +00000000000419b0 R_X86_64_RELATIVE *ABS*+0x0000000000038f30 +00000000000419c0 R_X86_64_RELATIVE *ABS*+0x000000000001da50 +00000000000419d0 R_X86_64_RELATIVE *ABS*+0x00000000000397f0 +00000000000419f0 R_X86_64_RELATIVE *ABS*+0x0000000000024bf0 +00000000000419f8 R_X86_64_RELATIVE *ABS*+0x000000000003b170 +0000000000041a08 R_X86_64_RELATIVE *ABS*+0x000000000001f5a0 +0000000000041a10 R_X86_64_RELATIVE *ABS*+0x000000000001efb0 +0000000000041a38 R_X86_64_RELATIVE *ABS*+0x0000000000039280 +0000000000041a40 R_X86_64_RELATIVE *ABS*+0x000000000001d870 +0000000000041a50 R_X86_64_RELATIVE *ABS*+0x0000000000038a00 +0000000000041a58 R_X86_64_RELATIVE *ABS*+0x000000000003ba50 +0000000000041a68 R_X86_64_RELATIVE *ABS*+0x000000000003c600 +0000000000041a70 R_X86_64_RELATIVE *ABS*+0x000000000003ca80 +0000000000041a78 R_X86_64_RELATIVE *ABS*+0x000000000001fb70 +0000000000041a80 R_X86_64_RELATIVE *ABS*+0x000000000003b650 +0000000000041a88 R_X86_64_RELATIVE *ABS*+0x000000000003b150 +0000000000041a90 R_X86_64_RELATIVE *ABS*+0x000000000003c610 +0000000000041a98 R_X86_64_RELATIVE *ABS*+0x000000000003c620 +0000000000041aa0 R_X86_64_RELATIVE *ABS*+0x00000000000391d0 +0000000000041aa8 R_X86_64_RELATIVE *ABS*+0x000000000003b270 +0000000000041ab0 R_X86_64_RELATIVE *ABS*+0x000000000003e2c0 +0000000000041ab8 R_X86_64_RELATIVE *ABS*+0x000000000003d000 +0000000000041ac0 R_X86_64_RELATIVE *ABS*+0x0000000000020010 +0000000000041ac8 R_X86_64_RELATIVE *ABS*+0x000000000001d6c0 +0000000000041ad8 R_X86_64_RELATIVE *ABS*+0x0000000000021630 +0000000000041af8 R_X86_64_RELATIVE *ABS*+0x000000000003a2d0 +0000000000041b10 R_X86_64_RELATIVE *ABS*+0x0000000000039310 +0000000000041b18 R_X86_64_RELATIVE *ABS*+0x00000000000392f0 +0000000000041b20 R_X86_64_RELATIVE *ABS*+0x000000000001e000 +0000000000041b28 R_X86_64_RELATIVE *ABS*+0x0000000000022550 +0000000000041b30 R_X86_64_RELATIVE *ABS*+0x00000000000205c0 +0000000000041b38 R_X86_64_RELATIVE *ABS*+0x000000000002dad0 +0000000000041b40 R_X86_64_RELATIVE *ABS*+0x0000000000022590 +0000000000041b48 R_X86_64_RELATIVE *ABS*+0x0000000000039300 +0000000000041b58 R_X86_64_RELATIVE *ABS*+0x0000000000039320 +0000000000041b60 R_X86_64_RELATIVE *ABS*+0x0000000000022cd0 +0000000000041b68 R_X86_64_RELATIVE *ABS*+0x000000000002db20 +0000000000041b70 R_X86_64_RELATIVE *ABS*+0x000000000003dba0 +0000000000041bc0 R_X86_64_RELATIVE *ABS*+0x00000000000212f0 +0000000000041bc8 R_X86_64_RELATIVE *ABS*+0x00000000000224b0 +0000000000041bd0 R_X86_64_RELATIVE *ABS*+0x00000000000389a0 +0000000000041bd8 R_X86_64_RELATIVE *ABS*+0x000000000003a2c0 +0000000000041be0 R_X86_64_RELATIVE *ABS*+0x0000000000024f90 +0000000000041be8 R_X86_64_RELATIVE *ABS*+0x000000000003b200 +0000000000041bf0 R_X86_64_RELATIVE *ABS*+0x0000000000039d30 +0000000000041bf8 R_X86_64_RELATIVE *ABS*+0x0000000000039e80 +0000000000041c50 R_X86_64_RELATIVE *ABS*+0x0000000000038a10 +0000000000041c58 R_X86_64_RELATIVE *ABS*+0x0000000000011430 +0000000000041c68 R_X86_64_RELATIVE *ABS*+0x000000000001db70 +0000000000041c70 R_X86_64_RELATIVE *ABS*+0x000000000003e4a0 +0000000000041c78 R_X86_64_RELATIVE *ABS*+0x0000000000031310 +0000000000041c80 R_X86_64_RELATIVE *ABS*+0x0000000000031300 +0000000000041c88 R_X86_64_RELATIVE *ABS*+0x0000000000031990 +0000000000041c90 R_X86_64_RELATIVE *ABS*+0x0000000000031aa0 +0000000000041c98 R_X86_64_RELATIVE *ABS*+0x0000000000032fc0 +0000000000041ca0 R_X86_64_RELATIVE *ABS*+0x0000000000032fe0 +0000000000041ca8 R_X86_64_RELATIVE *ABS*+0x0000000000032f30 +0000000000041cb0 R_X86_64_RELATIVE *ABS*+0x0000000000031ae0 +0000000000041cb8 R_X86_64_RELATIVE *ABS*+0x000000000002ded0 +0000000000041cc0 R_X86_64_RELATIVE *ABS*+0x000000000002ea40 +0000000000041cc8 R_X86_64_RELATIVE *ABS*+0x0000000000023d10 +0000000000041ce8 R_X86_64_RELATIVE *ABS*+0x0000000000021ab0 +0000000000041cf8 R_X86_64_RELATIVE *ABS*+0x0000000000021a00 +0000000000041d28 R_X86_64_RELATIVE *ABS*+0x0000000000030e90 +0000000000041d30 R_X86_64_RELATIVE *ABS*+0x000000000003b9d0 +0000000000041d38 R_X86_64_RELATIVE *ABS*+0x000000000003d660 +0000000000041d40 R_X86_64_RELATIVE *ABS*+0x000000000003d700 +0000000000041d48 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 +0000000000041d50 R_X86_64_RELATIVE *ABS*+0x0000000000038af0 +0000000000041d58 R_X86_64_RELATIVE *ABS*+0x000000000003b730 +0000000000041d60 R_X86_64_RELATIVE *ABS*+0x000000000003d350 +0000000000041d68 R_X86_64_RELATIVE *ABS*+0x000000000003d340 +0000000000041d70 R_X86_64_RELATIVE *ABS*+0x000000000003e2a0 +0000000000041d78 R_X86_64_RELATIVE *ABS*+0x000000000003bcb0 +0000000000041d80 R_X86_64_RELATIVE *ABS*+0x0000000000034040 +0000000000041d88 R_X86_64_RELATIVE *ABS*+0x000000000003b2b0 +0000000000041d90 R_X86_64_RELATIVE *ABS*+0x0000000000037c40 +0000000000041d98 R_X86_64_RELATIVE *ABS*+0x0000000000033440 +0000000000041da0 R_X86_64_RELATIVE *ABS*+0x0000000000011440 +0000000000041da8 R_X86_64_RELATIVE *ABS*+0x0000000000021860 +0000000000041db0 R_X86_64_RELATIVE *ABS*+0x00000000000395f0 +0000000000041db8 R_X86_64_RELATIVE *ABS*+0x0000000000039880 +0000000000041dc0 R_X86_64_RELATIVE *ABS*+0x000000000003aaa0 +0000000000041dc8 R_X86_64_RELATIVE *ABS*+0x0000000000039b30 +0000000000041dd0 R_X86_64_RELATIVE *ABS*+0x000000000003a530 +0000000000042e08 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 +0000000000042e10 R_X86_64_RELATIVE *ABS*+0x000000000002dbe0 +0000000000042e28 R_X86_64_RELATIVE *ABS*+0x00000000000214f0 +0000000000042e38 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 +0000000000042e40 R_X86_64_RELATIVE *ABS*+0x0000000000008ab5 +0000000000041758 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5 +0000000000041760 R_X86_64_GLOB_DAT __gmon_start__@Base +0000000000041768 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable@Base +0000000000041770 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable@Base +0000000000041778 R_X86_64_GLOB_DAT __cxa_finalize@GLIBC_2.2.5 +00000000000417d8 R_X86_64_GLOB_DAT free@GLIBC_2.2.5 +00000000000417c8 R_X86_64_GLOB_DAT malloc@GLIBC_2.2.5 +00000000000417e0 R_X86_64_GLOB_DAT memcpy@GLIBC_2.14 +00000000000417e8 R_X86_64_GLOB_DAT memset@GLIBC_2.2.5 +00000000000417d0 R_X86_64_GLOB_DAT realloc@GLIBC_2.2.5 +00000000000417f0 R_X86_64_GLOB_DAT roc__mainForHost_1_exposed@Base +0000000000041808 R_X86_64_GLOB_DAT write@GLIBC_2.2.5 +0000000000041c00 R_X86_64_GLOB_DAT bcmp@GLIBC_2.2.5 +0000000000041a48 R_X86_64_GLOB_DAT _Unwind_Backtrace@GCC_3.3 +0000000000041a60 R_X86_64_GLOB_DAT _Unwind_GetIP@GCC_3.0 +0000000000041c48 R_X86_64_GLOB_DAT __cxa_thread_atexit_impl@GLIBC_2.18 +00000000000419e8 R_X86_64_GLOB_DAT __errno_location@GLIBC_2.2.5 +0000000000041c20 R_X86_64_GLOB_DAT __xpg_strerror_r@GLIBC_2.3.4 +0000000000041b80 R_X86_64_GLOB_DAT abort@GLIBC_2.2.5 +0000000000041b08 R_X86_64_GLOB_DAT calloc@GLIBC_2.2.5 +00000000000418d8 R_X86_64_GLOB_DAT close@GLIBC_2.2.5 +0000000000041c60 R_X86_64_GLOB_DAT dl_iterate_phdr@GLIBC_2.2.5 +0000000000041c08 R_X86_64_GLOB_DAT dlsym@GLIBC_2.2.5 +0000000000041c30 R_X86_64_GLOB_DAT exit@GLIBC_2.2.5 +00000000000419d8 R_X86_64_GLOB_DAT getcwd@GLIBC_2.2.5 +0000000000041c28 R_X86_64_GLOB_DAT getenv@GLIBC_2.2.5 +00000000000419b8 R_X86_64_GLOB_DAT memchr@GLIBC_2.2.5 +0000000000041908 R_X86_64_GLOB_DAT memmove@GLIBC_2.2.5 +0000000000041c38 R_X86_64_GLOB_DAT mmap@GLIBC_2.2.5 +0000000000041c40 R_X86_64_GLOB_DAT mprotect@GLIBC_2.2.5 +00000000000418f8 R_X86_64_GLOB_DAT munmap@GLIBC_2.2.5 +0000000000041b88 R_X86_64_GLOB_DAT open@GLIBC_2.2.5 +0000000000041c10 R_X86_64_GLOB_DAT open64@GLIBC_2.2.5 +0000000000041b78 R_X86_64_GLOB_DAT poll@GLIBC_2.2.5 +0000000000041b00 R_X86_64_GLOB_DAT posix_memalign@GLIBC_2.2.5 +0000000000041bb8 R_X86_64_GLOB_DAT pthread_attr_destroy@GLIBC_2.2.5 +0000000000041bb0 R_X86_64_GLOB_DAT pthread_attr_getstack@GLIBC_2.2.5 +0000000000041ba8 R_X86_64_GLOB_DAT pthread_getattr_np@GLIBC_2.2.5 +0000000000041ae0 R_X86_64_GLOB_DAT pthread_getspecific@GLIBC_2.2.5 +0000000000041ae8 R_X86_64_GLOB_DAT pthread_key_create@GLIBC_2.2.5 +0000000000041af0 R_X86_64_GLOB_DAT pthread_key_delete@GLIBC_2.2.5 +0000000000041958 R_X86_64_GLOB_DAT pthread_mutex_destroy@GLIBC_2.2.5 +00000000000419c8 R_X86_64_GLOB_DAT pthread_mutex_lock@GLIBC_2.2.5 +0000000000041a18 R_X86_64_GLOB_DAT pthread_mutex_trylock@GLIBC_2.2.5 +00000000000418d0 R_X86_64_GLOB_DAT pthread_mutex_unlock@GLIBC_2.2.5 +0000000000041b50 R_X86_64_GLOB_DAT pthread_rwlock_rdlock@GLIBC_2.2.5 +0000000000041900 R_X86_64_GLOB_DAT pthread_rwlock_unlock@GLIBC_2.2.5 +0000000000041ba0 R_X86_64_GLOB_DAT pthread_self@GLIBC_2.2.5 +0000000000041ad0 R_X86_64_GLOB_DAT pthread_setspecific@GLIBC_2.2.5 +0000000000041c18 R_X86_64_GLOB_DAT readlink@GLIBC_2.2.5 +0000000000041b98 R_X86_64_GLOB_DAT sigaction@GLIBC_2.2.5 +0000000000041a20 R_X86_64_GLOB_DAT sigaltstack@GLIBC_2.2.5 +0000000000041b90 R_X86_64_GLOB_DAT signal@GLIBC_2.2.5 +00000000000419e0 R_X86_64_GLOB_DAT strlen@GLIBC_2.2.5 +0000000000041a30 R_X86_64_GLOB_DAT syscall@GLIBC_2.2.5 +0000000000041a28 R_X86_64_GLOB_DAT sysconf@GLIBC_2.2.5 +0000000000041a00 R_X86_64_GLOB_DAT writev@GLIBC_2.2.5 +0000000000041ce0 R_X86_64_GLOB_DAT _Unwind_DeleteException@GCC_3.0 +0000000000041cd0 R_X86_64_GLOB_DAT _Unwind_GetDataRelBase@GCC_3.0 +0000000000041d08 R_X86_64_GLOB_DAT _Unwind_GetIPInfo@GCC_4.2.0 +0000000000041d00 R_X86_64_GLOB_DAT _Unwind_GetLanguageSpecificData@GCC_3.0 +0000000000041d10 R_X86_64_GLOB_DAT _Unwind_GetRegionStart@GCC_3.0 +0000000000041cd8 R_X86_64_GLOB_DAT _Unwind_GetTextRelBase@GCC_3.0 +0000000000041cf0 R_X86_64_GLOB_DAT _Unwind_RaiseException@GCC_3.0 +0000000000041d18 R_X86_64_GLOB_DAT _Unwind_SetGR@GCC_3.0 +0000000000041d20 R_X86_64_GLOB_DAT _Unwind_SetIP@GCC_3.0 +0000000000041df0 R_X86_64_JUMP_SLOT __cxa_finalize@GLIBC_2.2.5 +0000000000041df8 R_X86_64_JUMP_SLOT _Unwind_Resume@GCC_3.0 +0000000000041e00 R_X86_64_JUMP_SLOT __fxstat64@GLIBC_2.2.5 + + From d4c32df97d2611291be4cc7b48cb2f788fc8e85c Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 22:00:58 -0700 Subject: [PATCH 30/96] Sate clippy and fix accidental garbage file --- .gitignore | 1 + linker/src/lib.rs | 5 +- ult = result ^ roc_memcpy_ptr | 651 ---------------------------------- 3 files changed, 3 insertions(+), 654 deletions(-) delete mode 100644 ult = result ^ roc_memcpy_ptr diff --git a/.gitignore b/.gitignore index 1c7ea24a83..33679f024a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ zig-cache .direnv *.rs.bk *.o +*.tmp # llvm human-readable output *.ll diff --git a/linker/src/lib.rs b/linker/src/lib.rs index 15ec09084a..be4aa891c9 100644 --- a/linker/src/lib.rs +++ b/linker/src/lib.rs @@ -318,7 +318,7 @@ fn preprocess_impl( sym.is_definition() && sym.name().is_ok() && sym.name().unwrap().starts_with("roc_") }) { // remove potentially trailing "@version". - let name = sym.name().unwrap().split("@").next().unwrap().to_string(); + let name = sym.name().unwrap().split('@').next().unwrap().to_string(); // special exceptions for memcpy and memset. if &name == "roc_memcpy" { @@ -406,7 +406,7 @@ fn preprocess_impl( } None }) - .filter_map(|x| x) + .flatten() .collect(); for sym in app_syms.iter() { @@ -429,7 +429,6 @@ fn preprocess_impl( if reloc.target() == RelocationTarget::Symbol(symbol.index()) { let func_address = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_address; let func_offset = (i as u64 + 1) * PLT_ADDRESS_OFFSET + plt_offset; - println!("{}", symbol.name().unwrap().to_string()); app_func_addresses.insert(func_address, symbol.name().unwrap()); md.plt_addresses.insert( symbol.name().unwrap().to_string(), diff --git a/ult = result ^ roc_memcpy_ptr b/ult = result ^ roc_memcpy_ptr deleted file mode 100644 index 88b26daf7c..0000000000 --- a/ult = result ^ roc_memcpy_ptr +++ /dev/null @@ -1,651 +0,0 @@ - -examples/hello-rust/platform/dynhost: file format elf64-x86-64 - -DYNAMIC RELOCATION RECORDS -OFFSET TYPE VALUE -000000000003f520 R_X86_64_RELATIVE *ABS*+0x00000000000112f0 -000000000003f528 R_X86_64_RELATIVE *ABS*+0x0000000000023b70 -000000000003f530 R_X86_64_RELATIVE *ABS*+0x0000000000011330 -000000000003f538 R_X86_64_RELATIVE *ABS*+0x00000000000113f0 -000000000003f550 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 -000000000003f558 R_X86_64_RELATIVE *ABS*+0x00000000000113d0 -000000000003f560 R_X86_64_RELATIVE *ABS*+0x00000000000113e0 -000000000003f568 R_X86_64_RELATIVE *ABS*+0x0000000000006a10 -000000000003f580 R_X86_64_RELATIVE *ABS*+0x0000000000011560 -000000000003f598 R_X86_64_RELATIVE *ABS*+0x00000000000115f0 -000000000003f5a0 R_X86_64_RELATIVE *ABS*+0x0000000000011650 -000000000003f5b8 R_X86_64_RELATIVE *ABS*+0x0000000000011590 -000000000003f5c0 R_X86_64_RELATIVE *ABS*+0x0000000000011570 -000000000003f5c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f5e0 R_X86_64_RELATIVE *ABS*+0x0000000000015090 -000000000003f5e8 R_X86_64_RELATIVE *ABS*+0x0000000000014f80 -000000000003f5f0 R_X86_64_RELATIVE *ABS*+0x0000000000014f90 -000000000003f5f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f610 R_X86_64_RELATIVE *ABS*+0x0000000000015080 -000000000003f618 R_X86_64_RELATIVE *ABS*+0x0000000000014ea0 -000000000003f620 R_X86_64_RELATIVE *ABS*+0x0000000000014fe0 -000000000003f628 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f640 R_X86_64_RELATIVE *ABS*+0x0000000000015100 -000000000003f648 R_X86_64_RELATIVE *ABS*+0x0000000000014d80 -000000000003f650 R_X86_64_RELATIVE *ABS*+0x0000000000015030 -000000000003f658 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f670 R_X86_64_RELATIVE *ABS*+0x0000000000011870 -000000000003f678 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003f690 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f6a8 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f6c0 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f6d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f6f0 R_X86_64_RELATIVE *ABS*+0x00000000000116d0 -000000000003f6f8 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 -000000000003f710 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 -000000000003f728 R_X86_64_RELATIVE *ABS*+0x0000000000007d00 -000000000003f740 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f758 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f770 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f788 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f7a0 R_X86_64_RELATIVE *ABS*+0x0000000000007d70 -000000000003f7b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f7d0 R_X86_64_RELATIVE *ABS*+0x000000000001d480 -000000000003f7d8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f7f0 R_X86_64_RELATIVE *ABS*+0x00000000000391b0 -000000000003f7f8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f810 R_X86_64_RELATIVE *ABS*+0x0000000000039190 -000000000003f818 R_X86_64_RELATIVE *ABS*+0x0000000000012420 -000000000003f830 R_X86_64_RELATIVE *ABS*+0x00000000000239d0 -000000000003f838 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f850 R_X86_64_RELATIVE *ABS*+0x000000000003e1c0 -000000000003f858 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f870 R_X86_64_RELATIVE *ABS*+0x0000000000011700 -000000000003f878 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003f890 R_X86_64_RELATIVE *ABS*+0x0000000000011810 -000000000003f898 R_X86_64_RELATIVE *ABS*+0x0000000000007e14 -000000000003f8b0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f8c8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f8e0 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f8f8 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f910 R_X86_64_RELATIVE *ABS*+0x0000000000007e60 -000000000003f928 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f940 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f958 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f970 R_X86_64_RELATIVE *ABS*+0x0000000000007eaa -000000000003f988 R_X86_64_RELATIVE *ABS*+0x0000000000007b99 -000000000003f9a0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003f9b8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003f9d0 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003f9e8 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa00 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa18 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa30 R_X86_64_RELATIVE *ABS*+0x0000000000007f44 -000000000003fa48 R_X86_64_RELATIVE *ABS*+0x0000000000007fe0 -000000000003fa60 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003fa78 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003fa90 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003faa8 R_X86_64_RELATIVE *ABS*+0x000000000000805d -000000000003fac0 R_X86_64_RELATIVE *ABS*+0x0000000000008184 -000000000003fad0 R_X86_64_RELATIVE *ABS*+0x00000000000081a8 -000000000003fae0 R_X86_64_RELATIVE *ABS*+0x000000000000816e -000000000003faf8 R_X86_64_RELATIVE *ABS*+0x00000000000081ab -000000000003fb08 R_X86_64_RELATIVE *ABS*+0x00000000000081cc -000000000003fb20 R_X86_64_RELATIVE *ABS*+0x00000000000081f4 -000000000003fb30 R_X86_64_RELATIVE *ABS*+0x00000000000081cc -000000000003fb48 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003fb58 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003fb68 R_X86_64_RELATIVE *ABS*+0x0000000000008336 -000000000003fb78 R_X86_64_RELATIVE *ABS*+0x0000000000008341 -000000000003fb88 R_X86_64_RELATIVE *ABS*+0x0000000000008342 -000000000003fb98 R_X86_64_RELATIVE *ABS*+0x000000000000835e -000000000003fbb0 R_X86_64_RELATIVE *ABS*+0x000000000000837b -000000000003fbc8 R_X86_64_RELATIVE *ABS*+0x000000000000837b -000000000003fbe0 R_X86_64_RELATIVE *ABS*+0x0000000000008394 -000000000003fbf0 R_X86_64_RELATIVE *ABS*+0x0000000000013010 -000000000003fc08 R_X86_64_RELATIVE *ABS*+0x000000000001ef40 -000000000003fc10 R_X86_64_RELATIVE *ABS*+0x0000000000011910 -000000000003fc18 R_X86_64_RELATIVE *ABS*+0x0000000000011b40 -000000000003fc20 R_X86_64_RELATIVE *ABS*+0x0000000000013010 -000000000003fc38 R_X86_64_RELATIVE *ABS*+0x000000000001eda0 -000000000003fc40 R_X86_64_RELATIVE *ABS*+0x0000000000011a20 -000000000003fc48 R_X86_64_RELATIVE *ABS*+0x0000000000011b00 -000000000003fc50 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fc68 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fc80 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fc98 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fcb0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fcc8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fce0 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fcf8 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fd10 R_X86_64_RELATIVE *ABS*+0x00000000000083a5 -000000000003fd28 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003fd40 R_X86_64_RELATIVE *ABS*+0x000000000001fe50 -000000000003fd48 R_X86_64_RELATIVE *ABS*+0x0000000000011c00 -000000000003fd50 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fd68 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fd80 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fd98 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fdb0 R_X86_64_RELATIVE *ABS*+0x00000000000083bc -000000000003fdc8 R_X86_64_RELATIVE *ABS*+0x000000000000843b -000000000003fdd8 R_X86_64_RELATIVE *ABS*+0x0000000000013090 -000000000003fdf0 R_X86_64_RELATIVE *ABS*+0x00000000000207d0 -000000000003fdf8 R_X86_64_RELATIVE *ABS*+0x0000000000011b80 -000000000003fe00 R_X86_64_RELATIVE *ABS*+0x000000000000844c -000000000003fe10 R_X86_64_RELATIVE *ABS*+0x00000000000083a4 -000000000003fe20 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -000000000003fe30 R_X86_64_RELATIVE *ABS*+0x00000000000084e3 -000000000003fe40 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fe58 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fe70 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fe88 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fea0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003feb8 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fed0 R_X86_64_RELATIVE *ABS*+0x0000000000008520 -000000000003fee8 R_X86_64_RELATIVE *ABS*+0x000000000000856f -000000000003fef8 R_X86_64_RELATIVE *ABS*+0x0000000000008584 -000000000003ff08 R_X86_64_RELATIVE *ABS*+0x0000000000008585 -000000000003ff18 R_X86_64_RELATIVE *ABS*+0x00000000000085a2 -000000000003ff28 R_X86_64_RELATIVE *ABS*+0x00000000000085b7 -000000000003ff38 R_X86_64_RELATIVE *ABS*+0x00000000000085c5 -000000000003ff48 R_X86_64_RELATIVE *ABS*+0x00000000000085e1 -000000000003ff58 R_X86_64_RELATIVE *ABS*+0x0000000000008605 -000000000003ff70 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -000000000003ff88 R_X86_64_RELATIVE *ABS*+0x00000000000249d0 -000000000003ff90 R_X86_64_RELATIVE *ABS*+0x0000000000024a30 -000000000003ff98 R_X86_64_RELATIVE *ABS*+0x0000000000024a90 -000000000003ffa0 R_X86_64_RELATIVE *ABS*+0x0000000000024aa0 -000000000003ffa8 R_X86_64_RELATIVE *ABS*+0x000000000001e550 -000000000003ffb0 R_X86_64_RELATIVE *ABS*+0x000000000001e640 -000000000003ffb8 R_X86_64_RELATIVE *ABS*+0x000000000001ec50 -000000000003ffc8 R_X86_64_RELATIVE *ABS*+0x0000000000012190 -000000000003ffe0 R_X86_64_RELATIVE *ABS*+0x000000000001e2c0 -000000000003ffe8 R_X86_64_RELATIVE *ABS*+0x000000000001e330 -000000000003fff0 R_X86_64_RELATIVE *ABS*+0x000000000001e4c0 -000000000003fff8 R_X86_64_RELATIVE *ABS*+0x000000000001e540 -0000000000040000 R_X86_64_RELATIVE *ABS*+0x000000000001e4d0 -0000000000040008 R_X86_64_RELATIVE *ABS*+0x000000000001e890 -0000000000040010 R_X86_64_RELATIVE *ABS*+0x000000000001eb00 -0000000000040020 R_X86_64_RELATIVE *ABS*+0x0000000000008150 -0000000000040030 R_X86_64_RELATIVE *ABS*+0x0000000000008636 -0000000000040040 R_X86_64_RELATIVE *ABS*+0x0000000000008645 -0000000000040050 R_X86_64_RELATIVE *ABS*+0x0000000000008584 -0000000000040060 R_X86_64_RELATIVE *ABS*+0x0000000000008648 -0000000000040070 R_X86_64_RELATIVE *ABS*+0x0000000000008605 -0000000000040088 R_X86_64_RELATIVE *ABS*+0x0000000000008605 -00000000000400a0 R_X86_64_RELATIVE *ABS*+0x0000000000012d90 -00000000000400b8 R_X86_64_RELATIVE *ABS*+0x0000000000022690 -00000000000400c0 R_X86_64_RELATIVE *ABS*+0x0000000000022790 -00000000000400c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000400e0 R_X86_64_RELATIVE *ABS*+0x0000000000022830 -00000000000400e8 R_X86_64_RELATIVE *ABS*+0x0000000000022880 -00000000000400f0 R_X86_64_RELATIVE *ABS*+0x0000000000012190 -0000000000040108 R_X86_64_RELATIVE *ABS*+0x00000000000116e0 -0000000000040110 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -0000000000040128 R_X86_64_RELATIVE *ABS*+0x00000000000116f0 -0000000000040130 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -0000000000040148 R_X86_64_RELATIVE *ABS*+0x00000000000228f0 -0000000000040150 R_X86_64_RELATIVE *ABS*+0x0000000000022950 -0000000000040158 R_X86_64_RELATIVE *ABS*+0x0000000000008696 -0000000000040168 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -0000000000040178 R_X86_64_RELATIVE *ABS*+0x00000000000086c8 -0000000000040188 R_X86_64_RELATIVE *ABS*+0x00000000000086f9 -0000000000040198 R_X86_64_RELATIVE *ABS*+0x0000000000007c20 -00000000000401a8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000401c0 R_X86_64_RELATIVE *ABS*+0x0000000000020a40 -00000000000401c8 R_X86_64_RELATIVE *ABS*+0x0000000000011c20 -00000000000401d0 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000401e8 R_X86_64_RELATIVE *ABS*+0x0000000000020810 -00000000000401f0 R_X86_64_RELATIVE *ABS*+0x0000000000011bf0 -00000000000401f8 R_X86_64_RELATIVE *ABS*+0x0000000000008724 -0000000000040210 R_X86_64_RELATIVE *ABS*+0x0000000000008758 -0000000000040220 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -0000000000040230 R_X86_64_RELATIVE *ABS*+0x0000000000008379 -0000000000040240 R_X86_64_RELATIVE *ABS*+0x0000000000007ae8 -0000000000040250 R_X86_64_RELATIVE *ABS*+0x00000000000087d0 -0000000000040260 R_X86_64_RELATIVE *ABS*+0x00000000000084da -0000000000040270 R_X86_64_RELATIVE *ABS*+0x0000000000006b40 -0000000000040280 R_X86_64_RELATIVE *ABS*+0x00000000000083a3 -0000000000040290 R_X86_64_RELATIVE *ABS*+0x0000000000008584 -00000000000402a0 R_X86_64_RELATIVE *ABS*+0x00000000000087fd -00000000000402b8 R_X86_64_RELATIVE *ABS*+0x0000000000011c30 -00000000000402d0 R_X86_64_RELATIVE *ABS*+0x0000000000011780 -00000000000402d8 R_X86_64_RELATIVE *ABS*+0x000000000000884a -00000000000402f0 R_X86_64_RELATIVE *ABS*+0x000000000000887a -0000000000040308 R_X86_64_RELATIVE *ABS*+0x000000000000887a -0000000000040320 R_X86_64_RELATIVE *ABS*+0x00000000000088a6 -0000000000040330 R_X86_64_RELATIVE *ABS*+0x0000000000008902 -0000000000040348 R_X86_64_RELATIVE *ABS*+0x0000000000008902 -0000000000040360 R_X86_64_RELATIVE *ABS*+0x000000000000894d -0000000000040370 R_X86_64_RELATIVE *ABS*+0x0000000000008956 -0000000000040380 R_X86_64_RELATIVE *ABS*+0x0000000000008971 -0000000000040390 R_X86_64_RELATIVE *ABS*+0x000000000000897f -00000000000403a0 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 -00000000000403b8 R_X86_64_RELATIVE *ABS*+0x00000000000089d2 -00000000000403c8 R_X86_64_RELATIVE *ABS*+0x00000000000089a8 -00000000000403e0 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 -00000000000403f8 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 -0000000000040410 R_X86_64_RELATIVE *ABS*+0x0000000000008a01 -0000000000040428 R_X86_64_RELATIVE *ABS*+0x0000000000008a23 -0000000000040440 R_X86_64_RELATIVE *ABS*+0x0000000000008a96 -0000000000040458 R_X86_64_RELATIVE *ABS*+0x0000000000008abb -0000000000040470 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 -0000000000040488 R_X86_64_RELATIVE *ABS*+0x0000000000008af7 -00000000000404a0 R_X86_64_RELATIVE *ABS*+0x0000000000008b2d -00000000000404b8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 -00000000000404d0 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 -00000000000404d8 R_X86_64_RELATIVE *ABS*+0x000000000002dbc0 -00000000000404e0 R_X86_64_RELATIVE *ABS*+0x000000000002d880 -00000000000404e8 R_X86_64_RELATIVE *ABS*+0x000000000002d900 -0000000000040500 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 -0000000000040508 R_X86_64_RELATIVE *ABS*+0x000000000002dbd0 -0000000000040510 R_X86_64_RELATIVE *ABS*+0x000000000002d870 -0000000000040518 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 -0000000000040530 R_X86_64_RELATIVE *ABS*+0x0000000000008c57 -0000000000040548 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040560 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040578 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040590 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000405f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040608 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040620 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040638 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040650 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040668 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040680 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040698 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000406f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040710 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040728 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040740 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040758 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040770 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040788 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407a0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407b8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407d0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000407e8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040800 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040818 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040830 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040848 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040860 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040878 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040890 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408a8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408c0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408d8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000408f0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040908 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040920 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040938 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040950 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040968 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040980 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040998 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409b0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409c8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409e0 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -00000000000409f8 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040a10 R_X86_64_RELATIVE *ABS*+0x0000000000008cb7 -0000000000040a28 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 -0000000000040a40 R_X86_64_RELATIVE *ABS*+0x0000000000008d92 -0000000000040a58 R_X86_64_RELATIVE *ABS*+0x00000000000315f0 -0000000000040a70 R_X86_64_RELATIVE *ABS*+0x0000000000031590 -0000000000040a78 R_X86_64_RELATIVE *ABS*+0x000000000000904b -0000000000040a90 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040aa8 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040ac0 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040ad8 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040af0 R_X86_64_RELATIVE *ABS*+0x00000000000090db -0000000000040b08 R_X86_64_RELATIVE *ABS*+0x0000000000009260 -0000000000040b20 R_X86_64_RELATIVE *ABS*+0x0000000000009260 -0000000000040b38 R_X86_64_RELATIVE *ABS*+0x0000000000009260 -0000000000040b50 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040b68 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040b80 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040b98 R_X86_64_RELATIVE *ABS*+0x00000000000331f0 -0000000000040bb0 R_X86_64_RELATIVE *ABS*+0x000000000003d290 -0000000000040bb8 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040bd0 R_X86_64_RELATIVE *ABS*+0x000000000000960e -0000000000040be8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c00 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c18 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c30 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c48 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c60 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c78 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040c90 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040ca8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040cc0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040cd8 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040cf0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d08 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d20 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d38 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d50 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d68 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d80 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040d98 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040db0 R_X86_64_RELATIVE *ABS*+0x00000000000096b3 -0000000000040dc8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040de0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040df8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e10 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e28 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e40 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e58 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e70 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040e88 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040ea0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040eb8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040ed0 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040ee8 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040f00 R_X86_64_RELATIVE *ABS*+0x0000000000009712 -0000000000040f18 R_X86_64_RELATIVE *ABS*+0x000000000000979a -0000000000040f28 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 -0000000000040f40 R_X86_64_RELATIVE *ABS*+0x00000000000097d8 -0000000000040f58 R_X86_64_RELATIVE *ABS*+0x0000000000009828 -0000000000040f70 R_X86_64_RELATIVE *ABS*+0x0000000000009939 -0000000000040f80 R_X86_64_RELATIVE *ABS*+0x0000000000009975 -0000000000040f98 R_X86_64_RELATIVE *ABS*+0x0000000000009990 -0000000000040fa8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 -0000000000040fb8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000040fd0 R_X86_64_RELATIVE *ABS*+0x0000000000039180 -0000000000040fd8 R_X86_64_RELATIVE *ABS*+0x00000000000099e2 -0000000000040fe8 R_X86_64_RELATIVE *ABS*+0x00000000000099e3 -0000000000040ff8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 -0000000000041008 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 -0000000000041018 R_X86_64_RELATIVE *ABS*+0x00000000000099e6 -0000000000041028 R_X86_64_RELATIVE *ABS*+0x0000000000007c80 -0000000000041038 R_X86_64_RELATIVE *ABS*+0x00000000000099e7 -0000000000041048 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 -0000000000041058 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d -0000000000041068 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f -0000000000041078 R_X86_64_RELATIVE *ABS*+0x0000000000009a3b -0000000000041088 R_X86_64_RELATIVE *ABS*+0x0000000000009a04 -0000000000041098 R_X86_64_RELATIVE *ABS*+0x0000000000009a1d -00000000000410a8 R_X86_64_RELATIVE *ABS*+0x0000000000009a2f -00000000000410b8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -00000000000410c8 R_X86_64_RELATIVE *ABS*+0x0000000000009938 -00000000000410d8 R_X86_64_RELATIVE *ABS*+0x0000000000009a3f -00000000000410e8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041100 R_X86_64_RELATIVE *ABS*+0x0000000000039880 -0000000000041108 R_X86_64_RELATIVE *ABS*+0x000000000003a060 -0000000000041110 R_X86_64_RELATIVE *ABS*+0x000000000003a140 -0000000000041118 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 -0000000000041130 R_X86_64_RELATIVE *ABS*+0x0000000000007ce0 -0000000000041148 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041160 R_X86_64_RELATIVE *ABS*+0x000000000003deb0 -0000000000041168 R_X86_64_RELATIVE *ABS*+0x0000000000009a56 -0000000000041180 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041198 R_X86_64_RELATIVE *ABS*+0x000000000003a180 -00000000000411a0 R_X86_64_RELATIVE *ABS*+0x000000000003a190 -00000000000411a8 R_X86_64_RELATIVE *ABS*+0x000000000003a270 -00000000000411b0 R_X86_64_RELATIVE *ABS*+0x0000000000007c60 -00000000000411c8 R_X86_64_RELATIVE *ABS*+0x0000000000009b3b -00000000000411d8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d -00000000000411e8 R_X86_64_RELATIVE *ABS*+0x0000000000006c30 -00000000000411f8 R_X86_64_RELATIVE *ABS*+0x0000000000009b4d -0000000000041208 R_X86_64_RELATIVE *ABS*+0x0000000000009b6f -0000000000041218 R_X86_64_RELATIVE *ABS*+0x0000000000009b85 -0000000000041228 R_X86_64_RELATIVE *ABS*+0x0000000000009b92 -0000000000041238 R_X86_64_RELATIVE *ABS*+0x0000000000009ba7 -0000000000041248 R_X86_64_RELATIVE *ABS*+0x0000000000009a52 -0000000000041258 R_X86_64_RELATIVE *ABS*+0x0000000000009bfc -0000000000041270 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -0000000000041288 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412a0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412b8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -00000000000412e8 R_X86_64_RELATIVE *ABS*+0x0000000000009d1f -0000000000041300 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041318 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041330 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041348 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041360 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041378 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -0000000000041390 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -00000000000413a8 R_X86_64_RELATIVE *ABS*+0x0000000000009d3e -00000000000413c0 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 -00000000000413d0 R_X86_64_RELATIVE *ABS*+0x0000000000009d8e -00000000000413e0 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -00000000000413f0 R_X86_64_RELATIVE *ABS*+0x0000000000009da4 -0000000000041400 R_X86_64_RELATIVE *ABS*+0x0000000000007f18 -0000000000041410 R_X86_64_RELATIVE *ABS*+0x0000000000006b50 -0000000000041420 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -0000000000041430 R_X86_64_RELATIVE *ABS*+0x0000000000009d83 -0000000000041440 R_X86_64_RELATIVE *ABS*+0x0000000000009db2 -0000000000041450 R_X86_64_RELATIVE *ABS*+0x0000000000008120 -0000000000041460 R_X86_64_RELATIVE *ABS*+0x0000000000009dd8 -0000000000041470 R_X86_64_RELATIVE *ABS*+0x0000000000009a3e -0000000000041480 R_X86_64_RELATIVE *ABS*+0x0000000000009dde -0000000000041498 R_X86_64_RELATIVE *ABS*+0x0000000000009dde -00000000000414b0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a -00000000000414c8 R_X86_64_RELATIVE *ABS*+0x000000000000a34a -00000000000414e0 R_X86_64_RELATIVE *ABS*+0x000000000000a34a -00000000000414f8 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041510 R_X86_64_RELATIVE *ABS*+0x000000000003e130 -0000000000041518 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041530 R_X86_64_RELATIVE *ABS*+0x000000000003de00 -0000000000041538 R_X86_64_RELATIVE *ABS*+0x0000000000038f70 -0000000000041550 R_X86_64_RELATIVE *ABS*+0x000000000003e030 -0000000000041780 R_X86_64_RELATIVE *ABS*+0x00000000000114b0 -0000000000041788 R_X86_64_RELATIVE *ABS*+0x0000000000011470 -0000000000041790 R_X86_64_RELATIVE *ABS*+0x0000000000011460 -0000000000041798 R_X86_64_RELATIVE *ABS*+0x0000000000011490 -00000000000417a0 R_X86_64_RELATIVE *ABS*+0x0000000000011480 -00000000000417a8 R_X86_64_RELATIVE *ABS*+0x00000000000114a0 -00000000000417b0 R_X86_64_RELATIVE *ABS*+0x000000000001fe20 -00000000000417b8 R_X86_64_RELATIVE *ABS*+0x0000000000022dc0 -00000000000417c0 R_X86_64_RELATIVE *ABS*+0x0000000000011690 -00000000000417f8 R_X86_64_RELATIVE *ABS*+0x0000000000011660 -0000000000041800 R_X86_64_RELATIVE *ABS*+0x0000000000011680 -0000000000041810 R_X86_64_RELATIVE *ABS*+0x0000000000039480 -0000000000041818 R_X86_64_RELATIVE *ABS*+0x000000000001fe40 -0000000000041820 R_X86_64_RELATIVE *ABS*+0x0000000000011400 -0000000000041828 R_X86_64_RELATIVE *ABS*+0x0000000000038ad0 -0000000000041830 R_X86_64_RELATIVE *ABS*+0x0000000000022970 -0000000000041838 R_X86_64_RELATIVE *ABS*+0x000000000003b1c0 -0000000000041840 R_X86_64_RELATIVE *ABS*+0x000000000003d660 -0000000000041848 R_X86_64_RELATIVE *ABS*+0x000000000003b1d0 -0000000000041850 R_X86_64_RELATIVE *ABS*+0x000000000003d700 -0000000000041858 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 -0000000000041860 R_X86_64_RELATIVE *ABS*+0x0000000000020d80 -0000000000041868 R_X86_64_RELATIVE *ABS*+0x000000000003b240 -0000000000041870 R_X86_64_RELATIVE *ABS*+0x000000000003a020 -0000000000041878 R_X86_64_RELATIVE *ABS*+0x000000000003a030 -0000000000041880 R_X86_64_RELATIVE *ABS*+0x000000000003d520 -0000000000041888 R_X86_64_RELATIVE *ABS*+0x000000000003d5c0 -0000000000041890 R_X86_64_RELATIVE *ABS*+0x000000000003da60 -0000000000041898 R_X86_64_RELATIVE *ABS*+0x000000000003d3e0 -00000000000418a0 R_X86_64_RELATIVE *ABS*+0x000000000003d480 -00000000000418a8 R_X86_64_RELATIVE *ABS*+0x000000000003d9d0 -00000000000418b0 R_X86_64_RELATIVE *ABS*+0x0000000000039490 -00000000000418b8 R_X86_64_RELATIVE *ABS*+0x000000000003b290 -00000000000418c0 R_X86_64_RELATIVE *ABS*+0x000000000003a310 -00000000000418c8 R_X86_64_RELATIVE *ABS*+0x0000000000011410 -00000000000418e0 R_X86_64_RELATIVE *ABS*+0x0000000000042f78 -00000000000418e8 R_X86_64_RELATIVE *ABS*+0x0000000000022450 -00000000000418f0 R_X86_64_RELATIVE *ABS*+0x00000000000204c0 -0000000000041910 R_X86_64_RELATIVE *ABS*+0x000000000003b8d0 -0000000000041918 R_X86_64_RELATIVE *ABS*+0x000000000003bcd0 -0000000000041920 R_X86_64_RELATIVE *ABS*+0x0000000000039570 -0000000000041928 R_X86_64_RELATIVE *ABS*+0x000000000003cb90 -0000000000041930 R_X86_64_RELATIVE *ABS*+0x000000000003b850 -0000000000041938 R_X86_64_RELATIVE *ABS*+0x000000000003b950 -0000000000041940 R_X86_64_RELATIVE *ABS*+0x0000000000039520 -0000000000041948 R_X86_64_RELATIVE *ABS*+0x0000000000039630 -0000000000041950 R_X86_64_RELATIVE *ABS*+0x000000000003b1b0 -0000000000041960 R_X86_64_RELATIVE *ABS*+0x0000000000011420 -0000000000041968 R_X86_64_RELATIVE *ABS*+0x0000000000038c90 -0000000000041970 R_X86_64_RELATIVE *ABS*+0x00000000000331c0 -0000000000041978 R_X86_64_RELATIVE *ABS*+0x0000000000033190 -0000000000041980 R_X86_64_RELATIVE *ABS*+0x0000000000038cb0 -0000000000041988 R_X86_64_RELATIVE *ABS*+0x0000000000031480 -0000000000041990 R_X86_64_RELATIVE *ABS*+0x0000000000033150 -0000000000041998 R_X86_64_RELATIVE *ABS*+0x000000000003b1e0 -00000000000419a0 R_X86_64_RELATIVE *ABS*+0x0000000000039ce0 -00000000000419a8 R_X86_64_RELATIVE *ABS*+0x0000000000024ab0 -00000000000419b0 R_X86_64_RELATIVE *ABS*+0x0000000000038f30 -00000000000419c0 R_X86_64_RELATIVE *ABS*+0x000000000001da50 -00000000000419d0 R_X86_64_RELATIVE *ABS*+0x00000000000397f0 -00000000000419f0 R_X86_64_RELATIVE *ABS*+0x0000000000024bf0 -00000000000419f8 R_X86_64_RELATIVE *ABS*+0x000000000003b170 -0000000000041a08 R_X86_64_RELATIVE *ABS*+0x000000000001f5a0 -0000000000041a10 R_X86_64_RELATIVE *ABS*+0x000000000001efb0 -0000000000041a38 R_X86_64_RELATIVE *ABS*+0x0000000000039280 -0000000000041a40 R_X86_64_RELATIVE *ABS*+0x000000000001d870 -0000000000041a50 R_X86_64_RELATIVE *ABS*+0x0000000000038a00 -0000000000041a58 R_X86_64_RELATIVE *ABS*+0x000000000003ba50 -0000000000041a68 R_X86_64_RELATIVE *ABS*+0x000000000003c600 -0000000000041a70 R_X86_64_RELATIVE *ABS*+0x000000000003ca80 -0000000000041a78 R_X86_64_RELATIVE *ABS*+0x000000000001fb70 -0000000000041a80 R_X86_64_RELATIVE *ABS*+0x000000000003b650 -0000000000041a88 R_X86_64_RELATIVE *ABS*+0x000000000003b150 -0000000000041a90 R_X86_64_RELATIVE *ABS*+0x000000000003c610 -0000000000041a98 R_X86_64_RELATIVE *ABS*+0x000000000003c620 -0000000000041aa0 R_X86_64_RELATIVE *ABS*+0x00000000000391d0 -0000000000041aa8 R_X86_64_RELATIVE *ABS*+0x000000000003b270 -0000000000041ab0 R_X86_64_RELATIVE *ABS*+0x000000000003e2c0 -0000000000041ab8 R_X86_64_RELATIVE *ABS*+0x000000000003d000 -0000000000041ac0 R_X86_64_RELATIVE *ABS*+0x0000000000020010 -0000000000041ac8 R_X86_64_RELATIVE *ABS*+0x000000000001d6c0 -0000000000041ad8 R_X86_64_RELATIVE *ABS*+0x0000000000021630 -0000000000041af8 R_X86_64_RELATIVE *ABS*+0x000000000003a2d0 -0000000000041b10 R_X86_64_RELATIVE *ABS*+0x0000000000039310 -0000000000041b18 R_X86_64_RELATIVE *ABS*+0x00000000000392f0 -0000000000041b20 R_X86_64_RELATIVE *ABS*+0x000000000001e000 -0000000000041b28 R_X86_64_RELATIVE *ABS*+0x0000000000022550 -0000000000041b30 R_X86_64_RELATIVE *ABS*+0x00000000000205c0 -0000000000041b38 R_X86_64_RELATIVE *ABS*+0x000000000002dad0 -0000000000041b40 R_X86_64_RELATIVE *ABS*+0x0000000000022590 -0000000000041b48 R_X86_64_RELATIVE *ABS*+0x0000000000039300 -0000000000041b58 R_X86_64_RELATIVE *ABS*+0x0000000000039320 -0000000000041b60 R_X86_64_RELATIVE *ABS*+0x0000000000022cd0 -0000000000041b68 R_X86_64_RELATIVE *ABS*+0x000000000002db20 -0000000000041b70 R_X86_64_RELATIVE *ABS*+0x000000000003dba0 -0000000000041bc0 R_X86_64_RELATIVE *ABS*+0x00000000000212f0 -0000000000041bc8 R_X86_64_RELATIVE *ABS*+0x00000000000224b0 -0000000000041bd0 R_X86_64_RELATIVE *ABS*+0x00000000000389a0 -0000000000041bd8 R_X86_64_RELATIVE *ABS*+0x000000000003a2c0 -0000000000041be0 R_X86_64_RELATIVE *ABS*+0x0000000000024f90 -0000000000041be8 R_X86_64_RELATIVE *ABS*+0x000000000003b200 -0000000000041bf0 R_X86_64_RELATIVE *ABS*+0x0000000000039d30 -0000000000041bf8 R_X86_64_RELATIVE *ABS*+0x0000000000039e80 -0000000000041c50 R_X86_64_RELATIVE *ABS*+0x0000000000038a10 -0000000000041c58 R_X86_64_RELATIVE *ABS*+0x0000000000011430 -0000000000041c68 R_X86_64_RELATIVE *ABS*+0x000000000001db70 -0000000000041c70 R_X86_64_RELATIVE *ABS*+0x000000000003e4a0 -0000000000041c78 R_X86_64_RELATIVE *ABS*+0x0000000000031310 -0000000000041c80 R_X86_64_RELATIVE *ABS*+0x0000000000031300 -0000000000041c88 R_X86_64_RELATIVE *ABS*+0x0000000000031990 -0000000000041c90 R_X86_64_RELATIVE *ABS*+0x0000000000031aa0 -0000000000041c98 R_X86_64_RELATIVE *ABS*+0x0000000000032fc0 -0000000000041ca0 R_X86_64_RELATIVE *ABS*+0x0000000000032fe0 -0000000000041ca8 R_X86_64_RELATIVE *ABS*+0x0000000000032f30 -0000000000041cb0 R_X86_64_RELATIVE *ABS*+0x0000000000031ae0 -0000000000041cb8 R_X86_64_RELATIVE *ABS*+0x000000000002ded0 -0000000000041cc0 R_X86_64_RELATIVE *ABS*+0x000000000002ea40 -0000000000041cc8 R_X86_64_RELATIVE *ABS*+0x0000000000023d10 -0000000000041ce8 R_X86_64_RELATIVE *ABS*+0x0000000000021ab0 -0000000000041cf8 R_X86_64_RELATIVE *ABS*+0x0000000000021a00 -0000000000041d28 R_X86_64_RELATIVE *ABS*+0x0000000000030e90 -0000000000041d30 R_X86_64_RELATIVE *ABS*+0x000000000003b9d0 -0000000000041d38 R_X86_64_RELATIVE *ABS*+0x000000000003d660 -0000000000041d40 R_X86_64_RELATIVE *ABS*+0x000000000003d700 -0000000000041d48 R_X86_64_RELATIVE *ABS*+0x000000000003dcd0 -0000000000041d50 R_X86_64_RELATIVE *ABS*+0x0000000000038af0 -0000000000041d58 R_X86_64_RELATIVE *ABS*+0x000000000003b730 -0000000000041d60 R_X86_64_RELATIVE *ABS*+0x000000000003d350 -0000000000041d68 R_X86_64_RELATIVE *ABS*+0x000000000003d340 -0000000000041d70 R_X86_64_RELATIVE *ABS*+0x000000000003e2a0 -0000000000041d78 R_X86_64_RELATIVE *ABS*+0x000000000003bcb0 -0000000000041d80 R_X86_64_RELATIVE *ABS*+0x0000000000034040 -0000000000041d88 R_X86_64_RELATIVE *ABS*+0x000000000003b2b0 -0000000000041d90 R_X86_64_RELATIVE *ABS*+0x0000000000037c40 -0000000000041d98 R_X86_64_RELATIVE *ABS*+0x0000000000033440 -0000000000041da0 R_X86_64_RELATIVE *ABS*+0x0000000000011440 -0000000000041da8 R_X86_64_RELATIVE *ABS*+0x0000000000021860 -0000000000041db0 R_X86_64_RELATIVE *ABS*+0x00000000000395f0 -0000000000041db8 R_X86_64_RELATIVE *ABS*+0x0000000000039880 -0000000000041dc0 R_X86_64_RELATIVE *ABS*+0x000000000003aaa0 -0000000000041dc8 R_X86_64_RELATIVE *ABS*+0x0000000000039b30 -0000000000041dd0 R_X86_64_RELATIVE *ABS*+0x000000000003a530 -0000000000042e08 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 -0000000000042e10 R_X86_64_RELATIVE *ABS*+0x000000000002dbe0 -0000000000042e28 R_X86_64_RELATIVE *ABS*+0x00000000000214f0 -0000000000042e38 R_X86_64_RELATIVE *ABS*+0x0000000000042e08 -0000000000042e40 R_X86_64_RELATIVE *ABS*+0x0000000000008ab5 -0000000000041758 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5 -0000000000041760 R_X86_64_GLOB_DAT __gmon_start__@Base -0000000000041768 R_X86_64_GLOB_DAT _ITM_deregisterTMCloneTable@Base -0000000000041770 R_X86_64_GLOB_DAT _ITM_registerTMCloneTable@Base -0000000000041778 R_X86_64_GLOB_DAT __cxa_finalize@GLIBC_2.2.5 -00000000000417d8 R_X86_64_GLOB_DAT free@GLIBC_2.2.5 -00000000000417c8 R_X86_64_GLOB_DAT malloc@GLIBC_2.2.5 -00000000000417e0 R_X86_64_GLOB_DAT memcpy@GLIBC_2.14 -00000000000417e8 R_X86_64_GLOB_DAT memset@GLIBC_2.2.5 -00000000000417d0 R_X86_64_GLOB_DAT realloc@GLIBC_2.2.5 -00000000000417f0 R_X86_64_GLOB_DAT roc__mainForHost_1_exposed@Base -0000000000041808 R_X86_64_GLOB_DAT write@GLIBC_2.2.5 -0000000000041c00 R_X86_64_GLOB_DAT bcmp@GLIBC_2.2.5 -0000000000041a48 R_X86_64_GLOB_DAT _Unwind_Backtrace@GCC_3.3 -0000000000041a60 R_X86_64_GLOB_DAT _Unwind_GetIP@GCC_3.0 -0000000000041c48 R_X86_64_GLOB_DAT __cxa_thread_atexit_impl@GLIBC_2.18 -00000000000419e8 R_X86_64_GLOB_DAT __errno_location@GLIBC_2.2.5 -0000000000041c20 R_X86_64_GLOB_DAT __xpg_strerror_r@GLIBC_2.3.4 -0000000000041b80 R_X86_64_GLOB_DAT abort@GLIBC_2.2.5 -0000000000041b08 R_X86_64_GLOB_DAT calloc@GLIBC_2.2.5 -00000000000418d8 R_X86_64_GLOB_DAT close@GLIBC_2.2.5 -0000000000041c60 R_X86_64_GLOB_DAT dl_iterate_phdr@GLIBC_2.2.5 -0000000000041c08 R_X86_64_GLOB_DAT dlsym@GLIBC_2.2.5 -0000000000041c30 R_X86_64_GLOB_DAT exit@GLIBC_2.2.5 -00000000000419d8 R_X86_64_GLOB_DAT getcwd@GLIBC_2.2.5 -0000000000041c28 R_X86_64_GLOB_DAT getenv@GLIBC_2.2.5 -00000000000419b8 R_X86_64_GLOB_DAT memchr@GLIBC_2.2.5 -0000000000041908 R_X86_64_GLOB_DAT memmove@GLIBC_2.2.5 -0000000000041c38 R_X86_64_GLOB_DAT mmap@GLIBC_2.2.5 -0000000000041c40 R_X86_64_GLOB_DAT mprotect@GLIBC_2.2.5 -00000000000418f8 R_X86_64_GLOB_DAT munmap@GLIBC_2.2.5 -0000000000041b88 R_X86_64_GLOB_DAT open@GLIBC_2.2.5 -0000000000041c10 R_X86_64_GLOB_DAT open64@GLIBC_2.2.5 -0000000000041b78 R_X86_64_GLOB_DAT poll@GLIBC_2.2.5 -0000000000041b00 R_X86_64_GLOB_DAT posix_memalign@GLIBC_2.2.5 -0000000000041bb8 R_X86_64_GLOB_DAT pthread_attr_destroy@GLIBC_2.2.5 -0000000000041bb0 R_X86_64_GLOB_DAT pthread_attr_getstack@GLIBC_2.2.5 -0000000000041ba8 R_X86_64_GLOB_DAT pthread_getattr_np@GLIBC_2.2.5 -0000000000041ae0 R_X86_64_GLOB_DAT pthread_getspecific@GLIBC_2.2.5 -0000000000041ae8 R_X86_64_GLOB_DAT pthread_key_create@GLIBC_2.2.5 -0000000000041af0 R_X86_64_GLOB_DAT pthread_key_delete@GLIBC_2.2.5 -0000000000041958 R_X86_64_GLOB_DAT pthread_mutex_destroy@GLIBC_2.2.5 -00000000000419c8 R_X86_64_GLOB_DAT pthread_mutex_lock@GLIBC_2.2.5 -0000000000041a18 R_X86_64_GLOB_DAT pthread_mutex_trylock@GLIBC_2.2.5 -00000000000418d0 R_X86_64_GLOB_DAT pthread_mutex_unlock@GLIBC_2.2.5 -0000000000041b50 R_X86_64_GLOB_DAT pthread_rwlock_rdlock@GLIBC_2.2.5 -0000000000041900 R_X86_64_GLOB_DAT pthread_rwlock_unlock@GLIBC_2.2.5 -0000000000041ba0 R_X86_64_GLOB_DAT pthread_self@GLIBC_2.2.5 -0000000000041ad0 R_X86_64_GLOB_DAT pthread_setspecific@GLIBC_2.2.5 -0000000000041c18 R_X86_64_GLOB_DAT readlink@GLIBC_2.2.5 -0000000000041b98 R_X86_64_GLOB_DAT sigaction@GLIBC_2.2.5 -0000000000041a20 R_X86_64_GLOB_DAT sigaltstack@GLIBC_2.2.5 -0000000000041b90 R_X86_64_GLOB_DAT signal@GLIBC_2.2.5 -00000000000419e0 R_X86_64_GLOB_DAT strlen@GLIBC_2.2.5 -0000000000041a30 R_X86_64_GLOB_DAT syscall@GLIBC_2.2.5 -0000000000041a28 R_X86_64_GLOB_DAT sysconf@GLIBC_2.2.5 -0000000000041a00 R_X86_64_GLOB_DAT writev@GLIBC_2.2.5 -0000000000041ce0 R_X86_64_GLOB_DAT _Unwind_DeleteException@GCC_3.0 -0000000000041cd0 R_X86_64_GLOB_DAT _Unwind_GetDataRelBase@GCC_3.0 -0000000000041d08 R_X86_64_GLOB_DAT _Unwind_GetIPInfo@GCC_4.2.0 -0000000000041d00 R_X86_64_GLOB_DAT _Unwind_GetLanguageSpecificData@GCC_3.0 -0000000000041d10 R_X86_64_GLOB_DAT _Unwind_GetRegionStart@GCC_3.0 -0000000000041cd8 R_X86_64_GLOB_DAT _Unwind_GetTextRelBase@GCC_3.0 -0000000000041cf0 R_X86_64_GLOB_DAT _Unwind_RaiseException@GCC_3.0 -0000000000041d18 R_X86_64_GLOB_DAT _Unwind_SetGR@GCC_3.0 -0000000000041d20 R_X86_64_GLOB_DAT _Unwind_SetIP@GCC_3.0 -0000000000041df0 R_X86_64_JUMP_SLOT __cxa_finalize@GLIBC_2.2.5 -0000000000041df8 R_X86_64_JUMP_SLOT _Unwind_Resume@GCC_3.0 -0000000000041e00 R_X86_64_JUMP_SLOT __fxstat64@GLIBC_2.2.5 - - From 754130d25fb07c889ebe57714cd573c4136ac7f3 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Fri, 24 Sep 2021 22:03:59 -0700 Subject: [PATCH 31/96] Remove old unused linker tests --- linker/tests/fib/.gitignore | 11 --- linker/tests/fib/Main.roc | 15 ---- linker/tests/fib/README.md | 48 ------------- linker/tests/fib/platform/Package-Config.roc | 10 --- linker/tests/fib/platform/app.zig | 1 - linker/tests/fib/platform/build.zig | 33 --------- linker/tests/fib/platform/host.zig | 73 -------------------- 7 files changed, 191 deletions(-) delete mode 100644 linker/tests/fib/.gitignore delete mode 100644 linker/tests/fib/Main.roc delete mode 100644 linker/tests/fib/README.md delete mode 100644 linker/tests/fib/platform/Package-Config.roc delete mode 100644 linker/tests/fib/platform/app.zig delete mode 100644 linker/tests/fib/platform/build.zig delete mode 100644 linker/tests/fib/platform/host.zig diff --git a/linker/tests/fib/.gitignore b/linker/tests/fib/.gitignore deleted file mode 100644 index a3c0d77f6d..0000000000 --- a/linker/tests/fib/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -fib - -zig-cache -zig-out - -*.o - -dynhost -preprocessedhost -metadata -libapp.so \ No newline at end of file diff --git a/linker/tests/fib/Main.roc b/linker/tests/fib/Main.roc deleted file mode 100644 index 646fdbea75..0000000000 --- a/linker/tests/fib/Main.roc +++ /dev/null @@ -1,15 +0,0 @@ -app "fib" - packages { base: "platform" } - imports [] - provides [ main ] to base - -main : U64 -> U64 -main = \index -> - fibHelp index 0 1 - -fibHelp : U64, U64, U64 -> U64 -fibHelp = \index, parent, grandparent -> - if index == 0 then - parent - else - fibHelp (index - 1) grandparent (parent + grandparent) \ No newline at end of file diff --git a/linker/tests/fib/README.md b/linker/tests/fib/README.md deleted file mode 100644 index 0f1af10077..0000000000 --- a/linker/tests/fib/README.md +++ /dev/null @@ -1,48 +0,0 @@ -# Hello, World! - -To run, `cd` into this directory and run: - -```bash -$ cargo run Hello.roc -``` - -To run in release mode instead, do: - -```bash -$ cargo run --release Hello.roc -``` - -## Troubleshooting - -If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`. - -## Design Notes - -This demonstrates the basic design of hosts: Roc code gets compiled into a pure -function (in this case, a thunk that always returns `"Hello, World!"`) and -then the host calls that function. Fundamentally, that's the whole idea! The host -might not even have a `main` - it could be a library, a plugin, anything. -Everything else is built on this basic "hosts calling linked pure functions" design. - -For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported -I/O operation.) - -In this trivial example, it's very easy to line up the API between the host and -the Roc program. In a more involved host, this would be much trickier - especially -if the API were changing frequently during development. - -The idea there is to have a first-class concept of "glue code" which host authors -can write (it would be plain Roc code, but with some extra keywords that aren't -available in normal modules - kinda like `port module` in Elm), and which -describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary. -Roc application authors only care about the Roc-host/Roc-app portion, and the -host author only cares about the Roc-host/C boundary when implementing the host. - -Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) -generates correct Rust FFI bindings from C headers. diff --git a/linker/tests/fib/platform/Package-Config.roc b/linker/tests/fib/platform/Package-Config.roc deleted file mode 100644 index d93cd7c258..0000000000 --- a/linker/tests/fib/platform/Package-Config.roc +++ /dev/null @@ -1,10 +0,0 @@ -platform tests/fib - requires {}{ main : U64 -> U64 } - exposes [] - packages {} - imports [] - provides [ mainForHost ] - effects fx.Effect {} - -mainForHost : U64 -> U64 -mainForHost = \arg -> main arg # workaround for https://github.com/rtfeldman/roc/issues/1622 \ No newline at end of file diff --git a/linker/tests/fib/platform/app.zig b/linker/tests/fib/platform/app.zig deleted file mode 100644 index 105908633f..0000000000 --- a/linker/tests/fib/platform/app.zig +++ /dev/null @@ -1 +0,0 @@ -export fn roc__mainForHost_1_exposed(_i: i64, _result: *u64) void {} diff --git a/linker/tests/fib/platform/build.zig b/linker/tests/fib/platform/build.zig deleted file mode 100644 index deb36d6c78..0000000000 --- a/linker/tests/fib/platform/build.zig +++ /dev/null @@ -1,33 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - // Standard target options allows the person running `zig build` to choose - // what target to build for. Here we do not override the defaults, which - // means any target is allowed, and the default is native. Other options - // for restricting supported target set are available. - const target = b.standardTargetOptions(.{}); - - // Standard release options allow the person running `zig build` to select - // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. - const mode = b.standardReleaseOptions(); - - const app = b.addSharedLibrary("app", "app.zig", .unversioned); - app.setTarget(target); - app.setBuildMode(mode); - app.install(); - - const exe = b.addExecutable("dynhost", "host.zig"); - exe.pie = true; - exe.strip = true; - exe.setTarget(target); - exe.setBuildMode(mode); - exe.linkLibrary(app); - exe.linkLibC(); - exe.install(); - - const run_cmd = exe.run(); - run_cmd.step.dependOn(b.getInstallStep()); - - const run_step = b.step("run", "Run the app"); - run_step.dependOn(&run_cmd.step); -} diff --git a/linker/tests/fib/platform/host.zig b/linker/tests/fib/platform/host.zig deleted file mode 100644 index c439c889c7..0000000000 --- a/linker/tests/fib/platform/host.zig +++ /dev/null @@ -1,73 +0,0 @@ -const std = @import("std"); -const testing = std.testing; -const expectEqual = testing.expectEqual; -const expect = testing.expect; - -comptime { - // This is a workaround for https://github.com/ziglang/zig/issues/8218 - // which is only necessary on macOS. - // - // Once that issue is fixed, we can undo the changes in - // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing - // -fcompiler-rt in link.rs instead of doing this. Note that this - // workaround is present in many host.zig files, so make sure to undo - // it everywhere! - if (std.builtin.os.tag == .macos) { - _ = @import("compiler_rt"); - } -} - -extern fn malloc(size: usize) callconv(.C) ?*c_void; -extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; -extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; - -export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { - return malloc(size); -} - -export fn roc_realloc(c_ptr: *c_void, old_size: usize, new_size: usize, alignment: u32) callconv(.C) ?*c_void { - return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); -} - -export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { - free(@alignCast(16, @ptrCast([*]u8, c_ptr))); -} - -export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void { - const stderr = std.io.getStdErr().writer(); - const msg = @ptrCast([*:0]const u8, c_ptr); - stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; - std.process.exit(0); -} - -const mem = std.mem; -const Allocator = mem.Allocator; - -extern fn roc__mainForHost_1_exposed(i64, *i64) void; - -const Unit = extern struct {}; - -pub export fn main() u8 { - const stdout = std.io.getStdOut().writer(); - const fib_number_to_find: u64 = 10; // find the nth Fibonacci number - const iterations: usize = 50; // number of times to repeatedly find that Fibonacci number - - // make space for the result - var callresult = 0; - var remaining_iterations = iterations; - - while (remaining_iterations > 0) { - // actually call roc to populate the callresult - roc__mainForHost_1_exposed(fib_number_to_find, &callresult); - - remaining_iterations -= 1; - } - - // stdout the final result - stdout.print( - "After calling the Roc app {d} times, the Fibonacci number at index {d} is {d}\n", - .{ iterations, fib_number_to_find, callresult }, - ) catch unreachable; - - return 0; -} From a6bc37eccaff7924a6b761d51d0b3e7f7ce4db00 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 25 Sep 2021 15:16:17 +0200 Subject: [PATCH 32/96] handle small strings --- examples/hello-web/platform/host.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hello-web/platform/host.zig b/examples/hello-web/platform/host.zig index 2623097836..bfd519e9de 100644 --- a/examples/hello-web/platform/host.zig +++ b/examples/hello-web/platform/host.zig @@ -60,7 +60,7 @@ pub fn main() u8 { roc__mainForHost_1_exposed(&callresult); // display the result using JavaScript - js_display_roc_string(callresult.str_bytes, callresult.str_len); + js_display_roc_string(callresult.asU8ptr(), callresult.len()); callresult.deinit(); From 69391b26d7d76e908b1d95814be74cc12fe83ba8 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 17:14:32 +0200 Subject: [PATCH 33/96] removed lang and markup folder from editors, changed imports to use new crates --- Cargo.lock | 5 + ast/Cargo.toml | 5 +- ast/src/ast_error.rs | 10 + ast/src/constrain.rs | 362 +++++ ast/src/lang/core/ast.rs | 11 +- ast/src/lang/core/def/def2.rs | 27 +- ast/src/lang/core/def/mod.rs | 2 +- ast/src/lang/core/mod.rs | 2 +- ast/src/lang/core/str.rs | 61 +- ast/src/lib.rs | 6 +- ast/src/mod.rs | 2 +- ast/src/parse/mod.rs | 3 + ast/src/parse/parse_ast.rs | 10 +- ast/src/parse/parse_header.rs | 2 + ast/src/solve_type.rs | 12 +- editor/Cargo.toml | 2 + editor/src/editor/ed_error.rs | 42 +- editor/src/editor/grid_node_map.rs | 11 +- editor/src/editor/main.rs | 4 +- editor/src/editor/markup/attribute.rs | 123 -- editor/src/editor/markup/common_nodes.rs | 107 -- editor/src/editor/markup/mod.rs | 3 - editor/src/editor/markup/nodes.rs | 876 ----------- editor/src/editor/mod.rs | 3 - editor/src/editor/mvc/break_line.rs | 7 +- editor/src/editor/mvc/ed_model.rs | 33 +- editor/src/editor/mvc/ed_update.rs | 36 +- editor/src/editor/mvc/ed_view.rs | 2 +- editor/src/editor/mvc/int_update.rs | 20 +- editor/src/editor/mvc/let_update.rs | 17 +- editor/src/editor/mvc/list_update.rs | 17 +- editor/src/editor/mvc/lookup_update.rs | 7 +- editor/src/editor/mvc/record_update.rs | 25 +- editor/src/editor/mvc/string_update.rs | 17 +- editor/src/editor/mvc/tld_value_update.rs | 16 +- editor/src/editor/render_ast.rs | 6 +- editor/src/editor/render_debug.rs | 4 +- editor/src/editor/slow_pool.rs | 77 - editor/src/editor/syntax_highlight.rs | 47 - editor/src/editor/theme.rs | 2 +- editor/src/lang/ast.rs | 742 --------- editor/src/lang/constrain.rs | 1746 -------------------- editor/src/lang/def.rs | 1444 ----------------- editor/src/lang/expr.rs | 1555 ------------------ editor/src/lang/mod.rs | 12 - editor/src/lang/module.rs | 318 ---- editor/src/lang/parse.rs | 98 -- editor/src/lang/pattern.rs | 619 -------- editor/src/lang/pool.rs | 635 -------- editor/src/lang/roc_file.rs | 133 -- editor/src/lang/scope.rs | 325 ---- editor/src/lang/solve.rs | 1752 --------------------- editor/src/lang/types.rs | 863 ---------- editor/src/lib.rs | 1 - editor/tests/solve_expr2.rs | 372 ----- 55 files changed, 653 insertions(+), 11986 deletions(-) create mode 100644 ast/src/parse/mod.rs delete mode 100644 editor/src/editor/markup/attribute.rs delete mode 100644 editor/src/editor/markup/common_nodes.rs delete mode 100644 editor/src/editor/markup/mod.rs delete mode 100644 editor/src/editor/markup/nodes.rs delete mode 100644 editor/src/editor/slow_pool.rs delete mode 100644 editor/src/editor/syntax_highlight.rs delete mode 100644 editor/src/lang/ast.rs delete mode 100644 editor/src/lang/constrain.rs delete mode 100644 editor/src/lang/def.rs delete mode 100644 editor/src/lang/expr.rs delete mode 100644 editor/src/lang/mod.rs delete mode 100644 editor/src/lang/module.rs delete mode 100644 editor/src/lang/parse.rs delete mode 100644 editor/src/lang/pattern.rs delete mode 100644 editor/src/lang/pool.rs delete mode 100644 editor/src/lang/roc_file.rs delete mode 100644 editor/src/lang/scope.rs delete mode 100644 editor/src/lang/solve.rs delete mode 100644 editor/src/lang/types.rs delete mode 100644 editor/tests/solve_expr2.rs diff --git a/Cargo.lock b/Cargo.lock index 3fdcc1094b..d54543d19f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3393,8 +3393,10 @@ version = "0.1.0" dependencies = [ "arraystring", "bumpalo", + "indoc 1.0.3", "libc", "page_size", + "pretty_assertions 0.6.1", "roc_can", "roc_collections", "roc_module", @@ -3402,6 +3404,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_types", + "roc_unify", "snafu", "ven_graph", ] @@ -3619,8 +3622,10 @@ dependencies = [ "quickcheck 1.0.3", "quickcheck_macros 1.0.0", "rand 0.8.4", + "roc_ast", "roc_builtins", "roc_can", + "roc_code_markup", "roc_collections", "roc_fmt", "roc_load", diff --git a/ast/Cargo.toml b/ast/Cargo.toml index 76d1658885..39bf52a471 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -14,11 +14,14 @@ roc_module = { path = "../compiler/module" } roc_parse = { path = "../compiler/parse" } roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } +roc_unify = { path = "../compiler/unify"} arraystring = "0.3.0" bumpalo = { version = "3.6.1", features = ["collections"] } libc = "0.2" page_size = "0.4" snafu = { version = "0.6", features = ["backtraces"] } ven_graph = { path = "../vendor/pathfinding" } +indoc = "1.0" -[dev-dependencies] \ No newline at end of file +[dev-dependencies] +pretty_assertions = "0.6" diff --git a/ast/src/ast_error.rs b/ast/src/ast_error.rs index b18970e852..cf42c7762d 100644 --- a/ast/src/ast_error.rs +++ b/ast/src/ast_error.rs @@ -15,6 +15,16 @@ pub enum ASTError { ast_node_id: ASTNodeId, backtrace: Backtrace, }, + #[snafu(display( + "UnexpectedASTNode: required a {} at this position, node was a {}.", + required_node_type, + encountered_node_type + ))] + UnexpectedASTNode { + required_node_type: String, + encountered_node_type: String, + backtrace: Backtrace, + }, #[snafu(display( "UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.", required_pattern2, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 07c7ba8480..48a1d0cb8c 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -1738,3 +1738,365 @@ fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { pool.add(alias_content), ) } + +pub mod test_constrain { + use bumpalo::Bump; + use roc_can::expected::Expected; + use roc_collections::all::MutMap; + use roc_module::{ident::Lowercase, symbol::{IdentIds, Interns, ModuleIds, Symbol}}; + use roc_region::all::Region; + use roc_types::{pretty_print::content_to_string, solved_types::Solved, subs::{Subs, VarStore, Variable}}; + + use crate::{constrain::constrain_expr, lang::{core::{expr::expr_to_expr2::str_to_expr2, types::Type2}, env::Env, scope::Scope}, pool::pool::Pool, solve_type}; + use super::Constraint; + use indoc::indoc; + + + fn run_solve<'a>( + arena: &'a Bump, + mempool: &mut Pool, + aliases: MutMap, + rigid_variables: MutMap, + constraint: Constraint, + var_store: VarStore, + ) -> (Solved, solve_type::Env, Vec) { + let env = solve_type::Env { + vars_by_symbol: MutMap::default(), + aliases, + }; + + let mut subs = Subs::new(var_store); + + for (var, name) in rigid_variables { + subs.rigid_var(var, name); + } + + // Now that the module is parsed, canonicalized, and constrained, + // we need to type check it. + let mut problems = Vec::new(); + + // Run the solver to populate Subs. + let (solved_subs, solved_env) = + solve_type::run(arena, mempool, &env, &mut problems, subs, &constraint); + + (solved_subs, solved_env, problems) + } + + fn infer_eq(actual: &str, expected_str: &str) { + let mut env_pool = Pool::with_capacity(1024); + let env_arena = Bump::new(); + let code_arena = Bump::new(); + + let mut var_store = VarStore::default(); + let var = var_store.fresh(); + let dep_idents = IdentIds::exposed_builtins(8); + let exposed_ident_ids = IdentIds::default(); + let mut module_ids = ModuleIds::default(); + let mod_id = module_ids.get_or_insert(&"ModId123".into()); + + let mut env = Env::new( + mod_id, + &env_arena, + &mut env_pool, + &mut var_store, + dep_idents, + &module_ids, + exposed_ident_ids, + ); + + let mut scope = Scope::new(env.home, env.pool, env.var_store); + + let region = Region::zero(); + + let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); + + match expr2_result { + Ok((expr, _)) => { + let constraint = constrain_expr( + &code_arena, + &mut env, + &expr, + Expected::NoExpectation(Type2::Variable(var)), + Region::zero(), + ); + + let Env { + pool, + var_store: ref_var_store, + mut dep_idents, + .. + } = env; + + // extract the var_store out of the env again + let mut var_store = VarStore::default(); + std::mem::swap(ref_var_store, &mut var_store); + + let (mut solved, _, _) = run_solve( + &code_arena, + pool, + Default::default(), + Default::default(), + constraint, + var_store, + ); + + let subs = solved.inner_mut(); + + let content = subs.get_content_without_compacting(var); + + // Connect the ModuleId to it's IdentIds + dep_idents.insert(mod_id, env.ident_ids); + + let interns = Interns { + module_ids: env.module_ids.clone(), + all_ident_ids: dep_idents, + }; + + let actual_str = content_to_string(content, subs, mod_id, &interns); + + assert_eq!(actual_str, expected_str); + } + Err(e) => panic!("syntax error {:?}", e), + } + } + + #[test] + fn constrain_str() { + infer_eq( + indoc!( + r#" + "type inference!" + "# + ), + "Str", + ) + } + + // This will be more useful once we actually map + // strings less than 15 chars to SmallStr + #[test] + fn constrain_small_str() { + infer_eq( + indoc!( + r#" + "a" + "# + ), + "Str", + ) + } + + #[test] + fn constrain_empty_record() { + infer_eq( + indoc!( + r#" + {} + "# + ), + "{}", + ) + } + + #[test] + fn constrain_small_int() { + infer_eq( + indoc!( + r#" + 12 + "# + ), + "Num *", + ) + } + + #[test] + fn constrain_float() { + infer_eq( + indoc!( + r#" + 3.14 + "# + ), + "Float *", + ) + } + + #[test] + fn constrain_record() { + infer_eq( + indoc!( + r#" + { x : 1, y : "hi" } + "# + ), + "{ x : Num *, y : Str }", + ) + } + + #[test] + fn constrain_empty_list() { + infer_eq( + indoc!( + r#" + [] + "# + ), + "List *", + ) + } + + #[test] + fn constrain_list() { + infer_eq( + indoc!( + r#" + [ 1, 2 ] + "# + ), + "List (Num *)", + ) + } + + #[test] + fn constrain_list_of_records() { + infer_eq( + indoc!( + r#" + [ { x: 1 }, { x: 3 } ] + "# + ), + "List { x : Num * }", + ) + } + + #[test] + fn constrain_global_tag() { + infer_eq( + indoc!( + r#" + Foo + "# + ), + "[ Foo ]*", + ) + } + + #[test] + fn constrain_private_tag() { + infer_eq( + indoc!( + r#" + @Foo + "# + ), + "[ @Foo ]*", + ) + } + + #[test] + fn constrain_call_and_accessor() { + infer_eq( + indoc!( + r#" + .foo { foo: "bar" } + "# + ), + "Str", + ) + } + + #[test] + fn constrain_access() { + infer_eq( + indoc!( + r#" + { foo: "bar" }.foo + "# + ), + "Str", + ) + } + + #[test] + fn constrain_if() { + infer_eq( + indoc!( + r#" + if True then Green else Red + "# + ), + "[ Green, Red ]*", + ) + } + + #[test] + fn constrain_when() { + infer_eq( + indoc!( + r#" + when if True then Green else Red is + Green -> Blue + Red -> Purple + "# + ), + "[ Blue, Purple ]*", + ) + } + + #[test] + fn constrain_let_value() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + person + "# + ), + "{ name : Str }", + ) + } + + #[test] + fn constrain_update() { + infer_eq( + indoc!( + r#" + person = { name: "roc" } + + { person & name: "bird" } + "# + ), + "{ name : Str }", + ) + } + + #[ignore = "TODO: implement builtins in the editor"] + #[test] + fn constrain_run_low_level() { + infer_eq( + indoc!( + r#" + List.map [ { name: "roc" }, { name: "bird" } ] .name + "# + ), + "List Str", + ) + } + + #[test] + fn constrain_closure() { + infer_eq( + indoc!( + r#" + x = 1 + + \{} -> x + "# + ), + "{}* -> Num *", + ) + } + +} diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs index 2cf25730b9..12241c567b 100644 --- a/ast/src/lang/core/ast.rs +++ b/ast/src/lang/core/ast.rs @@ -1,6 +1,6 @@ -use crate::ast_error::{ASTResult, ASTNodeIdWithoutExprId}; +use crate::{ast_error::{ASTResult, ASTNodeIdWithoutExprId}, pool::pool::Pool}; -use super::{def::def2::DefId, expr::expr2::ExprId, header::AppHeader}; +use super::{def::def2::{DefId, def2_to_string}, expr::{expr2::ExprId, expr2_to_string::expr2_to_string}, header::AppHeader}; #[derive(Debug)] pub struct AST { @@ -28,4 +28,11 @@ impl ASTNodeId { _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, } } +} + +pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { + match node_id { + ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), + ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), + } } \ No newline at end of file diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs index 578a831d62..935bfe6609 100644 --- a/ast/src/lang/core/def/def2.rs +++ b/ast/src/lang/core/def/def2.rs @@ -1,4 +1,4 @@ -use crate::{lang::core::{expr::expr2::Expr2, pattern::Pattern2}, pool::pool::NodeId}; +use crate::{lang::core::{expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, pattern::Pattern2}, pool::pool::{NodeId, Pool}}; // A top level definition, not inside a function. For example: `main = "Hello, world!"` @@ -12,4 +12,27 @@ pub enum Def2 { Blank, } -pub type DefId = NodeId; \ No newline at end of file +pub type DefId = NodeId; + +pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { + let mut full_string = String::new(); + let def2 = pool.get(node_id); + + match def2 { + Def2::ValueDef { + identifier_id, + expr_id, + } => { + full_string.push_str(&format!( + "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", + pool.get(*identifier_id), + expr2_to_string(*expr_id, pool) + )); + } + Def2::Blank => { + full_string.push_str("Def2::Blank"); + } + } + + full_string +} \ No newline at end of file diff --git a/ast/src/lang/core/def/mod.rs b/ast/src/lang/core/def/mod.rs index 0f62252832..d790ec3ea5 100644 --- a/ast/src/lang/core/def/mod.rs +++ b/ast/src/lang/core/def/mod.rs @@ -1,3 +1,3 @@ -mod def_to_def2; +pub mod def_to_def2; pub mod def; pub mod def2; \ No newline at end of file diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs index 2ee105850f..41e852d20f 100644 --- a/ast/src/lang/core/mod.rs +++ b/ast/src/lang/core/mod.rs @@ -6,5 +6,5 @@ pub mod val_def; mod fun_def; pub mod pattern; pub mod types; -mod str; +pub mod str; mod declaration; \ No newline at end of file diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index bc203c0753..5e94ad76e3 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -1,9 +1,9 @@ use roc_module::{operator::CalledVia, symbol::Symbol}; use roc_parse::ast::StrLiteral; -use crate::{lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{ast_error::{ASTResult, UnexpectedASTNode}, lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}}; -use super::expr::{expr2::Expr2, output::Output}; +use super::expr::{expr2::{Expr2, ExprId}, output::Output}; pub (crate) fn flatten_str_literal<'a>( env: &mut Env<'a>, @@ -161,4 +161,61 @@ fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Exp } expr +} + +pub fn update_str_expr( + node_id: ExprId, + new_char: char, + insert_index: usize, + pool: &mut Pool, +) -> ASTResult<()> { + let str_expr = pool.get_mut(node_id); + + enum Either { + MyString(String), + MyPoolStr(PoolStr), + Done, + } + + let insert_either = match str_expr { + Expr2::SmallStr(arr_string) => { + let insert_res = arr_string.try_insert(insert_index as u8, new_char); + + match insert_res { + Ok(_) => Either::Done, + _ => { + let mut new_string = arr_string.as_str().to_string(); + new_string.insert(insert_index, new_char); + + Either::MyString(new_string) + } + } + } + Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), + other => UnexpectedASTNode { + required_node_type: "SmallStr or Str", + encountered_node_type: format!("{:?}", other), + } + .fail()?, + }; + + match insert_either { + Either::MyString(new_string) => { + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::MyPoolStr(old_pool_str) => { + let mut new_string = old_pool_str.as_str(pool).to_owned(); + + new_string.insert(insert_index, new_char); + + let new_pool_str = PoolStr::new(&new_string, pool); + + pool.set(node_id, Expr2::Str(new_pool_str)) + } + Either::Done => (), + } + + Ok(()) } \ No newline at end of file diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 02cf199e70..55191171a8 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -1,5 +1,7 @@ pub mod lang; pub mod pool; -mod constrain; +pub mod parse; +pub mod constrain; mod canonicalize; -pub mod ast_error; \ No newline at end of file +pub mod ast_error; +pub mod solve_type; \ No newline at end of file diff --git a/ast/src/mod.rs b/ast/src/mod.rs index 273c344ee5..f9d51e4b1f 100644 --- a/ast/src/mod.rs +++ b/ast/src/mod.rs @@ -7,7 +7,7 @@ mod pattern; pub mod pool; pub mod roc_file; mod scope; -mod solve; +pub mod solve; mod types; mod rigids; mod canonicalize; diff --git a/ast/src/parse/mod.rs b/ast/src/parse/mod.rs new file mode 100644 index 0000000000..095e190b43 --- /dev/null +++ b/ast/src/parse/mod.rs @@ -0,0 +1,3 @@ +pub mod parse_ast; +pub mod parse_expr; +pub mod parse_header; \ No newline at end of file diff --git a/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs index 2f99b8a071..55ea683966 100644 --- a/ast/src/parse/parse_ast.rs +++ b/ast/src/parse/parse_ast.rs @@ -1,3 +1,11 @@ +use bumpalo::Bump; +use roc_parse::parser::SyntaxError; +use roc_region::all::Region; + +use crate::lang::{core::{ast::AST, def::{def2::DefId, def_to_def2::str_to_def2}, expr::expr2::Expr2}, env::Env, scope::Scope}; + +use super::parse_header; + pub fn parse_from_string<'a>( @@ -28,7 +36,7 @@ pub fn parse_from_string<'a>( let ast_node_id = env.pool.add(Expr2::Blank); Ok(AST { - header: AppHeader::parse_from_string(header_str, ast_node_id), + header: parse_header::parse_from_string(header_str, ast_node_id), def_ids, }) } \ No newline at end of file diff --git a/ast/src/parse/parse_header.rs b/ast/src/parse/parse_header.rs index 0b9f2016f9..6287806656 100644 --- a/ast/src/parse/parse_header.rs +++ b/ast/src/parse/parse_header.rs @@ -1,3 +1,5 @@ +use crate::lang::core::{expr::expr2::ExprId, header::AppHeader}; + // TODO don't use mock struct and actually parse string pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 68b29c7635..7c89b3440a 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -1,8 +1,5 @@ #![allow(clippy::all)] #![allow(dead_code)] -use crate::lang::constrain::Constraint::{self, *}; -use crate::lang::pool::{Pool, PoolVec, ShallowClone}; -use crate::lang::types::Type2; use bumpalo::Bump; use roc_can::expected::{Expected, PExpected}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; @@ -20,6 +17,12 @@ use roc_types::types::{ use roc_unify::unify::unify; use roc_unify::unify::Unified::*; +use crate::constrain::Constraint; +use crate::lang::core::types::Type2; +use crate::pool::pool::Pool; +use crate::pool::pool_vec::PoolVec; +use crate::pool::shallow_clone::ShallowClone; + // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler // Thank you, Evan! @@ -197,6 +200,9 @@ fn solve<'a>( subs: &mut Subs, constraint: &Constraint, ) -> State { + + use crate::solve_type::Constraint::*; + match constraint { True => state, // SaveTheEnvironment => { diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 40ebc820bd..f825b7dbc5 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -7,10 +7,12 @@ edition = "2018" description = "An editor for Roc" [dependencies] +roc_ast = { path = "../ast" } roc_collections = { path = "../compiler/collections" } roc_load = { path = "../compiler/load" } roc_builtins = { path = "../compiler/builtins" } roc_can = { path = "../compiler/can" } +roc_code_markup = { path = "../code_markup"} roc_parse = { path = "../compiler/parse" } roc_region = { path = "../compiler/region" } roc_module = { path = "../compiler/module" } diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index b838fdef42..e79e90e23e 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,7 +1,10 @@ -use crate::lang::parse::ASTNodeId; -use crate::ui::ui_error::UIResult; -use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; + +use crate::{ui::text::text_pos::TextPos}; use colored::*; +use roc_ast::ast_error::ASTError; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_code_markup::markup_error::MarkError; +use roc_code_markup::slow_pool::MarkNodeId; use snafu::{Backtrace, ErrorCompat, NoneError, ResultExt, Snafu}; //import errors as follows: @@ -212,8 +215,12 @@ pub enum EdError { #[snafu(display("StringParseError: {}", msg))] StringParseError { msg: String, backtrace: Backtrace }, + #[snafu(display("ASTError: {}", msg))] + ASTErrorBacktrace { msg: String, backtrace: Backtrace }, #[snafu(display("UIError: {}", msg))] UIErrorBacktrace { msg: String, backtrace: Backtrace }, + #[snafu(display("MarkError: {}", msg))] + MarkErrorBacktrace { msg: String, backtrace: Backtrace }, } pub type EdResult = std::result::Result; @@ -226,14 +233,6 @@ pub fn print_err(err: &EdError) { } } -/*pub fn print_ui_err(err: &UIError) { - eprintln!("{}", format!("{}", err).truecolor(255, 0, 0)); - - if let Some(backtrace) = ErrorCompat::backtrace(err) { - eprintln!("{}", color_backtrace(backtrace)); - } -}*/ - fn color_backtrace(backtrace: &snafu::Backtrace) -> String { let backtrace_str = format!("{}", backtrace); let backtrace_split = backtrace_str.split('\n'); @@ -293,9 +292,22 @@ impl From for EdError { } } -pub fn from_ui_res(ui_res: UIResult) -> EdResult { - match ui_res { - Ok(t) => Ok(t), - Err(ui_err) => Err(EdError::from(ui_err)), +impl From for EdError { + fn from(mark_err: MarkError) -> Self { + let msg = format!("{}", mark_err); + + // hack to handle EdError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(MarkErrorBacktrace { msg }).unwrap_err() } } + +impl From for EdError { + fn from(ast_err: ASTError) -> Self { + let msg = format!("{}", ast_err); + + // hack to handle EdError derive + let dummy_res: Result<(), NoneError> = Err(NoneError {}); + dummy_res.context(ASTErrorBacktrace { msg }).unwrap_err() + } +} \ No newline at end of file diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index 2cece54f43..38355aacc2 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -2,20 +2,19 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::NestedNodeWithoutChildren; use crate::editor::ed_error::{NoDefMarkNodeBeforeLineNr, NodeIdNotInGridNodeMap}; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; use crate::editor::util::first_last_index_of; use crate::editor::util::index_of; -use crate::lang::parse::ASTNodeId; use crate::ui::text::selection::Selection; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::{LineInsertionFailed, OutOfBounds, UIResult}; use crate::ui::util::{slice_get, slice_get_mut}; -use snafu::OptionExt; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_code_markup::markup::nodes::get_root_mark_node_id; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::slow_pool::SlowPool; use std::cmp::Ordering; use std::fmt; - -use super::markup::nodes::get_root_mark_node_id; +use snafu::OptionExt; #[derive(Debug)] pub struct GridNodeMap { diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index 9ab70d32ba..a3cf2d3fe7 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -17,14 +17,14 @@ use crate::graphics::{ primitives::rect::Rect, primitives::text::{build_glyph_brush, example_code_glyph_rect, queue_text_draw, Text}, }; -use crate::lang::expr::Env; -use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::CaretPos; use crate::ui::util::path_to_string; use bumpalo::Bump; use cgmath::Vector2; use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use pipelines::RectResources; +use roc_ast::lang::env::Env; +use roc_ast::pool::pool::Pool; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load; diff --git a/editor/src/editor/markup/attribute.rs b/editor/src/editor/markup/attribute.rs deleted file mode 100644 index 304c4aadce..0000000000 --- a/editor/src/editor/markup/attribute.rs +++ /dev/null @@ -1,123 +0,0 @@ -#![allow(dead_code)] - -use crate::editor::ed_error::{CaretNotFound, EdResult}; -use snafu::ensure; - -#[derive(Debug, Copy, Clone)] -pub struct Caret { - pub offset_col: usize, -} - -impl Caret { - pub fn new_attr(offset_col: usize) -> Attribute { - Attribute::Caret { - caret: Caret { offset_col }, - } - } -} -#[derive(Debug)] -pub struct SelectionStart { - offset_col: usize, -} -#[derive(Debug)] -pub struct SelectionEnd { - offset_col: usize, -} - -// Highlight is used for example when searching for a specific string to highlight all search results in the module -#[derive(Debug)] -pub struct HighlightStart { - offset_col: usize, -} -#[derive(Debug)] -pub struct HighlightEnd { - offset_col: usize, -} - -// Underline is used for warnings and errors -#[derive(Debug)] -pub struct UnderlineStart { - offset_col: usize, -} -#[derive(Debug)] -pub struct UnderlineEnd { - offset_col: usize, -} - -#[derive(Debug)] -pub enum Attribute { - // Rust does not yet support types for enum variants so we have to do it like this - Caret { caret: Caret }, - - SelectionStart { selection_start: SelectionStart }, - SelectionEnd { selection_end: SelectionEnd }, - - HighlightStart { highlight_start: HighlightStart }, - HighlightEnd { highlight_end: HighlightEnd }, - - UnderlineStart { underline_start: UnderlineStart }, - UnderlineEnd { underline_end: UnderlineEnd }, -} - -#[derive(Debug)] -pub struct Attributes { - pub all: Vec, -} - -impl Attributes { - pub fn new() -> Attributes { - Attributes { all: Vec::new() } - } - - pub fn add(&mut self, attr: Attribute) { - self.all.push(attr); - } - - pub fn add_caret(&mut self, offset_col: usize) { - self.all.push(Attribute::Caret { - caret: Caret { offset_col }, - }); - } - - pub fn get_mut_carets(&mut self) -> Vec<&mut Caret> { - let mut carets = Vec::new(); - - for attr in self.all.iter_mut() { - if let Attribute::Caret { caret } = attr { - carets.push(caret) - } - } - - carets - } - - pub fn get_carets(&self) -> Vec { - let mut carets = Vec::new(); - - for attr in self.all.iter() { - if let Attribute::Caret { caret } = attr { - carets.push(*caret) - } - } - - carets - } - - pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> EdResult<()> { - let old_len = self.all.len(); - - self.all.retain(|attr| { - if let Attribute::Caret { caret } = attr { - caret.offset_col != offset_col - } else { - true - } - }); - - let new_len = self.all.len(); - - ensure!(old_len != new_len, CaretNotFound { node_id }); - - Ok(()) - } -} diff --git a/editor/src/editor/markup/common_nodes.rs b/editor/src/editor/markup/common_nodes.rs deleted file mode 100644 index f74648f3fa..0000000000 --- a/editor/src/editor/markup/common_nodes.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::{ - editor::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}, - lang::{ast::ExprId, parse::ASTNodeId}, -}; - -use super::{attribute::Attributes, nodes, nodes::MarkupNode}; - -pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::EQUALS.to_owned(), - ast_node_id, - syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::COMMA.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Blank { - ast_node_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_blank_mn_w_nls( - ast_node_id: ASTNodeId, - parent_id_opt: Option, - nr_of_newlines: usize, -) -> MarkupNode { - MarkupNode::Blank { - ast_node_id, - syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: nr_of_newlines, - } -} - -pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::COLON.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::LEFT_ACCOLADE.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::RIGHT_ACCOLADE.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::LEFT_SQUARE_BR.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} - -pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) -> MarkupNode { - MarkupNode::Text { - content: nodes::RIGHT_SQUARE_BR.to_owned(), - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), - parent_id_opt, - newlines_at_end: 0, - } -} diff --git a/editor/src/editor/markup/mod.rs b/editor/src/editor/markup/mod.rs deleted file mode 100644 index de015ebd80..0000000000 --- a/editor/src/editor/markup/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod attribute; -pub mod common_nodes; -pub mod nodes; diff --git a/editor/src/editor/markup/nodes.rs b/editor/src/editor/markup/nodes.rs deleted file mode 100644 index 00b5358e88..0000000000 --- a/editor/src/editor/markup/nodes.rs +++ /dev/null @@ -1,876 +0,0 @@ -use super::attribute::Attributes; -use crate::editor::ed_error::EdResult; -use crate::editor::ed_error::ExpectedTextNode; -use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_colon_mn; -use crate::editor::markup::common_nodes::new_comma_mn; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_left_square_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::common_nodes::new_right_square_mn; -use crate::editor::mvc::tld_value_update::tld_mark_node; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::ast::DefId; -use crate::lang::ast::ExprId; -use crate::lang::ast::RecordField; -use crate::lang::ast::ValueDef; -use crate::lang::parse::ASTNodeId; -use crate::lang::parse::{AppHeader, AST}; -use crate::lang::pattern::get_identifier_string; -use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; -use crate::ui::util::slice_get; -use bumpalo::Bump; -use roc_module::symbol::Interns; -use std::fmt; - -#[derive(Debug)] -pub enum MarkupNode { - Nested { - ast_node_id: ASTNodeId, - children_ids: Vec, - parent_id_opt: Option, - newlines_at_end: usize, - }, - Text { - content: String, - ast_node_id: ASTNodeId, - syn_high_style: HighlightStyle, - attributes: Attributes, - parent_id_opt: Option, - newlines_at_end: usize, - }, - Blank { - ast_node_id: ASTNodeId, - attributes: Attributes, - syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank - parent_id_opt: Option, - newlines_at_end: usize, - }, -} - -impl MarkupNode { - pub fn get_ast_node_id(&self) -> ASTNodeId { - match self { - MarkupNode::Nested { ast_node_id, .. } => *ast_node_id, - MarkupNode::Text { ast_node_id, .. } => *ast_node_id, - MarkupNode::Blank { ast_node_id, .. } => *ast_node_id, - } - } - - pub fn get_parent_id_opt(&self) -> Option { - match self { - MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt, - MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt, - MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt, - } - } - - pub fn get_children_ids(&self) -> Vec { - match self { - MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(), - MarkupNode::Text { .. } => vec![], - MarkupNode::Blank { .. } => vec![], - } - } - - pub fn get_sibling_ids(&self, mark_node_pool: &SlowPool) -> Vec { - if let Some(parent_id) = self.get_parent_id_opt() { - let parent_node = mark_node_pool.get(parent_id); - - parent_node.get_children_ids() - } else { - vec![] - } - } - - // return (index of child in list of children, closest ast index of child corresponding to ast node) - pub fn get_child_indices( - &self, - child_id: MarkNodeId, - mark_node_pool: &SlowPool, - ) -> EdResult<(usize, usize)> { - match self { - MarkupNode::Nested { children_ids, .. } => { - let mut mark_child_index_opt: Option = None; - let mut child_ids_with_ast: Vec = Vec::new(); - let self_ast_id = self.get_ast_node_id(); - - for (indx, &mark_child_id) in children_ids.iter().enumerate() { - if mark_child_id == child_id { - mark_child_index_opt = Some(indx); - } - - let child_mark_node = mark_node_pool.get(mark_child_id); - // a node that points to the same ast_node as the parent is a ',', '[', ']' - // those are not "real" ast children - if child_mark_node.get_ast_node_id() != self_ast_id { - child_ids_with_ast.push(mark_child_id) - } - } - - if let Some(child_index) = mark_child_index_opt { - if child_index == (children_ids.len() - 1) { - let ast_child_index = child_ids_with_ast.len(); - - Ok((child_index, ast_child_index)) - } else { - // we want to find the index of the closest ast mark node to child_index - let indices_in_mark_res: EdResult> = child_ids_with_ast - .iter() - .map(|c_id| index_of(*c_id, children_ids)) - .collect(); - - let indices_in_mark = indices_in_mark_res?; - - let mut last_diff = usize::MAX; - let mut best_index = 0; - - for index in indices_in_mark.iter() { - let curr_diff = - isize::abs((*index as isize) - (child_index as isize)) as usize; - - if curr_diff >= last_diff { - break; - } else { - last_diff = curr_diff; - best_index = *index; - } - } - - let closest_ast_child = slice_get(best_index, children_ids)?; - - let closest_ast_child_index = - index_of(*closest_ast_child, &child_ids_with_ast)?; - - // +1 because we want to insert after ast_child - Ok((child_index, closest_ast_child_index + 1)) - } - } else { - NestedNodeMissingChild { - node_id: child_id, - children_ids: children_ids.clone(), - } - .fail() - } - } - _ => NestedNodeRequired { - node_type: self.node_type_as_string(), - } - .fail(), - } - } - - pub fn get_content(&self) -> String { - match self { - MarkupNode::Nested { .. } => "".to_owned(), - MarkupNode::Text { content, .. } => content.clone(), - MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(), - } - } - - // gets content and adds newline from newline_at_end - pub fn get_full_content(&self) -> String { - let mut full_content = self.get_content(); - - for _ in 0..self.get_newlines_at_end() { - full_content.push('\n') - } - - full_content - } - - pub fn get_content_mut(&mut self) -> EdResult<&mut String> { - match self { - MarkupNode::Nested { .. } => ExpectedTextNode { - function_name: "set_content".to_owned(), - node_type: self.node_type_as_string(), - } - .fail(), - MarkupNode::Text { content, .. } => Ok(content), - MarkupNode::Blank { .. } => ExpectedTextNode { - function_name: "set_content".to_owned(), - node_type: self.node_type_as_string(), - } - .fail(), - } - } - - pub fn is_all_alphanumeric(&self) -> bool { - self.get_content() - .chars() - .all(|chr| chr.is_ascii_alphanumeric()) - } - - pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> { - if let MarkupNode::Nested { children_ids, .. } = self { - children_ids.splice(index..index, vec![child_id]); - } else { - NestedNodeRequired { - node_type: self.node_type_as_string(), - } - .fail()?; - } - - Ok(()) - } - - pub fn node_type_as_string(&self) -> String { - let type_str = match self { - MarkupNode::Nested { .. } => "Nested", - MarkupNode::Text { .. } => "Text", - MarkupNode::Blank { .. } => "Blank", - }; - - type_str.to_owned() - } - - pub fn is_blank(&self) -> bool { - matches!(self, MarkupNode::Blank { .. }) - } - - pub fn is_nested(&self) -> bool { - matches!(self, MarkupNode::Nested { .. }) - } - - pub fn get_newlines_at_end(&self) -> usize { - match self { - MarkupNode::Nested { - newlines_at_end, .. - } => *newlines_at_end, - MarkupNode::Text { - newlines_at_end, .. - } => *newlines_at_end, - MarkupNode::Blank { - newlines_at_end, .. - } => *newlines_at_end, - } - } - - pub fn add_newline_at_end(&mut self) { - match self { - MarkupNode::Nested { - newlines_at_end, .. - } => *newlines_at_end += 1, - MarkupNode::Text { - newlines_at_end, .. - } => *newlines_at_end += 1, - MarkupNode::Blank { - newlines_at_end, .. - } => *newlines_at_end += 1, - } - } -} - -fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String { - pool_str.as_str(env.pool).to_owned() -} - -pub const BLANK_PLACEHOLDER: &str = " "; -pub const LEFT_ACCOLADE: &str = "{ "; -pub const RIGHT_ACCOLADE: &str = " }"; -pub const LEFT_SQUARE_BR: &str = "[ "; -pub const RIGHT_SQUARE_BR: &str = " ]"; -pub const COLON: &str = ": "; -pub const COMMA: &str = ", "; -pub const STRING_QUOTES: &str = "\"\""; -pub const EQUALS: &str = " = "; - -fn new_markup_node( - text: String, - node_id: ASTNodeId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> MarkNodeId { - let node = MarkupNode::Text { - content: text, - ast_node_id: node_id, - syn_high_style: highlight_style, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(node) -} - -pub fn def2_to_markup<'a, 'b>( - arena: &'a Bump, - env: &mut Env<'b>, - def2: &Def2, - def2_node_id: DefId, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> EdResult { - let ast_node_id = ASTNodeId::ADefId(def2_node_id); - - let mark_node_id = match def2 { - Def2::ValueDef { - identifier_id, - expr_id, - } => { - let expr_mn_id = expr2_to_markup( - arena, - env, - env.pool.get(*expr_id), - *expr_id, - mark_node_pool, - interns, - )?; - - let tld_mn = tld_mark_node( - *identifier_id, - expr_mn_id, - ast_node_id, - mark_node_pool, - env, - interns, - )?; - - mark_node_pool.add(tld_mn) - } - Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)), - }; - - Ok(mark_node_id) -} - -// make Markup Nodes: generate String representation, assign Highlighting Style -pub fn expr2_to_markup<'a, 'b>( - arena: &'a Bump, - env: &mut Env<'b>, - expr2: &Expr2, - expr2_node_id: ExprId, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> EdResult { - let ast_node_id = ASTNodeId::AExprId(expr2_node_id); - - let mark_node_id = match expr2 { - Expr2::SmallInt { text, .. } - | Expr2::I128 { text, .. } - | Expr2::U128 { text, .. } - | Expr2::Float { text, .. } => { - let num_str = get_string(env, text); - - new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool) - } - Expr2::Str(text) => new_markup_node( - "\"".to_owned() + text.as_str(env.pool) + "\"", - ast_node_id, - HighlightStyle::String, - mark_node_pool, - ), - Expr2::GlobalTag { name, .. } => new_markup_node( - get_string(env, name), - ast_node_id, - HighlightStyle::Type, - mark_node_pool, - ), - Expr2::Call { expr: expr_id, .. } => { - let expr = env.pool.get(*expr_id); - expr2_to_markup(arena, env, expr, *expr_id, mark_node_pool, interns)? - } - Expr2::Var(symbol) => { - //TODO make bump_format with arena - let text = format!("{:?}", symbol); - new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool) - } - Expr2::List { elems, .. } => { - let mut children_ids = - vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))]; - - let indexed_node_ids: Vec<(usize, ExprId)> = - elems.iter(env.pool).copied().enumerate().collect(); - - for (idx, node_id) in indexed_node_ids.iter() { - let sub_expr2 = env.pool.get(*node_id); - - children_ids.push(expr2_to_markup( - arena, - env, - sub_expr2, - *node_id, - mark_node_pool, - interns, - )?); - - if idx + 1 < elems.len() { - children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); - } - } - children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None))); - - let list_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(list_node) - } - Expr2::EmptyRecord => { - let children_ids = vec![ - mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)), - mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)), - ]; - - let record_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(record_node) - } - Expr2::Record { fields, .. } => { - let mut children_ids = - vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))]; - - for (idx, field_node_id) in fields.iter_node_ids().enumerate() { - let record_field = env.pool.get(field_node_id); - - let field_name = record_field.get_record_field_pool_str(); - - children_ids.push(new_markup_node( - field_name.as_str(env.pool).to_owned(), - ast_node_id, - HighlightStyle::RecordField, - mark_node_pool, - )); - - match record_field { - RecordField::InvalidLabelOnly(_, _) => (), - RecordField::LabelOnly(_, _, _) => (), - RecordField::LabeledValue(_, _, sub_expr2_node_id) => { - children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None))); - - let sub_expr2 = env.pool.get(*sub_expr2_node_id); - children_ids.push(expr2_to_markup( - arena, - env, - sub_expr2, - *sub_expr2_node_id, - mark_node_pool, - interns, - )?); - } - } - - if idx + 1 < fields.len() { - children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None))); - } - } - - children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None))); - - let record_node = MarkupNode::Nested { - ast_node_id, - children_ids, - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(record_node) - } - Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)), - Expr2::LetValue { - def_id, - body_id: _, - body_var: _, - } => { - let pattern_id = env.pool.get(*def_id).get_pattern_id(); - - let pattern2 = env.pool.get(pattern_id); - - let val_name = get_identifier_string(pattern2, interns)?; - - let val_name_mn = MarkupNode::Text { - content: val_name, - ast_node_id, - syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - let val_name_mn_id = mark_node_pool.add(val_name_mn); - - let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None)); - - let value_def = env.pool.get(*def_id); - - match value_def { - ValueDef::NoAnnotation { - pattern_id: _, - expr_id, - expr_var: _, - } => { - let body_mn_id = expr2_to_markup( - arena, - env, - env.pool.get(*expr_id), - *expr_id, - mark_node_pool, - interns, - )?; - - let body_mn = mark_node_pool.get_mut(body_mn_id); - body_mn.add_newline_at_end(); - - let full_let_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id], - parent_id_opt: None, - newlines_at_end: 1, - }; - - mark_node_pool.add(full_let_node) - } - other => { - unimplemented!( - "I don't know how to convert {:?} into a MarkupNode yet.", - other - ) - } - } - } - Expr2::RuntimeError() => new_markup_node( - "RunTimeError".to_string(), - ast_node_id, - HighlightStyle::Blank, - mark_node_pool, - ), - rest => todo!("implement expr2_to_markup for {:?}", rest), - }; - - Ok(mark_node_id) -} - -pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) { - let node = mark_node_pool.get(markup_node_id); - - if let MarkupNode::Nested { - ast_node_id: _, - children_ids, - parent_id_opt: _, - newlines_at_end: _, - } = node - { - // need to clone because of borrowing issues - let children_ids_clone = children_ids.clone(); - - for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); - } - } -} - -pub fn set_parent_for_all_helper( - markup_node_id: MarkNodeId, - parent_node_id: MarkNodeId, - mark_node_pool: &mut SlowPool, -) { - let node = mark_node_pool.get_mut(markup_node_id); - - match node { - MarkupNode::Nested { - children_ids, - parent_id_opt, - .. - } => { - *parent_id_opt = Some(parent_node_id); - - // need to clone because of borrowing issues - let children_ids_clone = children_ids.clone(); - - for child_id in children_ids_clone { - set_parent_for_all_helper(child_id, markup_node_id, mark_node_pool); - } - } - MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), - MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id), - } -} - -fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId { - let mark_node = MarkupNode::Text { - content, - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: HighlightStyle::PackageRelated, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(mark_node) -} - -fn header_val_mn( - content: String, - expr_id: ExprId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> MarkNodeId { - let mark_node = MarkupNode::Text { - content, - ast_node_id: ASTNodeId::AExprId(expr_id), - syn_high_style: highlight_style, - attributes: Attributes::new(), - parent_id_opt: None, - newlines_at_end: 0, - }; - - mark_node_pool.add(mark_node) -} - -pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId { - let expr_id = app_header.ast_node_id; - let ast_node_id = ASTNodeId::AExprId(expr_id); - - let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool); - - let app_name_node_id = header_val_mn( - app_header.app_name.clone(), - expr_id, - HighlightStyle::String, - mark_node_pool, - ); - - let full_app_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![app_node_id, app_name_node_id], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool); - - let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None)); - - let pack_base_node_id = header_val_mn( - "base: ".to_owned(), - expr_id, - HighlightStyle::RecordField, - mark_node_pool, - ); - - let pack_val_node_id = header_val_mn( - app_header.packages_base.clone(), - expr_id, - HighlightStyle::String, - mark_node_pool, - ); - - let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None)); - - let full_packages_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![ - packages_node_id, - pack_left_acc_node_id, - pack_base_node_id, - pack_val_node_id, - pack_right_acc_node_id, - ], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool); - - let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); - - let mut import_child_ids: Vec = add_header_mn_list( - &app_header.imports, - expr_id, - HighlightStyle::Import, - mark_node_pool, - ); - - let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); - - let mut full_import_children = vec![imports_node_id, imports_left_square_node_id]; - - full_import_children.append(&mut import_child_ids); - full_import_children.push(imports_right_square_node_id); - - let full_import_node = MarkupNode::Nested { - ast_node_id, - children_ids: full_import_children, - parent_id_opt: None, - newlines_at_end: 1, - }; - - let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool); - - let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None)); - - let mut provides_val_node_ids: Vec = add_header_mn_list( - &app_header.provides, - expr_id, - HighlightStyle::Provides, - mark_node_pool, - ); - - let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None)); - - let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool); - - let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id]; - - full_provides_children.append(&mut provides_val_node_ids); - full_provides_children.push(provides_right_square_node_id); - full_provides_children.push(provides_end_node_id); - - let full_provides_node = MarkupNode::Nested { - ast_node_id, - children_ids: full_provides_children, - parent_id_opt: None, - newlines_at_end: 1, - }; - - let full_app_node_id = mark_node_pool.add(full_app_node); - let full_packages_node = mark_node_pool.add(full_packages_node); - let full_import_node_id = mark_node_pool.add(full_import_node); - let full_provides_node_id = mark_node_pool.add(full_provides_node); - - let header_mark_node = MarkupNode::Nested { - ast_node_id, - children_ids: vec![ - full_app_node_id, - full_packages_node, - full_import_node_id, - full_provides_node_id, - ], - parent_id_opt: None, - newlines_at_end: 1, - }; - - let header_mn_id = mark_node_pool.add(header_mark_node); - - set_parent_for_all(header_mn_id, mark_node_pool); - - header_mn_id -} - -// Used for provides and imports -fn add_header_mn_list( - str_vec: &[String], - expr_id: ExprId, - highlight_style: HighlightStyle, - mark_node_pool: &mut SlowPool, -) -> Vec { - let nr_of_elts = str_vec.len(); - - str_vec - .iter() - .enumerate() - .map(|(indx, provide_str)| { - let provide_str = header_val_mn( - provide_str.to_owned(), - expr_id, - highlight_style, - mark_node_pool, - ); - - if indx != nr_of_elts - 1 { - vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))] - } else { - vec![provide_str] - } - }) - .flatten() - .collect() -} - -pub fn ast_to_mark_nodes<'a, 'b>( - arena: &'a Bump, - env: &mut Env<'b>, - ast: &AST, - mark_node_pool: &mut SlowPool, - interns: &Interns, -) -> EdResult> { - let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; - - for &def_id in ast.def_ids.iter() { - let def2 = env.pool.get(def_id); - - let expr2_markup_id = def2_to_markup(arena, env, def2, def_id, mark_node_pool, interns)?; - - set_parent_for_all(expr2_markup_id, mark_node_pool); - - all_mark_node_ids.push(expr2_markup_id); - } - - Ok(all_mark_node_ids) -} - -impl fmt::Display for MarkupNode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} ({}, {})", - self.node_type_as_string(), - self.get_content(), - self.get_newlines_at_end() - ) - } -} - -pub fn tree_as_string(root_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> String { - let mut full_string = "\n(mark_node_tree)\n".to_owned(); - - let node = mark_node_pool.get(root_node_id); - - full_string.push_str(&format!("{} mn_id {}\n", node, root_node_id)); - - tree_as_string_helper(node, 1, &mut full_string, mark_node_pool); - - full_string -} - -fn tree_as_string_helper( - node: &MarkupNode, - level: usize, - tree_string: &mut String, - mark_node_pool: &SlowPool, -) { - for child_id in node.get_children_ids() { - let mut full_str = std::iter::repeat("|--- ") - .take(level) - .collect::>() - .join("") - .to_owned(); - - let child = mark_node_pool.get(child_id); - let child_str = format!("{}", mark_node_pool.get(child_id)).replace("\n", "\\n"); - - full_str.push_str(&format!("{} mn_id {}\n", child_str, child_id)); - - tree_string.push_str(&full_str); - - tree_as_string_helper(child, level + 1, tree_string, mark_node_pool); - } -} - -// return to the the root parent_id of the current node -pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool) -> MarkNodeId { - let mut curr_mark_node_id = mark_node_id; - let mut curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); - - while let Some(curr_parent_id) = curr_parent_id_opt { - curr_mark_node_id = curr_parent_id; - curr_parent_id_opt = mark_node_pool.get(curr_mark_node_id).get_parent_id_opt(); - } - - curr_mark_node_id -} diff --git a/editor/src/editor/mod.rs b/editor/src/editor/mod.rs index 89efb3a318..f776acc44c 100644 --- a/editor/src/editor/mod.rs +++ b/editor/src/editor/mod.rs @@ -4,13 +4,10 @@ pub mod ed_error; mod grid_node_map; mod keyboard_input; pub mod main; -mod markup; mod mvc; mod render_ast; mod render_debug; mod resources; -mod slow_pool; mod style; -mod syntax_highlight; mod theme; mod util; diff --git a/editor/src/editor/mvc/break_line.rs b/editor/src/editor/mvc/break_line.rs index 6fc8c7ca13..b8de1c1077 100644 --- a/editor/src/editor/mvc/break_line.rs +++ b/editor/src/editor/mvc/break_line.rs @@ -1,10 +1,11 @@ +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::def::def2::Def2; +use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls; + use crate::editor::ed_error::EdResult; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::parse::ASTNodeId; use crate::ui::text::text_pos::TextPos; // put everything after caret on new line, create a Def2::Blank if there was nothing after the caret. diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index a522bc6553..4d42e4e12c 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -1,21 +1,22 @@ use crate::editor::code_lines::CodeLines; use crate::editor::grid_node_map::GridNodeMap; -use crate::editor::markup::nodes::ast_to_mark_nodes; -use crate::editor::slow_pool::{MarkNodeId, SlowPool}; use crate::editor::{ ed_error::SrcParseError, ed_error::{EdResult, EmptyCodeString, MissingParent, NoNodeAtCaretPosition}, }; use crate::graphics::primitives::rect::Rect; -use crate::lang::expr::Env; -use crate::lang::parse::{ASTNodeId, AST}; -use crate::lang::pool::PoolStr; use crate::ui::text::caret_w_select::{CaretPos, CaretWSelect}; use crate::ui::text::lines::SelectableLines; use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; use bumpalo::Bump; use nonempty::NonEmpty; +use roc_ast::lang::core::ast::{AST, ASTNodeId}; +use roc_ast::lang::env::Env; +use roc_ast::parse::parse_ast; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::markup::nodes::ast_to_mark_nodes; +use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use roc_load::file::LoadedModule; use std::path::Path; @@ -61,12 +62,14 @@ pub fn init_model<'a>( let markup_ids = if code_str.is_empty() { EmptyCodeString {}.fail() } else { - ast_to_mark_nodes( - code_arena, - &mut module.env, - &module.ast, - &mut mark_node_pool, - &loaded_module.interns, + Ok( + ast_to_mark_nodes( + code_arena, + &mut module.env, + &module.ast, + &mut mark_node_pool, + &loaded_module.interns, + )? ) }?; @@ -152,7 +155,7 @@ impl<'a> EdModel<'a> { if let Some(parent_id) = curr_mark_node.get_parent_id_opt() { let parent = self.mark_node_pool.get(parent_id); - parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool) + Ok(parent.get_child_indices(curr_mark_node_id, &self.mark_node_pool)?) } else { MissingParent { node_id: curr_mark_node_id, @@ -180,7 +183,7 @@ pub struct EdModule<'a> { impl<'a> EdModule<'a> { pub fn new(code_str: &'a str, mut env: Env<'a>, ast_arena: &'a Bump) -> EdResult> { if !code_str.is_empty() { - let parse_res = AST::parse_from_string(code_str, &mut env, ast_arena); + let parse_res = parse_ast::parse_from_string(code_str, &mut env, ast_arena); match parse_res { Ok(ast) => Ok(EdModule { env, ast }), @@ -201,8 +204,6 @@ pub mod test_ed_model { use crate::editor::main::load_module; use crate::editor::mvc::ed_model; use crate::editor::resources::strings::HELLO_WORLD; - use crate::lang::expr::Env; - use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::test_caret_w_select::convert_dsl_to_selection; use crate::ui::text::caret_w_select::test_caret_w_select::convert_selection_to_dsl; use crate::ui::text::caret_w_select::CaretPos; @@ -211,6 +212,8 @@ pub mod test_ed_model { use crate::ui::ui_error::UIResult; use bumpalo::Bump; use ed_model::EdModel; + use roc_ast::lang::env::Env; + use roc_ast::pool::pool::Pool; use roc_load::file::LoadedModule; use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 96a9f61d33..2e8c527d2c 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -4,14 +4,14 @@ use std::process::Command; use std::process::Stdio; use crate::editor::code_lines::CodeLines; -use crate::editor::ed_error::from_ui_res; +//use crate::editor::ed_error::from_ui_res; use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingSelection; use crate::editor::grid_node_map::GridNodeMap; -use crate::editor::markup::attribute::Attributes; +/*use crate::editor::markup::attribute::Attributes; use crate::editor::markup::nodes; use crate::editor::markup::nodes::MarkupNode; -use crate::editor::markup::nodes::EQUALS; +use crate::editor::markup::nodes::EQUALS;*/ use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_model::SelectedBlock; @@ -27,7 +27,7 @@ use crate::editor::mvc::string_update::start_new_string; use crate::editor::mvc::string_update::update_small_string; use crate::editor::mvc::string_update::update_string; use crate::editor::mvc::tld_value_update::{start_new_tld_value, update_tld_val_name}; -use crate::editor::slow_pool::MarkNodeId; +/*use crate::editor::slow_pool::MarkNodeId; use crate::editor::slow_pool::SlowPool; use crate::editor::syntax_highlight::HighlightStyle; use crate::lang::ast::Def2; @@ -38,7 +38,7 @@ use crate::lang::parse::ASTNodeId; use crate::lang::pool::Pool; use crate::lang::pool::PoolStr; use crate::lang::types::Type2; -use crate::lang::{constrain::Constraint, solve}; +use crate::lang::{constrain::Constraint, solve};*/ use crate::ui::text::caret_w_select::CaretWSelect; use crate::ui::text::lines::MoveCaretFun; use crate::ui::text::selection::validate_raw_sel; @@ -51,7 +51,25 @@ use crate::ui::util::path_to_string; use crate::ui::util::write_to_file; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; +use roc_ast::constrain::Constraint; +use roc_ast::constrain::constrain_expr; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::def::def2::Def2; +use roc_ast::lang::core::def::def2::DefId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::expr::expr2::ExprId; +use roc_ast::lang::core::types::Type2; +use roc_ast::pool::pool::Pool; +use roc_ast::pool::pool_str::PoolStr; +use roc_ast::solve_type; use roc_can::expected::Expected; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::EQUALS; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::slow_pool::SlowPool; +use roc_code_markup::syntax_highlight::HighlightStyle; use roc_collections::all::MutMap; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -492,8 +510,8 @@ impl<'a> EdModel<'a> { rigid_variables: MutMap, constraint: Constraint, var_store: VarStore, - ) -> (Solved, solve::Env, Vec) { - let env = solve::Env { + ) -> (Solved, solve_type::Env, Vec) { + let env = solve_type::Env { vars_by_symbol: MutMap::default(), aliases, }; @@ -511,7 +529,7 @@ impl<'a> EdModel<'a> { // Run the solver to populate Subs. let (solved_subs, solved_env) = - solve::run(&arena, mempool, &env, &mut problems, subs, &constraint); + solve_type::run(&arena, mempool, &env, &mut problems, subs, &constraint); (solved_subs, solved_env, problems) } @@ -804,7 +822,7 @@ pub fn get_node_context<'a>(ed_model: &'a EdModel) -> EdResult> fn if_modifiers(modifiers: &Modifiers, shortcut_result: UIResult<()>) -> EdResult<()> { if modifiers.cmd_or_ctrl() { - from_ui_res(shortcut_result) + Ok(shortcut_result?) } else { Ok(()) } diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index 6fee1301f5..ab35ecbdbd 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -7,7 +7,6 @@ use crate::editor::render_debug::build_debug_graphics; use crate::editor::resources::strings::START_TIP; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text::{owned_section_from_text, Text}; -use crate::lang::pool::Pool; use crate::ui::text::caret_w_select::make_caret_rect; use crate::ui::text::caret_w_select::make_selection_rect; use crate::ui::text::caret_w_select::CaretWSelect; @@ -15,6 +14,7 @@ use crate::ui::text::selection::Selection; use crate::ui::tooltip::ToolTip; use crate::ui::ui_error::MissingGlyphDims; use cgmath::Vector2; +use roc_ast::pool::pool::Pool; use snafu::OptionExt; use winit::dpi::PhysicalSize; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index 237da57d86..bee8f0623d 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -1,17 +1,18 @@ +use roc_ast::lang::core::expr::expr2::IntStyle; +use roc_ast::lang::core::expr::expr2::Expr2::{SmallInt}; +use roc_ast::lang::core::expr::expr2::IntVal; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::syntax_highlight::HighlightStyle; + use crate::editor::ed_error::EdResult; use crate::editor::ed_error::StringParseError; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::Expr2::SmallInt; -use crate::lang::ast::IntVal; -use crate::lang::ast::{IntStyle, IntVal::*}; -use crate::lang::pool::PoolStr; use crate::ui::text::lines::SelectableLines; // digit_char should be verified to be a digit before calling this function @@ -145,6 +146,9 @@ pub fn update_int( } fn update_small_int_num(number: &mut IntVal, updated_str: &str) -> EdResult<()> { + + use IntVal::*; + *number = match number { I64(_) => I64(check_parse_res(updated_str.parse::())?), U64(_) => U64(check_parse_res(updated_str.parse::())?), diff --git a/editor/src/editor/mvc/let_update.rs b/editor/src/editor/mvc/let_update.rs index 697531bb5a..bab48fb6b9 100644 --- a/editor/src/editor/mvc/let_update.rs +++ b/editor/src/editor/mvc/let_update.rs @@ -1,18 +1,19 @@ +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::pattern::Pattern2; +use roc_ast::lang::core::val_def::ValueDef; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::common_nodes::new_blank_mn_w_nls; +use roc_code_markup::markup::common_nodes::new_equals_mn; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::syntax_highlight::HighlightStyle; use roc_module::symbol::Symbol; use crate::editor::ed_error::EdResult; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::{Expr2, ValueDef}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pattern::Pattern2; pub fn start_new_let_value(ed_model: &mut EdModel, new_char: &char) -> EdResult { let NodeContext { diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 65e6fce10b..764b81892b 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,19 +1,16 @@ +use roc_ast::lang::core::ast::{ASTNodeId, ast_node_to_string}; +use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; +use roc_ast::pool::pool_vec::PoolVec; +use roc_code_markup::markup::common_nodes::{new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn}; +use roc_code_markup::markup::nodes::{self, MarkupNode}; +use roc_code_markup::slow_pool::MarkNodeId; + use crate::editor::ed_error::EdResult; use crate::editor::ed_error::{MissingParent, UnexpectedASTNode}; -use crate::editor::markup::common_nodes::{ - new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, -}; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::lang::ast::ExprId; -use crate::lang::ast::{ast_node_to_string, Expr2}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pool::PoolVec; use crate::ui::text::text_pos::TextPos; pub fn start_new_list(ed_model: &mut EdModel) -> EdResult { diff --git a/editor/src/editor/mvc/lookup_update.rs b/editor/src/editor/mvc/lookup_update.rs index 03a4905942..53db22427a 100644 --- a/editor/src/editor/mvc/lookup_update.rs +++ b/editor/src/editor/mvc/lookup_update.rs @@ -1,9 +1,10 @@ +use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::slow_pool::MarkNodeId; + use crate::editor::ed_error::EdResult; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; -use crate::editor::slow_pool::MarkNodeId; -use crate::lang::ast::{Expr2, ExprId}; -use crate::lang::pool::PoolStr; use crate::ui::text::lines::SelectableLines; pub fn update_invalid_lookup( diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 0b8c911ab6..94f824375c 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -1,23 +1,26 @@ use crate::editor::ed_error::EdResult; use crate::editor::ed_error::MissingParent; use crate::editor::ed_error::RecordWithoutFields; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::syntax_highlight::HighlightStyle; use crate::editor::util::index_of; -use crate::lang::ast::{Expr2, ExprId, RecordField}; -use crate::lang::parse::ASTNodeId; -use crate::lang::pool::{PoolStr, PoolVec}; use crate::ui::text::text_pos::TextPos; +use roc_ast::lang::core::ast::ASTNodeId; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::expr::expr2::ExprId; +use roc_ast::lang::core::expr::record_field::RecordField; +use roc_ast::pool::pool_str::PoolStr; +use roc_ast::pool::pool_vec::PoolVec; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::common_nodes::new_blank_mn; +use roc_code_markup::markup::common_nodes::new_left_accolade_mn; +use roc_code_markup::markup::common_nodes::new_right_accolade_mn; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::slow_pool::MarkNodeId; +use roc_code_markup::syntax_highlight::HighlightStyle; use snafu::OptionExt; pub fn start_new_record(ed_model: &mut EdModel) -> EdResult { diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index 2c35ca89fd..fca9100495 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -1,16 +1,17 @@ +use roc_ast::lang::core::expr::expr2::ArrString; +use roc_ast::lang::core::expr::expr2::Expr2; +use roc_ast::lang::core::str::update_str_expr; +use roc_ast::pool::pool_str::PoolStr; +use roc_code_markup::markup::attribute::Attributes; +use roc_code_markup::markup::nodes; +use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::syntax_highlight::HighlightStyle; + use crate::editor::ed_error::EdResult; -use crate::editor::markup::attribute::Attributes; -use crate::editor::markup::nodes; -use crate::editor::markup::nodes::MarkupNode; use crate::editor::mvc::app_update::InputOutcome; use crate::editor::mvc::ed_model::EdModel; use crate::editor::mvc::ed_update::get_node_context; use crate::editor::mvc::ed_update::NodeContext; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::lang::ast::update_str_expr; -use crate::lang::ast::ArrString; -use crate::lang::ast::Expr2; -use crate::lang::pool::PoolStr; pub fn update_small_string( new_char: &char, diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index 8cdb85feba..b07e1c7010 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -1,22 +1,10 @@ +use roc_ast::{lang::{core::{ast::ASTNodeId, def::def2::Def2, expr::expr2::Expr2, pattern::{Pattern2, get_identifier_string}}, env::Env}, pool::pool::NodeId}; +use roc_code_markup::{markup::{attribute::Attributes, common_nodes::{new_blank_mn_w_nls, new_equals_mn}, nodes::{MarkupNode, set_parent_for_all}}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; use roc_module::symbol::{Interns, Symbol}; use crate::{ editor::{ ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, - markup::{ - attribute::Attributes, - common_nodes::{new_blank_mn_w_nls, new_equals_mn}, - nodes::{set_parent_for_all, MarkupNode}, - }, - slow_pool::{MarkNodeId, SlowPool}, - syntax_highlight::HighlightStyle, - }, - lang::{ - ast::{Def2, Expr2}, - expr::Env, - parse::ASTNodeId, - pattern::{get_identifier_string, Pattern2}, - pool::NodeId, }, ui::text::text_pos::TextPos, }; diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index 3fa42bc1de..651b55b47d 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,11 +1,11 @@ -use super::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; -use super::slow_pool::MarkNodeId; + use crate::editor::mvc::ed_view::RenderedWgpu; -use crate::editor::slow_pool::SlowPool; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text as gr_text; use cgmath::Vector2; +use roc_code_markup::markup::nodes::{BLANK_PLACEHOLDER, MarkupNode}; +use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use winit::dpi::PhysicalSize; use crate::{editor::config::Config, graphics::colors}; diff --git a/editor/src/editor/render_debug.rs b/editor/src/editor/render_debug.rs index 36e4377d80..2fc1f23863 100644 --- a/editor/src/editor/render_debug.rs +++ b/editor/src/editor/render_debug.rs @@ -1,11 +1,11 @@ use crate::editor::ed_error::EdResult; -use crate::editor::markup::nodes::tree_as_string; use crate::editor::mvc::ed_model::EdModel; use crate::graphics::colors; use crate::graphics::colors::from_hsb; use crate::graphics::primitives::text as gr_text; -use crate::lang::ast::def2_to_string; use cgmath::Vector2; +use roc_ast::lang::core::def::def2::def2_to_string; +use roc_code_markup::markup::nodes::tree_as_string; use winit::dpi::PhysicalSize; use crate::editor::config::Config; diff --git a/editor/src/editor/slow_pool.rs b/editor/src/editor/slow_pool.rs deleted file mode 100644 index 01058e9668..0000000000 --- a/editor/src/editor/slow_pool.rs +++ /dev/null @@ -1,77 +0,0 @@ - -use std::fmt; - -use super::markup::nodes::MarkupNode; - - -pub type MarkNodeId = usize; - -#[derive(Debug)] -pub struct SlowPool { - nodes: Vec, -} - -impl SlowPool { - pub fn new() -> SlowPool { - SlowPool { nodes: Vec::new() } - } - - pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { - let id = self.nodes.len(); - - self.nodes.push(node); - - id - } - - pub fn get(&self, node_id: MarkNodeId) -> &MarkupNode { - // unwrap because Pool doesn't return Result either - self.nodes.get(node_id).unwrap() - } - - pub fn get_mut(&mut self, node_id: MarkNodeId) -> &mut MarkupNode { - // unwrap because Pool doesn't return Result either - self.nodes.get_mut(node_id).unwrap() - } - - pub fn replace_node(&mut self, node_id: MarkNodeId, new_node: MarkupNode) { - self.nodes[node_id] = new_node; - - // TODO delete children of old node, this requires SlowPool to be changed to - // make sure the indexes still make sense after removal/compaction - } -} - -impl fmt::Display for SlowPool { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "\n\n(mark_node_pool)\n")?; - - for (index, node) in self.nodes.iter().enumerate() { - let ast_node_id_str = format!("{:?}", node.get_ast_node_id()); - let ast_node_id: String = ast_node_id_str - .chars() - .filter(|c| c.is_ascii_digit()) - .collect(); - - let mut child_str = String::new(); - - let node_children = node.get_children_ids(); - - if !node_children.is_empty() { - child_str = format!("children: {:?}", node_children); - } - - writeln!( - f, - "{}: {} ({}) ast_id {:?} {}", - index, - node.node_type_as_string(), - node.get_content(), - ast_node_id.parse::().unwrap(), - child_str - )?; - } - - Ok(()) - } -} diff --git a/editor/src/editor/syntax_highlight.rs b/editor/src/editor/syntax_highlight.rs deleted file mode 100644 index 602a335b48..0000000000 --- a/editor/src/editor/syntax_highlight.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::graphics::colors as gr_colors; -use gr_colors::{from_hsb, RgbaTup}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] -pub enum HighlightStyle { - Operator, // =+-<>... - String, - FunctionName, - Type, - Bracket, - Number, - PackageRelated, // app, packages, imports, exposes, provides... - Variable, - RecordField, - Import, - Provides, - Blank, -} - -pub fn default_highlight_map() -> HashMap { - use HighlightStyle::*; - - let mut highlight_map = HashMap::new(); - [ - (Operator, gr_colors::WHITE), - (String, from_hsb(346, 65, 97)), - (FunctionName, gr_colors::WHITE), - (Type, gr_colors::WHITE), - (Bracket, from_hsb(347, 80, 100)), - (Number, from_hsb(185, 50, 75)), - (PackageRelated, gr_colors::WHITE), - (Variable, gr_colors::WHITE), - (RecordField, from_hsb(258, 50, 90)), - (Import, from_hsb(185, 50, 75)), - (Provides, from_hsb(185, 50, 75)), - (Blank, from_hsb(258, 50, 90)), - // comment from_hsb(285, 6, 47) or 186, 35, 40 - ] - .iter() - .for_each(|tup| { - highlight_map.insert(tup.0, tup.1); - }); - - highlight_map -} diff --git a/editor/src/editor/theme.rs b/editor/src/editor/theme.rs index f0809cd890..dcc3b437b1 100644 --- a/editor/src/editor/theme.rs +++ b/editor/src/editor/theme.rs @@ -1,8 +1,8 @@ use gr_colors::{from_hsb, RgbaTup}; +use roc_code_markup::syntax_highlight::{HighlightStyle, default_highlight_map}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::editor::syntax_highlight::{default_highlight_map, HighlightStyle}; use crate::graphics::colors as gr_colors; use crate::ui::theme::UITheme; diff --git a/editor/src/lang/ast.rs b/editor/src/lang/ast.rs deleted file mode 100644 index 3fffb3cdad..0000000000 --- a/editor/src/lang/ast.rs +++ /dev/null @@ -1,742 +0,0 @@ -#![allow(clippy::manual_map)] - -use std::collections::{HashMap, HashSet}; -use std::hash::BuildHasherDefault; - -use crate::editor::ed_error::{EdResult, UnexpectedASTNode}; -use crate::lang::pattern::{Pattern2, PatternId}; -use crate::lang::pool::Pool; -use crate::lang::pool::{NodeId, PoolStr, PoolVec, ShallowClone}; -use crate::lang::types::{Type2, TypeId}; -use arraystring::{typenum::U30, ArrayString}; -use roc_can::expr::Recursive; -use roc_collections::all::WyHash; -use roc_module::low_level::LowLevel; -use roc_module::operator::CalledVia; -use roc_module::symbol::Symbol; -use roc_types::subs::Variable; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Problem { - RanOutOfNodeIds, -} - -pub type Res = Result; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum IntStyle { - Decimal, - Octal, - Hex, - Binary, -} - -impl IntStyle { - pub fn from_base(base: roc_parse::ast::Base) -> Self { - use roc_parse::ast::Base; - match base { - Base::Decimal => Self::Decimal, - Base::Octal => Self::Octal, - Base::Hex => Self::Hex, - Base::Binary => Self::Binary, - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum IntVal { - I64(i64), - U64(u64), - I32(i32), - U32(u32), - I16(i16), - U16(u16), - I8(i8), - U8(u8), -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum FloatVal { - F64(f64), - F32(f32), -} - -#[derive(Debug)] -pub enum RecordField { - InvalidLabelOnly(PoolStr, Variable), - LabelOnly(PoolStr, Variable, Symbol), - LabeledValue(PoolStr, Variable, ExprId), -} - -#[test] -fn size_of_intval() { - assert_eq!(std::mem::size_of::(), 16); -} - -pub type ArrString = ArrayString; - -/// An Expr that fits in 32B. -/// It has a 1B discriminant and variants which hold payloads of at most 31B. -#[derive(Debug)] -pub enum Expr2 { - /// A negative number literal without a dot - SmallInt { - number: IntVal, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - // TODO(rvcas): rename this eventually - /// A large (over 64-bit) negative number literal without a dot. - /// This variant can't use IntVal because if IntVal stored 128-bit - /// integers, it would be 32B on its own because of alignment. - I128 { - number: i128, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - // TODO(rvcas): rename this eventually - /// A large (over 64-bit) nonnegative number literal without a dot - /// This variant can't use IntVal because if IntVal stored 128-bit - /// integers, it would be 32B on its own because of alignment. - U128 { - number: u128, // 16B - var: Variable, // 4B - style: IntStyle, // 1B - text: PoolStr, // 8B - }, - /// A floating-point literal (with a dot) - Float { - number: FloatVal, // 16B - var: Variable, // 4B - text: PoolStr, // 8B - }, - /// string literals of length up to 30B - SmallStr(ArrString), // 31B - /// string literals of length 31B or more - Str(PoolStr), // 8B - // Lookups - Var(Symbol), // 8B - InvalidLookup(PoolStr), // 8B - - List { - elem_var: Variable, // 4B - elems: PoolVec, // 8B - }, - If { - cond_var: Variable, // 4B - expr_var: Variable, // 4B - branches: PoolVec<(ExprId, ExprId)>, // 8B - final_else: ExprId, // 4B - }, - When { - cond_var: Variable, // 4B - expr_var: Variable, // 4B - branches: PoolVec, // 8B - cond: ExprId, // 4B - }, - LetRec { - defs: PoolVec, // 8B - body_var: Variable, // 8B - body_id: ExprId, // 4B - }, - LetFunction { - def_id: NodeId, // 4B - body_var: Variable, // 8B - body_id: ExprId, // 4B - }, - LetValue { - def_id: NodeId, // 4B - body_id: ExprId, // 4B - body_var: Variable, // 4B - }, - Call { - args: PoolVec<(Variable, ExprId)>, // 8B - expr: ExprId, // 4B - expr_var: Variable, // 4B - fn_var: Variable, // 4B - closure_var: Variable, // 4B - called_via: CalledVia, // 2B - }, - RunLowLevel { - op: LowLevel, // 1B - args: PoolVec<(Variable, ExprId)>, // 8B - ret_var: Variable, // 4B - }, - Closure { - args: PoolVec<(Variable, NodeId)>, // 8B - name: Symbol, // 8B - body: ExprId, // 4B - function_type: Variable, // 4B - recursive: Recursive, // 1B - extra: NodeId, // 4B - }, - // Product Types - Record { - record_var: Variable, // 4B - fields: PoolVec, // 8B - }, - /// Empty record constant - EmptyRecord, - /// Look up exactly one field on a record, e.g. (expr).foo. - Access { - field: PoolStr, // 4B - expr: ExprId, // 4B - record_var: Variable, // 4B - ext_var: Variable, // 4B - field_var: Variable, // 4B - }, - - /// field accessor as a function, e.g. (.foo) expr - Accessor { - function_var: Variable, // 4B - closure_var: Variable, // 4B - field: PoolStr, // 4B - record_var: Variable, // 4B - ext_var: Variable, // 4B - field_var: Variable, // 4B - }, - Update { - symbol: Symbol, // 8B - updates: PoolVec, // 8B - record_var: Variable, // 4B - ext_var: Variable, // 4B - }, - - // Sum Types - GlobalTag { - name: PoolStr, // 4B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, - PrivateTag { - name: Symbol, // 8B - variant_var: Variable, // 4B - ext_var: Variable, // 4B - arguments: PoolVec<(Variable, ExprId)>, // 8B - }, - Blank, // Rendered as empty box in editor - - // Compiles, but will crash if reached - RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */), -} - -// A top level definition, not inside a function. For example: `main = "Hello, world!"` -#[derive(Debug)] -pub enum Def2 { - // ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!" - ValueDef { - identifier_id: NodeId, - expr_id: NodeId, - }, - Blank, -} - -#[derive(Debug)] -pub enum ValueDef { - WithAnnotation { - pattern_id: PatternId, // 4B - expr_id: ExprId, // 4B - type_id: TypeId, - rigids: Rigids, - expr_var: Variable, // 4B - }, - NoAnnotation { - pattern_id: PatternId, // 4B - expr_id: ExprId, // 4B - expr_var: Variable, // 4B - }, -} - -impl ShallowClone for ValueDef { - fn shallow_clone(&self) -> Self { - match self { - Self::WithAnnotation { - pattern_id, - expr_id, - type_id, - rigids, - expr_var, - } => Self::WithAnnotation { - pattern_id: *pattern_id, - expr_id: *expr_id, - type_id: *type_id, - rigids: rigids.shallow_clone(), - expr_var: *expr_var, - }, - Self::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => Self::NoAnnotation { - pattern_id: *pattern_id, - expr_id: *expr_id, - expr_var: *expr_var, - }, - } - } -} - -impl ValueDef { - pub fn get_expr_id(&self) -> ExprId { - match self { - ValueDef::WithAnnotation { expr_id, .. } => *expr_id, - ValueDef::NoAnnotation { expr_id, .. } => *expr_id, - } - } - - pub fn get_pattern_id(&self) -> NodeId { - match self { - ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id, - ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id, - } - } -} - -pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { - match val_def { - ValueDef::WithAnnotation { - pattern_id, - expr_id, - type_id, - rigids, - expr_var, - } => { - format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var) - } - ValueDef::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => { - format!( - "NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}", - pool.get(*pattern_id), - expr2_to_string(*expr_id, pool), - expr_var - ) - } - } -} - -#[derive(Debug)] -pub enum FunctionDef { - WithAnnotation { - name: Symbol, // 8B - arguments: PoolVec<(PatternId, Type2)>, // 8B - rigids: NodeId, // 4B - return_type: TypeId, // 4B - body: ExprId, // 4B - }, - NoAnnotation { - name: Symbol, // 8B - arguments: PoolVec<(PatternId, Variable)>, // 8B - return_var: Variable, // 4B - body: ExprId, // 4B - }, -} - -impl ShallowClone for FunctionDef { - fn shallow_clone(&self) -> Self { - match self { - Self::WithAnnotation { - name, - arguments, - rigids, - return_type, - body, - } => Self::WithAnnotation { - name: *name, - arguments: arguments.shallow_clone(), - rigids: *rigids, - return_type: *return_type, - body: *body, - }, - - Self::NoAnnotation { - name, - arguments, - return_var, - body, - } => Self::NoAnnotation { - name: *name, - arguments: arguments.shallow_clone(), - return_var: *return_var, - body: *body, - }, - } - } -} - -#[derive(Debug)] -pub struct Rigids { - pub names: PoolVec<(Option, Variable)>, // 8B - padding: [u8; 1], -} - -#[allow(clippy::needless_collect)] -impl Rigids { - pub fn new( - named: HashMap<&str, Variable, BuildHasherDefault>, - unnamed: HashSet>, - pool: &mut Pool, - ) -> Self { - let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool); - - let mut temp_names = Vec::new(); - - temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var))); - - temp_names.extend(unnamed.iter().map(|var| (None, *var))); - - for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) { - let poolstr = opt_name.map(|name| PoolStr::new(name, pool)); - - pool[node_id] = (poolstr, variable); - } - - Self { - names, - padding: Default::default(), - } - } - - pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> { - let named = self - .names - .iter(pool) - .filter_map(|(opt_pool_str, var)| { - if let Some(pool_str) = opt_pool_str { - Some((*pool_str, *var)) - } else { - None - } - }) - .collect::>(); - - PoolVec::new(named.into_iter(), pool) - } - - pub fn unnamed(&self, pool: &mut Pool) -> PoolVec { - let unnamed = self - .names - .iter(pool) - .filter_map(|(opt_pool_str, var)| { - if opt_pool_str.is_none() { - Some(*var) - } else { - None - } - }) - .collect::>(); - - PoolVec::new(unnamed.into_iter(), pool) - } -} - -/// This is overflow data from a Closure variant, which needs to store -/// more than 32B of total data -#[derive(Debug)] -pub struct ClosureExtra { - pub return_type: Variable, // 4B - pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B - pub closure_type: Variable, // 4B - pub closure_ext_var: Variable, // 4B -} - -#[derive(Debug)] -pub struct WhenBranch { - pub patterns: PoolVec, // 4B - pub body: ExprId, // 3B - pub guard: Option, // 4B -} - -// TODO make the inner types private? -pub type ExprId = NodeId; - -pub type DefId = NodeId; - -use RecordField::*; - -use super::parse::ASTNodeId; -impl RecordField { - pub fn get_record_field_var(&self) -> &Variable { - match self { - InvalidLabelOnly(_, var) => var, - LabelOnly(_, var, _) => var, - LabeledValue(_, var, _) => var, - } - } - - pub fn get_record_field_pool_str(&self) -> &PoolStr { - match self { - InvalidLabelOnly(pool_str, _) => pool_str, - LabelOnly(pool_str, _, _) => pool_str, - LabeledValue(pool_str, _, _) => pool_str, - } - } - - pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr { - match self { - InvalidLabelOnly(pool_str, _) => pool_str, - LabelOnly(pool_str, _, _) => pool_str, - LabeledValue(pool_str, _, _) => pool_str, - } - } - - pub fn get_record_field_val_node_id(&self) -> Option { - match self { - InvalidLabelOnly(_, _) => None, - LabelOnly(_, _, _) => None, - LabeledValue(_, _, field_val_id) => Some(*field_val_id), - } - } -} - -pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { - match node_id { - ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), - ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), - } -} - -pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { - let mut full_string = String::new(); - let expr2 = pool.get(node_id); - - expr2_to_string_helper(expr2, 0, pool, &mut full_string); - - full_string -} - -fn get_spacing(indent_level: usize) -> String { - std::iter::repeat(" ") - .take(indent_level) - .collect::>() - .join("") -} - -fn expr2_to_string_helper( - expr2: &Expr2, - indent_level: usize, - pool: &Pool, - out_string: &mut String, -) { - out_string.push_str(&get_spacing(indent_level)); - - match expr2 { - Expr2::SmallStr(arr_string) => out_string.push_str(&format!( - "{}{}{}", - "SmallStr(\"", - arr_string.as_str(), - "\")", - )), - Expr2::Str(pool_str) => { - out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",)) - } - Expr2::Blank => out_string.push_str("Blank"), - Expr2::EmptyRecord => out_string.push_str("EmptyRecord"), - Expr2::Record { record_var, fields } => { - out_string.push_str("Record:\n"); - out_string.push_str(&var_to_string(record_var, indent_level + 1)); - - out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1))); - - let mut first_child = true; - - for field in fields.iter(pool) { - if !first_child { - out_string.push_str(", ") - } else { - first_child = false; - } - - match field { - RecordField::InvalidLabelOnly(pool_str, var) => { - out_string.push_str(&format!( - "{}({}, Var({:?})", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - )); - } - RecordField::LabelOnly(pool_str, var, symbol) => { - out_string.push_str(&format!( - "{}({}, Var({:?}), Symbol({:?})", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - symbol - )); - } - RecordField::LabeledValue(pool_str, var, val_node_id) => { - out_string.push_str(&format!( - "{}({}, Var({:?}), Expr2(\n", - get_spacing(indent_level + 2), - pool_str.as_str(pool), - var, - )); - - let val_expr2 = pool.get(*val_node_id); - expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string); - out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2))); - } - } - } - - out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); - } - Expr2::List { elem_var, elems } => { - out_string.push_str("List:\n"); - out_string.push_str(&var_to_string(elem_var, indent_level + 1)); - out_string.push_str(&format!("{}elems: [\n", get_spacing(indent_level + 1))); - - let mut first_elt = true; - - for elem_expr2_id in elems.iter(pool) { - if !first_elt { - out_string.push_str(", ") - } else { - first_elt = false; - } - - let elem_expr2 = pool.get(*elem_expr2_id); - - expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string) - } - - out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1))); - } - Expr2::InvalidLookup(pool_str) => { - out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool))); - } - Expr2::SmallInt { text, .. } => { - out_string.push_str(&format!("SmallInt({})", text.as_str(pool))); - } - Expr2::LetValue { - def_id, body_id, .. - } => { - out_string.push_str(&format!( - "LetValue(def_id: >>{:?}), body_id: >>{:?})", - value_def_to_string(pool.get(*def_id), pool), - pool.get(*body_id) - )); - } - other => todo!("Implement for {:?}", other), - } - - out_string.push('\n'); -} - -pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { - let mut full_string = String::new(); - let def2 = pool.get(node_id); - - match def2 { - Def2::ValueDef { - identifier_id, - expr_id, - } => { - full_string.push_str(&format!( - "Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})", - pool.get(*identifier_id), - expr2_to_string(*expr_id, pool) - )); - } - Def2::Blank => { - full_string.push_str("Def2::Blank"); - } - } - - full_string -} - -fn var_to_string(some_var: &Variable, indent_level: usize) -> String { - format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) -} - -// get string from SmallStr or Str -pub fn get_string_from_expr2(node_id: ExprId, pool: &Pool) -> EdResult { - match pool.get(node_id) { - Expr2::SmallStr(arr_string) => Ok(arr_string.as_str().to_string()), - Expr2::Str(pool_str) => Ok(pool_str.as_str(pool).to_owned()), - other => UnexpectedASTNode { - required_node_type: "SmallStr or Str", - encountered_node_type: format!("{:?}", other), - } - .fail()?, - } -} - -pub fn update_str_expr( - node_id: ExprId, - new_char: char, - insert_index: usize, - pool: &mut Pool, -) -> EdResult<()> { - let str_expr = pool.get_mut(node_id); - - enum Either { - MyString(String), - MyPoolStr(PoolStr), - Done, - } - - let insert_either = match str_expr { - Expr2::SmallStr(arr_string) => { - let insert_res = arr_string.try_insert(insert_index as u8, new_char); - - match insert_res { - Ok(_) => Either::Done, - _ => { - let mut new_string = arr_string.as_str().to_string(); - new_string.insert(insert_index, new_char); - - Either::MyString(new_string) - } - } - } - Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str), - other => UnexpectedASTNode { - required_node_type: "SmallStr or Str", - encountered_node_type: format!("{:?}", other), - } - .fail()?, - }; - - match insert_either { - Either::MyString(new_string) => { - let new_pool_str = PoolStr::new(&new_string, pool); - - pool.set(node_id, Expr2::Str(new_pool_str)) - } - Either::MyPoolStr(old_pool_str) => { - let mut new_string = old_pool_str.as_str(pool).to_owned(); - - new_string.insert(insert_index, new_char); - - let new_pool_str = PoolStr::new(&new_string, pool); - - pool.set(node_id, Expr2::Str(new_pool_str)) - } - Either::Done => (), - } - - Ok(()) -} - -#[test] -fn size_of_expr() { - assert_eq!(std::mem::size_of::(), crate::lang::pool::NODE_BYTES); -} - -impl ShallowClone for Rigids { - fn shallow_clone(&self) -> Self { - Self { - names: self.names.shallow_clone(), - padding: self.padding, - } - } -} diff --git a/editor/src/lang/constrain.rs b/editor/src/lang/constrain.rs deleted file mode 100644 index 77d4edb0cd..0000000000 --- a/editor/src/lang/constrain.rs +++ /dev/null @@ -1,1746 +0,0 @@ -use bumpalo::{collections::Vec as BumpVec, Bump}; - -use crate::lang::{ - ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch}, - expr::Env, - pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, - pool::{Pool, PoolStr, PoolVec, ShallowClone}, - types::{Type2, TypeId}, -}; - -use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; -use roc_module::{ - ident::{Lowercase, TagName}, - symbol::Symbol, -}; -use roc_region::all::Region; -use roc_types::{ - subs::Variable, - types::{self, AnnotationSource, PReason, PatternCategory}, - types::{Category, Reason}, -}; - -#[derive(Debug)] -pub enum Constraint<'a> { - Eq(Type2, Expected, Category, Region), - // Store(Type, Variable, &'static str, u32), - Lookup(Symbol, Expected, Region), - Pattern(Region, PatternCategory, Type2, PExpected), - And(BumpVec<'a, Constraint<'a>>), - Let(&'a LetConstraint<'a>), - // SaveTheEnvironment, - True, // Used for things that always unify, e.g. blanks and runtime errors -} - -#[derive(Debug)] -pub struct LetConstraint<'a> { - pub rigid_vars: BumpVec<'a, Variable>, - pub flex_vars: BumpVec<'a, Variable>, - pub def_types: BumpMap, - pub defs_constraint: Constraint<'a>, - pub ret_constraint: Constraint<'a>, -} - -pub fn constrain_expr<'a>( - arena: &'a Bump, - env: &mut Env, - expr: &Expr2, - expected: Expected, - region: Region, -) -> Constraint<'a> { - use Constraint::*; - - match expr { - Expr2::Blank | Expr2::RuntimeError() | Expr2::InvalidLookup(_) => True, - Expr2::Str(_) => Eq(str_type(env.pool), expected, Category::Str, region), - Expr2::SmallStr(_) => Eq(str_type(env.pool), expected, Category::Str, region), - Expr2::Var(symbol) => Lookup(*symbol, expected, region), - Expr2::EmptyRecord => constrain_empty_record(expected, region), - Expr2::SmallInt { var, .. } | Expr2::I128 { var, .. } | Expr2::U128 { var, .. } => { - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - - flex_vars.push(*var); - - let precision_var = env.var_store.fresh(); - - let range_type = Type2::Variable(precision_var); - - let range_type_id = env.pool.add(range_type); - - exists( - arena, - flex_vars, - Eq( - num_num(env.pool, range_type_id), - expected, - Category::Num, - region, - ), - ) - } - Expr2::Float { var, .. } => { - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - let num_type = Type2::Variable(*var); - - flex_vars.push(*var); - - let precision_var = env.var_store.fresh(); - - let range_type = Type2::Variable(precision_var); - - let range_type_id = env.pool.add(range_type); - - and_constraints.push(Eq( - num_type.shallow_clone(), - Expected::ForReason( - Reason::FloatLiteral, - num_float(env.pool, range_type_id), - region, - ), - Category::Int, - region, - )); - - and_constraints.push(Eq(num_type, expected, Category::Float, region)); - - let defs_constraint = And(and_constraints); - - exists(arena, flex_vars, defs_constraint) - } - Expr2::List { - elem_var, elems, .. - } => { - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - - flex_vars.push(*elem_var); - - if elems.is_empty() { - exists( - arena, - flex_vars, - Eq( - empty_list_type(env.pool, *elem_var), - expected, - Category::List, - region, - ), - ) - } else { - let mut constraints = BumpVec::with_capacity_in(1 + elems.len(), arena); - - let list_elem_type = Type2::Variable(*elem_var); - - let indexed_node_ids: Vec<(usize, ExprId)> = - elems.iter(env.pool).copied().enumerate().collect(); - - for (index, elem_node_id) in indexed_node_ids { - let elem_expr = env.pool.get(elem_node_id); - - let elem_expected = Expected::ForReason( - Reason::ElemInList { - index: Index::zero_based(index), - }, - list_elem_type.shallow_clone(), - region, - ); - - let constraint = constrain_expr(arena, env, elem_expr, elem_expected, region); - - constraints.push(constraint); - } - - constraints.push(Eq( - list_type(env.pool, list_elem_type), - expected, - Category::List, - region, - )); - - exists(arena, flex_vars, And(constraints)) - } - } - Expr2::Record { fields, record_var } => { - if fields.is_empty() { - constrain_empty_record(expected, region) - } else { - let field_types = PoolVec::with_capacity(fields.len() as u32, env.pool); - - let mut field_vars = BumpVec::with_capacity_in(fields.len(), arena); - - // Constraints need capacity for each field - // + 1 for the record itself + 1 for record var - let mut constraints = BumpVec::with_capacity_in(2 + fields.len(), arena); - - for (record_field_node_id, field_type_node_id) in - fields.iter_node_ids().zip(field_types.iter_node_ids()) - { - let record_field = env.pool.get(record_field_node_id); - - match record_field { - RecordField::LabeledValue(pool_str, var, node_id) => { - let expr = env.pool.get(*node_id); - - let (field_type, field_con) = constrain_field(arena, env, *var, expr); - - field_vars.push(*var); - - let field_type_id = env.pool.add(field_type); - - env.pool[field_type_node_id] = - (*pool_str, types::RecordField::Required(field_type_id)); - - constraints.push(field_con); - } - e => todo!("{:?}", e), - } - } - - let record_type = Type2::Record(field_types, env.pool.add(Type2::EmptyRec)); - - let record_con = Eq( - record_type, - expected.shallow_clone(), - Category::Record, - region, - ); - - constraints.push(record_con); - - // variable to store in the AST - let stored_con = Eq( - Type2::Variable(*record_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - field_vars.push(*record_var); - constraints.push(stored_con); - - exists(arena, field_vars, And(constraints)) - } - } - Expr2::GlobalTag { - variant_var, - ext_var, - name, - arguments, - } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag( - arena, - env, - expected, - region, - tag_name, - arguments, - *ext_var, - *variant_var, - ) - } - Expr2::PrivateTag { - name, - arguments, - ext_var, - variant_var, - } => { - let tag_name = TagName::Private(*name); - - constrain_tag( - arena, - env, - expected, - region, - tag_name, - arguments, - *ext_var, - *variant_var, - ) - } - Expr2::Call { - args, - expr_var, - expr: expr_node_id, - closure_var, - fn_var, - .. - } => { - // The expression that evaluates to the function being called, e.g. `foo` in - // (foo) bar baz - let call_expr = env.pool.get(*expr_node_id); - - let opt_symbol = if let Expr2::Var(symbol) = call_expr { - Some(*symbol) - } else { - None - }; - - let fn_type = Type2::Variable(*fn_var); - let fn_region = region; - let fn_expected = Expected::NoExpectation(fn_type.shallow_clone()); - - let fn_reason = Reason::FnCall { - name: opt_symbol, - arity: args.len() as u8, - }; - - let fn_con = constrain_expr(arena, env, call_expr, fn_expected, region); - - // The function's return type - // TODO: don't use expr_var? - let ret_type = Type2::Variable(*expr_var); - - // type of values captured in the closure - let closure_type = Type2::Variable(*closure_var); - - // This will be used in the occurs check - let mut vars = BumpVec::with_capacity_in(2 + args.len(), arena); - - vars.push(*fn_var); - // TODO: don't use expr_var? - vars.push(*expr_var); - vars.push(*closure_var); - - let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); - let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); - - for (index, arg_node_id) in args.iter_node_ids().enumerate() { - let (arg_var, arg) = env.pool.get(arg_node_id); - let arg_expr = env.pool.get(*arg); - - let region = region; - let arg_type = Type2::Variable(*arg_var); - - let reason = Reason::FnArg { - name: opt_symbol, - arg_index: Index::zero_based(index), - }; - - let expected_arg = Expected::ForReason(reason, arg_type.shallow_clone(), region); - - let arg_con = constrain_expr(arena, env, arg_expr, expected_arg, region); - - vars.push(*arg_var); - arg_types.push(arg_type); - arg_cons.push(arg_con); - } - - let expected_fn_type = Expected::ForReason( - fn_reason, - Type2::Function( - PoolVec::new(arg_types.into_iter(), env.pool), - env.pool.add(closure_type), - env.pool.add(ret_type.shallow_clone()), - ), - region, - ); - - let category = Category::CallResult(opt_symbol); - - let mut and_constraints = BumpVec::with_capacity_in(4, arena); - - and_constraints.push(fn_con); - and_constraints.push(Eq(fn_type, expected_fn_type, category.clone(), fn_region)); - and_constraints.push(And(arg_cons)); - and_constraints.push(Eq(ret_type, expected, category, region)); - - exists(arena, vars, And(and_constraints)) - } - Expr2::Accessor { - function_var, - closure_var, - field, - record_var, - ext_var, - field_var, - } => { - let ext_var = *ext_var; - let ext_type = Type2::Variable(ext_var); - - let field_var = *field_var; - let field_type = Type2::Variable(field_var); - - let record_field = - types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); - - let record_type = Type2::Record( - PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), - env.pool.add(ext_type), - ); - - let category = Category::Accessor(field.as_str(env.pool).into()); - - let record_expected = Expected::NoExpectation(record_type.shallow_clone()); - let record_con = Eq( - Type2::Variable(*record_var), - record_expected, - category.clone(), - region, - ); - - let function_type = Type2::Function( - PoolVec::new(vec![record_type].into_iter(), env.pool), - env.pool.add(Type2::Variable(*closure_var)), - env.pool.add(field_type), - ); - - let mut flex_vars = BumpVec::with_capacity_in(5, arena); - - flex_vars.push(*record_var); - flex_vars.push(*function_var); - flex_vars.push(*closure_var); - flex_vars.push(field_var); - flex_vars.push(ext_var); - - let mut and_constraints = BumpVec::with_capacity_in(3, arena); - - and_constraints.push(Eq( - function_type.shallow_clone(), - expected, - category.clone(), - region, - )); - - and_constraints.push(Eq( - function_type, - Expected::NoExpectation(Type2::Variable(*function_var)), - category, - region, - )); - - and_constraints.push(record_con); - - exists(arena, flex_vars, And(and_constraints)) - } - Expr2::Access { - expr: expr_id, - field, - field_var, - record_var, - ext_var, - } => { - let ext_type = Type2::Variable(*ext_var); - - let field_type = Type2::Variable(*field_var); - - let record_field = - types::RecordField::Demanded(env.pool.add(field_type.shallow_clone())); - - let record_type = Type2::Record( - PoolVec::new(vec![(*field, record_field)].into_iter(), env.pool), - env.pool.add(ext_type), - ); - - let record_expected = Expected::NoExpectation(record_type); - - let category = Category::Access(field.as_str(env.pool).into()); - - let record_con = Eq( - Type2::Variable(*record_var), - record_expected.shallow_clone(), - category.clone(), - region, - ); - - let access_expr = env.pool.get(*expr_id); - - let constraint = constrain_expr(arena, env, access_expr, record_expected, region); - - let mut flex_vars = BumpVec::with_capacity_in(3, arena); - - flex_vars.push(*record_var); - flex_vars.push(*field_var); - flex_vars.push(*ext_var); - - let mut and_constraints = BumpVec::with_capacity_in(3, arena); - - and_constraints.push(constraint); - and_constraints.push(Eq(field_type, expected, category, region)); - and_constraints.push(record_con); - - exists(arena, flex_vars, And(and_constraints)) - } - Expr2::If { - cond_var, - expr_var, - branches, - final_else, - } => { - let expect_bool = |region| { - let bool_type = Type2::Variable(Variable::BOOL); - Expected::ForReason(Reason::IfCondition, bool_type, region) - }; - - let mut branch_cons = BumpVec::with_capacity_in(2 * branches.len() + 3, arena); - - // TODO why does this cond var exist? is it for error messages? - // let first_cond_region = branches[0].0.region; - let cond_var_is_bool_con = Eq( - Type2::Variable(*cond_var), - expect_bool(region), - Category::If, - region, - ); - - branch_cons.push(cond_var_is_bool_con); - - let final_else_expr = env.pool.get(*final_else); - - let mut flex_vars = BumpVec::with_capacity_in(2, arena); - - flex_vars.push(*cond_var); - flex_vars.push(*expr_var); - - match expected { - Expected::FromAnnotation(name, arity, _, tipe) => { - let num_branches = branches.len() + 1; - - for (index, branch_id) in branches.iter_node_ids().enumerate() { - let (cond_id, body_id) = env.pool.get(branch_id); - - let cond = env.pool.get(*cond_id); - let body = env.pool.get(*body_id); - - let cond_con = - constrain_expr(arena, env, cond, expect_bool(region), region); - - let then_con = constrain_expr( - arena, - env, - body, - Expected::FromAnnotation( - name.clone(), - arity, - AnnotationSource::TypedIfBranch { - index: Index::zero_based(index), - num_branches, - }, - tipe.shallow_clone(), - ), - region, - ); - - branch_cons.push(cond_con); - branch_cons.push(then_con); - } - - let else_con = constrain_expr( - arena, - env, - final_else_expr, - Expected::FromAnnotation( - name, - arity, - AnnotationSource::TypedIfBranch { - index: Index::zero_based(branches.len()), - num_branches, - }, - tipe.shallow_clone(), - ), - region, - ); - - let ast_con = Eq( - Type2::Variable(*expr_var), - Expected::NoExpectation(tipe), - Category::Storage(std::file!(), std::line!()), - region, - ); - - branch_cons.push(ast_con); - branch_cons.push(else_con); - - exists(arena, flex_vars, And(branch_cons)) - } - _ => { - for (index, branch_id) in branches.iter_node_ids().enumerate() { - let (cond_id, body_id) = env.pool.get(branch_id); - - let cond = env.pool.get(*cond_id); - let body = env.pool.get(*body_id); - - let cond_con = - constrain_expr(arena, env, cond, expect_bool(region), region); - - let then_con = constrain_expr( - arena, - env, - body, - Expected::ForReason( - Reason::IfBranch { - index: Index::zero_based(index), - total_branches: branches.len(), - }, - Type2::Variable(*expr_var), - // should be from body - region, - ), - region, - ); - - branch_cons.push(cond_con); - branch_cons.push(then_con); - } - - let else_con = constrain_expr( - arena, - env, - final_else_expr, - Expected::ForReason( - Reason::IfBranch { - index: Index::zero_based(branches.len()), - total_branches: branches.len() + 1, - }, - Type2::Variable(*expr_var), - // should come from final_else - region, - ), - region, - ); - - branch_cons.push(Eq( - Type2::Variable(*expr_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - )); - - branch_cons.push(else_con); - - exists(arena, flex_vars, And(branch_cons)) - } - } - } - Expr2::When { - cond_var, - expr_var, - cond: cond_id, - branches, - } => { - // Infer the condition expression's type. - let cond_type = Type2::Variable(*cond_var); - - let cond = env.pool.get(*cond_id); - - let expr_con = constrain_expr( - arena, - env, - cond, - Expected::NoExpectation(cond_type.shallow_clone()), - region, - ); - - let mut constraints = BumpVec::with_capacity_in(branches.len() + 1, arena); - - constraints.push(expr_con); - - let mut flex_vars = BumpVec::with_capacity_in(2, arena); - - flex_vars.push(*cond_var); - flex_vars.push(*expr_var); - - match &expected { - Expected::FromAnnotation(name, arity, _, _typ) => { - // NOTE deviation from elm. - // - // in elm, `_typ` is used, but because we have this `expr_var` too - // and need to constrain it, this is what works and gives better error messages - let typ = Type2::Variable(*expr_var); - - for (index, when_branch_id) in branches.iter_node_ids().enumerate() { - let when_branch = env.pool.get(when_branch_id); - - let pattern_region = region; - // let pattern_region = Region::across_all( - // when_branch.patterns.iter(env.pool).map(|v| &v.region), - // ); - - let branch_con = constrain_when_branch( - arena, - env, - // TODO: when_branch.value.region, - region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: Index::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), - Expected::FromAnnotation( - name.clone(), - *arity, - AnnotationSource::TypedWhenBranch { - index: Index::zero_based(index), - }, - typ.shallow_clone(), - ), - ); - - constraints.push(branch_con); - } - - constraints.push(Eq(typ, expected, Category::When, region)); - - return exists(arena, flex_vars, And(constraints)); - } - - _ => { - let branch_type = Type2::Variable(*expr_var); - let mut branch_cons = BumpVec::with_capacity_in(branches.len(), arena); - - for (index, when_branch_id) in branches.iter_node_ids().enumerate() { - let when_branch = env.pool.get(when_branch_id); - - let pattern_region = region; - // let pattern_region = - // Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); - - let branch_con = constrain_when_branch( - arena, - env, - region, - when_branch, - PExpected::ForReason( - PReason::WhenMatch { - index: Index::zero_based(index), - }, - cond_type.shallow_clone(), - pattern_region, - ), - Expected::ForReason( - Reason::WhenBranch { - index: Index::zero_based(index), - }, - branch_type.shallow_clone(), - // TODO: when_branch.value.region, - region, - ), - ); - - branch_cons.push(branch_con); - } - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - and_constraints.push(And(branch_cons)); - and_constraints.push(Eq(branch_type, expected, Category::When, region)); - - constraints.push(And(and_constraints)); - } - } - - // exhautiveness checking happens when converting to mono::Expr - exists(arena, flex_vars, And(constraints)) - } - Expr2::LetValue { - def_id, - body_id, - body_var, - } => { - let value_def = env.pool.get(*def_id); - let body = env.pool.get(*body_id); - - let body_con = constrain_expr(arena, env, body, expected.shallow_clone(), region); - - match value_def { - ValueDef::WithAnnotation { .. } => todo!("implement {:?}", value_def), - ValueDef::NoAnnotation { - pattern_id, - expr_id, - expr_var, - } => { - let pattern = env.pool.get(*pattern_id); - - let mut flex_vars = BumpVec::with_capacity_in(1, arena); - flex_vars.push(*body_var); - - let expr_type = Type2::Variable(*expr_var); - - let pattern_expected = PExpected::NoExpectation(expr_type.shallow_clone()); - let mut state = PatternState2 { - headers: BumpMap::new_in(arena), - vars: BumpVec::with_capacity_in(1, arena), - constraints: BumpVec::with_capacity_in(1, arena), - }; - - constrain_pattern(arena, env, pattern, region, pattern_expected, &mut state); - state.vars.push(*expr_var); - - let def_expr = env.pool.get(*expr_id); - - let constrained_def = Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), // always empty - flex_vars: BumpVec::new_in(arena), // empty, because our functions have no arguments - def_types: BumpMap::new_in(arena), // empty, because our functions have no arguments! - defs_constraint: And(state.constraints), - ret_constraint: constrain_expr( - arena, - env, - def_expr, - Expected::NoExpectation(expr_type), - region, - ), - })), - ret_constraint: body_con, - })); - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - and_constraints.push(constrained_def); - and_constraints.push(Eq( - Type2::Variable(*body_var), - expected, - Category::Storage(std::file!(), std::line!()), - // TODO: needs to be ret region - region, - )); - - exists(arena, flex_vars, And(and_constraints)) - } - } - } - Expr2::Update { - symbol, - updates, - ext_var, - record_var, - } => { - let field_types = PoolVec::with_capacity(updates.len() as u32, env.pool); - let mut flex_vars = BumpVec::with_capacity_in(updates.len() + 2, arena); - let mut cons = BumpVec::with_capacity_in(updates.len() + 1, arena); - let mut record_key_updates = SendMap::default(); - - for (record_field_id, field_type_node_id) in - updates.iter_node_ids().zip(field_types.iter_node_ids()) - { - let record_field = env.pool.get(record_field_id); - - match record_field { - RecordField::LabeledValue(pool_str, var, node_id) => { - let expr = env.pool.get(*node_id); - - let (field_type, field_con) = constrain_field_update( - arena, - env, - *var, - pool_str.as_str(env.pool).into(), - expr, - ); - - let field_type_id = env.pool.add(field_type); - - env.pool[field_type_node_id] = - (*pool_str, types::RecordField::Required(field_type_id)); - - record_key_updates.insert(pool_str.as_str(env.pool).into(), Region::zero()); - - flex_vars.push(*var); - cons.push(field_con); - } - e => todo!("{:?}", e), - } - } - - let fields_type = Type2::Record(field_types, env.pool.add(Type2::Variable(*ext_var))); - let record_type = Type2::Variable(*record_var); - - // NOTE from elm compiler: fields_type is separate so that Error propagates better - let fields_con = Eq( - record_type.shallow_clone(), - Expected::NoExpectation(fields_type), - Category::Record, - region, - ); - let record_con = Eq( - record_type.shallow_clone(), - expected, - Category::Record, - region, - ); - - flex_vars.push(*record_var); - flex_vars.push(*ext_var); - - let con = Lookup( - *symbol, - Expected::ForReason( - Reason::RecordUpdateKeys(*symbol, record_key_updates), - record_type, - region, - ), - region, - ); - - // ensure constraints are solved in this order, gives better errors - cons.insert(0, fields_con); - cons.insert(1, con); - cons.insert(2, record_con); - - exists(arena, flex_vars, And(cons)) - } - - Expr2::RunLowLevel { op, args, ret_var } => { - // This is a modified version of what we do for function calls. - - // The operation's return type - let ret_type = Type2::Variable(*ret_var); - - // This will be used in the occurs check - let mut vars = BumpVec::with_capacity_in(1 + args.len(), arena); - - vars.push(*ret_var); - - let mut arg_types = BumpVec::with_capacity_in(args.len(), arena); - let mut arg_cons = BumpVec::with_capacity_in(args.len(), arena); - - for (index, node_id) in args.iter_node_ids().enumerate() { - let (arg_var, arg_id) = env.pool.get(node_id); - - vars.push(*arg_var); - - let arg_type = Type2::Variable(*arg_var); - - let reason = Reason::LowLevelOpArg { - op: *op, - arg_index: Index::zero_based(index), - }; - let expected_arg = - Expected::ForReason(reason, arg_type.shallow_clone(), Region::zero()); - let arg = env.pool.get(*arg_id); - - let arg_con = constrain_expr(arena, env, arg, expected_arg, Region::zero()); - - arg_types.push(arg_type); - arg_cons.push(arg_con); - } - - let category = Category::LowLevelOpResult(*op); - - let mut and_constraints = BumpVec::with_capacity_in(2, arena); - - and_constraints.push(And(arg_cons)); - and_constraints.push(Eq(ret_type, expected, category, region)); - - exists(arena, vars, And(and_constraints)) - } - Expr2::Closure { - args, - name, - body: body_id, - function_type: fn_var, - extra, - .. - } => { - // NOTE defs are treated somewhere else! - let body = env.pool.get(*body_id); - - let ClosureExtra { - captured_symbols, - return_type: ret_var, - closure_type: closure_var, - closure_ext_var, - } = env.pool.get(*extra); - - let closure_type = Type2::Variable(*closure_var); - let return_type = Type2::Variable(*ret_var); - - let (mut vars, pattern_state, function_type) = - constrain_untyped_args(arena, env, args, closure_type, return_type.shallow_clone()); - - vars.push(*ret_var); - vars.push(*closure_var); - vars.push(*closure_ext_var); - vars.push(*fn_var); - - let expected_body_type = Expected::NoExpectation(return_type); - // Region here should come from body expr - let ret_constraint = constrain_expr(arena, env, body, expected_body_type, region); - - let captured_symbols_as_vec = captured_symbols - .iter(env.pool) - .copied() - .collect::>(); - - // make sure the captured symbols are sorted! - debug_assert_eq!(captured_symbols_as_vec, { - let mut copy: Vec<(Symbol, Variable)> = captured_symbols_as_vec.clone(); - - copy.sort(); - - copy - }); - - let closure_constraint = constrain_closure_size( - arena, - env, - *name, - region, - captured_symbols, - *closure_var, - *closure_ext_var, - &mut vars, - ); - - let mut and_constraints = BumpVec::with_capacity_in(4, arena); - - and_constraints.push(Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: pattern_state.vars, - def_types: pattern_state.headers, - defs_constraint: And(pattern_state.constraints), - ret_constraint, - }))); - - // "the closure's type is equal to expected type" - and_constraints.push(Eq( - function_type.shallow_clone(), - expected, - Category::Lambda, - region, - )); - - // "fn_var is equal to the closure's type" - fn_var is used in code gen - and_constraints.push(Eq( - Type2::Variable(*fn_var), - Expected::NoExpectation(function_type), - Category::Storage(std::file!(), std::line!()), - region, - )); - - and_constraints.push(closure_constraint); - - exists(arena, vars, And(and_constraints)) - } - Expr2::LetRec { .. } => todo!(), - Expr2::LetFunction { .. } => todo!(), - } -} - -fn exists<'a>( - arena: &'a Bump, - flex_vars: BumpVec<'a, Variable>, - defs_constraint: Constraint<'a>, -) -> Constraint<'a> { - Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars, - def_types: BumpMap::new_in(arena), - defs_constraint, - ret_constraint: Constraint::True, - })) -} - -#[allow(clippy::too_many_arguments)] -fn constrain_tag<'a>( - arena: &'a Bump, - env: &mut Env, - expected: Expected, - region: Region, - tag_name: TagName, - arguments: &PoolVec<(Variable, ExprId)>, - ext_var: Variable, - variant_var: Variable, -) -> Constraint<'a> { - use Constraint::*; - - let mut flex_vars = BumpVec::with_capacity_in(arguments.len(), arena); - let types = PoolVec::with_capacity(arguments.len() as u32, env.pool); - let mut arg_cons = BumpVec::with_capacity_in(arguments.len(), arena); - - for (argument_node_id, type_node_id) in arguments.iter_node_ids().zip(types.iter_node_ids()) { - let (var, expr_node_id) = env.pool.get(argument_node_id); - - let argument_expr = env.pool.get(*expr_node_id); - - let arg_con = constrain_expr( - arena, - env, - argument_expr, - Expected::NoExpectation(Type2::Variable(*var)), - region, - ); - - arg_cons.push(arg_con); - flex_vars.push(*var); - - env.pool[type_node_id] = Type2::Variable(*var); - } - - let union_con = Eq( - Type2::TagUnion( - PoolVec::new(std::iter::once((tag_name.clone(), types)), env.pool), - env.pool.add(Type2::Variable(ext_var)), - ), - expected.shallow_clone(), - Category::TagApply { - tag_name, - args_count: arguments.len(), - }, - region, - ); - - let ast_con = Eq( - Type2::Variable(variant_var), - expected, - Category::Storage(std::file!(), std::line!()), - region, - ); - - flex_vars.push(variant_var); - flex_vars.push(ext_var); - - arg_cons.push(union_con); - arg_cons.push(ast_con); - - exists(arena, flex_vars, And(arg_cons)) -} - -fn constrain_field<'a>( - arena: &'a Bump, - env: &mut Env, - field_var: Variable, - expr: &Expr2, -) -> (Type2, Constraint<'a>) { - let field_type = Type2::Variable(field_var); - let field_expected = Expected::NoExpectation(field_type.shallow_clone()); - let constraint = constrain_expr(arena, env, expr, field_expected, Region::zero()); - - (field_type, constraint) -} - -#[inline(always)] -fn constrain_field_update<'a>( - arena: &'a Bump, - env: &mut Env, - field_var: Variable, - field: Lowercase, - expr: &Expr2, -) -> (Type2, Constraint<'a>) { - let field_type = Type2::Variable(field_var); - let reason = Reason::RecordUpdateValue(field); - let field_expected = Expected::ForReason(reason, field_type.shallow_clone(), Region::zero()); - let con = constrain_expr(arena, env, expr, field_expected, Region::zero()); - - (field_type, con) -} - -fn constrain_empty_record<'a>(expected: Expected, region: Region) -> Constraint<'a> { - Constraint::Eq(Type2::EmptyRec, expected, Category::Record, region) -} - -#[inline(always)] -fn constrain_when_branch<'a>( - arena: &'a Bump, - env: &mut Env, - region: Region, - when_branch: &WhenBranch, - pattern_expected: PExpected, - expr_expected: Expected, -) -> Constraint<'a> { - let when_expr = env.pool.get(when_branch.body); - - let ret_constraint = constrain_expr(arena, env, when_expr, expr_expected, region); - - let mut state = PatternState2 { - headers: BumpMap::new_in(arena), - vars: BumpVec::with_capacity_in(1, arena), - constraints: BumpVec::with_capacity_in(1, arena), - }; - - // TODO investigate for error messages, is it better to unify all branches with a variable, - // then unify that variable with the expectation? - for pattern_id in when_branch.patterns.iter_node_ids() { - let pattern = env.pool.get(pattern_id); - - constrain_pattern( - arena, - env, - pattern, - // loc_pattern.region, - region, - pattern_expected.shallow_clone(), - &mut state, - ); - } - - if let Some(guard_id) = &when_branch.guard { - let guard = env.pool.get(*guard_id); - - let guard_constraint = constrain_expr( - arena, - env, - guard, - Expected::ForReason( - Reason::WhenGuard, - Type2::Variable(Variable::BOOL), - // TODO: loc_guard.region, - region, - ), - region, - ); - - // must introduce the headers from the pattern before constraining the guard - Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), - ret_constraint: Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: BumpVec::new_in(arena), - def_types: BumpMap::new_in(arena), - defs_constraint: guard_constraint, - ret_constraint, - })), - })) - } else { - Constraint::Let(arena.alloc(LetConstraint { - rigid_vars: BumpVec::new_in(arena), - flex_vars: state.vars, - def_types: state.headers, - defs_constraint: Constraint::And(state.constraints), - ret_constraint, - })) - } -} - -/// This accepts PatternState (rather than returning it) so that the caller can -/// initialize the Vecs in PatternState using with_capacity -/// based on its knowledge of their lengths. -pub fn constrain_pattern<'a>( - arena: &'a Bump, - env: &mut Env, - pattern: &Pattern2, - region: Region, - expected: PExpected, - state: &mut PatternState2<'a>, -) { - use Pattern2::*; - - match pattern { - Underscore | UnsupportedPattern(_) | MalformedPattern(_, _) | Shadowed { .. } => { - // Neither the _ pattern nor erroneous ones add any constraints. - } - - Identifier(symbol) => { - state.headers.insert(*symbol, expected.get_type()); - } - - NumLiteral(var, _) => { - state.vars.push(*var); - - let type_id = env.pool.add(Type2::Variable(*var)); - - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Num, - num_num(env.pool, type_id), - expected, - )); - } - - IntLiteral(_int_val) => { - let precision_var = env.var_store.fresh(); - - let range = env.add(Type2::Variable(precision_var), region); - - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Int, - num_int(env.pool, range), - expected, - )); - } - - FloatLiteral(_float_val) => { - let precision_var = env.var_store.fresh(); - - let range = env.add(Type2::Variable(precision_var), region); - - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Float, - num_float(env.pool, range), - expected, - )); - } - - StrLiteral(_) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::Str, - str_type(env.pool), - expected, - )); - } - - RecordDestructure { - whole_var, - ext_var, - destructs, - } => { - state.vars.push(*whole_var); - state.vars.push(*ext_var); - let ext_type = Type2::Variable(*ext_var); - - let mut field_types = Vec::new(); - - for destruct_id in destructs.iter_node_ids() { - let RecordDestruct { - var, - label, - symbol, - typ, - } = env.pool.get(destruct_id); - - let pat_type = Type2::Variable(*var); - let expected = PExpected::NoExpectation(pat_type.shallow_clone()); - - if !state.headers.contains_key(symbol) { - state.headers.insert(*symbol, pat_type.shallow_clone()); - } - - let destruct_type = env.pool.get(*typ); - - let field_type = match destruct_type { - DestructType::Guard(guard_var, guard_id) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::PatternGuard, - Type2::Variable(*guard_var), - PExpected::ForReason( - PReason::PatternGuard, - pat_type.shallow_clone(), - // TODO: region should be from guard_id - region, - ), - )); - - state.vars.push(*guard_var); - - let guard = env.pool.get(*guard_id); - - // TODO: region should be from guard_id - constrain_pattern(arena, env, guard, region, expected, state); - - types::RecordField::Demanded(env.pool.add(pat_type)) - } - DestructType::Optional(expr_var, expr_id) => { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::PatternDefault, - Type2::Variable(*expr_var), - PExpected::ForReason( - PReason::OptionalField, - pat_type.shallow_clone(), - // TODO: region should be from expr_id - region, - ), - )); - - state.vars.push(*expr_var); - - let expr_expected = Expected::ForReason( - Reason::RecordDefaultField(label.as_str(env.pool).into()), - pat_type.shallow_clone(), - // TODO: region should be from expr_id - region, - ); - - let expr = env.pool.get(*expr_id); - - // TODO: region should be from expr_id - let expr_con = constrain_expr(arena, env, expr, expr_expected, region); - - state.constraints.push(expr_con); - - types::RecordField::Optional(env.pool.add(pat_type)) - } - DestructType::Required => { - // No extra constraints necessary. - types::RecordField::Demanded(env.pool.add(pat_type)) - } - }; - - field_types.push((*label, field_type)); - - state.vars.push(*var); - } - - let record_type = Type2::Record( - PoolVec::new(field_types.into_iter(), env.pool), - env.pool.add(ext_type), - ); - - let whole_con = Constraint::Eq( - Type2::Variable(*whole_var), - Expected::NoExpectation(record_type), - Category::Storage(std::file!(), std::line!()), - region, - ); - - let record_con = Constraint::Pattern( - region, - PatternCategory::Record, - Type2::Variable(*whole_var), - expected, - ); - - state.constraints.push(whole_con); - state.constraints.push(record_con); - } - GlobalTag { - whole_var, - ext_var, - tag_name: name, - arguments, - } => { - let tag_name = TagName::Global(name.as_str(env.pool).into()); - - constrain_tag_pattern( - arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, - ); - } - PrivateTag { - whole_var, - ext_var, - tag_name: name, - arguments, - } => { - let tag_name = TagName::Private(*name); - - constrain_tag_pattern( - arena, env, region, expected, state, *whole_var, *ext_var, arguments, tag_name, - ); - } - } -} - -#[allow(clippy::too_many_arguments)] -fn constrain_tag_pattern<'a>( - arena: &'a Bump, - env: &mut Env, - region: Region, - expected: PExpected, - state: &mut PatternState2<'a>, - whole_var: Variable, - ext_var: Variable, - arguments: &PoolVec<(Variable, PatternId)>, - tag_name: TagName, -) { - let mut argument_types = Vec::with_capacity(arguments.len()); - - for (index, arg_id) in arguments.iter_node_ids().enumerate() { - let (pattern_var, pattern_id) = env.pool.get(arg_id); - let pattern = env.pool.get(*pattern_id); - - state.vars.push(*pattern_var); - - let pattern_type = Type2::Variable(*pattern_var); - argument_types.push(pattern_type.shallow_clone()); - - let expected = PExpected::ForReason( - PReason::TagArg { - tag_name: tag_name.clone(), - index: Index::zero_based(index), - }, - pattern_type, - region, - ); - - // TODO region should come from pattern - constrain_pattern(arena, env, pattern, region, expected, state); - } - - let whole_con = Constraint::Eq( - Type2::Variable(whole_var), - Expected::NoExpectation(Type2::TagUnion( - PoolVec::new( - vec![( - tag_name.clone(), - PoolVec::new(argument_types.into_iter(), env.pool), - )] - .into_iter(), - env.pool, - ), - env.pool.add(Type2::Variable(ext_var)), - )), - Category::Storage(std::file!(), std::line!()), - region, - ); - - let tag_con = Constraint::Pattern( - region, - PatternCategory::Ctor(tag_name), - Type2::Variable(whole_var), - expected, - ); - - state.vars.push(whole_var); - state.vars.push(ext_var); - state.constraints.push(whole_con); - state.constraints.push(tag_con); -} - -fn constrain_untyped_args<'a>( - arena: &'a Bump, - env: &mut Env, - arguments: &PoolVec<(Variable, PatternId)>, - closure_type: Type2, - return_type: Type2, -) -> (BumpVec<'a, Variable>, PatternState2<'a>, Type2) { - let mut vars = BumpVec::with_capacity_in(arguments.len(), arena); - - let pattern_types = PoolVec::with_capacity(arguments.len() as u32, env.pool); - - let mut pattern_state = PatternState2 { - headers: BumpMap::new_in(arena), - vars: BumpVec::with_capacity_in(1, arena), - constraints: BumpVec::with_capacity_in(1, arena), - }; - - for (arg_node_id, pattern_type_id) in - arguments.iter_node_ids().zip(pattern_types.iter_node_ids()) - { - let (pattern_var, pattern_id) = env.pool.get(arg_node_id); - let pattern = env.pool.get(*pattern_id); - - let pattern_type = Type2::Variable(*pattern_var); - let pattern_expected = PExpected::NoExpectation(pattern_type.shallow_clone()); - - env.pool[pattern_type_id] = pattern_type; - - constrain_pattern( - arena, - env, - pattern, - // TODO needs to come from pattern - Region::zero(), - pattern_expected, - &mut pattern_state, - ); - - vars.push(*pattern_var); - } - - let function_type = Type2::Function( - pattern_types, - env.pool.add(closure_type), - env.pool.add(return_type), - ); - - (vars, pattern_state, function_type) -} - -#[allow(clippy::too_many_arguments)] -fn constrain_closure_size<'a>( - arena: &'a Bump, - env: &mut Env, - name: Symbol, - region: Region, - captured_symbols: &PoolVec<(Symbol, Variable)>, - closure_var: Variable, - closure_ext_var: Variable, - variables: &mut BumpVec<'a, Variable>, -) -> Constraint<'a> { - use Constraint::*; - - debug_assert!(variables.iter().any(|s| *s == closure_var)); - debug_assert!(variables.iter().any(|s| *s == closure_ext_var)); - - let tag_arguments = PoolVec::with_capacity(captured_symbols.len() as u32, env.pool); - let mut captured_symbols_constraints = BumpVec::with_capacity_in(captured_symbols.len(), arena); - - for (captured_symbol_id, tag_arg_id) in captured_symbols - .iter_node_ids() - .zip(tag_arguments.iter_node_ids()) - { - let (symbol, var) = env.pool.get(captured_symbol_id); - - // make sure the variable is registered - variables.push(*var); - - let tag_arg_type = Type2::Variable(*var); - - // this symbol is captured, so it must be part of the closure type - env.pool[tag_arg_id] = tag_arg_type.shallow_clone(); - - // make the variable equal to the looked-up type of symbol - captured_symbols_constraints.push(Lookup( - *symbol, - Expected::NoExpectation(tag_arg_type), - Region::zero(), - )); - } - - let tag_name = TagName::Closure(name); - let closure_type = Type2::TagUnion( - PoolVec::new(vec![(tag_name, tag_arguments)].into_iter(), env.pool), - env.pool.add(Type2::Variable(closure_ext_var)), - ); - - let finalizer = Eq( - Type2::Variable(closure_var), - Expected::NoExpectation(closure_type), - Category::ClosureSize, - region, - ); - - captured_symbols_constraints.push(finalizer); - - And(captured_symbols_constraints) -} - -#[inline(always)] -fn builtin_type(symbol: Symbol, args: PoolVec) -> Type2 { - Type2::Apply(symbol, args) -} - -#[inline(always)] -fn str_type(pool: &mut Pool) -> Type2 { - builtin_type(Symbol::STR_STR, PoolVec::empty(pool)) -} - -#[inline(always)] -fn empty_list_type(pool: &mut Pool, var: Variable) -> Type2 { - list_type(pool, Type2::Variable(var)) -} - -#[inline(always)] -fn list_type(pool: &mut Pool, typ: Type2) -> Type2 { - builtin_type(Symbol::LIST_LIST, PoolVec::new(vec![typ].into_iter(), pool)) -} - -#[inline(always)] -fn num_float(pool: &mut Pool, range: TypeId) -> Type2 { - let num_floatingpoint_type = num_floatingpoint(pool, range); - let num_floatingpoint_id = pool.add(num_floatingpoint_type); - - let num_num_type = num_num(pool, num_floatingpoint_id); - let num_num_id = pool.add(num_num_type); - - Type2::Alias( - Symbol::NUM_FLOAT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - num_num_id, - ) -} - -#[inline(always)] -fn num_floatingpoint(pool: &mut Pool, range: TypeId) -> Type2 { - let range_type = pool.get(range); - - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_FLOATINGPOINT), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_FLOATINGPOINT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - pool.add(alias_content), - ) -} - -#[inline(always)] -fn num_int(pool: &mut Pool, range: TypeId) -> Type2 { - let num_integer_type = _num_integer(pool, range); - let num_integer_id = pool.add(num_integer_type); - - let num_num_type = num_num(pool, num_integer_id); - let num_num_id = pool.add(num_num_type); - - Type2::Alias( - Symbol::NUM_INT, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - num_num_id, - ) -} - -#[inline(always)] -fn _num_signed64(pool: &mut Pool) -> Type2 { - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_SIGNED64), - PoolVec::empty(pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_SIGNED64, - PoolVec::empty(pool), - pool.add(alias_content), - ) -} - -#[inline(always)] -fn _num_integer(pool: &mut Pool, range: TypeId) -> Type2 { - let range_type = pool.get(range); - - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_INTEGER), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_INTEGER, - PoolVec::new(vec![(PoolStr::new("range", pool), range)].into_iter(), pool), - pool.add(alias_content), - ) -} - -#[inline(always)] -fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { - let range_type = pool.get(type_id); - - let alias_content = Type2::TagUnion( - PoolVec::new( - vec![( - TagName::Private(Symbol::NUM_AT_NUM), - PoolVec::new(vec![range_type.shallow_clone()].into_iter(), pool), - )] - .into_iter(), - pool, - ), - pool.add(Type2::EmptyTagUnion), - ); - - Type2::Alias( - Symbol::NUM_NUM, - PoolVec::new( - vec![(PoolStr::new("range", pool), type_id)].into_iter(), - pool, - ), - pool.add(alias_content), - ) -} diff --git a/editor/src/lang/def.rs b/editor/src/lang/def.rs deleted file mode 100644 index c179145a7c..0000000000 --- a/editor/src/lang/def.rs +++ /dev/null @@ -1,1444 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_variables)] -// use crate::annotation::canonicalize_annotation; -// use crate::annotation::IntroducedVariables; -// use crate::env::Env; -// use crate::expr::Expr::{self, *}; -// use crate::expr::{ -// canonicalize_expr, local_successors, references_from_call, references_from_local, Output, -// Recursive, -// }; -// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; -// use crate::procedure::References; -use crate::lang::ast::{Expr2, FunctionDef, Rigids, ValueDef}; -use crate::lang::expr::Output; -use crate::lang::expr::{to_expr2, to_expr_id, Env}; -use crate::lang::pattern::{ - symbols_and_variables_from_pattern, symbols_from_pattern, to_pattern_id, Pattern2, PatternId, -}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId}; -use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap}; -use roc_module::ident::Lowercase; -use roc_module::symbol::Symbol; -use roc_parse::ast; -use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; -use std::collections::HashMap; -use std::fmt::Debug; -use ven_graph::{strongly_connected_components, topological_sort_into_groups}; - -#[derive(Debug)] -pub enum Def { - AnnotationOnly { rigids: Rigids, annotation: TypeId }, - Value(ValueDef), - Function(FunctionDef), -} - -impl Def { - pub fn symbols(&self, pool: &Pool) -> MutSet { - let mut output = MutSet::default(); - - match self { - Def::AnnotationOnly { .. } => todo!("lost pattern information here ... "), - Def::Value(value_def) => match value_def { - ValueDef::WithAnnotation { pattern_id, .. } - | ValueDef::NoAnnotation { pattern_id, .. } => { - let pattern2 = &pool[*pattern_id]; - output.extend(symbols_from_pattern(pool, pattern2)); - } - }, - Def::Function(function_def) => match function_def { - FunctionDef::NoAnnotation { name, .. } - | FunctionDef::WithAnnotation { name, .. } => { - output.insert(*name); - } - }, - } - - output - } -} - -impl ShallowClone for Def { - fn shallow_clone(&self) -> Self { - match self { - Self::AnnotationOnly { rigids, annotation } => Self::AnnotationOnly { - rigids: rigids.shallow_clone(), - annotation: *annotation, - }, - Self::Value(def) => Self::Value(def.shallow_clone()), - Self::Function(def) => Self::Function(def.shallow_clone()), - } - } -} - -/// A Def that has had patterns and type annnotations canonicalized, -/// but no Expr canonicalization has happened yet. Also, it has had spaces -/// and nesting resolved, and knows whether annotations are standalone or not. -#[derive(Debug)] -pub enum PendingDef<'a> { - /// A standalone annotation with no body - AnnotationOnly( - &'a Located>, - PatternId, - &'a Located>, - ), - /// A body with no type annotation - Body( - &'a Located>, - PatternId, - &'a Located>, - ), - /// A body with a type annotation - TypedBody( - &'a Located>, - PatternId, - &'a Located>, - &'a Located>, - ), - - /// A type alias, e.g. `Ints : List Int` - Alias { - name: Located, - vars: Vec>, - ann: &'a Located>, - }, - - /// An invalid alias, that is ignored in the rest of the pipeline - /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` - /// with an incorrect pattern - InvalidAlias, -} - -fn to_pending_def<'a>( - env: &mut Env<'a>, - def: &'a ast::Def<'a>, - scope: &mut Scope, - pattern_type: PatternType, -) -> Option<(Output, PendingDef<'a>)> { - use roc_parse::ast::Def::*; - - match def { - Annotation(loc_pattern, loc_ann) => { - // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - Some(( - output, - PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), - )) - } - Body(loc_pattern, loc_expr) => { - // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - Some(( - output, - PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr), - )) - } - - AnnotatedBody { - ann_pattern, - ann_type, - comment: _, - body_pattern, - body_expr, - } => { - if ann_pattern.value.equivalent(&body_pattern.value) { - // NOTE: Pick the body pattern, picking the annotation one is - // incorrect in the presence of optional record fields! - // - // { x, y } : { x : Int, y ? Bool }* - // { x, y ? False } = rec - Some(pending_typed_body( - env, - body_pattern, - ann_type, - body_expr, - scope, - pattern_type, - )) - } else { - // the pattern of the annotation does not match the pattern of the body direc - env.problem(Problem::SignatureDefMismatch { - annotation_pattern: ann_pattern.region, - def_pattern: body_pattern.region, - }); - - // TODO: Should we instead build some PendingDef::InvalidAnnotatedBody ? This would - // remove the `Option` on this function (and be probably more reliable for further - // problem/error reporting) - None - } - } - - roc_parse::ast::Def::Alias { name, vars, ann } => { - let region = Region::span_across(&name.region, &ann.region); - - match scope.introduce( - name.value.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); - - for loc_var in vars.iter() { - match loc_var.value { - ast::Pattern::Identifier(name) - if name.chars().next().unwrap().is_lowercase() => - { - let lowercase = Lowercase::from(name); - can_rigids.push(Located { - value: lowercase, - region: loc_var.region, - }); - } - _ => { - // any other pattern in this position is a syntax error. - env.problem(Problem::InvalidAliasRigid { - alias_name: symbol, - region: loc_var.region, - }); - - return Some((Output::default(), PendingDef::InvalidAlias)); - } - } - } - - Some(( - Output::default(), - PendingDef::Alias { - name: Located { - region: name.region, - value: symbol, - }, - vars: can_rigids, - ann, - }, - )) - } - - Err((original_region, loc_shadowed_symbol)) => { - env.problem(Problem::ShadowingInAnnotation { - original_region, - shadow: loc_shadowed_symbol, - }); - - Some((Output::default(), PendingDef::InvalidAlias)) - } - } - } - - Expect(_) => todo!(), - - SpaceBefore(sub_def, _) | SpaceAfter(sub_def, _) => { - to_pending_def(env, sub_def, scope, pattern_type) - } - - NotYetImplemented(s) => todo!("{}", s), - } -} - -fn pending_typed_body<'a>( - env: &mut Env<'a>, - loc_pattern: &'a Located>, - loc_ann: &'a Located>, - loc_expr: &'a Located>, - scope: &mut Scope, - pattern_type: PatternType, -) -> (Output, PendingDef<'a>) { - // This takes care of checking for shadowing and adding idents to scope. - let (output, loc_can_pattern) = to_pattern_id( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - ( - output, - PendingDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr), - ) -} - -fn from_pending_alias<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - name: Located, - vars: Vec>, - ann: &'a Located>, - mut output: Output, -) -> Output { - let symbol = name.value; - - match to_annotation2(env, scope, &ann.value, ann.region) { - Annotation2::Erroneous(_) => todo!(), - Annotation2::Annotation { - named_rigids, - unnamed_rigids, - symbols, - signature, - } => { - // Record all the annotation's references in output.references.lookups - - for symbol in symbols { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - for loc_lowercase in vars { - if !named_rigids.contains_key(loc_lowercase.value.as_str()) { - env.problem(Problem::PhantomTypeArgument { - alias: symbol, - variable_region: loc_lowercase.region, - variable_name: loc_lowercase.value.clone(), - }); - } - } - - let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); - - let annotation = match signature { - Signature::Value { annotation } => annotation, - Signature::Function { - arguments, - closure_type_id, - return_type_id, - } => Type2::Function(arguments, closure_type_id, return_type_id), - Signature::FunctionWithAliases { annotation, .. } => annotation, - }; - - if annotation.contains_symbol(env.pool, symbol) { - // the alias is recursive. If it's a tag union, we attempt to fix this - if let Type2::TagUnion(tags, ext) = annotation { - // re-canonicalize the alias with the alias already in scope - let rec_var = env.var_store.fresh(); - let rec_type_union = Type2::RecursiveTagUnion(rec_var, tags, ext); - - // NOTE this only looks at the symbol, and just assumes that the - // recursion is not polymorphic - rec_type_union.substitute_alias(env.pool, symbol, Type2::Variable(rec_var)); - - let annotation_id = env.add(rec_type_union, ann.region); - let named = rigids.named(env.pool); - - scope.add_alias(env.pool, symbol, named, annotation_id); - } else { - env.problem(Problem::CyclicAlias(symbol, name.region, vec![])); - return output; - } - } else { - let annotation_id = env.add(annotation, ann.region); - let named = rigids.named(env.pool); - - scope.add_alias(env.pool, symbol, named, annotation_id); - } - - output - } - } -} - -// TODO trim down these arguments! -#[allow(clippy::too_many_arguments)] -#[allow(clippy::cognitive_complexity)] -fn canonicalize_pending_def<'a>( - env: &mut Env<'a>, - pending_def: PendingDef<'a>, - mut output: Output, - scope: &mut Scope, - can_defs_by_symbol: &mut MutMap, - refs_by_symbol: &mut MutMap, - aliases: &mut MutMap, -) -> Output { - use PendingDef::*; - - // Make types for the body expr, even if we won't end up having a body. - let expr_var = env.var_store.fresh(); - - match pending_def { - AnnotationOnly(_, loc_can_pattern, loc_ann) => { - // annotation sans body cannot introduce new rigids that are visible in other annotations - // but the rigids can show up in type error messages, so still register them - - match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { - Annotation2::Erroneous(_) => todo!(), - Annotation2::Annotation { - named_rigids, - unnamed_rigids, - symbols, - signature, - } => { - // Record all the annotation's references in output.references.lookups - - for symbol in symbols { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); - - let annotation = match signature { - Signature::Value { annotation } => annotation, - Signature::Function { - arguments, - closure_type_id, - return_type_id, - } => Type2::Function(arguments, closure_type_id, return_type_id), - Signature::FunctionWithAliases { annotation, .. } => annotation, - }; - let annotation = env.add(annotation, loc_ann.region); - - let def = Def::AnnotationOnly { rigids, annotation }; - - for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { - can_defs_by_symbol.insert(symbol, def.shallow_clone()); - } - - output - } - } - } - - PendingDef::Alias { name, ann, vars } => { - from_pending_alias(env, scope, name, vars, ann, output) - } - - InvalidAlias => { - // invalid aliases (shadowed, incorrect patterns ) - todo!() - } - - TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { - match to_annotation2(env, scope, &loc_ann.value, loc_ann.region) { - Annotation2::Erroneous(_) => todo!(), - Annotation2::Annotation { - named_rigids, - unnamed_rigids, - symbols, - signature, - } => { - // Record all the annotation's references in output.references.lookups - - for symbol in symbols { - output.references.lookups.insert(symbol); - output.references.referenced_aliases.insert(symbol); - } - - let rigids = Rigids::new(named_rigids, unnamed_rigids, env.pool); - - // bookkeeping for tail-call detection. If we're assigning to an - // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. - let outer_identifier = env.tailcallable_symbol; - - if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { - env.tailcallable_symbol = Some(*defined_symbol); - }; - - // regiser the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&env.pool[loc_can_pattern], &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - let (loc_can_expr, can_output) = - to_expr2(env, scope, &loc_expr.value, loc_expr.region); - - output.references.union_mut(can_output.references.clone()); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // First, make sure we are actually assigning an identifier instead of (for example) a tag. - // - // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, - // which also implies it's not a self tail call! - // - // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - match loc_can_expr { - Expr2::Closure { - args: closure_args, - body, - extra, - name: closure_symbol, - .. - } => { - let symbol = match env.pool[loc_can_pattern] { - Pattern2::Identifier(ref s) => *s, - _ => todo!( - r"this is an error; functions must be bound with an identifier pattern!" - ), - }; - - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. - let references = - env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) - }); - - // TODO should we re-insert this function into env.closures? - env.closures.insert(symbol, references); - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.lookups.remove(&symbol); - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - - let arguments: PoolVec<(PatternId, Type2)> = - PoolVec::with_capacity(closure_args.len() as u32, env.pool); - - let return_type: TypeId; - - let annotation = match signature { - Signature::Value { .. } => { - todo!("type annotation says 0 arguments, but it's a function") - } - Signature::Function { - arguments: type_arguments, - closure_type_id, - return_type_id, - } - | Signature::FunctionWithAliases { - arguments: type_arguments, - closure_type_id, - return_type_id, - .. - } => { - if arguments.len() != type_arguments.len() { - panic!("argument number mismatch"); - } - - let it: Vec<_> = closure_args - .iter(env.pool) - .map(|(x, y)| (*x, *y)) - .zip( - type_arguments - .iter(env.pool) - .map(|t| t.shallow_clone()), - ) - .collect(); - - for (node_id, ((_, pattern_id), typ)) in - arguments.iter_node_ids().zip(it.into_iter()) - { - env.pool[node_id] = (pattern_id, typ); - } - - return_type = return_type_id; - } - }; - - let function_def = FunctionDef::WithAnnotation { - name: symbol, - arguments, - rigids: env.pool.add(rigids), - return_type, - body, - }; - - let def = Def::Function(function_def); - - can_defs_by_symbol.insert(symbol, def); - - output - } - - _ => { - let annotation = match signature { - Signature::Value { annotation } => annotation, - Signature::Function { - arguments, - closure_type_id, - return_type_id, - } => Type2::Function(arguments, closure_type_id, return_type_id), - Signature::FunctionWithAliases { annotation, .. } => annotation, - }; - let annotation = env.add(annotation, loc_ann.region); - - let value_def = ValueDef::WithAnnotation { - pattern_id: loc_can_pattern, - expr_id: env.pool.add(loc_can_expr), - type_id: annotation, - rigids: rigids, - expr_var: env.var_store.fresh(), - }; - - let def = Def::Value(value_def); - - for symbol in - symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) - { - can_defs_by_symbol.insert(symbol, def.shallow_clone()); - } - - for (_, (symbol, region)) in scope.idents() { - // if !vars_by_symbol.contains_key(&symbol) { - // continue; - // } - let refs = can_output.references.clone(); - refs_by_symbol.insert(*symbol, (*region, refs)); - } - - output - } - } - } - } - } - - Body(loc_pattern, loc_can_pattern, loc_expr) => { - // bookkeeping for tail-call detection. If we're assigning to an - // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. - let outer_identifier = env.tailcallable_symbol; - - if let Pattern2::Identifier(ref defined_symbol) = env.pool[loc_can_pattern] { - env.tailcallable_symbol = Some(*defined_symbol); - }; - - // regiser the name of this closure, to make sure the closure won't capture it's own name - if let (Pattern2::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = - (&env.pool[loc_can_pattern], &loc_expr.value) - { - env.closure_name_symbol = Some(*defined_symbol); - }; - - let (loc_can_expr, can_output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); - - output.references.union_mut(can_output.references.clone()); - - // reset the tailcallable_symbol - env.tailcallable_symbol = outer_identifier; - - // First, make sure we are actually assigning an identifier instead of (for example) a tag. - // - // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, - // which also implies it's not a self tail call! - // - // Only defs of the form (foo = ...) can be closure declarations or self tail calls. - match loc_can_expr { - Expr2::Closure { - args: closure_args, - body, - extra, - name: closure_symbol, - .. - } => { - let symbol = match env.pool[loc_can_pattern] { - Pattern2::Identifier(ref s) => *s, - _ => todo!( - r"this is an error; functions must be bound with an identifier pattern!" - ), - }; - - // Since everywhere in the code it'll be referred to by its defined name, - // remove its generated name from the closure map. - let references = - env.closures.remove(&closure_symbol).unwrap_or_else(|| { - panic!( r"Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_symbol, env.closures) - }); - - // TODO should we re-insert this function into env.closures? - env.closures.insert(symbol, references); - - // Recursion doesn't count as referencing. (If it did, all recursive functions - // would result in circular def errors!) - refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { - refs.lookups.remove(&symbol); - }); - - // Functions' references don't count in defs. - // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its - // parent commit for the bug this fixed! - let refs = References::new(); - - let arguments: PoolVec<(PatternId, Variable)> = - PoolVec::with_capacity(closure_args.len() as u32, env.pool); - - let it: Vec<_> = closure_args.iter(env.pool).map(|(x, y)| (*x, *y)).collect(); - - for (node_id, (_, pattern_id)) in arguments.iter_node_ids().zip(it.into_iter()) - { - env.pool[node_id] = (pattern_id, env.var_store.fresh()); - } - - let function_def = FunctionDef::NoAnnotation { - name: symbol, - arguments, - return_var: env.var_store.fresh(), - body, - }; - - let def = Def::Function(function_def); - - can_defs_by_symbol.insert(symbol, def); - - output - } - - _ => { - let value_def = ValueDef::NoAnnotation { - pattern_id: loc_can_pattern, - expr_id: env.pool.add(loc_can_expr), - expr_var: env.var_store.fresh(), - }; - - let def = Def::Value(value_def); - - for symbol in symbols_from_pattern(env.pool, env.pool.get(loc_can_pattern)) { - can_defs_by_symbol.insert(symbol, def.shallow_clone()); - } - - for (_, (symbol, region)) in scope.idents() { - // if !vars_by_symbol.contains_key(&symbol) { - // continue; - // } - let refs = can_output.references.clone(); - refs_by_symbol.insert(*symbol, (*region, refs)); - } - - output - } - } - } - } -} - -#[derive(Clone, Debug, Default, PartialEq)] -pub struct References { - pub bound_symbols: MutSet, - pub lookups: MutSet, - pub referenced_aliases: MutSet, - pub calls: MutSet, -} - -impl References { - pub fn new() -> References { - Self::default() - } - - pub fn union_mut(&mut self, other: References) { - self.lookups.extend(other.lookups); - self.calls.extend(other.calls); - self.bound_symbols.extend(other.bound_symbols); - self.referenced_aliases.extend(other.referenced_aliases); - } - - pub fn has_lookup(&self, symbol: Symbol) -> bool { - self.lookups.contains(&symbol) - } -} - -#[derive(Debug)] -pub struct CanDefs { - pub refs_by_symbol: MutMap, - pub can_defs_by_symbol: MutMap, - pub aliases: MutMap, -} - -#[inline(always)] -pub fn canonicalize_defs<'a>( - env: &mut Env<'a>, - mut output: Output, - original_scope: &Scope, - loc_defs: &'a [&'a Located>], - pattern_type: PatternType, -) -> (CanDefs, Scope, Output, MutMap) { - // Canonicalizing defs while detecting shadowing involves a multi-step process: - // - // 1. Go through each of the patterns. - // 2. For each identifier pattern, get the scope.symbol() for the ident. (That symbol will use the home module for its module.) - // 3. If that symbol is already in scope, then we're about to shadow it. Error! - // 4. Otherwise, add it to the scope immediately, so we can detect shadowing within the same - // pattern (e.g. (Foo a a) = ...) - // 5. Add this canonicalized pattern and its corresponding ast::Expr to pending_exprs. - // 5. Once every pattern has been processed and added to scope, go back and canonicalize the exprs from - // pending_exprs, this time building up a canonical def for each one. - // - // This way, whenever any expr is doing lookups, it knows everything that's in scope - - // even defs that appear after it in the source. - // - // This naturally handles recursion too, because a given expr which refers - // to itself won't be processed until after its def has been added to scope. - - // Record both the original and final idents from the scope, - // so we can diff them while detecting unused defs. - let mut scope = original_scope.shallow_clone(); - let num_defs = loc_defs.len(); - let mut refs_by_symbol = MutMap::default(); - let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); - let mut pending = Vec::with_capacity(num_defs); // TODO bump allocate this! - - // Canonicalize all the patterns, record shadowing problems, and store - // the ast::Expr values in pending_exprs for further canonicalization - // once we've finished assembling the entire scope. - for loc_def in loc_defs { - match to_pending_def(env, &loc_def.value, &mut scope, pattern_type) { - None => (), - Some((new_output, pending_def)) => { - // store the top-level defs, used to ensure that closures won't capture them - if let PatternType::TopLevelDef = pattern_type { - match &pending_def { - PendingDef::AnnotationOnly(_, loc_can_pattern, _) - | PendingDef::Body(_, loc_can_pattern, _) - | PendingDef::TypedBody(_, loc_can_pattern, _, _) => { - env.top_level_symbols.extend(symbols_from_pattern( - env.pool, - env.pool.get(*loc_can_pattern), - )) - } - PendingDef::Alias { .. } | PendingDef::InvalidAlias => {} - } - } - // Record the ast::Expr for later. We'll do another pass through these - // once we have the entire scope assembled. If we were to canonicalize - // the exprs right now, they wouldn't have symbols in scope from defs - // that get would have gotten added later in the defs list! - pending.push(pending_def); - output.union(new_output); - } - } - } - - if cfg!(debug_assertions) { - env.home.register_debug_idents(&env.ident_ids); - } - - // TODO what to do here? aliases are already in the scope! - let mut aliases = MutMap::default(); - let mut value_defs = Vec::new(); - - for pending_def in pending.into_iter() { - match pending_def { - PendingDef::Alias { name, vars, ann } => { - output = from_pending_alias(env, &mut scope, name, vars, ann, output); - } - other => value_defs.push(other), - } - } - - // TODO - // correct_mutual_recursive_type_alias(env, &mut aliases, var_store); - - // Now that we have the scope completely assembled, and shadowing resolved, - // we're ready to canonicalize any body exprs. - for pending_def in value_defs.into_iter() { - output = canonicalize_pending_def( - env, - pending_def, - output, - &mut scope, - &mut can_defs_by_symbol, - &mut refs_by_symbol, - &mut aliases, - ); - - // TODO we should do something with these references; they include - // things like type annotations. - } - - // Determine which idents we introduced in the course of this process. - let mut symbols_introduced = MutMap::default(); - - for (symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(symbol) { - symbols_introduced.insert(symbol, region); - } - } - - // This returns both the defs info as well as the new scope. - // - // We have to return the new scope because we added defs to it - // (and those lookups shouldn't fail later, e.g. when canonicalizing - // the return expr), but we didn't want to mutate the original scope - // directly because we wanted to keep a clone of it around to diff - // when looking for unused idents. - // - // We have to return the scope separately from the defs, because the - // defs need to get moved later. - ( - CanDefs { - refs_by_symbol, - can_defs_by_symbol, - aliases, - }, - scope, - output, - symbols_introduced, - ) -} - -// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -pub enum Declaration { - Declare(Def), - DeclareRec(Vec), - Builtin(Def), - InvalidCycle(Vec, Vec<(Region /* pattern */, Region /* expr */)>), -} - -impl Declaration { - pub fn def_count(&self) -> usize { - use Declaration::*; - match self { - Declare(_) => 1, - DeclareRec(defs) => defs.len(), - InvalidCycle(_, _) => 0, - Builtin(_) => 0, - } - } -} - -#[inline(always)] -pub fn sort_can_defs( - env: &mut Env<'_>, - defs: CanDefs, - mut output: Output, -) -> (Result, RuntimeError>, Output) { - let CanDefs { - refs_by_symbol, - can_defs_by_symbol, - aliases, - } = defs; - - // for (symbol, alias) in aliases.into_iter() { - // output.aliases.insert(symbol, alias); - // } - - // Determine the full set of references by traversing the graph. - let mut visited_symbols = MutSet::default(); - let returned_lookups = MutSet::clone(&output.references.lookups); - - // Start with the return expression's referenced locals. They're the only ones that count! - // - // If I have two defs which reference each other, but neither of them is referenced - // in the return expression, I don't want either of them (or their references) to end up - // in the final output.references. They were unused, and so were their references! - // - // The reason we need a graph here is so we don't overlook transitive dependencies. - // For example, if I have `a = b + 1` and the def returns `a + 1`, then the - // def as a whole references both `a` *and* `b`, even though it doesn't - // directly mention `b` - because `a` depends on `b`. If we didn't traverse a graph here, - // we'd erroneously give a warning that `b` was unused since it wasn't directly referenced. - for symbol in returned_lookups.into_iter() { - // We only care about local symbols in this analysis. - if symbol.module_id() == env.home { - // Traverse the graph and look up *all* the references for this local symbol. - let refs = - references_from_local(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); - - output.references.union_mut(refs); - } - } - - for symbol in output.references.calls.clone() { - // Traverse the graph and look up *all* the references for this call. - // Reuse the same visited_symbols as before; if we already visited it, - // we won't learn anything new from visiting it again! - let refs = - references_from_call(symbol, &mut visited_symbols, &refs_by_symbol, &env.closures); - - output.references.union_mut(refs); - } - - let mut defined_symbols: Vec = Vec::new(); - let mut defined_symbols_set: MutSet = MutSet::default(); - - for symbol in can_defs_by_symbol.keys().into_iter() { - defined_symbols.push(*symbol); - defined_symbols_set.insert(*symbol); - } - - // Use topological sort to reorder the defs based on their dependencies to one another. - // This way, during code gen, no def will refer to a value that hasn't been initialized yet. - // As a bonus, the topological sort also reveals any cycles between the defs, allowing - // us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually - // recursive definitions. - - // All successors that occur in the body of a symbol. - let all_successors_without_self = |symbol: &Symbol| -> MutSet { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors(&references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(References { lookups, .. }) = env.closures.get(symbol) { - let home = env.home; - - for lookup in lookups { - if lookup != symbol && lookup.module_id() == home { - // DO NOT register a self-call behind a lambda! - // - // We allow `boom = \_ -> boom {}`, but not `x = x` - loc_succ.insert(*lookup); - } - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols_set.contains(key)); - - loc_succ - } - None => MutSet::default(), - } - }; - - // All successors that occur in the body of a symbol, including the symbol itself - // This is required to determine whether a symbol is recursive. Recursive symbols - // (that are not faulty) always need a DeclareRec, even if there is just one symbol in the - // group - let mut all_successors_with_self = |symbol: &Symbol| -> MutSet { - // This may not be in refs_by_symbol. For example, the `f` in `f x` here: - // - // f = \z -> z - // - // (\x -> - // a = f x - // x - // ) - // - // It's not part of the current defs (the one with `a = f x`); rather, - // it's in the enclosing scope. It's still referenced though, so successors - // will receive it as an argument! - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - // We can only sort the symbols at the current level. That is safe because - // symbols defined at higher levels cannot refer to symbols at lower levels. - // Therefore they can never form a cycle! - // - // In the above example, `f` cannot reference `a`, and in the closure - // a call to `f` cannot cycle back to `a`. - let mut loc_succ = local_successors(&references, &env.closures); - - // if the current symbol is a closure, peek into its body - if let Some(References { lookups, .. }) = env.closures.get(symbol) { - for lookup in lookups { - loc_succ.insert(*lookup); - } - } - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols_set.contains(key)); - - loc_succ - } - None => MutSet::default(), - } - }; - - // If a symbol is a direct successor of itself, there is an invalid cycle. - // The difference with the function above is that this one does not look behind lambdas, - // but does consider direct self-recursion. - let direct_successors = |symbol: &Symbol| -> MutSet { - match refs_by_symbol.get(symbol) { - Some((_, references)) => { - let mut loc_succ = local_successors(&references, &env.closures); - - // NOTE: if the symbol is a closure we DONT look into its body - - // remove anything that is not defined in the current block - loc_succ.retain(|key| defined_symbols_set.contains(key)); - - // NOTE: direct recursion does matter here: `x = x` is invalid recursion! - - loc_succ - } - None => MutSet::default(), - } - }; - - // TODO also do the same `addDirects` check elm/compiler does, so we can - // report an error if a recursive definition can't possibly terminate! - match ven_graph::topological_sort_into_groups( - defined_symbols.as_slice(), - all_successors_without_self, - ) { - Ok(groups) => { - let mut declarations = Vec::new(); - - // groups are in reversed order - let mut can_defs_by_symbol = can_defs_by_symbol; - let cdbs = &mut can_defs_by_symbol; - for group in groups.into_iter().rev() { - group_to_declaration( - &group, - &env.closures, - &mut all_successors_with_self, - cdbs, - &mut declarations, - ); - } - - (Ok(declarations), output) - } - Err((mut groups, nodes_in_cycle)) => { - let mut declarations = Vec::new(); - let problems = Vec::new(); - - // nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem, - // and in general it's impossible to decide whether it is. So we use a crude heuristic: - // - // Definitions where the cycle occurs behind a lambda are OK - // - // boom = \_ -> boom {} - // - // But otherwise we report an error, e.g. - // - // foo = if b then foo else bar - - for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self) - { - // check whether the cycle is faulty, which is when it has - // a direct successor in the current cycle. This catches things like: - // - // x = x - // - // or - // - // p = q - // q = p - let is_invalid_cycle = match cycle.get(0) { - Some(symbol) => { - let mut succs = direct_successors(symbol); - - succs.retain(|key| cycle.contains(key)); - - !succs.is_empty() - } - None => false, - }; - - if is_invalid_cycle { - // We want to show the entire cycle in the error message, so expand it out. - let mut loc_symbols = Vec::new(); - - for symbol in cycle { - match refs_by_symbol.get(&symbol) { - None => unreachable!( - r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#, - symbol, refs_by_symbol - ), - Some((region, _)) => { - loc_symbols.push(Located::at(*region, symbol)); - } - } - } - - // TODO we don't store those regions any more! - // let regions = Vec::with_capacity(can_defs_by_symbol.len()); - // for def in can_defs_by_symbol.values() { - // regions.push((def.loc_pattern.region, def.loc_expr.region)); - // } - // - // // Sort them by line number to make the report more helpful. - // loc_symbols.sort(); - // regions.sort(); - - // let symbols_in_cycle: Vec = - // loc_symbols.into_iter().map(|s| s.value).collect(); - // - // problems.push(Problem::RuntimeError(RuntimeError::CircularDef( - // symbols_in_cycle.clone(), - // regions.clone(), - // ))); - // - // declarations.push(Declaration::InvalidCycle(symbols_in_cycle, regions)); - panic!("Invalid Cycle"); - } else { - // slightly inefficient, because we know this becomes exactly one DeclareRec already - groups.push(cycle); - } - } - - // now we have a collection of groups whose dependencies are not cyclic. - // They are however not yet topologically sorted. Here we have to get a bit - // creative to get all the definitions in the correct sorted order. - - let mut group_ids = Vec::with_capacity(groups.len()); - let mut symbol_to_group_index = MutMap::default(); - for (i, group) in groups.iter().enumerate() { - for symbol in group { - symbol_to_group_index.insert(*symbol, i); - } - - group_ids.push(i); - } - - let successors_of_group = |group_id: &usize| { - let mut result = MutSet::default(); - - // for each symbol in this group - for symbol in &groups[*group_id] { - // find its successors - for succ in all_successors_without_self(symbol) { - // and add its group to the result - result.insert(symbol_to_group_index[&succ]); - } - } - - // don't introduce any cycles to self - result.remove(group_id); - - result - }; - - match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) { - Ok(sorted_group_ids) => { - let mut can_defs_by_symbol = can_defs_by_symbol; - let cdbs = &mut can_defs_by_symbol; - for sorted_group in sorted_group_ids.iter().rev() { - for group_id in sorted_group.iter().rev() { - let group = &groups[*group_id]; - - group_to_declaration( - group, - &env.closures, - &mut all_successors_with_self, - cdbs, - &mut declarations, - ); - } - } - } - Err(_) => unreachable!("there should be no cycles now!"), - } - - for problem in problems { - env.problem(problem); - } - - (Ok(declarations), output) - } - } -} - -pub fn references_from_local<'a, T>( - defined_symbol: Symbol, - visited: &'a mut MutSet, - refs_by_def: &'a MutMap, - closures: &'a MutMap, -) -> References -where - T: Debug, -{ - let mut answer: References = References::new(); - - match refs_by_def.get(&defined_symbol) { - Some((_, refs)) => { - visited.insert(defined_symbol); - - for local in refs.lookups.iter() { - if !visited.contains(&local) { - let other_refs: References = - references_from_local(*local, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.lookups.insert(*local); - } - - for call in refs.calls.iter() { - if !visited.contains(&call) { - let other_refs = references_from_call(*call, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.calls.insert(*call); - } - - answer - } - None => answer, - } -} - -pub fn references_from_call<'a, T>( - call_symbol: Symbol, - visited: &'a mut MutSet, - refs_by_def: &'a MutMap, - closures: &'a MutMap, -) -> References -where - T: Debug, -{ - match closures.get(&call_symbol) { - Some(references) => { - let mut answer = references.clone(); - - visited.insert(call_symbol); - - for closed_over_local in references.lookups.iter() { - if !visited.contains(&closed_over_local) { - let other_refs = - references_from_local(*closed_over_local, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.lookups.insert(*closed_over_local); - } - - for call in references.calls.iter() { - if !visited.contains(&call) { - let other_refs = references_from_call(*call, visited, refs_by_def, closures); - - answer.union_mut(other_refs); - } - - answer.calls.insert(*call); - } - - answer - } - None => { - // If the call symbol was not in the closure map, that means we're calling a non-function and - // will get a type mismatch later. For now, assume no references as a result of the "call." - References::new() - } - } -} - -fn local_successors( - references: &References, - closures: &MutMap, -) -> MutSet { - let mut answer = references.lookups.clone(); - - for call_symbol in references.calls.iter() { - answer.extend(call_successors(*call_symbol, closures)); - } - - answer -} - -fn call_successors<'a>( - call_symbol: Symbol, - closures: &'a MutMap, -) -> MutSet { - let mut answer = MutSet::default(); - let mut seen = MutSet::default(); - let mut queue = vec![call_symbol]; - - while let Some(symbol) = queue.pop() { - if seen.contains(&symbol) { - continue; - } - - if let Some(references) = closures.get(&symbol) { - answer.extend(references.lookups.iter().copied()); - queue.extend(references.calls.iter().copied()); - - seen.insert(symbol); - } - } - - answer -} - -fn group_to_declaration( - group: &[Symbol], - closures: &MutMap, - successors: &mut dyn FnMut(&Symbol) -> MutSet, - can_defs_by_symbol: &mut MutMap, - declarations: &mut Vec, -) { - use Declaration::*; - - // We want only successors in the current group, otherwise definitions get duplicated - let filtered_successors = |symbol: &Symbol| -> MutSet { - let mut result = successors(symbol); - - result.retain(|key| group.contains(key)); - result - }; - - // TODO fix this - // Patterns like - // - // { x, y } = someDef - // - // Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function), - // normally `someDef` would be inserted twice. We use the region of the pattern as a unique key - // for a definition, so every definition is only inserted (thus typechecked and emitted) once - // let mut seen_pattern_regions: MutSet = MutSet::default(); - - for cycle in strongly_connected_components(&group, filtered_successors) { - if cycle.len() == 1 { - let symbol = &cycle[0]; - - if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { - // Determine recursivity of closures that are not tail-recursive - - let is_recursive = successors(&symbol).contains(&symbol); - - if is_recursive { - declarations.push(DeclareRec(vec![can_def])); - } else { - declarations.push(Declare(can_def)); - } - } - } else { - let mut can_defs = Vec::new(); - - // Topological sort gives us the reverse of the sorting we want! - for symbol in cycle.into_iter().rev() { - if let Some(can_def) = can_defs_by_symbol.remove(&symbol) { - can_defs.push(can_def); - } - } - - declarations.push(DeclareRec(can_defs)); - } - } -} diff --git a/editor/src/lang/expr.rs b/editor/src/lang/expr.rs deleted file mode 100644 index a50c315f94..0000000000 --- a/editor/src/lang/expr.rs +++ /dev/null @@ -1,1555 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use bumpalo::{collections::Vec as BumpVec, Bump}; -use std::collections::HashMap; -use std::iter::FromIterator; - -use crate::lang::ast::{ - expr2_to_string, value_def_to_string, ClosureExtra, Def2, Expr2, ExprId, FloatVal, IntStyle, - IntVal, RecordField, ValueDef, WhenBranch, -}; -use crate::lang::def::{ - canonicalize_defs, sort_can_defs, CanDefs, Declaration, Def, PendingDef, References, -}; -use crate::lang::pattern::{to_pattern2, Pattern2, PatternId}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::{Alias, Annotation2, Type2, TypeId}; - -use roc_can::expr::Recursive; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; -use roc_can::operator::desugar_expr; -use roc_collections::all::default_hasher; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, Lowercase, ModuleName}; -use roc_module::low_level::LowLevel; -use roc_module::operator::CalledVia; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; -use roc_parse::ast; -use roc_parse::ast::Expr; -use roc_parse::ast::StrLiteral; -use roc_parse::parser::{loc, Parser, State, SyntaxError}; -use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; - -#[derive(Clone, Debug, PartialEq, Default)] -pub struct IntroducedVariables { - // Rigids must be unique within a type annoation. - // E.g. in `identity : a -> a`, there should only be one - // variable (a rigid one, with name "a"). - // Hence `rigids : Map` - // - // But then between annotations, the same name can occur multiple times, - // but a variable can only have one name. Therefore - // `ftv : Map`. - pub wildcards: Vec, - pub var_by_name: MutMap, - pub name_by_var: MutMap, - pub host_exposed_aliases: MutMap, -} - -impl IntroducedVariables { - pub fn insert_named(&mut self, name: Lowercase, var: Variable) { - self.var_by_name.insert(name.clone(), var); - self.name_by_var.insert(var, name); - } - - pub fn insert_wildcard(&mut self, var: Variable) { - self.wildcards.push(var); - } - - pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) { - self.host_exposed_aliases.insert(symbol, var); - } - - pub fn union(&mut self, other: &Self) { - self.wildcards.extend(other.wildcards.iter().cloned()); - self.var_by_name.extend(other.var_by_name.clone()); - self.name_by_var.extend(other.name_by_var.clone()); - self.host_exposed_aliases - .extend(other.host_exposed_aliases.clone()); - } - - pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { - self.var_by_name.get(name) - } - - pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { - self.name_by_var.get(&var) - } -} - -#[derive(Clone, Default, Debug, PartialEq)] -pub struct Output { - pub references: References, - pub tail_call: Option, - pub introduced_variables: IntroducedVariables, - pub aliases: MutMap>, - pub non_closures: MutSet, -} - -impl Output { - pub fn union(&mut self, other: Self) { - self.references.union_mut(other.references); - - if let (None, Some(later)) = (self.tail_call, other.tail_call) { - self.tail_call = Some(later); - } - - self.aliases.extend(other.aliases); - self.non_closures.extend(other.non_closures); - } -} - -#[derive(Debug)] -pub struct Env<'a> { - pub home: ModuleId, - pub var_store: &'a mut VarStore, - pub pool: &'a mut Pool, - pub arena: &'a Bump, - - pub problems: BumpVec<'a, Problem>, - - pub dep_idents: MutMap, - pub module_ids: &'a ModuleIds, - pub ident_ids: IdentIds, - pub exposed_ident_ids: IdentIds, - - pub closures: MutMap, - /// Symbols which were referenced by qualified lookups. - pub qualified_lookups: MutSet, - - pub top_level_symbols: MutSet, - - pub closure_name_symbol: Option, - pub tailcallable_symbol: Option, -} - -impl<'a> Env<'a> { - pub fn new( - home: ModuleId, - arena: &'a Bump, - pool: &'a mut Pool, - var_store: &'a mut VarStore, - dep_idents: MutMap, - module_ids: &'a ModuleIds, - exposed_ident_ids: IdentIds, - ) -> Env<'a> { - Env { - home, - arena, - pool, - problems: BumpVec::new_in(arena), - var_store, - dep_idents, - module_ids, - ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later - exposed_ident_ids, - closures: MutMap::default(), - qualified_lookups: MutSet::default(), - tailcallable_symbol: None, - closure_name_symbol: None, - top_level_symbols: MutSet::default(), - } - } - - pub fn add(&mut self, item: T, region: Region) -> NodeId { - let id = self.pool.add(item); - self.set_region(id, region); - - id - } - - pub fn problem(&mut self, problem: Problem) { - self.problems.push(problem); - } - - pub fn set_region(&mut self, _node_id: NodeId, _region: Region) { - dbg!("Don't Forget to set the region eventually"); - } - - pub fn register_closure(&mut self, symbol: Symbol, references: References) { - self.closures.insert(symbol, references); - } - - /// Generates a unique, new symbol like "$1" or "$5", - /// using the home module as the module_id. - /// - /// This is used, for example, during canonicalization of an Expr::Closure - /// to generate a unique symbol to refer to that closure. - pub fn gen_unique_symbol(&mut self) -> Symbol { - let ident_id = self.ident_ids.gen_unique(); - - Symbol::new(self.home, ident_id) - } - - /// Returns Err if the symbol resolved, but it was not exposed by the given module - pub fn qualified_lookup( - &mut self, - module_name: &str, - ident: &str, - region: Region, - ) -> Result { - debug_assert!( - !module_name.is_empty(), - "Called env.qualified_lookup with an unqualified ident: {:?}", - ident - ); - - let module_name: ModuleName = module_name.into(); - - match self.module_ids.get_id(&module_name) { - Some(&module_id) => { - let ident: Ident = ident.into(); - - // You can do qualified lookups on your own module, e.g. - // if I'm in the Foo module, I can do a `Foo.bar` lookup. - if module_id == self.home { - match self.ident_ids.get_id(&ident) { - Some(ident_id) => { - let symbol = Symbol::new(module_id, *ident_id); - - self.qualified_lookups.insert(symbol); - - Ok(symbol) - } - None => Err(RuntimeError::LookupNotInScope( - Located { - value: ident, - region, - }, - self.ident_ids - .idents() - .map(|(_, string)| string.as_ref().into()) - .collect(), - )), - } - } else { - match self - .dep_idents - .get(&module_id) - .and_then(|exposed_ids| exposed_ids.get_id(&ident)) - { - Some(ident_id) => { - let symbol = Symbol::new(module_id, *ident_id); - - self.qualified_lookups.insert(symbol); - - Ok(symbol) - } - None => Err(RuntimeError::ValueNotExposed { - module_name: ModuleName::from(module_name), - ident, - region, - }), - } - } - } - None => Err(RuntimeError::ModuleNotImported { - module_name, - imported_modules: self - .module_ids - .available_modules() - .map(|string| string.as_ref().into()) - .collect(), - region, - }), - } - } -} - -const ZERO: Region = Region::zero(); - -pub fn as_expr_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - expr_id: ExprId, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> Output { - let (expr, output) = to_expr2(env, scope, parse_expr, region); - - env.pool[expr_id] = expr; - env.set_region(expr_id, region); - - output -} - -pub fn to_expr_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> (ExprId, Output) { - let (expr, output) = to_expr2(env, scope, parse_expr, region); - - (env.add(expr, region), output) -} - -pub fn str_to_def2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result, SyntaxError<'a>> { - match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) { - Ok(vec_loc_def) => Ok(defs_to_defs2( - arena, - env, - scope, - arena.alloc(vec_loc_def), - region, - )), - Err(fail) => Err(fail), - } -} - -pub fn str_to_expr2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result<(Expr2, self::Output), SyntaxError<'a>> { - match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), - Err(fail) => Err(fail), - } -} - -fn loc_expr_to_expr2<'a>( - arena: &'a Bump, - loc_expr: Located>, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> (Expr2, self::Output) { - let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr)); - - to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region) -} - -pub fn to_expr2<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - parse_expr: &'a roc_parse::ast::Expr<'a>, - region: Region, -) -> (Expr2, self::Output) { - use roc_parse::ast::Expr::*; - - match parse_expr { - Float(string) => { - match finish_parsing_float(string) { - Ok(float) => { - let expr = Expr2::Float { - number: FloatVal::F64(float), - var: env.var_store.fresh(), - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - Num(string) => { - match finish_parsing_int(string) { - Ok(int) => { - let expr = Expr2::SmallInt { - number: IntVal::I64(int), - var: env.var_store.fresh(), - // TODO non-hardcode - style: IntStyle::Decimal, - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidInt( - error, - roc_parse::ast::Base::Decimal, - ZERO, - raw.into(), - ); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - NonBase10Int { - string, - base, - is_negative, - } => { - match finish_parsing_base(string, *base, *is_negative) { - Ok(int) => { - let expr = Expr2::SmallInt { - number: IntVal::I64(int), - var: env.var_store.fresh(), - // TODO non-hardcode - style: IntStyle::from_base(*base), - text: PoolStr::new(string, &mut env.pool), - }; - - (expr, Output::default()) - } - Err((raw, error)) => { - // emit runtime error - let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); - - env.problem(Problem::RuntimeError(runtime_error.clone())); - // - // Expr::RuntimeError(runtime_error) - todo!() - } - } - } - - Str(literal) => flatten_str_literal(env, scope, &literal), - - List { items, .. } => { - let mut output = Output::default(); - let output_ref = &mut output; - - let elems: PoolVec = PoolVec::with_capacity(items.len() as u32, env.pool); - - for (node_id, item) in elems.iter_node_ids().zip(items.iter()) { - let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region); - - output_ref.union(sub_output); - - let expr_id = env.pool.add(expr); - env.pool[node_id] = expr_id; - } - - let expr = Expr2::List { - elem_var: env.var_store.fresh(), - elems, - }; - - (expr, output) - } - - GlobalTag(tag) => { - // a global tag without any arguments - ( - Expr2::GlobalTag { - name: PoolStr::new(tag, env.pool), - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } - PrivateTag(name) => { - // a private tag without any arguments - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - let name = Symbol::new(env.home, ident_id); - ( - Expr2::PrivateTag { - name, - variant_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - arguments: PoolVec::empty(env.pool), - }, - Output::default(), - ) - } - - RecordUpdate { - fields, - update: loc_update, - final_comments: _, - } => { - let (can_update, update_out) = - to_expr2(env, scope, &loc_update.value, loc_update.region); - - if let Expr2::Var(symbol) = &can_update { - match canonicalize_fields(env, scope, fields) { - Ok((can_fields, mut output)) => { - output.references.union_mut(update_out.references); - - let answer = Expr2::Update { - record_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - symbol: *symbol, - updates: can_fields, - }; - - (answer, output) - } - Err(CanonicalizeRecordProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - record_region: _, - }) => { - // let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue { - // field_name, - // field_region, - // record_region, - // }; - // - // env.problem(Problem::RuntimeError(runtime_error)); - - todo!() - } - } - } else { - // only (optionally qualified) variables can be updated, not arbitrary expressions - - // let error = roc_problem::can::RuntimeError::InvalidRecordUpdate { - // region: can_update.region, - // }; - // - // let answer = Expr::RuntimeError(error.clone()); - // - // env.problems.push(Problem::RuntimeError(error)); - // - // (answer, Output::default()) - todo!() - } - } - - Record { - fields, - final_comments: _, - } => { - if fields.is_empty() { - (Expr2::EmptyRecord, Output::default()) - } else { - match canonicalize_fields(env, scope, fields) { - Ok((can_fields, output)) => ( - Expr2::Record { - record_var: env.var_store.fresh(), - fields: can_fields, - }, - output, - ), - Err(CanonicalizeRecordProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - record_region: _, - }) => { - // let runtime_error = RuntimeError::InvalidOptionalValue { - // field_name, - // field_region, - // record_region, - // }; - // - // env.problem(runtime_error); - // ( - // Expr::RuntimeError( - // ), - // Output::default(), - // - // ) - todo!() - } - } - } - } - - Access(record_expr, field) => { - // TODO - let region = ZERO; - let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region); - - ( - Expr2::Access { - record_var: env.var_store.fresh(), - field_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - expr: record_expr_id, - field: PoolStr::new(field, env.pool), - }, - output, - ) - } - - AccessorFunction(field) => ( - Expr2::Accessor { - function_var: env.var_store.fresh(), - record_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - field_var: env.var_store.fresh(), - field: PoolStr::new(field, env.pool), - }, - Output::default(), - ), - - If(branches, final_else) => { - let mut new_branches = Vec::with_capacity(branches.len()); - let mut output = Output::default(); - - for (condition, then_branch) in branches.iter() { - let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region); - - let (then_expr, then_output) = - to_expr2(env, scope, &then_branch.value, then_branch.region); - - output.references.union_mut(cond_output.references); - output.references.union_mut(then_output.references); - - new_branches.push((env.pool.add(cond), env.pool.add(then_expr))); - } - - let (else_expr, else_output) = - to_expr2(env, scope, &final_else.value, final_else.region); - - output.references.union_mut(else_output.references); - - let expr = Expr2::If { - cond_var: env.var_store.fresh(), - expr_var: env.var_store.fresh(), - branches: PoolVec::new(new_branches.into_iter(), env.pool), - final_else: env.pool.add(else_expr), - }; - - (expr, output) - } - - When(loc_cond, branches) => { - // Infer the condition expression's type. - let cond_var = env.var_store.fresh(); - let (can_cond, mut output) = to_expr2(env, scope, &loc_cond.value, loc_cond.region); - - // the condition can never be a tail-call - output.tail_call = None; - - let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool); - - for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) { - let (can_when_branch, branch_references) = - canonicalize_when_branch(env, scope, *branch, &mut output); - - output.references.union_mut(branch_references); - - env.pool[node_id] = can_when_branch; - } - - // A "when" with no branches is a runtime error, but it will mess things up - // if code gen mistakenly thinks this is a tail call just because its condition - // happened to be one. (The condition gave us our initial output value.) - if branches.is_empty() { - output.tail_call = None; - } - - // Incorporate all three expressions into a combined Output value. - let expr = Expr2::When { - expr_var: env.var_store.fresh(), - cond_var, - cond: env.pool.add(can_cond), - branches: can_branches, - }; - - (expr, output) - } - - Closure(loc_arg_patterns, loc_body_expr) => { - // The globally unique symbol that will refer to this closure once it gets converted - // into a top-level procedure for code gen. - // - // In the Foo module, this will look something like Foo.$1 or Foo.$2. - let symbol = env - .closure_name_symbol - .unwrap_or_else(|| env.gen_unique_symbol()); - env.closure_name_symbol = None; - - // The body expression gets a new scope for canonicalization. - // Shadow `scope` to make sure we don't accidentally use the original one for the - // rest of this block, but keep the original around for later diffing. - let original_scope = scope; - let mut scope = original_scope.shallow_clone(); - let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool); - let mut output = Output::default(); - - let mut bound_by_argument_patterns = MutSet::default(); - - for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) { - let (new_output, can_arg) = to_pattern2( - env, - &mut scope, - roc_parse::pattern::PatternType::FunctionArg, - &loc_pattern.value, - loc_pattern.region, - ); - - bound_by_argument_patterns - .extend(new_output.references.bound_symbols.iter().copied()); - - output.union(new_output); - - let pattern_id = env.add(can_arg, loc_pattern.region); - env.pool[node_id] = (env.var_store.fresh(), pattern_id); - } - - let (body_expr, new_output) = - to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region); - - let mut captured_symbols: MutSet = - new_output.references.lookups.iter().copied().collect(); - - // filter out the closure's name itself - captured_symbols.remove(&symbol); - - // symbols bound either in this pattern or deeper down are not captured! - captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s)); - captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s)); - - // filter out top-level symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| !env.top_level_symbols.contains(s)); - - // filter out imported symbols - // those will be globally available, and don't need to be captured - captured_symbols.retain(|s| s.module_id() == env.home); - - // TODO any Closure that has an empty `captured_symbols` list could be excluded! - - output.union(new_output); - - // filter out aliases - captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s)); - - // filter out functions that don't close over anything - captured_symbols.retain(|s| !output.non_closures.contains(s)); - - // Now that we've collected all the references, check to see if any of the args we defined - // went unreferenced. If any did, report them as unused arguments. - for (sub_symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(sub_symbol) { - if !output.references.has_lookup(sub_symbol) { - // The body never referenced this argument we declared. It's an unused argument! - env.problem(Problem::UnusedArgument(symbol, sub_symbol, region)); - } - - // We shouldn't ultimately count arguments as referenced locals. Otherwise, - // we end up with weird conclusions like the expression (\x -> x + 1) - // references the (nonexistant) local variable x! - output.references.lookups.remove(&sub_symbol); - } - } - - env.register_closure(symbol, output.references.clone()); - - let mut captured_symbols: Vec<_> = captured_symbols - .into_iter() - .map(|s| (s, env.var_store.fresh())) - .collect(); - - // sort symbols, so we know the order in which they're stored in the closure record - captured_symbols.sort(); - - // store that this function doesn't capture anything. It will be promoted to a - // top-level function, and does not need to be captured by other surrounding functions. - if captured_symbols.is_empty() { - output.non_closures.insert(symbol); - } - - let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool); - - let extra = ClosureExtra { - return_type: env.var_store.fresh(), // 4B - captured_symbols, // 8B - closure_type: env.var_store.fresh(), // 4B - closure_ext_var: env.var_store.fresh(), // 4B - }; - - ( - Expr2::Closure { - function_type: env.var_store.fresh(), - name: symbol, - recursive: Recursive::NotRecursive, - args: can_args, - body: env.add(body_expr, loc_body_expr.region), - extra: env.pool.add(extra), - }, - output, - ) - } - - Apply(loc_fn, loc_args, application_style) => { - // The expression that evaluates to the function being called, e.g. `foo` in - // (foo) bar baz - let fn_region = loc_fn.region; - - // Canonicalize the function expression and its arguments - let (fn_expr, mut output) = to_expr2(env, scope, &loc_fn.value, fn_region); - - // The function's return type - let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool); - - for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) { - let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region); - - env.pool[node_id] = (env.var_store.fresh(), arg_expr_id); - - output.references.union_mut(arg_out.references); - } - - // Default: We're not tail-calling a symbol (by name), we're tail-calling a function value. - output.tail_call = None; - - let expr = match fn_expr { - Expr2::Var(ref symbol) => { - output.references.calls.insert(*symbol); - - // we're tail-calling a symbol by name, check if it's the tail-callable symbol - output.tail_call = match &env.tailcallable_symbol { - Some(tc_sym) if *tc_sym == *symbol => Some(*symbol), - Some(_) | None => None, - }; - - // IDEA: Expr2::CallByName? - let fn_expr_id = env.add(fn_expr, fn_region); - Expr2::Call { - args, - expr: fn_expr_id, - expr_var: env.var_store.fresh(), - fn_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - called_via: *application_style, - } - } - Expr2::RuntimeError() => { - // We can't call a runtime error; bail out by propagating it! - return (fn_expr, output); - } - Expr2::GlobalTag { - variant_var, - ext_var, - name, - .. - } => Expr2::GlobalTag { - variant_var, - ext_var, - name, - arguments: args, - }, - Expr2::PrivateTag { - variant_var, - ext_var, - name, - .. - } => Expr2::PrivateTag { - variant_var, - ext_var, - name, - arguments: args, - }, - _ => { - // This could be something like ((if True then fn1 else fn2) arg1 arg2). - let fn_expr_id = env.add(fn_expr, fn_region); - Expr2::Call { - args, - expr: fn_expr_id, - expr_var: env.var_store.fresh(), - fn_var: env.var_store.fresh(), - closure_var: env.var_store.fresh(), - called_via: *application_style, - } - } - }; - - (expr, output) - } - - Defs(loc_defs, loc_ret) => { - let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( - env, - Output::default(), - &scope, - loc_defs, - PatternType::DefExpr, - ); - - // The def as a whole is a tail call iff its return expression is a tail call. - // Use its output as a starting point because its tail_call already has the right answer! - let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region); - - output - .introduced_variables - .union(&defs_output.introduced_variables); - - output.references.union_mut(defs_output.references); - - // Now that we've collected all the references, check to see if any of the new idents - // we defined went unused by the return expression. If any were unused, report it. - for (symbol, region) in symbols_introduced { - if !output.references.has_lookup(symbol) { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - let (can_defs, output) = sort_can_defs(env, unsorted, output); - - match can_defs { - Ok(decls) => { - let mut expr = ret_expr; - - for declaration in decls.into_iter().rev() { - expr = decl_to_let(env.pool, env.var_store, declaration, expr); - } - - (expr, output) - } - Err(_err) => { - // TODO: fix this to be something from Expr2 - // (RuntimeError(err), output) - todo!() - } - } - } - - PrecedenceConflict { .. } => { - // use roc_problem::can::RuntimeError::*; - // - // let problem = PrecedenceProblem::BothNonAssociative( - // *whole_region, - // binop1.clone(), - // binop2.clone(), - // ); - // - // env.problem(Problem::PrecedenceProblem(problem.clone())); - // - // ( - // RuntimeError(InvalidPrecedence(problem, region)), - // Output::default(), - // ) - todo!() - } - MalformedClosure => { - // use roc_problem::can::RuntimeError::*; - // (RuntimeError(MalformedClosure(region)), Output::default()) - todo!() - } - MalformedIdent(_name, _problem) => { - // use roc_problem::can::RuntimeError::*; - // - // let problem = MalformedIdentifier((*name).into(), region); - // env.problem(Problem::RuntimeError(problem.clone())); - // - // (RuntimeError(problem), Output::default()) - todo!() - } - Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region), - - // Below this point, we shouln't see any of these nodes anymore because - // operator desugaring should have removed them! - bad_expr @ ParensAround(_) => { - panic!( - "A ParensAround did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ SpaceBefore(_, _) => { - panic!( - "A SpaceBefore did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ SpaceAfter(_, _) => { - panic!( - "A SpaceAfter did not get removed during operator desugaring somehow: {:#?}", - bad_expr - ); - } - bad_expr @ BinOps { .. } => { - panic!( - "A binary operator chain did not get desugared somehow: {:#?}", - bad_expr - ); - } - bad_expr @ UnaryOp(_, _) => { - panic!( - "A unary operator did not get desugared somehow: {:#?}", - bad_expr - ); - } - - rest => todo!("not yet implemented {:?}", rest), - } -} - -pub fn defs_to_defs2<'a>( - arena: &'a Bump, - env: &mut Env<'a>, - scope: &mut Scope, - parsed_defs: &'a BumpVec>>, - region: Region, -) -> Vec { - use roc_parse::ast::Expr::*; - - parsed_defs - .iter() - .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) - .collect() -} - -pub fn to_def2_from_def<'a>( - arena: &'a Bump, - env: &mut Env<'a>, - scope: &mut Scope, - parsed_def: &'a roc_parse::ast::Def<'a>, - region: Region, -) -> Def2 { - use roc_parse::ast::Def::*; - - match parsed_def { - SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), - SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region), - Body(&loc_pattern, &loc_expr) => { - // TODO loc_pattern use identifier - let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0; - let expr_id = env.pool.add(expr2); - - use roc_parse::ast::Pattern::*; - - match loc_pattern.value { - Identifier(_) => { - let (_, pattern2) = to_pattern2( - env, - scope, - PatternType::TopLevelDef, - &loc_pattern.value, - region, - ); - let pattern_id = env.pool.add(pattern2); - - // TODO support with annotation - Def2::ValueDef { - identifier_id: pattern_id, - expr_id, - } - } - other => { - unimplemented!( - "I don't yet know how to convert the pattern {:?} into an expr2", - other - ) - } - } - } - other => { - unimplemented!( - "I don't know how to make an expr2 from this def yet: {:?}", - other - ) - } - } -} - -fn flatten_str_literal<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - literal: &StrLiteral<'a>, -) -> (Expr2, Output) { - use roc_parse::ast::StrLiteral::*; - - match literal { - PlainLine(str_slice) => { - // TODO use smallstr - let expr = Expr2::Str(PoolStr::new(str_slice, &mut env.pool)); - - (expr, Output::default()) - } - Line(segments) => flatten_str_lines(env, scope, &[segments]), - Block(lines) => flatten_str_lines(env, scope, lines), - } -} - -enum StrSegment { - Interpolation(Expr2), - Plaintext(PoolStr), -} - -fn flatten_str_lines<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - lines: &[&[roc_parse::ast::StrSegment<'a>]], -) -> (Expr2, Output) { - use roc_parse::ast::StrSegment::*; - - let mut buf = String::new(); - let mut segments = Vec::new(); - let mut output = Output::default(); - - for line in lines { - for segment in line.iter() { - match segment { - Plaintext(string) => { - buf.push_str(string); - } - Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) { - Ok(code_pt) => match std::char::from_u32(code_pt) { - Some(ch) => { - buf.push(ch); - } - None => { - // env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt( - // loc_hex_digits.region, - // )), - // output, - // ); - todo!() - } - }, - Err(_) => { - // env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidHexadecimal( - // loc_hex_digits.region, - // )), - // output, - // ); - todo!() - } - }, - Interpolated(loc_expr) => { - if roc_can::expr::is_valid_interpolation(loc_expr.value) { - // Interpolations desugar to Str.concat calls - output.references.calls.insert(Symbol::STR_CONCAT); - - if !buf.is_empty() { - segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); - - buf = String::new(); - } - - let (loc_expr, new_output) = - to_expr2(env, scope, loc_expr.value, loc_expr.region); - - output.union(new_output); - - segments.push(StrSegment::Interpolation(loc_expr)); - } else { - // env.problem(Problem::InvalidInterpolation(loc_expr.region)); - // - // return ( - // Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)), - // output, - // ); - todo!() - } - } - EscapedChar(escaped) => buf.push(roc_can::expr::unescape_char(escaped)), - } - } - } - - if !buf.is_empty() { - segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool))); - } - - (desugar_str_segments(env, segments), output) -} - -/// Resolve string interpolations by desugaring a sequence of StrSegments -/// into nested calls to Str.concat -fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Expr2 { - use StrSegment::*; - - let pool = &mut env.pool; - let var_store = &mut env.var_store; - - let mut iter = segments.into_iter().rev(); - let mut expr = match iter.next() { - Some(Plaintext(pool_str)) => Expr2::Str(pool_str), - Some(Interpolation(expr_id)) => expr_id, - None => { - // No segments? Empty string! - - let pool_str = PoolStr::new("", pool); - Expr2::Str(pool_str) - } - }; - - for seg in iter { - let new_expr = match seg { - Plaintext(string) => Expr2::Str(string), - Interpolation(expr_id) => expr_id, - }; - - let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT)); - - let args = vec![ - (var_store.fresh(), pool.add(new_expr)), - (var_store.fresh(), pool.add(expr)), - ]; - let args = PoolVec::new(args.into_iter(), pool); - - let new_call = Expr2::Call { - args, - expr: concat_expr_id, - expr_var: var_store.fresh(), - fn_var: var_store.fresh(), - closure_var: var_store.fresh(), - called_via: CalledVia::Space, - }; - - expr = new_call - } - - expr -} - -enum CanonicalizeRecordProblem { - InvalidOptionalValue { - field_name: PoolStr, - field_region: Region, - record_region: Region, - }, -} - -enum FieldVar { - VarAndExprId(Variable, ExprId), - OnlyVar(Variable), -} - -fn canonicalize_fields<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - fields: &'a [Located>>], -) -> Result<(PoolVec, Output), CanonicalizeRecordProblem> { - let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default(); - let mut output = Output::default(); - - for loc_field in fields.iter() { - match canonicalize_field(env, scope, &loc_field.value) { - Ok(can_field) => { - match can_field { - CanonicalField::LabelAndValue { - label, - value_expr, - value_output, - var, - } => { - let expr_id = env.pool.add(value_expr); - - let replaced = - can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id)); - - if let Some(_old) = replaced { - // env.problems.push(Problem::DuplicateRecordFieldValue { - // field_name: label, - // field_region: loc_field.region, - // record_region: region, - // replaced_region: old.region, - // }); - todo!() - } - - output.references.union_mut(value_output.references); - } - CanonicalField::InvalidLabelOnly { label, var } => { - let replaced = can_fields.insert(label, FieldVar::OnlyVar(var)); - - if let Some(_old) = replaced { - todo!() - } - } - } - } - - Err(CanonicalizeFieldProblem::InvalidOptionalValue { - field_name: _, - field_region: _, - }) => { - // env.problem(Problem::InvalidOptionalValue { - // field_name: field_name.clone(), - // field_region, - // record_region: region, - // }); - // return Err(CanonicalizeRecordProblem::InvalidOptionalValue { - // field_name, - // field_region, - // record_region: region, - // }); - todo!() - } - } - } - - let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool); - - for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) { - let name = PoolStr::new(string, env.pool); - - match field_var { - FieldVar::VarAndExprId(var, expr_id) => { - env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id); - } - FieldVar::OnlyVar(var) => { - env.pool[node_id] = RecordField::InvalidLabelOnly(name, var); - } // TODO RecordField::LabelOnly - } - } - - Ok((pool_vec, output)) -} - -enum CanonicalizeFieldProblem { - InvalidOptionalValue { - field_name: PoolStr, - field_region: Region, - }, -} -enum CanonicalField<'a> { - LabelAndValue { - label: &'a str, - value_expr: Expr2, - value_output: Output, - var: Variable, - }, - InvalidLabelOnly { - label: &'a str, - var: Variable, - }, // TODO make ValidLabelOnly -} -fn canonicalize_field<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>, -) -> Result, CanonicalizeFieldProblem> { - use roc_parse::ast::AssignedField::*; - - match field { - // Both a label and a value, e.g. `{ name: "blah" }` - RequiredValue(label, _, loc_expr) => { - let field_var = env.var_store.fresh(); - let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region); - - Ok(CanonicalField::LabelAndValue { - label: label.value, - value_expr: loc_can_expr, - value_output: output, - var: field_var, - }) - } - - OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue { - field_name: PoolStr::new(label.value, env.pool), - field_region: Region::span_across(&label.region, &loc_expr.region), - }), - - // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) - LabelOnly(label) => { - let field_var = env.var_store.fresh(); - // TODO return ValidLabel if label points to in scope variable - Ok(CanonicalField::InvalidLabelOnly { - label: label.value, - var: field_var, - }) - } - - SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => { - canonicalize_field(env, scope, sub_field) - } - - Malformed(_string) => { - panic!("TODO canonicalize malformed record field"); - } - } -} - -#[inline(always)] -fn canonicalize_when_branch<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - branch: &'a roc_parse::ast::WhenBranch<'a>, - output: &mut Output, -) -> (WhenBranch, References) { - let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool); - - let original_scope = scope; - let mut scope = original_scope.shallow_clone(); - - // TODO report symbols not bound in all patterns - for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) { - let (new_output, can_pattern) = to_pattern2( - env, - &mut scope, - roc_parse::pattern::PatternType::WhenBranch, - &loc_pattern.value, - loc_pattern.region, - ); - - output.union(new_output); - - env.set_region(node_id, loc_pattern.region); - env.pool[node_id] = can_pattern; - } - - let (value, mut branch_output) = - to_expr2(env, &mut scope, &branch.value.value, branch.value.region); - let value_id = env.pool.add(value); - env.set_region(value_id, branch.value.region); - - let guard = match &branch.guard { - None => None, - Some(loc_expr) => { - let (can_guard, guard_branch_output) = - to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region); - - let expr_id = env.pool.add(can_guard); - env.set_region(expr_id, loc_expr.region); - - branch_output.union(guard_branch_output); - Some(expr_id) - } - }; - - // Now that we've collected all the references for this branch, check to see if - // any of the new idents it defined were unused. If any were, report it. - for (symbol, region) in scope.symbols() { - let symbol = symbol; - - if !output.references.has_lookup(symbol) - && !branch_output.references.has_lookup(symbol) - && !original_scope.contains_symbol(symbol) - { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - let references = branch_output.references.clone(); - output.union(branch_output); - - ( - WhenBranch { - patterns, - body: value_id, - guard, - }, - references, - ) -} - -fn canonicalize_lookup( - env: &mut Env<'_>, - scope: &mut Scope, - module_name: &str, - ident: &str, - region: Region, -) -> (Expr2, Output) { - use Expr2::*; - - let mut output = Output::default(); - let can_expr = if module_name.is_empty() { - // Since module_name was empty, this is an unqualified var. - // Look it up in scope! - match scope.lookup(&(*ident).into(), region) { - Ok(symbol) => { - output.references.lookups.insert(symbol); - - Var(symbol) - } - Err(problem) => { - env.problem(Problem::RuntimeError(problem.clone())); - - RuntimeError() - } - } - } else { - // Since module_name was nonempty, this is a qualified var. - // Look it up in the env! - match env.qualified_lookup(module_name, ident, region) { - Ok(symbol) => { - output.references.lookups.insert(symbol); - - Var(symbol) - } - Err(problem) => { - // Either the module wasn't imported, or - // it was imported but it doesn't expose this ident. - env.problem(Problem::RuntimeError(problem.clone())); - - RuntimeError() - } - } - }; - - // If it's valid, this ident should be in scope already. - - (can_expr, output) -} - -fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { - match decl { - Declaration::Declare(def) => match def { - Def::AnnotationOnly { .. } => todo!(), - Def::Value(value_def) => { - let def_id = pool.add(value_def); - - let body_id = pool.add(ret); - - Expr2::LetValue { - def_id, - body_id, - body_var: var_store.fresh(), - } - } - Def::Function(function_def) => { - let def_id = pool.add(function_def); - let body_id = pool.add(ret); - - Expr2::LetFunction { - def_id, - body_id, - body_var: var_store.fresh(), - } - } - }, - Declaration::DeclareRec(defs) => { - let mut function_defs = vec![]; - - for def in defs { - match def { - Def::AnnotationOnly { .. } => todo!(), - Def::Function(function_def) => function_defs.push(function_def), - Def::Value(_) => unreachable!(), - } - } - - let body_id = pool.add(ret); - - Expr2::LetRec { - defs: PoolVec::new(function_defs.into_iter(), pool), - body_var: var_store.fresh(), - body_id, - } - } - Declaration::InvalidCycle(_entries, _) => { - // TODO: replace with something from Expr2 - // Expr::RuntimeError(RuntimeError::CircularDef(entries)) - todo!() - } - Declaration::Builtin(_) => { - // Builtins should only be added to top-level decls, not to let-exprs! - unreachable!() - } - } -} diff --git a/editor/src/lang/mod.rs b/editor/src/lang/mod.rs deleted file mode 100644 index fa747386e1..0000000000 --- a/editor/src/lang/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod ast; -pub mod constrain; -mod def; -pub mod expr; -mod module; -pub mod parse; -pub mod pattern; -pub mod pool; -pub mod roc_file; -pub mod scope; -pub mod solve; -pub mod types; diff --git a/editor/src/lang/module.rs b/editor/src/lang/module.rs deleted file mode 100644 index c0494d3193..0000000000 --- a/editor/src/lang/module.rs +++ /dev/null @@ -1,318 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_variables)] -use crate::lang::ast::{Expr2, FunctionDef, ValueDef}; -use crate::lang::def::{canonicalize_defs, sort_can_defs, Declaration, Def}; -use crate::lang::expr::Env; -use crate::lang::expr::Output; -use crate::lang::pattern::Pattern2; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use crate::lang::types::Alias; -use bumpalo::Bump; -use roc_can::operator::desugar_def; -use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; -use roc_module::ident::Ident; -use roc_module::ident::Lowercase; -use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; -use roc_parse::ast; -use roc_parse::pattern::PatternType; -use roc_problem::can::{Problem, RuntimeError}; -use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore, Variable}; - -pub struct ModuleOutput { - pub aliases: MutMap>, - pub rigid_variables: MutMap, - pub declarations: Vec, - pub exposed_imports: MutMap, - pub lookups: Vec<(Symbol, Variable, Region)>, - pub problems: Vec, - pub ident_ids: IdentIds, - pub references: MutSet, -} - -// TODO trim these down -#[allow(clippy::too_many_arguments)] -pub fn canonicalize_module_defs<'a>( - arena: &Bump, - loc_defs: &'a [Located>], - home: ModuleId, - module_ids: &ModuleIds, - exposed_ident_ids: IdentIds, - dep_idents: MutMap, - aliases: MutMap, - exposed_imports: MutMap, - mut exposed_symbols: MutSet, - var_store: &mut VarStore, -) -> Result { - let mut pool = Pool::with_capacity(1 << 10); - let mut can_exposed_imports = MutMap::default(); - let mut scope = Scope::new(home, &mut pool, var_store); - let num_deps = dep_idents.len(); - - for (name, alias) in aliases.into_iter() { - let vars = PoolVec::with_capacity(alias.targs.len() as u32, &mut pool); - - for (node_id, targ_id) in vars.iter_node_ids().zip(alias.targs.iter_node_ids()) { - let (poolstr, var) = &pool[targ_id]; - pool[node_id] = (poolstr.shallow_clone(), *var); - } - scope.add_alias(&mut pool, name, vars, alias.actual); - } - - // Desugar operators (convert them to Apply calls, taking into account - // operator precedence and associativity rules), before doing other canonicalization. - // - // If we did this *during* canonicalization, then each time we - // visited a BinOp node we'd recursively try to apply this to each of its nested - // operators, and then again on *their* nested operators, ultimately applying the - // rules multiple times unnecessarily. - let mut desugared = - bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena); - - for loc_def in loc_defs.iter() { - desugared.push(&*arena.alloc(Located { - value: desugar_def(arena, &loc_def.value), - region: loc_def.region, - })); - } - - let mut env = Env::new( - home, - arena, - &mut pool, - var_store, - dep_idents, - module_ids, - exposed_ident_ids, - ); - let mut lookups = Vec::with_capacity(num_deps); - let rigid_variables = MutMap::default(); - - // Exposed values are treated like defs that appear before any others, e.g. - // - // imports [ Foo.{ bar, baz } ] - // - // ...is basically the same as if we'd added these extra defs at the start of the module: - // - // bar = Foo.bar - // baz = Foo.baz - // - // Here we essentially add those "defs" to "the beginning of the module" - // by canonicalizing them right before we canonicalize the actual ast::Def nodes. - for (ident, (symbol, region)) in exposed_imports { - let first_char = ident.as_inline_str().chars().next().unwrap(); - - if first_char.is_lowercase() { - // this is a value definition - let expr_var = env.var_store.fresh(); - - match scope.import(ident, symbol, region) { - Ok(()) => { - // Add an entry to exposed_imports using the current module's name - // as the key; e.g. if this is the Foo module and we have - // exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when - // anything references `baz` in this Foo module, it will resolve to Bar.baz. - can_exposed_imports.insert(symbol, expr_var); - - // This will be used during constraint generation, - // to add the usual Lookup constraint as if this were a normal def. - lookups.push((symbol, expr_var, region)); - } - Err((_shadowed_symbol, _region)) => { - panic!("TODO gracefully handle shadowing in imports.") - } - } - } else { - // This is a type alias - - // the should already be added to the scope when this module is canonicalized - debug_assert!(scope.contains_alias(symbol)); - } - } - - let (defs, _scope, output, symbols_introduced) = canonicalize_defs( - &mut env, - Output::default(), - &scope, - &desugared, - PatternType::TopLevelDef, - ); - - // See if any of the new idents we defined went unused. - // If any were unused and also not exposed, report it. - for (symbol, region) in symbols_introduced { - if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) { - env.problem(Problem::UnusedDef(symbol, region)); - } - } - - // TODO register rigids - // for (var, lowercase) in output.introduced_variables.name_by_var.clone() { - // rigid_variables.insert(var, lowercase); - // } - - let mut references = MutSet::default(); - - // Gather up all the symbols that were referenced across all the defs' lookups. - for symbol in output.references.lookups.iter() { - references.insert(*symbol); - } - - // Gather up all the symbols that were referenced across all the defs' calls. - for symbol in output.references.calls.iter() { - references.insert(*symbol); - } - - // Gather up all the symbols that were referenced from other modules. - for symbol in env.qualified_lookups.iter() { - references.insert(*symbol); - } - - // NOTE previously we inserted builtin defs into the list of defs here - // this is now done later, in file.rs. - - match sort_can_defs(&mut env, defs, Output::default()) { - (Ok(mut declarations), output) => { - use Declaration::*; - - for decl in declarations.iter() { - match decl { - Declare(def) => { - for symbol in def.symbols(env.pool) { - if exposed_symbols.contains(&symbol) { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_symbols.remove(&symbol); - } - } - } - DeclareRec(defs) => { - for def in defs { - for symbol in def.symbols(env.pool) { - if exposed_symbols.contains(&symbol) { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_symbols.remove(&symbol); - } - } - } - } - - InvalidCycle(identifiers, _) => { - panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers); - } - Builtin(def) => { - // Builtins cannot be exposed in module declarations. - // This should never happen! - debug_assert!(def - .symbols(env.pool) - .iter() - .all(|symbol| !exposed_symbols.contains(symbol))); - } - } - } - - let mut aliases = MutMap::default(); - - for (symbol, alias) in output.aliases { - // Remove this from exposed_symbols, - // so that at the end of the process, - // we can see if there were any - // exposed symbols which did not have - // corresponding defs. - exposed_symbols.remove(&symbol); - - aliases.insert(symbol, alias); - } - - // By this point, all exposed symbols should have been removed from - // exposed_symbols and added to exposed_vars_by_symbol. If any were - // not, that means they were declared as exposed but there was - // no actual declaration with that name! - for symbol in exposed_symbols { - env.problem(Problem::ExposedButNotDefined(symbol)); - - // In case this exposed value is referenced by other modules, - // create a decl for it whose implementation is a runtime error. - let mut pattern_vars = SendMap::default(); - pattern_vars.insert(symbol, env.var_store.fresh()); - - let runtime_error = RuntimeError::ExposedButNotDefined(symbol); - - let value_def = { - let pattern_id = env.pool.add(Pattern2::Identifier(symbol)); - let expr_id = env.pool.add(Expr2::RuntimeError()); - ValueDef::NoAnnotation { - pattern_id, - expr_id, - expr_var: env.var_store.fresh(), - } - }; - - let def = Def::Value(value_def); - - declarations.push(Declaration::Declare(def)); - } - - // Incorporate any remaining output.lookups entries into references. - for symbol in output.references.lookups { - references.insert(symbol); - } - - // Incorporate any remaining output.calls entries into references. - for symbol in output.references.calls { - references.insert(symbol); - } - - // Gather up all the symbols that were referenced from other modules. - for symbol in env.qualified_lookups.iter() { - references.insert(*symbol); - } - - // TODO find captured variables - // for declaration in declarations.iter_mut() { - // match declaration { - // Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()), - // DeclareRec(defs) => { - // fix_values_captured_in_closure_defs(defs, &mut MutSet::default()) - // } - // InvalidCycle(_, _) | Builtin(_) => {} - // } - // } - - // TODO this loops over all symbols in the module, we can speed it up by having an - // iterator over all builtin symbols - - // TODO move over the builtins - // for symbol in references.iter() { - // if symbol.is_builtin() { - // // this can fail when the symbol is for builtin types, or has no implementation yet - // if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) { - // declarations.push(Declaration::Builtin(def)); - // } - // } - // } - - Ok(ModuleOutput { - aliases, - rigid_variables, - declarations, - references, - exposed_imports: can_exposed_imports, - problems: vec![], // TODO env.problems, - lookups, - ident_ids: env.ident_ids, - }) - } - (Err(runtime_error), _) => Err(runtime_error), - } -} diff --git a/editor/src/lang/parse.rs b/editor/src/lang/parse.rs deleted file mode 100644 index 9c7253bb75..0000000000 --- a/editor/src/lang/parse.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fmt::Debug; - -use crate::{ - editor::ed_error::ASTNodeIdWithoutExprId, editor::ed_error::EdResult, lang::scope::Scope, -}; -use bumpalo::Bump; -use roc_parse::parser::SyntaxError; -use roc_region::all::Region; - -use super::{ - ast::{DefId, Expr2, ExprId}, - expr::{str_to_def2, Env}, -}; - -#[derive(Debug)] -pub struct AST { - pub header: AppHeader, - pub def_ids: Vec, -} - -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum ASTNodeId { - ADefId(DefId), - AExprId(ExprId), -} - -impl ASTNodeId { - pub fn to_expr_id(&self) -> EdResult { - match self { - ASTNodeId::AExprId(expr_id) => Ok(*expr_id), - _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, - } - } - - pub fn to_def_id(&self) -> EdResult { - match self { - ASTNodeId::ADefId(def_id) => Ok(*def_id), - _ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?, - } - } -} - -#[derive(Debug)] -pub struct AppHeader { - pub app_name: String, - pub packages_base: String, - pub imports: Vec, - pub provides: Vec, - pub ast_node_id: ExprId, // TODO probably want to use HeaderId -} - -impl AST { - pub fn parse_from_string<'a>( - code_str: &'a str, - env: &mut Env<'a>, - ast_arena: &'a Bump, - ) -> Result> { - let blank_line_indx = code_str - .find("\n\n") - .expect("I was expecting a double newline to split header and rest of code."); - - let header_str = &code_str[0..blank_line_indx]; - let tail_str = &code_str[blank_line_indx..]; - - let mut scope = Scope::new(env.home, env.pool, env.var_store); - let region = Region::new(0, 0, 0, 0); - - let mut def_ids = Vec::::new(); - - let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?; - - for def2 in def2_vec { - let def_id = env.pool.add(def2); - - def_ids.push(def_id); - } - - let ast_node_id = env.pool.add(Expr2::Blank); - - Ok(AST { - header: AppHeader::parse_from_string(header_str, ast_node_id), - def_ids, - }) - } -} - -impl AppHeader { - // TODO don't use mock struct and actually parse string - pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> Self { - AppHeader { - app_name: "\"untitled-app\"".to_owned(), - packages_base: "\"platform\"".to_owned(), - imports: vec![], - provides: vec!["main".to_owned()], - ast_node_id, - } - } -} diff --git a/editor/src/lang/pattern.rs b/editor/src/lang/pattern.rs deleted file mode 100644 index aae7797e8a..0000000000 --- a/editor/src/lang/pattern.rs +++ /dev/null @@ -1,619 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant}; -use crate::lang::ast::{ExprId, FloatVal, IntVal}; -use crate::lang::expr::{to_expr_id, Env, Output}; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -use bumpalo::collections::Vec as BumpVec; -use roc_can::expr::unescape_char; -use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; -use roc_collections::all::BumpMap; -use roc_module::symbol::{Interns, Symbol}; -use roc_parse::ast::{StrLiteral, StrSegment}; -use roc_parse::pattern::PatternType; -use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; -use roc_region::all::Region; -use roc_types::subs::Variable; - -use super::constrain::Constraint; -use super::types::Type2; - -pub type PatternId = NodeId; - -#[derive(Debug)] -pub enum Pattern2 { - Identifier(Symbol), // 8B - NumLiteral(Variable, i64), // 4B + 8B - IntLiteral(IntVal), // 16B - FloatLiteral(FloatVal), // 16B - StrLiteral(PoolStr), // 8B - Underscore, // 0B - GlobalTag { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - tag_name: PoolStr, // 8B - arguments: PoolVec<(Variable, PatternId)>, // 8B - }, - PrivateTag { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - tag_name: Symbol, // 8B - arguments: PoolVec<(Variable, PatternId)>, // 8B - }, - RecordDestructure { - whole_var: Variable, // 4B - ext_var: Variable, // 4B - destructs: PoolVec, // 8B - }, - - // Runtime Exceptions - // TODO: figure out how to better handle regions - // to keep this member under 32. With 2 Regions - // it ends up at size 40 - Shadowed { - shadowed_ident: PoolStr, - // definition: Region, - // shadowed_at: Region, - }, - - /// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! - UnsupportedPattern(Region), - // parse error patterns - MalformedPattern(MalformedPatternProblem, Region), -} - -impl ShallowClone for Pattern2 { - fn shallow_clone(&self) -> Self { - todo!() - } -} - -#[derive(Debug)] -pub struct PatternState2<'a> { - pub headers: BumpMap, - pub vars: BumpVec<'a, Variable>, - pub constraints: BumpVec<'a, Constraint<'a>>, -} - -#[derive(Debug)] -pub struct RecordDestruct { - pub var: Variable, // 4B - pub label: PoolStr, // 8B - pub symbol: Symbol, // 8B - pub typ: NodeId, // 4B -} - -#[derive(Clone, Debug)] -pub enum DestructType { - Required, - Optional(Variable, ExprId), // 4B + 4B - Guard(Variable, PatternId), // 4B + 4B -} - -pub fn as_pattern_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - pattern_id: PatternId, - pattern_type: PatternType, - pattern: &roc_parse::ast::Pattern<'a>, - region: Region, -) -> Output { - let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); - - env.pool[pattern_id] = can_pattern; - env.set_region(pattern_id, region); - - output -} - -pub fn to_pattern_id<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - pattern_type: PatternType, - pattern: &roc_parse::ast::Pattern<'a>, - region: Region, -) -> (Output, PatternId) { - let (output, can_pattern) = to_pattern2(env, scope, pattern_type, pattern, region); - - let pattern_id = env.pool.add(can_pattern); - env.set_region(pattern_id, region); - - (output, pattern_id) -} - -pub fn to_pattern2<'a>( - env: &mut Env<'a>, - scope: &mut Scope, - pattern_type: PatternType, - pattern: &roc_parse::ast::Pattern<'a>, - region: Region, -) -> (Output, Pattern2) { - use roc_parse::ast::Pattern::*; - use PatternType::*; - - let mut output = Output::default(); - let can_pattern = match pattern { - Identifier(name) => match scope.introduce( - (*name).into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - output.references.bound_symbols.insert(symbol); - - Pattern2::Identifier(symbol) - } - Err((original_region, shadow)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - })); - - let name: &str = shadow.value.as_ref(); - - Pattern2::Shadowed { - shadowed_ident: PoolStr::new(name, env.pool), - } - } - }, - - QualifiedIdentifier { .. } => { - let problem = MalformedPatternProblem::QualifiedIdentifier; - malformed_pattern(env, problem, region) - } - - Underscore(_) => match pattern_type { - WhenBranch | FunctionArg => Pattern2::Underscore, - TopLevelDef | DefExpr => underscore_in_def(env, region), - }, - - FloatLiteral(ref string) => match pattern_type { - WhenBranch => match finish_parsing_float(string) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedFloat; - malformed_pattern(env, problem, region) - } - Ok(float) => Pattern2::FloatLiteral(FloatVal::F64(float)), - }, - ptype => unsupported_pattern(env, ptype, region), - }, - - NumLiteral(string) => match pattern_type { - WhenBranch => match finish_parsing_int(string) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedInt; - malformed_pattern(env, problem, region) - } - Ok(int) => Pattern2::NumLiteral(env.var_store.fresh(), int), - }, - ptype => unsupported_pattern(env, ptype, region), - }, - - NonBase10Literal { - string, - base, - is_negative, - } => match pattern_type { - WhenBranch => match finish_parsing_base(string, *base, *is_negative) { - Err(_error) => { - let problem = MalformedPatternProblem::MalformedBase(*base); - malformed_pattern(env, problem, region) - } - Ok(int) => { - if *is_negative { - Pattern2::IntLiteral(IntVal::I64(-int)) - } else { - Pattern2::IntLiteral(IntVal::I64(int)) - } - } - }, - ptype => unsupported_pattern(env, ptype, region), - }, - - StrLiteral(literal) => match pattern_type { - WhenBranch => flatten_str_literal(env.pool, literal), - ptype => unsupported_pattern(env, ptype, region), - }, - - GlobalTag(name) => { - // Canonicalize the tag's name. - Pattern2::GlobalTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: PoolStr::new(name, env.pool), - arguments: PoolVec::empty(env.pool), - } - } - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&(*name).into()); - - // Canonicalize the tag's name. - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: PoolVec::empty(env.pool), - } - } - - Apply(tag, patterns) => { - let can_patterns = PoolVec::with_capacity(patterns.len() as u32, env.pool); - for (loc_pattern, node_id) in (*patterns).iter().zip(can_patterns.iter_node_ids()) { - let (new_output, can_pattern) = to_pattern2( - env, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ); - - output.union(new_output); - - let can_pattern_id = env.pool.add(can_pattern); - - env.pool[node_id] = (env.var_store.fresh(), can_pattern_id); - } - - match tag.value { - GlobalTag(name) => Pattern2::GlobalTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: PoolStr::new(name, env.pool), - arguments: can_patterns, - }, - PrivateTag(name) => { - let ident_id = env.ident_ids.get_or_insert(&name.into()); - - Pattern2::PrivateTag { - whole_var: env.var_store.fresh(), - ext_var: env.var_store.fresh(), - tag_name: Symbol::new(env.home, ident_id), - arguments: can_patterns, - } - } - _ => unreachable!("Other patterns cannot be applied"), - } - } - - RecordDestructure(patterns) => { - let ext_var = env.var_store.fresh(); - let whole_var = env.var_store.fresh(); - let destructs = PoolVec::with_capacity(patterns.len() as u32, env.pool); - let opt_erroneous = None; - - for (node_id, loc_pattern) in destructs.iter_node_ids().zip((*patterns).iter()) { - match loc_pattern.value { - Identifier(label) => { - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - output.references.bound_symbols.insert(symbol); - - let destruct = RecordDestruct { - var: env.var_store.fresh(), - label: PoolStr::new(label, env.pool), - symbol, - typ: env.pool.add(DestructType::Required), - }; - - env.pool[node_id] = destruct; - env.set_region(node_id, loc_pattern.region); - } - Err((original_region, shadow)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - })); - - // let shadowed = Pattern2::Shadowed { - // definition: original_region, - // shadowed_at: loc_pattern.region, - // shadowed_ident: shadow.value, - // }; - - // No matter what the other patterns - // are, we're definitely shadowed and will - // get a runtime exception as soon as we - // encounter the first bad pattern. - // opt_erroneous = Some(); - // env.pool[node_id] = sha; - // env.set_region(node_id, loc_pattern.region); - todo!("we must both report/store the problem, but also not lose any information") - } - }; - } - - RequiredField(label, loc_guard) => { - // a guard does not introduce the label into scope! - let symbol = scope.ignore(label.into(), &mut env.ident_ids); - let (new_output, can_guard) = to_pattern_id( - env, - scope, - pattern_type, - &loc_guard.value, - loc_guard.region, - ); - - let destruct = RecordDestruct { - var: env.var_store.fresh(), - label: PoolStr::new(label, env.pool), - symbol, - typ: env - .pool - .add(DestructType::Guard(env.var_store.fresh(), can_guard)), - }; - - output.union(new_output); - - env.pool[node_id] = destruct; - env.set_region(node_id, loc_pattern.region); - } - OptionalField(label, loc_default) => { - // an optional DOES introduce the label into scope! - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - let (can_default, expr_output) = - to_expr_id(env, scope, &loc_default.value, loc_default.region); - - // an optional field binds the symbol! - output.references.bound_symbols.insert(symbol); - - output.union(expr_output); - - let destruct = RecordDestruct { - var: env.var_store.fresh(), - label: PoolStr::new(label, env.pool), - symbol, - typ: env.pool.add(DestructType::Optional( - env.var_store.fresh(), - can_default, - )), - }; - - env.pool[node_id] = destruct; - env.set_region(node_id, loc_pattern.region); - } - Err((original_region, shadow)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - })); - - // No matter what the other patterns - // are, we're definitely shadowed and will - // get a runtime exception as soon as we - // encounter the first bad pattern. - // opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); - todo!("must report problem but also not loose any information") - } - }; - } - _ => unreachable!("Any other pattern should have given a parse error"), - } - } - - // If we encountered an erroneous pattern (e.g. one with shadowing), - // use the resulting RuntimeError. Otherwise, return a successful record destructure. - opt_erroneous.unwrap_or(Pattern2::RecordDestructure { - whole_var, - ext_var, - destructs, - }) - } - - RequiredField(_name, _loc_pattern) => { - unreachable!("should have been handled in RecordDestructure"); - } - OptionalField(_name, _loc_pattern) => { - unreachable!("should have been handled in RecordDestructure"); - } - - Malformed(_str) => { - let problem = MalformedPatternProblem::Unknown; - malformed_pattern(env, problem, region) - } - - MalformedIdent(_str, bad_ident) => { - let problem = MalformedPatternProblem::BadIdent(*bad_ident); - malformed_pattern(env, problem, region) - } - - SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { - return to_pattern2(env, scope, pattern_type, sub_pattern, region) - } - }; - - (output, can_pattern) -} - -pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec { - use Pattern2::*; - let mut symbols = Vec::new(); - let mut stack = vec![initial]; - - while let Some(pattern) = stack.pop() { - match pattern { - Identifier(symbol) => { - symbols.push(*symbol); - } - - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { - for (_, pat_id) in arguments.iter(pool) { - let pat = pool.get(*pat_id); - stack.push(pat); - } - } - - RecordDestructure { destructs, .. } => { - for destruct in destructs.iter(pool) { - let destruct_type = pool.get(destruct.typ); - - if let DestructType::Guard(_, subpattern_id) = &destruct_type { - let subpattern = pool.get(*subpattern_id); - stack.push(subpattern); - } else { - symbols.push(destruct.symbol); - } - } - } - - NumLiteral(_, _) - | IntLiteral(_) - | FloatLiteral(_) - | StrLiteral(_) - | Underscore - | MalformedPattern(_, _) - | Shadowed { .. } - | UnsupportedPattern(_) => {} - } - } - - symbols -} - -pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult { - match pattern { - Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), - other => UnexpectedPattern2Variant { - required_pattern2: "Identifier".to_string(), - encountered_pattern2: format!("{:?}", other), - } - .fail()?, - } -} - -pub fn symbols_and_variables_from_pattern( - pool: &Pool, - initial: &Pattern2, - initial_var: Variable, -) -> Vec<(Symbol, Variable)> { - use Pattern2::*; - let mut symbols = Vec::new(); - let mut stack = vec![(initial_var, initial)]; - - while let Some((variable, pattern)) = stack.pop() { - match pattern { - Identifier(symbol) => { - symbols.push((*symbol, variable)); - } - - GlobalTag { arguments, .. } | PrivateTag { arguments, .. } => { - for (var, pat_id) in arguments.iter(pool) { - let pat = pool.get(*pat_id); - stack.push((*var, pat)); - } - } - - RecordDestructure { destructs, .. } => { - for destruct in destructs.iter(pool) { - let destruct_type = pool.get(destruct.typ); - - if let DestructType::Guard(_, subpattern_id) = &destruct_type { - let subpattern = pool.get(*subpattern_id); - stack.push((destruct.var, subpattern)); - } else { - symbols.push((destruct.symbol, destruct.var)); - } - } - } - - NumLiteral(_, _) - | IntLiteral(_) - | FloatLiteral(_) - | StrLiteral(_) - | Underscore - | MalformedPattern(_, _) - | Shadowed { .. } - | UnsupportedPattern(_) => {} - } - } - - symbols -} - -/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't -/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern. -fn unsupported_pattern<'a>( - env: &mut Env<'a>, - pattern_type: PatternType, - region: Region, -) -> Pattern2 { - use roc_problem::can::BadPattern; - env.problem(Problem::UnsupportedPattern( - BadPattern::Unsupported(pattern_type), - region, - )); - - Pattern2::UnsupportedPattern(region) -} - -fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { - use roc_problem::can::BadPattern; - env.problem(Problem::UnsupportedPattern( - BadPattern::UnderscoreInDef, - region, - )); - - Pattern2::UnsupportedPattern(region) -} - -fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { - use roc_parse::ast::StrLiteral::*; - - match literal { - PlainLine(str_slice) => Pattern2::StrLiteral(PoolStr::new(str_slice, pool)), - Line(segments) => flatten_str_lines(pool, &[segments]), - Block(lines) => flatten_str_lines(pool, lines), - } -} - -fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { - use StrSegment::*; - - let mut buf = String::new(); - - for line in lines { - for segment in line.iter() { - match segment { - Plaintext(string) => { - buf.push_str(string); - } - Unicode(loc_digits) => { - todo!("parse unicode digits {:?}", loc_digits); - } - Interpolated(loc_expr) => { - return Pattern2::UnsupportedPattern(loc_expr.region); - } - EscapedChar(escaped) => buf.push(unescape_char(escaped)), - } - } - } - - Pattern2::StrLiteral(PoolStr::new(&buf, pool)) -} - -/// When we detect a malformed pattern like `3.X` or `0b5`, -/// report it to Env and return an UnsupportedPattern runtime error pattern. -fn malformed_pattern<'a>( - env: &mut Env<'a>, - problem: MalformedPatternProblem, - region: Region, -) -> Pattern2 { - env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( - problem, region, - ))); - - Pattern2::MalformedPattern(problem, region) -} diff --git a/editor/src/lang/pool.rs b/editor/src/lang/pool.rs deleted file mode 100644 index ac0fd67206..0000000000 --- a/editor/src/lang/pool.rs +++ /dev/null @@ -1,635 +0,0 @@ -/// A pool of 32-byte nodes. The node value 0 is reserved for the pool's -/// use, and valid nodes may never have that value. -/// -/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes -/// into one page at a time, and when it runs out, it uses mmap to reserve an -/// anonymous memory page in which to store nodes. -/// -/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a -/// particular node by its NodeId, which is an opaque wrapper around a pointer. -/// -/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied. -/// This is important for performance. -use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; -use roc_can::expected::Expected; -use roc_can::expected::PExpected; -use std::any::type_name; -use std::cmp::Ordering; -use std::marker::PhantomData; -use std::mem::size_of; -use std::ptr::null; - -pub const NODE_BYTES: usize = 32; - -// Each page has 128 slots. Each slot holds one 32B node -// This means each page is 4096B, which is the size of a memory page -// on typical systems where the compiler will be run. -// -// Nice things about this system include: -// * Allocating a new pagShallowCloneal index (supporting up to 32 generations), then -// we would have 27 bits remaining, meaning we could only support at most -// 134M nodes. Since the editor has a separate Pool for each module, is that -// enough for any single module we'll encounter in practice? Probably, and -// especially if we allocate super large collection literals on the heap instead -// of in the pool. -// -// Another possible design is to try to catch reuse bugs using an "ASan" like -// approach: in development builds, whenever we "free" a particular slot, we -// can add it to a dev-build-only "freed nodes" list and don't hand it back -// out (so, we leak the memory.) Then we can (again, in development builds only) -// check to see if we're about to store something in zeroed-out memory; if so, check -// to see if it was - -#[derive(Debug, Eq)] -pub struct NodeId { - index: u32, - _phantom: PhantomData, -} - -impl Clone for NodeId { - fn clone(&self) -> Self { - NodeId { - index: self.index, - _phantom: PhantomData::default(), - } - } -} - -impl PartialEq for NodeId { - fn eq(&self, other: &Self) -> bool { - self.index == other.index - } -} - -impl Copy for NodeId {} - -#[derive(Debug)] -pub struct Pool { - nodes: *mut [u8; NODE_BYTES], - num_nodes: u32, - capacity: u32, - // free_1node_slots: Vec>, -} - -impl Pool { - pub fn with_capacity(nodes: u32) -> Self { - // round up number of nodes requested to nearest page size in bytes - let bytes_per_page = page_size::get(); - let node_bytes = NODE_BYTES * nodes as usize; - let leftover = node_bytes % bytes_per_page; - let bytes_to_mmap = if leftover == 0 { - node_bytes - } else { - node_bytes + bytes_per_page - leftover - }; - - let nodes = unsafe { - // mmap anonymous memory pages - that is, contiguous virtual memory - // addresses from the OS which will be lazily translated into - // physical memory one 4096-byte page at a time, once we actually - // try to read or write in that page's address range. - libc::mmap( - null::() as *mut c_void, - bytes_to_mmap, - PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS, - 0, - 0, - ) - } as *mut [u8; NODE_BYTES]; - - // This is our actual capacity, in nodes. - // It might be higher than the requested capacity due to rounding up - // to nearest page size. - let capacity = (bytes_to_mmap / NODE_BYTES) as u32; - - Pool { - nodes, - num_nodes: 0, - capacity, - } - } - - pub fn add(&mut self, node: T) -> NodeId { - // It's only safe to store this if T fits in S. - debug_assert!( - size_of::() <= NODE_BYTES, - "{} has a size of {}, but it needs to be at most {}", - type_name::(), - size_of::(), - NODE_BYTES - ); - - let node_id = self.reserve(1); - let node_ptr = unsafe { self.nodes.offset(node_id.index as isize) } as *mut T; - - unsafe { *node_ptr = node }; - - node_id - } - - /// Reserves the given number of contiguous node slots, and returns - /// the NodeId of the first one. We only allow reserving 2^32 in a row. - fn reserve(&mut self, nodes: u32) -> NodeId { - // TODO once we have a free list, look in there for an open slot first! - let index = self.num_nodes; - - if index < self.capacity { - self.num_nodes = index + nodes; - - NodeId { - index, - _phantom: PhantomData::default(), - } - } else { - todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows."); - } - } - - pub fn get<'a, 'b, T>(&'a self, node_id: NodeId) -> &'b T { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *const T; - - &*node_ptr - } - } - - pub fn get_mut(&mut self, node_id: NodeId) -> &mut T { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; - - &mut *node_ptr - } - } - - pub fn set(&mut self, node_id: NodeId, element: T) { - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T; - - *node_ptr = element; - } - } - - // A node is available iff its bytes are all zeroes - #[allow(dead_code)] - fn is_available(&self, node_id: NodeId) -> bool { - debug_assert_eq!(size_of::(), NODE_BYTES); - - unsafe { - let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES]; - - *node_ptr == [0; NODE_BYTES] - } - } -} - -impl std::ops::Index> for Pool { - type Output = T; - - fn index(&self, node_id: NodeId) -> &Self::Output { - self.get(node_id) - } -} - -impl std::ops::IndexMut> for Pool { - fn index_mut(&mut self, node_id: NodeId) -> &mut Self::Output { - self.get_mut(node_id) - } -} - -impl Drop for Pool { - fn drop(&mut self) { - unsafe { - libc::munmap( - self.nodes as *mut c_void, - NODE_BYTES * self.capacity as usize, - ); - } - } -} - -/// A string containing at most 2^32 pool-allocated bytes. -#[derive(Debug, Copy, Clone)] -pub struct PoolStr { - first_node_id: NodeId<()>, - len: u32, -} - -#[test] -fn pool_str_size() { - assert_eq!(size_of::(), 8); -} - -impl PoolStr { - pub fn new(string: &str, pool: &mut Pool) -> Self { - debug_assert!(string.len() <= u32::MAX as usize); - - let chars_per_node = NODE_BYTES / size_of::(); - - let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32; - - if number_of_nodes > 0 { - let first_node_id = pool.reserve(number_of_nodes); - let index = first_node_id.index as isize; - let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void; - - unsafe { - libc::memcpy( - next_node_ptr, - string.as_ptr() as *const c_void, - string.len(), - ); - } - - PoolStr { - first_node_id, - len: string.len() as u32, - } - } else { - PoolStr { - first_node_id: NodeId { - index: 0, - _phantom: PhantomData::default(), - }, - len: 0, - } - } - } - - pub fn as_str(&self, pool: &Pool) -> &str { - unsafe { - let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8; - - let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize); - - std::str::from_utf8_unchecked(&node_slice[0..self.len as usize]) - } - } - - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, pool: &Pool) -> usize { - let contents = self.as_str(pool); - - contents.len() - } - - pub fn is_empty(&self, pool: &Pool) -> bool { - self.len(pool) == 0 - } -} - -impl ShallowClone for PoolStr { - fn shallow_clone(&self) -> Self { - // Question: should this fully clone, or is a shallow copy - // (and the aliasing it entails) OK? - Self { - first_node_id: self.first_node_id, - len: self.len, - } - } -} - -/// An array of at most 2^32 pool-allocated nodes. -#[derive(Debug)] -pub struct PoolVec { - first_node_id: NodeId, - len: u32, -} - -#[test] -fn pool_vec_size() { - assert_eq!(size_of::>(), 8); -} - -impl<'a, T: 'a + Sized> PoolVec { - pub fn empty(pool: &mut Pool) -> Self { - Self::new(std::iter::empty(), pool) - } - - pub fn with_capacity(len: u32, pool: &mut Pool) -> Self { - debug_assert!( - size_of::() <= NODE_BYTES, - "{} has a size of {}", - type_name::(), - size_of::() - ); - - if len == 0 { - Self::empty(pool) - } else { - let first_node_id = pool.reserve(len); - - PoolVec { first_node_id, len } - } - } - - pub fn len(&self) -> usize { - self.len as usize - } - - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - pub fn new>(nodes: I, pool: &mut Pool) -> Self { - debug_assert!(nodes.len() <= u32::MAX as usize); - debug_assert!(size_of::() <= NODE_BYTES); - - let len = nodes.len() as u32; - - if len > 0 { - let first_node_id = pool.reserve(len); - let index = first_node_id.index as isize; - let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T; - - for (indx_inc, node) in nodes.enumerate() { - unsafe { - *next_node_ptr = node; - - next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T; - } - } - - PoolVec { first_node_id, len } - } else { - PoolVec { - first_node_id: NodeId { - index: 0, - _phantom: PhantomData::default(), - }, - len: 0, - } - } - } - - pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator { - self.pool_list_iter(pool) - } - - pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator { - self.pool_list_iter_mut(pool) - } - - pub fn iter_node_ids(&self) -> impl ExactSizeIterator> { - self.pool_list_iter_node_ids() - } - - /// Private version of into_iter which exposes the implementation detail - /// of PoolVecIter. We don't want that struct to be public, but we - /// actually do want to have this separate function for code reuse - /// in the iterator's next() method. - #[inline(always)] - fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> { - PoolVecIter { - pool, - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - #[inline(always)] - fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> { - PoolVecIterMut { - pool, - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - #[inline(always)] - fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds { - PoolVecIterNodeIds { - current_node_id: self.first_node_id, - len_remaining: self.len, - } - } - - pub fn free(self, pool: &'a mut Pool) { - // zero out the memory - unsafe { - let index = self.first_node_id.index as isize; - let node_ptr = pool.nodes.offset(index) as *mut c_void; - let bytes = self.len as usize * NODE_BYTES; - - libc::memset(node_ptr, 0, bytes); - } - - // TODO insert it into the pool's free list - } -} - -impl ShallowClone for PoolVec { - fn shallow_clone(&self) -> Self { - // Question: should this fully clone, or is a shallow copy - // (and the aliasing it entails) OK? - Self { - first_node_id: self.first_node_id, - len: self.len, - } - } -} - -struct PoolVecIter<'a, T> { - pool: &'a Pool, - current_node_id: NodeId, - len_remaining: u32, -} - -impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T> -where - T: 'a, -{ - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl<'a, T> Iterator for PoolVecIter<'a, T> -where - T: 'a, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(unsafe { &*node_ptr }) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T; - - Some(unsafe { &*node_ptr }) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -struct PoolVecIterMut<'a, T> { - pool: &'a Pool, - current_node_id: NodeId, - len_remaining: u32, -} - -impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T> -where - T: 'a, -{ - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl<'a, T> Iterator for PoolVecIterMut<'a, T> -where - T: 'a, -{ - type Item = &'a mut T; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(unsafe { &mut *node_ptr }) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - let index = self.current_node_id.index; - let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T; - - Some(unsafe { &mut *node_ptr }) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -struct PoolVecIterNodeIds { - current_node_id: NodeId, - len_remaining: u32, -} - -impl ExactSizeIterator for PoolVecIterNodeIds { - fn len(&self) -> usize { - self.len_remaining as usize - } -} - -impl Iterator for PoolVecIterNodeIds { - type Item = NodeId; - - fn next(&mut self) -> Option { - let len_remaining = self.len_remaining; - - match len_remaining.cmp(&1) { - Ordering::Greater => { - // Get the current node - let current = self.current_node_id; - let index = current.index; - - // Advance the node pointer to the next node in the current page - self.current_node_id = NodeId { - index: index + 1, - _phantom: PhantomData::default(), - }; - self.len_remaining = len_remaining - 1; - - Some(current) - } - Ordering::Equal => { - self.len_remaining = 0; - - // Don't advance the node pointer's node, because that might - // advance past the end of the page! - - Some(self.current_node_id) - } - Ordering::Less => { - // len_remaining was 0 - None - } - } - } -} - -#[test] -fn pool_vec_iter_test() { - let expected_vec: Vec = vec![2, 4, 8, 16]; - - let mut test_pool = Pool::with_capacity(1024); - let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool); - - let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); - - assert_eq!(current_vec, expected_vec); -} -/// Clones the outer node, but does not clone any nodeids -pub trait ShallowClone { - fn shallow_clone(&self) -> Self; -} - -impl ShallowClone for Expected { - fn shallow_clone(&self) -> Self { - use Expected::*; - - match self { - NoExpectation(t) => NoExpectation(t.shallow_clone()), - ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), - FromAnnotation(loc_pat, n, source, t) => { - FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone()) - } - } - } -} - -impl ShallowClone for PExpected { - fn shallow_clone(&self) -> Self { - use PExpected::*; - - match self { - NoExpectation(t) => NoExpectation(t.shallow_clone()), - ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region), - } - } -} diff --git a/editor/src/lang/roc_file.rs b/editor/src/lang/roc_file.rs deleted file mode 100644 index 5379e9097a..0000000000 --- a/editor/src/lang/roc_file.rs +++ /dev/null @@ -1,133 +0,0 @@ -use bumpalo::collections::Vec; -use bumpalo::Bump; -use roc_fmt::def::fmt_def; -use roc_fmt::module::fmt_module; -use roc_parse::ast::{Def, Module}; -use roc_parse::module::module_defs; -use roc_parse::parser; -use roc_parse::parser::{Parser, SyntaxError}; -use roc_region::all::Located; -use std::ffi::OsStr; -use std::path::Path; -use std::{fs, io}; - -#[derive(Debug)] -pub struct File<'a> { - path: &'a Path, - module_header: Module<'a>, - content: Vec<'a, Located>>, -} - -#[derive(Debug)] -pub enum ReadError<'a> { - Read(std::io::Error), - ParseDefs(SyntaxError<'a>), - ParseHeader(SyntaxError<'a>), - DoesntHaveRocExtension, -} - -impl<'a> File<'a> { - pub fn read(path: &'a Path, arena: &'a Bump) -> Result, ReadError<'a>> { - if path.extension() != Some(OsStr::new("roc")) { - return Err(ReadError::DoesntHaveRocExtension); - } - - let bytes = fs::read(path).map_err(ReadError::Read)?; - - let allocation = arena.alloc(bytes); - - let module_parse_state = parser::State::new(allocation); - let parsed_module = roc_parse::module::parse_header(arena, module_parse_state); - - match parsed_module { - Ok((module, state)) => { - let parsed_defs = module_defs().parse(arena, state); - - match parsed_defs { - Ok((_, defs, _)) => Ok(File { - path, - module_header: module, - content: defs, - }), - Err((_, error, _)) => Err(ReadError::ParseDefs(error)), - } - } - Err(error) => Err(ReadError::ParseHeader(SyntaxError::Header(error))), - } - } - - pub fn fmt(&self) -> String { - let arena = Bump::new(); - let mut formatted_file = String::new(); - - let mut module_header_buf = bumpalo::collections::String::new_in(&arena); - fmt_module(&mut module_header_buf, &self.module_header); - - formatted_file.push_str(module_header_buf.as_str()); - - for def in &self.content { - let mut def_buf = bumpalo::collections::String::new_in(&arena); - - fmt_def(&mut def_buf, &def.value, 0); - - formatted_file.push_str(def_buf.as_str()); - } - - formatted_file - } - - pub fn fmt_then_write_to(&self, write_path: &'a Path) -> io::Result<()> { - let formatted_file = self.fmt(); - - fs::write(write_path, formatted_file) - } - - pub fn fmt_then_write_with_name(&self, new_name: &str) -> io::Result<()> { - self.fmt_then_write_to( - self.path - .with_file_name(new_name) - .with_extension("roc") - .as_path(), - ) - } - - pub fn fmt_then_write(&self) -> io::Result<()> { - self.fmt_then_write_to(self.path) - } -} - -#[cfg(test)] -mod test_file { - use crate::lang::roc_file; - use bumpalo::Bump; - use std::path::Path; - - #[test] - fn read_and_fmt_simple_roc_module() { - let simple_module_path = Path::new("./tests/modules/SimpleUnformatted.roc"); - - let arena = Bump::new(); - - let file = roc_file::File::read(simple_module_path, &arena) - .expect("Could not read SimpleUnformatted.roc in test_file test"); - - assert_eq!( - file.fmt(), - indoc!( - r#" - interface Simple - exposes [ - v, x - ] - imports [] - - v : Str - - v = "Value!" - - x : Int - x = 4"# - ) - ); - } -} diff --git a/editor/src/lang/scope.rs b/editor/src/lang/scope.rs deleted file mode 100644 index e17f3e09a9..0000000000 --- a/editor/src/lang/scope.rs +++ /dev/null @@ -1,325 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use crate::lang::pool::{Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::types::{Alias, Type2, TypeId}; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, Lowercase}; -use roc_module::symbol::{IdentIds, ModuleId, Symbol}; -use roc_problem::can::RuntimeError; -use roc_region::all::{Located, Region}; -use roc_types::{ - builtin_aliases, - solved_types::{BuiltinAlias, FreeVars, SolvedType}, - subs::{VarId, VarStore, Variable}, -}; - -fn solved_type_to_type_id( - pool: &mut Pool, - solved_type: &SolvedType, - free_vars: &mut FreeVars, - var_store: &mut VarStore, -) -> TypeId { - let typ2 = to_type2(pool, solved_type, free_vars, var_store); - - pool.add(typ2) -} - -fn to_type2( - pool: &mut Pool, - solved_type: &SolvedType, - free_vars: &mut FreeVars, - var_store: &mut VarStore, -) -> Type2 { - match solved_type { - SolvedType::Alias(symbol, solved_type_variables, _todo, solved_actual) => { - let type_variables = PoolVec::with_capacity(solved_type_variables.len() as u32, pool); - - for (type_variable_node_id, (lowercase, solved_arg)) in type_variables - .iter_node_ids() - .zip(solved_type_variables.iter()) - { - let typ2 = to_type2(pool, solved_arg, free_vars, var_store); - - let node = pool.add(typ2); - - pool[type_variable_node_id] = (PoolStr::new(lowercase.as_str(), pool), node); - } - - let actual_typ2 = to_type2(pool, solved_actual, free_vars, var_store); - - let actual = pool.add(actual_typ2); - - let typ2 = Type2::Alias(*symbol, type_variables, actual); - - typ2 - } - SolvedType::TagUnion(tags, ext) => { - let new_tags = PoolVec::with_capacity(tags.len() as u32, pool); - - for (tag_node_id, (tag_name, args)) in new_tags.iter_node_ids().zip(tags.iter()) { - let new_args: PoolVec = PoolVec::with_capacity(args.len() as u32, pool); - - for (arg_node_id, arg) in new_args.iter_node_ids().zip(args.iter()) { - let node = to_type2(pool, arg, free_vars, var_store); - - pool[arg_node_id] = node; - } - - pool[tag_node_id] = (tag_name.clone(), new_args); - } - - let actual_typ2 = to_type2(pool, ext, free_vars, var_store); - - let actual = pool.add(actual_typ2); - - let typ2 = Type2::TagUnion(new_tags, actual); - - typ2 - } - SolvedType::Flex(var_id) => { - Type2::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)) - } - SolvedType::EmptyTagUnion => Type2::EmptyTagUnion, - rest => todo!("{:?}", rest), - } -} - -fn var_id_to_flex_var( - var_id: VarId, - free_vars: &mut FreeVars, - var_store: &mut VarStore, -) -> Variable { - if let Some(var) = free_vars.unnamed_vars.get(&var_id) { - *var - } else { - let var = var_store.fresh(); - free_vars.unnamed_vars.insert(var_id, var); - - var - } -} - -#[derive(Debug)] -pub struct Scope { - /// All the identifiers in scope, mapped to were they were defined and - /// the Symbol they resolve to. - idents: MutMap, - - /// A cache of all the symbols in scope. This makes lookups much - /// faster when checking for unused defs and unused arguments. - symbols: MutMap, - - /// The type aliases currently in scope - aliases: MutMap, - - /// The current module being processed. This will be used to turn - /// unqualified idents into Symbols. - home: ModuleId, -} - -impl Scope { - pub fn new(home: ModuleId, pool: &mut Pool, var_store: &mut VarStore) -> Scope { - let solved_aliases = builtin_aliases::aliases(); - let mut aliases = MutMap::default(); - - for (symbol, builtin_alias) in solved_aliases { - // let BuiltinAlias { region, vars, typ } = builtin_alias; - let BuiltinAlias { vars, typ, .. } = builtin_alias; - - let mut free_vars = FreeVars::default(); - - // roc_types::solved_types::to_type(&typ, &mut free_vars, var_store); - let actual = solved_type_to_type_id(pool, &typ, &mut free_vars, var_store); - - // make sure to sort these variables to make them line up with the type arguments - let mut type_variables: Vec<_> = free_vars.unnamed_vars.into_iter().collect(); - type_variables.sort(); - - debug_assert_eq!(vars.len(), type_variables.len()); - let variables = PoolVec::with_capacity(vars.len() as u32, pool); - - let it = variables - .iter_node_ids() - .zip(vars.iter()) - .zip(type_variables); - for ((node_id, loc_name), (_, var)) in it { - // TODO region is ignored, but "fake" anyway. How to resolve? - let name = PoolStr::new(loc_name.value.as_str(), pool); - pool[node_id] = (name, var); - } - - let alias = Alias { - actual, - /// We know that builtin aliases have no hiddden variables (e.g. in closures) - hidden_variables: PoolVec::empty(pool), - targs: variables, - }; - - aliases.insert(symbol, alias); - } - - let idents = Symbol::default_in_scope(); - let idents: MutMap<_, _> = idents.into_iter().collect(); - - Scope { - home, - idents, - symbols: MutMap::default(), - aliases, - } - } - - pub fn idents(&self) -> impl Iterator { - self.idents.iter() - } - - pub fn symbols(&self) -> impl Iterator + '_ { - self.symbols.iter().map(|(x, y)| (*x, *y)) - } - - pub fn contains_ident(&self, ident: &Ident) -> bool { - self.idents.contains_key(ident) - } - - pub fn contains_symbol(&self, symbol: Symbol) -> bool { - self.symbols.contains_key(&symbol) - } - - pub fn num_idents(&self) -> usize { - self.idents.len() - } - - pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result { - match self.idents.get(ident) { - Some((symbol, _)) => Ok(*symbol), - None => Err(RuntimeError::LookupNotInScope( - Located { - region, - value: ident.clone().into(), - }, - self.idents.keys().map(|v| v.as_ref().into()).collect(), - )), - } - } - - pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> { - self.aliases.get(&symbol) - } - - /// Introduce a new ident to scope. - /// - /// Returns Err if this would shadow an existing ident, including the - /// Symbol and Region of the ident we already had in scope under that name. - pub fn introduce( - &mut self, - ident: Ident, - exposed_ident_ids: &IdentIds, - all_ident_ids: &mut IdentIds, - region: Region, - ) -> Result)> { - match self.idents.get(&ident) { - Some((_, original_region)) => { - let shadow = Located { - value: ident, - region, - }; - - Err((*original_region, shadow)) - } - None => { - // If this IdentId was already added previously - // when the value was exposed in the module header, - // use that existing IdentId. Otherwise, create a fresh one. - let ident_id = match exposed_ident_ids.get_id(&ident) { - Some(ident_id) => *ident_id, - None => all_ident_ids.add(ident.clone().into()), - }; - - let symbol = Symbol::new(self.home, ident_id); - - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); - - Ok(symbol) - } - } - } - - /// Ignore an identifier. - /// - /// Used for record guards like { x: Just _ } - pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { - let ident_id = all_ident_ids.add(ident.into()); - Symbol::new(self.home, ident_id) - } - - /// Import a Symbol from another module into this module's top-level scope. - /// - /// Returns Err if this would shadow an existing ident, including the - /// Symbol and Region of the ident we already had in scope under that name. - pub fn import( - &mut self, - ident: Ident, - symbol: Symbol, - region: Region, - ) -> Result<(), (Symbol, Region)> { - match self.idents.get(&ident) { - Some(shadowed) => Err(*shadowed), - None => { - self.symbols.insert(symbol, region); - self.idents.insert(ident, (symbol, region)); - - Ok(()) - } - } - } - - pub fn add_alias( - &mut self, - pool: &mut Pool, - name: Symbol, - vars: PoolVec<(PoolStr, Variable)>, - typ: TypeId, - ) { - let mut hidden_variables = MutSet::default(); - hidden_variables.extend(typ.variables(pool)); - - for loc_var in vars.iter(pool) { - hidden_variables.remove(&loc_var.1); - } - - let hidden_variables_vec = PoolVec::with_capacity(hidden_variables.len() as u32, pool); - - for (node_id, var) in hidden_variables_vec.iter_node_ids().zip(hidden_variables) { - pool[node_id] = var; - } - - let alias = Alias { - targs: vars, - hidden_variables: hidden_variables_vec, - actual: typ, - }; - - self.aliases.insert(name, alias); - } - - pub fn contains_alias(&mut self, name: Symbol) -> bool { - self.aliases.contains_key(&name) - } -} - -impl ShallowClone for Scope { - fn shallow_clone(&self) -> Self { - Self { - idents: self.idents.clone(), - symbols: self.symbols.clone(), - aliases: self - .aliases - .iter() - .map(|(s, a)| (*s, a.shallow_clone())) - .collect(), - home: self.home, - } - } -} diff --git a/editor/src/lang/solve.rs b/editor/src/lang/solve.rs deleted file mode 100644 index 68b29c7635..0000000000 --- a/editor/src/lang/solve.rs +++ /dev/null @@ -1,1752 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -use crate::lang::constrain::Constraint::{self, *}; -use crate::lang::pool::{Pool, PoolVec, ShallowClone}; -use crate::lang::types::Type2; -use bumpalo::Bump; -use roc_can::expected::{Expected, PExpected}; -use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; -use roc_module::ident::TagName; -use roc_module::symbol::Symbol; -use roc_region::all::{Located, Region}; -use roc_types::solved_types::Solved; -use roc_types::subs::{ - AliasVariables, Content, Descriptor, FlatType, Mark, OptVariable, Rank, RecordFields, Subs, - SubsSlice, UnionTags, Variable, VariableSubsSlice, -}; -use roc_types::types::{ - gather_fields_unsorted_iter, Alias, Category, ErrorType, PatternCategory, RecordField, -}; -use roc_unify::unify::unify; -use roc_unify::unify::Unified::*; - -// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed -// https://github.com/elm/compiler -// Thank you, Evan! - -// A lot of energy was put into making type inference fast. That means it's pretty intimidating. -// -// Fundamentally, type inference assigns very general types based on syntax, and then tries to -// make all the pieces fit together. For instance when writing -// -// > f x -// -// We know that `f` is a function, and thus must have some type `a -> b`. -// `x` is just a variable, that gets the type `c` -// -// Next comes constraint generation. For `f x` to be well-typed, -// it must be the case that `c = a`, So a constraint `Eq(c, a)` is generated. -// But `Eq` is a bit special: `c` does not need to equal `a` exactly, but they need to be equivalent. -// This allows for instance the use of aliases. `c` could be an alias, and so looks different from -// `a`, but they still represent the same type. -// -// Then we get to solving, which happens in this file. -// -// When we hit an `Eq` constraint, then we check whether the two involved types are in fact -// equivalent using unification, and when they are, we can substitute one for the other. -// -// When all constraints are processed, and no unification errors have occurred, then the program -// is type-correct. Otherwise the errors are reported. -// -// Now, coming back to efficiency, this type checker uses *ranks* to optimize -// The rank tracks the number of let-bindings a variable is "under". Top-level definitions -// have rank 1. A let in a top-level definition gets rank 2, and so on. -// -// This has to do with generalization of type variables. This is described here -// -// http://okmij.org/ftp/ML/generalization.html#levels -// -// The problem is that when doing inference naively, this program would fail to typecheck -// -// f = -// id = \x -> x -// -// { a: id 1, b: id "foo" } -// -// Because `id` is applied to an integer, the type `Int -> Int` is inferred, which then gives a -// type error for `id "foo"`. -// -// Thus instead the inferred type for `id` is generalized (see the `generalize` function) to `a -> a`. -// Ranks are used to limit the number of type variables considered for generalization. Only those inside -// of the let (so those used in inferring the type of `\x -> x`) are considered. - -#[derive(PartialEq, Debug, Clone)] -pub enum TypeError { - BadExpr(Region, Category, ErrorType, Expected), - BadPattern(Region, PatternCategory, ErrorType, PExpected), - CircularType(Region, Symbol, ErrorType), - BadType(roc_types::types::Problem), - UnexposedLookup(Symbol), -} - -#[derive(Clone, Debug, Default)] -pub struct Env { - pub vars_by_symbol: MutMap, - pub aliases: MutMap, -} - -const DEFAULT_POOLS: usize = 8; - -#[derive(Clone, Debug)] -struct Pools(Vec>); - -impl Default for Pools { - fn default() -> Self { - Pools::new(DEFAULT_POOLS) - } -} - -impl Pools { - pub fn new(num_pools: usize) -> Self { - Pools(vec![Vec::new(); num_pools]) - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { - self.0 - .get_mut(rank.into_usize()) - .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) - } - - pub fn get(&self, rank: Rank) -> &Vec { - self.0 - .get(rank.into_usize()) - .unwrap_or_else(|| panic!("Compiler bug: could not find pool at rank {}", rank)) - } - - pub fn iter(&self) -> std::slice::Iter<'_, Vec> { - self.0.iter() - } - - pub fn split_last(&self) -> (&Vec, &[Vec]) { - self.0 - .split_last() - .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")) - } - - pub fn extend_to(&mut self, n: usize) { - for _ in self.len()..n { - self.0.push(Vec::new()); - } - } -} - -#[derive(Clone)] -struct State { - env: Env, - mark: Mark, -} - -pub fn run<'a>( - arena: &'a Bump, - mempool: &mut Pool, - env: &Env, - problems: &mut Vec, - mut subs: Subs, - constraint: &Constraint, -) -> (Solved, Env) { - let env = run_in_place(arena, mempool, env, problems, &mut subs, constraint); - - (Solved(subs), env) -} - -/// Modify an existing subs in-place instead -pub fn run_in_place<'a>( - arena: &'a Bump, - mempool: &mut Pool, - env: &Env, - problems: &mut Vec, - subs: &mut Subs, - constraint: &Constraint, -) -> Env { - let mut pools = Pools::default(); - let state = State { - env: env.clone(), - mark: Mark::NONE.next(), - }; - let rank = Rank::toplevel(); - let state = solve( - arena, - mempool, - env, - state, - rank, - &mut pools, - problems, - &mut MutMap::default(), - subs, - constraint, - ); - - state.env -} - -#[allow(clippy::too_many_arguments)] -fn solve<'a>( - arena: &'a Bump, - mempool: &mut Pool, - env: &Env, - state: State, - rank: Rank, - pools: &mut Pools, - problems: &mut Vec, - cached_aliases: &mut MutMap, - subs: &mut Subs, - constraint: &Constraint, -) -> State { - match constraint { - True => state, - // SaveTheEnvironment => { - // // NOTE deviation: elm only copies the env into the state on SaveTheEnvironment - // let mut copy = state; - // - // copy.env = env.clone(); - // - // copy - // } - Eq(typ, expectation, category, region) => { - let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - arena, - mempool, - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - match unify(subs, actual, expected) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadExpr( - *region, - category.clone(), - actual_type, - expectation.replace_ref(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - // Store(source, target, _filename, _linenr) => { - // // a special version of Eq that is used to store types in the AST. - // // IT DOES NOT REPORT ERRORS! - // let actual = type_to_var(subs, rank, pools, cached_aliases, source); - // let target = *target; - // - // match unify(subs, actual, target) { - // Success(vars) => { - // introduce(subs, rank, pools, &vars); - // - // state - // } - // Failure(vars, _actual_type, _expected_type) => { - // introduce(subs, rank, pools, &vars); - // - // // ERROR NOT REPORTED - // - // state - // } - // BadType(vars, _problem) => { - // introduce(subs, rank, pools, &vars); - // - // // ERROR NOT REPORTED - // - // state - // } - // } - // } - Lookup(symbol, expectation, region) => { - match env.vars_by_symbol.get(&symbol) { - Some(var) => { - // Deep copy the vars associated with this symbol before unifying them. - // Otherwise, suppose we have this: - // - // identity = \a -> a - // - // x = identity 5 - // - // When we call (identity 5), it's important that we not unify - // on identity's original vars. If we do, the type of `identity` will be - // mutated to be `Int -> Int` instead of `a -> `, which would be incorrect; - // the type of `identity` is more general than that! - // - // Instead, we want to unify on a *copy* of its vars. If the copy unifies - // successfully (in this case, to `Int -> Int`), we can use that to - // infer the type of this lookup (in this case, `Int`) without ever - // having mutated the original. - // - // If this Lookup is targeting a value in another module, - // then we copy from that module's Subs into our own. If the value - // is being looked up in this module, then we use our Subs as both - // the source and destination. - let actual = deep_copy_var(subs, rank, pools, *var); - - let expected = type_to_var( - arena, - mempool, - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - match unify(subs, actual, expected) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadExpr( - *region, - Category::Lookup(*symbol), - actual_type, - expectation.shallow_clone().replace(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - None => { - problems.push(TypeError::UnexposedLookup(*symbol)); - - state - } - } - } - And(sub_constraints) => { - let mut state = state; - - for sub_constraint in sub_constraints.iter() { - state = solve( - arena, - mempool, - env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - sub_constraint, - ); - } - - state - } - Pattern(region, category, typ, expectation) => { - let actual = type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); - let expected = type_to_var( - arena, - mempool, - subs, - rank, - pools, - cached_aliases, - expectation.get_type_ref(), - ); - - match unify(subs, actual, expected) { - Success(vars) => { - introduce(subs, rank, pools, &vars); - - state - } - Failure(vars, actual_type, expected_type) => { - introduce(subs, rank, pools, &vars); - - let problem = TypeError::BadPattern( - *region, - category.clone(), - actual_type, - expectation.shallow_clone().replace(expected_type), - ); - - problems.push(problem); - - state - } - BadType(vars, problem) => { - introduce(subs, rank, pools, &vars); - - problems.push(TypeError::BadType(problem)); - - state - } - } - } - Let(let_con) => { - match &let_con.ret_constraint { - True if let_con.rigid_vars.is_empty() => { - introduce(subs, rank, pools, &let_con.flex_vars); - - // If the return expression is guaranteed to solve, - // solve the assignments themselves and move on. - solve( - arena, - mempool, - &env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ) - } - ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => { - let state = solve( - arena, - mempool, - env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - // Add a variable for each def to new_vars_by_env. - let mut local_def_vars = BumpMap::new_in(arena); - - for (symbol, typ) in let_con.def_types.iter() { - let var = - type_to_var(arena, mempool, subs, rank, pools, cached_aliases, typ); - - // TODO: region should come from typ - local_def_vars.insert( - *symbol, - Located { - value: var, - region: Region::zero(), - }, - ); - } - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - if !new_env.vars_by_symbol.contains_key(&symbol) { - new_env.vars_by_symbol.insert(*symbol, loc_var.value); - } - } - - let new_state = solve( - arena, - mempool, - &new_env, - state, - rank, - pools, - problems, - cached_aliases, - subs, - ret_con, - ); - - for (symbol, loc_var) in local_def_vars { - check_for_infinite_type(subs, problems, symbol, loc_var); - } - - new_state - } - ret_con => { - let rigid_vars = &let_con.rigid_vars; - let flex_vars = &let_con.flex_vars; - - // work in the next pool to localize header - let next_rank = rank.next(); - - // introduce variables - for &var in rigid_vars.iter().chain(flex_vars.iter()) { - subs.set_rank(var, next_rank); - } - - // determine the next pool - let next_pools; - if next_rank.into_usize() < pools.len() { - next_pools = pools - } else { - // we should be off by one at this point - debug_assert_eq!(next_rank.into_usize(), 1 + pools.len()); - pools.extend_to(next_rank.into_usize()); - next_pools = pools; - } - - let pool: &mut Vec = next_pools.get_mut(next_rank); - - // Replace the contents of this pool with rigid_vars and flex_vars - pool.clear(); - pool.reserve(rigid_vars.len() + flex_vars.len()); - pool.extend(rigid_vars.iter()); - pool.extend(flex_vars.iter()); - - // run solver in next pool - - // Add a variable for each def to local_def_vars. - let mut local_def_vars = BumpMap::new_in(arena); - - for (symbol, typ) in let_con.def_types.iter() { - let var = type_to_var( - arena, - mempool, - subs, - next_rank, - next_pools, - cached_aliases, - typ, - ); - - // TODO: region should come from type - local_def_vars.insert( - *symbol, - Located { - value: var, - region: Region::zero(), - }, - ); - } - - // Solve the assignments' constraints first. - let State { - env: saved_env, - mark, - } = solve( - arena, - mempool, - &env, - state, - next_rank, - next_pools, - problems, - cached_aliases, - subs, - &let_con.defs_constraint, - ); - - let young_mark = mark; - let visit_mark = young_mark.next(); - let final_mark = visit_mark.next(); - - debug_assert_eq!( - { - let offenders = next_pools - .get(next_rank) - .iter() - .filter(|var| { - let current_rank = - subs.get_rank(roc_types::subs::Variable::clone(var)); - - current_rank.into_usize() > next_rank.into_usize() - }) - .collect::>(); - - let result = offenders.len(); - - if result > 0 { - dbg!(&subs, &offenders, &let_con.def_types); - } - - result - }, - 0 - ); - - // pop pool - generalize(subs, young_mark, visit_mark, next_rank, next_pools); - - next_pools.get_mut(next_rank).clear(); - - // check that things went well - debug_assert!({ - // NOTE the `subs.redundant` check is added for the uniqueness - // inference, and does not come from elm. It's unclear whether this is - // a bug with uniqueness inference (something is redundant that - // shouldn't be) or that it just never came up in elm. - let failing: Vec<_> = rigid_vars - .iter() - .filter(|&var| { - !subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE - }) - .collect(); - - if !failing.is_empty() { - println!("Rigids {:?}", &rigid_vars); - println!("Failing {:?}", failing); - } - - failing.is_empty() - }); - - let mut new_env = env.clone(); - for (symbol, loc_var) in local_def_vars.iter() { - // when there are duplicates, keep the one from `env` - if !new_env.vars_by_symbol.contains_key(&symbol) { - new_env.vars_by_symbol.insert(*symbol, loc_var.value); - } - } - - // Note that this vars_by_symbol is the one returned by the - // previous call to solve() - let temp_state = State { - env: saved_env, - mark: final_mark, - }; - - // Now solve the body, using the new vars_by_symbol which includes - // the assignments' name-to-variable mappings. - let new_state = solve( - arena, - mempool, - &new_env, - temp_state, - rank, - next_pools, - problems, - cached_aliases, - subs, - &ret_con, - ); - - for (symbol, loc_var) in local_def_vars { - check_for_infinite_type(subs, problems, symbol, loc_var); - } - - new_state - } - } - } // _ => todo!("implement {:?}", constraint), - } -} - -fn type_to_var<'a>( - arena: &'a Bump, - mempool: &mut Pool, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - cached: &mut MutMap, - typ: &Type2, -) -> Variable { - type_to_variable(arena, mempool, subs, rank, pools, cached, typ) -} - -/// Abusing existing functions for our purposes -/// this is to put a solved type back into subs -pub fn insert_type_into_subs<'a>( - arena: &'a Bump, - mempool: &mut Pool, - subs: &mut Subs, - typ: &Type2, -) -> Variable { - let rank = Rank::NONE; - let mut pools = Pools::default(); - let mut cached = MutMap::default(); - - type_to_variable(arena, mempool, subs, rank, &mut pools, &mut cached, typ) -} - -fn type_to_variable<'a>( - arena: &'a Bump, - mempool: &Pool, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - cached: &mut MutMap, - typ: &Type2, -) -> Variable { - use Type2::*; - - match typ { - Variable(var) => *var, - Apply(symbol, args) => { - let mut arg_vars = Vec::with_capacity(args.len()); - for var_id in args.iter_node_ids() { - let arg = mempool.get(var_id); - arg_vars.push(type_to_variable( - arena, mempool, subs, rank, pools, cached, arg, - )) - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, arg_vars); - let flat_type = FlatType::Apply(*symbol, arg_vars); - let content = Content::Structure(flat_type); - - register(subs, rank, pools, content) - } - - EmptyRec => roc_types::subs::Variable::EMPTY_RECORD, - EmptyTagUnion => roc_types::subs::Variable::EMPTY_TAG_UNION, - - Record(fields, ext_id) => { - let mut field_vars = Vec::new(); - - for node_id in fields.iter_node_ids() { - use RecordField::*; - - let (field, field_type) = mempool.get(node_id); - - let field_var = match field_type { - Required(type_id) => Required(type_to_variable( - arena, - mempool, - subs, - rank, - pools, - cached, - mempool.get(*type_id), - )), - Optional(type_id) => Optional(type_to_variable( - arena, - mempool, - subs, - rank, - pools, - cached, - mempool.get(*type_id), - )), - Demanded(type_id) => Demanded(type_to_variable( - arena, - mempool, - subs, - rank, - pools, - cached, - mempool.get(*type_id), - )), - }; - - field_vars.push((field.as_str(mempool).into(), field_var)); - } - - let ext = mempool.get(*ext_id); - let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); - - let (it, new_ext_var) = - gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var); - - let it = it - .into_iter() - .map(|(field, field_type)| (field.clone(), field_type)); - - field_vars.extend(it); - field_vars.sort_unstable_by(RecordFields::compare); - - let record_fields = RecordFields::insert_into_subs(subs, field_vars); - - let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); - - register(subs, rank, pools, content) - } - - Alias(symbol, args, alias_type_id) => { - // TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var! - // Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n) - // different variables (once for each occurrence). The recursion restriction is required - // for uniqueness types only: recursive aliases "introduce" an unbound uniqueness - // attribute in the body, when - // - // Peano : [ S Peano, Z ] - // - // becomes - // - // Peano : [ S (Attr u Peano), Z ] - // - // This `u` variable can be different between lists, so giving just one variable to - // this type is incorrect. - // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable - - let alias_type = mempool.get(*alias_type_id); - let is_recursive = false; // alias_type.is_recursive(); - let no_args = args.is_empty(); - /* - if no_args && !is_recursive { - if let Some(var) = cached.get(symbol) { - return *var; - } - } - */ - - let mut arg_vars = Vec::with_capacity(args.len()); - let mut new_aliases = BumpMap::new_in(arena); - - for (arg, arg_type_id) in args.iter(mempool) { - let arg_type = mempool.get(*arg_type_id); - - let arg_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); - - let arg_str = arg.as_str(mempool); - - arg_vars.push((roc_module::ident::Lowercase::from(arg_str), arg_var)); - new_aliases.insert(arg_str, arg_var); - } - - let arg_vars = AliasVariables::insert_into_subs(subs, arg_vars, []); - - let alias_var = type_to_variable(arena, mempool, subs, rank, pools, cached, alias_type); - let content = Content::Alias(*symbol, arg_vars, alias_var); - - let result = register(subs, rank, pools, content); - - if no_args && !is_recursive { - // cached.insert(*symbol, result); - } - - result - } - TagUnion(tags, ext_id) => { - let ext = mempool.get(*ext_id); - - let (union_tags, ext) = - type_to_union_tags(arena, mempool, subs, rank, pools, cached, tags, ext); - let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); - - register(subs, rank, pools, content) - } - // This case is important for the rank of boolean variables - Function(arg_vars, closure_type_id, ret_type_id) => { - let closure_type = mempool.get(*closure_type_id); - let ret_type = mempool.get(*ret_type_id); - - let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); - - for var_id in arg_vars.iter_node_ids() { - let arg = mempool.get(var_id); - let var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg); - new_arg_vars.push(var) - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - - let ret_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ret_type); - let closure_var = - type_to_variable(arena, mempool, subs, rank, pools, cached, closure_type); - - let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); - - register(subs, rank, pools, content) - } - other => todo!("not implemented {:?}", &other), - // RecursiveTagUnion(rec_var, tags, ext) => { - // let mut tag_vars = MutMap::default(); - // - // for (tag, tag_argument_types) in tags { - // let mut tag_argument_vars = Vec::with_capacity(tag_argument_types.len()); - // - // for arg_type in tag_argument_types { - // tag_argument_vars.push(type_to_variable(subs, rank, pools, cached, arg_type)); - // } - // - // tag_vars.insert(tag.clone(), tag_argument_vars); - // } - // - // let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); - // let mut ext_tag_vec = Vec::new(); - // let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union( - // subs, - // temp_ext_var, - // &mut ext_tag_vec, - // ) { - // Ok(()) => Variable::EMPTY_TAG_UNION, - // Err((new, _)) => new, - // }; - // tag_vars.extend(ext_tag_vec.into_iter()); - // - // let content = - // Content::Structure(FlatType::RecursiveTagUnion(*rec_var, tag_vars, new_ext_var)); - // - // let tag_union_var = register(subs, rank, pools, content); - // - // subs.set_content( - // *rec_var, - // Content::RecursionVar { - // opt_name: None, - // structure: tag_union_var, - // }, - // ); - // - // tag_union_var - // } - // HostExposedAlias { - // name: symbol, - // arguments: args, - // actual: alias_type, - // actual_var, - // .. - // } => { - // let mut arg_vars = Vec::with_capacity(args.len()); - // let mut new_aliases = ImMap::default(); - // - // for (arg, arg_type) in args { - // let arg_var = type_to_variable(subs, rank, pools, cached, arg_type); - // - // arg_vars.push((arg.clone(), arg_var)); - // new_aliases.insert(arg.clone(), arg_var); - // } - // - // let alias_var = type_to_variable(subs, rank, pools, cached, alias_type); - // - // // unify the actual_var with the result var - // // this can be used to access the type of the actual_var - // // to determine its layout later - // // subs.set_content(*actual_var, descriptor.content); - // - // //subs.set(*actual_var, descriptor.clone()); - // let content = Content::Alias(*symbol, arg_vars, alias_var); - // - // let result = register(subs, rank, pools, content); - // - // // We only want to unify the actual_var with the alias once - // // if it's already redirected (and therefore, redundant) - // // don't do it again - // if !subs.redundant(*actual_var) { - // let descriptor = subs.get(result); - // subs.union(result, *actual_var, descriptor); - // } - // - // result - // } - // Erroneous(problem) => { - // let content = Content::Structure(FlatType::Erroneous(problem.clone())); - // - // register(subs, rank, pools, content) - // } - } -} - -fn type_to_union_tags<'a>( - arena: &'a Bump, - mempool: &Pool, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - cached: &mut MutMap, - tags: &PoolVec<(TagName, PoolVec)>, - ext: &Type2, -) -> (UnionTags, Variable) { - let mut tag_vars = Vec::with_capacity(tags.len()); - - let mut tag_argument_vars = Vec::new(); - for id in tags.iter_node_ids() { - let (tag, tag_argument_types) = mempool.get(id); - for arg_id in tag_argument_types.iter_node_ids() { - let arg_type = mempool.get(arg_id); - let new_var = type_to_variable(arena, mempool, subs, rank, pools, cached, arg_type); - tag_argument_vars.push(new_var); - } - - let new_slice = VariableSubsSlice::insert_into_subs(subs, tag_argument_vars.drain(..)); - - tag_vars.push((tag.clone(), new_slice)); - } - - let temp_ext_var = type_to_variable(arena, mempool, subs, rank, pools, cached, ext); - - let ext = { - let (it, ext) = - roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); - - tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); - tag_vars.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); - - // deduplicate, keeping the right-most occurrence of a tag name - let mut i = 0; - - while i < tag_vars.len() { - match (tag_vars.get(i), tag_vars.get(i + 1)) { - (Some((t1, _)), Some((t2, _))) => { - if t1 == t2 { - tag_vars.remove(i); - } else { - i += 1; - } - } - _ => break, - } - } - - ext - }; - - (UnionTags::insert_slices_into_subs(subs, tag_vars), ext) -} - -fn check_for_infinite_type( - subs: &mut Subs, - problems: &mut Vec, - symbol: Symbol, - loc_var: Located, -) { - let var = loc_var.value; - - while let Err((recursive, _chain)) = subs.occurs(var) { - let description = subs.get(recursive); - let content = description.content; - - // try to make a tag union recursive, see if that helps - match content { - Content::Structure(FlatType::TagUnion(tags, ext_var)) => { - let rec_var = subs.fresh_unnamed_flex_var(); - subs.set_rank(rec_var, description.rank); - subs.set_content( - rec_var, - Content::RecursionVar { - opt_name: None, - structure: recursive, - }, - ); - - let mut new_tags = Vec::with_capacity(tags.len()); - - for (name_index, slice_index) in tags.iter_all() { - let slice = subs[slice_index]; - - let mut new_vars = Vec::new(); - for var_index in slice { - let var = subs[var_index]; - new_vars.push(subs.explicit_substitute(recursive, rec_var, var)); - } - - new_tags.push((subs[name_index].clone(), new_vars)); - } - - let new_ext_var = subs.explicit_substitute(recursive, rec_var, ext_var); - - let new_tags = UnionTags::insert_into_subs(subs, new_tags); - - let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext_var); - - subs.set_content(recursive, Content::Structure(flat_type)); - } - - _other => circular_error(subs, problems, symbol, &loc_var), - } - } -} - -fn circular_error( - subs: &mut Subs, - problems: &mut Vec, - symbol: Symbol, - loc_var: &Located, -) { - let var = loc_var.value; - let (error_type, _) = subs.var_to_error_type(var); - let problem = TypeError::CircularType(loc_var.region, symbol, error_type); - - subs.set_content(var, Content::Error); - - problems.push(problem); -} - -fn generalize( - subs: &mut Subs, - young_mark: Mark, - visit_mark: Mark, - young_rank: Rank, - pools: &mut Pools, -) { - let young_vars = pools.get(young_rank); - let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars); - - // Get the ranks right for each entry. - // Start at low ranks so we only have to pass over the information once. - for (index, table) in rank_table.iter().enumerate() { - for &var in table.iter() { - adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var); - } - } - - let (last_pool, all_but_last_pool) = rank_table.split_last(); - - // For variables that have rank lowerer than young_rank, register them in - // the appropriate old pool if they are not redundant. - for vars in all_but_last_pool { - for &var in vars { - if !subs.redundant(var) { - let rank = subs.get_rank(var); - - pools.get_mut(rank).push(var); - } - } - } - - // For variables with rank young_rank, if rank < young_rank: register in old pool, - // otherwise generalize - for &var in last_pool { - if !subs.redundant(var) { - let desc_rank = subs.get_rank(var); - - if desc_rank < young_rank { - pools.get_mut(desc_rank).push(var); - } else { - subs.set_rank(var, Rank::NONE); - } - } - } -} - -fn pool_to_rank_table( - subs: &mut Subs, - young_mark: Mark, - young_rank: Rank, - young_vars: &[Variable], -) -> Pools { - let mut pools = Pools::new(young_rank.into_usize() + 1); - - // Sort the variables into buckets by rank. - for &var in young_vars.iter() { - let rank = subs.get_rank(var); - subs.set_mark(var, young_mark); - - debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); - pools.get_mut(rank).push(var); - } - - pools -} - -/// Adjust variable ranks such that ranks never increase as you move deeper. -/// This way the outermost rank is representative of the entire structure. -fn adjust_rank( - subs: &mut Subs, - young_mark: Mark, - visit_mark: Mark, - group_rank: Rank, - var: Variable, -) -> Rank { - let desc = subs.get(var); - - if desc.mark == young_mark { - let Descriptor { - content, - rank: _, - mark: _, - copy, - } = desc; - - // Mark the variable as visited before adjusting content, as it may be cyclic. - subs.set_mark(var, visit_mark); - - let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, &content); - - subs.set( - var, - Descriptor { - content, - rank: max_rank, - mark: visit_mark, - copy, - }, - ); - - max_rank - } else if desc.mark == visit_mark { - // nothing changes - desc.rank - } else { - let mut desc = desc; - - let min_rank = group_rank.min(desc.rank); - - // TODO from elm-compiler: how can min_rank ever be group_rank? - desc.rank = min_rank; - desc.mark = visit_mark; - - subs.set(var, desc); - - min_rank - } -} - -fn adjust_rank_content( - subs: &mut Subs, - young_mark: Mark, - visit_mark: Mark, - group_rank: Rank, - content: &Content, -) -> Rank { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - match content { - FlexVar(_) | RigidVar(_) | Error => group_rank, - - RecursionVar { .. } => group_rank, - - Structure(flat_type) => { - match flat_type { - Apply(_, args) => { - let mut rank = Rank::toplevel(); - - for var_index in args.into_iter() { - let var = subs[var_index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - rank - } - - Func(arg_vars, closure_var, ret_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ret_var); - - // TODO investigate further. - // - // My theory is that because the closure_var contains variables already - // contained in the signature only, it does not need to be part of the rank - // calculuation - if true { - rank = rank.max(adjust_rank( - subs, - young_mark, - visit_mark, - group_rank, - *closure_var, - )); - } - - for index in arg_vars.into_iter() { - let var = subs[index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - rank - } - - EmptyRecord => { - // from elm-compiler: THEORY: an empty record never needs to get generalized - Rank::toplevel() - } - - EmptyTagUnion => Rank::toplevel(), - - Record(fields, ext_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - - for index in fields.iter_variables() { - let var = subs[index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - rank - } - - TagUnion(tags, ext_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - rank = rank - .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - } - - rank - } - - FunctionOrTagUnion(_, _, ext_var) => { - adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var) - } - - RecursiveTagUnion(rec_var, tags, ext_var) => { - let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var); - - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - rank = rank - .max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - } - - // THEORY: the recursion var has the same rank as the tag union itself - // all types it uses are also in the tags already, so it cannot influence the - // rank - debug_assert!( - rank >= adjust_rank(subs, young_mark, visit_mark, group_rank, *rec_var) - ); - - rank - } - - Erroneous(_) => group_rank, - } - } - - Alias(_, args, real_var) => { - let mut rank = Rank::toplevel(); - - for var_index in args.variables() { - let var = subs[var_index]; - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); - } - - // from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel() - // this theory is not true in Roc! aliases of function types capture the closure var - rank = rank.max(adjust_rank( - subs, young_mark, visit_mark, group_rank, *real_var, - )); - - rank - } - } -} - -/// Introduce some variables to Pools at the given rank. -/// Also, set each of their ranks in Subs to be the given rank. -fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { - let pool: &mut Vec = pools.get_mut(rank); - - for &var in vars.iter() { - subs.set_rank(var, rank); - } - - pool.extend(vars); -} - -/// Function that converts rigids variables to flex variables -/// this is used during the monomorphization process -pub fn instantiate_rigids(subs: &mut Subs, var: Variable) { - let rank = Rank::NONE; - let mut pools = Pools::default(); - - instantiate_rigids_help(subs, rank, &mut pools, var); -} - -fn instantiate_rigids_help( - subs: &mut Subs, - max_rank: Rank, - pools: &mut Pools, - var: Variable, -) -> Variable { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - let desc = subs.get_without_compacting(var); - - if let Some(copy) = desc.copy.into_variable() { - return copy; - } - - let make_descriptor = |content| Descriptor { - content, - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - let content = desc.content; - let copy = var; - - pools.get_mut(max_rank).push(copy); - - // Link the original variable to the new variable. This lets us - // avoid making multiple copies of the variable we are instantiating. - // - // Need to do this before recursively copying to avoid looping. - subs.set( - var, - Descriptor { - content: content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: copy.into(), - }, - ); - - // Now we recursively copy the content of the variable. - // We have already marked the variable as copied, so we - // will not repeat this work or crawl this variable again. - match content { - Structure(flat_type) => { - match flat_type { - Apply(_, args) => { - for var_index in args.into_iter() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - Func(arg_vars, closure_var, ret_var) => { - instantiate_rigids_help(subs, max_rank, pools, ret_var); - instantiate_rigids_help(subs, max_rank, pools, closure_var); - - for index in arg_vars.into_iter() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - EmptyRecord | EmptyTagUnion | Erroneous(_) => {} - - Record(fields, ext_var) => { - for index in fields.iter_variables() { - let var = subs[index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - - TagUnion(tags, ext_var) => { - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - - FunctionOrTagUnion(_tag_name, _symbol, ext_var) => { - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - - RecursiveTagUnion(rec_var, tags, ext_var) => { - instantiate_rigids_help(subs, max_rank, pools, rec_var); - - for (_, index) in tags.iter_all() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - } - - instantiate_rigids_help(subs, max_rank, pools, ext_var); - } - }; - } - - FlexVar(_) | Error => {} - - RecursionVar { structure, .. } => { - instantiate_rigids_help(subs, max_rank, pools, structure); - } - - RigidVar(name) => { - // what it's all about: convert the rigid var into a flex var - subs.set(copy, make_descriptor(FlexVar(Some(name)))); - } - - Alias(_, args, real_type_var) => { - for var_index in args.variables() { - let var = subs[var_index]; - instantiate_rigids_help(subs, max_rank, pools, var); - } - - instantiate_rigids_help(subs, max_rank, pools, real_type_var); - } - } - - var -} - -fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable { - let copy = deep_copy_var_help(subs, rank, pools, var); - - subs.restore(var); - - copy -} - -fn deep_copy_var_help( - subs: &mut Subs, - max_rank: Rank, - pools: &mut Pools, - var: Variable, -) -> Variable { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - let desc = subs.get_without_compacting(var); - - if let Some(copy) = desc.copy.into_variable() { - return copy; - } else if desc.rank != Rank::NONE { - return var; - } - - let make_descriptor = |content| Descriptor { - content, - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - let content = desc.content; - let copy = subs.fresh(make_descriptor(content.clone())); - - pools.get_mut(max_rank).push(copy); - - // Link the original variable to the new variable. This lets us - // avoid making multiple copies of the variable we are instantiating. - // - // Need to do this before recursively copying to avoid looping. - subs.set( - var, - Descriptor { - content: content.clone(), - rank: desc.rank, - mark: Mark::NONE, - copy: copy.into(), - }, - ); - - // Now we recursively copy the content of the variable. - // We have already marked the variable as copied, so we - // will not repeat this work or crawl this variable again. - match content { - Structure(flat_type) => { - let new_flat_type = match flat_type { - Apply(symbol, args) => { - let mut new_arg_vars = Vec::with_capacity(args.len()); - - for index in args.into_iter() { - let var = subs[index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, var); - new_arg_vars.push(copy_var); - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - - Apply(symbol, arg_vars) - } - - Func(arg_vars, closure_var, ret_var) => { - let new_ret_var = deep_copy_var_help(subs, max_rank, pools, ret_var); - let new_closure_var = deep_copy_var_help(subs, max_rank, pools, closure_var); - - let mut new_arg_vars = Vec::with_capacity(arg_vars.len()); - - for index in arg_vars.into_iter() { - let var = subs[index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, var); - new_arg_vars.push(copy_var); - } - - let arg_vars = VariableSubsSlice::insert_into_subs(subs, new_arg_vars); - - Func(arg_vars, new_closure_var, new_ret_var) - } - - same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, - - Record(fields, ext_var) => { - let record_fields = { - let mut new_vars = Vec::with_capacity(fields.len()); - - for index in fields.iter_variables() { - let var = subs[index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, var); - - new_vars.push(copy_var); - } - - let field_names_start = subs.field_names.len() as u32; - let variables_start = subs.variables.len() as u32; - let field_types_start = subs.record_fields.len() as u32; - - let mut length = 0; - - for ((i1, _, i3), var) in fields.iter_all().zip(new_vars) { - let record_field = subs[i3].map(|_| var); - - subs.field_names.push(subs[i1].clone()); - subs.record_fields.push(record_field.map(|_| ())); - subs.variables.push(*record_field.as_inner()); - - length += 1; - } - - RecordFields { - length, - field_names_start, - variables_start, - field_types_start, - } - }; - - Record( - record_fields, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ) - } - - TagUnion(tags, ext_var) => { - let mut new_variable_slices = Vec::with_capacity(tags.len()); - - let mut new_variables = Vec::new(); - for index in tags.variables() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_variables.push(new_var); - } - - let new_slice = - VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); - - new_variable_slices.push(new_slice); - } - - let new_variables = { - let start = subs.variable_slices.len() as u32; - let length = new_variable_slices.len() as u16; - subs.variable_slices.extend(new_variable_slices); - - SubsSlice::new(start, length) - }; - - let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); - - let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); - TagUnion(union_tags, new_ext) - } - - FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( - tag_name, - symbol, - deep_copy_var_help(subs, max_rank, pools, ext_var), - ), - - RecursiveTagUnion(rec_var, tags, ext_var) => { - let mut new_variable_slices = Vec::with_capacity(tags.len()); - - let mut new_variables = Vec::new(); - for index in tags.variables() { - let slice = subs[index]; - for var_index in slice { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_variables.push(new_var); - } - - let new_slice = - VariableSubsSlice::insert_into_subs(subs, new_variables.drain(..)); - - new_variable_slices.push(new_slice); - } - - let new_variables = { - let start = subs.variable_slices.len() as u32; - let length = new_variable_slices.len() as u16; - subs.variable_slices.extend(new_variable_slices); - - SubsSlice::new(start, length) - }; - - let union_tags = UnionTags::from_slices(tags.tag_names(), new_variables); - - let new_ext = deep_copy_var_help(subs, max_rank, pools, ext_var); - let new_rec_var = deep_copy_var_help(subs, max_rank, pools, rec_var); - FlatType::RecursiveTagUnion(new_rec_var, union_tags, new_ext) - } - }; - - subs.set(copy, make_descriptor(Structure(new_flat_type))); - - copy - } - - FlexVar(_) | Error => copy, - - RecursionVar { - opt_name, - structure, - } => { - let new_structure = deep_copy_var_help(subs, max_rank, pools, structure); - - subs.set( - copy, - make_descriptor(RecursionVar { - opt_name, - structure: new_structure, - }), - ); - - copy - } - - RigidVar(name) => { - subs.set(copy, make_descriptor(FlexVar(Some(name)))); - - copy - } - - Alias(symbol, mut args, real_type_var) => { - let mut new_args = Vec::with_capacity(args.variables().len()); - - for var_index in args.variables() { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_args.push(new_var); - } - - args.replace_variables(subs, new_args); - - let new_real_type_var = deep_copy_var_help(subs, max_rank, pools, real_type_var); - let new_content = Alias(symbol, args, new_real_type_var); - - subs.set(copy, make_descriptor(new_content)); - - copy - } - } -} - -fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { - let var = subs.fresh(Descriptor { - content, - rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }); - - pools.get_mut(rank).push(var); - - var -} diff --git a/editor/src/lang/types.rs b/editor/src/lang/types.rs deleted file mode 100644 index c935901acd..0000000000 --- a/editor/src/lang/types.rs +++ /dev/null @@ -1,863 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -#![allow(unused_imports)] -use crate::lang::expr::Env; -use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone}; -use crate::lang::scope::Scope; -// use roc_can::expr::Output; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, TagName}; -use roc_module::symbol::Symbol; -use roc_region::all::{Located, Region}; -use roc_types::types::{Problem, RecordField}; -use roc_types::{subs::Variable, types::ErrorType}; - -pub type TypeId = NodeId; - -#[derive(Debug)] -pub enum Type2 { - Variable(Variable), - - Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B - AsAlias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), // 24B = 8B + 12B + 4B - - // 32B - HostExposedAlias { - name: Symbol, // 8B - arguments: PoolVec<(PoolStr, TypeId)>, // 12B - actual_var: Variable, // 4B - actual: TypeId, // 4B - }, - EmptyTagUnion, - TagUnion(PoolVec<(TagName, PoolVec)>, TypeId), // 16B = 12B + 4B - RecursiveTagUnion(Variable, PoolVec<(TagName, PoolVec)>, TypeId), // 20B = 4B + 12B + 4B - - EmptyRec, - Record(PoolVec<(PoolStr, RecordField)>, TypeId), // 16B = 12B + 4B - - Function(PoolVec, TypeId, TypeId), // 20B = 12B + 4B + 4B - Apply(Symbol, PoolVec), // 20B = 8B + 12B - - Erroneous(Problem2), -} - -#[derive(Debug)] -pub enum Problem2 { - CanonicalizationProblem, - CircularType(Symbol, NodeId), // 12B = 8B + 4B - CyclicAlias(Symbol, PoolVec), // 20B = 8B + 12B - UnrecognizedIdent(PoolStr), // 8B - Shadowed(Located), - BadTypeArguments { - symbol: Symbol, // 8B - type_got: u8, // 1B - alias_needs: u8, // 1B - }, - InvalidModule, - SolvedTypeError, -} - -impl ShallowClone for Type2 { - fn shallow_clone(&self) -> Self { - match self { - Self::Variable(var) => Self::Variable(*var), - Self::Alias(symbol, args, alias_type_id) => { - Self::Alias(*symbol, args.shallow_clone(), alias_type_id.clone()) - } - Self::Record(fields, ext_id) => Self::Record(fields.shallow_clone(), ext_id.clone()), - Self::Function(args, closure_type_id, ret_type_id) => Self::Function( - args.shallow_clone(), - closure_type_id.clone(), - ret_type_id.clone(), - ), - rest => todo!("{:?}", rest), - } - } -} - -impl Type2 { - fn substitute(_pool: &mut Pool, _subs: &MutMap, _type_id: TypeId) { - todo!() - } - - pub fn variables(&self, pool: &mut Pool) -> MutSet { - use Type2::*; - - let mut stack = vec![self]; - let mut result = MutSet::default(); - - while let Some(this) = stack.pop() { - match this { - Variable(v) => { - result.insert(*v); - } - Alias(_, _, actual) | AsAlias(_, _, actual) => { - stack.push(pool.get(*actual)); - } - HostExposedAlias { - actual_var, actual, .. - } => { - result.insert(*actual_var); - stack.push(pool.get(*actual)); - } - EmptyTagUnion | EmptyRec | Erroneous(_) => {} - TagUnion(tags, ext) => { - for (_, args) in tags.iter(pool) { - stack.extend(args.iter(pool)); - } - stack.push(pool.get(*ext)); - } - RecursiveTagUnion(rec, tags, ext) => { - for (_, args) in tags.iter(pool) { - stack.extend(args.iter(pool)); - } - stack.push(pool.get(*ext)); - result.insert(*rec); - } - Record(fields, ext) => { - for (_, field) in fields.iter(pool) { - stack.push(pool.get(*field.as_inner())); - } - stack.push(pool.get(*ext)); - } - Function(args, closure, result) => { - stack.extend(args.iter(pool)); - stack.push(pool.get(*closure)); - stack.push(pool.get(*result)); - } - Apply(_, args) => { - stack.extend(args.iter(pool)); - } - } - } - - result - } - - pub fn contains_symbol(&self, _pool: &mut Pool, _needle: Symbol) -> bool { - todo!() - } - - pub fn substitute_alias(&self, _pool: &mut Pool, _needle: Symbol, _actual: Self) { - todo!() - } -} - -impl NodeId { - pub fn variables(&self, _pool: &mut Pool) -> MutSet { - todo!() - } -} - -/// A temporary data structure to return a bunch of values to Def construction -pub enum Signature { - FunctionWithAliases { - annotation: Type2, - arguments: PoolVec, - closure_type_id: TypeId, - return_type_id: TypeId, - }, - Function { - arguments: PoolVec, - closure_type_id: TypeId, - return_type_id: TypeId, - }, - Value { - annotation: Type2, - }, -} - -pub enum Annotation2<'a> { - Annotation { - named_rigids: MutMap<&'a str, Variable>, - unnamed_rigids: MutSet, - symbols: MutSet, - signature: Signature, - }, - Erroneous(roc_types::types::Problem), -} - -pub fn to_annotation2<'a>( - env: &mut Env, - scope: &mut Scope, - annotation: &'a roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) -> Annotation2<'a> { - let mut references = References::default(); - - let annotation = to_type2(env, scope, &mut references, annotation, region); - - // we dealias until we hit a non-alias, then we either hit a function type (and produce a - // function annotation) or anything else (and produce a value annotation) - match annotation { - Type2::Function(arguments, closure_type_id, return_type_id) => { - let References { - named, - unnamed, - symbols, - .. - } = references; - - let signature = Signature::Function { - arguments, - closure_type_id, - return_type_id, - }; - - Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - } - } - Type2::Alias(_, _, _) => { - // most of the time, the annotation is not an alias, so this case is comparatively - // less efficient - shallow_dealias(env, references, annotation) - } - _ => { - let References { - named, - unnamed, - symbols, - .. - } = references; - - let signature = Signature::Value { annotation }; - - Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - } - } - } -} - -fn shallow_dealias<'a>( - env: &mut Env, - references: References<'a>, - annotation: Type2, -) -> Annotation2<'a> { - let References { - named, - unnamed, - symbols, - .. - } = references; - let mut inner = &annotation; - - loop { - match inner { - Type2::Alias(_, _, actual) => { - inner = env.pool.get(*actual); - } - Type2::Function(arguments, closure_type_id, return_type_id) => { - let signature = Signature::FunctionWithAliases { - arguments: arguments.shallow_clone(), - closure_type_id: *closure_type_id, - return_type_id: *return_type_id, - annotation, - }; - - return Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - }; - } - _ => { - let signature = Signature::Value { annotation }; - - return Annotation2::Annotation { - named_rigids: named, - unnamed_rigids: unnamed, - symbols, - signature, - }; - } - } - } -} - -#[derive(Default)] -pub struct References<'a> { - named: MutMap<&'a str, Variable>, - unnamed: MutSet, - hidden: MutSet, - symbols: MutSet, -} - -pub fn to_type_id<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - annotation: &roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) -> TypeId { - let type2 = to_type2(env, scope, rigids, annotation, region); - - env.add(type2, region) -} - -pub fn as_type_id<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - type_id: TypeId, - annotation: &roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) { - let type2 = to_type2(env, scope, rigids, annotation, region); - - env.pool[type_id] = type2; - env.set_region(type_id, region); -} - -pub fn to_type2<'a>( - env: &mut Env, - scope: &mut Scope, - references: &mut References<'a>, - annotation: &roc_parse::ast::TypeAnnotation<'a>, - region: Region, -) -> Type2 { - use roc_parse::ast::TypeAnnotation::*; - - match annotation { - Apply(module_name, ident, targs) => { - match to_type_apply(env, scope, references, module_name, ident, targs, region) { - TypeApply::Apply(symbol, args) => { - references.symbols.insert(symbol); - Type2::Apply(symbol, args) - } - TypeApply::Alias(symbol, args, actual) => { - references.symbols.insert(symbol); - Type2::Alias(symbol, args, actual) - } - TypeApply::Erroneous(_problem) => { - // Type2::Erroneous(problem) - todo!() - } - } - } - Function(argument_types, return_type) => { - let arguments = PoolVec::with_capacity(argument_types.len() as u32, env.pool); - - for (type_id, loc_arg) in arguments.iter_node_ids().zip(argument_types.iter()) { - as_type_id( - env, - scope, - references, - type_id, - &loc_arg.value, - loc_arg.region, - ); - } - - let return_type_id = to_type_id( - env, - scope, - references, - &return_type.value, - return_type.region, - ); - - let closure_type = Type2::Variable(env.var_store.fresh()); - let closure_type_id = env.pool.add(closure_type); - - Type2::Function(arguments, closure_type_id, return_type_id) - } - BoundVariable(v) => { - // a rigid type variable - match references.named.get(v) { - Some(var) => Type2::Variable(*var), - None => { - let var = env.var_store.fresh(); - - references.named.insert(v, var); - - Type2::Variable(var) - } - } - } - Wildcard | Malformed(_) => { - let var = env.var_store.fresh(); - - references.unnamed.insert(var); - - Type2::Variable(var) - } - Record { fields, ext, .. } => { - let field_types_map = can_assigned_fields(env, scope, references, fields, region); - - let field_types = PoolVec::with_capacity(field_types_map.len() as u32, env.pool); - - for (node_id, (label, field)) in field_types.iter_node_ids().zip(field_types_map) { - let poolstr = PoolStr::new(label, env.pool); - - let rec_field = match field { - RecordField::Optional(_) => { - let field_id = env.pool.add(field.into_inner()); - RecordField::Optional(field_id) - } - RecordField::Demanded(_) => { - let field_id = env.pool.add(field.into_inner()); - RecordField::Demanded(field_id) - } - RecordField::Required(_) => { - let field_id = env.pool.add(field.into_inner()); - RecordField::Required(field_id) - } - }; - env.pool[node_id] = (poolstr, rec_field); - } - - let ext_type = match ext { - Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), - None => env.add(Type2::EmptyRec, region), - }; - - Type2::Record(field_types, ext_type) - } - TagUnion { tags, ext, .. } => { - let tag_types_vec = can_tags(env, scope, references, tags, region); - - let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool); - - for (node_id, (tag_name, field)) in tag_types.iter_node_ids().zip(tag_types_vec) { - env.pool[node_id] = (tag_name, field); - } - - let ext_type = match ext { - Some(loc_ann) => to_type_id(env, scope, references, &loc_ann.value, region), - None => env.add(Type2::EmptyTagUnion, region), - }; - - Type2::TagUnion(tag_types, ext_type) - } - As(loc_inner, _spaces, loc_as) => { - // e.g. `{ x : Int, y : Int } as Point }` - match loc_as.value { - Apply(module_name, ident, loc_vars) if module_name.is_empty() => { - let symbol = match scope.introduce( - ident.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => symbol, - - Err((_original_region, _shadow)) => { - // let problem = Problem2::Shadowed(original_region, shadow.clone()); - - // env.problem(roc_problem::can::Problem::ShadowingInAnnotation { - // original_region, - // shadow, - // }); - - // return Type2::Erroneous(problem); - todo!(); - } - }; - - let inner_type = to_type2(env, scope, references, &loc_inner.value, region); - let vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); - - let lowercase_vars = PoolVec::with_capacity(loc_vars.len() as u32, env.pool); - - for ((loc_var, named_id), var_id) in loc_vars - .iter() - .zip(lowercase_vars.iter_node_ids()) - .zip(vars.iter_node_ids()) - { - match loc_var.value { - BoundVariable(ident) => { - let var_name = ident; - - if let Some(var) = references.named.get(&var_name) { - let poolstr = PoolStr::new(var_name, env.pool); - - let type_id = env.pool.add(Type2::Variable(*var)); - env.pool[var_id] = (poolstr.shallow_clone(), type_id); - - env.pool[named_id] = (poolstr, *var); - env.set_region(named_id, loc_var.region); - } else { - let var = env.var_store.fresh(); - - references.named.insert(var_name.clone(), var); - let poolstr = PoolStr::new(var_name, env.pool); - - let type_id = env.pool.add(Type2::Variable(var)); - env.pool[var_id] = (poolstr.shallow_clone(), type_id); - - env.pool[named_id] = (poolstr, var); - env.set_region(named_id, loc_var.region); - } - } - _ => { - // If anything other than a lowercase identifier - // appears here, the whole annotation is invalid. - return Type2::Erroneous(Problem2::CanonicalizationProblem); - } - } - } - - let alias_actual = inner_type; - // TODO instantiate recursive tag union - // let alias_actual = if let Type2::TagUnion(tags, ext) = inner_type { - // let rec_var = env.var_store.fresh(); - // - // let mut new_tags = Vec::with_capacity(tags.len()); - // for (tag_name, args) in tags { - // let mut new_args = Vec::with_capacity(args.len()); - // for arg in args { - // let mut new_arg = arg.clone(); - // new_arg.substitute_alias(symbol, &Type2::Variable(rec_var)); - // new_args.push(new_arg); - // } - // new_tags.push((tag_name.clone(), new_args)); - // } - // Type2::RecursiveTagUnion(rec_var, new_tags, ext) - // } else { - // inner_type - // }; - - let mut hidden_variables = MutSet::default(); - hidden_variables.extend(alias_actual.variables(env.pool)); - - for (_, var) in lowercase_vars.iter(env.pool) { - hidden_variables.remove(var); - } - - let alias_actual_id = env.pool.add(alias_actual); - scope.add_alias(env.pool, symbol, lowercase_vars, alias_actual_id); - - let alias = scope.lookup_alias(symbol).unwrap(); - // local_aliases.insert(symbol, alias.clone()); - - // TODO host-exposed - // if vars.is_empty() && env.home == symbol.module_id() { - // let actual_var = env.var_store.fresh(); - // rigids.host_exposed.insert(symbol, actual_var); - // Type::HostExposedAlias { - // name: symbol, - // arguments: vars, - // actual: Box::new(alias.typ.clone()), - // actual_var, - // } - // } else { - // Type::Alias(symbol, vars, Box::new(alias.typ.clone())) - // } - Type2::AsAlias(symbol, vars, alias.actual) - } - _ => { - // This is a syntactically invalid type alias. - Type2::Erroneous(Problem2::CanonicalizationProblem) - } - } - } - SpaceBefore(nested, _) | SpaceAfter(nested, _) => { - to_type2(env, scope, references, nested, region) - } - } -} - -// TODO trim down these arguments! -#[allow(clippy::too_many_arguments)] -fn can_assigned_fields<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - fields: &&[Located>>], - region: Region, -) -> MutMap<&'a str, RecordField> { - use roc_parse::ast::AssignedField::*; - use roc_types::types::RecordField::*; - - // SendMap doesn't have a `with_capacity` - let mut field_types = MutMap::default(); - - // field names we've seen so far in this record - let mut seen = std::collections::HashMap::with_capacity(fields.len()); - - 'outer: for loc_field in fields.iter() { - let mut field = &loc_field.value; - - // use this inner loop to unwrap the SpaceAfter/SpaceBefore - // when we find the name of this field, break out of the loop - // with that value, so we can check whether the field name is - // a duplicate - let new_name = 'inner: loop { - match field { - RequiredValue(field_name, _, annotation) => { - let field_type = - to_type2(env, scope, rigids, &annotation.value, annotation.region); - - let label = field_name.value; - field_types.insert(label, Required(field_type)); - - break 'inner label; - } - OptionalValue(field_name, _, annotation) => { - let field_type = - to_type2(env, scope, rigids, &annotation.value, annotation.region); - - let label = field_name.value; - field_types.insert(label.clone(), Optional(field_type)); - - break 'inner label; - } - LabelOnly(loc_field_name) => { - // Interpret { a, b } as { a : a, b : b } - let field_name = loc_field_name.value; - let field_type = { - if let Some(var) = rigids.named.get(&field_name) { - Type2::Variable(*var) - } else { - let field_var = env.var_store.fresh(); - rigids.named.insert(field_name, field_var); - Type2::Variable(field_var) - } - }; - - field_types.insert(field_name.clone(), Required(field_type)); - - break 'inner field_name; - } - SpaceBefore(nested, _) | SpaceAfter(nested, _) => { - // check the nested field instead - field = nested; - continue 'inner; - } - Malformed(_) => { - // TODO report this? - // completely skip this element, advance to the next tag - continue 'outer; - } - } - }; - - // ensure that the new name is not already in this record: - // note that the right-most tag wins when there are two with the same name - if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) { - env.problem(roc_problem::can::Problem::DuplicateRecordFieldType { - field_name: new_name.into(), - record_region: region, - field_region: loc_field.region, - replaced_region, - }); - } - } - - field_types -} - -fn can_tags<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - tags: &'a [Located>], - region: Region, -) -> Vec<(TagName, PoolVec)> { - use roc_parse::ast::Tag; - let mut tag_types = Vec::with_capacity(tags.len()); - - // tag names we've seen so far in this tag union - let mut seen = std::collections::HashMap::with_capacity(tags.len()); - - 'outer: for loc_tag in tags.iter() { - let mut tag = &loc_tag.value; - - // use this inner loop to unwrap the SpaceAfter/SpaceBefore - // when we find the name of this tag, break out of the loop - // with that value, so we can check whether the tag name is - // a duplicate - let new_name = 'inner: loop { - match tag { - Tag::Global { name, args } => { - let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); - - for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let tag_name = TagName::Global(name.value.into()); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::Private { name, args } => { - let ident_id = env.ident_ids.get_or_insert(&name.value.into()); - let symbol = Symbol::new(env.home, ident_id); - - let arg_types = PoolVec::with_capacity(args.len() as u32, env.pool); - - for (type_id, loc_arg) in arg_types.iter_node_ids().zip(args.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let tag_name = TagName::Private(symbol); - tag_types.push((tag_name.clone(), arg_types)); - - break 'inner tag_name; - } - Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => { - // check the nested tag instead - tag = nested; - continue 'inner; - } - Tag::Malformed(_) => { - // TODO report this? - // completely skip this element, advance to the next tag - continue 'outer; - } - } - }; - - // ensure that the new name is not already in this tag union: - // note that the right-most tag wins when there are two with the same name - if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) { - env.problem(roc_problem::can::Problem::DuplicateTag { - tag_region: loc_tag.region, - tag_union_region: region, - replaced_region, - tag_name: new_name, - }); - } - } - - tag_types -} - -enum TypeApply { - Apply(Symbol, PoolVec), - Alias(Symbol, PoolVec<(PoolStr, TypeId)>, TypeId), - Erroneous(roc_types::types::Problem), -} - -#[inline(always)] -fn to_type_apply<'a>( - env: &mut Env, - scope: &mut Scope, - rigids: &mut References<'a>, - module_name: &str, - ident: &str, - type_arguments: &[Located>], - region: Region, -) -> TypeApply { - let symbol = if module_name.is_empty() { - // Since module_name was empty, this is an unqualified type. - // Look it up in scope! - let ident: Ident = (*ident).into(); - - match scope.lookup(&ident, region) { - Ok(symbol) => symbol, - Err(problem) => { - env.problem(roc_problem::can::Problem::RuntimeError(problem)); - - return TypeApply::Erroneous(Problem::UnrecognizedIdent(ident.into())); - } - } - } else { - match env.qualified_lookup(module_name, ident, region) { - Ok(symbol) => symbol, - Err(problem) => { - // Either the module wasn't imported, or - // it was imported but it doesn't expose this ident. - env.problem(roc_problem::can::Problem::RuntimeError(problem)); - - return TypeApply::Erroneous(Problem::UnrecognizedIdent((*ident).into())); - } - } - }; - - let argument_type_ids = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); - - for (type_id, loc_arg) in argument_type_ids.iter_node_ids().zip(type_arguments.iter()) { - as_type_id(env, scope, rigids, type_id, &loc_arg.value, loc_arg.region); - } - - let args = type_arguments; - let opt_alias = scope.lookup_alias(symbol); - match opt_alias { - Some(ref alias) => { - // use a known alias - let actual = alias.actual; - let mut substitutions: MutMap = MutMap::default(); - - if alias.targs.len() != args.len() { - let error = TypeApply::Erroneous(Problem::BadTypeArguments { - symbol, - region, - alias_needs: alias.targs.len() as u8, - type_got: args.len() as u8, - }); - return error; - } - - let arguments = PoolVec::with_capacity(type_arguments.len() as u32, env.pool); - - let it = arguments.iter_node_ids().zip( - argument_type_ids - .iter_node_ids() - .zip(alias.targs.iter_node_ids()), - ); - - for (node_id, (type_id, loc_var_id)) in it { - let loc_var = &env.pool[loc_var_id]; - let name = loc_var.0.shallow_clone(); - let var = loc_var.1; - - env.pool[node_id] = (name, type_id); - - substitutions.insert(var, type_id); - } - - // make sure the recursion variable is freshly instantiated - // have to allocate these outside of the if for lifetime reasons... - let new = env.var_store.fresh(); - let fresh = env.pool.add(Type2::Variable(new)); - if let Type2::RecursiveTagUnion(rvar, ref tags, ext) = &mut env.pool[actual] { - substitutions.insert(*rvar, fresh); - - env.pool[actual] = Type2::RecursiveTagUnion(new, tags.shallow_clone(), *ext); - } - - // make sure hidden variables are freshly instantiated - for var_id in alias.hidden_variables.iter_node_ids() { - let var = env.pool[var_id]; - let fresh = env.pool.add(Type2::Variable(env.var_store.fresh())); - substitutions.insert(var, fresh); - } - - // instantiate variables - Type2::substitute(env.pool, &substitutions, actual); - - TypeApply::Alias(symbol, arguments, actual) - } - None => TypeApply::Apply(symbol, argument_type_ids), - } -} - -#[derive(Debug)] -pub struct Alias { - pub targs: PoolVec<(PoolStr, Variable)>, - pub actual: TypeId, - - /// hidden type variables, like the closure variable in `a -> b` - pub hidden_variables: PoolVec, -} - -impl ShallowClone for Alias { - fn shallow_clone(&self) -> Self { - Self { - targs: self.targs.shallow_clone(), - hidden_variables: self.hidden_variables.shallow_clone(), - actual: self.actual, - } - } -} diff --git a/editor/src/lib.rs b/editor/src/lib.rs index f6f3053840..3e4b259885 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -10,7 +10,6 @@ extern crate pest_derive; mod editor; mod graphics; -pub mod lang; //TODO remove pub for unused warnings mod ui; mod window; diff --git a/editor/tests/solve_expr2.rs b/editor/tests/solve_expr2.rs deleted file mode 100644 index 88f5e5248b..0000000000 --- a/editor/tests/solve_expr2.rs +++ /dev/null @@ -1,372 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -use bumpalo::Bump; -use roc_can::expected::Expected; -use roc_collections::all::MutMap; -use roc_editor::lang::solve; -use roc_editor::lang::{ - constrain::constrain_expr, - constrain::Constraint, - expr::{str_to_expr2, Env}, - pool::Pool, - scope::Scope, - types::Type2, -}; -use roc_module::ident::Lowercase; -use roc_module::symbol::Interns; -use roc_module::symbol::Symbol; -use roc_module::symbol::{IdentIds, ModuleIds}; -use roc_region::all::Region; -use roc_types::solved_types::Solved; -use roc_types::subs::{Subs, Variable}; -use roc_types::{pretty_print::content_to_string, subs::VarStore}; - -fn run_solve<'a>( - arena: &'a Bump, - mempool: &mut Pool, - aliases: MutMap, - rigid_variables: MutMap, - constraint: Constraint, - var_store: VarStore, -) -> (Solved, solve::Env, Vec) { - let env = solve::Env { - vars_by_symbol: MutMap::default(), - aliases, - }; - - let mut subs = Subs::new(var_store); - - for (var, name) in rigid_variables { - subs.rigid_var(var, name); - } - - // Now that the module is parsed, canonicalized, and constrained, - // we need to type check it. - let mut problems = Vec::new(); - - // Run the solver to populate Subs. - let (solved_subs, solved_env) = - solve::run(arena, mempool, &env, &mut problems, subs, &constraint); - - (solved_subs, solved_env, problems) -} - -fn infer_eq(actual: &str, expected_str: &str) { - let mut env_pool = Pool::with_capacity(1024); - let env_arena = Bump::new(); - let code_arena = Bump::new(); - - let mut var_store = VarStore::default(); - let var = var_store.fresh(); - let dep_idents = IdentIds::exposed_builtins(8); - let exposed_ident_ids = IdentIds::default(); - let mut module_ids = ModuleIds::default(); - let mod_id = module_ids.get_or_insert(&"ModId123".into()); - - let mut env = Env::new( - mod_id, - &env_arena, - &mut env_pool, - &mut var_store, - dep_idents, - &module_ids, - exposed_ident_ids, - ); - - let mut scope = Scope::new(env.home, env.pool, env.var_store); - - let region = Region::zero(); - - let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); - - match expr2_result { - Ok((expr, _)) => { - let constraint = constrain_expr( - &code_arena, - &mut env, - &expr, - Expected::NoExpectation(Type2::Variable(var)), - Region::zero(), - ); - - let Env { - pool, - var_store: ref_var_store, - mut dep_idents, - .. - } = env; - - // extract the var_store out of the env again - let mut var_store = VarStore::default(); - std::mem::swap(ref_var_store, &mut var_store); - - let (mut solved, _, _) = run_solve( - &code_arena, - pool, - Default::default(), - Default::default(), - constraint, - var_store, - ); - - let subs = solved.inner_mut(); - - let content = subs.get_content_without_compacting(var); - - // Connect the ModuleId to it's IdentIds - dep_idents.insert(mod_id, env.ident_ids); - - let interns = Interns { - module_ids: env.module_ids.clone(), - all_ident_ids: dep_idents, - }; - - let actual_str = content_to_string(content, subs, mod_id, &interns); - - assert_eq!(actual_str, expected_str); - } - Err(e) => panic!("syntax error {:?}", e), - } -} - -#[test] -fn constrain_str() { - infer_eq( - indoc!( - r#" - "type inference!" - "# - ), - "Str", - ) -} - -// This will be more useful once we actually map -// strings less than 15 chars to SmallStr -#[test] -fn constrain_small_str() { - infer_eq( - indoc!( - r#" - "a" - "# - ), - "Str", - ) -} - -#[test] -fn constrain_empty_record() { - infer_eq( - indoc!( - r#" - {} - "# - ), - "{}", - ) -} - -#[test] -fn constrain_small_int() { - infer_eq( - indoc!( - r#" - 12 - "# - ), - "Num *", - ) -} - -#[test] -fn constrain_float() { - infer_eq( - indoc!( - r#" - 3.14 - "# - ), - "Float *", - ) -} - -#[test] -fn constrain_record() { - infer_eq( - indoc!( - r#" - { x : 1, y : "hi" } - "# - ), - "{ x : Num *, y : Str }", - ) -} - -#[test] -fn constrain_empty_list() { - infer_eq( - indoc!( - r#" - [] - "# - ), - "List *", - ) -} - -#[test] -fn constrain_list() { - infer_eq( - indoc!( - r#" - [ 1, 2 ] - "# - ), - "List (Num *)", - ) -} - -#[test] -fn constrain_list_of_records() { - infer_eq( - indoc!( - r#" - [ { x: 1 }, { x: 3 } ] - "# - ), - "List { x : Num * }", - ) -} - -#[test] -fn constrain_global_tag() { - infer_eq( - indoc!( - r#" - Foo - "# - ), - "[ Foo ]*", - ) -} - -#[test] -fn constrain_private_tag() { - infer_eq( - indoc!( - r#" - @Foo - "# - ), - "[ @Foo ]*", - ) -} - -#[test] -fn constrain_call_and_accessor() { - infer_eq( - indoc!( - r#" - .foo { foo: "bar" } - "# - ), - "Str", - ) -} - -#[test] -fn constrain_access() { - infer_eq( - indoc!( - r#" - { foo: "bar" }.foo - "# - ), - "Str", - ) -} - -#[test] -fn constrain_if() { - infer_eq( - indoc!( - r#" - if True then Green else Red - "# - ), - "[ Green, Red ]*", - ) -} - -#[test] -fn constrain_when() { - infer_eq( - indoc!( - r#" - when if True then Green else Red is - Green -> Blue - Red -> Purple - "# - ), - "[ Blue, Purple ]*", - ) -} - -#[test] -fn constrain_let_value() { - infer_eq( - indoc!( - r#" - person = { name: "roc" } - - person - "# - ), - "{ name : Str }", - ) -} - -#[test] -fn constrain_update() { - infer_eq( - indoc!( - r#" - person = { name: "roc" } - - { person & name: "bird" } - "# - ), - "{ name : Str }", - ) -} - -#[ignore = "TODO: implement builtins in the editor"] -#[test] -fn constrain_run_low_level() { - infer_eq( - indoc!( - r#" - List.map [ { name: "roc" }, { name: "bird" } ] .name - "# - ), - "List Str", - ) -} - -#[test] -fn constrain_closure() { - infer_eq( - indoc!( - r#" - x = 1 - - \{} -> x - "# - ), - "{}* -> Num *", - ) -} From 4c2de0215ab24da53862f46248232c699fdafe8f Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 18:38:04 +0200 Subject: [PATCH 34/96] tests working --- ast/src/ast_error.rs | 4 +- ast/src/canonicalize/canonicalize.rs | 36 ++++-- ast/src/canonicalize/mod.rs | 2 +- ast/src/canonicalize/module.rs | 2 +- ast/src/constrain.rs | 138 ++++++++++++++-------- ast/src/lang/core/ast.rs | 13 +- ast/src/lang/core/declaration.rs | 14 ++- ast/src/lang/core/def/def.rs | 16 ++- ast/src/lang/core/def/def2.rs | 11 +- ast/src/lang/core/def/def_to_def2.rs | 11 +- ast/src/lang/core/def/mod.rs | 4 +- ast/src/lang/core/expr/expr2.rs | 9 +- ast/src/lang/core/expr/expr2_to_string.rs | 9 +- ast/src/lang/core/expr/expr_to_expr2.rs | 33 +++--- ast/src/lang/core/expr/introduced_vars.rs | 11 +- ast/src/lang/core/expr/mod.rs | 4 +- ast/src/lang/core/expr/output.rs | 6 +- ast/src/lang/core/expr/record_field.rs | 7 +- ast/src/lang/core/fun_def.rs | 15 ++- ast/src/lang/core/header.rs | 4 +- ast/src/lang/core/mod.rs | 10 +- ast/src/lang/core/pattern.rs | 6 +- ast/src/lang/core/str.rs | 15 ++- ast/src/lang/core/val_def.rs | 18 ++- ast/src/lang/env.rs | 4 +- ast/src/lang/mod.rs | 4 +- ast/src/lang/rigids.rs | 7 +- ast/src/lib.rs | 12 +- ast/src/parse/mod.rs | 2 +- ast/src/parse/parse_ast.rs | 14 ++- ast/src/parse/parse_expr.rs | 1 + ast/src/parse/parse_header.rs | 3 +- ast/src/pool/mod.rs | 2 +- ast/src/pool/pool.rs | 10 +- ast/src/pool/pool_str.rs | 6 +- ast/src/pool/pool_vec.rs | 9 +- ast/src/pool/shallow_clone.rs | 1 - ast/src/solve_type.rs | 1 - code_markup/src/lib.rs | 2 +- code_markup/src/markup/attribute.rs | 2 +- code_markup/src/markup/common_nodes.rs | 1 - code_markup/src/markup/nodes.rs | 37 +++++- code_markup/src/markup/top_level_def.rs | 20 +++- code_markup/src/markup_error.rs | 5 +- code_markup/src/slow_pool.rs | 1 - code_markup/src/syntax_highlight.rs | 3 +- editor/src/editor/ed_error.rs | 5 +- editor/src/editor/grid_node_map.rs | 2 +- editor/src/editor/mvc/ed_model.rs | 18 ++- editor/src/editor/mvc/ed_update.rs | 4 +- editor/src/editor/mvc/int_update.rs | 3 +- editor/src/editor/mvc/list_update.rs | 6 +- editor/src/editor/mvc/tld_value_update.rs | 27 ++++- editor/src/editor/render_ast.rs | 3 +- editor/src/editor/theme.rs | 2 +- editor/src/lib.rs | 1 - utils/src/lib.rs | 3 +- utils/src/util_error.rs | 3 +- 58 files changed, 394 insertions(+), 228 deletions(-) diff --git a/ast/src/ast_error.rs b/ast/src/ast_error.rs index cf42c7762d..6fb8546212 100644 --- a/ast/src/ast_error.rs +++ b/ast/src/ast_error.rs @@ -1,5 +1,3 @@ - - use snafu::{Backtrace, Snafu}; use crate::lang::core::ast::ASTNodeId; @@ -37,4 +35,4 @@ pub enum ASTError { }, } -pub type ASTResult = std::result::Result; \ No newline at end of file +pub type ASTResult = std::result::Result; diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalize/canonicalize.rs index 459fb6c7d1..b3adc2ab1d 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalize/canonicalize.rs @@ -1,13 +1,28 @@ - - use roc_collections::all::MutMap; -use roc_problem::can::{Problem}; +use roc_problem::can::Problem; use roc_region::all::{Located, Region}; -use roc_types::{subs::Variable}; +use roc_types::subs::Variable; -use crate::{lang::{core::{def::def::References, expr::{expr2::{Expr2, ExprId, WhenBranch}, expr_to_expr2::to_expr2, output::Output, record_field::RecordField}, pattern::to_pattern2}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::{ + core::{ + def::def::References, + expr::{ + expr2::{Expr2, ExprId, WhenBranch}, + expr_to_expr2::to_expr2, + output::Output, + record_field::RecordField, + }, + pattern::to_pattern2, + }, + env::Env, + scope::Scope, + }, + pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; -pub (crate) enum CanonicalizeRecordProblem { +pub(crate) enum CanonicalizeRecordProblem { + #[allow(dead_code)] InvalidOptionalValue { field_name: PoolStr, field_region: Region, @@ -20,7 +35,7 @@ enum FieldVar { OnlyVar(Variable), } -pub (crate) fn canonicalize_fields<'a>( +pub(crate) fn canonicalize_fields<'a>( env: &mut Env<'a>, scope: &mut Scope, fields: &'a [Located>>], @@ -102,6 +117,7 @@ pub (crate) fn canonicalize_fields<'a>( Ok((pool_vec, output)) } +#[allow(dead_code)] enum CanonicalizeFieldProblem { InvalidOptionalValue { field_name: PoolStr, @@ -167,7 +183,7 @@ fn canonicalize_field<'a>( } #[inline(always)] -pub (crate) fn canonicalize_when_branch<'a>( +pub(crate) fn canonicalize_when_branch<'a>( env: &mut Env<'a>, scope: &mut Scope, branch: &'a roc_parse::ast::WhenBranch<'a>, @@ -239,7 +255,7 @@ pub (crate) fn canonicalize_when_branch<'a>( ) } -pub (crate) fn canonicalize_lookup( +pub(crate) fn canonicalize_lookup( env: &mut Env<'_>, scope: &mut Scope, module_name: &str, @@ -286,4 +302,4 @@ pub (crate) fn canonicalize_lookup( // If it's valid, this ident should be in scope already. (can_expr, output) -} \ No newline at end of file +} diff --git a/ast/src/canonicalize/mod.rs b/ast/src/canonicalize/mod.rs index 45b2084adf..fdbc754fc0 100644 --- a/ast/src/canonicalize/mod.rs +++ b/ast/src/canonicalize/mod.rs @@ -1,2 +1,2 @@ pub mod canonicalize; -pub mod module; \ No newline at end of file +pub mod module; diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalize/module.rs index 0c1ad7b089..ace177df8b 100644 --- a/ast/src/canonicalize/module.rs +++ b/ast/src/canonicalize/module.rs @@ -14,6 +14,7 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; +use crate::lang::core::def::def::canonicalize_defs; use crate::lang::core::def::def::Def; use crate::lang::core::def::def::{sort_can_defs, Declaration}; use crate::lang::core::expr::expr2::Expr2; @@ -27,7 +28,6 @@ use crate::pool::pool::NodeId; use crate::pool::pool::Pool; use crate::pool::pool_vec::PoolVec; use crate::pool::shallow_clone::ShallowClone; -use crate::lang::core::def::def::canonicalize_defs; pub struct ModuleOutput { pub aliases: MutMap>, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index 48a1d0cb8c..ca6b1a9a8d 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -13,7 +13,21 @@ use roc_types::{ types::{Category, Reason}, }; -use crate::{lang::{core::{expr::{expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, record_field::RecordField}, pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, types::{Type2, TypeId}, val_def::ValueDef}, env::Env}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::{ + core::{ + expr::{ + expr2::{ClosureExtra, Expr2, ExprId, WhenBranch}, + record_field::RecordField, + }, + pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct}, + types::{Type2, TypeId}, + val_def::ValueDef, + }, + env::Env, + }, + pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; #[derive(Debug)] pub enum Constraint<'a> { @@ -1739,19 +1753,39 @@ fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 { ) } +#[cfg(test)] pub mod test_constrain { use bumpalo::Bump; use roc_can::expected::Expected; use roc_collections::all::MutMap; - use roc_module::{ident::Lowercase, symbol::{IdentIds, Interns, ModuleIds, Symbol}}; + use roc_module::{ + ident::Lowercase, + symbol::{IdentIds, Interns, ModuleIds, Symbol}, + }; + use roc_parse::parser::SyntaxError; use roc_region::all::Region; - use roc_types::{pretty_print::content_to_string, solved_types::Solved, subs::{Subs, VarStore, Variable}}; + use roc_types::{ + pretty_print::content_to_string, + solved_types::Solved, + subs::{Subs, VarStore, Variable}, + }; - use crate::{constrain::constrain_expr, lang::{core::{expr::expr_to_expr2::str_to_expr2, types::Type2}, env::Env, scope::Scope}, pool::pool::Pool, solve_type}; use super::Constraint; + use crate::{ + constrain::constrain_expr, + lang::{ + core::{ + expr::{expr2::Expr2, expr_to_expr2::loc_expr_to_expr2, output::Output}, + types::Type2, + }, + env::Env, + scope::Scope, + }, + pool::pool::Pool, + solve_type, + }; use indoc::indoc; - fn run_solve<'a>( arena: &'a Bump, mempool: &mut Pool, @@ -1764,36 +1798,36 @@ pub mod test_constrain { vars_by_symbol: MutMap::default(), aliases, }; - + let mut subs = Subs::new(var_store); - + for (var, name) in rigid_variables { subs.rigid_var(var, name); } - + // Now that the module is parsed, canonicalized, and constrained, // we need to type check it. let mut problems = Vec::new(); - + // Run the solver to populate Subs. let (solved_subs, solved_env) = solve_type::run(arena, mempool, &env, &mut problems, subs, &constraint); - + (solved_subs, solved_env, problems) } - + fn infer_eq(actual: &str, expected_str: &str) { let mut env_pool = Pool::with_capacity(1024); let env_arena = Bump::new(); let code_arena = Bump::new(); - + let mut var_store = VarStore::default(); let var = var_store.fresh(); let dep_idents = IdentIds::exposed_builtins(8); let exposed_ident_ids = IdentIds::default(); let mut module_ids = ModuleIds::default(); let mod_id = module_ids.get_or_insert(&"ModId123".into()); - + let mut env = Env::new( mod_id, &env_arena, @@ -1803,13 +1837,13 @@ pub mod test_constrain { &module_ids, exposed_ident_ids, ); - + let mut scope = Scope::new(env.home, env.pool, env.var_store); - + let region = Region::zero(); - + let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region); - + match expr2_result { Ok((expr, _)) => { let constraint = constrain_expr( @@ -1819,18 +1853,18 @@ pub mod test_constrain { Expected::NoExpectation(Type2::Variable(var)), Region::zero(), ); - + let Env { pool, var_store: ref_var_store, mut dep_idents, .. } = env; - + // extract the var_store out of the env again let mut var_store = VarStore::default(); std::mem::swap(ref_var_store, &mut var_store); - + let (mut solved, _, _) = run_solve( &code_arena, pool, @@ -1839,27 +1873,40 @@ pub mod test_constrain { constraint, var_store, ); - + let subs = solved.inner_mut(); - + let content = subs.get_content_without_compacting(var); - + // Connect the ModuleId to it's IdentIds dep_idents.insert(mod_id, env.ident_ids); - + let interns = Interns { module_ids: env.module_ids.clone(), all_ident_ids: dep_idents, }; - + let actual_str = content_to_string(content, subs, mod_id, &interns); - + assert_eq!(actual_str, expected_str); } Err(e) => panic!("syntax error {:?}", e), } } - + + pub fn str_to_expr2<'a>( + arena: &'a Bump, + input: &'a str, + env: &mut Env<'a>, + scope: &mut Scope, + region: Region, + ) -> Result<(Expr2, Output), SyntaxError<'a>> { + match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { + Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), + Err(fail) => Err(fail), + } + } + #[test] fn constrain_str() { infer_eq( @@ -1871,7 +1918,7 @@ pub mod test_constrain { "Str", ) } - + // This will be more useful once we actually map // strings less than 15 chars to SmallStr #[test] @@ -1885,7 +1932,7 @@ pub mod test_constrain { "Str", ) } - + #[test] fn constrain_empty_record() { infer_eq( @@ -1897,7 +1944,7 @@ pub mod test_constrain { "{}", ) } - + #[test] fn constrain_small_int() { infer_eq( @@ -1909,7 +1956,7 @@ pub mod test_constrain { "Num *", ) } - + #[test] fn constrain_float() { infer_eq( @@ -1921,7 +1968,7 @@ pub mod test_constrain { "Float *", ) } - + #[test] fn constrain_record() { infer_eq( @@ -1933,7 +1980,7 @@ pub mod test_constrain { "{ x : Num *, y : Str }", ) } - + #[test] fn constrain_empty_list() { infer_eq( @@ -1945,7 +1992,7 @@ pub mod test_constrain { "List *", ) } - + #[test] fn constrain_list() { infer_eq( @@ -1957,7 +2004,7 @@ pub mod test_constrain { "List (Num *)", ) } - + #[test] fn constrain_list_of_records() { infer_eq( @@ -1969,7 +2016,7 @@ pub mod test_constrain { "List { x : Num * }", ) } - + #[test] fn constrain_global_tag() { infer_eq( @@ -1981,7 +2028,7 @@ pub mod test_constrain { "[ Foo ]*", ) } - + #[test] fn constrain_private_tag() { infer_eq( @@ -1993,7 +2040,7 @@ pub mod test_constrain { "[ @Foo ]*", ) } - + #[test] fn constrain_call_and_accessor() { infer_eq( @@ -2005,7 +2052,7 @@ pub mod test_constrain { "Str", ) } - + #[test] fn constrain_access() { infer_eq( @@ -2017,7 +2064,7 @@ pub mod test_constrain { "Str", ) } - + #[test] fn constrain_if() { infer_eq( @@ -2029,7 +2076,7 @@ pub mod test_constrain { "[ Green, Red ]*", ) } - + #[test] fn constrain_when() { infer_eq( @@ -2043,7 +2090,7 @@ pub mod test_constrain { "[ Blue, Purple ]*", ) } - + #[test] fn constrain_let_value() { infer_eq( @@ -2057,7 +2104,7 @@ pub mod test_constrain { "{ name : Str }", ) } - + #[test] fn constrain_update() { infer_eq( @@ -2071,7 +2118,7 @@ pub mod test_constrain { "{ name : Str }", ) } - + #[ignore = "TODO: implement builtins in the editor"] #[test] fn constrain_run_low_level() { @@ -2084,7 +2131,7 @@ pub mod test_constrain { "List Str", ) } - + #[test] fn constrain_closure() { infer_eq( @@ -2098,5 +2145,4 @@ pub mod test_constrain { "{}* -> Num *", ) } - } diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs index 12241c567b..d337d91e6f 100644 --- a/ast/src/lang/core/ast.rs +++ b/ast/src/lang/core/ast.rs @@ -1,6 +1,13 @@ -use crate::{ast_error::{ASTResult, ASTNodeIdWithoutExprId}, pool::pool::Pool}; +use crate::{ + ast_error::{ASTNodeIdWithoutExprId, ASTResult}, + pool::pool::Pool, +}; -use super::{def::def2::{DefId, def2_to_string}, expr::{expr2::ExprId, expr2_to_string::expr2_to_string}, header::AppHeader}; +use super::{ + def::def2::{def2_to_string, DefId}, + expr::{expr2::ExprId, expr2_to_string::expr2_to_string}, + header::AppHeader, +}; #[derive(Debug)] pub struct AST { @@ -35,4 +42,4 @@ pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String { ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool), ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool), } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/declaration.rs b/ast/src/lang/core/declaration.rs index 207886bece..0c9ca03bfe 100644 --- a/ast/src/lang/core/declaration.rs +++ b/ast/src/lang/core/declaration.rs @@ -1,10 +1,18 @@ use roc_types::subs::VarStore; -use crate::{lang::core::{def::def::Def, expr::expr2::Expr2}, pool::{pool::Pool, pool_vec::PoolVec}}; +use crate::{ + lang::core::{def::def::Def, expr::expr2::Expr2}, + pool::{pool::Pool, pool_vec::PoolVec}, +}; use super::def::def::Declaration; -pub(crate) fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Declaration, ret: Expr2) -> Expr2 { +pub(crate) fn decl_to_let( + pool: &mut Pool, + var_store: &mut VarStore, + decl: Declaration, + ret: Expr2, +) -> Expr2 { match decl { Declaration::Declare(def) => match def { Def::AnnotationOnly { .. } => todo!(), @@ -59,4 +67,4 @@ pub(crate) fn decl_to_let(pool: &mut Pool, var_store: &mut VarStore, decl: Decla unreachable!() } } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 02c9e94ff9..3bcd60f670 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -24,7 +24,21 @@ use std::collections::HashMap; use std::fmt::Debug; use ven_graph::{strongly_connected_components, topological_sort_into_groups}; -use crate::{lang::{core::{expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output}, fun_def::FunctionDef, pattern::{self, Pattern2, PatternId, symbols_from_pattern, to_pattern_id}, types::{Alias, Annotation2, Signature, Type2, TypeId, to_annotation2}, val_def::ValueDef}, env::Env, rigids::Rigids, scope::Scope}, pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::{ + core::{ + expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output}, + fun_def::FunctionDef, + pattern::{self, symbols_from_pattern, to_pattern_id, Pattern2, PatternId}, + types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId}, + val_def::ValueDef, + }, + env::Env, + rigids::Rigids, + scope::Scope, + }, + pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; #[derive(Debug)] pub enum Def { diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs index 935bfe6609..85cd2b9a64 100644 --- a/ast/src/lang/core/def/def2.rs +++ b/ast/src/lang/core/def/def2.rs @@ -1,5 +1,10 @@ -use crate::{lang::core::{expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, pattern::Pattern2}, pool::pool::{NodeId, Pool}}; - +use crate::{ + lang::core::{ + expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, + pattern::Pattern2, + }, + pool::pool::{NodeId, Pool}, +}; // A top level definition, not inside a function. For example: `main = "Hello, world!"` #[derive(Debug)] @@ -35,4 +40,4 @@ pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String { } full_string -} \ No newline at end of file +} diff --git a/ast/src/lang/core/def/def_to_def2.rs b/ast/src/lang/core/def/def_to_def2.rs index b35b7e3890..05cf2747a6 100644 --- a/ast/src/lang/core/def/def_to_def2.rs +++ b/ast/src/lang/core/def/def_to_def2.rs @@ -1,9 +1,13 @@ -use bumpalo::Bump; use bumpalo::collections::Vec as BumpVec; +use bumpalo::Bump; use roc_parse::{parser::SyntaxError, pattern::PatternType}; use roc_region::all::Region; -use crate::lang::{core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, env::Env, scope::Scope}; +use crate::lang::{ + core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2}, + env::Env, + scope::Scope, +}; use super::def2::Def2; @@ -14,7 +18,6 @@ pub fn defs_to_defs2<'a>( parsed_defs: &'a BumpVec>>, region: Region, ) -> Vec { - parsed_defs .iter() .map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region)) @@ -91,4 +94,4 @@ pub fn str_to_def2<'a>( )), Err(fail) => Err(fail), } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/def/mod.rs b/ast/src/lang/core/def/mod.rs index d790ec3ea5..7ab541b811 100644 --- a/ast/src/lang/core/def/mod.rs +++ b/ast/src/lang/core/def/mod.rs @@ -1,3 +1,3 @@ -pub mod def_to_def2; pub mod def; -pub mod def2; \ No newline at end of file +pub mod def2; +pub mod def_to_def2; diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 83e263b3d2..8b2ad1b84b 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -1,11 +1,14 @@ use arraystring::{typenum::U30, ArrayString}; use roc_types::subs::Variable; -use crate::{lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{ + lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, + pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, +}; use roc_can::expr::Recursive; -use roc_module::symbol::Symbol; use roc_module::low_level::LowLevel; use roc_module::operator::CalledVia; +use roc_module::symbol::Symbol; use super::record_field::RecordField; @@ -227,4 +230,4 @@ pub struct ClosureExtra { pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B pub closure_type: Variable, // 4B pub closure_ext_var: Variable, // 4B -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs index b9ec8a234b..6f8255ccf6 100644 --- a/ast/src/lang/core/expr/expr2_to_string.rs +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -1,7 +1,10 @@ -use crate::{lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, pool::pool::Pool}; +use crate::{ + lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, + pool::pool::Pool, +}; -use roc_types::subs::Variable; use super::expr2::{Expr2, ExprId}; +use roc_types::subs::Variable; pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String { let mut full_string = String::new(); @@ -133,4 +136,4 @@ fn expr2_to_string_helper( fn var_to_string(some_var: &Variable, indent_level: usize) -> String { format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var) -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 4e277d590c..861ee2d49d 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -3,34 +3,29 @@ use roc_can::expr::Recursive; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::operator::desugar_expr; use roc_collections::all::MutSet; -use roc_parse::parser::SyntaxError; +use roc_module::symbol::Symbol; use roc_parse::{ast::Expr, pattern::PatternType}; use roc_problem::can::{Problem, RuntimeError}; -use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; +use super::{expr2::Expr2, output::Output}; +use crate::canonicalize::canonicalize::{ + canonicalize_fields, canonicalize_lookup, canonicalize_when_branch, CanonicalizeRecordProblem, +}; use crate::lang::core::declaration::decl_to_let; use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs}; use crate::lang::core::expr::expr2::ClosureExtra; use crate::lang::core::pattern::to_pattern2; use crate::lang::core::str::flatten_str_literal; use crate::pool::shallow_clone::ShallowClone; -use crate::{lang::{core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope}, pool::{pool_str::PoolStr, pool_vec::PoolVec}}; -use crate::canonicalize::canonicalize::{CanonicalizeRecordProblem, canonicalize_fields, canonicalize_lookup, canonicalize_when_branch}; -use super::{expr2::Expr2, output::Output}; - -pub fn str_to_expr2<'a>( - arena: &'a Bump, - input: &'a str, - env: &mut Env<'a>, - scope: &mut Scope, - region: Region, -) -> Result<(Expr2, self::Output), SyntaxError<'a>> { - match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) { - Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)), - Err(fail) => Err(fail), - } -} +use crate::{ + lang::{ + core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, + env::Env, + scope::Scope, + }, + pool::{pool_str::PoolStr, pool_vec::PoolVec}, +}; pub fn loc_expr_to_expr2<'a>( arena: &'a Bump, @@ -712,4 +707,4 @@ pub fn to_expr_id<'a>( let (expr, output) = to_expr2(env, scope, parse_expr, region); (env.add(expr, region), output) -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/introduced_vars.rs b/ast/src/lang/core/expr/introduced_vars.rs index 7a8db4c3fa..0abb087815 100644 --- a/ast/src/lang/core/expr/introduced_vars.rs +++ b/ast/src/lang/core/expr/introduced_vars.rs @@ -1,8 +1,7 @@ - -use roc_types::subs::{Variable}; -use roc_collections::all::{MutMap}; -use roc_module::ident::{Lowercase}; -use roc_module::symbol::{Symbol}; +use roc_collections::all::MutMap; +use roc_module::ident::Lowercase; +use roc_module::symbol::Symbol; +use roc_types::subs::Variable; #[derive(Clone, Debug, PartialEq, Default)] pub struct IntroducedVariables { @@ -49,4 +48,4 @@ impl IntroducedVariables { pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { self.name_by_var.get(&var) } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/expr/mod.rs b/ast/src/lang/core/expr/mod.rs index 1a58685fc2..32d768c4c9 100644 --- a/ast/src/lang/core/expr/mod.rs +++ b/ast/src/lang/core/expr/mod.rs @@ -1,6 +1,6 @@ pub mod expr2; pub mod expr2_to_string; -pub (crate) mod output; +pub(crate) mod expr_to_expr2; mod introduced_vars; -pub (crate) mod expr_to_expr2; +pub(crate) mod output; pub mod record_field; diff --git a/ast/src/lang/core/expr/output.rs b/ast/src/lang/core/expr/output.rs index 3c84e9a618..af7f455465 100644 --- a/ast/src/lang/core/expr/output.rs +++ b/ast/src/lang/core/expr/output.rs @@ -1,4 +1,7 @@ -use crate::{lang::core::{def::def::References, types::{Alias}}, pool::pool::NodeId}; +use crate::{ + lang::core::{def::def::References, types::Alias}, + pool::pool::NodeId, +}; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::Symbol; @@ -25,4 +28,3 @@ impl Output { self.non_closures.extend(other.non_closures); } } - \ No newline at end of file diff --git a/ast/src/lang/core/expr/record_field.rs b/ast/src/lang/core/expr/record_field.rs index 2db2fc6fe5..90c1fb6ed7 100644 --- a/ast/src/lang/core/expr/record_field.rs +++ b/ast/src/lang/core/expr/record_field.rs @@ -1,7 +1,6 @@ - use roc_types::subs::Variable; -use crate::{pool::{pool_str::PoolStr}}; +use crate::pool::pool_str::PoolStr; use roc_module::symbol::Symbol; use super::expr2::ExprId; @@ -16,8 +15,6 @@ pub enum RecordField { use RecordField::*; impl RecordField { - - pub fn get_record_field_var(&self) -> &Variable { match self { InvalidLabelOnly(_, var) => var, @@ -49,4 +46,4 @@ impl RecordField { LabeledValue(_, _, field_val_id) => Some(*field_val_id), } } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs index ea67f75082..773e6ecd66 100644 --- a/ast/src/lang/core/fun_def.rs +++ b/ast/src/lang/core/fun_def.rs @@ -1,10 +1,15 @@ - - -use crate::{lang::rigids::Rigids, pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}}; +use crate::{ + lang::rigids::Rigids, + pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}, +}; use roc_module::symbol::Symbol; use roc_types::subs::Variable; -use super::{expr::expr2::ExprId, pattern::PatternId, types::{Type2, TypeId}}; +use super::{ + expr::expr2::ExprId, + pattern::PatternId, + types::{Type2, TypeId}, +}; #[derive(Debug)] pub enum FunctionDef { @@ -53,4 +58,4 @@ impl ShallowClone for FunctionDef { }, } } -} \ No newline at end of file +} diff --git a/ast/src/lang/core/header.rs b/ast/src/lang/core/header.rs index 60c359af0e..4b10dad537 100644 --- a/ast/src/lang/core/header.rs +++ b/ast/src/lang/core/header.rs @@ -1,7 +1,5 @@ - use super::expr::expr2::ExprId; - #[derive(Debug)] pub struct AppHeader { pub app_name: String, @@ -9,4 +7,4 @@ pub struct AppHeader { pub imports: Vec, pub provides: Vec, pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId -} \ No newline at end of file +} diff --git a/ast/src/lang/core/mod.rs b/ast/src/lang/core/mod.rs index 41e852d20f..74300dab4f 100644 --- a/ast/src/lang/core/mod.rs +++ b/ast/src/lang/core/mod.rs @@ -1,10 +1,10 @@ +pub mod ast; +mod declaration; pub mod def; pub mod expr; -pub mod header; -pub mod ast; -pub mod val_def; mod fun_def; +pub mod header; pub mod pattern; -pub mod types; pub mod str; -mod declaration; \ No newline at end of file +pub mod types; +pub mod val_def; diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index ddab1bef5a..230812285e 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -13,6 +13,7 @@ use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::Region; use roc_types::subs::Variable; +use crate::ast_error::{ASTResult, UnexpectedPattern2Variant}; use crate::constrain::Constraint; use crate::lang::core::expr::expr_to_expr2::to_expr_id; use crate::lang::env::Env; @@ -21,7 +22,6 @@ use crate::pool::pool::{NodeId, Pool}; use crate::pool::pool_str::PoolStr; use crate::pool::pool_vec::PoolVec; use crate::pool::shallow_clone::ShallowClone; -use crate::ast_error::{ASTResult, UnexpectedPattern2Variant}; use super::expr::expr2::{ExprId, FloatVal, IntVal}; use super::expr::output::Output; @@ -576,7 +576,7 @@ fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 { Pattern2::UnsupportedPattern(region) } -pub (crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { +pub(crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { use roc_parse::ast::StrLiteral::*; match literal { @@ -586,7 +586,7 @@ pub (crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> } } -pub (crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { +pub(crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { use StrSegment::*; let mut buf = String::new(); diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 5e94ad76e3..69d2cca7d2 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -1,11 +1,18 @@ use roc_module::{operator::CalledVia, symbol::Symbol}; use roc_parse::ast::StrLiteral; -use crate::{ast_error::{ASTResult, UnexpectedASTNode}, lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}}; +use crate::{ + ast_error::{ASTResult, UnexpectedASTNode}, + lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, + pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}, +}; -use super::expr::{expr2::{Expr2, ExprId}, output::Output}; +use super::expr::{ + expr2::{Expr2, ExprId}, + output::Output, +}; -pub (crate) fn flatten_str_literal<'a>( +pub(crate) fn flatten_str_literal<'a>( env: &mut Env<'a>, scope: &mut Scope, literal: &StrLiteral<'a>, @@ -218,4 +225,4 @@ pub fn update_str_expr( } Ok(()) -} \ No newline at end of file +} diff --git a/ast/src/lang/core/val_def.rs b/ast/src/lang/core/val_def.rs index c97df3f810..2b97221c77 100644 --- a/ast/src/lang/core/val_def.rs +++ b/ast/src/lang/core/val_def.rs @@ -1,7 +1,17 @@ -use crate::{lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, pool::{pool::{NodeId, Pool}, shallow_clone::ShallowClone}}; -use roc_types::subs::{Variable}; +use crate::{ + lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, + pool::{ + pool::{NodeId, Pool}, + shallow_clone::ShallowClone, + }, +}; +use roc_types::subs::Variable; -use super::{expr::expr2::ExprId, pattern::{Pattern2, PatternId}, types::TypeId}; +use super::{ + expr::expr2::ExprId, + pattern::{Pattern2, PatternId}, + types::TypeId, +}; #[derive(Debug)] pub enum ValueDef { @@ -88,4 +98,4 @@ pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String { ) } } -} \ No newline at end of file +} diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index 5a6d212fcf..c3ee64edc3 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -4,7 +4,7 @@ use roc_module::ident::{Ident, ModuleName}; use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol}; use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; -use roc_types::subs::{VarStore}; +use roc_types::subs::VarStore; use crate::pool::pool::{NodeId, Pool}; @@ -165,4 +165,4 @@ impl<'a> Env<'a> { }), } } -} \ No newline at end of file +} diff --git a/ast/src/lang/mod.rs b/ast/src/lang/mod.rs index b6a8753591..fa21ea740d 100644 --- a/ast/src/lang/mod.rs +++ b/ast/src/lang/mod.rs @@ -1,4 +1,4 @@ pub mod core; -pub mod scope; -mod rigids; pub mod env; +mod rigids; +pub mod scope; diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index 94bca10038..be43173714 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -1,8 +1,11 @@ -use std::{collections::{HashMap, HashSet}, hash::BuildHasherDefault}; +use std::{ + collections::{HashMap, HashSet}, + hash::BuildHasherDefault, +}; use crate::pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}; use roc_collections::all::WyHash; -use roc_types::subs::{Variable}; +use roc_types::subs::Variable; #[derive(Debug)] pub struct Rigids { diff --git a/ast/src/lib.rs b/ast/src/lib.rs index 55191171a8..e7f03db59d 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -1,7 +1,7 @@ -pub mod lang; -pub mod pool; -pub mod parse; -pub mod constrain; -mod canonicalize; pub mod ast_error; -pub mod solve_type; \ No newline at end of file +mod canonicalize; +pub mod constrain; +pub mod lang; +pub mod parse; +pub mod pool; +pub mod solve_type; diff --git a/ast/src/parse/mod.rs b/ast/src/parse/mod.rs index 095e190b43..f14f61f5a0 100644 --- a/ast/src/parse/mod.rs +++ b/ast/src/parse/mod.rs @@ -1,3 +1,3 @@ pub mod parse_ast; pub mod parse_expr; -pub mod parse_header; \ No newline at end of file +pub mod parse_header; diff --git a/ast/src/parse/parse_ast.rs b/ast/src/parse/parse_ast.rs index 55ea683966..3ae3cca0d7 100644 --- a/ast/src/parse/parse_ast.rs +++ b/ast/src/parse/parse_ast.rs @@ -2,12 +2,18 @@ use bumpalo::Bump; use roc_parse::parser::SyntaxError; use roc_region::all::Region; -use crate::lang::{core::{ast::AST, def::{def2::DefId, def_to_def2::str_to_def2}, expr::expr2::Expr2}, env::Env, scope::Scope}; +use crate::lang::{ + core::{ + ast::AST, + def::{def2::DefId, def_to_def2::str_to_def2}, + expr::expr2::Expr2, + }, + env::Env, + scope::Scope, +}; use super::parse_header; - - pub fn parse_from_string<'a>( code_str: &'a str, env: &mut Env<'a>, @@ -39,4 +45,4 @@ pub fn parse_from_string<'a>( header: parse_header::parse_from_string(header_str, ast_node_id), def_ids, }) -} \ No newline at end of file +} diff --git a/ast/src/parse/parse_expr.rs b/ast/src/parse/parse_expr.rs index e69de29bb2..8b13789179 100644 --- a/ast/src/parse/parse_expr.rs +++ b/ast/src/parse/parse_expr.rs @@ -0,0 +1 @@ + diff --git a/ast/src/parse/parse_header.rs b/ast/src/parse/parse_header.rs index 6287806656..e387fea026 100644 --- a/ast/src/parse/parse_header.rs +++ b/ast/src/parse/parse_header.rs @@ -1,6 +1,5 @@ use crate::lang::core::{expr::expr2::ExprId, header::AppHeader}; - // TODO don't use mock struct and actually parse string pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { AppHeader { @@ -10,4 +9,4 @@ pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader { provides: vec!["main".to_owned()], ast_node_id, } -} \ No newline at end of file +} diff --git a/ast/src/pool/mod.rs b/ast/src/pool/mod.rs index 1b0cd90832..b6c8c83b8f 100644 --- a/ast/src/pool/mod.rs +++ b/ast/src/pool/mod.rs @@ -1,4 +1,4 @@ pub mod pool; pub mod pool_str; pub mod pool_vec; -pub mod shallow_clone; \ No newline at end of file +pub mod shallow_clone; diff --git a/ast/src/pool/pool.rs b/ast/src/pool/pool.rs index 97ec3ca7af..ab4ae5548d 100644 --- a/ast/src/pool/pool.rs +++ b/ast/src/pool/pool.rs @@ -61,8 +61,8 @@ pub const NODE_BYTES: usize = 32; #[derive(Debug, Eq)] pub struct NodeId { - pub (super) index: u32, - pub (super) _phantom: PhantomData, + pub(super) index: u32, + pub(super) _phantom: PhantomData, } impl Clone for NodeId { @@ -84,7 +84,7 @@ impl Copy for NodeId {} #[derive(Debug)] pub struct Pool { - pub (super) nodes: *mut [u8; NODE_BYTES], + pub(super) nodes: *mut [u8; NODE_BYTES], num_nodes: u32, capacity: u32, // free_1node_slots: Vec>, @@ -149,7 +149,7 @@ impl Pool { /// Reserves the given number of contiguous node slots, and returns /// the NodeId of the first one. We only allow reserving 2^32 in a row. - pub (super) fn reserve(&mut self, nodes: u32) -> NodeId { + pub(super) fn reserve(&mut self, nodes: u32) -> NodeId { // TODO once we have a free list, look in there for an open slot first! let index = self.num_nodes; @@ -225,4 +225,4 @@ impl Drop for Pool { ); } } -} \ No newline at end of file +} diff --git a/ast/src/pool/pool_str.rs b/ast/src/pool/pool_str.rs index 02a9d47603..435d4586bb 100644 --- a/ast/src/pool/pool_str.rs +++ b/ast/src/pool/pool_str.rs @@ -1,11 +1,9 @@ - use super::pool::{NodeId, Pool, NODE_BYTES}; use super::shallow_clone::ShallowClone; -use libc::{c_void}; +use libc::c_void; use std::marker::PhantomData; use std::mem::size_of; - /// A string containing at most 2^32 pool-allocated bytes. #[derive(Debug, Copy, Clone)] pub struct PoolStr { @@ -85,4 +83,4 @@ impl ShallowClone for PoolStr { len: self.len, } } -} \ No newline at end of file +} diff --git a/ast/src/pool/pool_vec.rs b/ast/src/pool/pool_vec.rs index 3da278e485..65c9e89b1b 100644 --- a/ast/src/pool/pool_vec.rs +++ b/ast/src/pool/pool_vec.rs @@ -1,11 +1,10 @@ - use super::pool::{NodeId, Pool, NODE_BYTES}; use super::shallow_clone::ShallowClone; -use libc::{c_void}; -use std::marker::PhantomData; -use std::mem::size_of; +use libc::c_void; use std::any::type_name; use std::cmp::Ordering; +use std::marker::PhantomData; +use std::mem::size_of; /// An array of at most 2^32 pool-allocated nodes. #[derive(Debug)] @@ -321,4 +320,4 @@ fn pool_vec_iter_test() { let current_vec: Vec = pool_vec.iter(&test_pool).copied().collect(); assert_eq!(current_vec, expected_vec); -} \ No newline at end of file +} diff --git a/ast/src/pool/shallow_clone.rs b/ast/src/pool/shallow_clone.rs index cf7c24f8f0..f444b1f897 100644 --- a/ast/src/pool/shallow_clone.rs +++ b/ast/src/pool/shallow_clone.rs @@ -1,4 +1,3 @@ - use roc_can::expected::Expected; use roc_can::expected::PExpected; diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 7c89b3440a..986f867856 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -200,7 +200,6 @@ fn solve<'a>( subs: &mut Subs, constraint: &Constraint, ) -> State { - use crate::solve_type::Constraint::*; match constraint { diff --git a/code_markup/src/lib.rs b/code_markup/src/lib.rs index df6e4f24ed..9e5d220855 100644 --- a/code_markup/src/lib.rs +++ b/code_markup/src/lib.rs @@ -1,5 +1,5 @@ +pub mod colors; pub mod markup; pub mod markup_error; pub mod slow_pool; pub mod syntax_highlight; -pub mod colors; diff --git a/code_markup/src/markup/attribute.rs b/code_markup/src/markup/attribute.rs index 31a575d35c..0adb489659 100644 --- a/code_markup/src/markup/attribute.rs +++ b/code_markup/src/markup/attribute.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use snafu::ensure; -use crate::markup_error::{MarkResult, CaretNotFound}; +use crate::markup_error::{CaretNotFound, MarkResult}; #[derive(Debug, Copy, Clone)] pub struct Caret { diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs index c44000534e..6cdc31eda9 100644 --- a/code_markup/src/markup/common_nodes.rs +++ b/code_markup/src/markup/common_nodes.rs @@ -1,4 +1,3 @@ - use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId}; use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle}; diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index 6a08432891..4fe4d5c954 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -1,6 +1,16 @@ -use crate::{markup::common_nodes::{new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn, new_right_square_mn}, markup_error::MarkResult, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; +use crate::{ + markup::common_nodes::{ + new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn, + new_left_square_mn, new_right_accolade_mn, new_right_square_mn, + }, + markup_error::MarkResult, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; -use super::{attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node}; +use super::{ + attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node, +}; /*use crate::editor::ed_error::EdResult; use crate::editor::ed_error::ExpectedTextNode; use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; @@ -28,12 +38,29 @@ use crate::lang::parse::{AppHeader, AST}; use crate::lang::pattern::get_identifier_string; use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; use crate::ui::util::slice_get;*/ +use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired}; use bumpalo::Bump; -use roc_ast::{ast_error::ASTResult, lang::{core::{ast::{AST, ASTNodeId}, def::def2::{Def2, DefId}, expr::{expr2::{Expr2, ExprId}, record_field::RecordField}, header::AppHeader, pattern::get_identifier_string, val_def::ValueDef}, env::Env}, pool::pool_str::PoolStr}; -use roc_utils::{index_of, slice_get}; +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::{ASTNodeId, AST}, + def::def2::{Def2, DefId}, + expr::{ + expr2::{Expr2, ExprId}, + record_field::RecordField, + }, + header::AppHeader, + pattern::get_identifier_string, + val_def::ValueDef, + }, + env::Env, + }, + pool::pool_str::PoolStr, +}; use roc_module::symbol::Interns; +use roc_utils::{index_of, slice_get}; use std::fmt; -use crate::markup_error::{NestedNodeMissingChild, NestedNodeRequired, ExpectedTextNode}; #[derive(Debug)] pub enum MarkupNode { diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs index 78d086f3fe..5cadc131ac 100644 --- a/code_markup/src/markup/top_level_def.rs +++ b/code_markup/src/markup/top_level_def.rs @@ -1,8 +1,20 @@ -use roc_ast::{ast_error::ASTResult, lang::{core::{ast::ASTNodeId, pattern::{PatternId, get_identifier_string}}, env::Env}}; +use roc_ast::{ + ast_error::ASTResult, + lang::{ + core::{ + ast::ASTNodeId, + pattern::{get_identifier_string, PatternId}, + }, + env::Env, + }, +}; use roc_module::symbol::Interns; -use crate::{markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; - +use crate::{ + markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode}, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; pub fn tld_mark_node<'a>( identifier_id: PatternId, @@ -36,4 +48,4 @@ pub fn tld_mark_node<'a>( }; Ok(full_let_node) -} \ No newline at end of file +} diff --git a/code_markup/src/markup_error.rs b/code_markup/src/markup_error.rs index a3d3d2a680..5de9768fba 100644 --- a/code_markup/src/markup_error.rs +++ b/code_markup/src/markup_error.rs @@ -1,6 +1,5 @@ - use roc_utils::util_error::UtilError; -use snafu::{Backtrace, NoneError, Snafu, ResultExt}; +use snafu::{Backtrace, NoneError, ResultExt, Snafu}; use crate::slow_pool::MarkNodeId; @@ -53,4 +52,4 @@ impl From for MarkError { let dummy_res: Result<(), NoneError> = Err(NoneError {}); dummy_res.context(UtilErrorBacktrace { msg }).unwrap_err() } -} \ No newline at end of file +} diff --git a/code_markup/src/slow_pool.rs b/code_markup/src/slow_pool.rs index df8335b570..1f8a12c4af 100644 --- a/code_markup/src/slow_pool.rs +++ b/code_markup/src/slow_pool.rs @@ -1,4 +1,3 @@ - use std::fmt; use crate::markup::nodes::MarkupNode; diff --git a/code_markup/src/syntax_highlight.rs b/code_markup/src/syntax_highlight.rs index 2f5fcf6865..12c0ef33e1 100644 --- a/code_markup/src/syntax_highlight.rs +++ b/code_markup/src/syntax_highlight.rs @@ -1,8 +1,7 @@ - use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::colors::{self, RgbaTup, from_hsb}; +use crate::colors::{self, from_hsb, RgbaTup}; #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] pub enum HighlightStyle { diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index e79e90e23e..357693bb69 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,5 +1,4 @@ - -use crate::{ui::text::text_pos::TextPos}; +use crate::ui::text::text_pos::TextPos; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId; @@ -310,4 +309,4 @@ impl From for EdError { let dummy_res: Result<(), NoneError> = Err(NoneError {}); dummy_res.context(ASTErrorBacktrace { msg }).unwrap_err() } -} \ No newline at end of file +} diff --git a/editor/src/editor/grid_node_map.rs b/editor/src/editor/grid_node_map.rs index 38355aacc2..e342d624c1 100644 --- a/editor/src/editor/grid_node_map.rs +++ b/editor/src/editor/grid_node_map.rs @@ -12,9 +12,9 @@ use roc_ast::lang::core::ast::ASTNodeId; use roc_code_markup::markup::nodes::get_root_mark_node_id; use roc_code_markup::slow_pool::MarkNodeId; use roc_code_markup::slow_pool::SlowPool; +use snafu::OptionExt; use std::cmp::Ordering; use std::fmt; -use snafu::OptionExt; #[derive(Debug)] pub struct GridNodeMap { diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 4d42e4e12c..12eb4bb110 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -11,7 +11,7 @@ use crate::ui::text::text_pos::TextPos; use crate::ui::ui_error::UIResult; use bumpalo::Bump; use nonempty::NonEmpty; -use roc_ast::lang::core::ast::{AST, ASTNodeId}; +use roc_ast::lang::core::ast::{ASTNodeId, AST}; use roc_ast::lang::env::Env; use roc_ast::parse::parse_ast; use roc_ast::pool::pool_str::PoolStr; @@ -62,15 +62,13 @@ pub fn init_model<'a>( let markup_ids = if code_str.is_empty() { EmptyCodeString {}.fail() } else { - Ok( - ast_to_mark_nodes( - code_arena, - &mut module.env, - &module.ast, - &mut mark_node_pool, - &loaded_module.interns, - )? - ) + Ok(ast_to_mark_nodes( + code_arena, + &mut module.env, + &module.ast, + &mut mark_node_pool, + &loaded_module.interns, + )?) }?; let mut code_lines = CodeLines::default(); diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index 2e8c527d2c..ce0c894955 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -51,8 +51,8 @@ use crate::ui::util::path_to_string; use crate::ui::util::write_to_file; use crate::window::keyboard_input::Modifiers; use bumpalo::Bump; -use roc_ast::constrain::Constraint; use roc_ast::constrain::constrain_expr; +use roc_ast::constrain::Constraint; use roc_ast::lang::core::ast::ASTNodeId; use roc_ast::lang::core::def::def2::Def2; use roc_ast::lang::core::def::def2::DefId; @@ -65,8 +65,8 @@ use roc_ast::solve_type; use roc_can::expected::Expected; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::nodes; -use roc_code_markup::markup::nodes::EQUALS; use roc_code_markup::markup::nodes::MarkupNode; +use roc_code_markup::markup::nodes::EQUALS; use roc_code_markup::slow_pool::MarkNodeId; use roc_code_markup::slow_pool::SlowPool; use roc_code_markup::syntax_highlight::HighlightStyle; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index bee8f0623d..c2b63212f8 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -1,5 +1,5 @@ +use roc_ast::lang::core::expr::expr2::Expr2::SmallInt; use roc_ast::lang::core::expr::expr2::IntStyle; -use roc_ast::lang::core::expr::expr2::Expr2::{SmallInt}; use roc_ast::lang::core::expr::expr2::IntVal; use roc_ast::pool::pool_str::PoolStr; use roc_code_markup::markup::attribute::Attributes; @@ -146,7 +146,6 @@ pub fn update_int( } fn update_small_int_num(number: &mut IntVal, updated_str: &str) -> EdResult<()> { - use IntVal::*; *number = match number { diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 764b81892b..7036701b9d 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,7 +1,9 @@ -use roc_ast::lang::core::ast::{ASTNodeId, ast_node_to_string}; +use roc_ast::lang::core::ast::{ast_node_to_string, ASTNodeId}; use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; use roc_ast::pool::pool_vec::PoolVec; -use roc_code_markup::markup::common_nodes::{new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn}; +use roc_code_markup::markup::common_nodes::{ + new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, +}; use roc_code_markup::markup::nodes::{self, MarkupNode}; use roc_code_markup::slow_pool::MarkNodeId; diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index b07e1c7010..e7a8b3258d 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -1,11 +1,28 @@ -use roc_ast::{lang::{core::{ast::ASTNodeId, def::def2::Def2, expr::expr2::Expr2, pattern::{Pattern2, get_identifier_string}}, env::Env}, pool::pool::NodeId}; -use roc_code_markup::{markup::{attribute::Attributes, common_nodes::{new_blank_mn_w_nls, new_equals_mn}, nodes::{MarkupNode, set_parent_for_all}}, slow_pool::{MarkNodeId, SlowPool}, syntax_highlight::HighlightStyle}; +use roc_ast::{ + lang::{ + core::{ + ast::ASTNodeId, + def::def2::Def2, + expr::expr2::Expr2, + pattern::{get_identifier_string, Pattern2}, + }, + env::Env, + }, + pool::pool::NodeId, +}; +use roc_code_markup::{ + markup::{ + attribute::Attributes, + common_nodes::{new_blank_mn_w_nls, new_equals_mn}, + nodes::{set_parent_for_all, MarkupNode}, + }, + slow_pool::{MarkNodeId, SlowPool}, + syntax_highlight::HighlightStyle, +}; use roc_module::symbol::{Interns, Symbol}; use crate::{ - editor::{ - ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, - }, + editor::ed_error::{EdResult, FailedToUpdateIdentIdName, KeyNotFound}, ui::text::text_pos::TextPos, }; diff --git a/editor/src/editor/render_ast.rs b/editor/src/editor/render_ast.rs index 651b55b47d..00d035a692 100644 --- a/editor/src/editor/render_ast.rs +++ b/editor/src/editor/render_ast.rs @@ -1,10 +1,9 @@ - use crate::editor::mvc::ed_view::RenderedWgpu; use crate::editor::{ed_error::EdResult, theme::EdTheme, util::map_get}; use crate::graphics::primitives::rect::Rect; use crate::graphics::primitives::text as gr_text; use cgmath::Vector2; -use roc_code_markup::markup::nodes::{BLANK_PLACEHOLDER, MarkupNode}; +use roc_code_markup::markup::nodes::{MarkupNode, BLANK_PLACEHOLDER}; use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use winit::dpi::PhysicalSize; diff --git a/editor/src/editor/theme.rs b/editor/src/editor/theme.rs index dcc3b437b1..30e45a47a9 100644 --- a/editor/src/editor/theme.rs +++ b/editor/src/editor/theme.rs @@ -1,5 +1,5 @@ use gr_colors::{from_hsb, RgbaTup}; -use roc_code_markup::syntax_highlight::{HighlightStyle, default_highlight_map}; +use roc_code_markup::syntax_highlight::{default_highlight_map, HighlightStyle}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 3e4b259885..5ee7ea9fd5 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -3,7 +3,6 @@ #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] #[cfg_attr(test, macro_use)] -extern crate indoc; extern crate pest; #[cfg_attr(test, macro_use)] extern crate pest_derive; diff --git a/utils/src/lib.rs b/utils/src/lib.rs index d9402be3e5..bbab75e59b 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,7 +1,6 @@ - use snafu::OptionExt; -use util_error::{UtilResult, KeyNotFound, IndexOfFailed, OutOfBounds}; use std::{collections::HashMap, slice::SliceIndex}; +use util_error::{IndexOfFailed, KeyNotFound, OutOfBounds, UtilResult}; pub mod util_error; diff --git a/utils/src/util_error.rs b/utils/src/util_error.rs index fda0c1a565..d19c230f11 100644 --- a/utils/src/util_error.rs +++ b/utils/src/util_error.rs @@ -1,4 +1,3 @@ - use snafu::{Backtrace, Snafu}; #[derive(Debug, Snafu)] @@ -33,4 +32,4 @@ pub enum UtilError { }, } -pub type UtilResult = std::result::Result; \ No newline at end of file +pub type UtilResult = std::result::Result; From 95a30210ce7675c191f07cb174ec55a298986be8 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 19:17:12 +0200 Subject: [PATCH 35/96] clippy fixes, fmt --- .../canonicalize.rs | 6 +++--- .../{canonicalize => canonicalization}/mod.rs | 0 .../module.rs | 8 ++++---- ast/src/constrain.rs | 4 ++-- ast/src/lang/core/ast.rs | 2 +- ast/src/lang/core/declaration.rs | 2 +- ast/src/lang/core/def/def.rs | 2 +- ast/src/lang/core/def/def2.rs | 2 +- ast/src/lang/core/expr/expr2.rs | 2 +- ast/src/lang/core/expr/expr2_to_string.rs | 2 +- ast/src/lang/core/expr/expr_to_expr2.rs | 16 ++++++++-------- ast/src/lang/core/expr/output.rs | 2 +- ast/src/lang/core/expr/record_field.rs | 2 +- ast/src/lang/core/fun_def.rs | 2 +- ast/src/lang/core/pattern.rs | 8 ++++---- ast/src/lang/core/str.rs | 4 ++-- ast/src/lang/core/types.rs | 8 ++++---- ast/src/lang/core/val_def.rs | 2 +- ast/src/lang/env.rs | 4 ++-- ast/src/lang/rigids.rs | 10 ++++------ ast/src/lang/scope.rs | 8 ++++---- ast/src/lib.rs | 4 ++-- ast/src/{pool => mem_pool}/mod.rs | 0 ast/src/{pool => mem_pool}/pool.rs | 0 ast/src/{pool => mem_pool}/pool_str.rs | 0 ast/src/{pool => mem_pool}/pool_vec.rs | 0 ast/src/{pool => mem_pool}/shallow_clone.rs | 0 ast/src/solve_type.rs | 6 +++--- code_markup/src/markup/attribute.rs | 10 ++++++---- code_markup/src/markup/common_nodes.rs | 18 +++++++++--------- code_markup/src/markup/nodes.rs | 10 +++++----- code_markup/src/markup/top_level_def.rs | 2 +- code_markup/src/slow_pool.rs | 10 ++++++---- editor/src/editor/main.rs | 2 +- editor/src/editor/mvc/ed_model.rs | 6 +++--- editor/src/editor/mvc/ed_update.rs | 6 +++--- editor/src/editor/mvc/ed_view.rs | 2 +- editor/src/editor/mvc/int_update.rs | 4 ++-- editor/src/editor/mvc/let_update.rs | 2 +- editor/src/editor/mvc/list_update.rs | 2 +- editor/src/editor/mvc/lookup_update.rs | 2 +- editor/src/editor/mvc/record_update.rs | 8 ++++---- editor/src/editor/mvc/string_update.rs | 4 ++-- editor/src/editor/mvc/tld_value_update.rs | 4 ++-- 44 files changed, 100 insertions(+), 98 deletions(-) rename ast/src/{canonicalize => canonicalization}/canonicalize.rs (97%) rename ast/src/{canonicalize => canonicalization}/mod.rs (100%) rename ast/src/{canonicalize => canonicalization}/module.rs (98%) rename ast/src/{pool => mem_pool}/mod.rs (100%) rename ast/src/{pool => mem_pool}/pool.rs (100%) rename ast/src/{pool => mem_pool}/pool_str.rs (100%) rename ast/src/{pool => mem_pool}/pool_vec.rs (100%) rename ast/src/{pool => mem_pool}/shallow_clone.rs (100%) diff --git a/ast/src/canonicalize/canonicalize.rs b/ast/src/canonicalization/canonicalize.rs similarity index 97% rename from ast/src/canonicalize/canonicalize.rs rename to ast/src/canonicalization/canonicalize.rs index b3adc2ab1d..211e22cf97 100644 --- a/ast/src/canonicalize/canonicalize.rs +++ b/ast/src/canonicalization/canonicalize.rs @@ -18,7 +18,7 @@ use crate::{ env::Env, scope::Scope, }, - pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; pub(crate) enum CanonicalizeRecordProblem { @@ -275,7 +275,7 @@ pub(crate) fn canonicalize_lookup( Var(symbol) } Err(problem) => { - env.problem(Problem::RuntimeError(problem.clone())); + env.problem(Problem::RuntimeError(problem)); RuntimeError() } @@ -292,7 +292,7 @@ pub(crate) fn canonicalize_lookup( Err(problem) => { // Either the module wasn't imported, or // it was imported but it doesn't expose this ident. - env.problem(Problem::RuntimeError(problem.clone())); + env.problem(Problem::RuntimeError(problem)); RuntimeError() } diff --git a/ast/src/canonicalize/mod.rs b/ast/src/canonicalization/mod.rs similarity index 100% rename from ast/src/canonicalize/mod.rs rename to ast/src/canonicalization/mod.rs diff --git a/ast/src/canonicalize/module.rs b/ast/src/canonicalization/module.rs similarity index 98% rename from ast/src/canonicalize/module.rs rename to ast/src/canonicalization/module.rs index ace177df8b..c8286f8c2d 100644 --- a/ast/src/canonicalize/module.rs +++ b/ast/src/canonicalization/module.rs @@ -24,10 +24,10 @@ use crate::lang::core::types::Alias; use crate::lang::core::val_def::ValueDef; use crate::lang::env::Env; use crate::lang::scope::Scope; -use crate::pool::pool::NodeId; -use crate::pool::pool::Pool; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::NodeId; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; pub struct ModuleOutput { pub aliases: MutMap>, diff --git a/ast/src/constrain.rs b/ast/src/constrain.rs index ca6b1a9a8d..1454f1d936 100644 --- a/ast/src/constrain.rs +++ b/ast/src/constrain.rs @@ -26,7 +26,7 @@ use crate::{ }, env::Env, }, - pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; #[derive(Debug)] @@ -1781,7 +1781,7 @@ pub mod test_constrain { env::Env, scope::Scope, }, - pool::pool::Pool, + mem_pool::pool::Pool, solve_type, }; use indoc::indoc; diff --git a/ast/src/lang/core/ast.rs b/ast/src/lang/core/ast.rs index d337d91e6f..586b29f19c 100644 --- a/ast/src/lang/core/ast.rs +++ b/ast/src/lang/core/ast.rs @@ -1,6 +1,6 @@ use crate::{ ast_error::{ASTNodeIdWithoutExprId, ASTResult}, - pool::pool::Pool, + mem_pool::pool::Pool, }; use super::{ diff --git a/ast/src/lang/core/declaration.rs b/ast/src/lang/core/declaration.rs index 0c9ca03bfe..e45668b484 100644 --- a/ast/src/lang/core/declaration.rs +++ b/ast/src/lang/core/declaration.rs @@ -2,7 +2,7 @@ use roc_types::subs::VarStore; use crate::{ lang::core::{def::def::Def, expr::expr2::Expr2}, - pool::{pool::Pool, pool_vec::PoolVec}, + mem_pool::{pool::Pool, pool_vec::PoolVec}, }; use super::def::def::Declaration; diff --git a/ast/src/lang/core/def/def.rs b/ast/src/lang/core/def/def.rs index 3bcd60f670..c8654b3d73 100644 --- a/ast/src/lang/core/def/def.rs +++ b/ast/src/lang/core/def/def.rs @@ -37,7 +37,7 @@ use crate::{ rigids::Rigids, scope::Scope, }, - pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; #[derive(Debug)] diff --git a/ast/src/lang/core/def/def2.rs b/ast/src/lang/core/def/def2.rs index 85cd2b9a64..023a39760d 100644 --- a/ast/src/lang/core/def/def2.rs +++ b/ast/src/lang/core/def/def2.rs @@ -3,7 +3,7 @@ use crate::{ expr::{expr2::Expr2, expr2_to_string::expr2_to_string}, pattern::Pattern2, }, - pool::pool::{NodeId, Pool}, + mem_pool::pool::{NodeId, Pool}, }; // A top level definition, not inside a function. For example: `main = "Hello, world!"` diff --git a/ast/src/lang/core/expr/expr2.rs b/ast/src/lang/core/expr/expr2.rs index 8b2ad1b84b..e06d169ece 100644 --- a/ast/src/lang/core/expr/expr2.rs +++ b/ast/src/lang/core/expr/expr2.rs @@ -3,7 +3,7 @@ use roc_types::subs::Variable; use crate::{ lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef}, - pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, + mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec}, }; use roc_can::expr::Recursive; use roc_module::low_level::LowLevel; diff --git a/ast/src/lang/core/expr/expr2_to_string.rs b/ast/src/lang/core/expr/expr2_to_string.rs index 6f8255ccf6..cbd2967766 100644 --- a/ast/src/lang/core/expr/expr2_to_string.rs +++ b/ast/src/lang/core/expr/expr2_to_string.rs @@ -1,6 +1,6 @@ use crate::{ lang::core::{expr::record_field::RecordField, val_def::value_def_to_string}, - pool::pool::Pool, + mem_pool::pool::Pool, }; use super::expr2::{Expr2, ExprId}; diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 861ee2d49d..febad6b13a 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -9,7 +9,7 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use super::{expr2::Expr2, output::Output}; -use crate::canonicalize::canonicalize::{ +use crate::canonicalization::canonicalize::{ canonicalize_fields, canonicalize_lookup, canonicalize_when_branch, CanonicalizeRecordProblem, }; use crate::lang::core::declaration::decl_to_let; @@ -17,14 +17,14 @@ use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs}; use crate::lang::core::expr::expr2::ClosureExtra; use crate::lang::core::pattern::to_pattern2; use crate::lang::core::str::flatten_str_literal; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::shallow_clone::ShallowClone; use crate::{ lang::{ core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal}, env::Env, scope::Scope, }, - pool::{pool_str::PoolStr, pool_vec::PoolVec}, + mem_pool::{pool_str::PoolStr, pool_vec::PoolVec}, }; pub fn loc_expr_to_expr2<'a>( @@ -65,7 +65,7 @@ pub fn to_expr2<'a>( // emit runtime error let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into()); - env.problem(Problem::RuntimeError(runtime_error.clone())); + env.problem(Problem::RuntimeError(runtime_error)); // // Expr::RuntimeError(runtime_error) todo!() @@ -94,7 +94,7 @@ pub fn to_expr2<'a>( raw.into(), ); - env.problem(Problem::RuntimeError(runtime_error.clone())); + env.problem(Problem::RuntimeError(runtime_error)); // // Expr::RuntimeError(runtime_error) todo!() @@ -122,7 +122,7 @@ pub fn to_expr2<'a>( // emit runtime error let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into()); - env.problem(Problem::RuntimeError(runtime_error.clone())); + env.problem(Problem::RuntimeError(runtime_error)); // // Expr::RuntimeError(runtime_error) todo!() @@ -130,7 +130,7 @@ pub fn to_expr2<'a>( } } - Str(literal) => flatten_str_literal(env, scope, &literal), + Str(literal) => flatten_str_literal(env, scope, literal), List { items, .. } => { let mut output = Output::default(); @@ -585,7 +585,7 @@ pub fn to_expr2<'a>( let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs( env, Output::default(), - &scope, + scope, loc_defs, PatternType::DefExpr, ); diff --git a/ast/src/lang/core/expr/output.rs b/ast/src/lang/core/expr/output.rs index af7f455465..4287e3f72f 100644 --- a/ast/src/lang/core/expr/output.rs +++ b/ast/src/lang/core/expr/output.rs @@ -1,6 +1,6 @@ use crate::{ lang::core::{def::def::References, types::Alias}, - pool::pool::NodeId, + mem_pool::pool::NodeId, }; use roc_collections::all::{MutMap, MutSet}; use roc_module::symbol::Symbol; diff --git a/ast/src/lang/core/expr/record_field.rs b/ast/src/lang/core/expr/record_field.rs index 90c1fb6ed7..aaf464799f 100644 --- a/ast/src/lang/core/expr/record_field.rs +++ b/ast/src/lang/core/expr/record_field.rs @@ -1,6 +1,6 @@ use roc_types::subs::Variable; -use crate::pool::pool_str::PoolStr; +use crate::mem_pool::pool_str::PoolStr; use roc_module::symbol::Symbol; use super::expr2::ExprId; diff --git a/ast/src/lang/core/fun_def.rs b/ast/src/lang/core/fun_def.rs index 773e6ecd66..588d07d996 100644 --- a/ast/src/lang/core/fun_def.rs +++ b/ast/src/lang/core/fun_def.rs @@ -1,6 +1,6 @@ use crate::{ lang::rigids::Rigids, - pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}, + mem_pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone}, }; use roc_module::symbol::Symbol; use roc_types::subs::Variable; diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index 230812285e..19a956bcd8 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -18,10 +18,10 @@ use crate::constrain::Constraint; use crate::lang::core::expr::expr_to_expr2::to_expr_id; use crate::lang::env::Env; use crate::lang::scope::Scope; -use crate::pool::pool::{NodeId, Pool}; -use crate::pool::pool_str::PoolStr; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; use super::expr::expr2::{ExprId, FloatVal, IntVal}; use super::expr::output::Output; diff --git a/ast/src/lang/core/str.rs b/ast/src/lang/core/str.rs index 69d2cca7d2..53b0a999cf 100644 --- a/ast/src/lang/core/str.rs +++ b/ast/src/lang/core/str.rs @@ -4,7 +4,7 @@ use roc_parse::ast::StrLiteral; use crate::{ ast_error::{ASTResult, UnexpectedASTNode}, lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope}, - pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}, + mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec}, }; use super::expr::{ @@ -123,7 +123,7 @@ fn flatten_str_lines<'a>( /// Resolve string interpolations by desugaring a sequence of StrSegments /// into nested calls to Str.concat -fn desugar_str_segments<'a>(env: &mut Env<'a>, segments: Vec) -> Expr2 { +fn desugar_str_segments(env: &mut Env, segments: Vec) -> Expr2 { use StrSegment::*; let pool = &mut env.pool; diff --git a/ast/src/lang/core/types.rs b/ast/src/lang/core/types.rs index 8279cd04e5..107a6e7946 100644 --- a/ast/src/lang/core/types.rs +++ b/ast/src/lang/core/types.rs @@ -11,10 +11,10 @@ use roc_types::{subs::Variable, types::ErrorType}; use crate::lang::env::Env; use crate::lang::scope::Scope; -use crate::pool::pool::{NodeId, Pool}; -use crate::pool::pool_str::PoolStr; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; pub type TypeId = NodeId; diff --git a/ast/src/lang/core/val_def.rs b/ast/src/lang/core/val_def.rs index 2b97221c77..b30bfb3ace 100644 --- a/ast/src/lang/core/val_def.rs +++ b/ast/src/lang/core/val_def.rs @@ -1,6 +1,6 @@ use crate::{ lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids}, - pool::{ + mem_pool::{ pool::{NodeId, Pool}, shallow_clone::ShallowClone, }, diff --git a/ast/src/lang/env.rs b/ast/src/lang/env.rs index c3ee64edc3..87a048151e 100644 --- a/ast/src/lang/env.rs +++ b/ast/src/lang/env.rs @@ -6,7 +6,7 @@ use roc_problem::can::{Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::VarStore; -use crate::pool::pool::{NodeId, Pool}; +use crate::mem_pool::pool::{NodeId, Pool}; use super::core::def::def::References; @@ -147,7 +147,7 @@ impl<'a> Env<'a> { Ok(symbol) } None => Err(RuntimeError::ValueNotExposed { - module_name: ModuleName::from(module_name), + module_name, ident, region, }), diff --git a/ast/src/lang/rigids.rs b/ast/src/lang/rigids.rs index be43173714..a10dce0016 100644 --- a/ast/src/lang/rigids.rs +++ b/ast/src/lang/rigids.rs @@ -3,7 +3,9 @@ use std::{ hash::BuildHasherDefault, }; -use crate::pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone}; +use crate::mem_pool::{ + pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone, +}; use roc_collections::all::WyHash; use roc_types::subs::Variable; @@ -45,11 +47,7 @@ impl Rigids { .names .iter(pool) .filter_map(|(opt_pool_str, var)| { - if let Some(pool_str) = opt_pool_str { - Some((*pool_str, *var)) - } else { - None - } + opt_pool_str.as_ref().map(|pool_str| (*pool_str, *var)) }) .collect::>(); diff --git a/ast/src/lang/scope.rs b/ast/src/lang/scope.rs index fa8cc7e9d2..9f3a11c60c 100644 --- a/ast/src/lang/scope.rs +++ b/ast/src/lang/scope.rs @@ -2,10 +2,10 @@ #![allow(dead_code)] #![allow(unused_imports)] -use crate::pool::pool::Pool; -use crate::pool::pool_str::PoolStr; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_str::PoolStr; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; diff --git a/ast/src/lib.rs b/ast/src/lib.rs index e7f03db59d..b3d987f99f 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -1,7 +1,7 @@ pub mod ast_error; -mod canonicalize; +mod canonicalization; pub mod constrain; pub mod lang; +pub mod mem_pool; pub mod parse; -pub mod pool; pub mod solve_type; diff --git a/ast/src/pool/mod.rs b/ast/src/mem_pool/mod.rs similarity index 100% rename from ast/src/pool/mod.rs rename to ast/src/mem_pool/mod.rs diff --git a/ast/src/pool/pool.rs b/ast/src/mem_pool/pool.rs similarity index 100% rename from ast/src/pool/pool.rs rename to ast/src/mem_pool/pool.rs diff --git a/ast/src/pool/pool_str.rs b/ast/src/mem_pool/pool_str.rs similarity index 100% rename from ast/src/pool/pool_str.rs rename to ast/src/mem_pool/pool_str.rs diff --git a/ast/src/pool/pool_vec.rs b/ast/src/mem_pool/pool_vec.rs similarity index 100% rename from ast/src/pool/pool_vec.rs rename to ast/src/mem_pool/pool_vec.rs diff --git a/ast/src/pool/shallow_clone.rs b/ast/src/mem_pool/shallow_clone.rs similarity index 100% rename from ast/src/pool/shallow_clone.rs rename to ast/src/mem_pool/shallow_clone.rs diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 986f867856..2b39c606b3 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -19,9 +19,9 @@ use roc_unify::unify::Unified::*; use crate::constrain::Constraint; use crate::lang::core::types::Type2; -use crate::pool::pool::Pool; -use crate::pool::pool_vec::PoolVec; -use crate::pool::shallow_clone::ShallowClone; +use crate::mem_pool::pool::Pool; +use crate::mem_pool::pool_vec::PoolVec; +use crate::mem_pool::shallow_clone::ShallowClone; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler diff --git a/code_markup/src/markup/attribute.rs b/code_markup/src/markup/attribute.rs index 0adb489659..7df80cbc70 100644 --- a/code_markup/src/markup/attribute.rs +++ b/code_markup/src/markup/attribute.rs @@ -65,10 +65,6 @@ pub struct Attributes { } impl Attributes { - pub fn new() -> Attributes { - Attributes { all: Vec::new() } - } - pub fn add(&mut self, attr: Attribute) { self.all.push(attr); } @@ -121,3 +117,9 @@ impl Attributes { Ok(()) } } + +impl Default for Attributes { + fn default() -> Self { + Attributes { all: Vec::new() } + } +} diff --git a/code_markup/src/markup/common_nodes.rs b/code_markup/src/markup/common_nodes.rs index 6cdc31eda9..89a8a8c2d9 100644 --- a/code_markup/src/markup/common_nodes.rs +++ b/code_markup/src/markup/common_nodes.rs @@ -9,7 +9,7 @@ pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) content: nodes::EQUALS.to_owned(), ast_node_id, syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -20,7 +20,7 @@ pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option) -> Marku content: nodes::COMMA.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -30,7 +30,7 @@ pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option) - MarkupNode::Blank { ast_node_id, syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -44,7 +44,7 @@ pub fn new_blank_mn_w_nls( MarkupNode::Blank { ast_node_id, syn_high_style: HighlightStyle::Blank, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: nr_of_newlines, } @@ -55,7 +55,7 @@ pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option) -> Marku content: nodes::COLON.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -66,7 +66,7 @@ pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option) content: nodes::LEFT_ACCOLADE.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -77,7 +77,7 @@ pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option) content: nodes::RIGHT_ACCOLADE.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -88,7 +88,7 @@ pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option) -> content: nodes::LEFT_SQUARE_BR.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } @@ -99,7 +99,7 @@ pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option) - content: nodes::RIGHT_SQUARE_BR.to_owned(), ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::Bracket, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, } diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index 4fe4d5c954..ff8907be30 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -56,7 +56,7 @@ use roc_ast::{ }, env::Env, }, - pool::pool_str::PoolStr, + mem_pool::pool_str::PoolStr, }; use roc_module::symbol::Interns; use roc_utils::{index_of, slice_get}; @@ -323,7 +323,7 @@ fn new_markup_node( content: text, ast_node_id: node_id, syn_high_style: highlight_style, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -528,7 +528,7 @@ pub fn expr2_to_markup<'a, 'b>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -637,7 +637,7 @@ fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: HighlightStyle::PackageRelated, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; @@ -655,7 +655,7 @@ fn header_val_mn( content, ast_node_id: ASTNodeId::AExprId(expr_id), syn_high_style: highlight_style, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; diff --git a/code_markup/src/markup/top_level_def.rs b/code_markup/src/markup/top_level_def.rs index 5cadc131ac..d2122fc790 100644 --- a/code_markup/src/markup/top_level_def.rs +++ b/code_markup/src/markup/top_level_def.rs @@ -31,7 +31,7 @@ pub fn tld_mark_node<'a>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; diff --git a/code_markup/src/slow_pool.rs b/code_markup/src/slow_pool.rs index 1f8a12c4af..d69e1a1a9c 100644 --- a/code_markup/src/slow_pool.rs +++ b/code_markup/src/slow_pool.rs @@ -10,10 +10,6 @@ pub struct SlowPool { } impl SlowPool { - pub fn new() -> SlowPool { - SlowPool { nodes: Vec::new() } - } - pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { let id = self.nodes.len(); @@ -73,3 +69,9 @@ impl fmt::Display for SlowPool { Ok(()) } } + +impl Default for SlowPool { + fn default() -> Self { + SlowPool { nodes: Vec::new() } + } +} diff --git a/editor/src/editor/main.rs b/editor/src/editor/main.rs index a3cf2d3fe7..4b68ed3f8d 100644 --- a/editor/src/editor/main.rs +++ b/editor/src/editor/main.rs @@ -24,7 +24,7 @@ use cgmath::Vector2; use fs_extra::dir::{copy, ls, CopyOptions, DirEntryAttr, DirEntryValue}; use pipelines::RectResources; use roc_ast::lang::env::Env; -use roc_ast::pool::pool::Pool; +use roc_ast::mem_pool::pool::Pool; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; use roc_load; diff --git a/editor/src/editor/mvc/ed_model.rs b/editor/src/editor/mvc/ed_model.rs index 12eb4bb110..d255bdbeb5 100644 --- a/editor/src/editor/mvc/ed_model.rs +++ b/editor/src/editor/mvc/ed_model.rs @@ -13,8 +13,8 @@ use bumpalo::Bump; use nonempty::NonEmpty; use roc_ast::lang::core::ast::{ASTNodeId, AST}; use roc_ast::lang::env::Env; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_ast::parse::parse_ast; -use roc_ast::pool::pool_str::PoolStr; use roc_code_markup::markup::nodes::ast_to_mark_nodes; use roc_code_markup::slow_pool::{MarkNodeId, SlowPool}; use roc_load::file::LoadedModule; @@ -57,7 +57,7 @@ pub fn init_model<'a>( ) -> EdResult> { let mut module = EdModule::new(code_str, env, code_arena)?; - let mut mark_node_pool = SlowPool::new(); + let mut mark_node_pool = SlowPool::default(); let markup_ids = if code_str.is_empty() { EmptyCodeString {}.fail() @@ -211,7 +211,7 @@ pub mod test_ed_model { use bumpalo::Bump; use ed_model::EdModel; use roc_ast::lang::env::Env; - use roc_ast::pool::pool::Pool; + use roc_ast::mem_pool::pool::Pool; use roc_load::file::LoadedModule; use roc_module::symbol::IdentIds; use roc_module::symbol::ModuleIds; diff --git a/editor/src/editor/mvc/ed_update.rs b/editor/src/editor/mvc/ed_update.rs index ce0c894955..cc093c6e2b 100644 --- a/editor/src/editor/mvc/ed_update.rs +++ b/editor/src/editor/mvc/ed_update.rs @@ -59,8 +59,8 @@ use roc_ast::lang::core::def::def2::DefId; use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::expr::expr2::ExprId; use roc_ast::lang::core::types::Type2; -use roc_ast::pool::pool::Pool; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool::Pool; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_ast::solve_type; use roc_can::expected::Expected; use roc_code_markup::markup::attribute::Attributes; @@ -589,7 +589,7 @@ impl<'a> EdModel<'a> { let blank_replacement = MarkupNode::Blank { ast_node_id: sel_block.ast_node_id, - attributes: Attributes::new(), + attributes: Attributes::default(), syn_high_style: HighlightStyle::Blank, parent_id_opt: expr2_level_mark_node.get_parent_id_opt(), newlines_at_end, diff --git a/editor/src/editor/mvc/ed_view.rs b/editor/src/editor/mvc/ed_view.rs index ab35ecbdbd..b9d4693b9e 100644 --- a/editor/src/editor/mvc/ed_view.rs +++ b/editor/src/editor/mvc/ed_view.rs @@ -14,7 +14,7 @@ use crate::ui::text::selection::Selection; use crate::ui::tooltip::ToolTip; use crate::ui::ui_error::MissingGlyphDims; use cgmath::Vector2; -use roc_ast::pool::pool::Pool; +use roc_ast::mem_pool::pool::Pool; use snafu::OptionExt; use winit::dpi::PhysicalSize; diff --git a/editor/src/editor/mvc/int_update.rs b/editor/src/editor/mvc/int_update.rs index c2b63212f8..75a8354b60 100644 --- a/editor/src/editor/mvc/int_update.rs +++ b/editor/src/editor/mvc/int_update.rs @@ -1,7 +1,7 @@ use roc_ast::lang::core::expr::expr2::Expr2::SmallInt; use roc_ast::lang::core::expr::expr2::IntStyle; use roc_ast::lang::core::expr::expr2::IntVal; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::nodes::MarkupNode; use roc_code_markup::slow_pool::MarkNodeId; @@ -49,7 +49,7 @@ pub fn start_new_int(ed_model: &mut EdModel, digit_char: &char) -> EdResult EdResult< content: val_name_string, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: Some(curr_mark_node_id), newlines_at_end: curr_mark_node_nls, }; diff --git a/editor/src/editor/mvc/list_update.rs b/editor/src/editor/mvc/list_update.rs index 7036701b9d..fef31b7627 100644 --- a/editor/src/editor/mvc/list_update.rs +++ b/editor/src/editor/mvc/list_update.rs @@ -1,6 +1,6 @@ use roc_ast::lang::core::ast::{ast_node_to_string, ASTNodeId}; use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; -use roc_ast::pool::pool_vec::PoolVec; +use roc_ast::mem_pool::pool_vec::PoolVec; use roc_code_markup::markup::common_nodes::{ new_blank_mn, new_comma_mn, new_left_square_mn, new_right_square_mn, }; diff --git a/editor/src/editor/mvc/lookup_update.rs b/editor/src/editor/mvc/lookup_update.rs index 53db22427a..5d5c9f5396 100644 --- a/editor/src/editor/mvc/lookup_update.rs +++ b/editor/src/editor/mvc/lookup_update.rs @@ -1,5 +1,5 @@ use roc_ast::lang::core::expr::expr2::{Expr2, ExprId}; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_code_markup::slow_pool::MarkNodeId; use crate::editor::ed_error::EdResult; diff --git a/editor/src/editor/mvc/record_update.rs b/editor/src/editor/mvc/record_update.rs index 94f824375c..d27bb0c5d0 100644 --- a/editor/src/editor/mvc/record_update.rs +++ b/editor/src/editor/mvc/record_update.rs @@ -11,8 +11,8 @@ use roc_ast::lang::core::ast::ASTNodeId; use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::expr::expr2::ExprId; use roc_ast::lang::core::expr::record_field::RecordField; -use roc_ast::pool::pool_str::PoolStr; -use roc_ast::pool::pool_vec::PoolVec; +use roc_ast::mem_pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_vec::PoolVec; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::common_nodes::new_blank_mn; use roc_code_markup::markup::common_nodes::new_left_accolade_mn; @@ -126,7 +126,7 @@ pub fn update_empty_record( content: new_input.to_owned(), ast_node_id, syn_high_style: HighlightStyle::RecordField, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: 0, }; @@ -235,7 +235,7 @@ pub fn update_record_colon( content: record_colon.to_owned(), ast_node_id: ASTNodeId::AExprId(record_ast_node_id), syn_high_style: HighlightStyle::Operator, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: Some(parent_id), newlines_at_end: 0, }; diff --git a/editor/src/editor/mvc/string_update.rs b/editor/src/editor/mvc/string_update.rs index fca9100495..8ee2ddd881 100644 --- a/editor/src/editor/mvc/string_update.rs +++ b/editor/src/editor/mvc/string_update.rs @@ -1,7 +1,7 @@ use roc_ast::lang::core::expr::expr2::ArrString; use roc_ast::lang::core::expr::expr2::Expr2; use roc_ast::lang::core::str::update_str_expr; -use roc_ast::pool::pool_str::PoolStr; +use roc_ast::mem_pool::pool_str::PoolStr; use roc_code_markup::markup::attribute::Attributes; use roc_code_markup::markup::nodes; use roc_code_markup::markup::nodes::MarkupNode; @@ -150,7 +150,7 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult { content: nodes::STRING_QUOTES.to_owned(), ast_node_id, syn_high_style: HighlightStyle::String, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt, newlines_at_end: curr_mark_node_nls, }; diff --git a/editor/src/editor/mvc/tld_value_update.rs b/editor/src/editor/mvc/tld_value_update.rs index e7a8b3258d..6ca2f8e0fd 100644 --- a/editor/src/editor/mvc/tld_value_update.rs +++ b/editor/src/editor/mvc/tld_value_update.rs @@ -8,7 +8,7 @@ use roc_ast::{ }, env::Env, }, - pool::pool::NodeId, + mem_pool::pool::NodeId, }; use roc_code_markup::{ markup::{ @@ -49,7 +49,7 @@ pub fn tld_mark_node<'a>( content: val_name, ast_node_id, syn_high_style: HighlightStyle::Variable, - attributes: Attributes::new(), + attributes: Attributes::default(), parent_id_opt: None, newlines_at_end: 0, }; From 358664a51a65a61c80837a0f8c622cdbe7df38e7 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sat, 25 Sep 2021 19:27:48 +0200 Subject: [PATCH 36/96] minor cleanup --- ast/src/mod.rs | 14 -------------- ast/src/parse/mod.rs | 1 - ast/src/parse/parse_expr.rs | 1 - code_markup/src/markup/nodes.rs | 28 +--------------------------- 4 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 ast/src/mod.rs delete mode 100644 ast/src/parse/parse_expr.rs diff --git a/ast/src/mod.rs b/ast/src/mod.rs deleted file mode 100644 index f9d51e4b1f..0000000000 --- a/ast/src/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod ast; -pub mod constrain; -pub mod lang; -mod module; -pub mod parse; -mod pattern; -pub mod pool; -pub mod roc_file; -mod scope; -pub mod solve; -mod types; -mod rigids; -mod canonicalize; -pub mod ast_error; \ No newline at end of file diff --git a/ast/src/parse/mod.rs b/ast/src/parse/mod.rs index f14f61f5a0..31bf78fd5e 100644 --- a/ast/src/parse/mod.rs +++ b/ast/src/parse/mod.rs @@ -1,3 +1,2 @@ pub mod parse_ast; -pub mod parse_expr; pub mod parse_header; diff --git a/ast/src/parse/parse_expr.rs b/ast/src/parse/parse_expr.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/ast/src/parse/parse_expr.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/code_markup/src/markup/nodes.rs b/code_markup/src/markup/nodes.rs index ff8907be30..cea08f288b 100644 --- a/code_markup/src/markup/nodes.rs +++ b/code_markup/src/markup/nodes.rs @@ -11,33 +11,7 @@ use crate::{ use super::{ attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node, }; -/*use crate::editor::ed_error::EdResult; -use crate::editor::ed_error::ExpectedTextNode; -use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; -use crate::editor::markup::common_nodes::new_blank_mn; -use crate::editor::markup::common_nodes::new_blank_mn_w_nls; -use crate::editor::markup::common_nodes::new_colon_mn; -use crate::editor::markup::common_nodes::new_comma_mn; -use crate::editor::markup::common_nodes::new_equals_mn; -use crate::editor::markup::common_nodes::new_left_accolade_mn; -use crate::editor::markup::common_nodes::new_left_square_mn; -use crate::editor::markup::common_nodes::new_right_accolade_mn; -use crate::editor::markup::common_nodes::new_right_square_mn; -use crate::editor::mvc::tld_value_update::tld_mark_node; -use crate::editor::slow_pool::MarkNodeId; -use crate::editor::slow_pool::SlowPool; -use crate::editor::syntax_highlight::HighlightStyle; -use crate::editor::util::index_of; -use crate::lang::ast::Def2; -use crate::lang::ast::DefId; -use crate::lang::ast::ExprId; -use crate::lang::ast::RecordField; -use crate::lang::ast::ValueDef; -use crate::lang::parse::ASTNodeId; -use crate::lang::parse::{AppHeader, AST}; -use crate::lang::pattern::get_identifier_string; -use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr}; -use crate::ui::util::slice_get;*/ + use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired}; use bumpalo::Bump; use roc_ast::{ From e6c137889a552f6f2c0ff408cbe7e63331b9e921 Mon Sep 17 00:00:00 2001 From: Brendan Hansknecht Date: Sat, 25 Sep 2021 12:19:56 -0700 Subject: [PATCH 37/96] Just disable gc-sections with surgical linker for rust hosts for now --- compiler/build/src/link.rs | 1 + examples/cli/platform/src/lib.rs | 2 +- examples/cli/platform/src/main.rs | 63 +----------------------- examples/hello-rust/platform/src/lib.rs | 3 +- examples/hello-rust/platform/src/main.rs | 50 +------------------ linker/README.md | 4 +- 6 files changed, 8 insertions(+), 115 deletions(-) diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 4764493911..32c8a4c9f6 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -385,6 +385,7 @@ pub fn rebuild_host( command.arg("--release"); } let source_file = if shared_lib_path.is_some() { + command.env("RUSTFLAGS", "-C link-dead-code"); command.args(&["--bin", "host"]); "src/main.rs" } else { diff --git a/examples/cli/platform/src/lib.rs b/examples/cli/platform/src/lib.rs index d313d6f424..d316e264d8 100644 --- a/examples/cli/platform/src/lib.rs +++ b/examples/cli/platform/src/lib.rs @@ -70,7 +70,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut } #[no_mangle] -pub extern "C" fn rust_main() -> isize { +pub extern "C" fn rust_main() -> i32 { let size = unsafe { roc_main_size() } as usize; let layout = Layout::array::(size).unwrap(); diff --git a/examples/cli/platform/src/main.rs b/examples/cli/platform/src/main.rs index 0d2ec6edb1..51175f934b 100644 --- a/examples/cli/platform/src/main.rs +++ b/examples/cli/platform/src/main.rs @@ -1,64 +1,3 @@ -#![allow(non_snake_case)] - -use core::ffi::c_void; -use roc_std::RocStr; - fn main() { - let mut result = host::rust_main(); - // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. - if result == 0x1234_5678_9ABC_DEF0 { - let roc_alloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, - ) - }; - let roc_realloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_realloc - as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, - ) - }; - let roc_dealloc_ptr: isize = unsafe { - std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_panic_ptr: isize = unsafe { - std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_memcpy_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memcpy - as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, - ) - }; - let roc_memset_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memset - as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, - ) - }; - let roc_fx_putLine_ptr: isize = unsafe { - std::mem::transmute(host::roc_fx_putLine as *const extern "C" fn(line: RocStr) -> ()) - }; - let roc_fx_getLine_ptr: isize = unsafe { - std::mem::transmute(host::roc_fx_getLine as *const extern "C" fn() -> RocStr) - }; - // I really want to use the equivalent of std::hint::black_box, but it is expirimental. - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - result = result ^ roc_fx_putLine_ptr; - result = result ^ roc_fx_getLine_ptr; - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - result = result ^ roc_fx_putLine_ptr; - result = result ^ roc_fx_getLine_ptr; - } - std::process::exit(result as i32); + std::process::exit(host::rust_main()); } diff --git a/examples/hello-rust/platform/src/lib.rs b/examples/hello-rust/platform/src/lib.rs index d81192b939..341556bb4b 100644 --- a/examples/hello-rust/platform/src/lib.rs +++ b/examples/hello-rust/platform/src/lib.rs @@ -1,7 +1,6 @@ #![allow(non_snake_case)] use core::ffi::c_void; -use core::mem::MaybeUninit; use libc::c_char; use roc_std::RocStr; use std::ffi::CStr; @@ -55,7 +54,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut } #[no_mangle] -pub extern "C" fn rust_main() -> isize { +pub extern "C" fn rust_main() -> i32 { unsafe { let roc_str = roc_main(); diff --git a/examples/hello-rust/platform/src/main.rs b/examples/hello-rust/platform/src/main.rs index 49cefd692f..51175f934b 100644 --- a/examples/hello-rust/platform/src/main.rs +++ b/examples/hello-rust/platform/src/main.rs @@ -1,51 +1,3 @@ -use core::ffi::c_void; - fn main() { - let mut result = host::rust_main(); - // This is stupid code that does nothing to avoid rust optimizing functions that roc needs away. - if result == 0x1234_5678_9ABC_DEF0 { - let roc_alloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_alloc as *const unsafe extern "C" fn(usize, u32) -> *mut c_void, - ) - }; - let roc_realloc_ptr: isize = unsafe { - std::mem::transmute( - host::roc_realloc - as *const unsafe extern "C" fn(*mut c_void, usize, usize, u32) -> *mut c_void, - ) - }; - let roc_dealloc_ptr: isize = unsafe { - std::mem::transmute(host::roc_dealloc as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_panic_ptr: isize = unsafe { - std::mem::transmute(host::roc_panic as *const unsafe extern "C" fn(*mut c_void, u32)) - }; - let roc_memcpy_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memcpy - as *const unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> *mut c_void, - ) - }; - let roc_memset_ptr: isize = unsafe { - std::mem::transmute( - host::roc_memset - as *const unsafe extern "C" fn(*mut c_void, i32, usize) -> *mut c_void, - ) - }; - // I really want to use the equivalent of std::hint::black_box, but it is expirimental. - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - result = result ^ roc_alloc_ptr; - result = result ^ roc_realloc_ptr; - result = result ^ roc_dealloc_ptr; - result = result ^ roc_panic_ptr; - result = result ^ roc_memcpy_ptr; - result = result ^ roc_memset_ptr; - } - std::process::exit(result as i32); + std::process::exit(host::rust_main()); } diff --git a/linker/README.md b/linker/README.md index 150eef59d3..e5342c64ea 100644 --- a/linker/README.md +++ b/linker/README.md @@ -31,7 +31,6 @@ This linker is run in 2 phases: preprocessing and surigical linking. ## TODO (In a lightly prioritized order) -- Try to make rust hosts nicer. Currently making it expose functions is a huge pain. - Add Macho support - Honestly should be almost exactly the same code. This means we likely need to do a lot of refactoring to minimize the duplicate code. @@ -40,4 +39,7 @@ This linker is run in 2 phases: preprocessing and surigical linking. - As a prereq, we need roc building on Windows (I'm not sure it does currently). - Definitely a solid bit different than elf, but hopefully after refactoring for Macho, won't be that crazy to add. - Look at enabling completely in memory linking that could be used with `roc run` and/or `roc repl` +- Look more into roc hosts and keeping certain functions. Currently I just disabled linker garbage collection. + This works but adds 1.2MB (40%) to even a tiny app. It may be a size issue for large rust hosts. + Roc, for reference, adds 13MB (20%) when linked without garbage collection. - Add a feature to the compiler to make this linker optional. From 142841ebd8367b358d45939d9b91b580141fa965 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 26 Sep 2021 10:23:12 +0200 Subject: [PATCH 38/96] copy ast and code_markup folders inside earthly --- Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Earthfile b/Earthfile index f2a31d2fc7..85308c46b6 100644 --- a/Earthfile +++ b/Earthfile @@ -46,7 +46,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli compiler docs editor roc_std vendor examples linker Cargo.toml Cargo.lock ./ + COPY --dir cli compiler docs editor ast code_markup roc_std vendor examples linker Cargo.toml Cargo.lock ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt @@ -66,7 +66,7 @@ check-rustfmt: check-typos: RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically - COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ + COPY --dir .github ci cli compiler docs editor examples ast code_markup linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ RUN typos test-rust: From 01c731fd93198a84995e1b3b6b3b6feed044802b Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 26 Sep 2021 10:26:01 +0200 Subject: [PATCH 39/96] forgot about utils --- Earthfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Earthfile b/Earthfile index 85308c46b6..04c9db6b8d 100644 --- a/Earthfile +++ b/Earthfile @@ -46,7 +46,7 @@ install-zig-llvm-valgrind-clippy-rustfmt: copy-dirs: FROM +install-zig-llvm-valgrind-clippy-rustfmt - COPY --dir cli compiler docs editor ast code_markup roc_std vendor examples linker Cargo.toml Cargo.lock ./ + COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock ./ test-zig: FROM +install-zig-llvm-valgrind-clippy-rustfmt @@ -66,7 +66,7 @@ check-rustfmt: check-typos: RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically - COPY --dir .github ci cli compiler docs editor examples ast code_markup linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ + COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ RUN typos test-rust: From 267f88626f71abe39713546f4cc8dd7bd730f41a Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Sep 2021 13:05:45 +0200 Subject: [PATCH 40/96] add failing test --- compiler/test_gen/src/gen_primitives.rs | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index d183b95d0b..10920b6f68 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -2906,3 +2906,30 @@ fn do_pass_bool_byte_closure_layout() { RocStr ); } + +#[test] +fn call_that_needs_closure_parameter() { + // here both p2 is lifted to the top-level, which means that `list` must be + // passed to it from `manyAux`. + assert_evals_to!( + indoc!( + r#" + Step state a : [ Loop state, Done a ] + + manyAux : List a -> [ Pair (Step (List a) (List a))] + manyAux = \list -> + p2 = \_ -> Pair (Done list) + + p2 "foo" + + manyAuxTest = (manyAux [ ]) == Pair (Loop [97]) + + runTest = \t -> if t then "PASS" else "FAIL" + + runTest manyAuxTest + "# + ), + RocStr::from_slice(b"FAIL"), + RocStr + ); +} From ed658ca2aaac3d80fd233b27b868a65b1f2149ed Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 25 Sep 2021 23:34:02 +0200 Subject: [PATCH 41/96] ugly but working fix for passing the closure env to a function --- compiler/mono/src/ir.rs | 118 ++++++++++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 22 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index bc620bdf74..ab0881eb36 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -6437,8 +6437,6 @@ fn call_by_name<'a>( assign_to_symbols(env, procs, layout_cache, iter, result) } } else { - let argument_layouts = lambda_set.extend_argument_list(env.arena, arg_layouts); - call_by_name_help( env, procs, @@ -6446,7 +6444,7 @@ fn call_by_name<'a>( proc_name, loc_args, lambda_set, - argument_layouts, + arg_layouts, ret_layout, layout_cache, assigned, @@ -6489,15 +6487,11 @@ fn call_by_name_help<'a>( ret_layout: &'a Layout<'a>, layout_cache: &mut LayoutCache<'a>, assigned: Symbol, - hole: &'a Stmt<'a>, + mut hole: &'a Stmt<'a>, ) -> Stmt<'a> { let original_fn_var = fn_var; let arena = env.arena; - // debug_assert!(!procs.module_thunks.contains(&proc_name), "{:?}", proc_name); - - let top_level_layout = ProcLayout::new(env.arena, argument_layouts, *ret_layout); - // the arguments given to the function, stored in symbols let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena); field_symbols.extend( @@ -6506,7 +6500,13 @@ fn call_by_name_help<'a>( .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), ); - let field_symbols = field_symbols.into_bump_slice(); + // If required, add an extra argument to the layout that is the captured environment + // afterwards, we MUST make sure the number of arguments in the layout matches the + // number of arguments actually passed. + let top_level_layout = { + let argument_layouts = lambda_set.extend_argument_list(env.arena, argument_layouts); + ProcLayout::new(env.arena, argument_layouts, *ret_layout) + }; // the variables of the given arguments let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); @@ -6535,6 +6535,8 @@ fn call_by_name_help<'a>( proc_name, ); + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6573,6 +6575,9 @@ fn call_by_name_help<'a>( "see call_by_name for background (scroll down a bit), function is {:?}", proc_name, ); + + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6625,6 +6630,8 @@ fn call_by_name_help<'a>( proc_name, ); + let field_symbols = field_symbols.into_bump_slice(); + let call = self::Call { call_type: CallType::ByName { name: proc_name, @@ -6643,6 +6650,19 @@ fn call_by_name_help<'a>( None => { let opt_partial_proc = procs.partial_procs.get(&proc_name); + /* + debug_assert_eq!( + argument_layouts.len(), + field_symbols.len(), + "Function {:?} is called with {} arguments, but the layout expects {}", + proc_name, + field_symbols.len(), + argument_layouts.len(), + ); + */ + + let field_symbols = field_symbols.into_bump_slice(); + match opt_partial_proc { Some(partial_proc) => { // TODO should pending_procs hold a Rc to avoid this .clone()? @@ -6655,20 +6675,74 @@ fn call_by_name_help<'a>( .specialized .insert((proc_name, top_level_layout), InProgress); + let captured = partial_proc.captured_symbols.clone(); + match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) { - Ok((proc, layout)) => call_specialized_proc( - env, - procs, - proc_name, - proc, - layout, - field_symbols, - loc_args, - layout_cache, - assigned, - hole, - ), + Ok((proc, layout)) => { + // ugh + + if lambda_set.is_represented().is_some() { + let closure_data_symbol = env.unique_symbol(); + + let function_layout = ProcLayout::from_raw(env.arena, layout); + + procs.specialized.remove(&(proc_name, function_layout)); + + procs + .specialized + .insert((proc_name, function_layout), Done(proc)); + + let symbols = match captured { + CapturedSymbols::Captured(captured_symbols) => { + Vec::from_iter_in( + captured_symbols.iter().map(|x| x.0), + env.arena, + ) + .into_bump_slice() + } + CapturedSymbols::None => unreachable!(), + }; + + dbg!(field_symbols, argument_layouts); + let new_hole = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + field_symbols, + argument_layouts, + *ret_layout, + assigned, + hole, + ); + + construct_closure_data( + env, + lambda_set, + proc_name, + symbols, + closure_data_symbol, + env.arena.alloc(new_hole), + ) + + // field_symbols.push(closure_data_symbol); + // debug_assert_eq!(argument_layouts.len(), field_symbols.len()); + // hole + } else { + call_specialized_proc( + env, + procs, + proc_name, + proc, + layout, + field_symbols, + loc_args, + layout_cache, + assigned, + hole, + ) + } + } Err(SpecializeFailure { attempted_layout, problem: _, @@ -6881,7 +6955,7 @@ fn call_specialized_proc<'a>( debug_assert_eq!( function_layout.arguments.len(), field_symbols.len(), - "function {:?} with layout {:?} expects {:?} arguments, but is applied to {:?}", + "function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}", proc_name, function_layout, function_layout.arguments.len(), From e9f2fb619341a72134e6e5db9aab0663c7a7bf5d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Sep 2021 12:45:52 +0200 Subject: [PATCH 42/96] actually define the arguments --- compiler/mono/src/ir.rs | 177 +++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 85 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index ab0881eb36..1a5283f08a 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -94,6 +94,12 @@ impl<'a> CapturedSymbols<'a> { } } +impl<'a> Default for CapturedSymbols<'a> { + fn default() -> Self { + CapturedSymbols::None + } +} + #[derive(Clone, Debug, PartialEq)] pub struct PendingSpecialization<'a> { solved_type: SolvedType, @@ -6208,6 +6214,8 @@ fn reuse_function_symbol<'a>( layout_cache, ); + // even though this function may not itself capture, + // unification may still cause it to have an extra argument construct_closure_data( env, lambda_set, @@ -6675,73 +6683,23 @@ fn call_by_name_help<'a>( .specialized .insert((proc_name, top_level_layout), InProgress); - let captured = partial_proc.captured_symbols.clone(); - match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) { Ok((proc, layout)) => { - // ugh - - if lambda_set.is_represented().is_some() { - let closure_data_symbol = env.unique_symbol(); - - let function_layout = ProcLayout::from_raw(env.arena, layout); - - procs.specialized.remove(&(proc_name, function_layout)); - - procs - .specialized - .insert((proc_name, function_layout), Done(proc)); - - let symbols = match captured { - CapturedSymbols::Captured(captured_symbols) => { - Vec::from_iter_in( - captured_symbols.iter().map(|x| x.0), - env.arena, - ) - .into_bump_slice() - } - CapturedSymbols::None => unreachable!(), - }; - - dbg!(field_symbols, argument_layouts); - let new_hole = match_on_lambda_set( - env, - lambda_set, - closure_data_symbol, - field_symbols, - argument_layouts, - *ret_layout, - assigned, - hole, - ); - - construct_closure_data( - env, - lambda_set, - proc_name, - symbols, - closure_data_symbol, - env.arena.alloc(new_hole), - ) - - // field_symbols.push(closure_data_symbol); - // debug_assert_eq!(argument_layouts.len(), field_symbols.len()); - // hole - } else { - call_specialized_proc( - env, - procs, - proc_name, - proc, - layout, - field_symbols, - loc_args, - layout_cache, - assigned, - hole, - ) - } + // now we just call our freshly-specialized function + call_specialized_proc( + env, + procs, + proc_name, + proc, + lambda_set, + layout, + field_symbols, + loc_args, + layout_cache, + assigned, + hole, + ) } Err(SpecializeFailure { attempted_layout, @@ -6758,6 +6716,7 @@ fn call_by_name_help<'a>( procs, proc_name, proc, + lambda_set, attempted_layout, field_symbols, loc_args, @@ -6907,6 +6866,7 @@ fn call_specialized_proc<'a>( procs: &mut Procs<'a>, proc_name: Symbol, proc: Proc<'a>, + lambda_set: LambdaSet<'a>, layout: RawFunctionLayout<'a>, field_symbols: &'a [Symbol], loc_args: std::vec::Vec<(Variable, Located)>, @@ -6945,6 +6905,8 @@ fn call_specialized_proc<'a>( arguments: field_symbols, }; + // the closure argument is already added here (to get the right specialization) + // but now we need to remove it because the `match_on_lambda_set` will add it again build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole) } RawFunctionLayout::ZeroArgumentThunk(_) => { @@ -6952,30 +6914,75 @@ fn call_specialized_proc<'a>( } } } else { - debug_assert_eq!( - function_layout.arguments.len(), - field_symbols.len(), - "function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}", - proc_name, - function_layout, - function_layout.arguments.len(), - field_symbols.len(), - ); - let call = self::Call { - call_type: CallType::ByName { - name: proc_name, - ret_layout: function_layout.result, - arg_layouts: function_layout.arguments, - specialization_id: env.next_call_specialization_id(), - }, - arguments: field_symbols, - }; - let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); - let result = build_call(env, call, assigned, function_layout.result, hole); + match procs + .partial_procs + .get(&proc_name) + .map(|pp| &pp.captured_symbols) + { + Some(&CapturedSymbols::Captured(captured_symbols)) => { + let symbols = Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena) + .into_bump_slice(); - assign_to_symbols(env, procs, layout_cache, iter, result) + let closure_data_symbol = env.unique_symbol(); + + // the closure argument is already added here (to get the right specialization) + // but now we need to remove it because the `match_on_lambda_set` will add it again + let mut argument_layouts = + Vec::from_iter_in(function_layout.arguments.iter().copied(), env.arena); + argument_layouts.pop().unwrap(); + + debug_assert_eq!(argument_layouts.len(), field_symbols.len(),); + + let new_hole = match_on_lambda_set( + env, + lambda_set, + closure_data_symbol, + field_symbols, + argument_layouts.into_bump_slice(), + function_layout.result, + assigned, + hole, + ); + + let result = construct_closure_data( + env, + lambda_set, + proc_name, + symbols, + closure_data_symbol, + env.arena.alloc(new_hole), + ); + + assign_to_symbols(env, procs, layout_cache, iter, result) + } + _ => { + debug_assert_eq!( + function_layout.arguments.len(), + field_symbols.len(), + "function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}", + proc_name, + function_layout, + function_layout.arguments.len(), + field_symbols.len(), + ); + + let call = self::Call { + call_type: CallType::ByName { + name: proc_name, + ret_layout: function_layout.result, + arg_layouts: function_layout.arguments, + specialization_id: env.next_call_specialization_id(), + }, + arguments: field_symbols, + }; + + let result = build_call(env, call, assigned, function_layout.result, hole); + + assign_to_symbols(env, procs, layout_cache, iter, result) + } + } } } From bd6e966833c0ee0e421c73a979313bdc0c62b150 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Sep 2021 13:28:03 +0200 Subject: [PATCH 43/96] clippy --- compiler/mono/src/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 1a5283f08a..c611ef99a2 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -6495,7 +6495,7 @@ fn call_by_name_help<'a>( ret_layout: &'a Layout<'a>, layout_cache: &mut LayoutCache<'a>, assigned: Symbol, - mut hole: &'a Stmt<'a>, + hole: &'a Stmt<'a>, ) -> Stmt<'a> { let original_fn_var = fn_var; let arena = env.arena; From 92b1d73bd6a29f29f7c2262a25342afafda6c9b4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 18 Sep 2021 15:17:20 +0100 Subject: [PATCH 44/96] Refactor build_proc into smaller helpers --- compiler/gen_wasm/src/backend.rs | 48 +++++++++++++++----------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 924c86b992..0bcb0f3e40 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -1,5 +1,5 @@ use parity_wasm::builder; -use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder}; +use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder}; use parity_wasm::elements::{ BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, }; @@ -92,22 +92,11 @@ impl<'a> WasmBackend<'a> { } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { - let ret_layout = WasmLayout::new(&proc.ret_layout); - - let ret_type = if let WasmLayout::StackMemory { .. } = ret_layout { - self.arg_types.push(PTR_TYPE); - None - } else { - Some(ret_layout.value_type()) - }; - - for (layout, symbol) in proc.args { - self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); - } + let signature_builder = self.build_signature(&proc); self.build_stmt(&proc.body, &proc.ret_layout)?; - let function_def = self.finalize(ret_type); + let function_def = self.finalize_proc(signature_builder); let location = self.builder.push_function(function_def); let function_index = location.body; self.proc_symbol_map.insert(sym, location); @@ -116,7 +105,24 @@ impl<'a> WasmBackend<'a> { Ok(function_index) } - fn finalize(&mut self, return_type: Option) -> FunctionDefinition { + fn build_signature(&mut self, proc: &Proc<'a>) -> SignatureBuilder { + let ret_layout = WasmLayout::new(&proc.ret_layout); + + let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { + self.arg_types.push(PTR_TYPE); + builder::signature() + } else { + builder::signature().with_result(ret_layout.value_type()) + }; + + for (layout, symbol) in proc.args { + self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter); + } + + signature_builder.with_params(self.arg_types.clone()) + } + + fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); if self.stack_memory > 0 { @@ -138,18 +144,8 @@ impl<'a> WasmBackend<'a> { } final_instructions.push(Instruction::End); - let signature_builder = if let Some(t) = return_type { - builder::signature().with_result(t) - } else { - builder::signature() - }; - - let signature = signature_builder - .with_params(self.arg_types.clone()) - .build_sig(); - let function_def = builder::function() - .with_signature(signature) + .with_signature(signature_builder.build_sig()) .body() .with_locals(self.locals.clone()) .with_instructions(Instructions::new(final_instructions)) From ab4f28fd69d5c5be5b5ae911ff53129d3df4a2ac Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 19:50:51 +0100 Subject: [PATCH 45/96] Comment out a test that isn't working --- compiler/gen_wasm/tests/wasm_records.rs | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 9c776ecfaa..7d4bc9af3f 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -851,20 +851,20 @@ mod wasm_records { // ); // } - #[test] - fn update_single_element_record() { - assert_evals_to!( - indoc!( - r#" - rec = { foo: 42} + // #[test] + // fn update_single_element_record() { + // assert_evals_to!( + // indoc!( + // r#" + // rec = { foo: 42} - { rec & foo: rec.foo + 1 } - "# - ), - 43, - i64 - ); - } + // { rec & foo: rec.foo + 1 } + // "# + // ), + // 43, + // i64 + // ); + // } // #[test] // fn booleans_in_record() { From 286ca680e0d97500b81974cdc1cb11ee85a6fc32 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 19:53:35 +0100 Subject: [PATCH 46/96] Tidy up load_literal --- compiler/gen_wasm/src/backend.rs | 62 +++++++++++++++----------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 0bcb0f3e40..35c68f4cc9 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -459,41 +459,35 @@ impl<'a> WasmBackend<'a> { } fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { - match lit { - Literal::Bool(x) => { - self.instructions.push(I32Const(*x as i32)); - Ok(()) + let instruction = match lit { + Literal::Bool(x) => I32Const(*x as i32), + Literal::Byte(x) => I32Const(*x as i32), + Literal::Int(x) => match layout { + Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), + Layout::Builtin( + Builtin::Int32 + | Builtin::Int16 + | Builtin::Int8 + | Builtin::Int1 + | Builtin::Usize, + ) => I32Const(*x as i32), + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); + } + }, + Literal::Float(x) => match layout { + Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), + Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); + } + }, + x => { + return Err(format!("loading literal, {:?}, is not yet implemented", x)); } - Literal::Byte(x) => { - self.instructions.push(I32Const(*x as i32)); - Ok(()) - } - Literal::Int(x) => { - let instruction = match layout { - Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), - Layout::Builtin( - Builtin::Int32 - | Builtin::Int16 - | Builtin::Int8 - | Builtin::Int1 - | Builtin::Usize, - ) => I32Const(*x as i32), - x => panic!("loading literal, {:?}, is not yet implemented", x), - }; - self.instructions.push(instruction); - Ok(()) - } - Literal::Float(x) => { - let instruction = match layout { - Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), - Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), - x => panic!("loading literal, {:?}, is not yet implemented", x), - }; - self.instructions.push(instruction); - Ok(()) - } - x => Err(format!("loading literal, {:?}, is not yet implemented", x)), - } + }; + self.instructions.push(instruction); + Ok(()) } fn build_call_low_level( From ae6cfe19927e66de031898b2968364f50b79e1e4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 19 Sep 2021 20:54:24 +0100 Subject: [PATCH 47/96] Moree implementations for Wasm32TestResult --- .../tests/helpers/wasm32_test_result.rs | 115 ++++++++++++++++-- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index a84bf48e89..7e73d5727e 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -46,11 +46,7 @@ macro_rules! build_wrapper_body_primitive { fn build_wrapper_body(main_function_index: u32) -> Vec { let size: i32 = 8; let mut instructions = Vec::with_capacity(16); - allocate_stack_frame( - &mut instructions, - size, - LocalId(STACK_POINTER_LOCAL_ID), - ); + allocate_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.extend([ // load result address to prepare for the store instruction later GetLocal(STACK_POINTER_LOCAL_ID), @@ -64,11 +60,7 @@ macro_rules! build_wrapper_body_primitive { // Return the result pointer GetLocal(STACK_POINTER_LOCAL_ID), ]); - free_stack_frame( - &mut instructions, - size, - LocalId(STACK_POINTER_LOCAL_ID), - ); + free_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.push(End); instructions } @@ -181,3 +173,106 @@ where ) } } + +impl Wasm32TestResult for (T, U, V, W) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH, + ) + } +} + +impl Wasm32TestResult for (T, U, V, W, X, Y, Z, A) +where + T: Wasm32TestResult + FromWasm32Memory, + U: Wasm32TestResult + FromWasm32Memory, + V: Wasm32TestResult + FromWasm32Memory, + W: Wasm32TestResult + FromWasm32Memory, + X: Wasm32TestResult + FromWasm32Memory, + Y: Wasm32TestResult + FromWasm32Memory, + Z: Wasm32TestResult + FromWasm32Memory, + A: Wasm32TestResult + FromWasm32Memory, +{ + fn build_wrapper_body(main_function_index: u32) -> Vec { + build_wrapper_body_stack_memory( + main_function_index, + T::ACTUAL_WIDTH + + U::ACTUAL_WIDTH + + V::ACTUAL_WIDTH + + W::ACTUAL_WIDTH + + X::ACTUAL_WIDTH + + Y::ACTUAL_WIDTH + + Z::ACTUAL_WIDTH + + A::ACTUAL_WIDTH, + ) + } +} From ecece45a837e3c74d748dd2ab6e3029aaf373dc1 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 26 Sep 2021 17:16:08 +0100 Subject: [PATCH 48/96] Add a helper method local_id_from_symbol --- compiler/gen_wasm/src/backend.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 35c68f4cc9..9577fd9703 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -224,6 +224,11 @@ impl<'a> WasmBackend<'a> { }) } + fn local_id_from_symbol(&self, sym: &Symbol) -> Result { + let SymbolStorage(local_id, _) = self.get_symbol_storage(sym)?; + Ok(*local_id) + } + fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> { let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?; let id: u32 = *local_id; @@ -329,15 +334,12 @@ impl<'a> WasmBackend<'a> { } // the LocalId of the symbol that we match on - let matched_on = match self.symbol_storage_map.get(cond_symbol) { - Some(SymbolStorage(local_id, _)) => local_id.0, - None => unreachable!("symbol not defined: {:?}", cond_symbol), - }; + let matched_on = self.local_id_from_symbol(cond_symbol)?; // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { // put the cond_symbol on the top of the stack - self.instructions.push(GetLocal(matched_on)); + self.instructions.push(GetLocal(matched_on.0)); self.instructions.push(I32Const(*value as i32)); @@ -404,12 +406,8 @@ impl<'a> WasmBackend<'a> { // put the arguments on the stack for (symbol, local_id) in arguments.iter().zip(locals.iter()) { - let argument = match self.symbol_storage_map.get(symbol) { - Some(SymbolStorage(local_id, _)) => local_id.0, - None => unreachable!("symbol not defined: {:?}", symbol), - }; - - self.instructions.push(GetLocal(argument)); + let argument = self.local_id_from_symbol(symbol)?; + self.instructions.push(GetLocal(argument.0)); self.instructions.push(SetLocal(local_id.0)); } From 39fda3e67585b73f79f28cd2737f821038f2b630 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 26 Sep 2021 19:16:54 +0100 Subject: [PATCH 49/96] Add more information to SymbolStorage SymbolStorage is function-specific, whereas WasmLayout is function-independent. SymbolStorage used to be a single-variant enum with a LocalId and a WasmLayout, but now we need more variants, so we can handle stack memory allocation.. - For values in stack memory, the storage needs to include an offset within the stack frame, but we don't need that for primitive values. - For function parameters, the allocated stack memory is in the caller's frame rather than the current frame, so we don't want size or offset. We could have kept a WasmLayout inside of SymbolStorage but that would have made possible a lot of invalid combinations. It seemed better to copy the fields we need. --- compiler/gen_wasm/src/backend.rs | 174 ++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 51 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 9577fd9703..392dd176ab 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -21,7 +21,52 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024; struct LabelId(u32); #[derive(Debug)] -struct SymbolStorage(LocalId, WasmLayout); +enum SymbolStorage { + ParamPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + ParamPointer { + local_id: LocalId, + }, + VarPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + VarStackMemory { + local_id: LocalId, + size: u32, + offset: u32, + }, + VarHeapMemory { + local_id: LocalId, + }, +} + +impl SymbolStorage { + fn local_id(&self) -> LocalId { + match self { + Self::ParamPrimitive { local_id, .. } => *local_id, + Self::ParamPointer { local_id, .. } => *local_id, + Self::VarPrimitive { local_id, .. } => *local_id, + Self::VarStackMemory { local_id, .. } => *local_id, + Self::VarHeapMemory { local_id, .. } => *local_id, + } + } + + #[allow(dead_code)] + fn value_type(&self) -> ValueType { + match self { + Self::ParamPrimitive { value_type, .. } => *value_type, + Self::VarPrimitive { value_type, .. } => *value_type, + Self::ParamPointer { .. } => ValueType::I32, + Self::VarStackMemory { .. } => ValueType::I32, + Self::VarHeapMemory { .. } => ValueType::I32, + } + } +} enum LocalKind { Parameter, @@ -164,39 +209,61 @@ impl<'a> WasmBackend<'a> { let local_index = (self.arg_types.len() + self.locals.len()) as u32; let local_id = LocalId(local_index); - match kind { + let storage = match kind { LocalKind::Parameter => { // Already stack-allocated by the caller if needed. self.arg_types.push(wasm_layout.value_type()); + match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive { + local_id, + value_type, + size, + }, + _ => SymbolStorage::ParamPointer { local_id }, + } } LocalKind::Variable => { self.locals.push(Local::new(1, wasm_layout.value_type())); - if let WasmLayout::StackMemory { - size, - alignment_bytes, - } = wasm_layout - { - let align = alignment_bytes as i32; - let mut offset = self.stack_memory; - offset += align - 1; - offset &= -align; - self.stack_memory = offset + (size - alignment_bytes) as i32; + match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive { + local_id, + value_type, + size, + }, - let frame_pointer = self.get_or_create_frame_pointer(); + WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { local_id }, - // initialise the local with the appropriate address - self.instructions.extend([ - GetLocal(frame_pointer.0), - I32Const(offset), - I32Add, - SetLocal(local_index), - ]); + WasmLayout::StackMemory { + size, + alignment_bytes, + } => { + let align = alignment_bytes as i32; + let mut offset = self.stack_memory; + offset += align - 1; + offset &= -align; + self.stack_memory = offset + (size - alignment_bytes) as i32; + + let frame_pointer = self.get_or_create_frame_pointer(); + + // initialise the local with the appropriate address + self.instructions.extend([ + GetLocal(frame_pointer.0), + I32Const(offset), + I32Add, + SetLocal(local_index), + ]); + + SymbolStorage::VarStackMemory { + local_id, + size, + offset: offset as u32, + } + } } } - } + }; - let storage = SymbolStorage(local_id, wasm_layout); self.symbol_storage_map.insert(symbol, storage); local_id @@ -225,14 +292,14 @@ impl<'a> WasmBackend<'a> { } fn local_id_from_symbol(&self, sym: &Symbol) -> Result { - let SymbolStorage(local_id, _) = self.get_symbol_storage(sym)?; - Ok(*local_id) + let storage = self.get_symbol_storage(sym)?; + Ok(storage.local_id()) } - fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> { - let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?; - let id: u32 = *local_id; - self.instructions.push(GetLocal(id)); + fn load_symbol(&mut self, sym: &Symbol) -> Result<(), String> { + let storage = self.get_symbol_storage(sym)?; + let index: u32 = storage.local_id().0; + self.instructions.push(GetLocal(index)); Ok(()) } @@ -265,7 +332,9 @@ impl<'a> WasmBackend<'a> { if let WasmLayout::StackMemory { .. } = wasm_layout { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later - let storage = SymbolStorage(LocalId(0), wasm_layout); + let storage = SymbolStorage::ParamPointer { + local_id: LocalId(0), + }; self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; @@ -287,30 +356,33 @@ impl<'a> WasmBackend<'a> { Stmt::Ret(sym) => { use crate::layout::WasmLayout::*; - let SymbolStorage(local_id, wasm_layout) = - self.symbol_storage_map.get(sym).unwrap(); + let storage = self.symbol_storage_map.get(sym).unwrap(); - match wasm_layout { - LocalOnly(_, _) | HeapMemory => { + match storage { + SymbolStorage::ParamPrimitive { local_id, .. } + | SymbolStorage::VarPrimitive { local_id, .. } + | SymbolStorage::ParamPointer { local_id, .. } + | SymbolStorage::VarHeapMemory { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); self.instructions.push(Return); } - StackMemory { - size, - alignment_bytes, - } => { - let from = local_id.clone(); - let to = LocalId(0); - let copy_size: u32 = *size; - let copy_alignment_bytes: u32 = *alignment_bytes; - copy_memory( - &mut self.instructions, - from, - to, - copy_size, - copy_alignment_bytes, - )?; + SymbolStorage::VarStackMemory { local_id, size, .. } => { + let ret_wasm_layout = WasmLayout::new(ret_layout); + if let StackMemory { alignment_bytes, .. } = ret_wasm_layout { + let from = local_id.clone(); + let to = LocalId(0); + let copy_size: u32 = *size; + copy_memory( + &mut self.instructions, + from, + to, + copy_size, + alignment_bytes, + )?; + } else { + panic!("Return layout doesn't match"); + } } } @@ -436,7 +508,7 @@ impl<'a> WasmBackend<'a> { }) => match call_type { CallType::ByName { name: func_sym, .. } => { for arg in *arguments { - self.load_from_symbol(arg)?; + self.load_symbol(arg)?; } let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", @@ -495,7 +567,7 @@ impl<'a> WasmBackend<'a> { return_layout: &Layout<'a>, ) -> Result<(), String> { for arg in args { - self.load_from_symbol(arg)?; + self.load_symbol(arg)?; } let wasm_layout = WasmLayout::new(return_layout); self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; @@ -513,7 +585,7 @@ impl<'a> WasmBackend<'a> { // For those, we'll need to pre-process each argument before the main op, // so simple arrays of instructions won't work. But there are common patterns. let instructions: &[Instruction] = match lowlevel { - // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_from_symbol? + // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_symbol? LowLevel::NumAdd => match return_value_type { ValueType::I32 => &[I32Add], ValueType::I64 => &[I64Add], From e0bd644c79c5cc31ffa0241427bd5785700d4dbe Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 27 Sep 2021 15:16:07 +0200 Subject: [PATCH 50/96] single threaded file loading with env var --- .github/workflows/benchmarks.yml | 1 + compiler/load/src/file.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 72cb2cbd22..375c8a87cd 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -7,6 +7,7 @@ name: Benchmarks env: RUST_BACKTRACE: 1 + ROC_NUM_WORKERS: 1 jobs: prep-dependency-container: diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 5aaefa463b..0a6fafe939 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -37,13 +37,13 @@ use roc_types::subs::{Subs, VarStore, Variable}; use roc_types::types::{Alias, Type}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet}; -use std::fs; use std::io; use std::iter; use std::path::{Path, PathBuf}; use std::str::from_utf8_unchecked; use std::sync::Arc; use std::time::{Duration, SystemTime}; +use std::{env, fs}; /// Default name for the binary generated for an app, if an invalid one was specified. const DEFAULT_APP_OUTPUT_PATH: &str = "app"; @@ -1351,7 +1351,12 @@ where // doing .max(1) on the entire expression guards against // num_cpus returning 0, while also avoiding wrapping // unsigned subtraction overflow. - let num_workers = num_cpus::get().max(2) - 1; + let default_num_workers = num_cpus::get().max(2) - 1; + + let num_workers = match env::var("ROC_NUM_WORKERS") { + Ok(env_str) => env_str.parse::().unwrap_or(default_num_workers), + Err(_) => default_num_workers, + }; let worker_arenas = arena.alloc(bumpalo::collections::Vec::with_capacity_in( num_workers, From 4026291945c70ce6ae73fa2ece2956dda6c802a6 Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 27 Sep 2021 15:17:13 +0200 Subject: [PATCH 51/96] re-enjable benchmarks --- .github/workflows/benchmarks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 375c8a87cd..a7e7ac0ffe 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -43,4 +43,4 @@ jobs: run: cd ci/bench-runner && cargo build --release && cd ../.. - name: run benchmarks with regression check - run: echo "TODO re-enable benchmarks once race condition is fixed"#./ci/bench-runner/target/release/bench-runner --check-executables-changed + run: ./ci/bench-runner/target/release/bench-runner --check-executables-changed From 02bb9028efbcde53dafa3d05919218e82c1ffd33 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 28 Sep 2021 08:06:59 +0100 Subject: [PATCH 52/96] Returning records on the stack from Wasm dev backend! --- compiler/gen_wasm/src/backend.rs | 199 ++++++----- compiler/gen_wasm/src/layout.rs | 83 +---- compiler/gen_wasm/src/lib.rs | 45 +-- compiler/gen_wasm/src/storage.rs | 156 +++++++++ compiler/gen_wasm/tests/wasm_records.rs | 431 +++++++++++------------- 5 files changed, 504 insertions(+), 410 deletions(-) create mode 100644 compiler/gen_wasm/src/storage.rs diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 392dd176ab..278ca68966 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,6 +11,7 @@ use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; +use crate::storage::SymbolStorage; use crate::{allocate_stack_frame, copy_memory, free_stack_frame, LocalId, PTR_TYPE}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -20,54 +21,6 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024; #[derive(Clone, Copy, Debug)] struct LabelId(u32); -#[derive(Debug)] -enum SymbolStorage { - ParamPrimitive { - local_id: LocalId, - value_type: ValueType, - size: u32, - }, - ParamPointer { - local_id: LocalId, - }, - VarPrimitive { - local_id: LocalId, - value_type: ValueType, - size: u32, - }, - VarStackMemory { - local_id: LocalId, - size: u32, - offset: u32, - }, - VarHeapMemory { - local_id: LocalId, - }, -} - -impl SymbolStorage { - fn local_id(&self) -> LocalId { - match self { - Self::ParamPrimitive { local_id, .. } => *local_id, - Self::ParamPointer { local_id, .. } => *local_id, - Self::VarPrimitive { local_id, .. } => *local_id, - Self::VarStackMemory { local_id, .. } => *local_id, - Self::VarHeapMemory { local_id, .. } => *local_id, - } - } - - #[allow(dead_code)] - fn value_type(&self) -> ValueType { - match self { - Self::ParamPrimitive { value_type, .. } => *value_type, - Self::VarPrimitive { value_type, .. } => *value_type, - Self::ParamPointer { .. } => ValueType::I32, - Self::VarStackMemory { .. } => ValueType::I32, - Self::VarHeapMemory { .. } => ValueType::I32, - } - } -} - enum LocalKind { Parameter, Variable, @@ -205,7 +158,7 @@ impl<'a> WasmBackend<'a> { wasm_layout: WasmLayout, symbol: Symbol, kind: LocalKind, - ) -> LocalId { + ) -> SymbolStorage { let local_index = (self.arg_types.len() + self.locals.len()) as u32; let local_id = LocalId(local_index); @@ -219,7 +172,10 @@ impl<'a> WasmBackend<'a> { value_type, size, }, - _ => SymbolStorage::ParamPointer { local_id }, + _ => SymbolStorage::ParamPointer { + local_id, + wasm_layout, + }, } } LocalKind::Variable => { @@ -242,11 +198,13 @@ impl<'a> WasmBackend<'a> { let mut offset = self.stack_memory; offset += align - 1; offset &= -align; - self.stack_memory = offset + (size - alignment_bytes) as i32; + self.stack_memory = offset + size as i32; + // TODO: if we're creating the frame pointer just reuse the same local_id! let frame_pointer = self.get_or_create_frame_pointer(); // initialise the local with the appropriate address + // TODO: skip this the first time, no point adding zero offset! self.instructions.extend([ GetLocal(frame_pointer.0), I32Const(offset), @@ -258,15 +216,16 @@ impl<'a> WasmBackend<'a> { local_id, size, offset: offset as u32, + alignment_bytes, } } } } }; - self.symbol_storage_map.insert(symbol, storage); + self.symbol_storage_map.insert(symbol, storage.clone()); - local_id + storage } fn get_or_create_frame_pointer(&mut self) -> LocalId { @@ -334,17 +293,20 @@ impl<'a> WasmBackend<'a> { // Saves us from having to copy it later let storage = SymbolStorage::ParamPointer { local_id: LocalId(0), + wasm_layout, }; self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; - self.instructions.push(Return); + self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack Ok(()) } Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable); + let local_id = self + .insert_local(wasm_layout, *sym, LocalKind::Variable) + .local_id(); self.build_expr(sym, expr, layout)?; self.instructions.push(SetLocal(local_id.0)); @@ -354,35 +316,37 @@ impl<'a> WasmBackend<'a> { } Stmt::Ret(sym) => { - use crate::layout::WasmLayout::*; + use crate::storage::SymbolStorage::*; let storage = self.symbol_storage_map.get(sym).unwrap(); match storage { - SymbolStorage::ParamPrimitive { local_id, .. } - | SymbolStorage::VarPrimitive { local_id, .. } - | SymbolStorage::ParamPointer { local_id, .. } - | SymbolStorage::VarHeapMemory { local_id, .. } => { - self.instructions.push(GetLocal(local_id.0)); - self.instructions.push(Return); + VarStackMemory { + local_id, + size, + alignment_bytes, + .. + } + | ParamPointer { + local_id, + wasm_layout: + WasmLayout::StackMemory { + size, + alignment_bytes, + .. + }, + } => { + let from = local_id.clone(); + let to = LocalId(0); + copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0)?; } - SymbolStorage::VarStackMemory { local_id, size, .. } => { - let ret_wasm_layout = WasmLayout::new(ret_layout); - if let StackMemory { alignment_bytes, .. } = ret_wasm_layout { - let from = local_id.clone(); - let to = LocalId(0); - let copy_size: u32 = *size; - copy_memory( - &mut self.instructions, - from, - to, - copy_size, - alignment_bytes, - )?; - } else { - panic!("Return layout doesn't match"); - } + ParamPrimitive { local_id, .. } + | VarPrimitive { local_id, .. } + | ParamPointer { local_id, .. } + | VarHeapMemory { local_id, .. } => { + self.instructions.push(GetLocal(local_id.0)); + self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack } } @@ -446,8 +410,9 @@ impl<'a> WasmBackend<'a> { let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let local_id = - self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); + let local_id = self + .insert_local(wasm_layout, parameter.symbol, LocalKind::Variable) + .local_id(); jp_parameter_local_ids.push(local_id); } @@ -524,6 +489,8 @@ impl<'a> WasmBackend<'a> { x => Err(format!("the call type, {:?}, is not yet implemented", x)), }, + Expr::Struct(fields) => self.create_struct(sym, layout, fields), + x => Err(format!("Expression is not yet implemented {:?}", x)), } } @@ -560,6 +527,78 @@ impl<'a> WasmBackend<'a> { Ok(()) } + fn create_struct( + &mut self, + sym: &Symbol, + layout: &Layout<'a>, + fields: &'a [Symbol], + ) -> Result<(), String> { + let storage = self.get_symbol_storage(sym)?.to_owned(); + + if let Layout::Struct(field_layouts) = layout { + match storage { + SymbolStorage::VarStackMemory { local_id, size, .. } + | SymbolStorage::ParamPointer { + local_id, + wasm_layout: WasmLayout::StackMemory { size, .. }, + } => { + if size > 0 { + let mut relative_offset = 0; + for (field, _) in fields.iter().zip(field_layouts.iter()) { + relative_offset += self.copy_symbol_to_pointer_at_offset( + local_id, + relative_offset, + field, + )?; + } + } else { + return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); + } + } + _ => { + return Err(format!("Cannot create struct {:?} with storage {:?}", sym, storage)); + } + } + } else { + // Struct expression but not Struct layout => single element. Copy it. + let field_storage = self.get_symbol_storage(&fields[0])?.to_owned(); + self.copy_storage(&storage, &field_storage)?; + } + Ok(()) + } + + fn copy_symbol_to_pointer_at_offset( + &mut self, + to_ptr: LocalId, + to_offset: u32, + from_symbol: &Symbol, + ) -> Result { + let from_storage = self.get_symbol_storage(from_symbol)?.to_owned(); + from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset) + } + + fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) -> Result<(), String> { + let has_stack_memory = to.has_stack_memory(); + debug_assert!(from.has_stack_memory() == has_stack_memory); + + if !has_stack_memory { + debug_assert!(from.value_type() == to.value_type()); + self.instructions.push(GetLocal(from.local_id().0)); + self.instructions.push(SetLocal(to.local_id().0)); + Ok(()) + } else { + let (size, alignment_bytes) = from.stack_size_and_alignment(); + copy_memory( + &mut self.instructions, + from.local_id(), + to.local_id(), + size, + alignment_bytes, + 0, + ) + } + } + fn build_call_low_level( &mut self, lowlevel: &LowLevel, diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 576316a049..8a067c412f 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -1,10 +1,10 @@ -use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +use parity_wasm::elements::ValueType; use roc_mono::layout::{Layout, UnionLayout}; -use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8, PTR_SIZE, PTR_TYPE}; +use crate::{PTR_SIZE, PTR_TYPE}; // See README for background information on Wasm locals, memory and function calls -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum WasmLayout { // Primitive number value. Just a Wasm local, without any stack memory. // For example, Roc i8 is represented as Wasm i32. Store the type and the original size. @@ -79,81 +79,4 @@ impl WasmLayout { _ => 0, } } - - #[allow(dead_code)] - fn load(&self, offset: u32) -> Result { - use crate::layout::WasmLayout::*; - use ValueType::*; - - match self { - LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)), - LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)), - LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)), - LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)), - LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)), - LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)), - - // TODO: Come back to this when we need to access fields of structs - // LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?) - // StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR? - - HeapMemory => { - if PTR_TYPE == I64 { - Ok(I64Load(ALIGN_8, offset)) - } else { - Ok(I32Load(ALIGN_4, offset)) - } - } - - _ => Err(format!( - "Failed to generate load instruction for WasmLayout {:?}", - self - )), - } - } - - #[allow(dead_code)] - pub fn store(&self, offset: u32, instructions: &mut Vec) -> Result<(), String> { - use crate::layout::WasmLayout::*; - use ValueType::*; - - let mut result = Ok(()); - match self { - LocalOnly(I32, 4) => instructions.push(I32Store(ALIGN_4, offset)), - LocalOnly(I32, 2) => instructions.push(I32Store16(ALIGN_2, offset)), - LocalOnly(I32, 1) => instructions.push(I32Store8(ALIGN_1, offset)), - LocalOnly(I64, 8) => instructions.push(I64Store(ALIGN_8, offset)), - LocalOnly(F64, 8) => instructions.push(F64Store(ALIGN_8, offset)), - LocalOnly(F32, 4) => instructions.push(F32Store(ALIGN_4, offset)), - - StackMemory { - size, - alignment_bytes, - } => { - // TODO - // Need extra arguments for this case that we don't need for primitives. - // Maybe it should be somewhere we have more relevant context? - // Come back to it when we need to insert things into structs. - let from_ptr = LocalId(0); // TODO - let to_ptr = LocalId(0); // TODO - copy_memory(instructions, from_ptr, to_ptr, *size, *alignment_bytes)?; - } - - HeapMemory => { - if PTR_TYPE == I64 { - instructions.push(I64Store(ALIGN_8, offset)); - } else { - instructions.push(I32Store(ALIGN_4, offset)); - } - } - - _ => { - result = Err(format!( - "Failed to generate store instruction for WasmLayout {:?}", - self - )); - } - } - result - } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index d439fca3cc..4a22343f45 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -1,6 +1,7 @@ mod backend; pub mod from_wasm32_memory; mod layout; +mod storage; use bumpalo::Bump; use parity_wasm::builder; @@ -110,13 +111,13 @@ pub fn build_module_help<'a>( Ok((backend.builder, main_function_index)) } -fn encode_alignment(bytes: u32) -> Result { +fn encode_alignment(bytes: u32) -> u32 { match bytes { - 1 => Ok(ALIGN_1), - 2 => Ok(ALIGN_2), - 4 => Ok(ALIGN_4), - 8 => Ok(ALIGN_8), - _ => Err(format!("{:?}-byte alignment is not supported", bytes)), + 1 => ALIGN_1, + 2 => ALIGN_2, + 4 => ALIGN_4, + 8 => ALIGN_8, + _ => panic!("{:?}-byte alignment is not supported", bytes), } } @@ -124,32 +125,32 @@ fn copy_memory( instructions: &mut Vec, from_ptr: LocalId, to_ptr: LocalId, - size_with_alignment: u32, + size: u32, alignment_bytes: u32, + offset: u32, ) -> Result<(), String> { - let alignment_flag = encode_alignment(alignment_bytes)?; - let size = size_with_alignment - alignment_bytes; - let mut offset = 0; - while size - offset >= 8 { + let alignment_flag = encode_alignment(alignment_bytes); + let mut current_offset = offset; + while size - current_offset >= 8 { instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(from_ptr.0)); - instructions.push(I64Load(alignment_flag, offset)); - instructions.push(I64Store(alignment_flag, offset)); - offset += 8; + instructions.push(I64Load(alignment_flag, current_offset)); + instructions.push(I64Store(alignment_flag, current_offset)); + current_offset += 8; } - if size - offset >= 4 { + if size - current_offset >= 4 { instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load(alignment_flag, offset)); - instructions.push(I32Store(alignment_flag, offset)); - offset += 4; + instructions.push(I32Load(alignment_flag, current_offset)); + instructions.push(I32Store(alignment_flag, current_offset)); + current_offset += 4; } - while size - offset > 0 { + while size - current_offset > 0 { instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load8U(alignment_flag, offset)); - instructions.push(I32Store8(alignment_flag, offset)); - offset += 1; + instructions.push(I32Load8U(alignment_flag, current_offset)); + instructions.push(I32Store8(alignment_flag, current_offset)); + current_offset += 1; } Ok(()) } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs new file mode 100644 index 0000000000..24b2e7a87c --- /dev/null +++ b/compiler/gen_wasm/src/storage.rs @@ -0,0 +1,156 @@ +use crate::{copy_memory, layout::WasmLayout, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; + +#[derive(Debug, Clone)] +pub enum SymbolStorage { + ParamPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + ParamPointer { + local_id: LocalId, + wasm_layout: WasmLayout, + }, + VarPrimitive { + local_id: LocalId, + value_type: ValueType, + size: u32, + }, + VarStackMemory { + local_id: LocalId, + size: u32, + offset: u32, + alignment_bytes: u32, + }, + VarHeapMemory { + local_id: LocalId, + }, +} + +impl SymbolStorage { + pub fn local_id(&self) -> LocalId { + match self { + Self::ParamPrimitive { local_id, .. } => *local_id, + Self::ParamPointer { local_id, .. } => *local_id, + Self::VarPrimitive { local_id, .. } => *local_id, + Self::VarStackMemory { local_id, .. } => *local_id, + Self::VarHeapMemory { local_id, .. } => *local_id, + } + } + + pub fn value_type(&self) -> ValueType { + match self { + Self::ParamPrimitive { value_type, .. } => *value_type, + Self::VarPrimitive { value_type, .. } => *value_type, + Self::ParamPointer { .. } => ValueType::I32, + Self::VarStackMemory { .. } => ValueType::I32, + Self::VarHeapMemory { .. } => ValueType::I32, + } + } + + pub fn has_stack_memory(&self) -> bool { + match self { + Self::ParamPointer { + wasm_layout: WasmLayout::StackMemory { .. }, + .. + } => true, + Self::ParamPointer { .. } => false, + Self::VarStackMemory { .. } => true, + Self::ParamPrimitive { .. } => false, + Self::VarPrimitive { .. } => false, + Self::VarHeapMemory { .. } => false, + } + } + + pub fn stack_size_and_alignment(&self) -> (u32, u32) { + match self { + Self::VarStackMemory { + size, + alignment_bytes, + .. + } + | Self::ParamPointer { + wasm_layout: + WasmLayout::StackMemory { + size, + alignment_bytes, + .. + }, + .. + } => (*size, *alignment_bytes), + + _ => (0, 0), + } + } + + pub fn copy_to_memory( + &self, + instructions: &mut Vec, + to_pointer: LocalId, + to_offset: u32, + ) -> Result { + match self { + Self::ParamPrimitive { + local_id, + value_type, + size, + .. + } + | Self::VarPrimitive { + local_id, + value_type, + size, + .. + } => { + let store_instruction = match (value_type, size) { + (ValueType::I64, 8) => I64Store(ALIGN_8, to_offset), + (ValueType::I32, 4) => I32Store(ALIGN_4, to_offset), + (ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset), + (ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset), + (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), + (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), + _ => { + return Err(format!("Cannot store {:?} with alignment of {:?}", value_type, size)); + } + }; + instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(local_id.0)); + instructions.push(store_instruction); + Ok(*size) + } + + Self::ParamPointer { + local_id, + wasm_layout: + WasmLayout::StackMemory { + size, + alignment_bytes, + }, + } + | Self::VarStackMemory { + local_id, + size, + alignment_bytes, + .. + } => { + copy_memory( + instructions, + *local_id, + to_pointer, + *size, + *alignment_bytes, + to_offset, + )?; + Ok(*size) + } + + Self::ParamPointer { local_id, .. } | Self::VarHeapMemory { local_id, .. } => { + instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(local_id.0)); + instructions.push(I32Store(ALIGN_4, to_offset)); + Ok(4) + } + } + } +} diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 7d4bc9af3f..d9776f97f2 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -308,125 +308,100 @@ mod wasm_records { // ); // } // - // #[test] - // fn i64_record1_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3 } - // "# - // ), - // 3, - // i64 - // ); - // } - - // #[test] - // fn i64_record2_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5 } - // "# - // ), - // (3, 5), - // (i64, i64) - // ); - // } - - // // #[test] - // // fn i64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3, y: 5, z: 17 } - // // "# - // // ), - // // (3, 5, 17), - // // (i64, i64, i64) - // // ); - // // } - - // #[test] - // fn f64_record2_literal() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3.1, y: 5.1 } - // "# - // ), - // (3.1, 5.1), - // (f64, f64) - // ); - // } - - // // #[test] - // // fn f64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3.1, y: 5.1, z: 17.1 } - // // "# - // // ), - // // (3.1, 5.1, 17.1), - // // (f64, f64, f64) - // // ); - // // } - - // // #[test] - // // fn bool_record4_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // record : { a : Bool, b : Bool, c : Bool, d : Bool } - // // record = { a: True, b: True, c : True, d : Bool } - - // // record - // // "# - // // ), - // // (true, false, false, true), - // // (bool, bool, bool, bool) - // // ); - // // } + #[test] + fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3 } + "# + ), + 3, + i64 + ); + } #[test] - fn i64_record1_literal() { + fn i64_record2_literal() { assert_evals_to!( indoc!( r#" - { a: 3 } + { x: 3, y: 5 } "# ), - 3, - i64 + (3, 5), + (i64, i64) ); } - // // #[test] - // // fn i64_record9_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } - // // "# - // // ), - // // (3, 5, 17, 1, 9, 12, 13, 14, 15), - // // (i64, i64, i64, i64, i64, i64, i64, i64, i64) - // // ); - // // } + #[test] + fn i64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 17 } + "# + ), + (3, 5, 17), + (i64, i64, i64) + ); + } + + #[test] + fn f64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1 } + "# + ), + (3.1, 5.1), + (f64, f64) + ); + } + + #[test] + fn f64_record3_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1, z: 17.1 } + "# + ), + (3.1, 5.1, 17.1), + (f64, f64, f64) + ); + } + + #[test] + fn bool_record4_literal() { + assert_evals_to!( + indoc!( + r#" + record : { a : Bool, b : Bool, c : Bool, d : Bool } + record = { a: True, b: False, c : False, d : True } + + record + "# + ), + [true, false, false, true], + [bool; 4] + ); + } + + #[test] + fn i64_record9_literal() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } + "# + ), + [3, 5, 17, 1, 9, 12, 13, 14, 15], + [i64; 9] + ); + } - // // #[test] - // // fn f64_record3_literal() { - // // assert_evals_to!( - // // indoc!( - // // r#" - // // { x: 3.1, y: 5.1, z: 17.1 } - // // "# - // // ), - // // (3.1, 5.1, 17.1), - // // (f64, f64, f64) - // // ); - // // } #[test] fn bool_literal() { @@ -667,135 +642,135 @@ mod wasm_records { // ); // } - // #[test] - // fn return_record_2() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5 } - // "# - // ), - // [3, 5], - // [i64; 2] - // ); - // } + #[test] + fn return_record_2() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5 } + "# + ), + [3, 5], + [i64; 2] + ); + } - // #[test] - // fn return_record_3() { - // assert_evals_to!( - // indoc!( - // r#" - // { x: 3, y: 5, z: 4 } - // "# - // ), - // (3, 5, 4), - // (i64, i64, i64) - // ); - // } + #[test] + fn return_record_3() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5, z: 4 } + "# + ), + (3, 5, 4), + (i64, i64, i64) + ); + } - // #[test] - // fn return_record_4() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2 } - // "# - // ), - // [3, 5, 4, 2], - // [i64; 4] - // ); - // } + #[test] + fn return_record_4() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2 } + "# + ), + [3, 5, 4, 2], + [i64; 4] + ); + } - // #[test] - // fn return_record_5() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1 } - // "# - // ), - // [3, 5, 4, 2, 1], - // [i64; 5] - // ); - // } + #[test] + fn return_record_5() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1 } + "# + ), + [3, 5, 4, 2, 1], + [i64; 5] + ); + } - // #[test] - // fn return_record_6() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } - // "# - // ), - // [3, 5, 4, 2, 1, 7], - // [i64; 6] - // ); - // } + #[test] + fn return_record_6() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } + "# + ), + [3, 5, 4, 2, 1, 7], + [i64; 6] + ); + } - // #[test] - // fn return_record_7() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } - // "# - // ), - // [3, 5, 4, 2, 1, 7, 8], - // [i64; 7] - // ); - // } + #[test] + fn return_record_7() { + assert_evals_to!( + indoc!( + r#" + { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } + "# + ), + [3, 5, 4, 2, 1, 7, 8], + [i64; 7] + ); + } - // #[test] - // fn return_record_float_int() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 3.14, b: 0x1 } - // "# - // ), - // (3.14, 0x1), - // (f64, i64) - // ); - // } + #[test] + fn return_record_float_int() { + assert_evals_to!( + indoc!( + r#" + { a: 3.14, b: 0x1 } + "# + ), + (3.14, 0x1), + (f64, i64) + ); + } - // #[test] - // fn return_record_int_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 0x1, b: 3.14 } - // "# - // ), - // (0x1, 3.14), - // (i64, f64) - // ); - // } + #[test] + fn return_record_int_float() { + assert_evals_to!( + indoc!( + r#" + { a: 0x1, b: 3.14 } + "# + ), + (0x1, 3.14), + (i64, f64) + ); + } - // #[test] - // fn return_record_float_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 6.28, b: 3.14 } - // "# - // ), - // (6.28, 3.14), - // (f64, f64) - // ); - // } + #[test] + fn return_record_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14 } + "# + ), + (6.28, 3.14), + (f64, f64) + ); + } - // #[test] - // fn return_record_float_float_float() { - // assert_evals_to!( - // indoc!( - // r#" - // { a: 6.28, b: 3.14, c: 0.1 } - // "# - // ), - // (6.28, 3.14, 0.1), - // (f64, f64, f64) - // ); - // } + #[test] + fn return_record_float_float_float() { + assert_evals_to!( + indoc!( + r#" + { a: 6.28, b: 3.14, c: 0.1 } + "# + ), + (6.28, 3.14, 0.1), + (f64, f64, f64) + ); + } // #[test] // fn return_nested_record() { From 45c9dc86176d2b616889e8040c7c026034bfc8b7 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 28 Sep 2021 19:06:47 +0100 Subject: [PATCH 53/96] update gen_wasm README --- compiler/gen_wasm/README.md | 66 ++++++++++++++++++++------------ compiler/gen_wasm/src/backend.rs | 2 +- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/compiler/gen_wasm/README.md b/compiler/gen_wasm/README.md index 50018f8758..a3f22297b6 100644 --- a/compiler/gen_wasm/README.md +++ b/compiler/gen_wasm/README.md @@ -3,48 +3,57 @@ ## Plan - Initial bringup - - Get a wasm backend working for some of the number tests. - - Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time. -- Improve the fundamentals + - [x] Get a wasm backend working for some of the number tests. + - [x] Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time. +- Get the fundamentals working + - [x] Come up with a way to do control flow - [x] Flesh out the details of value representations between local variables and stack memory - - [ ] Set up a way to write tests with any return value rather than just i64 and f64 - - [ ] Figure out relocations for linking object files - - [ ] Think about the Wasm module builder library we're using, are we happy with it? + - [x] Set up a way to write tests with any return value rather than just i64 and f64 + - [x] Implement stack memory + - [x] Push and pop stack frames + - [x] Deal with returning structs + - [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc. + - [ ] Ensure early Return statements don't skip stack cleanup + - [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec` + - [ ] Implement relocations + - Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec` rather than a `Vec`. It may be worth serialising each instruction as it is inserted. + +- Refactor for code sharing with CPU backends + + - [ ] Implement a `scan_ast` pre-pass like `Backend` does, but for reusing Wasm locals rather than CPU registers + - [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing + - [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible + - Integration - Move wasm files to `gen_dev/src/wasm` - Share tests between wasm and x64, with some way of saying which tests work on which backends, and dispatching to different eval helpers based on that. - Get `build_module` in object_builder.rs to dispatch to the wasm generator (adding some Wasm options to the `Triple` struct) - Get `build_module` to write to a file, or maybe return `Vec`, instead of returning an Object structure -- Code sharing - - Try to ensure that both Wasm and x64 use the same `Backend` trait so that we can share code. - - We need to work towards this after we've progressed a bit more with Wasm and gained more understanding and experience of the differences. - - We will have to think about how to deal with the `Backend` code that doesn't apply to Wasm. Perhaps we will end up with more traits like `RegisterBackend` / `StackBackend` or `NativeBackend` / `WasmBackend`, and perhaps even some traits to do with backends that support jumps and those that don't. ## Structured control flow -🚨 **This is an area that could be tricky** 🚨 - One of the security features of WebAssembly is that it does not allow unrestricted "jumps" to anywhere you like. It does not have an instruction for that. All of the [control instructions][control-inst] can only implement "structured" control flow, and have names like `if`, `loop`, `block` that you'd normally associate with high-level languages. There are branch (`br`) instructions that can jump to labelled blocks within the same function, but the blocks have to be nested in sensible ways. [control-inst]: https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions -Implications: +This way of representing control flow is similar to parts of the Roc AST like `When`, `If` and `LetRec`. But Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. We need to map back from graph to a tree again in the Wasm backend. -Roc, like most modern languages, is already enforcing structured control flow in the source program. Constructs from the Roc AST like `When`, `If` and `LetRec` can all be converted straightforwardly to Wasm constructs. +Our solution is to wrap all joinpoint/jump graphs in an outer `loop`, with nested `block`s inside it. -However the Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. That doesn't map so directly to the Wasm structures. This is such a common issue for compiler back-ends that the WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. We should probably investigate this area sooner rather than later. If relooping turns out to be necessary or difficult, we might need to switch from parity_wasm to binaryen. +### Possible future optimisations -> By the way, it's not obvious how to pronounce "binaryen" but apparently it rhymes with "Targaryen", the family name from the "Game of Thrones" TV series +There are other algorithms available that may result in more optimised control flow. We are not focusing on that for our development backend, but here are some notes for future reference. + +The WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf). + +> By the way, apparently "binaryen" rhymes with "Targaryen", the family name from the "Game of Thrones" TV series + +There is also an improvement on Relooper called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2). It can reorder the joinpoints and jumps to make code more efficient. (It is also has things Roc wouldn't need but C++ does, like support for "irreducible" graphs that include `goto`). [cfg-api]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api [binaryen-rs]: https://crates.io/crates/binaryen -Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf). - -There is an alternative algorithm that is supposed to be an improvement on Relooper, called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2). - - ## Stack machine vs register machine Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack. @@ -113,6 +122,7 @@ $ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm ``` The optimised functions have no local variables, and the code shrinks to about 60% of its original size. + ``` (func (;0;) (param i64 i64) (result i64) local.get 0 @@ -143,7 +153,7 @@ When we are talking about how we store values in _memory_, I'll use the term _st Of course our program can use another area of memory as a heap as well. WebAssembly doesn't mind how you divide up your memory. It just gives you some memory and some instructions for loading and storing. -## Function calls +## Calling conventions & stack memory In WebAssembly you call a function by pushing arguments to the stack and then issuing a `call` instruction, which specifies a function index. The VM knows how many values to pop off the stack by examining the _type_ of the function. In our example earlier, `Num.add` had the type `[i64 i64] → [i64]` so it expects to find two i64's on the stack and pushes one i64 back as the result. Remember, the runtime engine will validate the module before running it, and if your generated code is trying to call a function at a point in the program where the wrong value types are on the stack, it will fail validation. @@ -151,11 +161,17 @@ Function arguments are restricted to the four value types, `i32`, `i64`, `f32` a That's all great for primitive values but what happens when we want to pass more complex data structures between functions? -Well, remember, "stack memory" is not a special kind of memory in WebAssembly, it's just an area of our memory where we _decide_ that we want to implement a stack data structure. So we can implement it however we want. A good choice would be to make our stack frame look the same as it would when we're targeting a CPU, except without the return address (since there's no need for one). We can also decide to pass numbers through the machine stack rather than in stack memory, since that takes fewer instructions. +Well, remember, "stack memory" is not a special kind of memory in WebAssembly, and is separate from the VM stack. It's just an area of our memory where we implement a stack data structure. But there are some conventions that it makes sense to follow so that we can easily link to Wasm code generated from Zig or other languages. -The only other thing we need is a stack pointer. On CPU targets, there's often have a specific "stack pointer" register. WebAssembly has no equivalent to that, but we can use a `global` variable. +### Observations from compiled C code -The system I've outlined above is based on my experience of compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals). +- `global 0` is used as the stack pointer, and its value is normally copied to a `local` as well (presumably because locals tend to be assigned to CPU registers) +- Stack memory grows downwards +- If a C function returns a struct, the compiled WebAssembly function has no return value, but instead has an extra _argument_. The argument is an `i32` pointer to space allocated in the caller's stack, that the called function can write to. +- There is no maximum number of arguments for a WebAssembly function, and arguments are not passed via _stack memory_. This makes sense because the _VM stack_ has no size limit. It's like having a CPU with an unlimited number of registers. +- Stack memory is only used for allocating local variables, not for passing arguments. And it's only used for values that cannot be stored in one of WebAssembly's primitive values (`i32`, `i64`, `f32`, `f64`). + +These observations are based on experiments compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals). ## Modules vs Instances diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 278ca68966..53a3f429f4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -204,7 +204,7 @@ impl<'a> WasmBackend<'a> { let frame_pointer = self.get_or_create_frame_pointer(); // initialise the local with the appropriate address - // TODO: skip this the first time, no point adding zero offset! + // TODO: skip this the first time, no point generating code to add zero offset! self.instructions.extend([ GetLocal(frame_pointer.0), I32Const(offset), From 7ea59ad9d44f87ad82febc82c73a6a9cf403731b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Tue, 28 Sep 2021 23:16:34 +0100 Subject: [PATCH 54/96] PR tidy-ups --- compiler/gen_wasm/src/backend.rs | 24 ++++++++++++---------- compiler/gen_wasm/src/layout.rs | 2 +- compiler/gen_wasm/src/lib.rs | 12 +++++++++-- compiler/gen_wasm/src/storage.rs | 5 ++++- compiler/gen_wasm/tests/wasm_records.rs | 27 ++++++++++++------------- 5 files changed, 41 insertions(+), 29 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 53a3f429f4..920adae7b3 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -12,7 +12,9 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; -use crate::{allocate_stack_frame, copy_memory, free_stack_frame, LocalId, PTR_TYPE}; +use crate::{ + allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_TYPE, +}; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Follow Emscripten's example by using 1kB (4 bytes would probably do) @@ -142,15 +144,13 @@ impl<'a> WasmBackend<'a> { } final_instructions.push(Instruction::End); - let function_def = builder::function() + builder::function() .with_signature(signature_builder.build_sig()) .body() .with_locals(self.locals.clone()) .with_instructions(Instructions::new(final_instructions)) .build() // body - .build(); // function - - function_def + .build() // function } fn insert_local( @@ -194,10 +194,9 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, } => { - let align = alignment_bytes as i32; - let mut offset = self.stack_memory; - offset += align - 1; - offset &= -align; + let offset = + round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + self.stack_memory = offset + size as i32; // TODO: if we're creating the frame pointer just reuse the same local_id! @@ -336,7 +335,7 @@ impl<'a> WasmBackend<'a> { .. }, } => { - let from = local_id.clone(); + let from = *local_id; let to = LocalId(0); copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0)?; } @@ -556,7 +555,10 @@ impl<'a> WasmBackend<'a> { } } _ => { - return Err(format!("Cannot create struct {:?} with storage {:?}", sym, storage)); + return Err(format!( + "Cannot create struct {:?} with storage {:?}", + sym, storage + )); } } } else { diff --git a/compiler/gen_wasm/src/layout.rs b/compiler/gen_wasm/src/layout.rs index 8a067c412f..df59b80eb1 100644 --- a/compiler/gen_wasm/src/layout.rs +++ b/compiler/gen_wasm/src/layout.rs @@ -31,7 +31,7 @@ impl WasmLayout { Layout::Builtin(Int64) => Self::LocalOnly(I64, size), - Layout::Builtin(Float32 | Float16) => Self::LocalOnly(F32, size), + Layout::Builtin(Float32) => Self::LocalOnly(F32, size), Layout::Builtin(Float64) => Self::LocalOnly(F64, size), diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 4a22343f45..d5d680e283 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -155,12 +155,20 @@ fn copy_memory( Ok(()) } +/// Round up to alignment_bytes (assumed to be a power of 2) +pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { + let mut aligned = unaligned; + aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary + aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0 + aligned +} + pub fn allocate_stack_frame( instructions: &mut Vec, size: i32, local_frame_pointer: LocalId, ) { - let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); + let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); instructions.extend([ GetGlobal(STACK_POINTER_GLOBAL_ID), I32Const(aligned_size), @@ -175,7 +183,7 @@ pub fn free_stack_frame( size: i32, local_frame_pointer: LocalId, ) { - let aligned_size = (size + STACK_ALIGNMENT_BYTES - 1) & (-STACK_ALIGNMENT_BYTES); + let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES); instructions.extend([ GetLocal(local_frame_pointer.0), I32Const(aligned_size), diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 24b2e7a87c..500f178e80 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -111,7 +111,10 @@ impl SymbolStorage { (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), _ => { - return Err(format!("Cannot store {:?} with alignment of {:?}", value_type, size)); + return Err(format!( + "Cannot store {:?} with alignment of {:?}", + value_type, size + )); } }; instructions.push(GetLocal(to_pointer.0)); diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index d9776f97f2..5ae26092ef 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -307,19 +307,19 @@ mod wasm_records { // () // ); // } - // - #[test] - fn i64_record1_literal() { - assert_evals_to!( - indoc!( - r#" - { x: 3 } - "# - ), - 3, - i64 - ); - } + + #[test] + fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3 } + "# + ), + 3, + i64 + ); + } #[test] fn i64_record2_literal() { @@ -402,7 +402,6 @@ mod wasm_records { ); } - #[test] fn bool_literal() { assert_evals_to!( From b32a42f05a4bf52f8c5721342be0d06e2d9b2b8f Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 17:32:42 -0400 Subject: [PATCH 55/96] Add Str.repeat builtin --- compiler/builtins/bitcode/src/main.zig | 1 + compiler/builtins/bitcode/src/str.zig | 17 ++++++++++++++++ compiler/builtins/src/bitcode.rs | 1 + compiler/builtins/src/std.rs | 7 +++++++ compiler/can/src/builtins.rs | 21 +++++++++++++++++++ compiler/gen_llvm/src/llvm/build.rs | 8 +++++++- compiler/gen_llvm/src/llvm/build_str.rs | 12 +++++++++++ compiler/module/src/low_level.rs | 27 +++++++++++++------------ compiler/module/src/symbol.rs | 1 + compiler/mono/src/borrow.rs | 1 + compiler/test_gen/src/gen_str.rs | 27 +++++++++++++++++++++++++ 11 files changed, 109 insertions(+), 14 deletions(-) diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 8fe350f16d..0841a8e8f5 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -101,6 +101,7 @@ comptime { exportStrFn(str.strToUtf8C, "to_utf8"); exportStrFn(str.fromUtf8C, "from_utf8"); exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); + exportStrFn(str.repeat, "repeat"); } // Utils diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 2172e693d7..112c74cd5b 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -866,6 +866,23 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool { return true; } + +// Str.repeat +pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr { + const bytes_len = string.len(); + const bytes_ptr = string.asU8ptr(); + + var ret_string = RocStr.allocate(.Clone, count * bytes_len); + var ret_string_ptr = ret_string.asU8ptr(); + + var i: usize = 0; + while (i < count) : (i += 1) { + @memcpy(ret_string_ptr + (i * bytes_len), bytes_ptr, bytes_len); + } + + return ret_string; +} + // Str.startsWithCodePt pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool { const bytes_ptr = string.asU8ptr(); diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index b45d822c69..fd1ac7b17c 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -28,6 +28,7 @@ pub const STR_EQUAL: &str = "roc_builtins.str.equal"; pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range"; +pub const STR_REPEAT: &str = "roc_builtins.str.repeat"; pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 98762b568b..7615879227 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -618,6 +618,13 @@ pub fn types() -> MutMap { Box::new(str_type()) ); + // repeat : Str, Nat -> Str + add_top_level_function_type!( + Symbol::STR_REPEAT, + vec![str_type(), nat_type()], + Box::new(str_type()) + ); + // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* { let bad_utf8 = SolvedType::TagUnion( diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index fe45f1f673..d4fff2ef74 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -66,6 +66,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option STR_FROM_UTF8_RANGE => str_from_utf8_range, STR_TO_UTF8 => str_to_utf8, STR_FROM_FLOAT=> str_from_float, + STR_REPEAT => str_repeat, LIST_LEN => list_len, LIST_GET => list_get, LIST_SET => list_set, @@ -1233,6 +1234,26 @@ fn str_split(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// Str.repeat : Str, Nat -> Str +fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def { + let str_var = var_store.fresh(); + let nat_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::StrRepeat, + args: vec![(str_var, Var(Symbol::ARG_1)), (nat_var, Var(Symbol::ARG_2))], + ret_var: str_var, + }; + + defn( + symbol, + vec![(str_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)], + var_store, + body, + str_var, + ) +} + /// Str.concat : Str, Str -> Str fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { let str_var = var_store.fresh(); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 4864b04fcb..9864f277a8 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -15,7 +15,7 @@ use crate::llvm::build_list::{ }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, - str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_split, + str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split, str_starts_with, str_starts_with_code_point, str_to_utf8, }; use crate::llvm::compare::{generic_eq, generic_neq}; @@ -4853,6 +4853,12 @@ fn run_low_level<'a, 'ctx, 'env>( str_to_utf8(env, string.into_struct_value()) } + StrRepeat => { + // Str.repeat : Str, Nat -> Str + debug_assert_eq!(args.len(), 2); + + str_repeat(env, scope, args[0], args[1]) + } StrSplit => { // Str.split : Str, Str -> List Str debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 80bfa0fa3a..15fa225458 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -12,6 +12,18 @@ use super::build::load_symbol; pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8); +/// Str.repeat : Str, Nat -> Str +pub fn str_repeat<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + scope: &Scope<'a, 'ctx>, + str_symbol: Symbol, + count_symbol: Symbol, +) -> BasicValueEnum<'ctx> { + let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol); + let count = load_symbol(scope, &count_symbol); + call_bitcode_fn(env, &[str_c_abi.into(), count.into()], bitcode::STR_REPEAT) +} + /// Str.split : Str, Str -> List Str pub fn str_split<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 583e6017c1..7dec1f4ccf 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -15,6 +15,7 @@ pub enum LowLevel { StrFromUtf8, StrFromUtf8Range, StrToUtf8, + StrRepeat, StrFromFloat, ListLen, ListGetUnsafe, @@ -114,19 +115,19 @@ impl LowLevel { match self { StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 - | StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet - | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains - | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty - | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues - | DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap - | NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap - | NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked - | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos - | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling - | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd - | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 - | NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not - | Hash | ExpectTrue => false, + | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe + | ListSet | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat + | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap + | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe + | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference + | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap + | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt + | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf + | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound + | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan + | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy + | NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast + | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false, ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index aaa9ce321d..6e825a9953 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -927,6 +927,7 @@ define_builtins! { 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime 18 STR_FROM_UTF8_RANGE: "fromUtf8Range" + 19 STR_REPEAT: "repeat" } 4 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 89d6ae2446..45f5eb9c0d 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -1013,6 +1013,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrToUtf8 => arena.alloc_slice_copy(&[owned]), + StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), DictSize => arena.alloc_slice_copy(&[borrowed]), diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 2b5bdb45ad..8aca3288fd 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -949,3 +949,30 @@ fn str_from_utf8_range_count_too_high_for_start() { RocStr ); } + +#[test] +fn str_repeat() { + assert_evals_to!( + indoc!(r#"Str.repeat "Roc" 3"#), + RocStr::from("RocRocRoc"), + RocStr + ); +} + +#[test] +fn str_repeat_empty_string() { + assert_evals_to!( + indoc!(r#"Str.repeat "" 3"#), + RocStr::from(""), + RocStr + ); +} + +#[test] +fn str_repeat_zero_times() { + assert_evals_to!( + indoc!(r#"Str.repeat "Roc" 0"#), + RocStr::from(""), + RocStr + ); +} From 0cdafa16231ae7cee92a693a54170b80c6401df3 Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 21:54:06 -0400 Subject: [PATCH 56/96] Add Str.repeat test for big strings --- compiler/test_gen/src/gen_str.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/compiler/test_gen/src/gen_str.rs b/compiler/test_gen/src/gen_str.rs index 8aca3288fd..292946403e 100644 --- a/compiler/test_gen/src/gen_str.rs +++ b/compiler/test_gen/src/gen_str.rs @@ -951,7 +951,7 @@ fn str_from_utf8_range_count_too_high_for_start() { } #[test] -fn str_repeat() { +fn str_repeat_small() { assert_evals_to!( indoc!(r#"Str.repeat "Roc" 3"#), RocStr::from("RocRocRoc"), @@ -960,19 +960,20 @@ fn str_repeat() { } #[test] -fn str_repeat_empty_string() { +fn str_repeat_big() { assert_evals_to!( - indoc!(r#"Str.repeat "" 3"#), - RocStr::from(""), + indoc!(r#"Str.repeat "more than 16 characters" 2"#), + RocStr::from("more than 16 charactersmore than 16 characters"), RocStr ); } #[test] -fn str_repeat_zero_times() { - assert_evals_to!( - indoc!(r#"Str.repeat "Roc" 0"#), - RocStr::from(""), - RocStr - ); +fn str_repeat_empty_string() { + assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr); +} + +#[test] +fn str_repeat_zero_times() { + assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr); } From b2343cb0adf14e4edc18b8f5a78423dd643b98dc Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 21:54:14 -0400 Subject: [PATCH 57/96] Format zig --- compiler/builtins/bitcode/src/str.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 112c74cd5b..ac2b3f6399 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -866,7 +866,6 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool { return true; } - // Str.repeat pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr { const bytes_len = string.len(); From 2f574ea75eea53536649fc638c154328ac0ebffe Mon Sep 17 00:00:00 2001 From: Kofi Gumbs Date: Wed, 29 Sep 2021 22:41:30 -0400 Subject: [PATCH 58/96] Remove extraneous .into() --- compiler/gen_llvm/src/llvm/build_str.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 15fa225458..02098150b7 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -21,7 +21,7 @@ pub fn str_repeat<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol); let count = load_symbol(scope, &count_symbol); - call_bitcode_fn(env, &[str_c_abi.into(), count.into()], bitcode::STR_REPEAT) + call_bitcode_fn(env, &[str_c_abi.into(), count], bitcode::STR_REPEAT) } /// Str.split : Str, Str -> List Str From f1b14c14e37165c8a1a5ac7dbbe38d05e758b09a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Sep 2021 20:35:52 +0100 Subject: [PATCH 59/96] Refactor SymbolStorage to emphasise stack/non-stack params We don't have much (any) code that cares about the difference between heap pointers and other primitives, but we have a _lot_ of code that cares if it's stack memory. So let's encode it that way. --- compiler/gen_wasm/src/backend.rs | 45 ++++++++++++++++++------------- compiler/gen_wasm/src/storage.rs | 46 +++++++++++++------------------- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 920adae7b3..1a5f975995 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,8 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_TYPE, + allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + PTR_TYPE, }; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -172,9 +173,20 @@ impl<'a> WasmBackend<'a> { value_type, size, }, - _ => SymbolStorage::ParamPointer { + + WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive { local_id, - wasm_layout, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, + + WasmLayout::StackMemory { + size, + alignment_bytes, + } => SymbolStorage::ParamStackMemory { + local_id, + size, + alignment_bytes, }, } } @@ -287,12 +299,17 @@ impl<'a> WasmBackend<'a> { // Simple optimisation: if we are just returning the expression, we don't need a local Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { let wasm_layout = WasmLayout::new(layout); - if let WasmLayout::StackMemory { .. } = wasm_layout { + if let WasmLayout::StackMemory { + size, + alignment_bytes, + } = wasm_layout + { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later - let storage = SymbolStorage::ParamPointer { + let storage = SymbolStorage::ParamStackMemory { local_id: LocalId(0), - wasm_layout, + size, + alignment_bytes, }; self.symbol_storage_map.insert(*let_sym, storage); } @@ -326,14 +343,10 @@ impl<'a> WasmBackend<'a> { alignment_bytes, .. } - | ParamPointer { + | ParamStackMemory { local_id, - wasm_layout: - WasmLayout::StackMemory { - size, - alignment_bytes, - .. - }, + size, + alignment_bytes, } => { let from = *local_id; let to = LocalId(0); @@ -342,7 +355,6 @@ impl<'a> WasmBackend<'a> { ParamPrimitive { local_id, .. } | VarPrimitive { local_id, .. } - | ParamPointer { local_id, .. } | VarHeapMemory { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack @@ -537,10 +549,7 @@ impl<'a> WasmBackend<'a> { if let Layout::Struct(field_layouts) = layout { match storage { SymbolStorage::VarStackMemory { local_id, size, .. } - | SymbolStorage::ParamPointer { - local_id, - wasm_layout: WasmLayout::StackMemory { size, .. }, - } => { + | SymbolStorage::ParamStackMemory { local_id, size, .. } => { if size > 0 { let mut relative_offset = 0; for (field, _) in fields.iter().zip(field_layouts.iter()) { diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 500f178e80..095998a0af 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,18 +1,14 @@ -use crate::{copy_memory, layout::WasmLayout, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] pub enum SymbolStorage { - ParamPrimitive { + VarPrimitive { local_id: LocalId, value_type: ValueType, size: u32, }, - ParamPointer { - local_id: LocalId, - wasm_layout: WasmLayout, - }, - VarPrimitive { + ParamPrimitive { local_id: LocalId, value_type: ValueType, size: u32, @@ -23,6 +19,11 @@ pub enum SymbolStorage { offset: u32, alignment_bytes: u32, }, + ParamStackMemory { + local_id: LocalId, + size: u32, + alignment_bytes: u32, + }, VarHeapMemory { local_id: LocalId, }, @@ -32,7 +33,7 @@ impl SymbolStorage { pub fn local_id(&self) -> LocalId { match self { Self::ParamPrimitive { local_id, .. } => *local_id, - Self::ParamPointer { local_id, .. } => *local_id, + Self::ParamStackMemory { local_id, .. } => *local_id, Self::VarPrimitive { local_id, .. } => *local_id, Self::VarStackMemory { local_id, .. } => *local_id, Self::VarHeapMemory { local_id, .. } => *local_id, @@ -43,7 +44,7 @@ impl SymbolStorage { match self { Self::ParamPrimitive { value_type, .. } => *value_type, Self::VarPrimitive { value_type, .. } => *value_type, - Self::ParamPointer { .. } => ValueType::I32, + Self::ParamStackMemory { .. } => ValueType::I32, Self::VarStackMemory { .. } => ValueType::I32, Self::VarHeapMemory { .. } => ValueType::I32, } @@ -51,11 +52,7 @@ impl SymbolStorage { pub fn has_stack_memory(&self) -> bool { match self { - Self::ParamPointer { - wasm_layout: WasmLayout::StackMemory { .. }, - .. - } => true, - Self::ParamPointer { .. } => false, + Self::ParamStackMemory { .. } => true, Self::VarStackMemory { .. } => true, Self::ParamPrimitive { .. } => false, Self::VarPrimitive { .. } => false, @@ -70,13 +67,9 @@ impl SymbolStorage { alignment_bytes, .. } - | Self::ParamPointer { - wasm_layout: - WasmLayout::StackMemory { - size, - alignment_bytes, - .. - }, + | Self::ParamStackMemory { + size, + alignment_bytes, .. } => (*size, *alignment_bytes), @@ -123,13 +116,10 @@ impl SymbolStorage { Ok(*size) } - Self::ParamPointer { + Self::ParamStackMemory { local_id, - wasm_layout: - WasmLayout::StackMemory { - size, - alignment_bytes, - }, + size, + alignment_bytes, } | Self::VarStackMemory { local_id, @@ -148,7 +138,7 @@ impl SymbolStorage { Ok(*size) } - Self::ParamPointer { local_id, .. } | Self::VarHeapMemory { local_id, .. } => { + Self::VarHeapMemory { local_id, .. } => { instructions.push(GetLocal(to_pointer.0)); instructions.push(GetLocal(local_id.0)); instructions.push(I32Store(ALIGN_4, to_offset)); From 79ac2f04b82f5b2b01bc7eb49f737648f7797681 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Sep 2021 21:19:57 +0100 Subject: [PATCH 60/96] Improve stack allocation code --- compiler/gen_wasm/src/backend.rs | 46 ++++++++++++++------------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 1a5f975995..b6fd083d57 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -160,8 +160,7 @@ impl<'a> WasmBackend<'a> { symbol: Symbol, kind: LocalKind, ) -> SymbolStorage { - let local_index = (self.arg_types.len() + self.locals.len()) as u32; - let local_id = LocalId(local_index); + let local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); let storage = match kind { LocalKind::Parameter => { @@ -208,20 +207,28 @@ impl<'a> WasmBackend<'a> { } => { let offset = round_up_to_alignment(self.stack_memory, alignment_bytes as i32); - self.stack_memory = offset + size as i32; - // TODO: if we're creating the frame pointer just reuse the same local_id! - let frame_pointer = self.get_or_create_frame_pointer(); + match self.stack_frame_pointer { + None => { + // This is the first stack-memory variable in the function + // That means we can reuse it as the stack frame pointer, + // and it will get initialised at the start of the function + self.stack_frame_pointer = Some(local_id); + } - // initialise the local with the appropriate address - // TODO: skip this the first time, no point generating code to add zero offset! - self.instructions.extend([ - GetLocal(frame_pointer.0), - I32Const(offset), - I32Add, - SetLocal(local_index), - ]); + Some(frame_ptr_id) => { + // This local points to the base of a struct, at an offset from the stack frame pointer + // Having one local per variable means params and locals work the same way in code gen. + // (alternatively we could use one frame pointer + offset for all struct variables) + self.instructions.extend([ + GetLocal(frame_ptr_id.0), + I32Const(offset), + I32Add, + SetLocal(local_id.0), + ]); + } + }; SymbolStorage::VarStackMemory { local_id, @@ -239,19 +246,6 @@ impl<'a> WasmBackend<'a> { storage } - fn get_or_create_frame_pointer(&mut self) -> LocalId { - match self.stack_frame_pointer { - Some(local_id) => local_id, - None => { - let local_index = (self.arg_types.len() + self.locals.len()) as u32; - let local_id = LocalId(local_index); - self.stack_frame_pointer = Some(local_id); - self.locals.push(Local::new(1, ValueType::I32)); - local_id - } - } - } - fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { self.symbol_storage_map.get(sym).ok_or_else(|| { format!( From 7ac7e16f60a6caddcf870ec89b07c10729f2561b Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Sep 2021 15:49:54 +0100 Subject: [PATCH 61/96] Ensure stack frame is always popped when procedure returns from inside a branch --- compiler/gen_wasm/src/backend.rs | 41 +++++++++++-------- compiler/gen_wasm/src/lib.rs | 6 +-- .../tests/helpers/wasm32_test_result.rs | 8 ++-- compiler/gen_wasm/tests/wasm_records.rs | 18 ++++++++ 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index b6fd083d57..bba7d5271f 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - allocate_stack_frame, copy_memory, free_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + push_stack_frame, copy_memory, pop_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, PTR_TYPE, }; @@ -93,7 +93,7 @@ impl<'a> WasmBackend<'a> { } pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result { - let signature_builder = self.build_signature(&proc); + let signature_builder = self.start_proc(&proc); self.build_stmt(&proc.body, &proc.ret_layout)?; @@ -106,14 +106,17 @@ impl<'a> WasmBackend<'a> { Ok(function_index) } - fn build_signature(&mut self, proc: &Proc<'a>) -> SignatureBuilder { + fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder { let ret_layout = WasmLayout::new(&proc.ret_layout); let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout { self.arg_types.push(PTR_TYPE); + self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any) builder::signature() } else { - builder::signature().with_result(ret_layout.value_type()) + let ret_type = ret_layout.value_type(); + self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any) + builder::signature().with_result(ret_type) }; for (layout, symbol) in proc.args { @@ -124,10 +127,12 @@ impl<'a> WasmBackend<'a> { } fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition { + self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any) + let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10); if self.stack_memory > 0 { - allocate_stack_frame( + push_stack_frame( &mut final_instructions, self.stack_memory, self.stack_frame_pointer.unwrap(), @@ -137,13 +142,13 @@ impl<'a> WasmBackend<'a> { final_instructions.extend(self.instructions.drain(0..)); if self.stack_memory > 0 { - free_stack_frame( + pop_stack_frame( &mut final_instructions, self.stack_memory, self.stack_frame_pointer.unwrap(), ); } - final_instructions.push(Instruction::End); + final_instructions.push(End); builder::function() .with_signature(signature_builder.build_sig()) @@ -275,12 +280,9 @@ impl<'a> WasmBackend<'a> { self.instructions.push(Loop(BlockType::Value(value_type))); } - fn start_block(&mut self) { + fn start_block(&mut self, block_type: BlockType) { self.block_depth += 1; - - // Our blocks always end with a `return` or `br`, - // so they never leave extra values on the stack - self.instructions.push(Block(BlockType::NoResult)); + self.instructions.push(Block(block_type)); } fn end_block(&mut self) { @@ -308,7 +310,7 @@ impl<'a> WasmBackend<'a> { self.symbol_storage_map.insert(*let_sym, storage); } self.build_expr(let_sym, expr, layout)?; - self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack + self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop) Ok(()) } @@ -319,7 +321,12 @@ impl<'a> WasmBackend<'a> { .local_id(); self.build_expr(sym, expr, layout)?; - self.instructions.push(SetLocal(local_id.0)); + + // If this local is shared with the stack frame pointer, it's already assigned + match self.stack_frame_pointer { + Some(sfp) if sfp == local_id => {} + _ => self.instructions.push(SetLocal(local_id.0)) + } self.build_stmt(following, ret_layout)?; Ok(()) @@ -351,7 +358,7 @@ impl<'a> WasmBackend<'a> { | VarPrimitive { local_id, .. } | VarHeapMemory { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); - self.instructions.push(Return); // TODO: branch instead of return so we can clean up stack + self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } } @@ -371,7 +378,7 @@ impl<'a> WasmBackend<'a> { // create (number_of_branches - 1) new blocks. for _ in 0..branches.len() { - self.start_block() + self.start_block(BlockType::NoResult) } // the LocalId of the symbol that we match on @@ -422,7 +429,7 @@ impl<'a> WasmBackend<'a> { jp_parameter_local_ids.push(local_id); } - self.start_block(); + self.start_block(BlockType::NoResult); self.joinpoint_label_map .insert(*id, (self.block_depth, jp_parameter_local_ids)); diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index d5d680e283..eac77a4795 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -26,7 +26,7 @@ pub const ALIGN_8: u32 = 3; pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const STACK_ALIGNMENT_BYTES: i32 = 16; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct LocalId(pub u32); pub struct Env<'a> { @@ -163,7 +163,7 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 { aligned } -pub fn allocate_stack_frame( +pub fn push_stack_frame( instructions: &mut Vec, size: i32, local_frame_pointer: LocalId, @@ -178,7 +178,7 @@ pub fn allocate_stack_frame( ]); } -pub fn free_stack_frame( +pub fn pop_stack_frame( instructions: &mut Vec, size: i32, local_frame_pointer: LocalId, diff --git a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs index 7e73d5727e..5cf709a597 100644 --- a/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs +++ b/compiler/gen_wasm/tests/helpers/wasm32_test_result.rs @@ -46,7 +46,7 @@ macro_rules! build_wrapper_body_primitive { fn build_wrapper_body(main_function_index: u32) -> Vec { let size: i32 = 8; let mut instructions = Vec::with_capacity(16); - allocate_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); + push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.extend([ // load result address to prepare for the store instruction later GetLocal(STACK_POINTER_LOCAL_ID), @@ -60,7 +60,7 @@ macro_rules! build_wrapper_body_primitive { // Return the result pointer GetLocal(STACK_POINTER_LOCAL_ID), ]); - free_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); + pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID)); instructions.push(End); instructions } @@ -77,7 +77,7 @@ macro_rules! wasm_test_result_primitive { fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec { let mut instructions = Vec::with_capacity(16); - allocate_stack_frame( + push_stack_frame( &mut instructions, size as i32, LocalId(STACK_POINTER_LOCAL_ID), @@ -92,7 +92,7 @@ fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec // Return the result address GetLocal(STACK_POINTER_LOCAL_ID), ]); - free_stack_frame( + pop_stack_frame( &mut instructions, size as i32, LocalId(STACK_POINTER_LOCAL_ID), diff --git a/compiler/gen_wasm/tests/wasm_records.rs b/compiler/gen_wasm/tests/wasm_records.rs index 5ae26092ef..884e92c7db 100644 --- a/compiler/gen_wasm/tests/wasm_records.rs +++ b/compiler/gen_wasm/tests/wasm_records.rs @@ -873,6 +873,24 @@ mod wasm_records { // ); // } + #[test] + fn stack_memory_return_from_branch() { + // stack memory pointer should end up in the right place after returning from a branch + assert_evals_to!( + indoc!( + r#" + stackMemoryJunk = { x: 999, y: 111 } + if True then + { x: 123, y: 321 } + else + stackMemoryJunk + "# + ), + (123, 321), + (i64, i64) + ); + } + // #[test] // fn blue_and_present() { // assert_evals_to!( From 92085a0fcb5b88171086076aa9a4e92b5fa447cc Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 29 Sep 2021 19:47:16 +0100 Subject: [PATCH 62/96] README notes on reducing gets and sets --- compiler/gen_wasm/README.md | 43 +++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/compiler/gen_wasm/README.md b/compiler/gen_wasm/README.md index a3f22297b6..9681e350c3 100644 --- a/compiler/gen_wasm/README.md +++ b/compiler/gen_wasm/README.md @@ -99,29 +99,29 @@ The Mono IR contains two functions, `Num.add` and `main`, so we generate two cor (func (;1;) (result i64) ; declare function index 1 (main) with no parameters and an i64 result (local i64 i64 i64 i64) ; declare 4 local variables, all with type i64, one for each symbol in the Mono IR - i64.const 1 ; load constant of type i64 and value 1 stack=[1] - local.set 0 ; store top of stack to local0 stack=[] local0=1 - i64.const 2 ; load constant of type i64 and value 2 stack=[2] local0=1 - local.set 1 ; store top of stack to local1 stack=[] local0=1 local1=2 - local.get 0 ; load local0 to top of stack stack=[1] local0=1 local1=2 - local.get 1 ; load local1 to top of stack stack=[1,2] local0=1 local1=2 - call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[3] local0=1 local1=2 - local.set 2 ; store top of stack to local2 stack=[] local0=1 local1=2 local2=3 - i64.const 4 ; load constant of type i64 and value 4 stack=[4] local0=1 local1=2 local2=3 - local.set 3 ; store top of stack to local3 stack=[] local0=1 local1=2 local2=3 local3=4 - local.get 2 ; load local2 to top of stack stack=[3] local0=1 local1=2 local2=3 local3=4 - local.get 3 ; load local3 to top of stack stack=[3,4] local0=1 local1=2 local2=3 local3=4 - call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[7] local0=1 local1=2 local2=3 local3=4 + i64.const 1 ; stack=[1] + local.set 0 ; stack=[] local0=1 + i64.const 2 ; stack=[2] local0=1 + local.set 1 ; stack=[] local0=1 local1=2 + local.get 0 ; stack=[1] local0=1 local1=2 + local.get 1 ; stack=[1,2] local0=1 local1=2 + call 0 ; stack=[3] local0=1 local1=2 + local.set 2 ; stack=[] local0=1 local1=2 local2=3 + i64.const 4 ; stack=[4] local0=1 local1=2 local2=3 + local.set 3 ; stack=[] local0=1 local1=2 local2=3 local3=4 + local.get 2 ; stack=[3] local0=1 local1=2 local2=3 local3=4 + local.get 3 ; stack=[3,4] local0=1 local1=2 local2=3 local3=4 + call 0 ; stack=[7] local0=1 local1=2 local2=3 local3=4 return) ; return the value at the top of the stack ``` -If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away. The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). +If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away (which is all of them in this example!). The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). ``` $ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm ``` -The optimised functions have no local variables, and the code shrinks to about 60% of its original size. +The optimised functions have no local variables at all for this example. (Of course, this is an oversimplified toy example! It might not be so extreme in a real program.) ``` (func (;0;) (param i64 i64) (result i64) @@ -132,9 +132,20 @@ The optimised functions have no local variables, and the code shrinks to about 6 i64.const 1 i64.const 2 call 0 - i64.const 4) + i64.const 4 + call 0) ``` +### Reducing sets and gets + +It would be nice to find some cheap optimisation to reduce the number of `local.set` and `local.get` instructions. + +We don't need a `local` if the value we want is already at the top of the VM stack. In fact, for our example above, it just so happens that if we simply skip generating the `local.set` instructions, everything _does_ appear on the VM stack in the right order, which means we can skip the `local.get` too. It ends up being very close to the fully optimised version! I assume this is because the Mono IR within the function is in dependency order, but I'm not sure... + +Of course the trick is to do this reliably for more complex dependency graphs. I am investigating whether we can do it by optimistically assuming it's OK not to create a local, and then keeping track of which symbols are at which positions in the VM stack after every instruction. Then when we need to use a symbol we can first check if it's on the VM stack and only create a local if it's not. In cases where we _do_ need to create a local, we need to go back and insert a `local.set` instruction at an earlier point in the program. We can make this fast by waiting to do all of the insertions in one batch when we're finalising the procedure. + +For a while we thought it would be very helpful to reuse the same local for multiple symbols at different points in the program. And we already have similar code in the CPU backends for register allocation. But on further examination, it doesn't actually buy us much! In our example above, we would still have the same number of `local.set` and `local.get` instructions - they'd just be operating on two locals instead of four! That doesn't shrink much code. Only the declaration at the top of the function would shrink from `(local i64 i64 i64 i64)` to `(local i64 i64)`... and in fact that's only smaller in the text format, it's the same size in the binary format! So the `scan_ast` pass doesn't seem worthwhile for Wasm. + ## Memory WebAssembly programs have a "linear memory" for storing data, which is a block of memory assigned to it by the host. You can assign a min and max size to the memory, and the WebAssembly program can request 64kB pages from the host, just like a "normal" program would request pages from the OS. Addresses start at zero and go up to whatever the current size is. Zero is a perfectly normal address like any other, and dereferencing it is not a segfault. But addresses beyond the current memory size are out of bounds and dereferencing them will cause a panic. From 304e9c904f1c6e1f4d6469ab5d34d738a810cba8 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Thu, 30 Sep 2021 17:23:02 +0100 Subject: [PATCH 63/96] Formatting --- compiler/gen_wasm/src/backend.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index bba7d5271f..24de4a3576 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - push_stack_frame, copy_memory, pop_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, PTR_TYPE, }; @@ -325,7 +325,7 @@ impl<'a> WasmBackend<'a> { // If this local is shared with the stack frame pointer, it's already assigned match self.stack_frame_pointer { Some(sfp) if sfp == local_id => {} - _ => self.instructions.push(SetLocal(local_id.0)) + _ => self.instructions.push(SetLocal(local_id.0)), } self.build_stmt(following, ret_layout)?; From 08a3f0fb9ae29745953f69a80fa00bd42963b662 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 1 Oct 2021 13:06:32 -0500 Subject: [PATCH 64/96] Add 'a taste of roc' link to www --- www/public/index.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/public/index.html b/www/public/index.html index 7144f8368a..cccb3ab181 100644 --- a/www/public/index.html +++ b/www/public/index.html @@ -16,9 +16,10 @@

Roc's initial release is still under development, and this website is a placeholder until that release is ready.

In the meantime, if you'd like to learn more about Roc, here are some videos:

From f1ec5c30d02746208da38d53f0cb2c8aa9a077b8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 1 Oct 2021 20:33:00 +0200 Subject: [PATCH 65/96] make things compile --- cli/src/build.rs | 2 ++ cli/src/main.rs | 7 ++++--- compiler/build/src/link.rs | 2 +- compiler/build/src/program.rs | 2 -- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index 85fc609717..e62ddb7043 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -3,6 +3,7 @@ use roc_build::{ link::{link, rebuild_host, LinkType}, program, }; +#[cfg(feature = "llvm")] use roc_builtins::bitcode; use roc_can::builtins::builtin_defs_map; use roc_collections::all::MutMap; @@ -11,6 +12,7 @@ use roc_mono::ir::OptLevel; use std::path::PathBuf; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; +#[cfg(feature = "llvm")] use tempfile::Builder; fn report_timing(buf: &mut String, label: &str, duration: Duration) { diff --git a/cli/src/main.rs b/cli/src/main.rs index 739b341907..15a1de4b48 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -11,12 +11,13 @@ use std::path::{Path, PathBuf}; #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; -#[cfg(feature = "llvm")] -use roc_cli::build; use std::ffi::{OsStr, OsString}; +#[cfg(feature = "llvm")] +use roc_cli::build; + #[cfg(not(feature = "llvm"))] -fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result { +fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result { panic!("Building without LLVM is not currently supported."); } diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs index 2d3517ee60..d7a894838c 100644 --- a/compiler/build/src/link.rs +++ b/compiler/build/src/link.rs @@ -2,7 +2,7 @@ use crate::target::arch_str; #[cfg(feature = "llvm")] use libloading::{Error, Library}; use roc_builtins::bitcode; -#[cfg(feature = "llvm")] +// #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; use std::collections::HashMap; use std::env; diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 226bfaf8be..7d9803e4be 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -2,11 +2,9 @@ use roc_gen_llvm::llvm::build::module_from_builtins; #[cfg(feature = "llvm")] pub use roc_gen_llvm::llvm::build::FunctionIterator; -#[cfg(feature = "llvm")] use roc_load::file::MonomorphizedModule; #[cfg(feature = "llvm")] use roc_mono::ir::OptLevel; -#[cfg(feature = "llvm")] use std::path::{Path, PathBuf}; use std::time::Duration; From 4aa2452e018b0834f7904f589d7ef66861123985 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 13:04:10 +0100 Subject: [PATCH 66/96] gen_wasm: Change some compiler bugs error handling from Result to panic Result makes sense where I have something meaningful to say to the user like "X is not implemented yet". And also for public functions that may interface with other parts of the project like Backend. But for private functions internal to gen_wasm, it's just unhelpful to get a stack trace to where the Result is unwrapped! I want a stack trace to the root cause. I always end up temporarily rewriting Err("oops") to panic!("oops") and then waiting for it to recompile. This feels like a more balanced approach, using each technique where it makes sense. --- compiler/gen_wasm/src/backend.rs | 44 +++++++++++++++----------------- compiler/gen_wasm/src/lib.rs | 3 +-- compiler/gen_wasm/src/storage.rs | 15 +++++------ 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 24de4a3576..303fe38349 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -251,25 +251,24 @@ impl<'a> WasmBackend<'a> { storage } - fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { - self.symbol_storage_map.get(sym).ok_or_else(|| { - format!( + fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage { + self.symbol_storage_map.get(sym).unwrap_or_else(|| { + panic!( "Symbol {:?} not found in function scope:\n{:?}", sym, self.symbol_storage_map ) }) } - fn local_id_from_symbol(&self, sym: &Symbol) -> Result { - let storage = self.get_symbol_storage(sym)?; - Ok(storage.local_id()) + fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { + let storage = self.get_symbol_storage(sym); + storage.local_id() } - fn load_symbol(&mut self, sym: &Symbol) -> Result<(), String> { - let storage = self.get_symbol_storage(sym)?; + fn load_symbol(&mut self, sym: &Symbol) { + let storage = self.get_symbol_storage(sym); let index: u32 = storage.local_id().0; self.instructions.push(GetLocal(index)); - Ok(()) } /// start a loop that leaves a value on the stack @@ -351,7 +350,7 @@ impl<'a> WasmBackend<'a> { } => { let from = *local_id; let to = LocalId(0); - copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0)?; + copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0); } ParamPrimitive { local_id, .. } @@ -382,7 +381,7 @@ impl<'a> WasmBackend<'a> { } // the LocalId of the symbol that we match on - let matched_on = self.local_id_from_symbol(cond_symbol)?; + let matched_on = self.local_id_from_symbol(cond_symbol); // then, we jump whenever the value under scrutiny is equal to the value of a branch for (i, (value, _, _)) in branches.iter().enumerate() { @@ -455,7 +454,7 @@ impl<'a> WasmBackend<'a> { // put the arguments on the stack for (symbol, local_id) in arguments.iter().zip(locals.iter()) { - let argument = self.local_id_from_symbol(symbol)?; + let argument = self.local_id_from_symbol(symbol); self.instructions.push(GetLocal(argument.0)); self.instructions.push(SetLocal(local_id.0)); } @@ -485,7 +484,7 @@ impl<'a> WasmBackend<'a> { }) => match call_type { CallType::ByName { name: func_sym, .. } => { for arg in *arguments { - self.load_symbol(arg)?; + self.load_symbol(arg); } let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( "Cannot find function {:?} called from {:?}", @@ -545,7 +544,7 @@ impl<'a> WasmBackend<'a> { layout: &Layout<'a>, fields: &'a [Symbol], ) -> Result<(), String> { - let storage = self.get_symbol_storage(sym)?.to_owned(); + let storage = self.get_symbol_storage(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { match storage { @@ -558,7 +557,7 @@ impl<'a> WasmBackend<'a> { local_id, relative_offset, field, - )?; + ); } } else { return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); @@ -573,8 +572,8 @@ impl<'a> WasmBackend<'a> { } } else { // Struct expression but not Struct layout => single element. Copy it. - let field_storage = self.get_symbol_storage(&fields[0])?.to_owned(); - self.copy_storage(&storage, &field_storage)?; + let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); + self.copy_storage(&storage, &field_storage); } Ok(()) } @@ -584,12 +583,12 @@ impl<'a> WasmBackend<'a> { to_ptr: LocalId, to_offset: u32, from_symbol: &Symbol, - ) -> Result { - let from_storage = self.get_symbol_storage(from_symbol)?.to_owned(); + ) -> u32 { + let from_storage = self.get_symbol_storage(from_symbol).to_owned(); from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset) } - fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) -> Result<(), String> { + fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) { let has_stack_memory = to.has_stack_memory(); debug_assert!(from.has_stack_memory() == has_stack_memory); @@ -597,7 +596,6 @@ impl<'a> WasmBackend<'a> { debug_assert!(from.value_type() == to.value_type()); self.instructions.push(GetLocal(from.local_id().0)); self.instructions.push(SetLocal(to.local_id().0)); - Ok(()) } else { let (size, alignment_bytes) = from.stack_size_and_alignment(); copy_memory( @@ -607,7 +605,7 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, 0, - ) + ); } } @@ -618,7 +616,7 @@ impl<'a> WasmBackend<'a> { return_layout: &Layout<'a>, ) -> Result<(), String> { for arg in args { - self.load_symbol(arg)?; + self.load_symbol(arg); } let wasm_layout = WasmLayout::new(return_layout); self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index eac77a4795..32ff3e83ad 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -128,7 +128,7 @@ fn copy_memory( size: u32, alignment_bytes: u32, offset: u32, -) -> Result<(), String> { +) { let alignment_flag = encode_alignment(alignment_bytes); let mut current_offset = offset; while size - current_offset >= 8 { @@ -152,7 +152,6 @@ fn copy_memory( instructions.push(I32Store8(alignment_flag, current_offset)); current_offset += 1; } - Ok(()) } /// Round up to alignment_bytes (assumed to be a power of 2) diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 095998a0af..51c14203b1 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -82,7 +82,7 @@ impl SymbolStorage { instructions: &mut Vec, to_pointer: LocalId, to_offset: u32, - ) -> Result { + ) -> u32 { match self { Self::ParamPrimitive { local_id, @@ -104,16 +104,13 @@ impl SymbolStorage { (ValueType::F32, 4) => F32Store(ALIGN_4, to_offset), (ValueType::F64, 8) => F64Store(ALIGN_8, to_offset), _ => { - return Err(format!( - "Cannot store {:?} with alignment of {:?}", - value_type, size - )); + panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; instructions.push(GetLocal(to_pointer.0)); instructions.push(GetLocal(local_id.0)); instructions.push(store_instruction); - Ok(*size) + *size } Self::ParamStackMemory { @@ -134,15 +131,15 @@ impl SymbolStorage { *size, *alignment_bytes, to_offset, - )?; - Ok(*size) + ); + *size } Self::VarHeapMemory { local_id, .. } => { instructions.push(GetLocal(to_pointer.0)); instructions.push(GetLocal(local_id.0)); instructions.push(I32Store(ALIGN_4, to_offset)); - Ok(4) + 4 } } } From ffa6ff0a622621e2f84236eae0f7067ebdf2ff2a Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 15:12:25 +0100 Subject: [PATCH 67/96] gen_wasm: Get rid of individual locals for values in stack memory All values in stack memory can share the stack frame pointer, with different offsets. This avoids having to generate initialisation code for individual pointers we used to have. It should also make things more efficient when the runtime compiles Wasm to machine code. It can just assign the stack frame pointer to a single CPU register and leave it there. The original idea was to make params and local variables work the same. (A struct param will be passed as a pointer to caller stack memory.) But now I don't think that's worth it. Some match expressions get more awkward this way but we might be able to refactor that to be nicer. --- compiler/gen_wasm/src/backend.rs | 126 +++++++++++++++++-------------- compiler/gen_wasm/src/lib.rs | 56 +++++++------- compiler/gen_wasm/src/storage.rs | 49 ++++++++---- 3 files changed, 134 insertions(+), 97 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 303fe38349..1f028660d4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,7 +13,7 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::SymbolStorage; use crate::{ - copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, PTR_SIZE, + pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, MemoryCopy, PTR_SIZE, PTR_TYPE, }; @@ -165,7 +165,7 @@ impl<'a> WasmBackend<'a> { symbol: Symbol, kind: LocalKind, ) -> SymbolStorage { - let local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); + let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); let storage = match kind { LocalKind::Parameter => { @@ -173,13 +173,13 @@ impl<'a> WasmBackend<'a> { self.arg_types.push(wasm_layout.value_type()); match wasm_layout { WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive { - local_id, + local_id: next_local_id, value_type, size, }, WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive { - local_id, + local_id: next_local_id, value_type: PTR_TYPE, size: PTR_SIZE, }, @@ -188,7 +188,7 @@ impl<'a> WasmBackend<'a> { size, alignment_bytes, } => SymbolStorage::ParamStackMemory { - local_id, + local_id: next_local_id, size, alignment_bytes, }, @@ -199,12 +199,14 @@ impl<'a> WasmBackend<'a> { match wasm_layout { WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive { - local_id, + local_id: next_local_id, value_type, size, }, - WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { local_id }, + WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { + local_id: next_local_id, + }, WasmLayout::StackMemory { size, @@ -216,27 +218,12 @@ impl<'a> WasmBackend<'a> { match self.stack_frame_pointer { None => { - // This is the first stack-memory variable in the function - // That means we can reuse it as the stack frame pointer, - // and it will get initialised at the start of the function - self.stack_frame_pointer = Some(local_id); - } - - Some(frame_ptr_id) => { - // This local points to the base of a struct, at an offset from the stack frame pointer - // Having one local per variable means params and locals work the same way in code gen. - // (alternatively we could use one frame pointer + offset for all struct variables) - self.instructions.extend([ - GetLocal(frame_ptr_id.0), - I32Const(offset), - I32Add, - SetLocal(local_id.0), - ]); + self.stack_frame_pointer = Some(next_local_id); } + Some(_) => {} }; SymbolStorage::VarStackMemory { - local_id, size, offset: offset as u32, alignment_bytes, @@ -262,12 +249,12 @@ impl<'a> WasmBackend<'a> { fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { let storage = self.get_symbol_storage(sym); - storage.local_id() + storage.local_id(self.stack_frame_pointer) } fn load_symbol(&mut self, sym: &Symbol) { let storage = self.get_symbol_storage(sym); - let index: u32 = storage.local_id().0; + let index: u32 = storage.local_id(self.stack_frame_pointer).0; self.instructions.push(GetLocal(index)); } @@ -317,7 +304,7 @@ impl<'a> WasmBackend<'a> { let wasm_layout = WasmLayout::new(layout); let local_id = self .insert_local(wasm_layout, *sym, LocalKind::Variable) - .local_id(); + .local_id(self.stack_frame_pointer); self.build_expr(sym, expr, layout)?; @@ -338,19 +325,35 @@ impl<'a> WasmBackend<'a> { match storage { VarStackMemory { - local_id, size, alignment_bytes, - .. + offset, + } => { + let copy = MemoryCopy { + from_ptr: self.stack_frame_pointer.unwrap(), + from_offset: *offset, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(&mut self.instructions); } - | ParamStackMemory { + + ParamStackMemory { local_id, size, alignment_bytes, } => { - let from = *local_id; - let to = LocalId(0); - copy_memory(&mut self.instructions, from, to, *size, *alignment_bytes, 0); + let copy = MemoryCopy { + from_ptr: *local_id, + from_offset: 0, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(&mut self.instructions); } ParamPrimitive { local_id, .. } @@ -423,7 +426,7 @@ impl<'a> WasmBackend<'a> { let wasm_layout = WasmLayout::new(¶meter.layout); let local_id = self .insert_local(wasm_layout, parameter.symbol, LocalKind::Variable) - .local_id(); + .local_id(self.stack_frame_pointer); jp_parameter_local_ids.push(local_id); } @@ -547,28 +550,27 @@ impl<'a> WasmBackend<'a> { let storage = self.get_symbol_storage(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { - match storage { - SymbolStorage::VarStackMemory { local_id, size, .. } - | SymbolStorage::ParamStackMemory { local_id, size, .. } => { - if size > 0 { - let mut relative_offset = 0; - for (field, _) in fields.iter().zip(field_layouts.iter()) { - relative_offset += self.copy_symbol_to_pointer_at_offset( - local_id, - relative_offset, - field, - ); - } - } else { - return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); - } + let (local_id, size) = match storage { + SymbolStorage::VarStackMemory { size, .. } => { + (self.stack_frame_pointer.unwrap(), size) } + SymbolStorage::ParamStackMemory { local_id, size, .. } => (local_id, size), _ => { return Err(format!( "Cannot create struct {:?} with storage {:?}", sym, storage )); } + }; + + if size > 0 { + let mut relative_offset = 0; + for (field, _) in fields.iter().zip(field_layouts.iter()) { + relative_offset += + self.copy_symbol_to_pointer_at_offset(local_id, relative_offset, field); + } + } else { + return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); } } else { // Struct expression but not Struct layout => single element. Copy it. @@ -585,7 +587,12 @@ impl<'a> WasmBackend<'a> { from_symbol: &Symbol, ) -> u32 { let from_storage = self.get_symbol_storage(from_symbol).to_owned(); - from_storage.copy_to_memory(&mut self.instructions, to_ptr, to_offset) + from_storage.copy_to_memory( + &mut self.instructions, + to_ptr, + to_offset, + self.stack_frame_pointer, + ) } fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) { @@ -594,18 +601,21 @@ impl<'a> WasmBackend<'a> { if !has_stack_memory { debug_assert!(from.value_type() == to.value_type()); - self.instructions.push(GetLocal(from.local_id().0)); - self.instructions.push(SetLocal(to.local_id().0)); + self.instructions + .push(GetLocal(from.local_id(self.stack_frame_pointer).0)); + self.instructions + .push(SetLocal(to.local_id(self.stack_frame_pointer).0)); } else { let (size, alignment_bytes) = from.stack_size_and_alignment(); - copy_memory( - &mut self.instructions, - from.local_id(), - to.local_id(), + let copy = MemoryCopy { + from_ptr: from.local_id(self.stack_frame_pointer), + to_ptr: to.local_id(self.stack_frame_pointer), + from_offset: from.address_offset().unwrap(), + to_offset: to.address_offset().unwrap(), size, alignment_bytes, - 0, - ); + }; + copy.generate(&mut self.instructions); } } diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 32ff3e83ad..394b59b840 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -121,36 +121,40 @@ fn encode_alignment(bytes: u32) -> u32 { } } -fn copy_memory( - instructions: &mut Vec, +pub struct MemoryCopy { from_ptr: LocalId, + from_offset: u32, to_ptr: LocalId, + to_offset: u32, size: u32, alignment_bytes: u32, - offset: u32, -) { - let alignment_flag = encode_alignment(alignment_bytes); - let mut current_offset = offset; - while size - current_offset >= 8 { - instructions.push(GetLocal(to_ptr.0)); - instructions.push(GetLocal(from_ptr.0)); - instructions.push(I64Load(alignment_flag, current_offset)); - instructions.push(I64Store(alignment_flag, current_offset)); - current_offset += 8; - } - if size - current_offset >= 4 { - instructions.push(GetLocal(to_ptr.0)); - instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load(alignment_flag, current_offset)); - instructions.push(I32Store(alignment_flag, current_offset)); - current_offset += 4; - } - while size - current_offset > 0 { - instructions.push(GetLocal(to_ptr.0)); - instructions.push(GetLocal(from_ptr.0)); - instructions.push(I32Load8U(alignment_flag, current_offset)); - instructions.push(I32Store8(alignment_flag, current_offset)); - current_offset += 1; +} + +impl MemoryCopy { + pub fn generate(&self, instructions: &mut Vec) { + let alignment_flag = encode_alignment(self.alignment_bytes); + let mut i = 0; + while self.size - i >= 8 { + instructions.push(GetLocal(self.to_ptr.0)); + instructions.push(GetLocal(self.from_ptr.0)); + instructions.push(I64Load(alignment_flag, i + self.from_offset)); + instructions.push(I64Store(alignment_flag, i + self.to_offset)); + i += 8; + } + if self.size - i >= 4 { + instructions.push(GetLocal(self.to_ptr.0)); + instructions.push(GetLocal(self.from_ptr.0)); + instructions.push(I32Load(alignment_flag, i + self.from_offset)); + instructions.push(I32Store(alignment_flag, i + self.to_offset)); + i += 4; + } + while self.size - i > 0 { + instructions.push(GetLocal(self.to_ptr.0)); + instructions.push(GetLocal(self.from_ptr.0)); + instructions.push(I32Load8U(alignment_flag, i + self.from_offset)); + instructions.push(I32Store8(alignment_flag, i + self.to_offset)); + i += 1; + } } } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 51c14203b1..59d728db72 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,4 +1,4 @@ -use crate::{copy_memory, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use crate::{LocalId, MemoryCopy, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] @@ -14,7 +14,6 @@ pub enum SymbolStorage { size: u32, }, VarStackMemory { - local_id: LocalId, size: u32, offset: u32, alignment_bytes: u32, @@ -30,12 +29,12 @@ pub enum SymbolStorage { } impl SymbolStorage { - pub fn local_id(&self) -> LocalId { + pub fn local_id(&self, stack_frame_pointer: Option) -> LocalId { match self { Self::ParamPrimitive { local_id, .. } => *local_id, Self::ParamStackMemory { local_id, .. } => *local_id, Self::VarPrimitive { local_id, .. } => *local_id, - Self::VarStackMemory { local_id, .. } => *local_id, + Self::VarStackMemory { .. } => stack_frame_pointer.unwrap(), Self::VarHeapMemory { local_id, .. } => *local_id, } } @@ -60,6 +59,16 @@ impl SymbolStorage { } } + pub fn address_offset(&self) -> Option { + match self { + Self::ParamStackMemory { .. } => Some(0), + Self::VarStackMemory { offset, .. } => Some(*offset), + Self::ParamPrimitive { .. } => None, + Self::VarPrimitive { .. } => None, + Self::VarHeapMemory { .. } => None, + } + } + pub fn stack_size_and_alignment(&self) -> (u32, u32) { match self { Self::VarStackMemory { @@ -82,6 +91,7 @@ impl SymbolStorage { instructions: &mut Vec, to_pointer: LocalId, to_offset: u32, + stack_frame_pointer: Option, ) -> u32 { match self { Self::ParamPrimitive { @@ -117,21 +127,34 @@ impl SymbolStorage { local_id, size, alignment_bytes, + } => { + let copy = MemoryCopy { + from_ptr: *local_id, + from_offset: 0, + to_ptr: to_pointer, + to_offset, + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(instructions); + *size } - | Self::VarStackMemory { - local_id, + + Self::VarStackMemory { size, alignment_bytes, + offset, .. } => { - copy_memory( - instructions, - *local_id, - to_pointer, - *size, - *alignment_bytes, + let copy = MemoryCopy { + from_ptr: stack_frame_pointer.unwrap(), + from_offset: *offset, + to_ptr: to_pointer, to_offset, - ); + size: *size, + alignment_bytes: *alignment_bytes, + }; + copy.generate(instructions); *size } From 6aed70978d51d8127b95fe2f85bff03443c94d9c Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 16:15:06 +0100 Subject: [PATCH 68/96] Refactor SymbolStorage into two enums instead of one --- compiler/gen_wasm/src/backend.rs | 119 ++++++++++++------------------- compiler/gen_wasm/src/storage.rs | 108 ++++++++-------------------- 2 files changed, 78 insertions(+), 149 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 1f028660d4..b9c6cd6900 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -11,7 +11,7 @@ use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; -use crate::storage::SymbolStorage; +use crate::storage::{StackMemoryLocation, SymbolStorage}; use crate::{ pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, MemoryCopy, PTR_SIZE, PTR_TYPE, @@ -167,68 +167,55 @@ impl<'a> WasmBackend<'a> { ) -> SymbolStorage { let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); - let storage = match kind { + match kind { LocalKind::Parameter => { - // Already stack-allocated by the caller if needed. self.arg_types.push(wasm_layout.value_type()); - match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => SymbolStorage::ParamPrimitive { - local_id: next_local_id, - value_type, - size, - }, - - WasmLayout::HeapMemory => SymbolStorage::ParamPrimitive { - local_id: next_local_id, - value_type: PTR_TYPE, - size: PTR_SIZE, - }, - - WasmLayout::StackMemory { - size, - alignment_bytes, - } => SymbolStorage::ParamStackMemory { - local_id: next_local_id, - size, - alignment_bytes, - }, - } } LocalKind::Variable => { self.locals.push(Local::new(1, wasm_layout.value_type())); + } + } - match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => SymbolStorage::VarPrimitive { - local_id: next_local_id, - value_type, - size, - }, + let storage = match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => SymbolStorage::Local { + local_id: next_local_id, + value_type, + size, + }, - WasmLayout::HeapMemory => SymbolStorage::VarHeapMemory { - local_id: next_local_id, - }, + WasmLayout::HeapMemory => SymbolStorage::Local { + local_id: next_local_id, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, - WasmLayout::StackMemory { - size, - alignment_bytes, - } => { + WasmLayout::StackMemory { + size, + alignment_bytes, + } => { + let location = match kind { + LocalKind::Parameter => StackMemoryLocation::ExternalPointer(next_local_id), + + LocalKind::Variable => { let offset = round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + self.stack_memory = offset + size as i32; match self.stack_frame_pointer { + Some(_) => {} None => { self.stack_frame_pointer = Some(next_local_id); } - Some(_) => {} }; - SymbolStorage::VarStackMemory { - size, - offset: offset as u32, - alignment_bytes, - } + StackMemoryLocation::InternalOffset(offset as u32) } + }; + SymbolStorage::StackMemory { + location, + size, + alignment_bytes, } } }; @@ -288,8 +275,8 @@ impl<'a> WasmBackend<'a> { { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later - let storage = SymbolStorage::ParamStackMemory { - local_id: LocalId(0), + let storage = SymbolStorage::StackMemory { + location: StackMemoryLocation::ExternalPointer(LocalId(0)), size, alignment_bytes, }; @@ -324,14 +311,21 @@ impl<'a> WasmBackend<'a> { let storage = self.symbol_storage_map.get(sym).unwrap(); match storage { - VarStackMemory { + StackMemory { + location, size, alignment_bytes, - offset, } => { + let (from_ptr, from_offset) = match location { + StackMemoryLocation::ExternalPointer(local_id) => (*local_id, 0), + StackMemoryLocation::InternalOffset(offset) => { + (self.stack_frame_pointer.unwrap(), *offset) + } + }; + let copy = MemoryCopy { - from_ptr: self.stack_frame_pointer.unwrap(), - from_offset: *offset, + from_ptr, + from_offset, to_ptr: LocalId(0), to_offset: 0, size: *size, @@ -340,25 +334,7 @@ impl<'a> WasmBackend<'a> { copy.generate(&mut self.instructions); } - ParamStackMemory { - local_id, - size, - alignment_bytes, - } => { - let copy = MemoryCopy { - from_ptr: *local_id, - from_offset: 0, - to_ptr: LocalId(0), - to_offset: 0, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(&mut self.instructions); - } - - ParamPrimitive { local_id, .. } - | VarPrimitive { local_id, .. } - | VarHeapMemory { local_id, .. } => { + Local { local_id, .. } => { self.instructions.push(GetLocal(local_id.0)); self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop) } @@ -551,10 +527,9 @@ impl<'a> WasmBackend<'a> { if let Layout::Struct(field_layouts) = layout { let (local_id, size) = match storage { - SymbolStorage::VarStackMemory { size, .. } => { - (self.stack_frame_pointer.unwrap(), size) + SymbolStorage::StackMemory { size, .. } => { + (storage.local_id(self.stack_frame_pointer), size) } - SymbolStorage::ParamStackMemory { local_id, size, .. } => (local_id, size), _ => { return Err(format!( "Cannot create struct {:?} with storage {:?}", diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 59d728db72..f1eb565097 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,82 +1,66 @@ use crate::{LocalId, MemoryCopy, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; +#[derive(Debug, Clone)] +pub enum StackMemoryLocation { + ExternalPointer(LocalId), + InternalOffset(u32), +} + #[derive(Debug, Clone)] pub enum SymbolStorage { - VarPrimitive { + Local { local_id: LocalId, value_type: ValueType, size: u32, }, - ParamPrimitive { - local_id: LocalId, - value_type: ValueType, - size: u32, - }, - VarStackMemory { - size: u32, - offset: u32, - alignment_bytes: u32, - }, - ParamStackMemory { - local_id: LocalId, + StackMemory { + location: StackMemoryLocation, size: u32, alignment_bytes: u32, }, - VarHeapMemory { - local_id: LocalId, - }, } impl SymbolStorage { pub fn local_id(&self, stack_frame_pointer: Option) -> LocalId { + use StackMemoryLocation::*; match self { - Self::ParamPrimitive { local_id, .. } => *local_id, - Self::ParamStackMemory { local_id, .. } => *local_id, - Self::VarPrimitive { local_id, .. } => *local_id, - Self::VarStackMemory { .. } => stack_frame_pointer.unwrap(), - Self::VarHeapMemory { local_id, .. } => *local_id, + Self::Local { local_id, .. } => *local_id, + Self::StackMemory { location, .. } => match *location { + ExternalPointer(local_id) => local_id, + InternalOffset(_) => stack_frame_pointer.unwrap(), + }, } } pub fn value_type(&self) -> ValueType { match self { - Self::ParamPrimitive { value_type, .. } => *value_type, - Self::VarPrimitive { value_type, .. } => *value_type, - Self::ParamStackMemory { .. } => ValueType::I32, - Self::VarStackMemory { .. } => ValueType::I32, - Self::VarHeapMemory { .. } => ValueType::I32, + Self::Local { value_type, .. } => *value_type, + Self::StackMemory { .. } => ValueType::I32, } } pub fn has_stack_memory(&self) -> bool { match self { - Self::ParamStackMemory { .. } => true, - Self::VarStackMemory { .. } => true, - Self::ParamPrimitive { .. } => false, - Self::VarPrimitive { .. } => false, - Self::VarHeapMemory { .. } => false, + Self::Local { .. } => false, + Self::StackMemory { .. } => true, } } pub fn address_offset(&self) -> Option { + use StackMemoryLocation::*; match self { - Self::ParamStackMemory { .. } => Some(0), - Self::VarStackMemory { offset, .. } => Some(*offset), - Self::ParamPrimitive { .. } => None, - Self::VarPrimitive { .. } => None, - Self::VarHeapMemory { .. } => None, + Self::Local { .. } => None, + Self::StackMemory { location, .. } => match *location { + ExternalPointer(_) => Some(0), + InternalOffset(offset) => Some(offset), + }, } } pub fn stack_size_and_alignment(&self) -> (u32, u32) { match self { - Self::VarStackMemory { - size, - alignment_bytes, - .. - } - | Self::ParamStackMemory { + Self::StackMemory { size, alignment_bytes, .. @@ -94,13 +78,7 @@ impl SymbolStorage { stack_frame_pointer: Option, ) -> u32 { match self { - Self::ParamPrimitive { - local_id, - value_type, - size, - .. - } - | Self::VarPrimitive { + Self::Local { local_id, value_type, size, @@ -123,13 +101,14 @@ impl SymbolStorage { *size } - Self::ParamStackMemory { - local_id, + Self::StackMemory { size, alignment_bytes, + .. } => { + let local_id = self.local_id(stack_frame_pointer); let copy = MemoryCopy { - from_ptr: *local_id, + from_ptr: local_id, from_offset: 0, to_ptr: to_pointer, to_offset, @@ -139,31 +118,6 @@ impl SymbolStorage { copy.generate(instructions); *size } - - Self::VarStackMemory { - size, - alignment_bytes, - offset, - .. - } => { - let copy = MemoryCopy { - from_ptr: stack_frame_pointer.unwrap(), - from_offset: *offset, - to_ptr: to_pointer, - to_offset, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(instructions); - *size - } - - Self::VarHeapMemory { local_id, .. } => { - instructions.push(GetLocal(to_pointer.0)); - instructions.push(GetLocal(local_id.0)); - instructions.push(I32Store(ALIGN_4, to_offset)); - 4 - } } } } From 2fe431f1b86f8ce1041a825e54d86f161e8133da Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 2 Oct 2021 22:45:31 +0200 Subject: [PATCH 69/96] update morphic (can solve quicksort now) --- Cargo.lock | 1 + vendor/morphic_lib/Cargo.toml | 5 +- vendor/morphic_lib/src/analyze.rs | 1770 ++++++++++++++++++++ vendor/morphic_lib/src/api.rs | 81 +- vendor/morphic_lib/src/ir.rs | 114 +- vendor/morphic_lib/src/lib.rs | 1 + vendor/morphic_lib/src/preprocess.rs | 41 +- vendor/morphic_lib/src/type_cache.rs | 2 + vendor/morphic_lib/src/util/blocks.rs | 23 + vendor/morphic_lib/src/util/flat_slices.rs | 8 +- vendor/morphic_lib/src/util/get2_mut.rs | 16 + vendor/morphic_lib/src/util/id_bi_map.rs | 4 + vendor/morphic_lib/src/util/id_vec.rs | 12 + vendor/morphic_lib/src/util/mod.rs | 2 + vendor/morphic_lib/src/util/norm_pair.rs | 33 + vendor/morphic_lib/tests/recursive.rs | 5 +- vendor/morphic_lib/tests/structures.rs | 73 + 17 files changed, 2117 insertions(+), 74 deletions(-) create mode 100644 vendor/morphic_lib/src/analyze.rs create mode 100644 vendor/morphic_lib/src/util/get2_mut.rs create mode 100644 vendor/morphic_lib/src/util/norm_pair.rs create mode 100644 vendor/morphic_lib/tests/structures.rs diff --git a/Cargo.lock b/Cargo.lock index e8289983d3..ec0031bb77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2279,6 +2279,7 @@ dependencies = [ "sha2", "smallvec", "thiserror", + "typed-arena", ] [[package]] diff --git a/vendor/morphic_lib/Cargo.toml b/vendor/morphic_lib/Cargo.toml index 5406cf3647..594ad4c408 100644 --- a/vendor/morphic_lib/Cargo.toml +++ b/vendor/morphic_lib/Cargo.toml @@ -5,6 +5,7 @@ authors = ["William Brandon", "Wilson Berkow", "Frank Dai", "Benjamin Driscoll"] edition = "2018" [dependencies] -thiserror = "1.0.24" +thiserror = "1.0" sha2 = "0.9.4" -smallvec = "1.6.1" +smallvec = "1.6" +typed-arena = "2.0" diff --git a/vendor/morphic_lib/src/analyze.rs b/vendor/morphic_lib/src/analyze.rs new file mode 100644 index 0000000000..fbb69785ef --- /dev/null +++ b/vendor/morphic_lib/src/analyze.rs @@ -0,0 +1,1770 @@ +use smallvec::SmallVec; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::convert::TryInto; +use typed_arena::Arena; + +use crate::api; +use crate::ir; +use crate::name_cache::{EntryPointId, FuncId}; +use crate::type_cache::{TypeCache, TypeData, TypeId}; +use crate::util::flat_slices::FlatSlices; +use crate::util::id_type::Count; +use crate::util::id_vec::IdVec; +use crate::util::norm_pair::NormPair; +use crate::util::op_graph; +use crate::util::replace_none::replace_none; +use crate::util::strongly_connected::{strongly_connected, SccKind}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +struct SubSlots { + end_indices: SmallVec<[u32; 10]>, +} + +impl SubSlots { + fn from_slot_counts(slot_counts: impl Iterator) -> Self { + let mut total = 0; + let end_indices = slot_counts + .map(|count| { + total += count; + total + }) + .collect(); + Self { end_indices } + } + + fn slot_count(&self) -> u32 { + self.end_indices.last().cloned().unwrap_or(0) + } + + /// Returns bounds `a, b` for a range of slot indices `a..b` + fn sub_slots(&self, index: u32) -> (u32, u32) { + let start = if index == 0 { + 0 + } else { + self.end_indices[index as usize - 1] + }; + let end = self.end_indices[index as usize]; + (start, end) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum TypeSlots { + Named, + Tuple { field_slots: SubSlots }, + Union { variant_slots: SubSlots }, + HeapCell, + Bag { item_slots: u32 }, +} + +impl TypeSlots { + fn slot_count(&self) -> u32 { + match self { + TypeSlots::Named => 1, + TypeSlots::Tuple { field_slots } => field_slots.slot_count(), + TypeSlots::Union { variant_slots } => variant_slots.slot_count(), + TypeSlots::HeapCell => 1, + TypeSlots::Bag { item_slots } => *item_slots, + } + } +} + +#[derive(Clone, Debug)] +struct SlotCache { + type_cache: TypeCache, + slots: IdVec, +} + +impl SlotCache { + fn new(type_cache: TypeCache) -> Self { + let mut slots: IdVec<_, TypeSlots> = IdVec::new(); + // NOTE: This only works because 'type_cache.types' is guaranteed to assign ids in + // topological order. + for (id, type_) in type_cache.types.iter() { + let this_slots = match type_ { + TypeData::Named { named: _ } => TypeSlots::Named, + TypeData::Tuple { fields } => { + let field_slots = SubSlots::from_slot_counts( + fields.iter().map(|field| slots[field].slot_count()), + ); + TypeSlots::Tuple { field_slots } + } + TypeData::Union { variants } => { + let variant_slots = SubSlots::from_slot_counts( + variants.iter().map(|variant| slots[variant].slot_count()), + ); + TypeSlots::Union { variant_slots } + } + TypeData::HeapCell => TypeSlots::HeapCell, + TypeData::Bag { item } => { + let item_slots = slots[item].slot_count(); + TypeSlots::Bag { item_slots } + } + }; + let pushed_id = slots.push(this_slots); + debug_assert_eq!(pushed_id, id); + } + Self { type_cache, slots } + } + + fn type_cache(&self) -> &TypeCache { + &self.type_cache + } + + fn slots(&self) -> &IdVec { + &self.slots + } +} + +id_type! { + HeapCellId(u32); +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum QueryPoint { + Update(api::UpdateModeVarId), + EntryArg(u32), + CallArg(api::CalleeSpecVarId, u32), + CallRet(api::CalleeSpecVarId, u32), +} + +#[derive(Clone, Debug)] +struct BackRefState { + // TODO: use a more efficient representation + overlay: HashMap>, + parents: Vec, +} + +id_type! { + BackRefStateVersionId(u32); +} + +#[derive(Clone, Copy, Debug)] +struct CallInfo { + callee: FuncId, + ret_slots: u32, +} + +#[derive(Clone)] +struct ForwardState<'a> { + slots_arena: &'a Arena, + value_slots: IdVec>, + // Represents value slots for "iteration n - 1" in an SCC + // TODO: Should this be array-of-structs instead of struct-of-arrays? + value_slots_inductive: IdVec>, + call_arg_aliases: IdVec>>>, + call_arg_origins: IdVec>>, + // TODO: Find a better place to store the data mapping in `calls` + calls: IdVec>, + update_origins: IdVec>, + arg_slots: Option<&'a [HeapCellId]>, + heap_cells: IdVec, + back_ref_states: IdVec, + block_versions: IdVec>, + block_versions_inductive: IdVec>, + entry_version: BackRefStateVersionId, + fates: HashMap, +} + +type Set = HashSet; + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Origin { + /// This heap cell might have been obtained from a `const_ref` op. + /// In this case we don't care what arg slots it might have also been obtained from, because we + /// definitely can't mutate it. + FromConst, + /// This heap cell was definitely not obtained from a `const_ref` op. + /// In this case we care about the (potentially empty) set of arg slots we might have obtained + /// it from. + FromArgSlots(Set), +} + +impl Origin { + pub fn union_with(&mut self, other: &Origin) { + match (&mut *self, other) { + (Origin::FromConst, _) => {} + (Origin::FromArgSlots(_), Origin::FromConst) => *self = Origin::FromConst, + (Origin::FromArgSlots(slots1), Origin::FromArgSlots(slots2)) => slots1.extend(slots2), + } + } +} + +impl Default for Origin { + fn default() -> Self { + Origin::FromArgSlots(Set::new()) + } +} + +#[derive(Clone, Debug)] +struct ForwardData { + origin: Origin, + // invariant: does not contain current heap cell id (all heap cells implicitly alias themselves, + // so storing reflexive alias edges would be redundant). + // invariant: aliases are symmetric; if we alias another heap cell, that heap cell should also + // alias us. + aliases: Set, +} + +fn result_slot_count(sc: &mut SlotCache, val: &op_graph::Node) -> u32 { + sc.slots()[val.op.result_type].slot_count() +} + +fn id_result_slot_count(sc: &mut SlotCache, graph: &ir::Graph, val_id: ir::ValueId) -> u32 { + sc.slots()[graph.values().node(val_id).op.result_type].slot_count() +} + +type HeapCellSlotMapping = HashMap>; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ForwardSccSlotSummary { + pre_aliases: Set, + inductive_aliases: Set<(ir::ValueId, u32)>, + internal_aliases: Set<(ir::ValueId, u32)>, + back_refs: HashSet, +} + +type ForwardSccSummary = HashMap>; + +fn block_values_inclusive( + graph: &ir::Graph, + block: ir::BlockId, +) -> impl Iterator + '_ { + graph + .blocks() + .block_info(block) + .param + .iter() // iterator impl on Option + .cloned() + .chain(graph.blocks().block_values(block)) +} + +impl<'a> ForwardState<'a> { + fn add_heap_cell(&mut self) -> HeapCellId { + self.heap_cells.push(ForwardData { + origin: Origin::default(), + aliases: Set::new(), + }) + } + + // Factored into a separate function to allow use when other (disjoint) fields of 'self' are borrowed + fn back_refs_in_states( + back_ref_states: &mut IdVec, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + ) -> &mut HashSet { + // TODO: Optimize this so that it does not traverse the whole parent chain when the heap + // cell is guaranteed to not have any back ref annotations before a certain point (i.e., + // when we have some information about when the heap cell was created). + // TODO: Remove query points from back ref sets when they are set to 'DirectTouch'. + if back_ref_states[version].overlay.contains_key(&heap_cell) { + return back_ref_states[version] + .overlay + .get_mut(&heap_cell) + .unwrap(); + } + let back_refs = match &back_ref_states[version].parents as &[_] { + &[parent] => Self::back_refs_in_states(back_ref_states, parent, heap_cell).clone(), + parents => { + let num_parents = parents.len(); + let mut back_refs = HashSet::new(); + for parent_i in 0..num_parents { + let parent = back_ref_states[version].parents[parent_i]; + let parent_back_refs = + Self::back_refs_in_states(back_ref_states, parent, heap_cell); + back_refs.extend(parent_back_refs.iter()); + } + back_refs + } + }; + back_ref_states[version] + .overlay + .entry(heap_cell) + .or_insert(back_refs) // always inserts + } + + fn back_refs( + &mut self, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + ) -> &mut HashSet { + Self::back_refs_in_states(&mut self.back_ref_states, version, heap_cell) + } + + fn add_heap_cells(&mut self, n: u32) -> &'a mut [HeapCellId] { + self.slots_arena + .alloc_extend(std::iter::repeat_with(|| self.add_heap_cell()).take(n as usize)) + } + + fn copy_heap_cell(&mut self, cell: HeapCellId, n: u32) -> &'a mut [HeapCellId] { + self.slots_arena + .alloc_extend(std::iter::repeat(cell).take(n as usize)) + } + + fn add_alias(&mut self, cell1: HeapCellId, cell2: HeapCellId) { + if cell1 == cell2 { + return; + } + self.heap_cells[cell1].aliases.insert(cell2); + self.heap_cells[cell2].aliases.insert(cell1); + } + + fn copy_aliases(&mut self, src: HeapCellId, dst: HeapCellId) { + self.add_alias(src, dst); + + // A trick so that we can iterate over `aliases` and call `add_alias` at the same time. At + // the end of the function we put `src_aliases` back in place. + // + // TODO: revist this if we start using "small" sets + let src_aliases = std::mem::take(&mut self.heap_cells[src].aliases); + for &other in &src_aliases { + debug_assert_ne!(other, src); + self.add_alias(other, dst); + } + self.heap_cells[src].aliases = src_aliases; + } + + fn copy_non_alias_data( + &mut self, + src_version: BackRefStateVersionId, + src: HeapCellId, + dst_version: BackRefStateVersionId, + dst: HeapCellId, + ) { + if src == dst { + return; + } + + let (src_data, dst_data) = self.heap_cells.get2_mut(src, dst).unwrap(); + src_data.origin.union_with(&dst_data.origin); + + let src_back_refs = std::mem::take(self.back_refs(src_version, src)); + let dst_back_refs = self.back_refs(dst_version, dst); + dst_back_refs.extend(src_back_refs.iter()); + debug_assert!(self.back_refs(src_version, src).is_empty()); + *self.back_refs(src_version, src) = src_back_refs; + } + + fn copy_data( + &mut self, + src_version: BackRefStateVersionId, + src: HeapCellId, + dst_version: BackRefStateVersionId, + dst: HeapCellId, + ) { + self.copy_non_alias_data(src_version, src, dst_version, dst); + self.copy_aliases(src, dst); + } + + fn touch(&mut self, version: BackRefStateVersionId, heap_cell: HeapCellId) { + let back_refs = std::mem::take(self.back_refs(version, heap_cell)); + for &query_point in &back_refs { + self.fates.insert(query_point, Fate::DirectTouch); + } + *self.back_refs(version, heap_cell) = back_refs; + } + + fn recursive_touch(&mut self, version: BackRefStateVersionId, heap_cells: &[HeapCellId]) { + for &heap_cell in heap_cells { + self.touch(version, heap_cell); + } + } + + fn add_back_refs( + &mut self, + version: BackRefStateVersionId, + heap_cell: HeapCellId, + query_point: QueryPoint, + mut other_filter: impl FnMut(HeapCellId) -> bool, + ) { + let aliases = std::mem::take(&mut self.heap_cells[heap_cell].aliases); + for other in std::iter::once(heap_cell).chain(aliases.iter().cloned()) { + if other_filter(other) { + self.back_refs(version, other).insert(query_point); + } + } + self.heap_cells[heap_cell].aliases = aliases; + } + + fn analyze_value( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + version: BackRefStateVersionId, + val_id: ir::ValueId, + ) { + let val_node = graph.values().node(val_id); + let input_slot_arrs: SmallVec<[_; 16]> = val_node + .inputs + .iter() + .map(|input| { + self.value_slots[input].expect("values should be processed in topological order") + }) + .collect(); + let op = match &val_node.op.kind { + ir::ValueKind::Op(op) => op, + ir::ValueKind::BlockParam => { + unreachable!("block param should never appear in the values of a block") + } + }; + let ret_slots: &[_] = match op { + ir::OpKind::UnknownWith => { + let new_cell = self.add_heap_cell(); + self.heap_cells[new_cell].origin = Origin::FromConst; + for input_slots in input_slot_arrs { + self.recursive_touch(version, input_slots); + for &input_cell in input_slots { + self.copy_aliases(input_cell, new_cell); + } + } + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(new_cell, slot_count) + } + + ir::OpKind::Call { + callee_spec_var, + callee, + } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let arg_slots = input_slot_arrs[0]; + // TODO: optimize this entire case! + let mut heap_cell_slots = HashMap::>::new(); + for (slot_i, &heap_cell) in arg_slots.iter().enumerate() { + heap_cell_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push(slot_i.try_into().unwrap()); + } + let mut arg_aliases = HashSet::new(); + for (heap_cell, slot_indices) in &heap_cell_slots { + // Wire up to ocurrences of the same heap cell in the argument slots + for (i, &slot_i) in slot_indices.iter().enumerate() { + for &slot_j in &slot_indices[..i] { + arg_aliases.insert(NormPair::new(slot_i, slot_j)); + } + } + // Wire up to distinct aliased heap cells in the argument slots + for &other in &self.heap_cells[heap_cell].aliases { + if let Some(other_slot_indices) = heap_cell_slots.get(&other) { + for &this_slot_i in slot_indices { + for &other_slot_i in other_slot_indices { + arg_aliases.insert(NormPair::new(this_slot_i, other_slot_i)); + } + } + } + } + } + + let ret_slots: &[_] = self.add_heap_cells(result_slot_count(sc, &val_node)); + + if let Some(basic_analysis) = ctx.get_analysis(sc, *callee, None) { + for (arg_slot_i, slot_analysis) in basic_analysis.arg_slots.iter().enumerate() { + if matches!(slot_analysis.fate, Fate::DirectTouch) { + self.touch(version, arg_slots[arg_slot_i]); + } + } + for (ret_slot_i, slot_analysis) in basic_analysis.ret_slots.iter().enumerate() { + let ret_heap_cell = ret_slots[ret_slot_i]; + if slot_analysis.from_const { + self.heap_cells[ret_heap_cell].origin = Origin::FromConst; + } + // Temporarily violate symmetry invariant + for &arg_slot_i in &slot_analysis.arg_aliases { + let arg_heap_cell = arg_slots[arg_slot_i as usize]; + self.heap_cells[ret_heap_cell].aliases.insert(arg_heap_cell); + let (arg_heap_cell_data, ret_heap_cell_data) = self + .heap_cells + .get2_mut(arg_heap_cell, ret_heap_cell) + .unwrap(); + for &alias_of_arg in &arg_heap_cell_data.aliases { + ret_heap_cell_data.aliases.insert(alias_of_arg); + } + self.copy_non_alias_data( + version, + arg_heap_cell, + version, + ret_heap_cell, + ); + } + for &other_ret_slot_i in &slot_analysis.ret_aliases { + self.heap_cells[ret_heap_cell] + .aliases + .insert(ret_slots[other_ret_slot_i as usize]); + } + } + } + + for &arg_alias in &arg_aliases { + if let Some(part_analysis) = ctx.get_analysis(sc, *callee, Some(arg_alias)) { + for (arg_slot_i, slot_analysis) in + part_analysis.arg_slots.iter().enumerate() + { + if matches!(slot_analysis.fate, Fate::DirectTouch) { + self.touch(version, arg_slots[arg_slot_i]); + } + } + for (ret_slot_i, slot_analysis) in + part_analysis.ret_slots.iter().enumerate() + { + // Temporarily violate symmetry invariant + let ret_heap_cell = ret_slots[ret_slot_i]; + for &arg_slot_i in &slot_analysis.arg_aliases { + let arg_heap_cell = arg_slots[arg_slot_i as usize]; + self.heap_cells[ret_heap_cell].aliases.insert(arg_heap_cell); + } + for &other_ret_slot_i in &slot_analysis.ret_aliases { + self.heap_cells[ret_heap_cell] + .aliases + .insert(ret_slots[other_ret_slot_i as usize]); + } + } + } + } + + // Restore symmetry invariant + for &ret_heap_cell in ret_slots { + let aliases = std::mem::take(&mut self.heap_cells[ret_heap_cell].aliases); + for &other in &aliases { + debug_assert_ne!(other, ret_heap_cell); + self.heap_cells[other].aliases.insert(ret_heap_cell); + } + debug_assert!(self.heap_cells[ret_heap_cell].aliases.is_empty()); + self.heap_cells[ret_heap_cell].aliases = aliases; + } + + // We don't use 'replace_none' here because we may write these values multiple times + // during fixed-point iteration. + self.call_arg_aliases[callee_spec_var] = Some(arg_aliases); + self.call_arg_origins[callee_spec_var] = Some( + arg_slots + .iter() + .map(|heap_cell| self.heap_cells[heap_cell].origin.clone()) + .collect(), + ); + self.calls[callee_spec_var] = Some(CallInfo { + callee: *callee, + ret_slots: ret_slots.len().try_into().unwrap(), + }); + + for (arg_slot_i, &arg_heap_cell) in arg_slots.iter().enumerate() { + self.add_back_refs( + version, + arg_heap_cell, + QueryPoint::CallArg(*callee_spec_var, arg_slot_i.try_into().unwrap()), + // TODO: don't use a linear search here + |other| !ret_slots.contains(&other), + ); + } + for (ret_slot_i, &ret_heap_cell) in ret_slots.iter().enumerate() { + self.add_back_refs( + version, + ret_heap_cell, + QueryPoint::CallRet(*callee_spec_var, ret_slot_i.try_into().unwrap()), + // TODO: don't use a linear search here + |other| !arg_slots.contains(&other), + ); + } + + ret_slots + } + + ir::OpKind::ConstRef { const_: _ } => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let slot_count = result_slot_count(sc, &val_node); + let new_heap_cells: &[_] = self.add_heap_cells(slot_count); + for heap_cell in new_heap_cells { + self.heap_cells[heap_cell].origin = Origin::FromConst; + } + new_heap_cells + } + + ir::OpKind::NewHeapCell => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let new_cell = self.add_heap_cell(); + std::slice::from_ref(self.slots_arena.alloc(new_cell)) + } + + ir::OpKind::RecursiveTouch => { + debug_assert_eq!(input_slot_arrs.len(), 1); + self.recursive_touch(version, input_slot_arrs[0]); + &[] + } + + ir::OpKind::UpdateWriteOnly { update_mode_var } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + debug_assert_eq!(input_slot_arrs[0].len(), 1); + let heap_cell = input_slot_arrs[0][0]; + // We don't use 'replace_none' here because we may write this value multiple times + // during fixed-point iteration. + self.update_origins[*update_mode_var] = + Some(self.heap_cells[heap_cell].origin.clone()); + self.add_back_refs( + version, + heap_cell, + QueryPoint::Update(*update_mode_var), + |_| true, + ); + &[] + } + + ir::OpKind::EmptyBag => { + debug_assert_eq!(input_slot_arrs.len(), 0); + let slot_count = result_slot_count(sc, &val_node); + self.add_heap_cells(slot_count) + } + + ir::OpKind::BagInsert => { + debug_assert_eq!(input_slot_arrs.len(), 2); + let slot_count = result_slot_count(sc, &val_node); + let slots = self.add_heap_cells(slot_count); + for input_slots in input_slot_arrs { + for (&input_cell, &new_cell) in input_slots.iter().zip(slots.iter()) { + self.copy_data(version, input_cell, version, new_cell); + } + } + slots + } + + ir::OpKind::BagGet => { + debug_assert_eq!(input_slot_arrs.len(), 1); + input_slot_arrs[0] + } + + ir::OpKind::BagRemove => { + debug_assert_eq!(input_slot_arrs.len(), 1); + self.slots_arena.alloc_extend( + input_slot_arrs[0] + .iter() + .chain(input_slot_arrs[0].iter()) + .cloned(), + ) + } + + ir::OpKind::MakeTuple => self.slots_arena.alloc_extend( + input_slot_arrs + .iter() + .flat_map(|slots| slots.iter().cloned()), + ), + + ir::OpKind::GetTupleField { field_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let input_type = graph.values().node(val_node.inputs[0]).op.result_type; + let field_slots = if let TypeSlots::Tuple { field_slots } = &sc.slots()[input_type] + { + field_slots + } else { + unreachable!() + }; + let (start, end) = field_slots.sub_slots(*field_idx); + &input_slot_arrs[0][start as usize..end as usize] + } + + ir::OpKind::MakeUnion { variant_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let variant_slots = if let TypeSlots::Union { variant_slots } = + &sc.slots()[val_node.op.result_type] + { + variant_slots + } else { + unreachable!() + }; + let (start, end) = variant_slots.sub_slots(*variant_idx); + debug_assert_eq!((end - start) as usize, input_slot_arrs[0].len()); + self.slots_arena + .alloc_extend((0..variant_slots.slot_count()).map(|i| { + if start <= i && i < end { + input_slot_arrs[0][(i - start) as usize] + } else { + self.add_heap_cell() + } + })) + } + + ir::OpKind::UnwrapUnion { variant_idx } => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let input_type = graph.values().node(val_node.inputs[0]).op.result_type; + let variant_slots = + if let TypeSlots::Union { variant_slots } = &sc.slots()[input_type] { + variant_slots + } else { + unreachable!() + }; + let (start, end) = variant_slots.sub_slots(*variant_idx); + &input_slot_arrs[0][start as usize..end as usize] + } + + ir::OpKind::MakeNamed => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let new_cell = self.add_heap_cell(); + for &input_cell in input_slot_arrs[0] { + self.copy_data(version, input_cell, version, new_cell); + } + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(new_cell, slot_count) + } + + ir::OpKind::UnwrapNamed => { + debug_assert_eq!(input_slot_arrs.len(), 1); + let slot_count = result_slot_count(sc, &val_node); + self.copy_heap_cell(input_slot_arrs[0][0], slot_count) + } + }; + replace_none(&mut self.value_slots[val_id], ret_slots).unwrap(); + } + + fn target_arg_slots(&self, graph: &ir::Graph, pred: ir::Predecessor) -> &'a [HeapCellId] { + match pred { + ir::Predecessor::Block(block) => { + self.value_slots[graph.blocks().block_info(block).target_arg.unwrap()].unwrap() + } + ir::Predecessor::Entry => self.arg_slots.unwrap(), + } + } + + fn merge_slots( + &mut self, + new_version: BackRefStateVersionId, + slot_count: u32, + sources: impl Iterator, + ) -> &'a [HeapCellId] + where + F: for<'b> FnMut(&'b Self) -> BackRefStateVersionId, + G: for<'b> FnMut(&'b Self) -> I, + I: Iterator, + { + let min_new_id = self.heap_cells.count(); + let merged_slots: &[_] = self.add_heap_cells(slot_count); + for (mut source_version_fn, mut source) in sources { + let source_version = source_version_fn(self); + // We need to add all of the alias edges up front between slots in the source and slots + // in the value under construction because we might only know that two slots in the + // value under construction alias because of their transitive relationship through + // source slots. For instance, consider the following code: + // + // let x = []; + // let result: (_, _) = choice { (x, x) } or { ([], []) }; + // + // We know that the two slots in result (the value under construction) might alias + // because they alias the first and second tuple elements in the first choice branch + // respectively, and those elements alias. + let mut i = 0; + for source_heap_cell in source(self) { + let merged_heap_cell = merged_slots[i as usize]; + self.copy_data( + source_version, + source_heap_cell, + new_version, + merged_heap_cell, + ); + i += 1; + } + debug_assert_eq!(i, slot_count); + + // Consider the following code: + // + // let x = []; + // let result: (_, _) = choice { (x, []) } or { ([], x) }; + // + // The first and second slots in result (the value under construction) cannot alias. If + // we do not remove the edge between the first slot of result and the first slot of the + // tuple in the first choice branch, then on the next iteration of the loop the + // algorithm will see a transitive alias edge between them. + // + // Note also that we need to remove *all* symmetric edges pointing back from heap cells + // which predate the value under construction, not just symmetric edges pointing back + // from the heap cells which appear directly in the predecessor value. For example, + // consider the following code: + // + // let x = []; + // let y = /* something that aliases x, but has a distinct heap cell */; + // let result: (_, _) = choice { (x, []) } or { ([], y) }; + // + // After processing the first branch of the choice, we need to remove the newly-created + // edges pointing back from both x *and* y. + for &merged_heap_cell in merged_slots { + let merged_aliases = std::mem::take(&mut self.heap_cells[merged_heap_cell].aliases); + for &other in &merged_aliases { + // This removes edges back from heap cells which predate the value under + // construction, but does not remove edges between heap cells in the value under + // construction. Preserving edges within the value under construction is + // important for handling cases like the following example (also discussed + // above) correctly: + // + // let x = []; + // let result: (_, _) = choice { (x, x) } or { ([], []) }; + if other < min_new_id { + // Temporarily violate symmetry invariant + self.heap_cells[other].aliases.remove(&merged_heap_cell); + } + } + debug_assert!(self.heap_cells[merged_heap_cell].aliases.is_empty()); + self.heap_cells[merged_heap_cell].aliases = merged_aliases; + } + } + for &merged_heap_cell in merged_slots { + let merged_aliases = std::mem::take(&mut self.heap_cells[merged_heap_cell].aliases); + for &other in &merged_aliases { + if other < min_new_id { + // Restore symmetry invariant + self.heap_cells[other].aliases.insert(merged_heap_cell); + } + } + debug_assert!(self.heap_cells[merged_heap_cell].aliases.is_empty()); + self.heap_cells[merged_heap_cell].aliases = merged_aliases; + } + merged_slots + } + + fn predecessor_back_refs(&self, pred: ir::Predecessor) -> BackRefStateVersionId { + match pred { + ir::Predecessor::Entry => self.entry_version, + ir::Predecessor::Block(block) => self.block_versions[block].unwrap(), + } + } + + fn analyze_block( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + block: ir::BlockId, + ) { + let block_info = graph.blocks().block_info(block); + let new_version = self.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: block_info + .predecessors + .iter() + .map(|&pred| self.predecessor_back_refs(pred)) + .collect(), + }); + if let Some(param_id) = block_info.param { + let param_slots = if block_info.predecessors.len() == 1 { + self.target_arg_slots(graph, block_info.predecessors[0]) + } else { + let slot_count = id_result_slot_count(sc, graph, param_id); + self.merge_slots( + new_version, + slot_count, + block_info.predecessors.iter().map(|&pred| { + let pred_version = move |this: &Self| this.predecessor_back_refs(pred); + // TODO: is this correct if a block is its own predecessor, without any + // indirection? + let pred_slots = + move |this: &Self| this.target_arg_slots(graph, pred).iter().cloned(); + (pred_version, pred_slots) + }), + ) + }; + replace_none(&mut self.value_slots[param_id], param_slots).unwrap(); + } + for val_id in graph.blocks().block_values(block) { + self.analyze_value(sc, ctx, graph, new_version, val_id); + } + replace_none(&mut self.block_versions[block], new_version).unwrap(); + } + + // TODO: Everything to do with SCC analysis can be significantly optimized + + fn heap_cell_slot_mapping( + &self, + graph: &ir::Graph, + blocks: impl Iterator, + ) -> HeapCellSlotMapping { + let mut heap_cell_to_slots = HashMap::new(); + for block in blocks { + for val_id in block_values_inclusive(graph, block) { + for (i, &heap_cell) in self.value_slots[val_id].unwrap().iter().enumerate() { + heap_cell_to_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push((val_id, i.try_into().unwrap())); + } + } + } + heap_cell_to_slots + } + + fn summarize_scc( + &mut self, + graph: &ir::Graph, + blocks: impl Iterator, + min_new_id: Count, + heap_cell_slots_inductive: &HeapCellSlotMapping, + heap_cell_slots_current: &HeapCellSlotMapping, + ) -> ForwardSccSummary { + let mut summary = ForwardSccSummary::new(); + for block in blocks { + for val_id in block_values_inclusive(graph, block) { + let block_version = self.block_versions[block].unwrap(); + let slot_summaries = self.value_slots[val_id] + .unwrap() + .iter() + .enumerate() + .map(|(slot_i, &heap_cell)| { + let mut val_summary = ForwardSccSlotSummary { + pre_aliases: Set::new(), + inductive_aliases: Set::new(), + internal_aliases: Set::new(), + back_refs: Self::back_refs_in_states( + &mut self.back_ref_states, + block_version, + heap_cell, + ) + .clone(), + }; + let aliased_heap_cells = std::iter::once(heap_cell) + .chain(self.heap_cells[heap_cell].aliases.iter().cloned()); + for aliased in aliased_heap_cells { + if aliased < min_new_id { + val_summary.pre_aliases.insert(aliased); + } + for &aliased_slot in heap_cell_slots_current + .get(&aliased) + .iter() + .cloned() + .flatten() + { + if aliased_slot == (val_id, slot_i.try_into().unwrap()) { + continue; + } + val_summary.internal_aliases.insert(aliased_slot); + } + for &aliased_slot in heap_cell_slots_inductive + .get(&aliased) + .iter() + .cloned() + .flatten() + { + if aliased_slot == (val_id, slot_i.try_into().unwrap()) { + continue; + } + val_summary.inductive_aliases.insert(aliased_slot); + } + } + val_summary + }) + .collect(); + summary.insert(val_id, slot_summaries); + } + } + summary + } + + fn disconnect_heap_cell(&mut self, heap_cell: HeapCellId) { + let aliases = std::mem::take(&mut self.heap_cells[heap_cell].aliases); + for &other in &aliases { + self.heap_cells[other].aliases.remove(&heap_cell); + } + } + + fn analyze_block_scc( + &mut self, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + scc_id: ir::SccId, + ) { + let scc = graph.sccs().get(scc_id); + match scc.info { + SccKind::Acyclic => { + debug_assert!(scc.items.len() == 1); + self.analyze_block(sc, ctx, graph, scc.items[0]); + } + SccKind::Cyclic => { + let init_version_parents = scc + .items + .iter() + .flat_map(|&block| { + graph + .blocks() + .block_info(block) + .predecessors + .iter() + .filter_map(|&pred| match pred { + ir::Predecessor::Entry => Some(self.entry_version), + ir::Predecessor::Block(pred_block) => { + self.block_versions[pred_block] + } + }) + }) + .collect::>() + .into_iter() + .collect::>(); + let init_version = self.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: init_version_parents, + }); + + let min_new_id = self.heap_cells.count(); + for &block in scc.items { + replace_none(&mut self.block_versions[block], init_version).unwrap(); + for val_id in block_values_inclusive(graph, block) { + let slot_count = id_result_slot_count(sc, graph, val_id); + let init_slots = self.add_heap_cells(slot_count); + replace_none(&mut self.value_slots[val_id], init_slots).unwrap(); + } + } + let mut prev_iter_summary = None; + let mut prev_iter_heap_cell_slot_mapping = + self.heap_cell_slot_mapping(graph, scc.items.iter().cloned()); + let mut prev_iter_min_new_id = min_new_id; + loop { + let curr_iter_min_new_id = self.heap_cells.count(); + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores irrelevant data + for &block in scc.items { + let version = std::mem::take(&mut self.block_versions[block]); + debug_assert!(version.is_some()); + self.block_versions_inductive[block] = version; + for val_id in block_values_inclusive(graph, block) { + let slots = std::mem::take(&mut self.value_slots[val_id]); + debug_assert!(slots.is_some()); + self.value_slots_inductive[val_id] = slots; + } + // Now: + // - Main layer stores previous iteration state, except for current block, + // for which it stores 'None' + // - Inductive layer stores a mix of irrelevant data and current iteration + // state, except for current block, for which it stores previous iteration + // state + self.analyze_block(sc, ctx, graph, block); + // Now: + // - Main layer stores previous iteration state, except for current block, + // for which it stores current iteration state + // - Inductive layer stores a mix of irrelevant data and current iteration + // state, except for current block, for which it stores previous iteration + // state + std::mem::swap( + &mut self.block_versions[block], + &mut self.block_versions_inductive[block], + ); + for val_id in block_values_inclusive(graph, block) { + std::mem::swap( + &mut self.value_slots[val_id], + &mut self.value_slots_inductive[val_id], + ); + } + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores a mix of irrelevant data and current iteration + // state. In particular, for all blocks processed so far (including this + // one) it stores current iteration state. + } + // Now: + // - Main layer stores previous iteration state + // - Inductive layer stores current iteration state + for &block in scc.items { + std::mem::swap( + &mut self.block_versions[block], + &mut self.block_versions_inductive[block], + ); + for val_id in block_values_inclusive(graph, block) { + std::mem::swap( + &mut self.value_slots[val_id], + &mut self.value_slots_inductive[val_id], + ); + } + } + // Now: + // - Main layer stores current iteration state + // - Inductive layer stores previous iteration state + let curr_iter_heap_cell_slot_mapping = + self.heap_cell_slot_mapping(graph, scc.items.iter().cloned()); + let curr_iter_summary = self.summarize_scc( + graph, + scc.items.iter().cloned(), + min_new_id, + &prev_iter_heap_cell_slot_mapping, + &curr_iter_heap_cell_slot_mapping, + ); + if Some(&curr_iter_summary) == prev_iter_summary.as_ref() { + break; + } + + // Garbage collect connectsions to irrelevant heap cells from previous iteration + for heap_cell in + (prev_iter_min_new_id.0 .0..curr_iter_min_new_id.0 .0).map(HeapCellId) + { + self.disconnect_heap_cell(heap_cell); + } + + prev_iter_summary = Some(curr_iter_summary); + prev_iter_heap_cell_slot_mapping = curr_iter_heap_cell_slot_mapping; + prev_iter_min_new_id = curr_iter_min_new_id; + } + } + } + } + + fn analyze_graph( + slots_arena: &'a Arena, + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + graph: &ir::Graph, + arg_alias: Option>, + ) -> (Self, &'a [HeapCellId]) { + let mut heap_cells = IdVec::new(); + let arg_slots = graph + .blocks() + .block_info(graph.entry_block()) + .param + .map(|arg_val_id| { + let slot_count = id_result_slot_count(sc, graph, arg_val_id); + let arg_slots: &[_] = slots_arena.alloc_extend((0..slot_count).map(|i| { + let mut origin_arg_slots = Set::new(); + origin_arg_slots.insert(i); + heap_cells.push(ForwardData { + origin: Origin::FromArgSlots(origin_arg_slots), + aliases: Set::new(), + }) + })); + if let Some(arg_alias) = arg_alias { + let fst = arg_slots[*arg_alias.fst() as usize]; + let snd = arg_slots[*arg_alias.snd() as usize]; + heap_cells[fst].aliases.insert(snd); + heap_cells[snd].aliases.insert(fst); + } + arg_slots + }); + + let mut back_ref_states = IdVec::new(); + let entry_version = back_ref_states.push(BackRefState { + overlay: arg_slots + .iter() + .cloned() + .flatten() + .enumerate() + .map(|(slot_i, &heap_cell)| { + let mut heap_cell_back_refs = HashSet::new(); + heap_cell_back_refs.insert(QueryPoint::EntryArg(slot_i.try_into().unwrap())); + (heap_cell, heap_cell_back_refs) + }) + .collect(), + parents: Vec::new(), + }); + + let mut state = ForwardState { + slots_arena, + value_slots: IdVec::filled_with(graph.values().count(), || None), + value_slots_inductive: IdVec::filled_with(graph.values().count(), || None), + call_arg_aliases: IdVec::filled_with(graph.callee_spec_vars(), || None), + call_arg_origins: IdVec::filled_with(graph.callee_spec_vars(), || None), + calls: IdVec::filled_with(graph.callee_spec_vars(), || None), + update_origins: IdVec::filled_with(graph.update_mode_vars(), || None), + arg_slots, + heap_cells, + back_ref_states, + block_versions: IdVec::filled_with(graph.blocks().block_count(), || None), + block_versions_inductive: IdVec::filled_with(graph.blocks().block_count(), || None), + entry_version, + fates: HashMap::new(), + }; + + for scc_id in graph.sccs().count().iter() { + state.analyze_block_scc(sc, ctx, graph, scc_id); + } + + let exit_version = state.back_ref_states.push(BackRefState { + overlay: HashMap::new(), + parents: graph + .exit_blocks() + .iter() + .map(|&block| state.block_versions[block].unwrap()) + .collect(), + }); + let ret_slot_count = sc.slots()[graph.ret_type()].slot_count(); + let ret_heap_cells = state.merge_slots( + exit_version, + ret_slot_count, + graph.exit_blocks().iter().map(|&block| { + let block_version = move |this: &Self| this.block_versions[block].unwrap(); + let block_slots = move |this: &Self| { + this.target_arg_slots(graph, ir::Predecessor::Block(block)) + .iter() + .cloned() + }; + (block_version, block_slots) + }), + ); + + for (ret_slot_i, &ret_heap_cell) in ret_heap_cells.iter().enumerate() { + for &query_point in + Self::back_refs_in_states(&mut state.back_ref_states, exit_version, ret_heap_cell) + .iter() + { + if let Fate::Other { ret_slots, .. } = state.fates.entry(query_point).or_default() { + ret_slots.insert(ret_slot_i.try_into().unwrap()); + } + } + } + + (state, ret_heap_cells) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum Fate { + DirectTouch, + Other { + indirect_touch: bool, + ret_slots: Set, + }, +} + +impl Fate { + fn union_with(&mut self, other: &Fate) { + match (&mut *self, other) { + (Fate::DirectTouch, _) => {} + (Fate::Other { .. }, Fate::DirectTouch) => { + *self = Fate::DirectTouch; + } + ( + Fate::Other { + indirect_touch: indirect_touch_1, + ret_slots: ret_slots_1, + }, + Fate::Other { + indirect_touch: indirect_touch_2, + ret_slots: ret_slots_2, + }, + ) => { + *indirect_touch_1 = *indirect_touch_1 || *indirect_touch_2; + ret_slots_1.extend(ret_slots_2); + } + } + } +} + +impl Default for Fate { + fn default() -> Self { + Fate::Other { + indirect_touch: false, + ret_slots: Set::new(), + } + } +} + +fn analyze_func( + sc: &mut SlotCache, + ctx: &mut SccAnalysisContext, + func_def: &ir::FuncDef, + arg_alias: Option>, +) -> FuncAnalysis { + let slots_arena = Arena::new(); + let (forward, ret_slots) = + ForwardState::analyze_graph(&slots_arena, sc, ctx, &func_def.graph, arg_alias); + let mut heap_cell_to_arg_slot = HashMap::::new(); + for (slot_i, &heap_cell) in forward.arg_slots.unwrap().iter().enumerate() { + let existing = heap_cell_to_arg_slot.insert(heap_cell, slot_i.try_into().unwrap()); + debug_assert!(existing.is_none()); + } + let mut heap_cell_to_ret_slots = HashMap::>::new(); + for (slot_i, &heap_cell) in ret_slots.iter().enumerate() { + heap_cell_to_ret_slots + .entry(heap_cell) + .or_insert_with(SmallVec::new) + .push(slot_i.try_into().unwrap()); + } + let arg_slot_analyses = (0..forward.arg_slots.unwrap().len()) + .map(|arg_slot_i| ArgSlotAnalysis { + fate: forward + .fates + .get(&QueryPoint::EntryArg(arg_slot_i.try_into().unwrap())) + .cloned() + .unwrap_or_default(), + }) + .collect(); + let ret_slot_analyses = ret_slots + .iter() + .enumerate() + .map(|(this_ret_slot_i, &heap_cell)| { + let mut arg_aliases = Set::new(); + let mut ret_aliases = Set::new(); + for other in std::iter::once(heap_cell) + .chain(forward.heap_cells[heap_cell].aliases.iter().cloned()) + { + if let Some(&arg_slot_i) = heap_cell_to_arg_slot.get(&other) { + arg_aliases.insert(arg_slot_i); + } + if let Some(ret_slots_i) = heap_cell_to_ret_slots.get(&other) { + for &ret_slot_i in ret_slots_i { + if ret_slot_i as usize != this_ret_slot_i { + ret_aliases.insert(ret_slot_i); + } + } + } + } + RetSlotAnalysis { + from_const: matches!(forward.heap_cells[heap_cell].origin, Origin::FromConst), + arg_aliases, + ret_aliases, + } + }) + .collect(); + FuncAnalysis { + graph_analysis: GraphAnalysis { + updates: IdVec::filled_with_indexed( + func_def.graph.update_mode_vars(), + |update_mode_var| UpdateAnalysis { + origin: forward.update_origins[update_mode_var] + .as_ref() + .unwrap() + .clone(), + fate: forward + .fates + .get(&QueryPoint::Update(update_mode_var)) + .cloned() + .unwrap_or_default(), + }, + ), + calls: IdVec::filled_with_indexed( + func_def.graph.callee_spec_vars(), + |callee_spec_var| { + // let call_fates = backward.call_fates[callee_spec_var].as_ref().unwrap(); + let call_info = forward.calls[callee_spec_var].unwrap(); + CallAnalysis { + callee: call_info.callee, + arg_aliases: forward.call_arg_aliases[callee_spec_var] + .as_ref() + .unwrap() + .clone(), + arg_slots: forward.call_arg_origins[callee_spec_var] + .as_ref() + .unwrap() + .iter() + .enumerate() + .map(|(arg_slot_i, origin)| ArgAnalysis { + origin: origin.clone(), + fate: forward + .fates + .get(&QueryPoint::CallArg( + callee_spec_var, + arg_slot_i.try_into().unwrap(), + )) + .cloned() + .unwrap_or_default(), + }) + .collect(), + ret_slots: (0..call_info.ret_slots) + .map(|ret_slot_i| { + forward + .fates + .get(&QueryPoint::CallRet(callee_spec_var, ret_slot_i)) + .cloned() + .unwrap_or_default() + }) + .collect(), + } + }, + ), + }, + arg_slots: arg_slot_analyses, + ret_slots: ret_slot_analyses, + } +} + +id_type! { + FuncSccId(u32); +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct UpdateAnalysis { + origin: Origin, + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ArgAnalysis { + origin: Origin, + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct CallAnalysis { + // Find a better place to store the callee + callee: FuncId, + arg_aliases: Set>, + arg_slots: SmallVec<[ArgAnalysis; 4]>, + ret_slots: SmallVec<[Fate; 4]>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct GraphAnalysis { + updates: IdVec, + calls: IdVec, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct ArgSlotAnalysis { + fate: Fate, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct RetSlotAnalysis { + from_const: bool, + arg_aliases: Set, + ret_aliases: Set, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +struct FuncAnalysis { + graph_analysis: GraphAnalysis, + arg_slots: SmallVec<[ArgSlotAnalysis; 4]>, + ret_slots: SmallVec<[RetSlotAnalysis; 4]>, +} + +#[derive(Clone, Debug)] +struct GlobalAnalysisContext<'a> { + func_defs: &'a IdVec, + sccs: &'a FlatSlices, + func_to_scc: &'a IdVec, + committed: IdVec>, FuncAnalysis>>, +} + +impl<'a> GlobalAnalysisContext<'a> { + fn analyze( + &mut self, + sc: &mut SlotCache, + func: FuncId, + arg_alias: Option>, + ) -> &FuncAnalysis { + debug_assert!(!self.committed[func].contains_key(&arg_alias)); + let scc = self.func_to_scc[func]; + let scc_kind = *self.sccs.get(scc).info; + let mut scc_ctx = SccAnalysisContext { + global: &mut *self, + scc, + prev_iter: HashMap::new(), + curr_iter: HashMap::new(), + }; + match scc_kind { + SccKind::Acyclic => { + scc_ctx.get_analysis(sc, func, arg_alias); + debug_assert_eq!(scc_ctx.curr_iter.len(), 1); + } + SccKind::Cyclic => loop { + scc_ctx.get_analysis(sc, func, arg_alias); + // TODO: only compare "signature" information here, not internal annotations on body + // values. + if scc_ctx.curr_iter == scc_ctx.prev_iter { + break; + } + scc_ctx.prev_iter = std::mem::take(&mut scc_ctx.curr_iter); + }, + }; + let results = scc_ctx.curr_iter; + for ((analyzed_func, analyzed_arg_alias), analysis) in results { + let existing = self.committed[analyzed_func] + .insert(analyzed_arg_alias, analysis.unwrap_complete()); + debug_assert!(existing.is_none()); + } + &self.committed[func][&arg_alias] + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, PartialEq, Eq)] +enum AnalysisState { + Pending, + Complete(FuncAnalysis), +} + +impl AnalysisState { + fn unwrap_complete(self) -> FuncAnalysis { + match self { + AnalysisState::Pending => unreachable!(), + AnalysisState::Complete(analysis) => analysis, + } + } + + fn unwrap_complete_ref(&self) -> &FuncAnalysis { + match self { + AnalysisState::Pending => unreachable!(), + AnalysisState::Complete(analysis) => analysis, + } + } +} + +#[derive(Debug)] +struct SccAnalysisContext<'a, 'b> { + global: &'b mut GlobalAnalysisContext<'a>, + scc: FuncSccId, + // Invariant: 'prev_iter' should contain no 'Pending' analyses + prev_iter: HashMap<(FuncId, Option>), AnalysisState>, + curr_iter: HashMap<(FuncId, Option>), AnalysisState>, +} + +impl<'a, 'b> SccAnalysisContext<'a, 'b> { + fn get_analysis<'c>( + &'c mut self, + sc: &mut SlotCache, + func: FuncId, + arg_alias: Option>, + ) -> Option<&'c FuncAnalysis> { + if self.global.committed[func].contains_key(&arg_alias) { + // TODO: is there a way to avoid the double lookup here while passing the borrow + // checker? + return Some(&self.global.committed[func][&arg_alias]); + } + if self.global.func_to_scc[func] != self.scc { + return Some(self.global.analyze(sc, func, arg_alias)); + } + // TODO: can we resolve this clippy error while passing the borrow checker? + #[allow(clippy::map_entry)] + if self.curr_iter.contains_key(&(func, arg_alias)) { + // TODO: as above, can we avoid the double lookup? + match &self.curr_iter[&(func, arg_alias)] { + AnalysisState::Complete(analysis) => Some(analysis), + AnalysisState::Pending => self + .prev_iter + .get(&(func, arg_alias)) + .map(AnalysisState::unwrap_complete_ref), + } + } else { + self.curr_iter + .insert((func, arg_alias), AnalysisState::Pending); + let analysis = analyze_func(sc, self, &self.global.func_defs[func], arg_alias); + match self.curr_iter.entry((func, arg_alias)) { + std::collections::hash_map::Entry::Occupied(mut occupied) => { + *occupied.get_mut() = AnalysisState::Complete(analysis); + Some(occupied.into_mut().unwrap_complete_ref()) + } + std::collections::hash_map::Entry::Vacant(_) => { + unreachable!() + } + } + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +struct Query { + // TODO: improve sparsity of contextual information, prune everything inessential + arg_aliases: BTreeSet>, + // For the purposes of `arg_slots_touched`, an arg slot being 'FromConst' is the same as being + // touched after the call. + arg_slots_touched: SmallVec<[bool; 8]>, + ret_slots_touched: SmallVec<[bool; 8]>, +} + +impl Query { + fn to_spec(&self, func: FuncId) -> api::FuncSpec { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(func.0.to_le_bytes()); + hasher.update((self.arg_aliases.len() as u64).to_le_bytes()); + for arg_alias in &self.arg_aliases { + hasher.update(arg_alias.fst().to_le_bytes()); + hasher.update(arg_alias.snd().to_le_bytes()); + } + hasher.update((self.arg_slots_touched.len() as u64).to_le_bytes()); + for &arg_touched in &self.arg_slots_touched { + hasher.update(&[arg_touched as u8]); + } + hasher.update((self.ret_slots_touched.len() as u64).to_le_bytes()); + for &ret_touched in &self.ret_slots_touched { + hasher.update(&[ret_touched as u8]); + } + api::FuncSpec(hasher.finalize().into()) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct FuncSolution { + pub(crate) update_modes: IdVec, + pub(crate) callee_specs: IdVec, +} + +#[derive(Clone, Debug)] +pub(crate) struct FuncSolutions { + pub(crate) solutions: IdVec>>, +} + +fn resolve_origin<'a>(query: &Query, mut origins: impl Iterator) -> bool { + origins.any(|origin| match origin { + Origin::FromConst => true, + Origin::FromArgSlots(arg_slots) => arg_slots + .iter() + .any(|&arg_slot| query.arg_slots_touched[arg_slot as usize]), + }) +} + +fn resolve_fate<'a>(query: &Query, mut fates: impl Iterator) -> bool { + fates.any(|fate| match fate { + Fate::DirectTouch => true, + Fate::Other { + indirect_touch, + ret_slots, + } => { + *indirect_touch + || ret_slots + .iter() + .any(|&ret_slot| query.ret_slots_touched[ret_slot as usize]) + } + }) +} + +impl FuncSolutions { + fn resolve( + &mut self, + analyses: &IdVec>, FuncAnalysis>>, + func: FuncId, + query: &Query, + ) -> api::FuncSpec { + let spec = query.to_spec(func); + if let std::collections::hash_map::Entry::Vacant(vacant) = self.solutions[func].entry(spec) + { + let func_analyses = &analyses[func]; + let basic_analysis = &func_analyses[&None].graph_analysis; + vacant.insert(None); + let query_analyses: SmallVec<[&GraphAnalysis; 8]> = std::iter::once(basic_analysis) + .chain( + query + .arg_aliases + .iter() + .map(|&arg_alias| &func_analyses[&Some(arg_alias)].graph_analysis), + ) + .collect(); + let update_modes = + IdVec::filled_with_indexed(basic_analysis.updates.count(), |update_mode_var| { + let touched = resolve_origin( + query, + query_analyses + .iter() + .map(|analysis| &analysis.updates[update_mode_var].origin), + ) || resolve_fate( + query, + query_analyses + .iter() + .map(|analysis| &analysis.updates[update_mode_var].fate), + ); + if touched { + api::UpdateMode::Immutable + } else { + api::UpdateMode::InPlace + } + }); + let callee_specs = + IdVec::filled_with_indexed(basic_analysis.calls.count(), |callee_spec_var| { + let mut sub_arg_aliases = BTreeSet::new(); + for analysis in &query_analyses { + sub_arg_aliases.extend(&analysis.calls[callee_spec_var].arg_aliases); + } + let num_arg_slots = basic_analysis.calls[callee_spec_var].arg_slots.len(); + let num_ret_slots = basic_analysis.calls[callee_spec_var].ret_slots.len(); + let sub_arg_slots_touched = (0..num_arg_slots) + .map(|arg_slot_i| { + resolve_origin( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].arg_slots[arg_slot_i].origin + }), + ) || resolve_fate( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].arg_slots[arg_slot_i].fate + }), + ) + }) + .collect(); + let sub_ret_slots_touched = (0..num_ret_slots) + .map(|ret_slot_i| { + resolve_fate( + query, + query_analyses.iter().map(|analysis| { + &analysis.calls[callee_spec_var].ret_slots[ret_slot_i] + }), + ) + }) + .collect(); + let sub_query = Query { + arg_aliases: sub_arg_aliases, + arg_slots_touched: sub_arg_slots_touched, + ret_slots_touched: sub_ret_slots_touched, + }; + self.resolve( + analyses, + basic_analysis.calls[callee_spec_var].callee, + &sub_query, + ) + }); + let solution = FuncSolution { + update_modes, + callee_specs, + }; + self.solutions[func].insert(spec, Some(solution)); + } + spec + } +} + +#[derive(Clone, Debug)] +pub(crate) struct ProgramSolutions { + pub(crate) funcs: FuncSolutions, + pub(crate) entry_points: IdVec, +} + +pub(crate) fn analyze(tc: TypeCache, program: &ir::Program) -> ProgramSolutions { + let mut sc = SlotCache::new(tc); + + let func_sccs: FlatSlices = + strongly_connected(program.funcs.count(), |func_id| { + let func_def = &program.funcs[func_id]; + let values = func_def.graph.values(); + values + .count() + .iter() + .filter_map(move |val_id| match &values.node(val_id).op.kind { + ir::ValueKind::Op(ir::OpKind::Call { + callee, + callee_spec_var: _, + }) => Some(*callee), + + _ => None, + }) + }); + + let mut func_to_scc = IdVec::filled_with(program.funcs.count(), || FuncSccId(u32::MAX)); + for scc_id in func_sccs.count().iter() { + for &func in func_sccs.get(scc_id).items { + func_to_scc[func] = scc_id; + } + } + + let mut ctx = GlobalAnalysisContext { + func_defs: &program.funcs, + sccs: &func_sccs, + func_to_scc: &func_to_scc, + committed: IdVec::filled_with(program.funcs.count(), HashMap::new), + }; + + for (_, &func) in &program.entry_points { + if !ctx.committed[func].contains_key(&None) { + ctx.analyze(&mut sc, func, None); + } + } + + let mut func_solutions = FuncSolutions { + solutions: IdVec::filled_with(program.funcs.count(), HashMap::new), + }; + + let entry_point_solutions = program.entry_points.map(|_, &func| { + func_solutions.resolve( + &ctx.committed, + func, + &Query { + arg_aliases: BTreeSet::new(), + arg_slots_touched: SmallVec::new(), + ret_slots_touched: SmallVec::new(), + }, + ) + }); + + ProgramSolutions { + funcs: func_solutions, + entry_points: entry_point_solutions, + } +} diff --git a/vendor/morphic_lib/src/api.rs b/vendor/morphic_lib/src/api.rs index 6dc57d1c6f..893e2a1c48 100644 --- a/vendor/morphic_lib/src/api.rs +++ b/vendor/morphic_lib/src/api.rs @@ -1,7 +1,9 @@ use sha2::{digest::Digest, Sha256}; use smallvec::SmallVec; use std::collections::{btree_map::Entry, BTreeMap}; +use std::rc::Rc; +use crate::analyze; use crate::preprocess; use crate::render_api_ir; use crate::util::blocks::Blocks; @@ -1372,13 +1374,15 @@ pub enum UpdateMode { pub const SPEC_HASH_BYTES: usize = 32; #[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FuncSpec(pub [u8; SPEC_HASH_BYTES]); /// The solution table for an individual specialization. pub struct FuncSpecSolutions { - func_def: FuncDef, - callee_specs: IdVec, + // TODO: eliminate the RC here (this will require introducing a lifetime, and is therefore a + // breaking API change) + func_def: Rc, + solution: analyze::FuncSolution, } impl FuncSpecSolutions { @@ -1392,7 +1396,7 @@ impl FuncSpecSolutions { .callee_spec_vars .get_by_val(&var.into()) { - Some(id) => Ok(self.callee_specs[id]), + Some(id) => Ok(self.solution.callee_specs[id]), None => Err(ErrorKind::CalleeSpecVarNotFound(var.into()).into()), } } @@ -1407,7 +1411,7 @@ impl FuncSpecSolutions { .update_mode_vars .get_by_val(&var.into()) { - Some(_id) => Ok(UpdateMode::Immutable), + Some(id) => Ok(self.solution.update_modes[id]), None => Err(ErrorKind::UpdateModeVarNotFound(var.into()).into()), } } @@ -1416,20 +1420,20 @@ impl FuncSpecSolutions { /// Zero or more specializations for a single function, and the solution table for each /// specialization. pub struct FuncSolutions { - spec: FuncSpec, - spec_solutions: FuncSpecSolutions, + spec_solutions: BTreeMap, } impl FuncSolutions { pub fn specs(&self) -> impl Iterator { - std::iter::once(&self.spec) + self.spec_solutions.keys() } pub fn spec(&self, spec: &FuncSpec) -> Result<&FuncSpecSolutions> { - if &self.spec != spec { - return Err(ErrorKind::FuncSpecNotFound(*spec).into()); + if let Some(solution) = self.spec_solutions.get(spec) { + Ok(solution) + } else { + Err(ErrorKind::FuncSpecNotFound(*spec).into()) } - Ok(&self.spec_solutions) } } @@ -1501,7 +1505,7 @@ impl ModSolutions { /// Specializations and solution tables generated for the entire program. pub struct Solutions { mods: BTreeMap, - entry_points: BTreeMap, + entry_points: BTreeMap, } impl Solutions { @@ -1521,9 +1525,8 @@ impl Solutions { // TODO: The clone here is unnecessary -- avoid it! // (might require something like a transmute) match self.entry_points.get(&entry_point.into()) { - Some((mod_name, func_name)) => { - let spec = hash_func_name(mod_name.borrowed(), func_name.borrowed()); - Ok((mod_name.borrowed(), func_name.borrowed(), spec)) + Some((mod_name, func_name, spec)) => { + Ok((mod_name.borrowed(), func_name.borrowed(), *spec)) } None => Err(ErrorKind::EntryPointNotFound(entry_point.into()).into()), } @@ -1552,11 +1555,14 @@ fn populate_specs( results.into_mapped(|_, spec| spec.unwrap()) } -pub fn solve(program: Program) -> Result { - preprocess::preprocess(&program).map_err(ErrorKind::PreprocessError)?; +pub fn solve(api_program: Program) -> Result { + let (nc, tc, program) = + preprocess::preprocess(&api_program).map_err(ErrorKind::PreprocessError)?; + + let mut solutions = analyze::analyze(tc, &program); Ok(Solutions { - mods: program + mods: api_program .mods .into_iter() .map(|(mod_name, mod_def)| { @@ -1565,16 +1571,27 @@ pub fn solve(program: Program) -> Result { .func_defs .into_iter() .map(|(func_name, func_def)| { - let callee_specs = populate_specs( - func_def.builder.expr_builder.callee_spec_vars.count(), - &func_def.builder.expr_builder.vals, - ); + // TODO: avoid the clones here + let func_id = nc + .funcs + .get_by_val(&(mod_name.clone(), func_name.clone())) + .unwrap(); + let func_def = Rc::new(func_def); let func_sols = FuncSolutions { - spec: hash_func_name(mod_name.borrowed(), func_name.borrowed()), - spec_solutions: FuncSpecSolutions { - func_def, - callee_specs, - }, + spec_solutions: std::mem::take( + &mut solutions.funcs.solutions[func_id], + ) + .into_iter() + .map(|(spec, solution)| { + ( + spec, + FuncSpecSolutions { + func_def: func_def.clone(), + solution: solution.unwrap(), + }, + ) + }) + .collect(), }; (func_name, func_sols) }) @@ -1600,7 +1617,15 @@ pub fn solve(program: Program) -> Result { (mod_name, mod_sols) }) .collect(), - entry_points: program.entry_points, + entry_points: api_program + .entry_points + .into_iter() + .map(|(entry_point_name, (mod_name, func_name))| { + let entry_point_id = nc.entry_points.get_by_val(&entry_point_name).unwrap(); + let spec = solutions.entry_points[entry_point_id]; + (entry_point_name, (mod_name, func_name, spec)) + }) + .collect(), }) } diff --git a/vendor/morphic_lib/src/ir.rs b/vendor/morphic_lib/src/ir.rs index b6408ebc98..d283d84bed 100644 --- a/vendor/morphic_lib/src/ir.rs +++ b/vendor/morphic_lib/src/ir.rs @@ -6,10 +6,12 @@ use smallvec::SmallVec; use crate::api::{CalleeSpecVarId, UpdateModeVarId}; -use crate::name_cache::{ConstId, FuncId}; +use crate::name_cache::{ConstId, EntryPointId, FuncId, NamedTypeId}; use crate::type_cache::TypeId; use crate::util::blocks::Blocks; -use crate::util::flat_slices::{FlatSlices, Slice}; +use crate::util::flat_slices::FlatSlices; +use crate::util::id_type::Count; +use crate::util::id_vec::IdVec; use crate::util::op_graph::OpGraph; use crate::util::strongly_connected::{strongly_connected, SccKind}; @@ -123,6 +125,13 @@ pub(crate) enum JumpTarget { Ret, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub(crate) enum Predecessor { + Block(BlockId), + Entry, +} + +pub(crate) const PREDECESSORS_INLINE_COUNT: usize = 8; pub(crate) const JUMP_TARGETS_INLINE_COUNT: usize = 8; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -132,6 +141,8 @@ pub(crate) struct BlockInfo { /// /// Invariant: If `param` is `Some`, it must point to a `BlockParam` value, not an `Op`. pub(crate) param: Option, + /// Blocks which jump to this block + pub(crate) predecessors: SmallVec<[Predecessor; PREDECESSORS_INLINE_COUNT]>, /// List of zero or more jump targets to nondeterministically choose from. pub(crate) jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, /// Optional argument which will be passed to the chosen jump target. @@ -149,6 +160,7 @@ id_type! { pub(crate) struct GraphBuilder { values: OpGraph, blocks: Blocks, + exit_blocks: SmallVec<[BlockId; 1]>, } impl GraphBuilder { @@ -156,6 +168,7 @@ impl GraphBuilder { GraphBuilder { values: OpGraph::new(), blocks: Blocks::new(), + exit_blocks: SmallVec::new(), } } @@ -182,6 +195,7 @@ impl GraphBuilder { self.values.count().0, BlockInfo { param: None, + predecessors: SmallVec::new(), jump_targets: SmallVec::new(), target_arg: None, }, @@ -200,6 +214,7 @@ impl GraphBuilder { self.values.count().0, BlockInfo { param: Some(param_id), + predecessors: SmallVec::new(), jump_targets: SmallVec::new(), target_arg: None, }, @@ -213,8 +228,22 @@ impl GraphBuilder { target_arg: Option, jump_targets: SmallVec<[JumpTarget; JUMP_TARGETS_INLINE_COUNT]>, ) { + for target in &jump_targets { + match target { + &JumpTarget::Block(successor) => { + self.blocks + .block_info_mut(successor) + .predecessors + .push(Predecessor::Block(block)); + } + JumpTarget::Ret => { + self.exit_blocks.push(block); + } + } + } let info = self.blocks.block_info_mut(block); info.target_arg = target_arg; + debug_assert!(info.jump_targets.is_empty()); info.jump_targets = jump_targets; } @@ -226,23 +255,37 @@ impl GraphBuilder { &self.blocks } - pub(crate) fn build(self, entry_block: BlockId) -> Graph { + pub(crate) fn build( + mut self, + entry_block: BlockId, + ret_type: TypeId, + update_mode_vars: Count, + callee_spec_vars: Count, + ) -> Graph { debug_assert!(entry_block < self.blocks.block_count()); - let rev_sccs = strongly_connected(self.blocks.block_count(), |block| { + self.blocks + .block_info_mut(entry_block) + .predecessors + .push(Predecessor::Entry); + let sccs = strongly_connected(self.blocks.block_count(), |block| { self.blocks .block_info(block) - .jump_targets + .predecessors .iter() - .filter_map(|&jump_target| match jump_target { - JumpTarget::Ret => None, - JumpTarget::Block(target) => Some(target), + .filter_map(|&pred| match pred { + Predecessor::Entry => None, + Predecessor::Block(pred_block) => Some(pred_block), }) }); Graph { values: self.values, blocks: self.blocks, entry_block, - rev_sccs, + exit_blocks: self.exit_blocks, + ret_type, + sccs, + update_mode_vars, + callee_spec_vars, } } } @@ -252,14 +295,15 @@ pub(crate) struct Graph { values: OpGraph, blocks: Blocks, entry_block: BlockId, - - // Invariant: `rev_sccs` must be stored in *reverse* topological order. If an SCC 'A' can jump - // to an SCC 'B', then 'A' must appear *after* 'B' in `rev_sccs`. - // - // We don't store the SCCs in topological order because control flow graph edges point from - // *source block* to *target block*, so running Tarjan's algorithm on the control flow graph - // gives us a reverse topological sort rather than a topological sort. - rev_sccs: FlatSlices, + // We use an inline capacity of 1 here because, in the current implementation of `preprocess`, + // there is always exactly one exit block per function. However, this is no fundamental reason + // this must be so. + exit_blocks: SmallVec<[BlockId; 1]>, + ret_type: TypeId, + // Invariant: `sccs` is strored in topological order. + sccs: FlatSlices, + update_mode_vars: Count, + callee_spec_vars: Count, } impl Graph { @@ -275,20 +319,24 @@ impl Graph { self.entry_block } - pub(crate) fn rev_sccs(&self) -> &FlatSlices { - &self.rev_sccs + pub(crate) fn exit_blocks(&self) -> &[BlockId] { + &self.exit_blocks } - /// Iterate over sccs in topological order. - /// - /// IF an SCC 'A' can jump to an SCC 'B', then 'A' is guaranteed to appear *before* 'B' in the - /// returned iterator. - pub(crate) fn iter_sccs(&self) -> impl Iterator> + '_ { - self.rev_sccs - .count() - .iter() - .rev() - .map(move |scc_id| self.rev_sccs.get(scc_id)) + pub(crate) fn ret_type(&self) -> TypeId { + self.ret_type + } + + pub(crate) fn sccs(&self) -> &FlatSlices { + &self.sccs + } + + pub(crate) fn update_mode_vars(&self) -> Count { + self.update_mode_vars + } + + pub(crate) fn callee_spec_vars(&self) -> Count { + self.callee_spec_vars } } @@ -301,3 +349,11 @@ pub(crate) struct FuncDef { pub(crate) struct ConstDef { pub(crate) graph: Graph, } + +#[derive(Clone, Debug)] +pub(crate) struct Program { + pub(crate) named_types: IdVec, + pub(crate) funcs: IdVec, + pub(crate) consts: IdVec, + pub(crate) entry_points: IdVec, +} diff --git a/vendor/morphic_lib/src/lib.rs b/vendor/morphic_lib/src/lib.rs index a1470770dc..3b72a6227b 100644 --- a/vendor/morphic_lib/src/lib.rs +++ b/vendor/morphic_lib/src/lib.rs @@ -4,6 +4,7 @@ #[macro_use] mod util; +mod analyze; mod api; mod bindings; mod ir; diff --git a/vendor/morphic_lib/src/preprocess.rs b/vendor/morphic_lib/src/preprocess.rs index f52e0f648b..36eccb21bd 100644 --- a/vendor/morphic_lib/src/preprocess.rs +++ b/vendor/morphic_lib/src/preprocess.rs @@ -265,7 +265,9 @@ struct FuncSig { ret_type: TypeId, } -pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { +pub(crate) fn preprocess( + program: &api::Program, +) -> Result<(NameCache, TypeCache, ir::Program), Error> { let mut nc = NameCache::default(); let mut tc = TypeCache::default(); @@ -348,15 +350,15 @@ pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { const_sigs: &const_sigs, }; - for (func_id, func_def) in &funcs { + let preprocessed_funcs = funcs.try_map(|func_id, func_def| { preprocess_func_def(&mut tc, ctx, func_def, &func_body_types[func_id]) - .map_err(Error::annotate_func_def(&nc, func_id))?; - } + .map_err(Error::annotate_func_def(&nc, func_id)) + })?; - for (const_id, const_def) in &consts { + let preprocessed_consts = consts.try_map(|const_id, const_def| { preprocess_const_def(&mut tc, ctx, const_def, &const_body_types[const_id]) - .map_err(Error::annotate_const_def(&nc, const_id))?; - } + .map_err(Error::annotate_const_def(&nc, const_id)) + })?; let mut entry_points = IdVec::::new(); for (entry_point_name, (mod_, func)) in &program.entry_points { @@ -383,7 +385,16 @@ pub(crate) fn preprocess(program: &api::Program) -> Result<(), Error> { debug_assert_eq!(nc_id, pushed_id); } - Ok(()) + Ok(( + nc, + tc, + ir::Program { + named_types: typedef_contents, + funcs: preprocessed_funcs, + consts: preprocessed_consts, + entry_points, + }, + )) } #[derive(Clone, Copy, Debug)] @@ -1165,7 +1176,12 @@ fn preprocess_func_def( )?; graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); Ok(ir::FuncDef { - graph: graph_builder.build(entry_block), + graph: graph_builder.build( + entry_block, + body_types[func_def.ret_type], + func_def.builder.expr_builder.update_mode_vars.count(), + func_def.builder.expr_builder.callee_spec_vars.count(), + ), }) } @@ -1199,6 +1215,11 @@ fn preprocess_const_def( )?; graph_builder.set_jump_targets(final_block, Some(ret_val), smallvec![ir::JumpTarget::Ret]); Ok(ir::ConstDef { - graph: graph_builder.build(entry_block), + graph: graph_builder.build( + entry_block, + body_types[const_def.type_], + const_def.builder.expr_builder.update_mode_vars.count(), + const_def.builder.expr_builder.callee_spec_vars.count(), + ), }) } diff --git a/vendor/morphic_lib/src/type_cache.rs b/vendor/morphic_lib/src/type_cache.rs index beb16b182b..d9b1b9dd18 100644 --- a/vendor/morphic_lib/src/type_cache.rs +++ b/vendor/morphic_lib/src/type_cache.rs @@ -7,6 +7,8 @@ id_type! { pub TypeId(u32); } +// TODO: Add slot information + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum TypeData { Named { named: NamedTypeId }, diff --git a/vendor/morphic_lib/src/util/blocks.rs b/vendor/morphic_lib/src/util/blocks.rs index 22c182cbfa..56a1ce60a0 100644 --- a/vendor/morphic_lib/src/util/blocks.rs +++ b/vendor/morphic_lib/src/util/blocks.rs @@ -14,6 +14,7 @@ struct BlockFrag { min_val: ValId, /// Exclusive bound max_val: ValId, + prev: Option, next: Option, } @@ -57,6 +58,7 @@ impl Blocks { let frag = BlockFrag { min_val: start_hint.clone(), max_val: start_hint, + prev: None, next: None, }; let frag_id = self.frags.push(frag); @@ -77,6 +79,7 @@ impl Blocks { let new_tail = BlockFrag { min_val: val_id.clone(), max_val: ValId::from_index_or_panic(val_id.to_index() + 1), + prev: Some(block.tail), next: None, }; let new_tail_id = self.frags.push(new_tail); @@ -113,4 +116,24 @@ impl Blocks { Some(this_val) }) } + + pub fn block_values_rev(&self, block_id: BlockId) -> impl Iterator + '_ { + let mut frag = &self.frags[self.blocks[block_id].tail]; + let mut val = frag.max_val.clone(); + std::iter::from_fn(move || { + while val.to_index() <= frag.min_val.to_index() { + match frag.prev { + Some(prev) => { + frag = &self.frags[prev]; + val = frag.max_val.clone(); + } + None => { + return None; + } + } + } + val = ValId::from_index_unchecked(val.to_index() - 1); + Some(val.clone()) + }) + } } diff --git a/vendor/morphic_lib/src/util/flat_slices.rs b/vendor/morphic_lib/src/util/flat_slices.rs index fe72cce7e8..ea56c2a624 100644 --- a/vendor/morphic_lib/src/util/flat_slices.rs +++ b/vendor/morphic_lib/src/util/flat_slices.rs @@ -99,14 +99,16 @@ impl FlatSlices { items: &mut self.flat_data[start..end], } } + + pub fn iter(&self) -> impl Iterator)> { + self.count().iter().map(move |i| (i.clone(), self.get(i))) + } } impl fmt::Debug for FlatSlices { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map() - .entries(self.count().iter().map(|i| (i.clone(), self.get(i)))) - .finish() + f.debug_map().entries(self.iter()).finish() } } diff --git a/vendor/morphic_lib/src/util/get2_mut.rs b/vendor/morphic_lib/src/util/get2_mut.rs new file mode 100644 index 0000000000..8359e118ab --- /dev/null +++ b/vendor/morphic_lib/src/util/get2_mut.rs @@ -0,0 +1,16 @@ +use std::cmp::Ordering; + +// inspired by https://docs.rs/generational-arena/0.2.8/generational_arena/struct.Arena.html#method.get2_mut +pub fn get2_mut(slice: &mut [T], i: usize, j: usize) -> Option<(&mut T, &mut T)> { + match i.cmp(&j) { + Ordering::Less => { + let (l, r) = slice.split_at_mut(j); + Some((&mut l[i], &mut r[0])) + } + Ordering::Greater => { + let (l, r) = slice.split_at_mut(i); + Some((&mut r[0], &mut l[j])) + } + Ordering::Equal => None, + } +} diff --git a/vendor/morphic_lib/src/util/id_bi_map.rs b/vendor/morphic_lib/src/util/id_bi_map.rs index 00b04c2142..876574c49f 100644 --- a/vendor/morphic_lib/src/util/id_bi_map.rs +++ b/vendor/morphic_lib/src/util/id_bi_map.rs @@ -67,4 +67,8 @@ impl IdBiMap { pub fn get_by_val(&self, val: &V) -> Option { self.val_to_key.get(val).cloned() } + + pub fn iter(&self) -> impl Iterator { + self.key_to_val.iter() + } } diff --git a/vendor/morphic_lib/src/util/id_vec.rs b/vendor/morphic_lib/src/util/id_vec.rs index ab222559ea..52c6553502 100644 --- a/vendor/morphic_lib/src/util/id_vec.rs +++ b/vendor/morphic_lib/src/util/id_vec.rs @@ -6,6 +6,7 @@ use std::ops::{Index, IndexMut}; use std::slice; use std::vec; +use crate::util::get2_mut::get2_mut; use crate::util::id_type::{Count, Id}; #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -129,6 +130,13 @@ impl IdVec { } } + pub fn filled_with_indexed(count: Count, mut f: impl FnMut(K) -> V) -> Self { + IdVec { + key: PhantomData, + items: count.iter().map(|k| f(k)).collect(), + } + } + pub fn items(&self) -> &[V] { &self.items } @@ -234,6 +242,10 @@ impl IdVec { } Some(Self::from_items(items)) } + + pub fn get2_mut(&mut self, i: K, j: K) -> Option<(&mut V, &mut V)> { + get2_mut(&mut self.items, i.to_index(), j.to_index()) + } } impl> Index for IdVec { diff --git a/vendor/morphic_lib/src/util/mod.rs b/vendor/morphic_lib/src/util/mod.rs index d5fb572b08..a7f5146c48 100644 --- a/vendor/morphic_lib/src/util/mod.rs +++ b/vendor/morphic_lib/src/util/mod.rs @@ -9,8 +9,10 @@ pub mod forward_trait; pub mod blocks; pub mod flat_slices; +pub mod get2_mut; pub mod id_bi_map; pub mod id_vec; +pub mod norm_pair; pub mod op_graph; pub mod replace_none; pub mod strongly_connected; diff --git a/vendor/morphic_lib/src/util/norm_pair.rs b/vendor/morphic_lib/src/util/norm_pair.rs new file mode 100644 index 0000000000..f370b40cfa --- /dev/null +++ b/vendor/morphic_lib/src/util/norm_pair.rs @@ -0,0 +1,33 @@ +/// A normalized unordered pair, where the first component is always <= the second +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct NormPair(T, T); + +impl NormPair { + pub fn new(fst: T, snd: T) -> Self { + if fst <= snd { + NormPair(fst, snd) + } else { + NormPair(snd, fst) + } + } + + pub fn fst(&self) -> &T { + &self.0 + } + + pub fn snd(&self) -> &T { + &self.1 + } + + pub fn into_fst(self) -> T { + self.0 + } + + pub fn into_snd(self) -> T { + self.1 + } + + pub fn into_tuple(self) -> (T, T) { + (self.0, self.1) + } +} diff --git a/vendor/morphic_lib/tests/recursive.rs b/vendor/morphic_lib/tests/recursive.rs index 9e50172a50..629e19852d 100644 --- a/vendor/morphic_lib/tests/recursive.rs +++ b/vendor/morphic_lib/tests/recursive.rs @@ -1,6 +1,6 @@ use morphic_lib::{ BlockExpr, CalleeSpecVar, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, - ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateModeVar, + ModDefBuilder, ModName, ProgramBuilder, TypeContext, UpdateMode, UpdateModeVar, }; #[test] @@ -83,7 +83,8 @@ fn test_recursive() { .func_solutions(FuncName(b"rec"))? .spec(&rec_spec)?; - let _update_mode = rec_sol.update_mode(UpdateModeVar(b"mode"))?; + let update_mode = rec_sol.update_mode(UpdateModeVar(b"mode"))?; + assert_eq!(update_mode, UpdateMode::InPlace); Ok(()) } diff --git a/vendor/morphic_lib/tests/structures.rs b/vendor/morphic_lib/tests/structures.rs new file mode 100644 index 0000000000..7143e3f9e1 --- /dev/null +++ b/vendor/morphic_lib/tests/structures.rs @@ -0,0 +1,73 @@ +use morphic_lib::{ + BlockExpr, EntryPointName, Error, ExprContext, FuncDefBuilder, FuncName, ModDefBuilder, + ModName, ProgramBuilder, TypeContext, UpdateMode, UpdateModeVar, +}; + +#[test] +fn test_structures() { + fn run() -> Result<(), Error> { + let main_def = { + let mut f = FuncDefBuilder::new(); + let b = f.add_block(); + let h1 = f.add_new_heap_cell(b)?; + let h2 = f.add_new_heap_cell(b)?; + let t = f.add_make_tuple(b, &[h1, h2])?; + let heap_cell_type = f.add_heap_cell_type(); + let u1 = f.add_make_union(b, &[heap_cell_type, heap_cell_type], 0, h1)?; + let u2 = f.add_make_union(b, &[heap_cell_type, heap_cell_type], 1, h2)?; + let h3 = f.add_get_tuple_field(b, t, 0)?; + let h4 = f.add_get_tuple_field(b, t, 1)?; + let h5 = f.add_unwrap_union(b, u1, 0)?; + let h6 = f.add_unwrap_union(b, u2, 1)?; + f.add_touch(b, h3)?; + f.add_update_write_only(b, UpdateModeVar(b"mode1"), h1)?; + f.add_update_write_only(b, UpdateModeVar(b"mode2"), h2)?; + f.add_update_write_only(b, UpdateModeVar(b"mode3"), h5)?; + f.add_update_write_only(b, UpdateModeVar(b"mode4"), h6)?; + f.add_touch(b, h4)?; + let unit = f.add_make_tuple(b, &[])?; + let unit_type = f.add_tuple_type(&[])?; + f.build(unit_type, unit_type, BlockExpr(b, unit))? + }; + + let main_mod = { + let mut m = ModDefBuilder::new(); + m.add_func(FuncName(b"main"), main_def)?; + m.build()? + }; + + let program = { + let mut p = ProgramBuilder::new(); + p.add_mod(ModName(b"main"), main_mod)?; + p.add_entry_point(EntryPointName(b"main"), ModName(b"main"), FuncName(b"main"))?; + p.build()? + }; + + let program_sol = morphic_lib::solve(program)?; + + let (_, _, main_spec) = program_sol.entry_point_solution(EntryPointName(b"main"))?; + + let main_mod_sol = program_sol.mod_solutions(ModName(b"main"))?; + + let main_def_sol = main_mod_sol + .func_solutions(FuncName(b"main"))? + .spec(&main_spec)?; + + let mode1 = main_def_sol.update_mode(UpdateModeVar(b"mode1"))?; + let mode2 = main_def_sol.update_mode(UpdateModeVar(b"mode2"))?; + let mode3 = main_def_sol.update_mode(UpdateModeVar(b"mode3"))?; + let mode4 = main_def_sol.update_mode(UpdateModeVar(b"mode4"))?; + + assert_eq!(mode1, UpdateMode::InPlace); + assert_eq!(mode2, UpdateMode::Immutable); + assert_eq!(mode3, UpdateMode::InPlace); + assert_eq!(mode4, UpdateMode::Immutable); + + Ok(()) + } + + let result = run(); + if let Err(err) = result { + panic!("error: {}", err); + } +} From 79a4f2bbf7d039ca23ba63fe142ed2a3762f7222 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sat, 2 Oct 2021 22:07:12 +0100 Subject: [PATCH 70/96] Refactor SymbolStorage local_id code, and copy_memory SymbolStorage had some rather ad-hoc methods for extracting pieces of data. This change makes that more intentional and organised. Also cleaned up the API for the related function copy_memory, as it had lots of positional arguments with the same types. Created a struct for this just to name them and make the code clearer. --- compiler/gen_wasm/src/backend.rs | 204 ++++++++++++++++--------------- compiler/gen_wasm/src/lib.rs | 50 ++++---- compiler/gen_wasm/src/storage.rs | 146 +++++++++++++--------- 3 files changed, 217 insertions(+), 183 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index b9c6cd6900..b6b837acf4 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -13,8 +13,8 @@ use roc_mono::layout::{Builtin, Layout}; use crate::layout::WasmLayout; use crate::storage::{StackMemoryLocation, SymbolStorage}; use crate::{ - pop_stack_frame, push_stack_frame, round_up_to_alignment, LocalId, MemoryCopy, PTR_SIZE, - PTR_TYPE, + copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig, + LocalId, PTR_SIZE, PTR_TYPE, }; // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. @@ -164,7 +164,7 @@ impl<'a> WasmBackend<'a> { wasm_layout: WasmLayout, symbol: Symbol, kind: LocalKind, - ) -> SymbolStorage { + ) -> Option { let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32); match kind { @@ -176,32 +176,33 @@ impl<'a> WasmBackend<'a> { } } - let storage = match wasm_layout { - WasmLayout::LocalOnly(value_type, size) => SymbolStorage::Local { - local_id: next_local_id, - value_type, - size, - }, + let (maybe_local_id, storage) = match wasm_layout { + WasmLayout::LocalOnly(value_type, size) => ( + Some(next_local_id), + SymbolStorage::Local { + local_id: next_local_id, + value_type, + size, + }, + ), - WasmLayout::HeapMemory => SymbolStorage::Local { - local_id: next_local_id, - value_type: PTR_TYPE, - size: PTR_SIZE, - }, + WasmLayout::HeapMemory => ( + Some(next_local_id), + SymbolStorage::Local { + local_id: next_local_id, + value_type: PTR_TYPE, + size: PTR_SIZE, + }, + ), WasmLayout::StackMemory { size, alignment_bytes, } => { let location = match kind { - LocalKind::Parameter => StackMemoryLocation::ExternalPointer(next_local_id), + LocalKind::Parameter => StackMemoryLocation::CallerFrame(next_local_id), LocalKind::Variable => { - let offset = - round_up_to_alignment(self.stack_memory, alignment_bytes as i32); - - self.stack_memory = offset + size as i32; - match self.stack_frame_pointer { Some(_) => {} None => { @@ -209,20 +210,29 @@ impl<'a> WasmBackend<'a> { } }; - StackMemoryLocation::InternalOffset(offset as u32) + let offset = + round_up_to_alignment(self.stack_memory, alignment_bytes as i32); + + self.stack_memory = offset + size as i32; + + StackMemoryLocation::OwnFrame(offset as u32) } }; - SymbolStorage::StackMemory { - location, - size, - alignment_bytes, - } + + ( + None, + SymbolStorage::StackMemory { + location, + size, + alignment_bytes, + }, + ) } }; - self.symbol_storage_map.insert(symbol, storage.clone()); + self.symbol_storage_map.insert(symbol, storage); - storage + maybe_local_id } fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage { @@ -236,20 +246,42 @@ impl<'a> WasmBackend<'a> { fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId { let storage = self.get_symbol_storage(sym); - storage.local_id(self.stack_frame_pointer) + match storage { + SymbolStorage::Local { local_id, .. } => *local_id, + _ => { + panic!("{:?} does not have a local_id", sym); + } + } } + /// Load a symbol, e.g. for passing to a function call fn load_symbol(&mut self, sym: &Symbol) { - let storage = self.get_symbol_storage(sym); - let index: u32 = storage.local_id(self.stack_frame_pointer).0; - self.instructions.push(GetLocal(index)); + let storage = self.get_symbol_storage(sym).to_owned(); + match storage { + SymbolStorage::Local { local_id, .. } + | SymbolStorage::StackMemory { + location: StackMemoryLocation::CallerFrame(local_id), + .. + } => { + self.instructions.push(GetLocal(local_id.0)); + } + + SymbolStorage::StackMemory { + location: StackMemoryLocation::OwnFrame(offset), + .. + } => { + self.instructions.extend([ + GetLocal(self.stack_frame_pointer.unwrap().0), + I32Const(offset as i32), + I32Add, + ]); + } + } } /// start a loop that leaves a value on the stack fn start_loop_with_return(&mut self, value_type: ValueType) { self.block_depth += 1; - - // self.instructions.push(Loop(BlockType::NoResult)); self.instructions.push(Loop(BlockType::Value(value_type))); } @@ -276,7 +308,7 @@ impl<'a> WasmBackend<'a> { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later let storage = SymbolStorage::StackMemory { - location: StackMemoryLocation::ExternalPointer(LocalId(0)), + location: StackMemoryLocation::CallerFrame(LocalId(0)), size, alignment_bytes, }; @@ -289,16 +321,12 @@ impl<'a> WasmBackend<'a> { Stmt::Let(sym, expr, layout, following) => { let wasm_layout = WasmLayout::new(layout); - let local_id = self - .insert_local(wasm_layout, *sym, LocalKind::Variable) - .local_id(self.stack_frame_pointer); + let maybe_local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable); self.build_expr(sym, expr, layout)?; - // If this local is shared with the stack frame pointer, it's already assigned - match self.stack_frame_pointer { - Some(sfp) if sfp == local_id => {} - _ => self.instructions.push(SetLocal(local_id.0)), + if let Some(local_id) = maybe_local_id { + self.instructions.push(SetLocal(local_id.0)); } self.build_stmt(following, ret_layout)?; @@ -317,21 +345,23 @@ impl<'a> WasmBackend<'a> { alignment_bytes, } => { let (from_ptr, from_offset) = match location { - StackMemoryLocation::ExternalPointer(local_id) => (*local_id, 0), - StackMemoryLocation::InternalOffset(offset) => { + StackMemoryLocation::CallerFrame(local_id) => (*local_id, 0), + StackMemoryLocation::OwnFrame(offset) => { (self.stack_frame_pointer.unwrap(), *offset) } }; - let copy = MemoryCopy { - from_ptr, - from_offset, - to_ptr: LocalId(0), - to_offset: 0, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(&mut self.instructions); + copy_memory( + &mut self.instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr: LocalId(0), + to_offset: 0, + size: *size, + alignment_bytes: *alignment_bytes, + }, + ); } Local { local_id, .. } => { @@ -400,11 +430,10 @@ impl<'a> WasmBackend<'a> { let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); for parameter in parameters.iter() { let wasm_layout = WasmLayout::new(¶meter.layout); - let local_id = self - .insert_local(wasm_layout, parameter.symbol, LocalKind::Variable) - .local_id(self.stack_frame_pointer); - - jp_parameter_local_ids.push(local_id); + let maybe_local_id = + self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable); + let jp_param_id = maybe_local_id.unwrap(); + jp_parameter_local_ids.push(jp_param_id); } self.start_block(BlockType::NoResult); @@ -526,9 +555,22 @@ impl<'a> WasmBackend<'a> { let storage = self.get_symbol_storage(sym).to_owned(); if let Layout::Struct(field_layouts) = layout { - let (local_id, size) = match storage { - SymbolStorage::StackMemory { size, .. } => { - (storage.local_id(self.stack_frame_pointer), size) + match storage { + SymbolStorage::StackMemory { location, size, .. } => { + if size > 0 { + let (local_id, struct_offset) = + location.local_and_offset(self.stack_frame_pointer); + let mut field_offset = struct_offset; + for (field, _) in fields.iter().zip(field_layouts.iter()) { + field_offset += self.copy_symbol_to_pointer_at_offset( + local_id, + field_offset, + field, + ); + } + } else { + return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); + } } _ => { return Err(format!( @@ -537,20 +579,14 @@ impl<'a> WasmBackend<'a> { )); } }; - - if size > 0 { - let mut relative_offset = 0; - for (field, _) in fields.iter().zip(field_layouts.iter()) { - relative_offset += - self.copy_symbol_to_pointer_at_offset(local_id, relative_offset, field); - } - } else { - return Err(format!("Not supported yet: zero-size struct at {:?}", sym)); - } } else { // Struct expression but not Struct layout => single element. Copy it. let field_storage = self.get_symbol_storage(&fields[0]).to_owned(); - self.copy_storage(&storage, &field_storage); + storage.copy_from( + &field_storage, + &mut self.instructions, + self.stack_frame_pointer, + ); } Ok(()) } @@ -570,30 +606,6 @@ impl<'a> WasmBackend<'a> { ) } - fn copy_storage(&mut self, to: &SymbolStorage, from: &SymbolStorage) { - let has_stack_memory = to.has_stack_memory(); - debug_assert!(from.has_stack_memory() == has_stack_memory); - - if !has_stack_memory { - debug_assert!(from.value_type() == to.value_type()); - self.instructions - .push(GetLocal(from.local_id(self.stack_frame_pointer).0)); - self.instructions - .push(SetLocal(to.local_id(self.stack_frame_pointer).0)); - } else { - let (size, alignment_bytes) = from.stack_size_and_alignment(); - let copy = MemoryCopy { - from_ptr: from.local_id(self.stack_frame_pointer), - to_ptr: to.local_id(self.stack_frame_pointer), - from_offset: from.address_offset().unwrap(), - to_offset: to.address_offset().unwrap(), - size, - alignment_bytes, - }; - copy.generate(&mut self.instructions); - } - } - fn build_call_low_level( &mut self, lowlevel: &LowLevel, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index 394b59b840..a5517cb4e4 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -121,7 +121,7 @@ fn encode_alignment(bytes: u32) -> u32 { } } -pub struct MemoryCopy { +pub struct CopyMemoryConfig { from_ptr: LocalId, from_offset: u32, to_ptr: LocalId, @@ -130,31 +130,29 @@ pub struct MemoryCopy { alignment_bytes: u32, } -impl MemoryCopy { - pub fn generate(&self, instructions: &mut Vec) { - let alignment_flag = encode_alignment(self.alignment_bytes); - let mut i = 0; - while self.size - i >= 8 { - instructions.push(GetLocal(self.to_ptr.0)); - instructions.push(GetLocal(self.from_ptr.0)); - instructions.push(I64Load(alignment_flag, i + self.from_offset)); - instructions.push(I64Store(alignment_flag, i + self.to_offset)); - i += 8; - } - if self.size - i >= 4 { - instructions.push(GetLocal(self.to_ptr.0)); - instructions.push(GetLocal(self.from_ptr.0)); - instructions.push(I32Load(alignment_flag, i + self.from_offset)); - instructions.push(I32Store(alignment_flag, i + self.to_offset)); - i += 4; - } - while self.size - i > 0 { - instructions.push(GetLocal(self.to_ptr.0)); - instructions.push(GetLocal(self.from_ptr.0)); - instructions.push(I32Load8U(alignment_flag, i + self.from_offset)); - instructions.push(I32Store8(alignment_flag, i + self.to_offset)); - i += 1; - } +pub fn copy_memory(instructions: &mut Vec, config: CopyMemoryConfig) { + let alignment_flag = encode_alignment(config.alignment_bytes); + let mut i = 0; + while config.size - i >= 8 { + instructions.push(GetLocal(config.to_ptr.0)); + instructions.push(GetLocal(config.from_ptr.0)); + instructions.push(I64Load(alignment_flag, i + config.from_offset)); + instructions.push(I64Store(alignment_flag, i + config.to_offset)); + i += 8; + } + if config.size - i >= 4 { + instructions.push(GetLocal(config.to_ptr.0)); + instructions.push(GetLocal(config.from_ptr.0)); + instructions.push(I32Load(alignment_flag, i + config.from_offset)); + instructions.push(I32Store(alignment_flag, i + config.to_offset)); + i += 4; + } + while config.size - i > 0 { + instructions.push(GetLocal(config.to_ptr.0)); + instructions.push(GetLocal(config.from_ptr.0)); + instructions.push(I32Load8U(alignment_flag, i + config.from_offset)); + instructions.push(I32Store8(alignment_flag, i + config.to_offset)); + i += 1; } } diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index f1eb565097..cc70956b84 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -1,14 +1,25 @@ -use crate::{LocalId, MemoryCopy, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; +use crate::{copy_memory, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8}; use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] pub enum StackMemoryLocation { - ExternalPointer(LocalId), - InternalOffset(u32), + CallerFrame(LocalId), + OwnFrame(u32), +} + +impl StackMemoryLocation { + pub fn local_and_offset(&self, stack_frame_pointer: Option) -> (LocalId, u32) { + match self { + Self::CallerFrame(local_id) => (*local_id, 0), + Self::OwnFrame(offset) => (stack_frame_pointer.unwrap(), *offset), + } + } } #[derive(Debug, Clone)] pub enum SymbolStorage { + // TODO: implicit storage in the VM stack + // TODO: const data storage Local { local_id: LocalId, value_type: ValueType, @@ -22,58 +33,69 @@ pub enum SymbolStorage { } impl SymbolStorage { - pub fn local_id(&self, stack_frame_pointer: Option) -> LocalId { - use StackMemoryLocation::*; - match self { - Self::Local { local_id, .. } => *local_id, - Self::StackMemory { location, .. } => match *location { - ExternalPointer(local_id) => local_id, - InternalOffset(_) => stack_frame_pointer.unwrap(), - }, - } - } - - pub fn value_type(&self) -> ValueType { - match self { - Self::Local { value_type, .. } => *value_type, - Self::StackMemory { .. } => ValueType::I32, - } - } - - pub fn has_stack_memory(&self) -> bool { - match self { - Self::Local { .. } => false, - Self::StackMemory { .. } => true, - } - } - - pub fn address_offset(&self) -> Option { - use StackMemoryLocation::*; - match self { - Self::Local { .. } => None, - Self::StackMemory { location, .. } => match *location { - ExternalPointer(_) => Some(0), - InternalOffset(offset) => Some(offset), - }, - } - } - - pub fn stack_size_and_alignment(&self) -> (u32, u32) { - match self { - Self::StackMemory { - size, - alignment_bytes, - .. - } => (*size, *alignment_bytes), - - _ => (0, 0), + /// generate code to copy from another storage of the same type + pub fn copy_from( + &self, + from: &Self, + instructions: &mut Vec, + stack_frame_pointer: Option, + ) { + match (self, from) { + ( + Self::Local { + local_id: to_local_id, + .. + }, + Self::Local { + local_id: from_local_id, + .. + }, + ) => { + instructions.push(GetLocal(from_local_id.0)); + instructions.push(SetLocal(to_local_id.0)); + } + ( + Self::StackMemory { + location: to_location, + size: to_size, + alignment_bytes: to_alignment_bytes, + }, + Self::StackMemory { + location: from_location, + size: from_size, + alignment_bytes: from_alignment_bytes, + }, + ) => { + let (from_ptr, from_offset) = from_location.local_and_offset(stack_frame_pointer); + let (to_ptr, to_offset) = to_location.local_and_offset(stack_frame_pointer); + debug_assert!(*to_size == *from_size); + debug_assert!(*to_alignment_bytes == *from_alignment_bytes); + copy_memory( + instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size: *from_size, + alignment_bytes: *from_alignment_bytes, + }, + ); + } + _ => { + panic!( + "Cannot copy different storage types {:?} to {:?}", + from, self + ); + } } } + /// Generate code to copy to a memory address (such as a struct index) pub fn copy_to_memory( &self, instructions: &mut Vec, - to_pointer: LocalId, + to_ptr: LocalId, to_offset: u32, stack_frame_pointer: Option, ) -> u32 { @@ -95,27 +117,29 @@ impl SymbolStorage { panic!("Cannot store {:?} with alignment of {:?}", value_type, size); } }; - instructions.push(GetLocal(to_pointer.0)); + instructions.push(GetLocal(to_ptr.0)); instructions.push(GetLocal(local_id.0)); instructions.push(store_instruction); *size } Self::StackMemory { + location, size, alignment_bytes, - .. } => { - let local_id = self.local_id(stack_frame_pointer); - let copy = MemoryCopy { - from_ptr: local_id, - from_offset: 0, - to_ptr: to_pointer, - to_offset, - size: *size, - alignment_bytes: *alignment_bytes, - }; - copy.generate(instructions); + let (from_ptr, from_offset) = location.local_and_offset(stack_frame_pointer); + copy_memory( + instructions, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size: *size, + alignment_bytes: *alignment_bytes, + }, + ); *size } } From 5a8547f2d4dfe666a83861b5ebcafcc8e8dd3578 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:05:17 +0200 Subject: [PATCH 71/96] trick morphic into specializing closure callers --- compiler/mono/src/alias_analysis.rs | 112 +++++++++++++++++++++------- 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index bff15284a7..5a7e3e1e86 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -9,8 +9,10 @@ use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use std::convert::TryFrom; -use crate::ir::{Call, CallType, Expr, ListLiteralElement, Literal, ModifyRc, Proc, Stmt}; -use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; +use crate::ir::{ + Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, Proc, Stmt, +}; +use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout}; // just using one module for now pub const MOD_APP: ModName = ModName(b"UserApp"); @@ -145,25 +147,33 @@ where }; m.add_const(STATIC_LIST_NAME, static_list_def)?; - // the entry point wrapper - let roc_main_bytes = func_name_bytes_help( - entry_point.symbol, - entry_point.layout.arguments.iter().copied(), - entry_point.layout.result, - ); - let roc_main = FuncName(&roc_main_bytes); - - let entry_point_function = build_entry_point(entry_point.layout, roc_main)?; - let entry_point_name = FuncName(ENTRY_POINT_NAME); - m.add_func(entry_point_name, entry_point_function)?; - let mut type_definitions = MutSet::default(); + let mut host_exposed_functions = Vec::new(); // all other functions for proc in procs { let bytes = func_name_bytes(proc); let func_name = FuncName(&bytes); + if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts { + for (_, (symbol, top_level, layout)) in aliases { + match layout { + RawFunctionLayout::Function(_, _, _) => { + let it = top_level.arguments.iter().copied(); + let bytes = func_name_bytes_help(*symbol, it, top_level.result); + + host_exposed_functions.push((bytes, top_level.arguments)); + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + let it = std::iter::once(Layout::Struct(&[])); + let bytes = func_name_bytes_help(*symbol, it, top_level.result); + + host_exposed_functions.push((bytes, top_level.arguments)); + } + } + } + } + if DEBUG { eprintln!( "{:?}: {:?} with {:?} args", @@ -180,6 +190,19 @@ where m.add_func(func_name, spec)?; } + // the entry point wrapper + let roc_main_bytes = func_name_bytes_help( + entry_point.symbol, + entry_point.layout.arguments.iter().copied(), + entry_point.layout.result, + ); + let roc_main = FuncName(&roc_main_bytes); + + let entry_point_function = + build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?; + let entry_point_name = FuncName(ENTRY_POINT_NAME); + m.add_func(entry_point_name, entry_point_function)?; + for union_layout in type_definitions { let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name = TypeName(&type_name_bytes); @@ -219,23 +242,62 @@ where morphic_lib::solve(program) } -fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Result { +fn build_entry_point( + layout: crate::ir::ProcLayout, + func_name: FuncName, + host_exposed_functions: &[([u8; SIZE], &[Layout])], +) -> Result { let mut builder = FuncDefBuilder::new(); - let block = builder.add_block(); + let outer_block = builder.add_block(); - // to the modelling language, the arguments appear out of thin air - let argument_type = build_tuple_type(&mut builder, layout.arguments)?; - let argument = builder.add_unknown_with(block, &[], argument_type)?; + let mut cases = Vec::new(); - let name_bytes = [0; 16]; - let spec_var = CalleeSpecVar(&name_bytes); - let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?; + { + let block = builder.add_block(); + + // to the modelling language, the arguments appear out of thin air + let argument_type = build_tuple_type(&mut builder, layout.arguments)?; + let argument = builder.add_unknown_with(block, &[], argument_type)?; + + let name_bytes = [0; 16]; + let spec_var = CalleeSpecVar(&name_bytes); + let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?; + + // to the modelling language, the result disappears into the void + let unit_type = builder.add_tuple_type(&[])?; + let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + + cases.push(BlockExpr(block, unit_value)); + } + + // add fake calls to host-exposed functions so they are specialized + for (name_bytes, layouts) in host_exposed_functions { + let host_exposed_func_name = FuncName(name_bytes); + + if host_exposed_func_name == func_name { + continue; + } + + let block = builder.add_block(); + + let type_id = layout_spec(&mut builder, &Layout::Struct(layouts))?; + + let argument = builder.add_unknown_with(block, &[], type_id)?; + + let spec_var = CalleeSpecVar(name_bytes); + let result = + builder.add_call(block, spec_var, MOD_APP, host_exposed_func_name, argument)?; + + let unit_type = builder.add_tuple_type(&[])?; + let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + + cases.push(BlockExpr(block, unit_value)); + } - // to the modelling language, the result disappears into the void let unit_type = builder.add_tuple_type(&[])?; - let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; + let unit_value = builder.add_choice(outer_block, &cases)?; - let root = BlockExpr(block, unit_value); + let root = BlockExpr(outer_block, unit_value); let spec = builder.build(unit_type, unit_type, root)?; Ok(spec) From 7e6a3431e224002337d761bbfd9901aca9cd1784 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:07:38 +0200 Subject: [PATCH 72/96] trick morphic into updating a value that comes from the host --- compiler/mono/src/alias_analysis.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 5a7e3e1e86..96d6ddb292 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -242,6 +242,22 @@ where morphic_lib::solve(program) } +/// if you want an "escape hatch" which allows you construct "best-case scenario" values +/// of an arbitrary type in much the same way that 'unknown_with' allows you to construct +/// "worst-case scenario" values of an arbitrary type, you can use the following terrible hack: +/// use 'add_make_union' to construct an instance of variant 0 of a union type 'union {(), your_type}', +/// and then use 'add_unwrap_union' to extract variant 1 from the value you just constructed. +/// In the current implementation (but not necessarily in future versions), +/// I can promise this will effectively give you a value of type 'your_type' +/// all of whose heap cells are considered unique and mutable. +fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId) -> Result { + let variant_types = vec![builder.add_tuple_type(&[])?, type_id]; + let unit = builder.add_make_tuple(block, &[])?; + let value = builder.add_make_union(block, &variant_types, 0, unit)?; + + builder.add_unwrap_union(block, value, 1) +} + fn build_entry_point( layout: crate::ir::ProcLayout, func_name: FuncName, @@ -257,7 +273,12 @@ fn build_entry_point( // to the modelling language, the arguments appear out of thin air let argument_type = build_tuple_type(&mut builder, layout.arguments)?; - let argument = builder.add_unknown_with(block, &[], argument_type)?; + + // does not make any assumptions about the input + // let argument = builder.add_unknown_with(block, &[], argument_type)?; + + // assumes the input can be updated in-place + let argument = terrible_hack(&mut builder, block, argument_type)?; let name_bytes = [0; 16]; let spec_var = CalleeSpecVar(&name_bytes); From fa57ff88a53dd544dd38d5deead045427db42151 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:16:52 +0200 Subject: [PATCH 73/96] disable list of constants in read-only section --- compiler/gen_llvm/src/llvm/build.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 9864f277a8..28e21e930a 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -2185,7 +2185,10 @@ fn list_literal<'a, 'ctx, 'env>( let list_length = elems.len(); let list_length_intval = env.ptr_int().const_int(list_length as _, false); - if element_type.is_int_type() { + // TODO re-enable, currently causes morphic segfaults because it tries to update + // constants in-place... + // if element_type.is_int_type() { + if false { let element_type = element_type.into_int_type(); let element_width = elem_layout.stack_size(env.ptr_bytes); let size = list_length * element_width as usize; @@ -2228,15 +2231,16 @@ fn list_literal<'a, 'ctx, 'env>( let val = load_symbol(scope, symbol); let intval = val.into_int_value(); - if intval.is_const() { - global_elements.push(intval); - } else { - is_all_constant = false; + // here we'd like to furthermore check for intval.is_const(). + // if all elements are const for LLVM, we could make the array a constant. + // BUT morphic does not know about this, and could allow us to modify that + // array in-place. That would cause a segfault. So, we'll have to find + // constants ourselves and cannot lean on LLVM here. + is_all_constant = false; - runtime_evaluated_elements.push((index, val)); + runtime_evaluated_elements.push((index, val)); - global_elements.push(element_type.get_undef()); - } + global_elements.push(element_type.get_undef()); } }; } From 3d7b42deba89961f74b9333b43e981768b193da4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:17:15 +0200 Subject: [PATCH 74/96] better error message for when morphic did not specialize --- compiler/gen_llvm/src/llvm/build.rs | 33 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 28e21e930a..e1558897e9 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4109,19 +4109,28 @@ pub fn build_proc<'a, 'ctx, 'env>( let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let mut it = func_solutions.specs(); - let func_spec = it.next().unwrap(); - debug_assert!( - it.next().is_none(), - "we expect only one specialization of this symbol" - ); + let evaluator = match it.next() { + Some(func_spec) => { + debug_assert!( + it.next().is_none(), + "we expect only one specialization of this symbol" + ); - let evaluator = function_value_by_func_spec( - env, - *func_spec, - symbol, - top_level.arguments, - &top_level.result, - ); + function_value_by_func_spec( + env, + *func_spec, + symbol, + top_level.arguments, + &top_level.result, + ) + } + None => { + // morphic did not generate a specialization for this function, + // therefore it must actually be unused. + // An example is our closure callers + panic!("morphic did not specialize {:?}", symbol); + } + }; let ident_string = proc.name.as_str(&env.interns); let fn_name: String = format!("{}_1", ident_string); From dbb8acbe6753c3678b1d8632f6207bef83c360ed Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:25:35 +0200 Subject: [PATCH 75/96] add in-place List.swap --- compiler/builtins/bitcode/src/list.zig | 16 +++++++++++----- compiler/gen_llvm/src/llvm/build.rs | 11 +++++++---- compiler/gen_llvm/src/llvm/build_list.rs | 12 ++++++++++++ 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 22e76054ea..09890b5b12 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -763,18 +763,24 @@ pub fn listSwap( element_width: usize, index_1: usize, index_2: usize, + can_update_in_place: bool, ) callconv(.C) RocList { const size = list.len(); - if (index_1 >= size or index_2 >= size) { + if (index_1 == index_2 or index_1 >= size or index_2 >= size) { // Either index out of bounds so we just return return list; } - const newList = list.makeUnique(alignment, element_width); + const newList = blk: { + if (can_update_in_place) { + break :blk list; + } else { + break :blk list.makeUnique(alignment, element_width); + } + }; - if (newList.bytes) |source_ptr| { - swapElements(source_ptr, element_width, index_1, index_2); - } + const source_ptr = @ptrCast([*]u8, newList.bytes); + swapElements(source_ptr, element_width, index_1, index_2); return newList; } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index e1558897e9..f47a6e7046 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -934,7 +934,9 @@ pub fn build_exp_call<'a, 'ctx, 'env>( CallType::LowLevel { op, update_mode } => { let bytes = update_mode.to_bytes(); let update_var = UpdateModeVar(&bytes); - let update_mode = func_spec_solutions.update_mode(update_var).ok(); + let update_mode = func_spec_solutions + .update_mode(update_var) + .unwrap_or(UpdateMode::Immutable); run_low_level( env, @@ -2229,13 +2231,13 @@ fn list_literal<'a, 'ctx, 'env>( } ListLiteralElement::Symbol(symbol) => { let val = load_symbol(scope, symbol); - let intval = val.into_int_value(); // here we'd like to furthermore check for intval.is_const(). // if all elements are const for LLVM, we could make the array a constant. // BUT morphic does not know about this, and could allow us to modify that // array in-place. That would cause a segfault. So, we'll have to find // constants ourselves and cannot lean on LLVM here. + is_all_constant = false; runtime_evaluated_elements.push((index, val)); @@ -4790,7 +4792,7 @@ fn run_low_level<'a, 'ctx, 'env>( layout: &Layout<'a>, op: LowLevel, args: &[Symbol], - update_mode: Option, + update_mode: UpdateMode, // expect_failed: *const (), ) -> BasicValueEnum<'ctx> { use LowLevel::*; @@ -4990,6 +4992,7 @@ fn run_low_level<'a, 'ctx, 'env>( index_1.into_int_value(), index_2.into_int_value(), element_layout, + update_mode, ), _ => unreachable!("Invalid layout {:?} in List.swap", list_layout), } @@ -5317,7 +5320,7 @@ fn run_low_level<'a, 'ctx, 'env>( index.into_int_value(), element, element_layout, - update_mode.unwrap(), + update_mode, ), _ => unreachable!("invalid dict layout"), } diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 2fe94d4cb5..93be451ddb 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -17,6 +17,16 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_mono::layout::{Builtin, Layout, LayoutIds}; +fn pass_update_mode<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + update_mode: UpdateMode, +) -> BasicValueEnum<'ctx> { + match update_mode { + UpdateMode::Immutable => env.context.bool_type().const_zero().into(), + UpdateMode::InPlace => env.context.bool_type().const_int(1, false).into(), + } +} + fn list_returned_from_zig<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, output: BasicValueEnum<'ctx>, @@ -267,6 +277,7 @@ pub fn list_swap<'a, 'ctx, 'env>( index_1: IntValue<'ctx>, index_2: IntValue<'ctx>, element_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { call_bitcode_fn_returns_list( env, @@ -276,6 +287,7 @@ pub fn list_swap<'a, 'ctx, 'env>( layout_width(env, element_layout), index_1.into(), index_2.into(), + pass_update_mode(env, update_mode), ], bitcode::LIST_SWAP, ) From 9e97a09a8720307546ab6da320a0e0c9f0319e67 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 00:57:54 +0200 Subject: [PATCH 76/96] check if exposed function is defined already --- compiler/gen_llvm/src/llvm/build.rs | 178 ++++++++++++++++------------ 1 file changed, 102 insertions(+), 76 deletions(-) diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index f47a6e7046..17c79cc3a3 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -5637,89 +5637,115 @@ fn build_foreign_symbol<'a, 'ctx, 'env>( let builder = env.builder; let context = env.context; - // Here we build two functions: - // - // - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine` - // This is just a type signature that we make available to the linker, - // and can use in the wrapper - // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` + let fastcc_function_name = format!("{}_fastcc_wrapper", foreign.as_str()); - let return_type = basic_type_from_layout(env, ret_layout); - let cc_return = to_cc_return(env, ret_layout); + let (fastcc_function, arguments) = match env.module.get_function(fastcc_function_name.as_str()) + { + Some(function_value) => { + let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); - let mut cc_argument_types = Vec::with_capacity_in(argument_symbols.len() + 1, env.arena); - let mut fastcc_argument_types = Vec::with_capacity_in(argument_symbols.len(), env.arena); - let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); + for symbol in argument_symbols { + let (value, _) = load_symbol_and_layout(scope, symbol); - for symbol in argument_symbols { - let (value, layout) = load_symbol_and_layout(scope, symbol); + arguments.push(value); + } - cc_argument_types.push(to_cc_type(env, layout)); - - let basic_type = basic_type_from_layout(env, layout); - fastcc_argument_types.push(basic_type); - - arguments.push(value); - } - - let cc_type = match cc_return { - CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false), - CCReturn::ByPointer => { - cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); - env.context.void_type().fn_type(&cc_argument_types, false) + (function_value, arguments) + } + None => { + // Here we build two functions: + // + // - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine` + // This is just a type signature that we make available to the linker, + // and can use in the wrapper + // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper` + + let return_type = basic_type_from_layout(env, ret_layout); + let cc_return = to_cc_return(env, ret_layout); + + let mut cc_argument_types = + Vec::with_capacity_in(argument_symbols.len() + 1, env.arena); + let mut fastcc_argument_types = + Vec::with_capacity_in(argument_symbols.len(), env.arena); + let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena); + + for symbol in argument_symbols { + let (value, layout) = load_symbol_and_layout(scope, symbol); + + cc_argument_types.push(to_cc_type(env, layout)); + + let basic_type = basic_type_from_layout(env, layout); + fastcc_argument_types.push(basic_type); + + arguments.push(value); + } + + let cc_type = match cc_return { + CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false), + CCReturn::ByPointer => { + cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); + env.context.void_type().fn_type(&cc_argument_types, false) + } + CCReturn::Return => return_type.fn_type(&cc_argument_types, false), + }; + + let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); + + let fastcc_type = return_type.fn_type(&fastcc_argument_types, false); + + let fastcc_function = add_func( + env.module, + &fastcc_function_name, + fastcc_type, + Linkage::Private, + FAST_CALL_CONV, + ); + + let old = builder.get_insert_block().unwrap(); + + let entry = context.append_basic_block(fastcc_function, "entry"); + { + builder.position_at_end(entry); + let return_pointer = env.builder.build_alloca(return_type, "return_value"); + + let fastcc_parameters = fastcc_function.get_params(); + let mut cc_arguments = + Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); + + for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) + { + if param.get_type() == *cc_type { + cc_arguments.push(param); + } else { + let as_cc_type = + complex_bitcast(env.builder, param, *cc_type, "to_cc_type"); + cc_arguments.push(as_cc_type); + } + } + + if let CCReturn::ByPointer = cc_return { + cc_arguments.push(return_pointer.into()); + } + + let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); + call.set_call_convention(C_CALL_CONV); + + let return_value = match cc_return { + CCReturn::Return => call.try_as_basic_value().left().unwrap(), + + CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), + CCReturn::Void => return_type.const_zero(), + }; + + builder.build_return(Some(&return_value)); + } + + builder.position_at_end(old); + + (fastcc_function, arguments) } - CCReturn::Return => return_type.fn_type(&cc_argument_types, false), }; - let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type); - - let fastcc_type = return_type.fn_type(&fastcc_argument_types, false); - - let fastcc_function = add_func( - env.module, - &format!("{}_fastcc_wrapper", foreign.as_str()), - fastcc_type, - Linkage::Private, - FAST_CALL_CONV, - ); - - let old = builder.get_insert_block().unwrap(); - - let entry = context.append_basic_block(fastcc_function, "entry"); - { - builder.position_at_end(entry); - let return_pointer = env.builder.build_alloca(return_type, "return_value"); - - let fastcc_parameters = fastcc_function.get_params(); - let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena); - - for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) { - if param.get_type() == *cc_type { - cc_arguments.push(param); - } else { - let as_cc_type = complex_bitcast(env.builder, param, *cc_type, "to_cc_type"); - cc_arguments.push(as_cc_type); - } - } - - if let CCReturn::ByPointer = cc_return { - cc_arguments.push(return_pointer.into()); - } - - let call = env.builder.build_call(cc_function, &cc_arguments, "tmp"); - call.set_call_convention(C_CALL_CONV); - - let return_value = match cc_return { - CCReturn::Return => call.try_as_basic_value().left().unwrap(), - - CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"), - CCReturn::Void => return_type.const_zero(), - }; - - builder.build_return(Some(&return_value)); - } - - builder.position_at_end(old); let call = env.builder.build_call(fastcc_function, &arguments, "tmp"); call.set_call_convention(FAST_CALL_CONV); return call.try_as_basic_value().left().unwrap(); From 018348bd830dafe93c1172182b7a11129d8600ec Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 12:13:17 +0200 Subject: [PATCH 77/96] make Str.fromUtf8 in-place --- compiler/builtins/bitcode/src/list.zig | 13 ++++++++-- compiler/builtins/bitcode/src/str.zig | 30 ++++++++++++++++++------ compiler/builtins/bitcode/src/utils.zig | 5 ++++ compiler/gen_llvm/src/llvm/build.rs | 2 +- compiler/gen_llvm/src/llvm/build_list.rs | 6 ++--- compiler/gen_llvm/src/llvm/build_str.rs | 7 +++++- 6 files changed, 49 insertions(+), 14 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 09890b5b12..d5224eac3c 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -1,6 +1,7 @@ const std = @import("std"); const utils = @import("utils.zig"); const RocResult = utils.RocResult; +const UpdateMode = utils.UpdateMode; const mem = std.mem; const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; @@ -52,6 +53,14 @@ pub const RocList = extern struct { }; } + pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) RocList { + if (update_mode == .InPlace) { + return self; + } else { + return self.makeUnique(alignment, element_width); + } + } + pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList { if (self.isEmpty()) { return self; @@ -763,7 +772,7 @@ pub fn listSwap( element_width: usize, index_1: usize, index_2: usize, - can_update_in_place: bool, + update_mode: update_mode, ) callconv(.C) RocList { const size = list.len(); if (index_1 == index_2 or index_1 >= size or index_2 >= size) { @@ -772,7 +781,7 @@ pub fn listSwap( } const newList = blk: { - if (can_update_in_place) { + if (update_mode == .InPlace) { break :blk list; } else { break :blk list.makeUnique(alignment, element_width); diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index ac2b3f6399..d04bf0086b 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1,5 +1,6 @@ const utils = @import("utils.zig"); const RocList = @import("list.zig").RocList; +const UpdateMode = utils.UpdateMode; const std = @import("std"); const mem = std.mem; const always_inline = std.builtin.CallOptions.Modifier.always_inline; @@ -1177,11 +1178,11 @@ const CountAndStart = extern struct { start: usize, }; -pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { - output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); +pub fn fromUtf8C(arg: RocList, update_mode: UpdateMode, output: *FromUtf8Result) callconv(.C) void { + output.* = fromUtf8(arg, update_mode); } -fn fromUtf8(arg: RocList) FromUtf8Result { +inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result { const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length]; if (unicode.utf8ValidateSlice(bytes)) { @@ -1194,13 +1195,23 @@ fn fromUtf8(arg: RocList) FromUtf8Result { const data_bytes = arg.len(); utils.decref(arg.bytes, data_bytes, RocStr.alignment); - return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + return FromUtf8Result{ + .is_ok = true, + .string = string, + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; } else { - const byte_list = arg.makeUnique(RocStr.alignment, @sizeOf(u8)); + const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode); const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length }; - return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; + return FromUtf8Result{ + .is_ok = true, + .string = string, + .byte_index = 0, + .problem_code = Utf8ByteProblem.InvalidStartByte, + }; } } else { const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); @@ -1209,7 +1220,12 @@ fn fromUtf8(arg: RocList) FromUtf8Result { const data_bytes = arg.len(); utils.decref(arg.bytes, data_bytes, RocStr.alignment); - return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem }; + return FromUtf8Result{ + .is_ok = false, + .string = RocStr.empty(), + .byte_index = temp.index, + .problem_code = temp.problem, + }; } } diff --git a/compiler/builtins/bitcode/src/utils.zig b/compiler/builtins/bitcode/src/utils.zig index 67240bdb2d..1371afe674 100644 --- a/compiler/builtins/bitcode/src/utils.zig +++ b/compiler/builtins/bitcode/src/utils.zig @@ -256,3 +256,8 @@ pub const Ordering = enum(u8) { GT = 1, LT = 2, }; + +pub const UpdateMode = extern enum(u8) { + Immutable = 0, + InPlace = 1, +}; diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 17c79cc3a3..722f0b4376 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4848,7 +4848,7 @@ fn run_low_level<'a, 'ctx, 'env>( let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); - str_from_utf8(env, parent, original_wrapper) + str_from_utf8(env, parent, original_wrapper, update_mode) } StrFromUtf8Range => { debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 93be451ddb..f159759153 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -17,13 +17,13 @@ use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_mono::layout::{Builtin, Layout, LayoutIds}; -fn pass_update_mode<'a, 'ctx, 'env>( +pub fn pass_update_mode<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { match update_mode { - UpdateMode::Immutable => env.context.bool_type().const_zero().into(), - UpdateMode::InPlace => env.context.bool_type().const_int(1, false).into(), + UpdateMode::Immutable => env.context.i8_type().const_zero().into(), + UpdateMode::InPlace => env.context.i8_type().const_int(1, false).into(), } } diff --git a/compiler/gen_llvm/src/llvm/build_str.rs b/compiler/gen_llvm/src/llvm/build_str.rs index 02098150b7..564f35625e 100644 --- a/compiler/gen_llvm/src/llvm/build_str.rs +++ b/compiler/gen_llvm/src/llvm/build_str.rs @@ -1,9 +1,12 @@ use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; use crate::llvm::build::{complex_bitcast, Env, Scope}; -use crate::llvm::build_list::{allocate_list, call_bitcode_fn_returns_list, store_list}; +use crate::llvm::build_list::{ + allocate_list, call_bitcode_fn_returns_list, pass_update_mode, store_list, +}; use inkwell::builder::Builder; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::AddressSpace; +use morphic_lib::UpdateMode; use roc_builtins::bitcode; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; @@ -350,6 +353,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, _parent: FunctionValue<'ctx>, original_wrapper: StructValue<'ctx>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { let builder = env.builder; @@ -365,6 +369,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>( env.str_list_c_abi().into(), "to_i128", ), + pass_update_mode(env, update_mode), result_ptr.into(), ], bitcode::STR_FROM_UTF8, From 486f1d540fa62928661e6aa68b92caa13c6693ef Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 13:22:03 +0200 Subject: [PATCH 78/96] add specifications for fromUtf8 and toUtf8 --- compiler/builtins/bitcode/src/list.zig | 2 +- compiler/builtins/bitcode/src/str.zig | 4 ++-- compiler/mono/src/alias_analysis.rs | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index d5224eac3c..6bfc5b70de 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -772,7 +772,7 @@ pub fn listSwap( element_width: usize, index_1: usize, index_2: usize, - update_mode: update_mode, + update_mode: UpdateMode, ) callconv(.C) RocList { const size = list.len(); if (index_1 == index_2 or index_1 >= size or index_2 >= size) { diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index d04bf0086b..5506d4b654 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1148,10 +1148,10 @@ test "RocStr.joinWith: result is big" { // Str.toUtf8 pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList { - return @call(.{ .modifier = always_inline }, strToBytes, .{arg}); + return strToBytes(arg); } -fn strToBytes(arg: RocStr) RocList { +inline fn strToBytes(arg: RocStr) RocList { if (arg.isEmpty()) { return RocList.empty(); } else if (arg.isSmallStr()) { diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index 96d6ddb292..f0dc294a95 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -933,6 +933,27 @@ fn lowlevel_spec( let new_cell = builder.add_new_heap_cell(block)?; builder.add_make_tuple(block, &[new_cell, bag]) } + StrToUtf8 => { + let string = env.symbols[&arguments[0]]; + + let u8_type = builder.add_tuple_type(&[])?; + let bag = builder.add_empty_bag(block, u8_type)?; + let cell = builder.add_get_tuple_field(block, string, LIST_CELL_INDEX)?; + + builder.add_make_tuple(block, &[cell, bag]) + } + StrFromUtf8 => { + let list = env.symbols[&arguments[0]]; + + let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + let string = builder.add_make_tuple(block, &[cell])?; + + let byte_index = builder.add_make_tuple(block, &[])?; + let is_ok = builder.add_make_tuple(block, &[])?; + let problem_code = builder.add_make_tuple(block, &[])?; + + builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code]) + } DictEmpty => { match layout { Layout::Builtin(Builtin::EmptyDict) => { From 13d480d5f37d3ba529b778e747fa75945c312edc Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 13:27:47 +0200 Subject: [PATCH 79/96] spec for list append --- compiler/builtins/bitcode/src/list.zig | 5 ++++- compiler/gen_llvm/src/llvm/build.rs | 2 +- compiler/gen_llvm/src/llvm/build_list.rs | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 6bfc5b70de..d83f888e22 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -729,10 +729,13 @@ pub fn listSingle(alignment: u32, element: Opaque, element_width: usize) callcon return output; } -pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { +pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { const old_length = list.len(); var output = list.reallocate(alignment, old_length + 1, element_width); + // we'd need capacity to use update_mode here + _ = update_mode; + if (output.bytes) |target| { if (element) |source| { @memcpy(target + old_length * element_width, source, element_width); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 722f0b4376..4720653867 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4972,7 +4972,7 @@ fn run_low_level<'a, 'ctx, 'env>( let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]); - list_append(env, original_wrapper, elem, elem_layout) + list_append(env, original_wrapper, elem, elem_layout, update_mode) } ListSwap => { // List.swap : List elem, Nat, Nat -> List elem diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index f159759153..5c42d24c52 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -238,6 +238,7 @@ pub fn list_append<'a, 'ctx, 'env>( original_wrapper: StructValue<'ctx>, element: BasicValueEnum<'ctx>, element_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { call_bitcode_fn_returns_list( env, @@ -246,6 +247,7 @@ pub fn list_append<'a, 'ctx, 'env>( env.alignment_intvalue(element_layout), pass_element_as_opaque(env, element), layout_width(env, element_layout), + pass_update_mode(env, update_mode), ], bitcode::LIST_APPEND, ) From 06906331163a872093e78427ed9c81509c47bf75 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 13:34:09 +0200 Subject: [PATCH 80/96] spec for List.reverse --- compiler/builtins/bitcode/src/list.zig | 4 ++-- compiler/gen_llvm/src/llvm/build.rs | 2 +- compiler/gen_llvm/src/llvm/build_list.rs | 2 ++ compiler/mono/src/alias_analysis.rs | 11 +++++++++++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index d83f888e22..6e6351a6e7 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -141,14 +141,14 @@ const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; -pub fn listReverse(list: RocList, alignment: u32, element_width: usize) callconv(.C) RocList { +pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList { if (list.bytes) |source_ptr| { const size = list.len(); var i: usize = 0; const end: usize = size - 1; - if (list.isUnique()) { + if (update_mode == .InPlace or list.isUnique()) { // Working from the front and back so // we only need to go ~(n / 2) iterations. diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 4720653867..0694299329 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -4930,7 +4930,7 @@ fn run_low_level<'a, 'ctx, 'env>( let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); - list_reverse(env, list, list_layout) + list_reverse(env, list, list_layout, update_mode) } ListConcat => { debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 5c42d24c52..d46d454458 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -172,6 +172,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, list: BasicValueEnum<'ctx>, list_layout: &Layout<'a>, + update_mode: UpdateMode, ) -> BasicValueEnum<'ctx> { let element_layout = match *list_layout { Layout::Builtin(Builtin::EmptyList) => { @@ -190,6 +191,7 @@ pub fn list_reverse<'a, 'ctx, 'env>( pass_list_cc(env, list), env.alignment_intvalue(&element_layout), layout_width(env, &element_layout), + pass_update_mode(env, update_mode), ], bitcode::LIST_REVERSE, ) diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index f0dc294a95..b218875425 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -918,6 +918,17 @@ fn lowlevel_spec( let new_cell = builder.add_new_heap_cell(block)?; builder.add_make_tuple(block, &[new_cell, bag]) } + ListReverse => { + let list = env.symbols[&arguments[0]]; + + let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; + let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?; + + let _unit = builder.add_update(block, update_mode_var, cell)?; + + let new_cell = builder.add_new_heap_cell(block)?; + builder.add_make_tuple(block, &[new_cell, bag]) + } ListAppend => { let list = env.symbols[&arguments[0]]; let to_insert = env.symbols[&arguments[1]]; From 313bc71f48623f148ec9e636af0401a1ebe816ab Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 14:06:40 +0200 Subject: [PATCH 81/96] disable RBTreeDel test/benchmark --- cli/benches/time_bench.rs | 2 +- cli/cli_utils/src/bench_utils.rs | 1 + cli/tests/cli_run.rs | 142 ++++++++++++------------ nightly_benches/benches/events_bench.rs | 4 +- 4 files changed, 75 insertions(+), 74 deletions(-) diff --git a/cli/benches/time_bench.rs b/cli/benches/time_bench.rs index 56395acc75..ffbcabfb66 100644 --- a/cli/benches/time_bench.rs +++ b/cli/benches/time_bench.rs @@ -29,7 +29,7 @@ fn bench_group_wall_time(c: &mut Criterion) { bench_cfold, // e = mkExpr 17 1 bench_deriv, // nest deriv 8 f bench_rbtree_ck, // ms = makeMap 5 80000 - bench_rbtree_delete, // m = makeMap 100000 + // bench_rbtree_delete, // m = makeMap 100000 bench_quicksort, // list size 10000 ]; diff --git a/cli/cli_utils/src/bench_utils.rs b/cli/cli_utils/src/bench_utils.rs index 146f500380..b01ec495fd 100644 --- a/cli/cli_utils/src/bench_utils.rs +++ b/cli/cli_utils/src/bench_utils.rs @@ -131,6 +131,7 @@ pub fn bench_rbtree_ck(bench_group_opt: Option<&mut BenchmarkGro ); } +#[allow(dead_code)] pub fn bench_rbtree_delete(bench_group_opt: Option<&mut BenchmarkGroup>) { exec_bench_w_input( &example_file("benchmarks", "RBTreeDel.roc"), diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index aa7020700f..79a64047ea 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -435,77 +435,77 @@ mod cli_run { } benchmarks! { - nqueens => Example { - filename: "NQueens.roc", - executable_filename: "nqueens", - stdin: &["6"], - expected_ending: "4\n", - use_valgrind: true, - }, - cfold => Example { - filename: "CFold.roc", - executable_filename: "cfold", - stdin: &["3"], - expected_ending: "11 & 11\n", - use_valgrind: true, - }, - deriv => Example { - filename: "Deriv.roc", - executable_filename: "deriv", - stdin: &["2"], - expected_ending: "1 count: 6\n2 count: 22\n", - use_valgrind: true, - }, - rbtree_ck => Example { - filename: "RBTreeCk.roc", - executable_filename: "rbtree-ck", - stdin: &["100"], - expected_ending: "10\n", - use_valgrind: true, - }, - rbtree_insert => Example { - filename: "RBTreeInsert.roc", - executable_filename: "rbtree-insert", - stdin: &[], - expected_ending: "Node Black 0 {} Empty Empty\n", - use_valgrind: true, - }, - rbtree_del => Example { - filename: "RBTreeDel.roc", - executable_filename: "rbtree-del", - stdin: &["420"], - expected_ending: "30\n", - use_valgrind: true, - }, - astar => Example { - filename: "TestAStar.roc", - executable_filename: "test-astar", - stdin: &[], - expected_ending: "True\n", - use_valgrind: false, - }, - base64 => Example { - filename: "TestBase64.roc", - executable_filename: "test-base64", - stdin: &[], - expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", - use_valgrind: true, - }, - closure => Example { - filename: "Closure.roc", - executable_filename: "closure", - stdin: &[], - expected_ending: "", - use_valgrind: true, - }, - quicksort_app => Example { - filename: "QuicksortApp.roc", - executable_filename: "quicksortapp", - stdin: &[], - expected_ending: "todo put the correct quicksort answer here", - use_valgrind: true, - }, - } + nqueens => Example { + filename: "NQueens.roc", + executable_filename: "nqueens", + stdin: &["6"], + expected_ending: "4\n", + use_valgrind: true, + }, + cfold => Example { + filename: "CFold.roc", + executable_filename: "cfold", + stdin: &["3"], + expected_ending: "11 & 11\n", + use_valgrind: true, + }, + deriv => Example { + filename: "Deriv.roc", + executable_filename: "deriv", + stdin: &["2"], + expected_ending: "1 count: 6\n2 count: 22\n", + use_valgrind: true, + }, + rbtree_ck => Example { + filename: "RBTreeCk.roc", + executable_filename: "rbtree-ck", + stdin: &["100"], + expected_ending: "10\n", + use_valgrind: true, + }, + rbtree_insert => Example { + filename: "RBTreeInsert.roc", + executable_filename: "rbtree-insert", + stdin: &[], + expected_ending: "Node Black 0 {} Empty Empty\n", + use_valgrind: true, + }, + // rbtree_del => Example { + // filename: "RBTreeDel.roc", + // executable_filename: "rbtree-del", + // stdin: &["420"], + // expected_ending: "30\n", + // use_valgrind: true, + // }, + astar => Example { + filename: "TestAStar.roc", + executable_filename: "test-astar", + stdin: &[], + expected_ending: "True\n", + use_valgrind: false, + }, + base64 => Example { + filename: "TestBase64.roc", + executable_filename: "test-base64", + stdin: &[], + expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", + use_valgrind: true, + }, + closure => Example { + filename: "Closure.roc", + executable_filename: "closure", + stdin: &[], + expected_ending: "", + use_valgrind: true, + }, + quicksort_app => Example { + filename: "QuicksortApp.roc", + executable_filename: "quicksortapp", + stdin: &[], + expected_ending: "todo put the correct quicksort answer here", + use_valgrind: true, + }, + } #[cfg(not(debug_assertions))] fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { diff --git a/nightly_benches/benches/events_bench.rs b/nightly_benches/benches/events_bench.rs index c722eeae7c..be889672f2 100644 --- a/nightly_benches/benches/events_bench.rs +++ b/nightly_benches/benches/events_bench.rs @@ -1,6 +1,6 @@ // Keep this benchmark. It's commented because it requires nightly rust. use cli_utils::bench_utils::{ - bench_cfold, bench_deriv, bench_nqueens, bench_rbtree_ck, bench_rbtree_delete, bench_quicksort + bench_cfold, bench_deriv, bench_nqueens, bench_quicksort, bench_rbtree_ck, bench_rbtree_delete, }; use criterion_perf_events::Perf; use perfcnt::linux::HardwareEventType as Hardware; @@ -18,7 +18,7 @@ fn bench_group(c: &mut Criterion, hw_event_str: &str) { bench_cfold, bench_deriv, bench_rbtree_ck, - bench_rbtree_delete, + // bench_rbtree_delete, bench_quicksort, ]; From f8ac85195b7178c4dde21760d4e4acbcb2bee65e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 3 Oct 2021 14:18:48 +0200 Subject: [PATCH 82/96] fix zig test --- compiler/builtins/bitcode/src/str.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/builtins/bitcode/src/str.zig b/compiler/builtins/bitcode/src/str.zig index 5506d4b654..9ffb9a1375 100644 --- a/compiler/builtins/bitcode/src/str.zig +++ b/compiler/builtins/bitcode/src/str.zig @@ -1308,11 +1308,11 @@ pub const Utf8ByteProblem = enum(u8) { }; fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result { - return fromUtf8(RocList{ .bytes = bytes, .length = length }); + return fromUtf8(RocList{ .bytes = bytes, .length = length }, .Immutable); } fn validateUtf8BytesX(str: RocList) FromUtf8Result { - return fromUtf8(str); + return fromUtf8(str, .Immutable); } fn expectOk(result: FromUtf8Result) !void { From 13d64c48bdcad566446513b49d03818d1fe8f11b Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:17:23 +0200 Subject: [PATCH 83/96] fmt --- cli/benches/time_bench.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/benches/time_bench.rs b/cli/benches/time_bench.rs index ffbcabfb66..f4c8f6e8be 100644 --- a/cli/benches/time_bench.rs +++ b/cli/benches/time_bench.rs @@ -25,12 +25,12 @@ fn bench_group_wall_time(c: &mut Criterion) { group.sample_size(nr_of_runs); let bench_funcs: Vec>) -> ()> = vec![ - bench_nqueens, // queens 11 - bench_cfold, // e = mkExpr 17 1 - bench_deriv, // nest deriv 8 f - bench_rbtree_ck, // ms = makeMap 5 80000 + bench_nqueens, // queens 11 + bench_cfold, // e = mkExpr 17 1 + bench_deriv, // nest deriv 8 f + bench_rbtree_ck, // ms = makeMap 5 80000 // bench_rbtree_delete, // m = makeMap 100000 - bench_quicksort, // list size 10000 + bench_quicksort, // list size 10000 ]; for bench_func in bench_funcs.iter() { From f4ea4e4ad7217f0a4fc06d35f6ba75eb476c558a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 3 Oct 2021 17:26:11 +0200 Subject: [PATCH 84/96] just want to run tests --- Earthfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Earthfile b/Earthfile index c6b754ce64..fd77785c8a 100644 --- a/Earthfile +++ b/Earthfile @@ -101,7 +101,7 @@ test-all: BUILD +test-rust BUILD +verify-no-git-changes -# compile everything needed for benchmarks and output a self-contained folder +# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run. prep-bench-folder: FROM +copy-dirs ARG BENCH_SUFFIX=branch From 09917b1f01a019e878f77f8ea1bc1a5327f95cfe Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Sun, 3 Oct 2021 18:34:04 +0200 Subject: [PATCH 85/96] ignore missing RBTreeDel test --- cli/tests/cli_run.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 79a64047ea..925af2335d 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -562,10 +562,10 @@ mod cli_run { file.read_exact(buf).unwrap(); // Only app modules in this directory are considered benchmarks. - if "app".as_bytes() == buf { + if "app".as_bytes() == buf && !benchmark_file_name.contains("RBTreeDel") { all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| { - panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); - }); + panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); + }); } } } From 3baff93a975d1e40e361ad849892d1290e869622 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sat, 2 Oct 2021 20:03:07 -0500 Subject: [PATCH 86/96] add first version of List.dropAt * adds an implementation with no uniqueness/mutability --- compiler/builtins/bitcode/src/list.zig | 48 ++++++++++++++++++++++++ compiler/builtins/bitcode/src/main.zig | 1 + compiler/builtins/docs/List.roc | 7 +++- compiler/builtins/src/bitcode.rs | 1 + compiler/builtins/src/std.rs | 7 ++++ compiler/can/src/builtins.rs | 24 ++++++++++++ compiler/gen_llvm/src/llvm/build.rs | 28 ++++++++++++-- compiler/gen_llvm/src/llvm/build_list.rs | 25 +++++++++++- compiler/module/src/low_level.rs | 15 ++++---- compiler/module/src/symbol.rs | 1 + compiler/mono/src/borrow.rs | 1 + 11 files changed, 145 insertions(+), 13 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 6e6351a6e7..2224ac53cb 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -833,6 +833,54 @@ pub fn listDrop( } } +// GIESCH add type inference test +// GIESCH add unit tests +// GIESCH do a uniqueness check, and reuse the same array if possible +// GIESCH figure out where to specify uniqueness of output, update builtins readme +pub fn listDropAt( + list: RocList, + alignment: u32, + element_width: usize, + drop_index: usize, + dec: Dec, +) callconv(.C) RocList { + if (list.bytes) |source_ptr| { + const size = list.len(); + + if (drop_index >= size) { + return RocList.empty(); + } + + if (drop_index < size) { + const element = source_ptr + drop_index * element_width; + dec(element); + } + + // GIESCH is this necessary? + if (size < 2 and drop_index == 0) { + return RocList.empty(); + } + + const output = RocList.allocate(alignment, (size - 1), element_width); + const target_ptr = output.bytes orelse unreachable; + + const head_size = drop_index * element_width; + @memcpy(target_ptr, source_ptr, head_size); + + const tail_target = target_ptr + drop_index * element_width; + const tail_source = source_ptr + (drop_index + 1) * element_width; + const tail_size = (size - drop_index - 1) * element_width; + @memcpy(tail_target, tail_source, tail_size); + + // GIESCH what's the difference between this and Dec? + utils.decref(list.bytes, size * element_width, alignment); + + return output; + } else { + return RocList.empty(); + } +} + pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList { return switch (width) { .U8 => helper1(u8, low, high), diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index 0841a8e8f5..d82b8542a8 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -39,6 +39,7 @@ comptime { exportListFn(list.listSortWith, "sort_with"); exportListFn(list.listConcat, "concat"); exportListFn(list.listDrop, "drop"); + exportListFn(list.listDropAt, "drop_at"); exportListFn(list.listSet, "set"); exportListFn(list.listSetInPlace, "set_in_place"); exportListFn(list.listSwap, "swap"); diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index f8f9a2df43..3be184ff5b 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -422,15 +422,18 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* ## If the given index is outside the bounds of the list, returns the original ## list unmodified. ## -## To drop the element at a given index, instead of replacing it, see [List.drop]. +## To drop the element at a given index, instead of replacing it, see [List.dropAt]. set : List elem, Nat, elem -> List elem +# GIESCH figure out if we should add docs for List.drop; +# what's the relationship with List.dropFirst, below? +# GIESCH add docs re: uniqueness and performance ## Drops the element at the given index from the list. ## ## This has no effect if the given index is outside the bounds of the list. ## ## To replace the element at a given index, instead of dropping it, see [List.set]. -drop : List elem, Nat -> List elem +dropAt : List elem, Nat -> List elem ## Adds a new element to the end of the list. ## diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index fd1ac7b17c..e451a1e68d 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -63,6 +63,7 @@ pub const LIST_REPEAT: &str = "roc_builtins.list.repeat"; pub const LIST_APPEND: &str = "roc_builtins.list.append"; pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_DROP: &str = "roc_builtins.list.drop"; +pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at"; pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SINGLE: &str = "roc_builtins.list.single"; pub const LIST_JOIN: &str = "roc_builtins.list.join"; diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 7615879227..0374e228e2 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -920,6 +920,13 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // dropAt : List elem, Nat -> List elem + add_top_level_function_type!( + Symbol::LIST_DROP_AT, + vec![list_type(flex(TVAR1)), nat_type()], + Box::new(list_type(flex(TVAR1))), + ); + // swap : List elem, Nat, Nat -> List elem add_top_level_function_type!( Symbol::LIST_SWAP, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index d4fff2ef74..ea0bde537a 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -87,6 +87,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_MAP2 => list_map2, LIST_MAP3 => list_map3, LIST_DROP => list_drop, + LIST_DROP_AT => list_drop_at, LIST_SWAP => list_swap, LIST_MAP_WITH_INDEX => list_map_with_index, LIST_KEEP_IF => list_keep_if, @@ -1979,6 +1980,29 @@ fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.dropAt : List elem, Nat -> List elem +fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def { + let list_var = var_store.fresh(); + let index_var = var_store.fresh(); + + let body = RunLowLevel { + op: LowLevel::ListDropAt, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (index_var, Var(Symbol::ARG_2)), + ], + ret_var: list_var, + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1), (index_var, Symbol::ARG_2)], + var_store, + body, + list_var, + ) +} + /// List.append : List elem, elem -> List elem fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 0694299329..505c2b924f 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -9,9 +9,10 @@ use crate::llvm::build_dict::{ use crate::llvm::build_hash::generic_hash; use crate::llvm::build_list::{ self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, - list_contains, list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if, - list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, list_prepend, - list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, list_swap, + list_contains, list_drop, list_drop_at, list_get_unsafe, list_join, list_keep_errs, + list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, + list_prepend, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, + list_swap, }; use crate::llvm::build_str::{ empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, @@ -5018,6 +5019,27 @@ fn run_low_level<'a, 'ctx, 'env>( _ => unreachable!("Invalid layout {:?} in List.drop", list_layout), } } + ListDropAt => { + // List.dropAt : List elem, Nat -> List elem + debug_assert_eq!(args.len(), 2); + + let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); + let original_wrapper = list.into_struct_value(); + + let count = load_symbol(scope, &args[1]); + + match list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::List(element_layout)) => list_drop_at( + env, + layout_ids, + original_wrapper, + count.into_int_value(), + element_layout, + ), + _ => unreachable!("Invalid layout {:?} in List.dropAt", list_layout), + } + } ListPrepend => { // List.prepend : List elem, elem -> List elem debug_assert_eq!(args.len(), 2); diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index d46d454458..2428dd6000 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -297,7 +297,7 @@ pub fn list_swap<'a, 'ctx, 'env>( ) } -/// List.drop : List elem, Nat, Nat -> List elem +/// List.drop : List elem, Nat -> List elem pub fn list_drop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, @@ -319,6 +319,29 @@ pub fn list_drop<'a, 'ctx, 'env>( ) } +// GIESCH ask about how this calling/linking to compiled zig works +/// List.dropAt : List elem, Nat -> List elem +pub fn list_drop_at<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + original_wrapper: StructValue<'ctx>, + count: IntValue<'ctx>, + element_layout: &Layout<'a>, +) -> BasicValueEnum<'ctx> { + let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout); + call_bitcode_fn_returns_list( + env, + &[ + pass_list_cc(env, original_wrapper.into()), + env.alignment_intvalue(element_layout), + layout_width(env, element_layout), + count.into(), + dec_element_fn.as_global_value().as_pointer_value().into(), + ], + bitcode::LIST_DROP_AT, + ) +} + /// List.set : List elem, Nat, elem -> List elem pub fn list_set<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/compiler/module/src/low_level.rs b/compiler/module/src/low_level.rs index 7dec1f4ccf..8753d21af0 100644 --- a/compiler/module/src/low_level.rs +++ b/compiler/module/src/low_level.rs @@ -41,6 +41,7 @@ pub enum LowLevel { ListKeepErrs, ListSortWith, ListDrop, + ListDropAt, ListSwap, DictSize, DictEmpty, @@ -116,13 +117,13 @@ impl LowLevel { StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe - | ListSet | ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat - | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListSwap - | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe - | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference - | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap - | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt - | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf + | ListSet | ListDrop | ListDropAt | ListSingle | ListRepeat | ListReverse + | ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange + | ListSwap | DictSize | DictEmpty | DictInsert | DictRemove | DictContains + | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection + | DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub + | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte + | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 6e825a9953..8591e27bf2 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -964,6 +964,7 @@ define_builtins! { 31 LIST_SORT_WITH: "sortWith" 32 LIST_DROP: "drop" 33 LIST_SWAP: "swap" + 34 LIST_DROP_AT: "dropAt" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 45f5eb9c0d..e6221e798b 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -993,6 +993,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { // List.append should own its first argument ListAppend => arena.alloc_slice_copy(&[owned, owned]), ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]), + ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), From 700ab20a8c3cf66e4eb3b3f255b47ea732634f70 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sat, 2 Oct 2021 21:44:35 -0500 Subject: [PATCH 87/96] add tests for List.dropAt --- compiler/builtins/bitcode/src/list.zig | 10 ++-------- compiler/solve/tests/solve_expr.rs | 12 ++++++++++++ compiler/test_gen/src/gen_list.rs | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 2224ac53cb..0a7c725e92 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -833,8 +833,6 @@ pub fn listDrop( } } -// GIESCH add type inference test -// GIESCH add unit tests // GIESCH do a uniqueness check, and reuse the same array if possible // GIESCH figure out where to specify uniqueness of output, update builtins readme pub fn listDropAt( @@ -848,7 +846,8 @@ pub fn listDropAt( const size = list.len(); if (drop_index >= size) { - return RocList.empty(); + // GIESCH should this still copy/reallocate if non-unique? + return list; } if (drop_index < size) { @@ -856,11 +855,6 @@ pub fn listDropAt( dec(element); } - // GIESCH is this necessary? - if (size < 2 and drop_index == 0) { - return RocList.empty(); - } - const output = RocList.allocate(alignment, (size - 1), element_width); const target_ptr = output.bytes orelse unreachable; diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 4f93fe449e..e43ac44ebc 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3709,6 +3709,18 @@ mod solve_expr { ); } + #[test] + fn list_drop_at() { + infer_eq_without_problem( + indoc!( + r#" + List.dropAt + "# + ), + "List a, Nat -> List a", + ); + } + #[test] fn function_that_captures_nothing_is_not_captured() { // we should make sure that a function that doesn't capture anything it not itself captured diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 758603f2c1..ba9bbc1561 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -198,6 +198,21 @@ fn list_drop() { assert_evals_to!("List.drop [1,2] 5", RocList::from_slice(&[]), RocList); } +#[test] +fn list_drop_at() { + assert_evals_to!( + "List.dropAt [1, 2, 3] 0", + RocList::from_slice(&[2, 3]), + RocList + ); + assert_evals_to!( + "List.dropAt [0, 0, 0] 3", + RocList::from_slice(&[1, 2, 3]), + RocList + ); + assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); +} + #[test] fn list_swap() { assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList); From 65821d6a9f67262185dad6668ce6dfc640191c3d Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 09:59:32 -0500 Subject: [PATCH 88/96] remove giesch/todo tags, add List.drop doc --- compiler/builtins/bitcode/src/list.zig | 4 ---- compiler/builtins/docs/List.roc | 7 ++++--- compiler/gen_llvm/src/llvm/build_list.rs | 1 - 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 0a7c725e92..e6f0ca8c5d 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -833,8 +833,6 @@ pub fn listDrop( } } -// GIESCH do a uniqueness check, and reuse the same array if possible -// GIESCH figure out where to specify uniqueness of output, update builtins readme pub fn listDropAt( list: RocList, alignment: u32, @@ -846,7 +844,6 @@ pub fn listDropAt( const size = list.len(); if (drop_index >= size) { - // GIESCH should this still copy/reallocate if non-unique? return list; } @@ -866,7 +863,6 @@ pub fn listDropAt( const tail_size = (size - drop_index - 1) * element_width; @memcpy(tail_target, tail_source, tail_size); - // GIESCH what's the difference between this and Dec? utils.decref(list.bytes, size * element_width, alignment); return output; diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index 3be184ff5b..964affa15b 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -31,6 +31,7 @@ interface List range, sortWith, drop, + dropAt, swap ] imports [] @@ -425,9 +426,9 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* ## To drop the element at a given index, instead of replacing it, see [List.dropAt]. set : List elem, Nat, elem -> List elem -# GIESCH figure out if we should add docs for List.drop; -# what's the relationship with List.dropFirst, below? -# GIESCH add docs re: uniqueness and performance +## Drops n elements from the beginning of the list. +drop : List elem, Nat -> List elem + ## Drops the element at the given index from the list. ## ## This has no effect if the given index is outside the bounds of the list. diff --git a/compiler/gen_llvm/src/llvm/build_list.rs b/compiler/gen_llvm/src/llvm/build_list.rs index 2428dd6000..ddc6cd645b 100644 --- a/compiler/gen_llvm/src/llvm/build_list.rs +++ b/compiler/gen_llvm/src/llvm/build_list.rs @@ -319,7 +319,6 @@ pub fn list_drop<'a, 'ctx, 'env>( ) } -// GIESCH ask about how this calling/linking to compiled zig works /// List.dropAt : List elem, Nat -> List elem pub fn list_drop_at<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, From d1ee9c90b2885b1533401bd44a72834c65a6b89a Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 11:07:11 -0500 Subject: [PATCH 89/96] fix copy/paste error in test --- compiler/test_gen/src/gen_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index ba9bbc1561..ce99435505 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -207,7 +207,7 @@ fn list_drop_at() { ); assert_evals_to!( "List.dropAt [0, 0, 0] 3", - RocList::from_slice(&[1, 2, 3]), + RocList::from_slice(&[0, 0, 0]), RocList ); assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); From 2a724391175a06455b7e479a3c0977982c7365c1 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 13:54:47 -0500 Subject: [PATCH 90/96] WIP adding unique mutable case --- compiler/builtins/bitcode/src/list.zig | 28 +++++++++++++++++++++++++- compiler/test_gen/src/gen_list.rs | 19 +++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index e6f0ca8c5d..395fbcbc45 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -852,7 +852,33 @@ pub fn listDropAt( dec(element); } - const output = RocList.allocate(alignment, (size - 1), element_width); + // NOTE + // we need to return an empty list explicitly, + // because we rely on the pointer field being null if the list is empty + // which also requires duplicating the utils.decref call to spend the RC token + if (size < 2) { + utils.decref(list.bytes, size * element_width, alignment); + return RocList.empty(); + } + + if (list.isUnique()) { + var i = drop_index; + while (i < size) : (i += 1) { + const copy_target = source_ptr + i * element_width; + const copy_source = copy_target + element_width; + @memcpy(copy_target, copy_source, element_width); + } + + var new_list = list; + + new_list.length -= 1; + return new_list; + } + + const stdout = std.io.getStdOut().writer(); + stdout.print("Hit non-unique branch with list, {any}!\n", .{list}) catch unreachable; + + const output = RocList.allocate(alignment, size - 1, element_width); const target_ptr = output.bytes orelse unreachable; const head_size = drop_index * element_width; diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index ce99435505..bb3fb4dda4 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -211,6 +211,25 @@ fn list_drop_at() { RocList ); assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); + assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList); + + assert_evals_to!( + indoc!( + r#" + list : List I64 + list = [ 1, 2, 3 ] + + { newList: List.dropAt list 0, original: list } + "# + ), + ( + // new_list + RocList::from_slice(&[2, 3]), + // original + RocList::from_slice(&[1, 2, 3]), + ), + (RocList, RocList,) + ); } #[test] From 25215cb3e39c0468e0dd7e087137f9265e7f59de Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 14:09:07 -0500 Subject: [PATCH 91/96] cleanup mutable case, split test --- compiler/builtins/bitcode/src/list.zig | 3 --- compiler/test_gen/src/gen_list.rs | 9 ++++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/builtins/bitcode/src/list.zig b/compiler/builtins/bitcode/src/list.zig index 395fbcbc45..2b83458408 100644 --- a/compiler/builtins/bitcode/src/list.zig +++ b/compiler/builtins/bitcode/src/list.zig @@ -875,9 +875,6 @@ pub fn listDropAt( return new_list; } - const stdout = std.io.getStdOut().writer(); - stdout.print("Hit non-unique branch with list, {any}!\n", .{list}) catch unreachable; - const output = RocList.allocate(alignment, size - 1, element_width); const target_ptr = output.bytes orelse unreachable; diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index bb3fb4dda4..47ac4b3e96 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -212,21 +212,24 @@ fn list_drop_at() { ); assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList); assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList); +} +#[test] +fn list_drop_at_mutable() { assert_evals_to!( indoc!( r#" list : List I64 - list = [ 1, 2, 3 ] + list = [ if True then 4 else 4, 5, 6 ] { newList: List.dropAt list 0, original: list } "# ), ( // new_list - RocList::from_slice(&[2, 3]), + RocList::from_slice(&[5, 6]), // original - RocList::from_slice(&[1, 2, 3]), + RocList::from_slice(&[4, 5, 6]), ), (RocList, RocList,) ); From b83336d3c305455ac5c613f47fdc701926959f70 Mon Sep 17 00:00:00 2001 From: Dan Knutson Date: Sun, 3 Oct 2021 14:39:35 -0500 Subject: [PATCH 92/96] remove outdated uniqueness doc --- compiler/builtins/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index 8e1eb0e291..7780ccb9ac 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -62,12 +62,6 @@ Its one thing to actually write these functions, its _another_ thing to let the ### builtins/mono/src/borrow.rs After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail. -## Specifying the uniqueness of a function -### builtins/src/unique.rs -One of the cool things about Roc is that it evaluates if a value in memory is shared between scopes or if it is used in just one place. If the value is used in one place then it is 'unique', and it therefore can be mutated in place. For a value created by a function, the uniqueness of the output is determined in part by the uniqueness of the input arguments. For example `List.single : elem -> List elem` can return a unique list if the `elem` is also unique. - -We have to define the uniqueness constraints of a function just like we have to define a type signature. That is what happens in `unique.rs`. This can be tricky so it would be a good step to ask for help on if it is confusing. - ## Testing it ### solve/tests/solve_expr.rs To make sure that Roc is properly inferring the type of the new builtin, add a test to this file simlar to: From 2756425eb9952a8ead7dcaa9c9cbe7878c725795 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 3 Oct 2021 21:52:27 +0100 Subject: [PATCH 93/96] Assert storage matches when copying --- compiler/gen_wasm/src/storage.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index cc70956b84..63213e5101 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -44,13 +44,17 @@ impl SymbolStorage { ( Self::Local { local_id: to_local_id, - .. + value_type: to_value_type, + size: to_size, }, Self::Local { local_id: from_local_id, - .. + value_type: from_value_type, + size: from_size, }, ) => { + debug_assert!(to_value_type == from_value_type); + debug_assert!(to_size == from_size); instructions.push(GetLocal(from_local_id.0)); instructions.push(SetLocal(to_local_id.0)); } From cc6f83f284a841ac96442cea4670056c2592d6e4 Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Sun, 3 Oct 2021 21:53:18 +0100 Subject: [PATCH 94/96] Clearer variant names for StackMemoryLocation --- compiler/gen_wasm/src/backend.rs | 14 +++++++------- compiler/gen_wasm/src/storage.rs | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index b6b837acf4..4512203625 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -200,7 +200,7 @@ impl<'a> WasmBackend<'a> { alignment_bytes, } => { let location = match kind { - LocalKind::Parameter => StackMemoryLocation::CallerFrame(next_local_id), + LocalKind::Parameter => StackMemoryLocation::PointerArg(next_local_id), LocalKind::Variable => { match self.stack_frame_pointer { @@ -215,7 +215,7 @@ impl<'a> WasmBackend<'a> { self.stack_memory = offset + size as i32; - StackMemoryLocation::OwnFrame(offset as u32) + StackMemoryLocation::FrameOffset(offset as u32) } }; @@ -260,14 +260,14 @@ impl<'a> WasmBackend<'a> { match storage { SymbolStorage::Local { local_id, .. } | SymbolStorage::StackMemory { - location: StackMemoryLocation::CallerFrame(local_id), + location: StackMemoryLocation::PointerArg(local_id), .. } => { self.instructions.push(GetLocal(local_id.0)); } SymbolStorage::StackMemory { - location: StackMemoryLocation::OwnFrame(offset), + location: StackMemoryLocation::FrameOffset(offset), .. } => { self.instructions.extend([ @@ -308,7 +308,7 @@ impl<'a> WasmBackend<'a> { // Map this symbol to the first argument (pointer into caller's stack) // Saves us from having to copy it later let storage = SymbolStorage::StackMemory { - location: StackMemoryLocation::CallerFrame(LocalId(0)), + location: StackMemoryLocation::PointerArg(LocalId(0)), size, alignment_bytes, }; @@ -345,8 +345,8 @@ impl<'a> WasmBackend<'a> { alignment_bytes, } => { let (from_ptr, from_offset) = match location { - StackMemoryLocation::CallerFrame(local_id) => (*local_id, 0), - StackMemoryLocation::OwnFrame(offset) => { + StackMemoryLocation::PointerArg(local_id) => (*local_id, 0), + StackMemoryLocation::FrameOffset(offset) => { (self.stack_frame_pointer.unwrap(), *offset) } }; diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index 63213e5101..80c0583234 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -3,15 +3,15 @@ use parity_wasm::elements::{Instruction, Instruction::*, ValueType}; #[derive(Debug, Clone)] pub enum StackMemoryLocation { - CallerFrame(LocalId), - OwnFrame(u32), + FrameOffset(u32), + PointerArg(LocalId), } impl StackMemoryLocation { pub fn local_and_offset(&self, stack_frame_pointer: Option) -> (LocalId, u32) { match self { - Self::CallerFrame(local_id) => (*local_id, 0), - Self::OwnFrame(offset) => (stack_frame_pointer.unwrap(), *offset), + Self::PointerArg(local_id) => (*local_id, 0), + Self::FrameOffset(offset) => (stack_frame_pointer.unwrap(), *offset), } } } From 3ff134e1ce23b1c7ea78f8099ee1a028d17809cd Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:42:02 +0200 Subject: [PATCH 95/96] removed erroneous imports --- editor/src/editor/ed_error.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index f0090e7609..1ebd3a88e6 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,5 +1,4 @@ -use crate::lang::parse::ASTNodeId; -use crate::{editor::slow_pool::MarkNodeId, ui::text::text_pos::TextPos}; +use crate::{ui::text::text_pos::TextPos}; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId; From 9a88ba72d7c7230887c51707b87e548e1f7d132a Mon Sep 17 00:00:00 2001 From: Anton-4 <17049058+Anton-4@users.noreply.github.com> Date: Mon, 4 Oct 2021 09:56:35 +0200 Subject: [PATCH 96/96] fmt --- editor/src/editor/ed_error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/src/editor/ed_error.rs b/editor/src/editor/ed_error.rs index 1ebd3a88e6..357693bb69 100644 --- a/editor/src/editor/ed_error.rs +++ b/editor/src/editor/ed_error.rs @@ -1,4 +1,4 @@ -use crate::{ui::text::text_pos::TextPos}; +use crate::ui::text::text_pos::TextPos; use colored::*; use roc_ast::ast_error::ASTError; use roc_ast::lang::core::ast::ASTNodeId;