diff --git a/compiler/gen/src/llvm/refcounting.rs b/compiler/gen/src/llvm/refcounting.rs index cb1fa0a5a5..f18fb6221b 100644 --- a/compiler/gen/src/llvm/refcounting.rs +++ b/compiler/gen/src/llvm/refcounting.rs @@ -8,6 +8,7 @@ use crate::llvm::convert::{ basic_type_from_layout, block_of_memory, block_of_memory_slices, ptr_int, }; use bumpalo::collections::Vec; +use inkwell::basic_block::BasicBlock; use inkwell::context::Context; use inkwell::module::Linkage; use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum}; @@ -94,6 +95,14 @@ impl<'ctx> PointerToRefcount<'ctx> { Self::from_ptr_to_data(env, data_ptr) } + pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> { + let current = self.get_refcount(env); + let one = refcount_1(env.context, env.ptr_bytes); + + env.builder + .build_int_compare(IntPredicate::EQ, current, one, "is_one") + } + pub fn get_refcount<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> { env.builder .build_load(self.value, "get_refcount") @@ -724,7 +733,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>( Layout::RecursivePointer => match when_recursive { WhenRecursive::Unreachable => { - unreachable!("recursion pointers should never be hashed directly") + unreachable!("recursion pointers cannot be in/decremented directly") } WhenRecursive::Loop(union_layout) => { let layout = Layout::Union(*union_layout); @@ -1249,7 +1258,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( layout_ids: &mut LayoutIds<'a>, mode: Mode, when_recursive: &WhenRecursive<'a>, - tags: &[&[Layout<'a>]], + tags: &'a [&'a [roc_mono::layout::Layout<'a>]], fn_val: FunctionValue<'ctx>, is_nullable: bool, ) { @@ -1258,8 +1267,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>( let context = &env.context; let builder = env.builder; - let pick = |a, b| if let Mode::Inc = mode { a } else { b }; - // Add a basic block for the entry point let entry = context.append_basic_block(fn_val, "entry"); @@ -1281,6 +1288,92 @@ fn build_rec_union_help<'a, 'ctx, 'env>( debug_assert!(arg_val.is_pointer_value()); let value_ptr = arg_val.into_pointer_value(); + // to increment/decrement the cons-cell itself + let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr); + let call_mode = mode_to_call_mode(fn_val, mode); + + let should_recurse_block = env.context.append_basic_block(parent, "should_recurse"); + + let ctx = env.context; + if is_nullable { + let is_null = env.builder.build_is_null(value_ptr, "is_null"); + + let then_block = ctx.append_basic_block(parent, "then"); + + env.builder + .build_conditional_branch(is_null, then_block, should_recurse_block); + + { + env.builder.position_at_end(then_block); + env.builder.build_return(None); + } + } else { + env.builder.build_unconditional_branch(should_recurse_block); + } + + env.builder.position_at_end(should_recurse_block); + + match mode { + Mode::Inc => { + // inc is cheap; we never recurse + refcount_ptr.modify(call_mode, &layout, env); + env.builder.build_return(None); + } + + Mode::Dec => { + let do_recurse_block = env.context.append_basic_block(parent, "do_recurse"); + let no_recurse_block = env.context.append_basic_block(parent, "no_recurse"); + + builder.build_conditional_branch( + refcount_ptr.is_1(env), + do_recurse_block, + no_recurse_block, + ); + + { + env.builder.position_at_end(no_recurse_block); + + refcount_ptr.modify(call_mode, &layout, env); + env.builder.build_return(None); + } + + { + env.builder.position_at_end(do_recurse_block); + + build_rec_union_recursive_decrement( + env, + layout_ids, + when_recursive, + parent, + fn_val, + layout, + tags, + value_ptr, + refcount_ptr, + do_recurse_block, + ) + } + } + } +} + +#[allow(clippy::too_many_arguments)] +fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + when_recursive: &WhenRecursive<'a>, + parent: FunctionValue<'ctx>, + decrement_fn: FunctionValue<'ctx>, + layout: Layout<'a>, + tags: &[&[Layout<'a>]], + value_ptr: PointerValue<'ctx>, + refcount_ptr: PointerToRefcount<'ctx>, + match_block: BasicBlock<'ctx>, +) { + let mode = Mode::Dec; + let call_mode = mode_to_call_mode(decrement_fn, mode); + let builder = env.builder; + // branches that are not/don't contain anything refcounted // if there is only one branch, we don't need to switch let switch_needed: bool = (|| { @@ -1296,28 +1389,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>( false })(); - // to increment/decrement the cons-cell itself - let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, value_ptr); - let call_mode = mode_to_call_mode(fn_val, mode); - - let ctx = env.context; - let cont_block = ctx.append_basic_block(parent, "cont"); - if is_nullable { - let is_null = env.builder.build_is_null(value_ptr, "is_null"); - - let then_block = ctx.append_basic_block(parent, "then"); - - env.builder - .build_conditional_branch(is_null, then_block, cont_block); - - { - env.builder.position_at_end(then_block); - env.builder.build_return(None); - } - } else { - env.builder.build_unconditional_branch(cont_block); - } - // next, make a jump table for all possible values of the tag_id let mut cases = Vec::with_capacity_in(tags.len(), env.arena); @@ -1330,9 +1401,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( continue; } - let block = env - .context - .append_basic_block(parent, pick("tag_id_increment", "tag_id_decrement")); + let block = env.context.append_basic_block(parent, "tag_id_decrement"); env.builder.position_at_end(block); @@ -1382,10 +1451,9 @@ fn build_rec_union_help<'a, 'ctx, 'env>( .build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer") .unwrap(); - let field = env.builder.build_load( - elem_pointer, - pick("increment_struct_field", "decrement_struct_field"), - ); + let field = env + .builder + .build_load(elem_pointer, "decrement_struct_field"); deferred_nonrec.push((field, field_layout)); } @@ -1404,7 +1472,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( env, parent, layout_ids, - mode.to_call_mode(fn_val), + mode.to_call_mode(decrement_fn), when_recursive, field, field_layout, @@ -1413,7 +1481,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( for ptr in deferred_rec { // recursively decrement the field - let call = call_help(env, fn_val, mode.to_call_mode(fn_val), ptr); + let call = call_help(env, decrement_fn, mode.to_call_mode(decrement_fn), ptr); call.set_tail_call(true); } @@ -1426,9 +1494,9 @@ fn build_rec_union_help<'a, 'ctx, 'env>( )); } - cases.reverse(); + env.builder.position_at_end(match_block); - env.builder.position_at_end(cont_block); + cases.reverse(); if cases.len() == 1 && !switch_needed { // there is only one tag in total; we don't need a switch @@ -1441,9 +1509,7 @@ fn build_rec_union_help<'a, 'ctx, 'env>( // read the tag_id let current_tag_id = rec_union_read_tag(env, value_ptr); - let merge_block = env - .context - .append_basic_block(parent, pick("increment_merge", "decrement_merge")); + let merge_block = env.context.append_basic_block(parent, "decrement_merge"); // switch on it env.builder diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index e1273e0335..fd203de818 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -2025,7 +2025,7 @@ fn update<'a>( } MadeSpecializations { module_id, - mut ident_ids, + ident_ids, subs, procedures, external_specializations_requested, @@ -2048,6 +2048,8 @@ fn update<'a>( && state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations { + Proc::insert_refcount_operations(arena, &mut state.procedures); + // display the mono IR of the module, for debug purposes if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS { let procs_string = state @@ -2061,14 +2063,14 @@ fn update<'a>( println!("{}", result); } - Proc::insert_refcount_operations(arena, &mut state.procedures); - - Proc::optimize_refcount_operations( - arena, - module_id, - &mut ident_ids, - &mut state.procedures, - ); + // This is not safe with the new non-recursive RC updates that we do for tag unions + // + // Proc::optimize_refcount_operations( + // arena, + // module_id, + // &mut ident_ids, + // &mut state.procedures, + // ); state.constrained_ident_ids.insert(module_id, ident_ids); diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 5a266857ad..79176a02b5 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1113,7 +1113,6 @@ fn layout_from_flat_type<'a>( // That means none of the optimizations for enums or single tag tag unions apply let rec_var = subs.get_root_key_without_compacting(rec_var); - env.insert_seen(rec_var); let mut tag_layouts = Vec::with_capacity_in(tags.len(), arena); // VERY IMPORTANT: sort the tags @@ -1131,6 +1130,7 @@ fn layout_from_flat_type<'a>( } } + env.insert_seen(rec_var); for (index, (_name, variables)) in tags_vec.into_iter().enumerate() { if matches!(nullable, Some(i) if i == index as i64) { // don't add the @@ -1163,6 +1163,7 @@ fn layout_from_flat_type<'a>( tag_layouts.push(tag_layout.into_bump_slice()); } + env.remove_seen(rec_var); let union_layout = if let Some(tag_id) = nullable { match tag_layouts.into_bump_slice() { @@ -1186,8 +1187,6 @@ fn layout_from_flat_type<'a>( UnionLayout::Recursive(tag_layouts.into_bump_slice()) }; - env.remove_seen(rec_var); - Ok(Layout::Union(union_layout)) } EmptyTagUnion => { @@ -1422,10 +1421,6 @@ pub fn union_sorted_tags_help<'a>( seen: MutSet::default(), }; - if let Some(rec_var) = opt_rec_var { - env.insert_seen(rec_var); - } - match tags_vec.len() { 0 => { // trying to instantiate a type with no values diff --git a/compiler/test_mono/generated/let_with_record_pattern_list.txt b/compiler/test_mono/generated/let_with_record_pattern_list.txt index d7763fed7c..1b7ae1ec45 100644 --- a/compiler/test_mono/generated/let_with_record_pattern_list.txt +++ b/compiler/test_mono/generated/let_with_record_pattern_list.txt @@ -6,5 +6,6 @@ procedure Test.0 (): let Test.5 = 3.14f64; let Test.3 = Struct {Test.4, Test.5}; let Test.1 = Index 0 Test.3; - decref Test.3; + inc Test.1; + dec Test.3; ret Test.1; diff --git a/compiler/test_mono/generated/peano2.txt b/compiler/test_mono/generated/peano2.txt index 65764f2fe8..03ba41ecf0 100644 --- a/compiler/test_mono/generated/peano2.txt +++ b/compiler/test_mono/generated/peano2.txt @@ -12,10 +12,11 @@ procedure Test.0 (): let Test.17 = lowlevel Eq Test.15 Test.16; if Test.17 then let Test.11 = Index 1 Test.2; + inc Test.11; + dec Test.2; let Test.12 = 0i64; let Test.13 = Index 0 Test.11; dec Test.11; - decref Test.2; let Test.14 = lowlevel Eq Test.12 Test.13; if Test.14 then let Test.7 = 1i64; @@ -24,5 +25,6 @@ procedure Test.0 (): let Test.9 = 0i64; ret Test.9; else + dec Test.2; let Test.10 = 0i64; ret Test.10; diff --git a/editor/README.md b/editor/README.md index acb1311068..cbae3eb8ff 100644 --- a/editor/README.md +++ b/editor/README.md @@ -1,5 +1,5 @@ -The editor is a work in progress, only a limited subset of Roc expressions are currently supported. New features are added every week! +The editor is a work in progress, only a limited subset of Roc expressions are currently supported. Unlike most editors, we use projectional or structural editing to edit the [Abstract Syntax Tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) directly. This will allow for cool features like excellent auto-complete and refactoring. @@ -28,4 +28,4 @@ We thank the following open source projects in particular for inspiring us when - [learn-wgpu](https://github.com/sotrh/learn-wgpu) - [rgx](https://github.com/cloudhead/rgx) - [elm-editor](https://github.com/jxxcarlson/elm-editor) -- [iced](https://github.com/hecrj/iced) \ No newline at end of file +- [iced](https://github.com/hecrj/iced) diff --git a/editor/editor-ideas.md b/editor/editor-ideas.md index 41ff4dbfff..f17adf94a3 100644 --- a/editor/editor-ideas.md +++ b/editor/editor-ideas.md @@ -41,7 +41,11 @@ Nice collection of research on innovative editors, [link](https://futureofcoding * [Hest](https://ivanish.ca/hest-time-travel/) tool for making highly interactive simulations. * Say you have a failing test that used to work, it would be very valuable to see all code that was changed that was used only by that test. e.g. you have a test `calculate_sum_test` that only uses the function `add`, when the test fails you should be able to see a diff showing only what changed for the function `add`. It would also be great to have a diff of [expression values](https://homepages.cwi.nl/~storm/livelit/images/bret.png) Bret Victor style. An ambitious project would be to suggest or automatically try fixes based on these diffs. -* I think it could be possible to create a minimal reproduction of a program / block of code / code used by a single test. So for a failing unit test I would expect it to extract imports, the platform, types and functions that are necessary to run only that unit test and put them in a standalone roc project. This would be useful for sharing bugs with library+application authors and colleagues, for profiling or debugging with all "clutter" removed. +* I think it could be possible to create a minimal reproduction of a program / block of code / code used by a single test. So for a failing unit test I would expect it to extract imports, the platform, types and functions that are necessary to run only that unit test and put them in a standalone roc project. This would be useful for sharing bugs with library+application authors and colleagues, for profiling or debugging with all "clutter" removed. + +### Cool regular editors + +* [Helix](https://github.com/helix-editor/helix) modal (terminal, for now) editor in rust. Good UX. ### Structured Editing