From 9b43bf406402cfdbba51dce1a36a22c1abd69735 Mon Sep 17 00:00:00 2001 From: Logan Lowder Date: Sat, 30 Oct 2021 13:11:37 -0500 Subject: [PATCH 1/6] Update macOS docs regarding llvm See more details here: https://github.com/rtfeldman/roc/issues/1843 --- BUILDING_FROM_SOURCE.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index a531e22169..1b6cf814a5 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -50,8 +50,12 @@ If you want to install it manually, you can also download Zig directly [here](ht **version: 12.0.x** For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding -`/usr/local/opt/llvm/bin` to your `PATH`. You can confirm this worked by +`/usr/local/opt/llvm@12/bin` to your `PATH`. You can confirm this worked by running `llc --version` - it should mention "LLVM version 12.0.0" at the top. +You may also need to manually specify a prefix env var like so: +``` +export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12 +``` For Ubuntu and Debian: ``` From ef3ec61822bfe84307b2b400a225393c578fa692 Mon Sep 17 00:00:00 2001 From: Logan Lowder Date: Sat, 30 Oct 2021 13:14:56 -0500 Subject: [PATCH 2/6] Correct incorrectly ordered commands `clang` maps to `clang-12` and `llvm-as` maps to `llvm-as-12` --- BUILDING_FROM_SOURCE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 1b6cf814a5..df825d2935 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -66,7 +66,7 @@ chmod +x llvm.sh ``` If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`. -By default, the script installs them as `llvm-as-12` and `clang-12`, +By default, the script installs them as `clang-12` and `llvm-as-12`, respectively. You can address this with symlinks like so: ``` From a8e05e31c0be728378ae224437bf5a26393fb59f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 30 Oct 2021 19:01:51 -0400 Subject: [PATCH 3/6] Add Logan Lowder to AUTHORS --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 6aee48b7d8..52c85f4493 100644 --- a/AUTHORS +++ b/AUTHORS @@ -42,4 +42,4 @@ Viktor Fröberg Locria Cyber Matthias Beyer Tim Whiting - +Logan Lowder From 4afaf96aea4f4d7a45f9ac9c3297b8e69e98dea4 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sat, 30 Oct 2021 19:17:35 -0500 Subject: [PATCH 4/6] Add List.max builtin --- compiler/builtins/src/std.rs | 7 ++ compiler/can/src/builtins.rs | 131 ++++++++++++++++++++++++++++++ compiler/module/src/symbol.rs | 2 + compiler/test_gen/src/gen_list.rs | 26 ++++++ 4 files changed, 166 insertions(+) diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index e416912b1f..8ddc7e9990 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -753,6 +753,13 @@ pub fn types() -> MutMap { add_top_level_function_type!( Symbol::LIST_MIN, vec![list_type(num_type(flex(TVAR1)))], + Box::new(result_type(num_type(flex(TVAR1)), list_was_empty.clone())), + ); + + // max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* + add_top_level_function_type!( + Symbol::LIST_MAX, + vec![list_type(num_type(flex(TVAR1)))], Box::new(result_type(num_type(flex(TVAR1)), list_was_empty)), ); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index c21ed5f27c..5b93c6510f 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -81,6 +81,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_CONCAT => list_concat, LIST_CONTAINS => list_contains, LIST_MIN => list_min, + LIST_MAX => list_max, LIST_SUM => list_sum, LIST_PRODUCT => list_product, LIST_PREPEND => list_prepend, @@ -2268,6 +2269,136 @@ fn list_min_lt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr { ) } +// max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* +fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def { + let arg_var = var_store.fresh(); + let bool_var = var_store.fresh(); + let list_var = var_store.fresh(); + let len_var = Variable::NAT; + let num_var = len_var; + let num_precision_var = Variable::NATURAL; + let list_elem_var = var_store.fresh(); + let ret_var = var_store.fresh(); + let closure_var = var_store.fresh(); + + // Perform a bounds check. If it passes, delegate to List.getUnsafe. + let body = If { + cond_var: bool_var, + branch_var: var_store.fresh(), + branches: vec![( + // if-condition + no_region( + // List.len list != 0 + RunLowLevel { + op: LowLevel::NotEq, + args: vec![ + (len_var, int(num_var, num_precision_var, 0)), + ( + len_var, + RunLowLevel { + op: LowLevel::ListLen, + args: vec![(list_var, Var(Symbol::ARG_1))], + ret_var: len_var, + }, + ), + ], + ret_var: bool_var, + }, + ), + // list was not empty + no_region( + // Ok ( List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc ) + tag( + "Ok", + vec![ + // List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc + RunLowLevel { + op: LowLevel::ListWalk, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + // (List.getUnsafe list 0) + ( + list_elem_var, + RunLowLevel { + op: LowLevel::ListGetUnsafe, + args: vec![ + (list_var, Var(Symbol::ARG_1)), + (arg_var, int(num_var, num_precision_var, 0)), + ], + ret_var: list_elem_var, + }, + ), + // \acc,elem -> if elem < acc then elem else acc + (closure_var, list_max_gt(list_elem_var, var_store)), + ], + ret_var: list_elem_var, + }, + ], + var_store, + ), + ), + )], + final_else: Box::new( + // list was empty + no_region( + // Err ListWasEmpty + tag( + "Err", + vec![tag("ListWasEmpty", Vec::new(), var_store)], + var_store, + ), + ), + ), + }; + + defn( + symbol, + vec![(list_var, Symbol::ARG_1)], + var_store, + body, + ret_var, + ) +} + +// \acc,elem -> if elem > acc then elem else acc +fn list_max_gt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr { + let bool_var = var_store.fresh(); + // if elem > acc then elem else acc + let body = If { + cond_var: bool_var, + branch_var: list_elem_var, + branches: vec![( + // if-condition + no_region( + // elem > acc + RunLowLevel { + op: LowLevel::NumGt, + args: vec![ + (list_elem_var, Var(Symbol::ARG_4)), + (list_elem_var, Var(Symbol::ARG_3)), + ], + ret_var: bool_var, + }, + ), + // return elem + no_region(Var(Symbol::ARG_4)), + )], + // return acc + final_else: Box::new(no_region(Var(Symbol::ARG_3))), + }; + + defn_help( + Symbol::LIST_MAX_GT, + vec![ + (list_elem_var, Symbol::ARG_3), + (list_elem_var, Symbol::ARG_4), + ], + var_store, + body, + list_elem_var, + ) +} + /// List.sum : List (Num a) -> Num a fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def { let num_var = var_store.fresh(); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index ef3e57ad73..ec5f7edda7 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1058,6 +1058,8 @@ define_builtins! { 35 LIST_DROP_LAST: "dropLast" 36 LIST_MIN: "min" 37 LIST_MIN_LT: "#minlt" + 38 LIST_MAX: "#max" + 39 LIST_MAX_GT: "#maxGt" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 0aa87308cd..27006abdf3 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -1975,6 +1975,32 @@ fn list_min() { ); } +#[test] +fn list_max() { + assert_evals_to!( + indoc!( + r#" + when List.max [] is + Ok val -> val + Err _ -> -1 + "# + ), + -1, + i64 + ); + assert_evals_to!( + indoc!( + r#" + when List.max [3, 1, 2] is + Ok val -> val + Err _ -> -1 + "# + ), + 3, + i64 + ); +} + #[test] fn list_sum() { assert_evals_to!("List.sum []", 0, i64); From e01942f3877b6d75cbd61ff27af901c22a81c5b4 Mon Sep 17 00:00:00 2001 From: Chelsea Troy Date: Sat, 30 Oct 2021 19:23:08 -0500 Subject: [PATCH 5/6] Whoops, let's make List.max actually accessible on the frontend, lol --- compiler/module/src/symbol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index ec5f7edda7..56acdddcb2 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1058,7 +1058,7 @@ define_builtins! { 35 LIST_DROP_LAST: "dropLast" 36 LIST_MIN: "min" 37 LIST_MIN_LT: "#minlt" - 38 LIST_MAX: "#max" + 38 LIST_MAX: "max" 39 LIST_MAX_GT: "#maxGt" } 5 RESULT: "Result" => { From a40c4f4fd5de299636b8ff21e622ca5ccdfdd413 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 31 Oct 2021 13:47:02 +0100 Subject: [PATCH 6/6] use Entry --- compiler/mono/src/layout.rs | 113 ++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 38 deletions(-) diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 7768852324..26fff65873 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -8,6 +8,7 @@ use roc_types::subs::{ Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField}; +use std::collections::hash_map::Entry; use std::collections::HashMap; use ven_pretty::{DocAllocator, DocBuilder}; @@ -2534,6 +2535,64 @@ struct IdsByLayout<'a> { next_id: u32, } +impl<'a> IdsByLayout<'a> { + #[inline(always)] + fn insert_layout(&mut self, layout: Layout<'a>) -> LayoutId { + match self.by_id.entry(layout) { + Entry::Vacant(vacant) => { + let answer = self.next_id; + vacant.insert(answer); + self.next_id += 1; + + LayoutId(answer) + } + Entry::Occupied(occupied) => LayoutId(*occupied.get()), + } + } + + #[inline(always)] + fn singleton_layout(layout: Layout<'a>) -> (Self, LayoutId) { + let mut by_id = HashMap::with_capacity_and_hasher(1, default_hasher()); + by_id.insert(layout, 1); + + let ids_by_layout = IdsByLayout { + by_id, + toplevels_by_id: Default::default(), + next_id: 2, + }; + + (ids_by_layout, LayoutId(1)) + } + + #[inline(always)] + fn insert_toplevel(&mut self, layout: crate::ir::ProcLayout<'a>) -> LayoutId { + match self.toplevels_by_id.entry(layout) { + Entry::Vacant(vacant) => { + let answer = self.next_id; + vacant.insert(answer); + self.next_id += 1; + + LayoutId(answer) + } + Entry::Occupied(occupied) => LayoutId(*occupied.get()), + } + } + + #[inline(always)] + fn singleton_toplevel(layout: crate::ir::ProcLayout<'a>) -> (Self, LayoutId) { + let mut toplevels_by_id = HashMap::with_capacity_and_hasher(1, default_hasher()); + toplevels_by_id.insert(layout, 1); + + let ids_by_layout = IdsByLayout { + by_id: Default::default(), + toplevels_by_id, + next_id: 2, + }; + + (ids_by_layout, LayoutId(1)) + } +} + #[derive(Default)] pub struct LayoutIds<'a> { by_symbol: MutMap>, @@ -2542,60 +2601,38 @@ pub struct LayoutIds<'a> { impl<'a> LayoutIds<'a> { /// Returns a LayoutId which is unique for the given symbol and layout. /// If given the same symbol and same layout, returns the same LayoutId. + #[inline(always)] pub fn get<'b>(&mut self, symbol: Symbol, layout: &'b Layout<'a>) -> LayoutId { - // Note: this function does some weird stuff to satisfy the borrow checker. - // There's probably a nicer way to write it that still works. - let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout { - by_id: HashMap::with_capacity_and_hasher(1, default_hasher()), - toplevels_by_id: Default::default(), - next_id: 1, - }); + match self.by_symbol.entry(symbol) { + Entry::Vacant(vacant) => { + let (ids_by_layout, layout_id) = IdsByLayout::singleton_layout(*layout); - // Get the id associated with this layout, or default to next_id. - let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id); + vacant.insert(ids_by_layout); - // If we had to default to next_id, it must not have been found; - // store the ID we're going to return and increment next_id. - if answer == ids.next_id { - ids.by_id.insert(*layout, ids.next_id); - - ids.next_id += 1; + layout_id + } + Entry::Occupied(mut occupied_ids) => occupied_ids.get_mut().insert_layout(*layout), } - - LayoutId(answer) } /// Returns a LayoutId which is unique for the given symbol and layout. /// If given the same symbol and same layout, returns the same LayoutId. + #[inline(always)] pub fn get_toplevel<'b>( &mut self, symbol: Symbol, layout: &'b crate::ir::ProcLayout<'a>, ) -> LayoutId { - // Note: this function does some weird stuff to satisfy the borrow checker. - // There's probably a nicer way to write it that still works. - let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout { - by_id: Default::default(), - toplevels_by_id: HashMap::with_capacity_and_hasher(1, default_hasher()), - next_id: 1, - }); + match self.by_symbol.entry(symbol) { + Entry::Vacant(vacant) => { + let (ids_by_layout, layout_id) = IdsByLayout::singleton_toplevel(*layout); - // Get the id associated with this layout, or default to next_id. - let answer = ids - .toplevels_by_id - .get(layout) - .copied() - .unwrap_or(ids.next_id); + vacant.insert(ids_by_layout); - // If we had to default to next_id, it must not have been found; - // store the ID we're going to return and increment next_id. - if answer == ids.next_id { - ids.toplevels_by_id.insert(*layout, ids.next_id); - - ids.next_id += 1; + layout_id + } + Entry::Occupied(mut occupied_ids) => occupied_ids.get_mut().insert_toplevel(*layout), } - - LayoutId(answer) } }