diff options
38 files changed, 4572 insertions, 2701 deletions
@@ -84,6 +84,7 @@ OBJ += expand/include.o OBJ += expand/env.o OBJ += expand/test.o OBJ += expand/rustc_diagnostics.o +OBJ += expand/test_harness.o OBJ += macro_rules/mod.o macro_rules/eval.o macro_rules/parse.o OBJ += resolve/use.o resolve/index.o resolve/absolute.o OBJ += hir/from_ast.o hir/from_ast_expr.o @@ -97,7 +98,8 @@ OBJ += hir_typeck/outer.o hir_typeck/common.o hir_typeck/helpers.o hir_typeck/st OBJ += hir_typeck/expr_visit.o OBJ += hir_typeck/expr_cs.o OBJ += hir_typeck/expr_check.o -OBJ += hir_expand/annotate_value_usage.o hir_expand/closures.o hir_expand/ufcs_everything.o +OBJ += hir_expand/annotate_value_usage.o hir_expand/closures.o +OBJ += hir_expand/ufcs_everything.o OBJ += hir_expand/reborrow.o hir_expand/erased_types.o hir_expand/vtable.o OBJ += hir_expand/const_eval_full.o OBJ += mir/mir.o mir/mir_ptr.o @@ -144,7 +146,20 @@ output/lib%.hir: $(RUSTCSRC)src/lib%/src/lib.rs $(RUSTCSRC) $(BIN) $(DBG) $(ENV_$@) $(BIN) $< -o $@ $(RUST_FLAGS) $(ARGS_$@) $(PIPECMD) # # HACK: Work around gdb returning success even if the program crashed @test -e $@ - +output/lib%-test: $(RUSTCSRC)src/lib%/lib.rs $(RUSTCSRC) $(BIN) output/libtest.hir + @echo "--- [MRUSTC] --test -o $@" + @mkdir -p output/ + @rm -f $@ + $(DBG) $(ENV_$@) $(BIN) --test $< -o $@ -L output/libs $(RUST_FLAGS) $(ARGS_$@) $(PIPECMD) +# # HACK: Work around gdb returning success even if the program crashed + @test -e $@ +output/lib%-test: $(RUSTCSRC)src/lib%/src/lib.rs $(RUSTCSRC) $(BIN) output/libtest.hir + @echo "--- [MRUSTC] $@" + @mkdir -p output/ + @rm -f $@ + $(DBG) $(ENV_$@) $(BIN) --test $< -o $@ -L output/libs $(RUST_FLAGS) $(ARGS_$@) $(PIPECMD) +# # HACK: Work around gdb returning success even if the program crashed + @test -e $@ fcn_extcrate = $(patsubst %,output/lib%.hir,$(1)) fn_getdeps = \ @@ -282,7 +297,7 @@ output/rustc: $(RUSTCSRC)src/rustc/rustc.rs output/librustc_driver.hir output/ru .PHONY: RUSTCSRC RUSTCSRC: $(RUSTCSRC) -$(RUSTCSRC): rust-nightly-date +$(RUSTCSRC): rust-nightly-date rust_src.patch @export DL_RUST_DATE=$$(cat rust-nightly-date); \ export DISK_RUST_DATE=$$([ -f $(RUSTC_SRC_DL) ] && cat $(RUSTC_SRC_DL)); \ if [ "$$DL_RUST_DATE" != "$$DISK_RUST_DATE" ]; then \ @@ -291,6 +306,7 @@ $(RUSTCSRC): rust-nightly-date rm -rf rustc-nightly; \ curl -sS https://static.rust-lang.org/dist/$${DL_RUST_DATE}/rustc-nightly-src.tar.gz -o rustc-nightly-src.tar.gz; \ tar -xf rustc-nightly-src.tar.gz --transform 's~^rustc-nightly-src~rustc-nightly~'; \ + patch -p0 < rust_src.patch; \ echo "$$DL_RUST_DATE" > $(RUSTC_SRC_DL); \ fi @@ -436,18 +452,13 @@ DISABLED_TESTS += run-pass/dst-trait DISABLED_TESTS += run-pass/if-ret # If condition wasn't a bool DISABLED_TESTS += run-pass/intrinsics-integer # todo - bswap<i8> DISABLED_TESTS += run-pass/issue-11940 # todo: Match literal Borrow +DISABLED_TESTS += run-pass/mir_build_match_comparisons # - ^ DISABLED_TESTS += run-pass/issue-13620 # - Todo in cleanup +DISABLED_TESTS += run-pass/vec-matching-fold # todo: Match SplitSlice with tailing (rule gen) DISABLED_TESTS += run-pass/issue-17877 # - SplitSlice + array DISABLED_TESTS += run-pass/vec-matching-fixed # ^ -DISABLED_TESTS += run-pass/issue-15104 # - SplitSlice in DTN -DISABLED_TESTS += run-pass/issue-15080 # ^ -DISABLED_TESTS += run-pass/issue-37598 # ^ -DISABLED_TESTS += run-pass/vec-matching-fold # ^ -DISABLED_TESTS += run-pass/vec-matching-legal-tail-element-borrow # ^ DISABLED_TESTS += run-pass/vec-tail-matching # SplitSlice destructure array DISABLED_TESTS += run-pass/zero_sized_subslice_match # ^ -DISABLED_TESTS += run-pass/issue-18060 # - Overlapping value ranges -DISABLED_TESTS += run-pass/issue-13867 # ^ DISABLED_TESTS += run-pass/issue-18352 # - Match+const DISABLED_TESTS += run-pass/issue-28839 # - Move &mut ? DISABLED_TESTS += run-pass/union/union-inherent-method # ^ ? @@ -461,7 +472,6 @@ DISABLED_TESTS += run-pass/issue-30018-nopanic # ^ DISABLED_TESTS += run-pass/match-bot-2 # ^ DISABLED_TESTS += run-pass/unreachable-code # ^ DISABLED_TESTS += run-pass/issue-36936 # - Cast removed -DISABLED_TESTS += run-pass/mir_build_match_comparisons # todo in match DISABLED_TESTS += run-pass/struct-order-of-eval-1 # Struct init order (fails validation) DISABLED_TESTS += run-pass/struct-order-of-eval-3 # ^ DISABLED_TESTS += run-pass/const-enum-vec-index # This is valid code? @@ -705,6 +715,7 @@ DISABLED_TESTS += run-pass/vector-sort-panic-safe DISABLED_TESTS += run-pass/dynamic-drop # - Misc DISABLED_TESTS += run-pass/issue-16671 # Blocks forever +DISABLED_TESTS += run-pass/issue-13027 # Infinite loop (match?) # - BUG: Incorrect drop order of ? DISABLED_TESTS += run-pass/issue-23338-ensure-param-drop-order # - BUG: Incorrect consteval @@ -773,14 +784,20 @@ DISABLED_TESTS += run-pass/u128 # u128 not very good, unknown where error is DEF_RUST_TESTS = $(sort $(patsubst $(RUST_TESTS_DIR)%.rs,output/rust/%_out.txt,$(wildcard $(RUST_TESTS_DIR)$1/*.rs))) rust_tests-run-pass: $(filter-out $(patsubst %,output/rust/%_out.txt,$(DISABLED_TESTS)), $(call DEF_RUST_TESTS,run-pass) $(call DEF_RUST_TESTS,run-pass/union)) rust_tests-run-fail: $(call DEF_RUST_TESTS,run-fail) + +LIB_TESTS := collections collectionstest rustc_data_structures +RUNTIME_ARGS_output/libcollectionstest-test := --test-threads 1 --skip linked_list::test_ord_nan --skip ::slice::test_box_slice_clone_panics +rust_tests-libs: $(patsubst %,output/lib%-test_out.txt, $(LIB_TESTS)) + #rust_tests-compile-fail: $(call DEF_RUST_TESTS,compile-fail) output/rust/test_run-pass_hello: $(RUST_TESTS_DIR)run-pass/hello.rs output/libstd.hir $(BIN) output/liballoc_system.hir output/libpanic_abort.hir @mkdir -p $(dir $@) @echo "--- [MRUSTC] -o $@" $(DBG) $(BIN) $< -L output/libs -o $@ $(RUST_FLAGS) $(PIPECMD) - @echo "--- [$@]" - @./$@ +output/rust/test_run-pass_hello_out.txt: output/rust/test_run-pass_hello + @echo "--- [$<]" + @./$< | tee $@ TEST_ARGS_run-pass/cfg-in-crate-1 := --cfg bar TEST_ARGS_run-pass/cfgs-on-items := --cfg fooA --cfg fooB @@ -796,9 +813,9 @@ output/rust/%: $(RUST_TESTS_DIR)%.rs $(RUSTCSRC) $(BIN) output/libstd.hir output @echo "=== TEST $(patsubst output/rust/%,%,$@)" @echo "--- [MRUSTC] -o $@" $V$(BIN) $< -o $@ -L output/libs -L output/test_deps --stop-after $(RUST_TESTS_FINAL_STAGE) $(TEST_ARGS_$*) > $@.txt 2>&1 || (tail -n 1 $@.txt; false) -output/rust/%_out.txt: output/rust/% +output/%_out.txt: output/% @echo "--- [$<]" - @./$< > $@ || (tail -n 1 $@; mv $@ $@_fail; false) + @./$< $(RUNTIME_ARGS_$<) > $@ || (tail -n 1 $@; mv $@ $@_fail; false) output/test_deps/librust_test_helpers.a: output/test_deps/rust_test_helpers.o ar cur $@ $< @@ -830,7 +847,7 @@ test_deps_run-pass.mk: Makefile $(wildcard $(RUST_TESTS_DIR)run_pass/*.rs) # # TEST: Rust standard library and the "hello, world" run-pass test # -test: $(RUSTCSRC) output/libcore.hir output/liballoc.hir output/libcollections.hir output/libstd.hir output/rust/test_run-pass_hello $(BIN) +test: $(RUSTCSRC) output/libcore.hir output/liballoc.hir output/libcollections.hir output/libstd.hir output/rust/test_run-pass_hello_out.txt $(BIN) # # TEST: Attempt to compile rust_os (Tifflin) from ../rust_os diff --git a/Notes/MIR-Match.md b/Notes/MIR-Match.md index f47d2c88..cc95522c 100644 --- a/Notes/MIR-Match.md +++ b/Notes/MIR-Match.md @@ -20,3 +20,22 @@ For each index in the rule (all rules must be the same length) - Ranges sort after? + +Alternative Generator 2 +======================= + +Maintains match ordering + +1. Calculate branch rulesets (as existing) +1. While rules to process: + 1. Group based on shared values. + 1. Generate dispatch arm for each group + 1. Recurse into group, passing local _ as fallback (or parent _ if none) + +``` + +for + +``` + + diff --git a/Notes/todo.txt b/Notes/todo.txt index 35526889..54956718 100644 --- a/Notes/todo.txt +++ b/Notes/todo.txt @@ -19,5 +19,7 @@ TODO: - Optimise typecheck. -- MIR: Add a Parameter type that is either LValue, Constant - > For any place a LValue is currently used, but a constant is valid +- MIR: Unify Variable/Argument/Temporary LValue types + - This should reduce the amount of code needed for validation, but will be a + BIG change. + diff --git a/rust_src.patch b/rust_src.patch new file mode 100644 index 00000000..d782d16b --- /dev/null +++ b/rust_src.patch @@ -0,0 +1,25 @@ +--- rustc-nightly/src/libcore/intrinsics.rs ++++ rustc-nightly/src/libcore/intrinsics.rs +@@ -643,5 +643,8 @@ + pub fn drop_in_place<T: ?Sized>(to_drop: *mut T); + ++ /// Obtain the length of a slice pointer ++ pub fn mrustc_slice_len<T>(pointer: *const [T]) -> usize; ++ + /// Gets a static string slice containing the name of a type. + pub fn type_name<T: ?Sized>() -> &'static str; + +--- rustc-nightly/src/libcore/slice.rs ++++ rustc-nightly/src/libcore/slice.rs +@@ -340,6 +340,8 @@ + #[inline] + fn len(&self) -> usize { +- unsafe { +- mem::transmute::<&[T], Repr<T>>(self).len +- } ++ #[cfg(not(rust_compiler="mrustc"))] ++ let rv = unsafe { mem::transmute::<&[T], Repr<T>>(self).len }; ++ #[cfg(rust_compiler="mrustc")] ++ let rv = unsafe { ::intrinsics::mrustc_slice_len(self) }; ++ rv + } diff --git a/src/ast/crate.hpp b/src/ast/crate.hpp index b74012a3..f9594a83 100644 --- a/src/ast/crate.hpp +++ b/src/ast/crate.hpp @@ -10,6 +10,23 @@ namespace AST { class ExternCrate; +class TestDesc +{ +public: + ::AST::Path path; + ::std::string name; + bool ignore = false; + bool is_benchmark = false; + + enum class ShouldPanic { + No, + Yes, + YesWithMessage, + } panic_type = ShouldPanic::No; + + ::std::string expected_panic_message; +}; + class Crate { public: @@ -22,6 +39,10 @@ public: // Mapping filled by searching for (?visible) macros with is_pub=true ::std::map< ::std::string, const MacroRules*> m_exported_macros; + // List of tests (populated in expand if --test is passed) + bool m_test_harness = false; + ::std::vector<TestDesc> m_tests; + enum class Type { Unknown, RustLib, diff --git a/src/ast/expr.cpp b/src/ast/expr.cpp index c35700e7..dd586683 100644 --- a/src/ast/expr.cpp +++ b/src/ast/expr.cpp @@ -85,9 +85,7 @@ NODE(ExprNode_Block, { ::std::vector<ExprNodeP> nodes; for(const auto& n : m_nodes) nodes.push_back( n->clone() ); - if( m_local_mod ) - TODO(get_pos(), "Handle cloning ExprNode_Block with a module"); - return NEWNODE(ExprNode_Block, m_is_unsafe, m_yields_final_value, mv$(nodes), nullptr); + return NEWNODE(ExprNode_Block, m_is_unsafe, m_yields_final_value, mv$(nodes), m_local_mod); }) NODE(ExprNode_Macro, { diff --git a/src/common.hpp b/src/common.hpp index 0b0fad14..d3aca257 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -68,24 +68,56 @@ static inline Ordering ord(bool l, bool r) else return OrdLess; } +static inline Ordering ord(char l, char r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} + +static inline Ordering ord(unsigned char l, unsigned char r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} +static inline Ordering ord(unsigned short l, unsigned short r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} static inline Ordering ord(unsigned l, unsigned r) { - if(l == r) - return OrdEqual; - else if( l > r ) - return OrdGreater; - else - return OrdLess; + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); } -static inline Ordering ord(::std::uintptr_t l, ::std::uintptr_t r) +static inline Ordering ord(unsigned long l, unsigned long r) { - if(l == r) - return OrdEqual; - else if( l > r ) - return OrdGreater; - else - return OrdLess; + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); } +static inline Ordering ord(unsigned long long l, unsigned long long r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} +static inline Ordering ord(signed char l, signed char r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} +static inline Ordering ord(short l, short r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} +static inline Ordering ord(long l, long r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} +static inline Ordering ord(long long l, long long r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} +static inline Ordering ord(float l, float r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} +static inline Ordering ord(double l, double r) +{ + return (l == r ? OrdEqual : (l > r ? OrdGreater : OrdLess)); +} + static inline Ordering ord(const ::std::string& l, const ::std::string& r) { if(l == r) @@ -296,19 +328,8 @@ struct FmtEscaped { FmtEscaped(const ::std::string& s): s(s.c_str()) {} - friend ::std::ostream& operator<<(::std::ostream& os, const FmtEscaped& x) { - for(auto s = x.s; *s != '\0'; s ++) - { - switch(*s) - { - case '\n': os << "\\n"; break; - case '\\': os << "\\\\"; break; - case '"': os << "\\\""; break; - default: os << *s; break; - } - } - return os; - } + // See main.cpp + friend ::std::ostream& operator<<(::std::ostream& os, const FmtEscaped& x); }; // ------------------------------------------------------------------- diff --git a/src/expand/format_args.cpp b/src/expand/format_args.cpp index 0f881a6e..89503e0c 100644 --- a/src/expand/format_args.cpp +++ b/src/expand/format_args.cpp @@ -251,6 +251,7 @@ namespace { if( *s == '0' ) { args.zero_pad = true; + args.align_char = '0'; s ++; } else { diff --git a/src/expand/mod.cpp b/src/expand/mod.cpp index f152b1ab..b5bbc1a4 100644 --- a/src/expand/mod.cpp +++ b/src/expand/mod.cpp @@ -67,7 +67,7 @@ void Expand_Attrs(/*const */::AST::MetaItems& attrs, AttrStage stage, ::std::fu } void Expand_Attrs(::AST::MetaItems& attrs, AttrStage stage, ::AST::Crate& crate, const ::AST::Path& path, ::AST::Module& mod, ::AST::Item& item) { - Expand_Attrs(attrs, stage, [&](const auto& sp, const auto& d, const auto& a){ d.handle(sp, a, crate, path, mod, item); }); + Expand_Attrs(attrs, stage, [&](const auto& sp, const auto& d, const auto& a){ if(!item.is_None()) d.handle(sp, a, crate, path, mod, item); }); } void Expand_Attrs(::AST::MetaItems& attrs, AttrStage stage, ::AST::Crate& crate, ::AST::Module& mod, ::AST::ImplDef& impl) { @@ -109,7 +109,8 @@ void Expand_Attrs(::AST::MetaItems& attrs, AttrStage stage, ::AST::Crate& crate return e; } } - // TODO: Shouldn't this use the _last_ located macro? Allowing later (local) defininitions to override it? + // Find the last macro of this name (allows later #[macro_use] definitions to override) + const MacroRules* last_mac = nullptr; for( const auto& mri : mac_mod.macro_imports_res() ) { //DEBUG("- " << mri.name); @@ -118,10 +119,14 @@ void Expand_Attrs(::AST::MetaItems& attrs, AttrStage stage, ::AST::Crate& crate if( input_ident != "" ) ERROR(mi_span, E0000, "macro_rules! macros can't take an ident"); - auto e = Macro_Invoke(name.c_str(), *mri.data, mv$(input_tt), mod); - return e; + last_mac = mri.data; } } + if( last_mac ) + { + auto e = Macro_Invoke(name.c_str(), *last_mac, mv$(input_tt), mod); + return e; + } } // Error - Unknown macro name @@ -307,11 +312,9 @@ struct CExpandExpr: assert( ! this->replacement ); } void visit_vector(::std::vector< ::std::unique_ptr<AST::ExprNode> >& cnodes) { - for( auto& child : cnodes ) { - this->visit(child); - } - // Delete null children for( auto it = cnodes.begin(); it != cnodes.end(); ) { + assert( it->get() ); + this->visit(*it); if( it->get() == nullptr ) { it = cnodes.erase( it ); } @@ -321,48 +324,83 @@ struct CExpandExpr: } } - void visit(::AST::ExprNode_Macro& node) override + ::AST::ExprNodeP visit_macro(::AST::ExprNode_Macro& node, ::std::vector< ::AST::ExprNodeP>* nodes_out) { - TRACE_FUNCTION_F("ExprNode_Macro - name = " << node.m_name); + TRACE_FUNCTION_F(node.m_name << "!"); if( node.m_name == "" ) { - return ; + return ::AST::ExprNodeP(); } + ::AST::ExprNodeP rv; auto& mod = this->cur_mod(); - auto ttl = Expand_Macro( - crate, modstack, mod, - Span(node.get_pos()), - node.m_name, node.m_ident, node.m_tokens - ); - if( ttl.get() != nullptr ) + auto ttl = Expand_Macro( crate, modstack, mod, Span(node.get_pos()), node.m_name, node.m_ident, node.m_tokens ); + if( !ttl.get() ) + { + // No expansion + } + else { - if( ttl->lookahead(0) != TOK_EOF ) + while( ttl->lookahead(0) != TOK_EOF ) { SET_MODULE( (*ttl), mod ); + // Reparse as expression / item bool add_silence_if_end = false; ::std::shared_ptr< AST::Module> tmp_local_mod; auto& local_mod_ptr = (this->current_block ? this->current_block->m_local_mod : tmp_local_mod); - DEBUG("-- Parsing as expression line (legacy)"); + DEBUG("-- Parsing as expression line"); auto newexpr = Parse_ExprBlockLine_WithItems(*ttl, local_mod_ptr, add_silence_if_end); + + if( tmp_local_mod ) + TODO(node.get_pos(), "Handle edge case where a macro expansion outside of a _Block creates an item"); + if( newexpr ) { - // TODO: use add_silence_if_end - Applies if this node is the last node in the block. - - // Then call visit on it again - DEBUG("--- Visiting new node"); - this->visit(newexpr); - // And schedule it to replace the previous - replacement = mv$(newexpr); + if( nodes_out ) { + nodes_out->push_back( mv$(newexpr) ); + } + else { + assert( !rv ); + rv = mv$(newexpr); + } } else { - // TODO: Delete this node somehow? Or just leave it for later. + // Expansion line just added a new item + } + + if( ttl->lookahead(0) != TOK_EOF ) + { + if( !nodes_out ) { + ERROR(node.get_pos(), E0000, "Unused tokens at the end of macro expansion - " << ttl->getToken()); + } } - ASSERT_BUG(node.get_pos(), !tmp_local_mod, "TODO: Handle edge case where a macro expansion outside of a _Block creates an item"); } - DEBUG("ExprNode_Macro - replacement = " << replacement.get()); - node.m_name = ""; + } + + node.m_name = ""; + return mv$(rv); + } + + void visit(::AST::ExprNode_Macro& node) override + { + TRACE_FUNCTION_F("ExprNode_Macro - name = " << node.m_name); + if( node.m_name == "" ) { + return ; + } + + replacement = this->visit_macro(node, nullptr); + + if( this->replacement ) + { + DEBUG("--- Visiting new node"); + auto n = mv$(this->replacement); + this->visit(n); + if( n ) + { + assert( !this->replacement ); + this->replacement = mv$(n); + } } } @@ -377,7 +415,40 @@ struct CExpandExpr: auto saved = this->current_block; this->current_block = &node; - this->visit_vector(node.m_nodes); + + for( auto it = node.m_nodes.begin(); it != node.m_nodes.end(); ) + { + assert( it->get() ); + + if( auto* node_mac = dynamic_cast<::AST::ExprNode_Macro*>(it->get()) ) + { + Expand_Attrs((*it)->attrs(), AttrStage::Pre, [&](const auto& sp, const auto& d, const auto& a){ d.handle(sp, a, this->crate, *it); }); + if( !it->get() ) { + it = node.m_nodes.erase( it ); + continue ; + } + + assert(it->get() == node_mac); + + ::std::vector< ::AST::ExprNodeP> new_nodes; + this->visit_macro(*node_mac, &new_nodes); + + it = node.m_nodes.erase(it); + it = node.m_nodes.insert(it, ::std::make_move_iterator(new_nodes.begin()), ::std::make_move_iterator(new_nodes.end())); + // NOTE: Doesn't advance the iterator above, we want to re-visit the new node + } + else + { + this->visit(*it); + if( it->get() == nullptr ) { + it = node.m_nodes.erase( it ); + } + else { + ++ it; + } + } + } + this->current_block = saved; // HACK! Run Expand_Mod twice on local modules. diff --git a/src/expand/test.cpp b/src/expand/test.cpp index fba6556f..01e566ff 100644 --- a/src/expand/test.cpp +++ b/src/expand/test.cpp @@ -7,6 +7,7 @@ */ #include <synext_decorator.hpp> #include <ast/ast.hpp> +#include <ast/crate.hpp> class CTestHandler: public ExpandDecorator @@ -18,10 +19,83 @@ class CTestHandler: ERROR(sp, E0000, "#[test] can only be put on functions - found on " << i.tag_str()); } - // TODO: Proper #[test] support, for now just remove them - i = AST::Item::make_None({}); + if( crate.m_test_harness ) + { + ::AST::TestDesc td; + for(const auto& node : path.nodes()) + { + td.name += "::"; + td.name += node.name(); + } + td.path = ::AST::Path(path); + + crate.m_tests.push_back( mv$(td) ); + } + else + { + i = AST::Item::make_None({}); + } + } +}; +class CTestHandler_SP: + public ExpandDecorator +{ + AttrStage stage() const override { return AttrStage::Pre; } + + void handle(const Span& sp, const AST::MetaItem& mi, ::AST::Crate& crate, const AST::Path& path, AST::Module& mod, AST::Item&i) const override { + if( ! i.is_Function() ) { + ERROR(sp, E0000, "#[should_panic] can only be put on functions - found on " << i.tag_str()); + } + + if( crate.m_test_harness ) + { + for(auto& td : crate.m_tests) + { + if( td.path != path ) + continue ; + + if( mi.has_sub_items() ) + { + td.panic_type = ::AST::TestDesc::ShouldPanic::YesWithMessage; + // TODO: Check that name is correct and that it is a string + td.expected_panic_message = mi.items().at(0).string(); + } + else + { + td.panic_type = ::AST::TestDesc::ShouldPanic::Yes; + } + return ; + } + //ERROR() + } + } +}; +class CTestHandler_Ignore: + public ExpandDecorator +{ + AttrStage stage() const override { return AttrStage::Pre; } + + void handle(const Span& sp, const AST::MetaItem& mi, ::AST::Crate& crate, const AST::Path& path, AST::Module& mod, AST::Item&i) const override { + if( ! i.is_Function() ) { + ERROR(sp, E0000, "#[should_panic] can only be put on functions - found on " << i.tag_str()); + } + + if( crate.m_test_harness ) + { + for(auto& td : crate.m_tests) + { + if( td.path != path ) + continue ; + + td.ignore = true; + return ; + } + //ERROR() + } } }; STATIC_DECORATOR("test", CTestHandler); +STATIC_DECORATOR("should_panic", CTestHandler_SP); +STATIC_DECORATOR("ignore", CTestHandler_Ignore); diff --git a/src/expand/test_harness.cpp b/src/expand/test_harness.cpp new file mode 100644 index 00000000..12d32121 --- /dev/null +++ b/src/expand/test_harness.cpp @@ -0,0 +1,120 @@ +/* + * MRustC - Rust Compiler + * - By John Hodge (Mutabah/thePowersGang) + * + * expand/mod.cpp + * - Expand pass core code + */ +#include <ast/ast.hpp> +#include <ast/expr.hpp> +#include <ast/crate.hpp> +#include <main_bindings.hpp> +#include <hir/hir.hpp> // ABI_RUST + +#define NEWNODE(_ty, ...) ::AST::ExprNodeP(new ::AST::ExprNode##_ty(__VA_ARGS__)) + +void Expand_TestHarness(::AST::Crate& crate) +{ + // Create the following module: + // ``` + // mod `#test` { + // extern crate std; + // extern crate test; + // fn main() { + // self::test::test_main_static(&::`#test`::TESTS); + // } + // static TESTS: [test::TestDescAndFn; _] = [ + // test::TestDescAndFn { desc: test::TestDesc { name: "foo", ignore: false, should_panic: test::ShouldPanic::No }, testfn: ::path::to::foo }, + // ]; + // } + // ``` + + // ---- main function ---- + auto main_fn = ::AST::Function { Span(), {}, ABI_RUST, false, false, false, TypeRef(TypeRef::TagUnit(), Span()), {} }; + { + auto call_node = NEWNODE(_CallPath, + ::AST::Path("test", { ::AST::PathNode("test_main_static") }), + ::make_vec1( + NEWNODE(_UniOp, ::AST::ExprNode_UniOp::REF, + NEWNODE(_NamedValue, ::AST::Path("", { ::AST::PathNode("test#"), ::AST::PathNode("TESTS") })) + ) + ) + ); + main_fn.set_code( mv$(call_node) ); + } + + + // ---- test list ---- + ::std::vector< ::AST::ExprNodeP> test_nodes; + + for(const auto& test : crate.m_tests) + { + // HACK: Don't emit should_panic tests + if( test.panic_type != ::AST::TestDesc::ShouldPanic::No ) + continue ; + + ::AST::ExprNode_StructLiteral::t_values desc_vals; + // `name: "foo",` + desc_vals.push_back( ::std::make_pair("name", NEWNODE(_CallPath, + ::AST::Path("test", { ::AST::PathNode("StaticTestName") }), + ::make_vec1( NEWNODE(_String, test.name) ) + ) )); + // `ignore: false,` + desc_vals.push_back( ::std::make_pair("ignore", NEWNODE(_Bool, test.ignore)) ); + // `should_panic: ShouldPanic::No,` + { + ::AST::ExprNodeP should_panic_val; + switch(test.panic_type) + { + case ::AST::TestDesc::ShouldPanic::No: + should_panic_val = NEWNODE(_NamedValue, ::AST::Path("test", { ::AST::PathNode("ShouldPanic"), ::AST::PathNode("No") })); + break; + case ::AST::TestDesc::ShouldPanic::Yes: + should_panic_val = NEWNODE(_NamedValue, ::AST::Path("test", { ::AST::PathNode("ShouldPanic"), ::AST::PathNode("Yes") })); + break; + case ::AST::TestDesc::ShouldPanic::YesWithMessage: + should_panic_val = NEWNODE(_CallPath, + ::AST::Path("test", { ::AST::PathNode("ShouldPanic"), ::AST::PathNode("YesWithMessage") }), + make_vec1( NEWNODE(_String, test.expected_panic_message) ) + ); + break; + } + desc_vals.push_back( ::std::make_pair("should_panic", mv$(should_panic_val)) ); + } + auto desc_expr = NEWNODE(_StructLiteral, ::AST::Path("test", { ::AST::PathNode("TestDesc")}), nullptr, mv$(desc_vals)); + + ::AST::ExprNode_StructLiteral::t_values descandfn_vals; + descandfn_vals.push_back( ::std::make_pair(::std::string("desc"), mv$(desc_expr)) ); + + auto test_type_var_name = test.is_benchmark ? "StaticBenchFn" : "StaticTestFn"; + descandfn_vals.push_back( ::std::make_pair(::std::string("testfn"), NEWNODE(_CallPath, + ::AST::Path("test", { ::AST::PathNode(test_type_var_name) }), + ::make_vec1( NEWNODE(_NamedValue, AST::Path(test.path)) ) + ) ) ); + + test_nodes.push_back( NEWNODE(_StructLiteral, ::AST::Path("test", { ::AST::PathNode("TestDescAndFn")}), nullptr, mv$(descandfn_vals) ) ); + } + auto* tests_array = new ::AST::ExprNode_Array(mv$(test_nodes)); + + size_t test_count = tests_array->m_values.size(); + auto tests_list = ::AST::Static { ::AST::Static::Class::STATIC, + TypeRef(TypeRef::TagSizedArray(), Span(), + TypeRef(Span(), ::AST::Path("test", { ::AST::PathNode("TestDescAndFn") })), + ::std::shared_ptr<::AST::ExprNode>( new ::AST::ExprNode_Integer(test_count, CORETYPE_UINT) ) + ), + ::AST::Expr( mv$(tests_array) ) + }; + + // ---- module ---- + auto newmod = ::AST::Module { ::AST::Path("", { ::AST::PathNode("test#") }) }; + // - TODO: These need to be loaded too. + // > They don't actually need to exist here, just be loaded (and use absolute paths) + newmod.add_ext_crate(false, "std", "std", {}); + newmod.add_ext_crate(false, "test", "test", {}); + + newmod.add_item(false, "main", mv$(main_fn), {}); + newmod.add_item(false, "TESTS", mv$(tests_list), {}); + + crate.m_root_module.add_item(false, "test#", mv$(newmod), {}); + crate.m_lang_items["mrustc-main"] = ::AST::Path("", { AST::PathNode("test#"), AST::PathNode("main") }); +} diff --git a/src/hir/deserialise.cpp b/src/hir/deserialise.cpp index 4a9b5720..19306a63 100644 --- a/src/hir/deserialise.cpp +++ b/src/hir/deserialise.cpp @@ -1008,6 +1008,11 @@ namespace { m_in.read_bool(), static_cast<unsigned int>(m_in.read_count()) }); + case 4: + return ::MIR::Statement::make_ScopeEnd({ + deserialise_vec<unsigned int>(), + deserialise_vec<unsigned int>() + }); default: ::std::cerr << "Bad tag for a MIR Statement" << ::std::endl; throw ""; diff --git a/src/hir/serialise.cpp b/src/hir/serialise.cpp index 81319c2e..e5264ae2 100644 --- a/src/hir/serialise.cpp +++ b/src/hir/serialise.cpp @@ -495,6 +495,11 @@ namespace { m_out.write_count(e.idx); m_out.write_bool(e.new_val); m_out.write_count(e.other); + ), + (ScopeEnd, + m_out.write_tag(4); + serialise_vec(e.vars); + serialise_vec(e.tmps); ) ) } diff --git a/src/hir/type.cpp b/src/hir/type.cpp index 9b0bf766..7168da53 100644 --- a/src/hir/type.cpp +++ b/src/hir/type.cpp @@ -606,7 +606,26 @@ bool ::HIR::TypeRef::match_test_generics(const Span& sp, const ::HIR::TypeRef& x return Compare::Unequal; } TU_MATCH(::HIR::TypeRef::Data, (v.m_data, x.m_data), (te, xe), - (Infer, throw "";), + (Infer, + // Both sides are infer + switch(te.ty_class) + { + case ::HIR::InferClass::None: + case ::HIR::InferClass::Diverge: + return Compare::Fuzzy; + default: + switch(xe.ty_class) + { + case ::HIR::InferClass::None: + case ::HIR::InferClass::Diverge: + return Compare::Fuzzy; + default: + if( te.ty_class != xe.ty_class ) + return Compare::Unequal; + return Compare::Fuzzy; + } + } + ), (Generic, throw "";), (Primitive, return (te == xe ? Compare::Equal : Compare::Unequal); diff --git a/src/hir_expand/ufcs_everything.cpp b/src/hir_expand/ufcs_everything.cpp index 0562c418..00e7eee1 100644 --- a/src/hir_expand/ufcs_everything.cpp +++ b/src/hir_expand/ufcs_everything.cpp @@ -615,6 +615,7 @@ namespace { m_replacement = NEWNODE( mv$(node.m_res_type), Deref, sp, mv$(m_replacement) ); } +#if 0 void visit(::HIR::ExprNode_Deref& node) override { const auto& sp = node.span(); @@ -693,6 +694,7 @@ namespace { // - Dereference the result (which is an &-ptr) m_replacement = NEWNODE( mv$(node.m_res_type), Deref, sp, mv$(m_replacement) ); } +#endif diff --git a/src/hir_typeck/static.cpp b/src/hir_typeck/static.cpp index 5aa02a9a..f3b483c9 100644 --- a/src/hir_typeck/static.cpp +++ b/src/hir_typeck/static.cpp @@ -588,7 +588,7 @@ bool StaticTraitResolve::find_impl__check_crate_raw( //auto cmp = have .match_test_generics_fuzz(sp, exp, cb_ident, cb_match); auto cmp = exp .match_test_generics_fuzz(sp, have, cb_ident, cb_match); - ASSERT_BUG(sp, cmp == ::HIR::Compare::Equal, "Assoc ty " << aty_name << " mismatch, " << have << " != des " << exp); + ASSERT_BUG(sp, cmp != ::HIR::Compare::Unequal, "Assoc ty " << aty_name << " mismatch, " << have << " != des " << exp); return true; }); } @@ -1378,6 +1378,151 @@ bool StaticTraitResolve::type_is_sized(const Span& sp, const ::HIR::TypeRef& ty) throw ""; } +bool StaticTraitResolve::type_needs_drop_glue(const Span& sp, const ::HIR::TypeRef& ty) const +{ + // If `T: Copy`, then it can't need drop glue + if( type_is_copy(sp, ty) ) + return false; + + TU_MATCH(::HIR::TypeRef::Data, (ty.m_data), (e), + (Generic, + return true; + ), + (Path, + if( e.binding.is_Opaque() ) + return true; + + auto pp = ::HIR::PathParams(); + bool has_direct_drop = this->find_impl(sp, m_lang_Drop, &pp, ty, [&](auto , bool){ return true; }, true); + if( has_direct_drop ) + return true; + + ::HIR::TypeRef tmp_ty; + const auto& pe = e.path.m_data.as_Generic(); + auto monomorph_cb = monomorphise_type_get_cb(sp, nullptr, &pe.m_params, nullptr, nullptr); + auto monomorph = [&](const auto& tpl)->const ::HIR::TypeRef& { + if( monomorphise_type_needed(tpl) ) { + tmp_ty = monomorphise_type_with(sp, tpl, monomorph_cb, false); + this->expand_associated_types(sp, tmp_ty); + return tmp_ty; + } + else { + return tpl; + } + }; + TU_MATCHA( (e.binding), (pbe), + (Unbound, + BUG(sp, "Unbound path"); + ), + (Opaque, + // Technically a bug, checked above + return true; + ), + (Struct, + TU_MATCHA( (pbe->m_data), (se), + (Unit, + ), + (Tuple, + for(const auto& e : se) + { + if( type_needs_drop_glue(sp, monomorph(e.ent)) ) + return true; + } + ), + (Named, + for(const auto& e : se) + { + if( type_needs_drop_glue(sp, monomorph(e.second.ent)) ) + return true; + } + ) + ) + return false; + ), + (Enum, + for(const auto& e : pbe->m_variants) + { + TU_MATCHA( (e.second), (ve), + (Unit, + ), + (Value, + ), + (Tuple, + for(const auto& e : ve) + { + if( type_needs_drop_glue(sp, monomorph(e.ent)) ) + return true; + } + ), + (Struct, + for(const auto& e : ve) + { + if( type_needs_drop_glue(sp, monomorph(e.second.ent)) ) + return true; + } + ) + ) + } + return false; + ), + (Union, + // Unions don't have drop glue unless they impl Drop + return false; + ) + ) + ), + (Diverge, + return false; + ), + (Closure, + // TODO: Destructure? + return true; + ), + (Infer, + BUG(sp, "type_needs_drop_glue on _"); + return false; + ), + (Borrow, + // &-ptrs don't have drop glue + if( e.type != ::HIR::BorrowType::Owned ) + return false; + return type_needs_drop_glue(sp, *e.inner); + ), + (Pointer, + return false; + ), + (Function, + return false; + ), + (Primitive, + return false; + ), + (Array, + return type_needs_drop_glue(sp, *e.inner); + ), + (Slice, + return type_needs_drop_glue(sp, *e.inner); + ), + (TraitObject, + return true; + ), + (ErasedType, + // Is this an error? + return true; + ), + (Tuple, + for(const auto& ty : e) + { + if( !type_needs_drop_glue(sp, ty) ) + return true; + } + return false; + ) + ) + assert(!"Fell off the end of type_needs_drop_glue"); + throw ""; +} + const ::HIR::TypeRef* StaticTraitResolve::is_type_owned_box(const ::HIR::TypeRef& ty) const { if( ! ty.m_data.is_Path() ) { diff --git a/src/hir_typeck/static.hpp b/src/hir_typeck/static.hpp index cde9797c..16302218 100644 --- a/src/hir_typeck/static.hpp +++ b/src/hir_typeck/static.hpp @@ -178,6 +178,9 @@ public: bool type_is_copy(const Span& sp, const ::HIR::TypeRef& ty) const; bool type_is_sized(const Span& sp, const ::HIR::TypeRef& ty) const; + /// Returns `true` if the passed type either implements Drop, or contains a type that implements Drop + bool type_needs_drop_glue(const Span& sp, const ::HIR::TypeRef& ty) const; + const ::HIR::TypeRef* is_type_owned_box(const ::HIR::TypeRef& ty) const; const ::HIR::TypeRef* is_type_phantom_data(const ::HIR::TypeRef& ty) const; diff --git a/src/include/main_bindings.hpp b/src/include/main_bindings.hpp index c01ff86d..184f266f 100644 --- a/src/include/main_bindings.hpp +++ b/src/include/main_bindings.hpp @@ -16,6 +16,7 @@ extern AST::Crate Parse_Crate(::std::string mainfile); extern void Expand(::AST::Crate& crate); +extern void Expand_TestHarness(::AST::Crate& crate); /// Process #[] decorators extern void Process_Decorators(AST::Crate& crate); diff --git a/src/main.cpp b/src/main.cpp index c534ff15..f06ee545 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,6 +68,7 @@ void init_debug_list() g_debug_disable_map.insert( "MIR Cleanup" ); g_debug_disable_map.insert( "MIR Optimise" ); g_debug_disable_map.insert( "MIR Validate PO" ); + g_debug_disable_map.insert( "MIR Validate Full" ); g_debug_disable_map.insert( "HIR Serialise" ); g_debug_disable_map.insert( "Trans Enumerate" ); @@ -134,6 +135,8 @@ struct ProgramParams unsigned opt_level = 0; bool emit_debug_info = false; + bool test_harness = false; + ::std::vector<const char*> lib_search_dirs; ::std::vector<const char*> libraries; @@ -170,6 +173,7 @@ int main(int argc, char *argv[]) ProgramParams params(argc, argv); // Set up cfg values + Cfg_SetValue("rust_compiler", "mrustc"); // TODO: Target spec Cfg_SetFlag("unix"); Cfg_SetFlag("linux"); @@ -192,6 +196,11 @@ int main(int argc, char *argv[]) }); + if( params.test_harness ) + { + Cfg_SetFlag("test"); + } + try { @@ -199,6 +208,7 @@ int main(int argc, char *argv[]) AST::Crate crate = CompilePhase<AST::Crate>("Parse", [&]() { return Parse_Crate(params.infile); }); + crate.m_test_harness = params.test_harness; if( params.last_stage == ProgramParams::STAGE_PARSE ) { return 0; @@ -214,6 +224,11 @@ int main(int argc, char *argv[]) Expand(crate); }); + if( params.test_harness ) + { + Expand_TestHarness(crate); + } + // Extract the crate type and name from the crate attributes auto crate_type = params.crate_type; if( crate_type == ::AST::Crate::Type::Unknown ) { @@ -259,6 +274,10 @@ int main(int argc, char *argv[]) } } crate.m_crate_name = crate_name; + if( params.test_harness ) + { + crate.m_crate_name += "$test"; + } if( params.outfile == "" ) { switch( crate.m_crate_type ) @@ -286,7 +305,7 @@ int main(int argc, char *argv[]) } // Allocator and panic strategies - if( crate.m_crate_type == ::AST::Crate::Type::Executable ) + if( crate.m_crate_type == ::AST::Crate::Type::Executable || params.test_harness ) { // TODO: Detect if an allocator crate is already present. crate.load_extern_crate(Span(), "alloc_system"); @@ -426,6 +445,13 @@ int main(int argc, char *argv[]) CompilePhaseV("MIR Cleanup", [&]() { MIR_CleanupCrate(*hir_crate); }); + if( getenv("MRUSTC_FULL_VALIDATE_PREOPT") ) + { + CompilePhaseV("MIR Validate Full", [&]() { + MIR_CheckCrate_Full(*hir_crate); + }); + } + // Optimise the MIR CompilePhaseV("MIR Optimise", [&]() { MIR_OptimiseCrate(*hir_crate); @@ -438,8 +464,11 @@ int main(int argc, char *argv[]) CompilePhaseV("MIR Validate PO", [&]() { MIR_CheckCrate(*hir_crate); }); + // - Exhaustive MIR validation (follows every code path and checks variable validity) + // > DEBUGGING ONLY CompilePhaseV("MIR Validate Full", [&]() { - //MIR_CheckCrate_Full(*hir_crate); + if( getenv("MRUSTC_FULL_VALIDATE") ) + MIR_CheckCrate_Full(*hir_crate); }); if( params.last_stage == ProgramParams::STAGE_MIR ) { @@ -460,6 +489,11 @@ int main(int argc, char *argv[]) trans_opt.emit_debug_info = params.emit_debug_info; // Generate code for non-generic public items (if requested) + if( params.test_harness ) + { + // If the test harness is enabled, override crate type to "Executable" + crate_type = ::AST::Crate::Type::Executable; + } switch( crate_type ) { case ::AST::Crate::Type::Unknown: @@ -684,6 +718,9 @@ ProgramParams::ProgramParams(int argc, char *argv[]) exit(1); } } + else if( strcmp(arg, "--test") == 0 ) { + this->test_harness = true; + } else { ::std::cerr << "Unknown option '" << arg << "'" << ::std::endl; exit(1); @@ -692,3 +729,54 @@ ProgramParams::ProgramParams(int argc, char *argv[]) } } + +::std::ostream& operator<<(::std::ostream& os, const FmtEscaped& x) +{ + os << ::std::hex; + for(auto s = x.s; *s != '\0'; s ++) + { + switch(*s) + { + case '\0': os << "\\0"; break; + case '\n': os << "\\n"; break; + case '\\': os << "\\\\"; break; + case '"': os << "\\\""; break; + default: + uint8_t v = *s; + if( v < 0x80 ) + { + if( v < ' ' || v > 0x7F ) + os << "\\u{" << ::std::hex << (unsigned int)v << "}"; + else + os << v; + } + else if( v < 0xC0 ) + ; + else if( v < 0xE0 ) + { + uint32_t val = (uint32_t)(v & 0x1F) << 6; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 6; + os << "\\u{" << ::std::hex << val << "}"; + } + else if( v < 0xF0 ) + { + uint32_t val = (uint32_t)(v & 0x0F) << 12; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 12; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 6; + os << "\\u{" << ::std::hex << val << "}"; + } + else if( v < 0xF8 ) + { + uint32_t val = (uint32_t)(v & 0x07) << 18; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 18; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 12; + v = (uint8_t)*++s; if( (v & 0xC0) != 0x80 ) { s--; continue ; } val |= (uint32_t)v << 6; + os << "\\u{" << ::std::hex << val << "}"; + } + break; + } + } + os << ::std::dec; + return os; +} + diff --git a/src/mir/check.cpp b/src/mir/check.cpp index 7c8fbd13..8e933508 100644 --- a/src/mir/check.cpp +++ b/src/mir/check.cpp @@ -457,6 +457,12 @@ void MIR_Validate_ValState(::MIR::TypeResolve& state, const ::MIR::Function& fcn // Mark destination as valid val_state.mark_validity( state, stmt.as_Assign().dst, true ); break; + case ::MIR::Statement::TAG_ScopeEnd: + //for(auto idx : stmt.as_ScopeEnd().vars) + // val_state.mark_validity(state, ::MIR::LValue::make_Variable(idx), false); + //for(auto idx : stmt.as_ScopeEnd().tmps) + // val_state.mark_validity(state, ::MIR::LValue::make_Temporary({idx}), false); + break; } } @@ -852,6 +858,9 @@ void MIR_Validate(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path case ::MIR::Statement::TAG_Drop: // TODO: Anything need checking here? break; + case ::MIR::Statement::TAG_ScopeEnd: + // TODO: Mark listed values as descoped + break; } } diff --git a/src/mir/check_full.cpp b/src/mir/check_full.cpp index 8dcd359c..1f527ff2 100644 --- a/src/mir/check_full.cpp +++ b/src/mir/check_full.cpp @@ -12,7 +12,8 @@ #include <mir/helpers.hpp> #include <mir/visit_crate_mir.hpp> -#define ENABLE_LEAK_DETECTOR 1 +// DISABLED: Unsizing intentionally leaks +#define ENABLE_LEAK_DETECTOR 0 namespace { @@ -151,6 +152,134 @@ namespace ensure_valid(mir_res, lv, vs, path); } private: + struct InvalidReason { + enum { + Unwritten, + Moved, + Invalidated, + } ty; + size_t bb; + size_t stmt; + + void fmt(::std::ostream& os) const { + switch(this->ty) + { + case Unwritten: os << "Not Written"; break; + case Moved: os << "Moved at BB" << bb << "/" << stmt; break; + case Invalidated: os << "Invalidated at BB" << bb << "/" << stmt; break; + } + } + }; + InvalidReason find_invalid_reason(const ::MIR::TypeResolve& mir_res, const ::MIR::LValue& root_lv) const + { + using ::MIR::visit::ValUsage; + using ::MIR::visit::visit_mir_lvalues; + + ::HIR::TypeRef tmp; + bool is_copy = mir_res.m_resolve.type_is_copy( mir_res.sp, mir_res.get_lvalue_type(tmp, root_lv) ); + + size_t cur_stmt = mir_res.get_cur_stmt_ofs(); + if( !is_copy ) + { + // Walk backwards through the BBs and find where it's used by value + assert(this->bb_path.size() > 0); + size_t bb_idx; + size_t stmt_idx; + + bool was_moved = false; + size_t moved_bb, moved_stmt; + auto visit_cb = [&](const auto& lv, auto vu) { + if(lv == root_lv && vu == ValUsage::Move) { + was_moved = true; + moved_bb = bb_idx; + moved_stmt = stmt_idx; + return false; + } + return false; + }; + // Most recent block (incomplete) + { + bb_idx = this->bb_path.back(); + const auto& bb = mir_res.m_fcn.blocks.at(bb_idx); + for(stmt_idx = cur_stmt; stmt_idx -- && !was_moved; ) + { + visit_mir_lvalues(bb.statements[stmt_idx], visit_cb); + } + } + for(size_t i = this->bb_path.size()-1; i -- && !was_moved; ) + { + bb_idx = this->bb_path[i]; + const auto& bb = mir_res.m_fcn.blocks.at(bb_idx); + stmt_idx = bb.statements.size(); + + visit_mir_lvalues(bb.terminator, visit_cb); + + for(stmt_idx = bb.statements.size(); stmt_idx -- && !was_moved; ) + { + visit_mir_lvalues(bb.statements[stmt_idx], visit_cb); + } + } + + if( was_moved ) + { + // Reason found, the value was moved + DEBUG("- Moved in BB" << moved_bb << "/" << moved_stmt); + return InvalidReason { InvalidReason::Moved, moved_bb, moved_stmt }; + } + } + else + { + // Walk backwards to find assignment (if none, it's never initialized) + assert(this->bb_path.size() > 0); + size_t bb_idx; + size_t stmt_idx; + + bool assigned = false; + auto visit_cb = [&](const auto& lv, auto vu) { + if(lv == root_lv && vu == ValUsage::Write) { + assigned = true; + //assigned_bb = this->bb_path[i]; + //assigned_stmt = j; + return true; + } + return false; + }; + + // Most recent block (incomplete) + { + bb_idx = this->bb_path.back(); + const auto& bb = mir_res.m_fcn.blocks.at(bb_idx); + for(stmt_idx = cur_stmt; stmt_idx -- && !assigned; ) + { + visit_mir_lvalues(bb.statements[stmt_idx], visit_cb); + } + } + for(size_t i = this->bb_path.size()-1; i -- && !assigned; ) + { + bb_idx = this->bb_path[i]; + const auto& bb = mir_res.m_fcn.blocks.at(bb_idx); + stmt_idx = bb.statements.size(); + + visit_mir_lvalues(bb.terminator, visit_cb); + + for(stmt_idx = bb.statements.size(); stmt_idx -- && !assigned; ) + { + visit_mir_lvalues(bb.statements[stmt_idx], visit_cb); + } + } + + if( !assigned ) + { + // Value wasn't ever assigned, that's why it's not valid. + DEBUG("- Not assigned"); + return InvalidReason { InvalidReason::Unwritten, 0, 0 }; + } + } + // If neither of the above return a reason, check for blocks that don't have the value valid. + // TODO: This requires access to the lifetime bitmaps to know where it was invalidated + DEBUG("- (assume) lifetime invalidated [is_copy=" << is_copy << "]"); + return InvalidReason { InvalidReason::Invalidated, 0, 0 }; + } void ensure_valid(const ::MIR::TypeResolve& mir_res, const ::MIR::LValue& root_lv, const State& vs, ::std::vector<unsigned int>& path) const { if( vs.is_composite() ) @@ -168,7 +297,9 @@ namespace } else if( !vs.is_valid() ) { - MIR_BUG(mir_res, "Accessing invalidated lvalue - " << root_lv << " - field path=[" << path << "], BBs=[" << this->bb_path << "]"); + // Locate where it was invalidated. + auto reason = find_invalid_reason(mir_res, root_lv); + MIR_BUG(mir_res, "Accessing invalidated lvalue - " << root_lv << " - " << FMT_CB(s,reason.fmt(s);) << " - field path=[" << path << "], BBs=[" << this->bb_path << "]"); } else { @@ -509,6 +640,11 @@ void MIR_Validate_FullValState(::MIR::TypeResolve& mir_res, const ::MIR::Functio { ::std::vector<StateSet> block_entry_states( fcn.blocks.size() ); + // Determine value lifetimes (BBs in which Copy values are valid) + // - Used to mask out Copy value (prevents combinatorial explosion) + auto lifetimes = MIR_Helper_GetLifetimes(mir_res, fcn, /*dump_debug=*/true); + DEBUG(lifetimes.m_block_offsets); + ValueStates state; state.arguments.resize( mir_res.m_args.size(), State(true) ); state.vars.resize( fcn.named_variables.size() ); @@ -523,8 +659,56 @@ void MIR_Validate_FullValState(::MIR::TypeResolve& mir_res, const ::MIR::Functio auto state = mv$(todo_queue.back().second); todo_queue.pop_back(); + // Mask off any values which aren't valid in the first statement of this block + { + for(unsigned i = 0; i < state.vars.size(); i ++) + { + /*if( !variables_copy[i] ) + { + // Not Copy, don't apply masking + } + else*/ if( ! state.vars[i].is_valid() ) + { + // Already invalid + } + else if( lifetimes.var_valid(i, cur_block, 0) ) + { + // Expected to be valid in this block, leave as-is + } + else + { + // Copy value not used at/after this block, mask to false + DEBUG("BB" << cur_block << " - var$" << i << " - Outside lifetime, discard"); + state.vars[i] = State(false); + } + } + for(unsigned i = 0; i < state.temporaries.size(); i ++) + { + /*if( !variables_copy[i] ) + { + // Not Copy, don't apply masking + } + else*/ if( ! state.temporaries[i].is_valid() ) + { + // Already invalid + } + else if( lifetimes.tmp_valid(i, cur_block, 0) ) + { + // Expected to be valid in this block, leave as-is + } + else + { + // Copy value not used at/after this block, mask to false + DEBUG("BB" << cur_block << " - tmp$" << i << " - Outside lifetime, discard"); + state.temporaries[i] = State(false); + } + } + } + + // If this state already exists in the map, skip if( ! block_entry_states[cur_block].add_state(state) ) { + DEBUG("BB" << cur_block << " - Nothing new"); continue ; } DEBUG("BB" << cur_block << " - " << state); @@ -660,6 +844,9 @@ void MIR_Validate_FullValState(::MIR::TypeResolve& mir_res, const ::MIR::Functio state.move_lvalue(mir_res, se.slot); } } + ), + (ScopeEnd, + // TODO: Mark all mentioned variables as invalid ) ) } diff --git a/src/mir/cleanup.cpp b/src/mir/cleanup.cpp index 6516dd60..17dca948 100644 --- a/src/mir/cleanup.cpp +++ b/src/mir/cleanup.cpp @@ -932,6 +932,8 @@ void MIR_Cleanup(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path, ), (SetDropFlag, ), + (ScopeEnd, + ), (Asm, for(auto& v : se.inputs) MIR_Cleanup_LValue(state, mutator, v.second); diff --git a/src/mir/dump.cpp b/src/mir/dump.cpp index eb74dc9e..0187e8b2 100644 --- a/src/mir/dump.cpp +++ b/src/mir/dump.cpp @@ -99,6 +99,14 @@ namespace { m_os << " IF df$" << e.flag_idx; } m_os << ");\n"; + ), + (ScopeEnd, + m_os << "// Scope End: "; + for(auto idx : e.vars) + m_os << "var$" << idx << ","; + for(auto idx : e.tmps) + m_os << "tmp$" << idx << ","; + m_os << "\n"; ) ) } diff --git a/src/mir/from_hir.cpp b/src/mir/from_hir.cpp index df48627f..04291799 100644 --- a/src/mir/from_hir.cpp +++ b/src/mir/from_hir.cpp @@ -58,6 +58,7 @@ namespace { const ScopeHandle* m_block_tmp_scope = nullptr; const ScopeHandle* m_borrow_raise_target = nullptr; + bool m_in_borrow = false; public: ExprVisitor_Conv(MirBuilder& builder, const ::std::vector< ::HIR::TypeRef>& var_types): @@ -749,7 +750,7 @@ namespace { auto scope = m_builder.new_scope_temp( cond->span() ); this->visit_node_ptr(*cond_p); ASSERT_BUG(cond->span(), cond->m_res_type == ::HIR::CoreType::Bool, "If condition wasn't a bool"); - decision_val = m_builder.get_result_in_lvalue(cond->span(), ::HIR::CoreType::Bool); + decision_val = m_builder.get_result_in_if_cond(cond->span()); m_builder.terminate_scope(cond->span(), mv$(scope)); } @@ -1141,6 +1142,8 @@ namespace { { TRACE_FUNCTION_F("_Borrow"); + auto _ = save_and_edit(m_in_borrow, true); + const auto& ty_val = node.m_value->m_res_type; this->visit_node_ptr(node.m_value); auto val = m_builder.get_result_in_lvalue(node.m_value->span(), ty_val); @@ -1421,10 +1424,63 @@ namespace { if( m_builder.is_type_owned_box( ty_val ) ) { // Box magically derefs. - // HACK: Break out of the switch used for TU_MATCH_DEF - break; } - BUG(sp, "Deref on unsupported type - " << ty_val); + else + { + // TODO: Do operator replacement here after handling scope-raising for _Borrow + if( m_borrow_raise_target && m_in_borrow ) + { + DEBUG("- Raising deref in borrow to scope " << *m_borrow_raise_target); + m_builder.raise_variables(node.span(), val, *m_borrow_raise_target); + } + + + const char* langitem = nullptr; + const char* method = nullptr; + ::HIR::BorrowType bt; + // - Uses the value's usage beacuse for T: Copy node.m_value->m_usage is Borrow, but node.m_usage is Move + switch( node.m_value->m_usage ) + { + case ::HIR::ValueUsage::Unknown: + BUG(sp, "Unknown usage type of deref value"); + break; + case ::HIR::ValueUsage::Borrow: + bt = ::HIR::BorrowType::Shared; + langitem = method = "deref"; + break; + case ::HIR::ValueUsage::Mutate: + bt = ::HIR::BorrowType::Unique; + langitem = method = "deref_mut"; + break; + case ::HIR::ValueUsage::Move: + TODO(sp, "ValueUsage::Move for desugared Deref of " << node.m_value->m_res_type); + break; + } + // Needs replacement, continue + assert(langitem); + assert(method); + + // - Construct trait path - Index*<IdxTy> + auto method_path = ::HIR::Path(ty_val.clone(), ::HIR::GenericPath(m_builder.resolve().m_crate.get_lang_item_path(node.span(), langitem), {}), method); + + // Store a borrow of the input value + ::std::vector<::MIR::Param> args; + args.push_back( m_builder.lvalue_or_temp(sp, + ::HIR::TypeRef::new_borrow(bt, node.m_value->m_res_type.clone()), + ::MIR::RValue::make_Borrow({0, bt, mv$(val)}) + ) ); + m_builder.moved_lvalue(node.span(), args[0].as_LValue()); + val = m_builder.new_temporary(::HIR::TypeRef::new_borrow(bt, node.m_res_type.clone())); + // Call the above trait method + // Store result of that call in `val` (which will be derefed below) + auto ok_block = m_builder.new_bb_unlinked(); + auto panic_block = m_builder.new_bb_unlinked(); + m_builder.end_block(::MIR::Terminator::make_Call({ ok_block, panic_block, val.clone(), mv$(method_path), mv$(args) })); + m_builder.set_cur_block(panic_block); + m_builder.end_block(::MIR::Terminator::make_Diverge({})); + + m_builder.set_cur_block(ok_block); + } ), (Pointer, // Deref on a pointer - TODO: Requires unsafe diff --git a/src/mir/from_hir.hpp b/src/mir/from_hir.hpp index 2a5da9fe..476aecda 100644 --- a/src/mir/from_hir.hpp +++ b/src/mir/from_hir.hpp @@ -53,10 +53,13 @@ enum class InvalidType { TAGGED_UNION_EX(VarState, (), Invalid, ( // Currently invalid (Invalid, InvalidType), - // Partially valid (Map of field states, Box is assumed to have one field) + // Partially valid (Map of field states) (Partial, struct { ::std::vector<VarState> inner_states; - unsigned int outer_flag = ~0u; // If ~0u there's no condition on the outer + }), + (MovedOut, struct { + ::std::unique_ptr<VarState> inner_state; + unsigned int outer_flag; }), // Optionally valid (integer indicates the drop flag index) (Optional, unsigned int), @@ -104,6 +107,14 @@ TAGGED_UNION(ScopeType, Variables, }) ); +enum class VarGroup +{ + Return, + Argument, + Variable, + Temporary, +}; + /// Helper class to construct MIR class MirBuilder { @@ -123,7 +134,8 @@ class MirBuilder bool m_result_valid; // TODO: Extra information. - //::std::vector<VarState> m_arg_states; + VarState m_return_state; + ::std::vector<VarState> m_arg_states; ::std::vector<VarState> m_variable_states; ::std::vector<VarState> m_temporary_states; @@ -147,6 +159,11 @@ class MirBuilder ::std::vector<ScopeDef> m_scopes; ::std::vector<unsigned int> m_scope_stack; ScopeHandle m_fcn_scope; + + // LValue used only for the condition of `if` + // - Using a fixed temporary simplifies parts of lowering (scope related) and reduces load on + // the optimiser. + ::MIR::LValue m_if_cond_lval; public: MirBuilder(const Span& sp, const StaticTraitResolve& resolve, const ::HIR::Function::args_t& args, ::MIR::Function& output); ~MirBuilder(); @@ -174,6 +191,17 @@ public: /// Obtains a result in a param (or a lvalue) ::MIR::Param get_result_in_param(const Span& sp, const ::HIR::TypeRef& ty, bool allow_missing_value=false); + ::MIR::LValue get_if_cond() const { + return m_if_cond_lval.clone(); + } + ::MIR::LValue get_rval_in_if_cond(const Span& sp, ::MIR::RValue val) { + push_stmt_assign(sp, m_if_cond_lval.clone(), mv$(val)); + return m_if_cond_lval.clone(); + } + ::MIR::LValue get_result_in_if_cond(const Span& sp) { + return get_rval_in_if_cond(sp, get_result(sp)); + } + // - Statements // Push an assignment. NOTE: This also marks the rvalue as moved void push_stmt_assign(const Span& sp, ::MIR::LValue dst, ::MIR::RValue val); @@ -186,6 +214,9 @@ public: // Push a setting/clearing of a drop flag void push_stmt_set_dropflag_val(const Span& sp, unsigned int index, bool value); void push_stmt_set_dropflag_other(const Span& sp, unsigned int index, unsigned int other); + void push_stmt_set_dropflag_default(const Span& sp, unsigned int index); + + void push_stmt(const Span& sp, ::MIR::Statement stmt); // - Block management bool block_active() const { @@ -201,6 +232,7 @@ public: void set_cur_block(unsigned int new_block); ::MIR::BasicBlockId pause_cur_block(); + void end_block(::MIR::Terminator term); ::MIR::BasicBlockId new_bb_linked(); @@ -229,11 +261,17 @@ public: // Helper - Marks a variable/... as moved (and checks if the move is valid) void moved_lvalue(const Span& sp, const ::MIR::LValue& lv); private: + const VarState& get_slot_state(const Span& sp, VarGroup ty, unsigned int idx, unsigned int skip_count=0) const; + VarState& get_slot_state_mut(const Span& sp, VarGroup ty, unsigned int idx); + const VarState& get_variable_state(const Span& sp, unsigned int idx, unsigned int skip_count=0) const; VarState& get_variable_state_mut(const Span& sp, unsigned int idx); const VarState& get_temp_state(const Span& sp, unsigned int idx, unsigned int skip_count=0) const; VarState& get_temp_state_mut(const Span& sp, unsigned int idx); + const VarState& get_val_state(const Span& sp, const ::MIR::LValue& lv, unsigned int skip_count=0); + VarState& get_val_state_mut(const Span& sp, const ::MIR::LValue& lv); + void terminate_loop_early(const Span& sp, ScopeType::Data_Loop& sd_loop); void drop_value_from_state(const Span& sp, const VarState& vs, ::MIR::LValue lv); diff --git a/src/mir/from_hir_match.cpp b/src/mir/from_hir_match.cpp index b098f900..ec720d8a 100644 --- a/src/mir/from_hir_match.cpp +++ b/src/mir/from_hir_match.cpp @@ -33,8 +33,6 @@ struct field_path_t }; TAGGED_UNION_EX(PatternRule, (), Any,( - // _ pattern - (Any, struct {}), // Enum variant (Variant, struct { unsigned int idx; ::std::vector<PatternRule> sub_rules; }), // Slice (includes desired length) @@ -46,11 +44,24 @@ TAGGED_UNION_EX(PatternRule, (), Any,( (Bool, bool), // General value (Value, ::MIR::Constant), - (ValueRange, struct { ::MIR::Constant first, last; }) + (ValueRange, struct { ::MIR::Constant first, last; }), + // _ pattern + (Any, struct {}) ), ( , field_path(mv$(x.field_path)) ), (field_path = mv$(x.field_path);), ( field_path_t field_path; + + bool operator<(const PatternRule& x) const { + return this->ord(x) == OrdLess; + } + bool operator==(const PatternRule& x) const { + return this->ord(x) == OrdEqual; + } + bool operator!=(const PatternRule& x) const { + return this->ord(x) != OrdEqual; + } + Ordering ord(const PatternRule& x) const; ) ); ::std::ostream& operator<<(::std::ostream& os, const PatternRule& x); @@ -74,11 +85,14 @@ struct ArmCode { ::MIR::BasicBlockId cond_end; ::MIR::LValue cond_lval; ::std::vector< ::MIR::BasicBlockId> destructures; // NOTE: Incomplete + + mutable ::MIR::BasicBlockId cond_fail_tgt = 0; }; typedef ::std::vector<PatternRuleset> t_arm_rules; void MIR_LowerHIR_Match_Simple( MirBuilder& builder, MirConverter& conv, ::HIR::ExprNode_Match& node, ::MIR::LValue match_val, t_arm_rules arm_rules, ::std::vector<ArmCode> arm_code, ::MIR::BasicBlockId first_cmp_block); +void MIR_LowerHIR_Match_Grouped( MirBuilder& builder, MirConverter& conv, ::HIR::ExprNode_Match& node, ::MIR::LValue match_val, t_arm_rules arm_rules, ::std::vector<ArmCode> arms_code, ::MIR::BasicBlockId first_cmp_block ); void MIR_LowerHIR_Match_DecisionTree( MirBuilder& builder, MirConverter& conv, ::HIR::ExprNode_Match& node, ::MIR::LValue match_val, t_arm_rules arm_rules, ::std::vector<ArmCode> arm_code , ::MIR::BasicBlockId first_cmp_block); /// Helper to construct rules from a passed pattern struct PatternRulesetBuilder @@ -103,6 +117,82 @@ struct PatternRulesetBuilder void push_rule(PatternRule r); }; +class RulesetRef +{ + ::std::vector<PatternRuleset>* m_rules_vec = nullptr; + RulesetRef* m_parent = nullptr; + size_t m_parent_ofs=0; // If len == 0, this is the innner index, else it's the base + size_t m_parent_len=0; +public: + RulesetRef(::std::vector<PatternRuleset>& rules): + m_rules_vec(&rules) + { + } + RulesetRef(RulesetRef& parent, size_t start, size_t n): + m_parent(&parent), + m_parent_ofs(start), + m_parent_len(n) + { + } + RulesetRef(RulesetRef& parent, size_t idx): + m_parent(&parent), + m_parent_ofs(idx) + { + } + + size_t size() const { + if( m_rules_vec ) { + return m_rules_vec->size(); + } + else if( m_parent_len ) { + return m_parent_len; + } + else { + return m_parent->size(); + } + } + RulesetRef slice(size_t s, size_t n) { + return RulesetRef(*this, s, n); + } + + const ::std::vector<PatternRule>& operator[](size_t i) const { + if( m_rules_vec ) { + return (*m_rules_vec)[i].m_rules; + } + else if( m_parent_len ) { + return (*m_parent)[m_parent_ofs + i]; + } + else { + // Fun part - Indexes into inner patterns + const auto& parent_rule = (*m_parent)[i][m_parent_ofs]; + if(const auto* re = parent_rule.opt_Variant()) { + return re->sub_rules; + } + else { + throw "TODO"; + } + } + } + void swap(size_t a, size_t b) { + TRACE_FUNCTION_F(a << ", " << b); + if( m_rules_vec ) { + ::std::swap( (*m_rules_vec)[a], (*m_rules_vec)[b] ); + } + else { + assert(m_parent); + if( m_parent_len ) { + m_parent->swap(m_parent_ofs + a, m_parent_ofs + b); + } + else { + m_parent->swap(a, b); + } + } + } +}; + +void sort_rulesets(RulesetRef rulesets, size_t idx=0); +void sort_rulesets_inner(RulesetRef rulesets, size_t idx); + // -------------------------------------------------------------------- // CODE // -------------------------------------------------------------------- @@ -282,8 +372,7 @@ void MIR_LowerHIR_Match( MirBuilder& builder, MirConverter& conv, ::HIR::ExprNod auto tmp_scope = builder.new_scope_temp(arm.m_cond->span()); conv.visit_node_ptr( arm.m_cond ); - ac.cond_lval = builder.get_result_in_lvalue(arm.m_cond->span(), ::HIR::TypeRef(::HIR::CoreType::Bool)); - // NOTE: Terminating the scope slightly early is safe, because the resulting boolean temp isn't invalidated. + ac.cond_lval = builder.get_result_in_if_cond(arm.m_cond->span()); builder.terminate_scope( arm.m_code->span(), mv$(tmp_scope) ); ac.cond_end = builder.pause_cur_block(); @@ -366,20 +455,55 @@ void MIR_LowerHIR_Match( MirBuilder& builder, MirConverter& conv, ::HIR::ExprNod for(const auto& arm_rule : arm_rules) { - DEBUG("> (" << arm_rule.arm_idx << ", " << arm_rule.pat_idx << ") - " << arm_rule.m_rules); + DEBUG("> (" << arm_rule.arm_idx << ", " << arm_rule.pat_idx << ") - " << arm_rule.m_rules + << (arm_code[arm_rule.arm_idx].has_condition ? " (cond)" : "")); } - // TODO: Don't generate inner code until decisions are generated (keeps MIR flow nice) + // TODO: Remove columns that are all `_`? + // - Ideally, only accessible structures would be fully destructured like this, making this check redundant + + // Sort rules using the following restrictions: + // - A rule cannot be reordered across an item that has an overlapping match set + // > e.g. nothing can cross _ + // > equal rules cannot be reordered + // > Values cannot cross ranges that contain the value + // > This will have to be a bubble sort to ensure that it's correctly stable. + sort_rulesets(arm_rules); + DEBUG("Post-sort"); + for(const auto& arm_rule : arm_rules) + { + DEBUG("> (" << arm_rule.arm_idx << ", " << arm_rule.pat_idx << ") - " << arm_rule.m_rules + << (arm_code[arm_rule.arm_idx].has_condition ? " (cond)" : "")); + } + // De-duplicate arms (emitting a warning when it happens) + // - This allows later code to assume that duplicate arms are a codegen bug. + if( ! arm_rules.empty() ) + { + for(auto it = arm_rules.begin()+1; it != arm_rules.end(); ) + { + // If duplicate rule, (and neither is conditional) + if( (it-1)->m_rules == it->m_rules && !arm_code[it->arm_idx].has_condition && !arm_code[(it-1)->arm_idx].has_condition ) + { + // Remove + it = arm_rules.erase(it); + WARNING(node.m_arms[it->arm_idx].m_code->span(), W0000, "Duplicate match pattern, unreachable code"); + } + else + { + ++ it; + } + } + } - // TODO: Detect if a rule is ordering-dependent. In this case we currently have to fall back on the simple match code - // - A way would be to search for `_` rules with non _ rules following. Would false-positive in some cases, but shouldn't false negative - // TODO: Merge equal rulesets if there's one with no condition. + // TODO: Don't generate inner code until decisions are generated (keeps MIR flow nice) + // - Challenging, as the decision code needs somewhere to jump to. + // - Allocating a BB and then rewriting references to it is a possibility. if( fall_back_on_simple ) { MIR_LowerHIR_Match_Simple( builder, conv, node, mv$(match_val), mv$(arm_rules), mv$(arm_code), first_cmp_block ); } else { - MIR_LowerHIR_Match_DecisionTree( builder, conv, node, mv$(match_val), mv$(arm_rules), mv$(arm_code), first_cmp_block ); + MIR_LowerHIR_Match_Grouped( builder, conv, node, mv$(match_val), mv$(arm_rules), mv$(arm_code), first_cmp_block ); } builder.set_cur_block( next_block ); @@ -424,6 +548,56 @@ void MIR_LowerHIR_Match( MirBuilder& builder, MirConverter& conv, ::HIR::ExprNod return os; } +::Ordering PatternRule::ord(const PatternRule& x) const +{ + if(tag() != x.tag()) + { + return tag() < x.tag() ? ::OrdLess : ::OrdGreater; + } + TU_MATCHA( (*this, x), (te, xe), + (Any, return OrdEqual;), + (Variant, + if(te.idx != xe.idx) return ::ord(te.idx, xe.idx); + assert( te.sub_rules.size() == xe.sub_rules.size() ); + for(unsigned int i = 0; i < te.sub_rules.size(); i ++) + { + auto cmp = te.sub_rules[i].ord( xe.sub_rules[i] ); + if( cmp != ::OrdEqual ) + return cmp; + } + return ::OrdEqual; + ), + (Slice, + if(te.len != xe.len) return ::ord(te.len, xe.len); + // Wait? Why would the rule count be the same? + assert( te.sub_rules.size() == xe.sub_rules.size() ); + for(unsigned int i = 0; i < te.sub_rules.size(); i ++) + { + auto cmp = te.sub_rules[i].ord( xe.sub_rules[i] ); + if( cmp != ::OrdEqual ) + return cmp; + } + return ::OrdEqual; + ), + (SplitSlice, + auto rv = ::ord( te.leading, xe.leading ); + if(rv != OrdEqual) return rv; + return ::ord(te.trailing, xe.trailing); + ), + (Bool, + return ::ord( te, xe ); + ), + (Value, + return ::ord( te, xe ); + ), + (ValueRange, + if( te.first != xe.first ) + return ::ord(te.first, xe.first); + return ::ord(te.last, xe.last); + ) + ) + throw ""; +} ::Ordering PatternRuleset::rule_is_before(const PatternRule& l, const PatternRule& r) { if( l.tag() != r.tag() ) { @@ -1212,6 +1386,7 @@ void PatternRulesetBuilder::append_from(const Span& sp, const ::HIR::Pattern& pa sub_builder.m_field_path.back() = 0; if( pe.trailing.size() ) { + // Needs a way of encoding the negative offset in the field path TODO(sp, "SplitSlice on [T] with trailing - " << pat); } auto trailing = mv$(sub_builder.m_rules); @@ -1281,6 +1456,227 @@ void PatternRulesetBuilder::append_from(const Span& sp, const ::HIR::Pattern& pa } namespace { + // Order rules ignoring inner rules + Ordering ord_rule_compatible(const PatternRule& a, const PatternRule& b) + { + if(a.tag() != b.tag()) + return ::ord( (unsigned)a.tag(), b.tag() ); + + TU_MATCHA( (a, b), (ae, be), + (Any, + return OrdEqual; + ), + (Variant, + return ::ord(ae.idx, be.idx); + ), + (Slice, + return ::ord(ae.len, be.len); + ), + (SplitSlice, + auto v = ::ord(ae.leading.size(), be.leading.size()); + if(v != OrdEqual) return v; + v = ::ord(ae.trailing.size(), be.trailing.size()); + if(v != OrdEqual) return v; + return OrdEqual; + ), + (Bool, + return ::ord(ae, be); + ), + (Value, + return ::ord(ae, be); + ), + (ValueRange, + auto v = ::ord(ae.first, be.first); + if(v != OrdEqual) return v; + return ::ord(ae.last, be.last); + ) + ) + throw ""; + } + bool rule_compatible(const PatternRule& a, const PatternRule& b) + { + return ord_rule_compatible(a,b) == OrdEqual; + } + + bool rules_overlap(const PatternRule& a, const PatternRule& b) + { + if( a.is_Any() || b.is_Any() ) + return true; + + // Defensive: If a constant is encountered, assume it overlaps with anything + if(const auto* ae = a.opt_Value()) { + if(ae->is_Const()) + return true; + } + if(const auto* be = b.opt_Value()) { + if(be->is_Const()) + return true; + } + + // Value Range: Overlaps with contained values. + if(const auto* ae = a.opt_ValueRange() ) + { + if(const auto* be = b.opt_Value() ) + { + return ( ae->first <= *be && *be <= ae->last ); + } + else if( const auto* be = b.opt_ValueRange() ) + { + // Start of B within A + if( ae->first <= be->first && be->first <= ae->last ) + return true; + // End of B within A + if( ae->first <= be->last && be->last <= ae->last ) + return true; + // Start of A within B + if( be->first <= ae->first && ae->first <= be->last ) + return true; + // End of A within B + if( be->first <= ae->last && ae->last <= be->last ) + return true; + + // Disjoint + return false; + } + else + { + TODO(Span(), "Check overlap of " << a << " and " << b); + } + } + if(const auto* be = b.opt_ValueRange()) + { + if(const auto* ae = a.opt_Value() ) + { + return (be->first <= *ae && *ae <= be->last); + } + // Note: A can't be ValueRange + else + { + TODO(Span(), "Check overlap of " << a << " and " << b); + } + } + + // SplitSlice patterns overlap with other SplitSlice patterns and larger slices + if(const auto* ae = a.opt_SplitSlice()) + { + if( b.is_SplitSlice() ) + { + return true; + } + else if( const auto* be = b.opt_Slice() ) + { + return be->len >= ae->min_len; + } + else + { + TODO(Span(), "Check overlap of " << a << " and " << b); + } + } + if(const auto* be = b.opt_SplitSlice()) + { + if( const auto* ae = a.opt_Slice() ) + { + return ae->len >= be->min_len; + } + else + { + TODO(Span(), "Check overlap of " << a << " and " << b); + } + } + + // Otherwise, If rules are approximately equal, they overlap + return ( ord_rule_compatible(a, b) == OrdEqual ); + } +} +void sort_rulesets(RulesetRef rulesets, size_t idx) +{ + if(rulesets.size() < 2) + return ; + + bool found_non_any = false; + for(size_t i = 0; i < rulesets.size(); i ++) + if( !rulesets[i][idx].is_Any() ) + found_non_any = true; + if( found_non_any ) + { + TRACE_FUNCTION_F(idx); + for(size_t i = 0; i < rulesets.size(); i ++) + DEBUG("- " << i << ": " << rulesets[i]); + + bool action_taken; + do + { + action_taken = false; + for(size_t i = 0; i < rulesets.size()-1; i ++) + { + if( rules_overlap(rulesets[i][idx], rulesets[i+1][idx]) ) + { + // Don't move + } + else if( ord_rule_compatible(rulesets[i][idx], rulesets[i+1][idx]) == OrdGreater ) + { + rulesets.swap(i, i+1); + action_taken = true; + } + else + { + } + } + } while(action_taken); + for(size_t i = 0; i < rulesets.size(); i ++) + DEBUG("- " << i << ": " << rulesets[i]); + + // TODO: Print sorted ruleset + + // Where compatible, sort insides + size_t start = 0; + for(size_t i = 1; i < rulesets.size(); i++) + { + if( ord_rule_compatible(rulesets[i][idx], rulesets[start][idx]) != OrdEqual ) + { + sort_rulesets_inner(rulesets.slice(start, i-start), idx); + start = i; + } + } + sort_rulesets_inner(rulesets.slice(start, rulesets.size()-start), idx); + + // Iterate onwards where rules are equal + if( idx + 1 < rulesets[0].size() ) + { + size_t start = 0; + for(size_t i = 1; i < rulesets.size(); i++) + { + if( rulesets[i][idx] != rulesets[start][idx] ) + { + sort_rulesets(rulesets.slice(start, i-start), idx+1); + start = i; + } + } + sort_rulesets(rulesets.slice(start, rulesets.size()-start), idx+1); + } + } + else + { + if( idx + 1 < rulesets[0].size() ) + { + sort_rulesets(rulesets, idx + 1); + } + } +} +void sort_rulesets_inner(RulesetRef rulesets, size_t idx) +{ + TRACE_FUNCTION_F(idx << " - " << rulesets[0][idx].tag_str()); + if( const auto* re = rulesets[0][idx].opt_Variant() ) + { + // Sort rules based on contents of enum + if( re->sub_rules.size() > 0 ) + { + sort_rulesets(RulesetRef(rulesets, idx), 0); + } + } +} + +namespace { void get_ty_and_val( const Span& sp, const StaticTraitResolve& resolve, const ::HIR::TypeRef& top_ty, const ::MIR::LValue& top_val, @@ -1437,7 +1833,6 @@ void MIR_LowerHIR_Match_Simple( MirBuilder& builder, MirConverter& conv, ::HIR:: TRACE_FUNCTION; // 1. Generate pattern matches - unsigned int rule_idx = 0; builder.set_cur_block( first_cmp_block ); for( unsigned int arm_idx = 0; arm_idx < node.m_arms.size(); arm_idx ++ ) { @@ -1451,6 +1846,10 @@ void MIR_LowerHIR_Match_Simple( MirBuilder& builder, MirConverter& conv, ::HIR:: if( arm_code.destructures[i] == 0 ) continue ; + size_t rule_idx = 0; + for(; rule_idx < arm_rules.size(); rule_idx++) + if( arm_rules[rule_idx].arm_idx == arm_idx && arm_rules[rule_idx].pat_idx == i ) + break; const auto& pat_rule = arm_rules[rule_idx]; bool is_last_pat = (i+1 == arm.m_patterns.size()); auto next_pattern_bb = (!is_last_pat ? builder.new_bb_unlinked() : next_arm_bb); @@ -1478,8 +1877,6 @@ void MIR_LowerHIR_Match_Simple( MirBuilder& builder, MirConverter& conv, ::HIR:: { builder.set_cur_block( next_pattern_bb ); } - - rule_idx ++; } if( arm_code.has_condition ) { @@ -1549,8 +1946,8 @@ int MIR_LowerHIR_Match_Simple__GeneratePattern(MirBuilder& builder, const Span& auto succ_bb = builder.new_bb_unlinked(); auto test_val = ::MIR::Param( ::MIR::Constant::make_Uint({ re.as_Uint().v, te })); - auto cmp_lval = builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::EQ, mv$(test_val) })); - builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval), succ_bb, fail_bb }) ); + builder.push_stmt_assign(sp, builder.get_if_cond(), ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::EQ, mv$(test_val) })); + builder.end_block( ::MIR::Terminator::make_If({ builder.get_if_cond(), succ_bb, fail_bb }) ); builder.set_cur_block(succ_bb); ), (ValueRange, @@ -1649,7 +2046,38 @@ int MIR_LowerHIR_Match_Simple__GeneratePattern(MirBuilder& builder, const Span& break; case ::HIR::CoreType::F32: case ::HIR::CoreType::F64: - TODO(sp, "Simple match over float - " << ty); + TU_MATCH_DEF( PatternRule, (rule), (re), + ( + BUG(sp, "PatternRule for float is not Value or ValueRange"); + ), + (Value, + auto succ_bb = builder.new_bb_unlinked(); + + auto test_val = ::MIR::Param(::MIR::Constant::make_Float({ re.as_Float().v, te })); + auto cmp_lval = builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::EQ, mv$(test_val) })); + builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval), succ_bb, fail_bb }) ); + builder.set_cur_block(succ_bb); + ), + (ValueRange, + auto succ_bb = builder.new_bb_unlinked(); + auto test_bb_2 = builder.new_bb_unlinked(); + + auto test_lt_val = ::MIR::Param(::MIR::Constant::make_Float({ re.first.as_Float().v, te })); + auto test_gt_val = ::MIR::Param(::MIR::Constant::make_Float({ re.last.as_Float().v, te })); + + // IF `val` < `first` : fail_bb + auto cmp_lt_lval = builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ ::MIR::Param(val.clone()), ::MIR::eBinOp::LT, mv$(test_lt_val) })); + builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lt_lval), fail_bb, test_bb_2 }) ); + + builder.set_cur_block(test_bb_2); + + // IF `val` > `last` : fail_bb + auto cmp_gt_lval = builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ ::MIR::Param(val.clone()), ::MIR::eBinOp::GT, mv$(test_gt_val) })); + builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_gt_lval), fail_bb, succ_bb }) ); + + builder.set_cur_block(succ_bb); + ) + ) break; case ::HIR::CoreType::Str: { ASSERT_BUG(sp, rule.is_Value() && rule.as_Value().is_StaticString(), ""); @@ -1690,7 +2118,11 @@ int MIR_LowerHIR_Match_Simple__GeneratePattern(MirBuilder& builder, const Span& TODO(sp, "Match over Union"); ), (Enum, - auto monomorph = [&](const auto& ty) { return monomorphise_type(sp, pbe->m_params, te.path.m_data.as_Generic().m_params, ty); }; + auto monomorph = [&](const auto& ty) { + auto rv = monomorphise_type(sp, pbe->m_params, te.path.m_data.as_Generic().m_params, ty); + builder.resolve().expand_associated_types(sp, rv); + return rv; + }; ASSERT_BUG(sp, rule.is_Variant(), "Rule for enum isn't Any or Variant"); const auto& re = rule.as_Variant(); unsigned int var_idx = re.idx; @@ -1865,1958 +2297,971 @@ int MIR_LowerHIR_Match_Simple__GeneratePattern(MirBuilder& builder, const Span& return 0; } -// -------------------------------------------------------------------- -// Decision Tree -// -------------------------------------------------------------------- +// -- +// Match v2 Algo - Grouped rules +// -- -// ## Create descision tree in-memory based off the ruleset -// > Tree contains an lvalue and a set of possibilities (PatternRule) connected to another tree or to a branch index -struct DecisionTreeNode + +class t_rules_subset { - TAGGED_UNION( Branch, Unset, - (Unset, struct{}), - (Subtree, ::std::unique_ptr<DecisionTreeNode>), - (Terminal, unsigned int) - ); - - template<typename T> - struct Range + ::std::vector<const ::std::vector<PatternRule>*> rule_sets; + bool is_arm_indexes; + ::std::vector<size_t> arm_idxes; +public: + t_rules_subset(size_t exp, bool is_arm_indexes): + is_arm_indexes(is_arm_indexes) { - T start; - T end; - - // `x` starts after this range ends - bool operator<(const Range<T>& x) const { - return (end < x.start); - } - // `x` falls above the end of this range - bool operator<(const T& x) const { - return (end < x); - } - - // `x` ends before this starts, or overlaps - bool operator>=(const Range<T>& x) const { - return start > x.end || ovelaps(x); - } - // `x` is before or within this range - bool operator>=(const T& x) const { - return start > x || contains(x); - } + rule_sets.reserve(exp); + arm_idxes.reserve(exp); + } - bool operator>(const Range<T>& x) const { - return (start > x.end); - } - bool operator>(const T& x) const { - return (start > x); - } + size_t size() const { + return rule_sets.size(); + } + const ::std::vector<PatternRule>& operator[](size_t n) const { + return *rule_sets[n]; + } + bool is_arm() const { return is_arm_indexes; } + ::std::pair<size_t,size_t> arm_idx(size_t n) const { + assert(is_arm_indexes); + auto v = arm_idxes.at(n); + return ::std::make_pair(v & 0xFFF, v >> 12); + } + ::MIR::BasicBlockId bb_idx(size_t n) const { + assert(!is_arm_indexes); + return arm_idxes.at(n); + } - bool operator==(const Range<T>& x) const { - return start == x.start && end == x.end; - } - bool operator!=(const Range<T>& x) const { - return start != x.start || end != x.end; - } + void sub_sort(size_t ofs, size_t start, size_t n) + { + ::std::vector<size_t> v; + for(size_t i = 0; i < n; i++) + v.push_back(start + i); + // Sort rules based on just the value (ignore inner rules) + ::std::stable_sort( v.begin(), v.end(), [&](auto a, auto b){ return ord_rule_compatible( (*rule_sets[a])[ofs], (*rule_sets[b])[ofs]) == OrdLess; } ); - bool contains(const T& x) const { - return (start <= x && x <= end); + // Reorder contents to above sorting + { + decltype(this->rule_sets) tmp; + for(auto i : v) + tmp.push_back(rule_sets[i]); + ::std::copy( tmp.begin(), tmp.end(), rule_sets.begin() + start ); } - bool overlaps(const Range<T>& x) const { - return (x.start <= start && start <= x.end) || (x.start <= end && end <= x.end); + { + decltype(this->arm_idxes) tmp; + for(auto i : v) + tmp.push_back(arm_idxes[i]); + ::std::copy( tmp.begin(), tmp.end(), arm_idxes.begin() + start ); } + } - friend ::std::ostream& operator<<(::std::ostream& os, const Range<T>& x) { - if( x.start == x.end ) { - return os << x.start; - } - else { - return os << x.start << " ... " << x.end; - } + t_rules_subset sub_slice(size_t ofs, size_t n) + { + t_rules_subset rv { n, this->is_arm_indexes }; + rv.rule_sets.reserve(n); + for(size_t i = 0; i < n; i++) + { + rv.rule_sets.push_back( this->rule_sets[ofs+i] ); + rv.arm_idxes.push_back( this->arm_idxes[ofs+i] ); } - }; - - TAGGED_UNION( Values, Unset, - (Unset, struct {}), - (Bool, struct { Branch false_branch, true_branch; }), - (Variant, ::std::vector< ::std::pair<unsigned int, Branch> >), - (Unsigned, ::std::vector< ::std::pair< Range<uint64_t>, Branch> >), - (Signed, ::std::vector< ::std::pair< Range<int64_t>, Branch> >), - (Float, ::std::vector< ::std::pair< Range<double>, Branch> >), - (String, ::std::vector< ::std::pair< ::std::string, Branch> >), - (Slice, struct { - ::std::vector< ::std::pair< unsigned int, Branch> > fixed_arms; - //::std::vector< ::std::pair< unsigned int, Branch> > variable_arms; - }) - ); - - // TODO: Arm specialisation? - field_path_t m_field_path; - Values m_branches; - Branch m_default; - - DecisionTreeNode( field_path_t field_path ): - // TODO: This is commented out fo a reason, but I don't know why. - //m_field_path( mv$(field_path) ), - m_branches(), - m_default() - {} - - static Branch clone(const Branch& b); - static Values clone(const Values& x); - DecisionTreeNode clone() const; - - void populate_tree_from_rule(const Span& sp, unsigned int arm_index, const PatternRule* first_rule, unsigned int rule_count) { - populate_tree_from_rule(sp, first_rule, rule_count, [sp,arm_index](auto& branch){ - TU_MATCHA( (branch), (e), - (Unset, - // Good - ), - (Subtree, - if( e->m_branches.is_Unset() && e->m_default.is_Unset() ) { - // Good. - } - else { - BUG(sp, "Duplicate terminal - branch="<<branch); - } - ), - (Terminal, - // TODO: This is ok if it's due to overlapping rules (e.g. ranges) - //BUG(sp, "Duplicate terminal - Existing goes to arm " << e << ", new goes to arm " << arm_index ); - ) - ) - branch = Branch::make_Terminal(arm_index); - }); + return rv; + } + void push_arm(const ::std::vector<PatternRule>& x, size_t arm_idx, size_t pat_idx) + { + assert(is_arm_indexes); + rule_sets.push_back(&x); + assert(arm_idx <= 0xFFF); + assert(pat_idx <= 0xFFF); + arm_idxes.push_back(arm_idx | (pat_idx << 12)); } - // `and_then` - Closure called after processing the final rule - void populate_tree_from_rule(const Span& sp, const PatternRule* first_rule, unsigned int rule_count, ::std::function<void(Branch&)> and_then); - - /// Simplifies the tree by eliminating nodes that don't make a decision - void simplify(); - /// Propagate the m_default arm's contents to value arms, and vice-versa - void propagate_default(); - /// HELPER: Unfies the rules from the provided branch with this node - void unify_from(const Branch& b); - - ::MIR::LValue get_field(const ::MIR::LValue& base, unsigned int base_depth) const { - ::MIR::LValue cur = base.clone(); - for(unsigned int i = base_depth; i < m_field_path.size(); i ++ ) { - const auto idx = m_field_path.data[i]; - if( idx == FIELD_DEREF ) { - cur = ::MIR::LValue::make_Deref({ box$(cur) }); + void push_bb(const ::std::vector<PatternRule>& x, ::MIR::BasicBlockId bb) + { + assert(!is_arm_indexes); + rule_sets.push_back(&x); + arm_idxes.push_back(bb); + } + + friend ::std::ostream& operator<<(::std::ostream& os, const t_rules_subset& x) { + os << "t_rules_subset{"; + for(size_t i = 0; i < x.rule_sets.size(); i ++) + { + if(i != 0) + os << ", "; + os << "["; + if(x.is_arm_indexes) + { + os << (x.arm_idxes[i] & 0xFFF) << "," << (x.arm_idxes[i] >> 12); } - else { - cur = ::MIR::LValue::make_Field({ box$(cur), idx }); + else + { + os << "bb" << x.arm_idxes[i]; } + os << "]"; + os << ": " << *x.rule_sets[i]; } - return cur; + os << "}"; + return os; } - - friend ::std::ostream& operator<<(::std::ostream& os, const Branch& x); - friend ::std::ostream& operator<<(::std::ostream& os, const DecisionTreeNode& x); }; -struct DecisionTreeGen +class MatchGenGrouped { + const Span& sp; MirBuilder& m_builder; - const ::std::vector< ::MIR::BasicBlockId>& m_rule_blocks; + const ::HIR::TypeRef& m_top_ty; + const ::MIR::LValue& m_top_val; + const ::std::vector<ArmCode>& m_arms_code; + + size_t m_field_path_ofs; +public: + MatchGenGrouped(MirBuilder& builder, const Span& sp, const ::HIR::TypeRef& top_ty, const ::MIR::LValue& top_val, const ::std::vector<ArmCode>& arms_code, size_t field_path_ofs): + sp(sp), + m_builder(builder), + m_top_ty(top_ty), + m_top_val(top_val), + m_arms_code(arms_code), + m_field_path_ofs(field_path_ofs) + { + } - DecisionTreeGen(MirBuilder& builder, const ::std::vector< ::MIR::BasicBlockId >& rule_blocks): - m_builder( builder ), - m_rule_blocks( rule_blocks ) - {} + void gen_for_slice(t_rules_subset rules, size_t ofs, ::MIR::BasicBlockId default_arm); + void gen_dispatch(const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk); + void gen_dispatch__primitive(::HIR::TypeRef ty, ::MIR::LValue val, const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk); + void gen_dispatch__enum(::HIR::TypeRef ty, ::MIR::LValue val, const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk); + void gen_dispatch__slice(::HIR::TypeRef ty, ::MIR::LValue val, const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk); - ::MIR::BasicBlockId get_block_for_rule(unsigned int rule_index) { - return m_rule_blocks.at( rule_index ); - } + void gen_dispatch_range(const field_path_t& field_path, const ::MIR::Constant& first, const ::MIR::Constant& last, ::MIR::BasicBlockId def_blk); + void gen_dispatch_splitslice(const field_path_t& field_path, const PatternRule::Data_SplitSlice& e, ::MIR::BasicBlockId def_blk); - void generate_tree_code(const Span& sp, const DecisionTreeNode& node, const ::HIR::TypeRef& ty, const ::MIR::LValue& val) { - generate_tree_code(sp, node, ty, 0, val, [&](const auto& n){ - DEBUG("node = " << n); - // - Recurse on this method - this->generate_tree_code(sp, n, ty, val); - }); + ::MIR::LValue push_compare(::MIR::LValue left, ::MIR::eBinOp op, ::MIR::Param right) + { + return m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, + ::MIR::RValue::make_BinOp({ mv$(left), op, mv$(right) }) + ); } - void generate_tree_code( - const Span& sp, - const DecisionTreeNode& node, - const ::HIR::TypeRef& ty, unsigned int path_ofs, const ::MIR::LValue& base_val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - - void generate_branch(const DecisionTreeNode::Branch& branch, ::std::function<void(const DecisionTreeNode&)> cb); - - // HELPER - ::MIR::LValue push_compare(const Span& sp, ::MIR::LValue left, ::MIR::eBinOp op, ::MIR::Param right); - - void generate_branches_Signed( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Signed& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - void generate_branches_Unsigned( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Unsigned& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - void generate_branches_Float( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Float& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - void generate_branches_Char( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Unsigned& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - void generate_branches_Bool( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Bool& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - void generate_branches_Borrow_str( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_String& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - - void generate_branches_Enum( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Variant& branches, - const field_path_t& field_path, // used to know when to stop handling sub-nodes - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - void generate_branches_Slice( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Slice& branches, - const field_path_t& field_path, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ); - void generate_tree_code__enum( - const Span& sp, - const DecisionTreeNode& node, const ::HIR::TypeRef& fake_ty, const ::MIR::LValue& val, - const field_path_t& path_prefix, - ::std::function<void(const DecisionTreeNode&)> and_then - ); }; -void MIR_LowerHIR_Match_DecisionTree( MirBuilder& builder, MirConverter& conv, ::HIR::ExprNode_Match& node, ::MIR::LValue match_val, t_arm_rules arm_rules, ::std::vector<ArmCode> arms_code, ::MIR::BasicBlockId first_cmp_block ) +void MIR_LowerHIR_Match_Grouped( + MirBuilder& builder, MirConverter& conv, ::HIR::ExprNode_Match& node, ::MIR::LValue match_val, + t_arm_rules arm_rules, ::std::vector<ArmCode> arms_code, ::MIR::BasicBlockId first_cmp_block + ) { - TRACE_FUNCTION; + TRACE_FUNCTION_F(""); - // XXX XXX XXX: The current codegen (below) will generate incorrect code if ordering matters. - // ``` - // match ("foo", "bar") - // { - // (_, "bar") => {}, // Expected - // ("foo", _) => {}, // Actual - // _ => {}, - // } - // ``` - - // TODO: Sort the columns in `arm_rules` to ensure that the most specific rule is parsed first. - // - Ordering within a pattern doesn't matter, only the order of arms matters. - // - This sort could be designed such that the above case would match correctly? - - DEBUG("- Generating rule bindings"); - ::std::vector< ::MIR::BasicBlockId> rule_blocks; - for(const auto& rule : arm_rules) + // - Create a "slice" of the passed rules, suitable for passing to the recursive part of the algo + t_rules_subset rules { arm_rules.size(), /*is_arm_indexes=*/true }; + for(const auto& r : arm_rules) { - const auto& arm_code = arms_code[rule.arm_idx]; - ASSERT_BUG(node.span(), !arm_code.has_condition, "Decision tree doesn't (yet) support conditionals"); - - assert( rule.pat_idx < arm_code.destructures.size() ); - // Set the target for when a rule succeeds to the destructuring code for this rule - rule_blocks.push_back( arm_code.destructures[rule.pat_idx] ); - // - Tie the end of that block to the code block for this arm - builder.set_cur_block( rule_blocks.back() ); - builder.end_block( ::MIR::Terminator::make_Goto(arm_code.code) ); + rules.push_arm( r.m_rules, r.arm_idx, r.pat_idx ); } + auto inst = MatchGenGrouped { builder, node.span(), node.m_value->m_res_type, match_val, arms_code, 0 }; - // - Build tree by running each arm's pattern across it - DEBUG("- Building decision tree"); - DecisionTreeNode root_node({}); - unsigned int rule_idx = 0; - for( const auto& arm_rule : arm_rules ) - { - auto arm_idx = arm_rule.arm_idx; - DEBUG("(" << arm_idx << ", " << arm_rule.pat_idx << "): " << arm_rule.m_rules); - root_node.populate_tree_from_rule( node.m_arms[arm_idx].m_code->span(), rule_idx, arm_rule.m_rules.data(), arm_rule.m_rules.size() ); - rule_idx += 1; - } - DEBUG("root_node = " << root_node); - root_node.simplify(); - DEBUG("root_node = " << root_node); - root_node.propagate_default(); - DEBUG("root_node = " << root_node); - // TODO: Pretty print `root_node` - - // - Convert the above decision tree into MIR - DEBUG("- Emitting decision tree"); - DecisionTreeGen gen { builder, rule_blocks }; - builder.set_cur_block( first_cmp_block ); - gen.generate_tree_code( node.span(), root_node, node.m_value->m_res_type, mv$(match_val) ); - ASSERT_BUG(node.span(), !builder.block_active(), "Decision tree didn't terminate the final block"); -} + // NOTE: This block should never be used + auto default_arm = builder.new_bb_unlinked(); -#if 0 -DecisionTreeNode MIR_LowerHIR_Match_DecisionTree__MakeTree(const Span& sp, t_arm_rules& arm_rules) -{ - ::std::vector<unsigned int> indexes; - ::std::vector< slice<PatternRule> > rules; - for(unsigned i = 0; i < arm_rules.size(); i ++) - { - rules.push_back( arm_rules[i].m_rules ); - indexes.push_back(i); - } + builder.set_cur_block( first_cmp_block ); + inst.gen_for_slice( mv$(rules), 0, default_arm ); - return MIR_LowerHIR_Match_DecisionTree__MakeTree_Node(sp, indexes, rules); + // Make the default infinite loop. + // - Preferably, it'd abort. + builder.set_cur_block(default_arm); + builder.end_block( ::MIR::Terminator::make_Goto(default_arm) ); } -DecisionTreeNode MIR_LowerHIR_Match_DecisionTree__MakeTree_Node(const Span& sp, slice<unsigned int> arm_indexes, slice< slice<PaternRule>> arm_rules) +void MatchGenGrouped::gen_for_slice(t_rules_subset arm_rules, size_t ofs, ::MIR::BasicBlockId default_arm) { - assert( arm_indexes.size() == arm_rules.size() ); - assert( arm_rules.size() > 1 ); - assert( arm_rules[0].size() > 0 ); + TRACE_FUNCTION_F("arm_rules=" << arm_rules << ", ofs="<<ofs); + ASSERT_BUG(sp, arm_rules.size() > 0, ""); - // 1. Sort list (should it already be sorted?) - for(const auto& rules : arm_rules) + // Quick hack: Skip any layers entirely made up of PatternRule::Any + for(;;) { - ASSERT_BUG(sp, rules.size() != arm_rules[0].size(), ""); - } - - // 2. Detect all arms being `_` and move on to the next condition - while( ::std::all_of(arm_rules.begin(), arm_rules.end(), [](const auto& r){ return r.m_rules[0].is_Any(); }) ) - { - // Delete first rule from all and continue. - if( arm_rules[0].size() == 1 ) { - // No rules left? - BUG(sp, "Duplicate match arms"); + bool is_all_any = true; + for(size_t i = 0; i < arm_rules.size() && is_all_any; i ++) + { + if( arm_rules[i].size() <= ofs ) + is_all_any = false; + else if( ! arm_rules[i][ofs].is_Any() ) + is_all_any = false; } - - for(auto& rules : arm_rules) + if( ! is_all_any ) { - rules = rules.subslice_from(1); + break ; } + ofs ++; + DEBUG("Skip to ofs=" << ofs); } - // We have a codition. - for(const auto& rules : arm_rules) + // Split current set of rules into groups based on _ patterns + for(size_t idx = 0; idx < arm_rules.size(); ) { - ASSERT_BUG(sp, rules[0].is_Any() || rules[0].tag() == arm_rules[0][0].tag(), "Mismatched rules in match"); - } - - bool has_any = arm_rules.back()[0].is_Any(); + // Completed arms + while( idx < arm_rules.size() && arm_rules[idx].size() <= ofs ) + { + auto next = idx+1 == arm_rules.size() ? default_arm : m_builder.new_bb_unlinked(); + ASSERT_BUG(sp, arm_rules[idx].size() == ofs, "Offset too large for rule - ofs=" << ofs << ", rules=" << arm_rules[idx]); + DEBUG(idx << ": Complete"); + // Emit jump to either arm code, or arm condition + if( arm_rules.is_arm() ) + { + auto ai = arm_rules.arm_idx(idx); + ASSERT_BUG(sp, m_arms_code.size() > 0, "Bottom-level ruleset with no arm code information"); + const auto& ac = m_arms_code[ai.first]; - // All rules must either be _ or the same type, and can't all be _ - switch( arm_rules[0][0].tag() ) - { - case PatternRule::TAGDEAD: throw ""; - case PatternRule::TAG_Any: throw ""; + m_builder.end_block( ::MIR::Terminator::make_Goto(ac.destructures[ai.second]) ); + m_builder.set_cur_block( ac.destructures[ai.second] ); - case PatternRule::TAG_Variant: - break; - // TODO: Value and ValueRange can appear together. - // - They also overlap in non-trivial ways. - } -} -#endif - -// ---------------------------- -// DecisionTreeNode -// ---------------------------- -DecisionTreeNode::Branch DecisionTreeNode::clone(const DecisionTreeNode::Branch& b) { - TU_MATCHA( (b), (e), - (Unset, return Branch(e); ), - (Subtree, return Branch(box$( e->clone() )); ), - (Terminal, return Branch(e); ) - ) - throw ""; -} -DecisionTreeNode::Values DecisionTreeNode::clone(const DecisionTreeNode::Values& x) { - TU_MATCHA( (x), (e), - (Unset, return Values(e); ), - (Bool, - return Values::make_Bool({ clone(e.false_branch), clone(e.true_branch) }); - ), - (Variant, - Values::Data_Variant rv; - rv.reserve(e.size()); - for(const auto& v : e) - rv.push_back( ::std::make_pair(v.first, clone(v.second)) ); - return Values( mv$(rv) ); - ), - (Unsigned, - Values::Data_Unsigned rv; - rv.reserve(e.size()); - for(const auto& v : e) - rv.push_back( ::std::make_pair(v.first, clone(v.second)) ); - return Values( mv$(rv) ); - ), - (Signed, - Values::Data_Signed rv; - rv.reserve(e.size()); - for(const auto& v : e) - rv.push_back( ::std::make_pair(v.first, clone(v.second)) ); - return Values( mv$(rv) ); - ), - (Float, - Values::Data_Float rv; - rv.reserve(e.size()); - for(const auto& v : e) - rv.push_back( ::std::make_pair(v.first, clone(v.second)) ); - return Values( mv$(rv) ); - ), - (String, - Values::Data_String rv; - rv.reserve(e.size()); - for(const auto& v : e) - rv.push_back( ::std::make_pair(v.first, clone(v.second)) ); - return Values( mv$(rv) ); - ), - (Slice, - Values::Data_Slice rv; - rv.fixed_arms.reserve(e.fixed_arms.size()); - for(const auto& v : e.fixed_arms) - rv.fixed_arms.push_back( ::std::make_pair(v.first, clone(v.second)) ); - return Values( mv$(rv) ); - ) - ) - throw ""; -} -DecisionTreeNode DecisionTreeNode::clone() const { - DecisionTreeNode rv(m_field_path); - rv.m_field_path = m_field_path; - rv.m_branches = clone(m_branches); - rv.m_default = clone(m_default); - return rv; -} + if( ac.has_condition ) + { + TODO(sp, "Handle conditionals in Grouped"); + // TODO: If the condition fails, this should re-try the match on other rules that could have worked. + // - For now, conditionals are disabled. -// Helpers for `populate_tree_from_rule` -namespace -{ - DecisionTreeNode::Branch new_branch_subtree(field_path_t path) - { - return DecisionTreeNode::Branch( box$(DecisionTreeNode( mv$(path) )) ); - } + // TODO: What if there's multiple patterns on this condition? + // - For now, only the first pattern gets edited. + // - Maybe clone the blocks used for the condition? + m_builder.end_block( ::MIR::Terminator::make_Goto(ac.cond_start) ); - // Common code for numerics (Int, Uint, and Float) - template<typename T> - static void from_rule_value( - const Span& sp, - ::std::vector< ::std::pair< DecisionTreeNode::Range<T>, DecisionTreeNode::Branch> >& be, T ve, - const char* name, const field_path_t& field_path, ::std::function<void(DecisionTreeNode::Branch&)> and_then - ) - { - auto it = ::std::find_if(be.begin(), be.end(), [&](const auto& v){ return v.first.end >= ve; }); - if( it == be.end() || it->first.start > ve ) { - it = be.insert( it, ::std::make_pair( DecisionTreeNode::Range<T> { ve,ve }, new_branch_subtree(field_path) ) ); - } - else if( it->first.start == ve && it->first.end == ve ) { - // Equal, continue and add sub-pat - } - else { - // Collide or overlap! - TODO(sp, "Value patterns - " << name << " - Overlapping - " << it->first.start << " <= " << ve << " <= " << it->first.end); - } - and_then( it->second ); - } + // Check for marking in `ac` that the block has already been terminated, assert that target is `next` + if( ai.second == 0 ) + { + if( ac.cond_fail_tgt != 0) + { + ASSERT_BUG(sp, ac.cond_fail_tgt == next, "Condition fail target already set with mismatching arm, set to bb" << ac.cond_fail_tgt << " cur is bb" << next); + } + else + { + ac.cond_fail_tgt = next; - template<typename T> - static void from_rule_valuerange( - const Span& sp, - ::std::vector< ::std::pair< DecisionTreeNode::Range<T>, DecisionTreeNode::Branch> >& be, T ve_start, T ve_end, - const char* name, const field_path_t& field_path, ::std::function<void(DecisionTreeNode::Branch&)> and_then - ) - { - TRACE_FUNCTION_F("be=[" << FMT_CB(os, for(const auto& i:be) os << i.first <<" , ";) << "], new=" << ve_start << "..." << ve_end); - ASSERT_BUG(sp, ve_start <= ve_end, "Range pattern with a start after the end - " << ve_start << "..." << ve_end); + m_builder.set_cur_block( ac.cond_end ); + m_builder.end_block( ::MIR::Terminator::make_If({ ac.cond_lval.clone(), ac.code, next }) ); + } + } - if( ve_start == ve_end ) { - from_rule_value(sp, be, ve_start, name, field_path, and_then); - return ; - } - // - Find the first entry that ends after the new one starts. - auto it = ::std::find_if(be.begin(), be.end(), [&](const auto& v){ return v.first.end >= ve_start; }); - while(ve_start < ve_end) - { - if( it == be.end() ) { - DEBUG("new = (" << ve_start << "..." << ve_end << "), exist=END"); - it = be.insert( it, ::std::make_pair( DecisionTreeNode::Range<T> { ve_start,ve_end }, new_branch_subtree(field_path) ) ); - and_then(it->second); - return ; - } - DEBUG("new = (" << ve_start << "..." << ve_end << "), exist=" << it->first); - // If the located entry starts after the end of this range - if( it->first.start >= ve_end ) { - DEBUG("- New free"); - it = be.insert( it, ::std::make_pair( DecisionTreeNode::Range<T> { ve_start,ve_end }, new_branch_subtree(field_path) ) ); - and_then(it->second); - return ; - } - // If this range is equal to the existing, just recurse into it - else if( it->first.start == ve_start && it->first.end == ve_end ) { - DEBUG("- Equal"); - and_then(it->second); - return ; - } - // If the new range starts before the start of this range, add a new entry before the existing one - else if( it->first.start > ve_start ) { - DEBUG("- New head, continue"); - it = be.insert( it, ::std::make_pair( DecisionTreeNode::Range<T> { ve_start,it->first.start-1 }, new_branch_subtree(field_path) ) ); - and_then(it->second); - ++ it; - ve_start = it->first.start; - } - // If the new range ends before the end of this range, split the existing range and recurse into the first - else if( it->first.end > ve_end ) { - DEBUG("- Inner"); - assert(ve_start == it->first.start); - it = be.insert( it, ::std::make_pair( DecisionTreeNode::Range<T> { ve_start, ve_end }, DecisionTreeNode::clone(it->second) ) ); - and_then(it->second); - (it+1)->first.start = ve_end+1; - return ; + if( next != default_arm ) + m_builder.set_cur_block(next); + } + else + { + m_builder.end_block( ::MIR::Terminator::make_Goto(ac.code) ); + ASSERT_BUG(sp, idx+1 == arm_rules.size(), "Ended arm with other arms present"); + } } - // (else) if the new range ends after the end of this range, apply to the rest of this range and advance - else { - DEBUG("- Shared head, continue"); - //assert(it->first.start == ve_start); - assert((it->first.end) < ve_end); - - and_then(it->second); - ve_start = it->first.end + 1; - ++ it; + else + { + auto bb = arm_rules.bb_idx(idx); + m_builder.end_block( ::MIR::Terminator::make_Goto(bb) ); + while( idx+1 < arm_rules.size() && bb == arm_rules.bb_idx(idx) && arm_rules[idx].size() == ofs ) + idx ++; + ASSERT_BUG(sp, idx+1 == arm_rules.size(), "Ended arm (inner) with other arms present"); } + idx ++; } - } -} -void DecisionTreeNode::populate_tree_from_rule(const Span& sp, const PatternRule* first_rule, unsigned int rule_count, ::std::function<void(Branch&)> and_then) -{ - assert( rule_count > 0 ); - const auto& rule = *first_rule; - if( m_field_path.size() == 0 ) { - m_field_path = rule.field_path; - } - else { - ASSERT_BUG(sp, m_field_path == rule.field_path, "Patterns with mismatched field paths - " << m_field_path << " != " << rule.field_path); - } - - #define GET_BRANCHES(fld, var) (({if( fld.is_Unset() ) {\ - fld = Values::make_##var({}); \ - } \ - else if( !fld.is_##var() ) { \ - BUG(sp, "Mismatched rules - have " #var ", but have seen " << fld.tag_str()); \ - }}), \ - fld.as_##var()) - - - TU_MATCHA( (rule), (e), - (Any, { - if( rule_count == 1 ) + // - Value arms + auto start = idx; + for(; idx < arm_rules.size() ; idx ++) { - ASSERT_BUG(sp, !m_default.is_Terminal(), "Duplicate terminal rule"); - and_then(m_default); - } - else - { - if( m_default.is_Unset() ) { - m_default = new_branch_subtree(rule.field_path); - m_default.as_Subtree()->populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else TU_IFLET( Branch, m_default, Subtree, be, - be->populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - ) - else { - // NOTE: All lists processed as part of the same tree should be the same length - BUG(sp, "Duplicate terminal rule"); - } + if( arm_rules[idx].size() <= ofs ) + break; + if( arm_rules[idx][ofs].is_Any() ) + break; + if( arm_rules[idx][ofs].is_SplitSlice() ) + break; + // TODO: It would be nice if ValueRange could be combined with Value (if there's no overlap) + if( arm_rules[idx][ofs].is_ValueRange() ) + break; } - // TODO: Should this also recurse into branches? - }), - (Variant, { - auto& be = GET_BRANCHES(m_branches, Variant); + auto first_any = idx; - auto it = ::std::find_if( be.begin(), be.end(), [&](const auto& x){ return x.first >= e.idx; }); - // If this variant isn't yet processed, add a new subtree for it - if( it == be.end() || it->first != e.idx ) { - it = be.insert(it, ::std::make_pair(e.idx, new_branch_subtree(rule.field_path))); - assert( it->second.is_Subtree() ); - } - else { - if( it->second.is_Terminal() ) { - BUG(sp, "Duplicate terminal rule - " << it->second.as_Terminal()); - } - assert( !it->second.is_Unset() ); - assert( it->second.is_Subtree() ); - } - auto& subtree = *it->second.as_Subtree(); + // Generate dispatch based on the above list + // - If there's value ranges they need special handling + // - Can sort arms within this group (ordering doesn't matter, as long as ranges are handled) + // - Sort must be stable. - if( e.sub_rules.size() > 0 && rule_count > 1 ) + if( start < first_any ) { - subtree.populate_tree_from_rule(sp, e.sub_rules.data(), e.sub_rules.size(), [&](auto& branch){ - TU_MATCH_DEF(Branch, (branch), (be), - ( - BUG(sp, "Duplicate terminator"); - ), - (Unset, - branch = new_branch_subtree(rule.field_path); - ), - (Subtree, - ) - ) - branch.as_Subtree()->populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - }); - } - else if( e.sub_rules.size() > 0) - { - subtree.populate_tree_from_rule(sp, e.sub_rules.data(), e.sub_rules.size(), and_then); - } - else if( rule_count > 1 ) - { - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(it->second); - } - }), - (Slice, - auto& be = GET_BRANCHES(m_branches, Slice); - - auto it = ::std::find_if( be.fixed_arms.begin(), be.fixed_arms.end(), [&](const auto& x){ return x.first >= e.len; } ); - if( it == be.fixed_arms.end() || it->first != e.len ) { - it = be.fixed_arms.insert(it, ::std::make_pair(e.len, new_branch_subtree(rule.field_path))); - } - else { - if( it->second.is_Terminal() ) { - BUG(sp, "Duplicate terminal rule - " << it->second.as_Terminal()); + DEBUG(start << "+" << (first_any-start) << ": Values"); + bool has_default = (first_any < arm_rules.size()); + auto next = (has_default ? m_builder.new_bb_unlinked() : default_arm); + + // Sort rules before getting compatible runs + // TODO: Is this a valid operation? + arm_rules.sub_sort(ofs, start, first_any - start); + + // Create list of compatible arm slices (runs with the same selector value) + ::std::vector<t_rules_subset> slices; + auto cur_test = start; + for(auto i = start; i < first_any; i ++) + { + // Just check if the decision value differs (don't check nested rules) + if( ! rule_compatible(arm_rules[i][ofs], arm_rules[cur_test][ofs]) ) + { + slices.push_back( arm_rules.sub_slice(cur_test, i - cur_test) ); + cur_test = i; + } } - assert( !it->second.is_Unset() ); - } - assert( it->second.is_Subtree() ); - auto& subtree = *it->second.as_Subtree(); + slices.push_back( arm_rules.sub_slice(cur_test, first_any - cur_test) ); + DEBUG("- " << slices.size() << " groupings"); + ::std::vector<::MIR::BasicBlockId> arm_blocks; + arm_blocks.reserve( slices.size() ); - if( e.sub_rules.size() > 0 && rule_count > 1 ) - { - subtree.populate_tree_from_rule(sp, e.sub_rules.data(), e.sub_rules.size(), [&](auto& branch){ - TU_MATCH_DEF(Branch, (branch), (be), - ( - BUG(sp, "Duplicate terminator"); - ), - (Unset, - branch = new_branch_subtree(rule.field_path); - ), - (Subtree, - ) - ) - branch.as_Subtree()->populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - }); - } - else if( e.sub_rules.size() > 0) - { - subtree.populate_tree_from_rule(sp, e.sub_rules.data(), e.sub_rules.size(), and_then); - } - else if( rule_count > 1 ) - { - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(it->second); - } - ), - (SplitSlice, - //auto& be = GET_BRANCHES(m_branches, Slice); - TODO(sp, "SplitSlice in DTN - " << rule); - ), - (Bool, - auto& be = GET_BRANCHES(m_branches, Bool); + auto cur_blk = m_builder.pause_cur_block(); + // > Stable sort list + ::std::sort( slices.begin(), slices.end(), [&](const auto& a, const auto& b){ return a[0][ofs] < b[0][ofs]; } ); + // TODO: Should this do a stable sort of inner patterns too? + // - A sort of inner patterns such that `_` (and range?) patterns don't change position. - auto& branch = (e ? be.true_branch : be.false_branch); - if( branch.is_Unset() ) { - branch = new_branch_subtree( rule.field_path ); - } - else if( branch.is_Terminal() ) { - BUG(sp, "Duplicate terminal rule - " << branch.as_Terminal()); - } - else { - // Good. - } - if( rule_count > 1 ) - { - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(branch); - } - ), - (Value, - TU_MATCHA( (e), (ve), - (Int, - auto& be = GET_BRANCHES(m_branches, Signed); - - from_rule_value(sp, be, ve.v, "Signed", rule.field_path, - [&](auto& branch) { - if( rule_count > 1 ) { - assert( branch.as_Subtree() ); - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(branch); - } - }); - ), - (Uint, - auto& be = GET_BRANCHES(m_branches, Unsigned); - - from_rule_value(sp, be, ve.v, "Unsigned", rule.field_path, - [&](auto& branch) { - if( rule_count > 1 ) { - assert( branch.as_Subtree() ); - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else + // > Get type of match, generate dispatch list. + for(size_t i = 0; i < slices.size(); i ++) + { + // If rules are actually different, split here. (handles Enum and Slice) + auto cur_block = m_builder.new_bb_unlinked(); + m_builder.set_cur_block(cur_block); + auto cur_start = 0; + for(size_t j = 0; j < slices[i].size(); j ++) + { + if(slices[i][j][ofs] != slices[i][cur_start][ofs]) { - and_then(branch); - } - }); - ), - (Float, - auto& be = GET_BRANCHES(m_branches, Float); - - from_rule_value(sp, be, ve.v, "Float", rule.field_path, - [&](auto& branch) { - if( rule_count > 1 ) { - assert( branch.as_Subtree() ); - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else { - and_then(branch); - } - }); - ), - (Bool, - BUG(sp, "Hit Bool in PatternRule::Value - " << e); - ), - (Bytes, - TODO(sp, "Value patterns - Bytes"); - ), - (StaticString, - auto& be = GET_BRANCHES(m_branches, String); + DEBUG("- Equal range : " << cur_start << "+" << (j - cur_start)); + // Package up previous rules and generate dispatch code + auto ns = slices[i].sub_slice(cur_start, j - cur_start); + this->gen_for_slice(mv$(ns), ofs+1, next); - auto it = ::std::find_if(be.begin(), be.end(), [&](const auto& v){ return v.first >= ve; }); - if( it == be.end() || it->first != ve ) { - it = be.insert( it, ::std::make_pair(ve, new_branch_subtree(rule.field_path) ) ); - } - auto& branch = it->second; - if( rule_count > 1 ) - { - assert( branch.as_Subtree() ); - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(branch); - } - ), - (Const, - BUG(sp, "Hit Const in PatternRule::Value - " << e); - ), - (ItemAddr, - BUG(sp, "Hit ItemAddr in PatternRule::Value - " << e); - ) - ) - ), - (ValueRange, + cur_block = m_builder.new_bb_unlinked(); + m_builder.set_cur_block(cur_block); - ASSERT_BUG(sp, e.first.tag() == e.last.tag(), "Constant type mismatch in ValueRange - " << e.first << " and " << e.last); - TU_MATCHA( (e.first, e.last), (ve_start, ve_end), - (Int, - auto& be = GET_BRANCHES(m_branches, Signed); - from_rule_valuerange(sp, be, ve_start.v, ve_end.v, "Signed", rule.field_path, - [&](auto& branch) { - if( rule_count > 1 ) - { - assert( branch.as_Subtree() ); - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(branch); + cur_start = j; } - }); - ), - (Uint, - // TODO: Share code between the three numeric groups - auto& be = GET_BRANCHES(m_branches, Unsigned); - from_rule_valuerange(sp, be, ve_start.v, ve_end.v, "Unsigned", rule.field_path, - [&](auto& branch) { - if( rule_count > 1 ) - { - assert( branch.as_Subtree() ); - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(branch); - } - }); - ), - (Float, - auto& be = GET_BRANCHES(m_branches, Float); - from_rule_valuerange(sp, be, ve_start.v, ve_end.v, "Float", rule.field_path, - [&](auto& branch) { - if( rule_count > 1 ) - { - assert( branch.as_Subtree() ); - auto& subtree = *branch.as_Subtree(); - subtree.populate_tree_from_rule(sp, first_rule+1, rule_count-1, and_then); - } - else - { - and_then(branch); - } - }); - ), - (Bool, - BUG(sp, "Hit Bool in PatternRule::ValueRange - " << e.first); - ), - (Bytes, - TODO(sp, "ValueRange patterns - Bytes"); - ), - (StaticString, - ERROR(sp, E0000, "Use of string in value range patter"); - ), - (Const, - BUG(sp, "Hit Const in PatternRule::ValueRange - " << e.first); - ), - (ItemAddr, - BUG(sp, "Hit ItemAddr in PatternRule::ValueRange - " << e.first); - ) - ) - ) - ) -} - -void DecisionTreeNode::simplify() -{ - struct H { - static void simplify_branch(Branch& b) - { - TU_IFLET(Branch, b, Subtree, be, - be->simplify(); - if( be->m_branches.is_Unset() ) { - auto v = mv$( be->m_default ); - b = mv$(v); + arm_blocks.push_back(cur_block); } - ) - } - }; - - TU_MATCHA( (m_branches), (e), - (Unset, - H::simplify_branch(m_default); - // Replace `this` with `m_default` if `m_default` is a subtree - // - Fixes the edge case for the top of the tree - if( m_default.is_Subtree() ) - { - *this = mv$(*m_default.as_Subtree()); - } - return ; - ), - (Bool, - H::simplify_branch(e.false_branch); - H::simplify_branch(e.true_branch); - ), - (Variant, - for(auto& branch : e) { - H::simplify_branch(branch.second); - } - ), - (Unsigned, - for(auto& branch : e) { - H::simplify_branch(branch.second); - } - ), - (Signed, - for(auto& branch : e) { - H::simplify_branch(branch.second); - } - ), - (Float, - for(auto& branch : e) { - H::simplify_branch(branch.second); - } - ), - (String, - for(auto& branch : e) { - H::simplify_branch(branch.second); - } - ), - (Slice, - for(auto& branch : e.fixed_arms) { - H::simplify_branch(branch.second); - } - ) - ) - H::simplify_branch(m_default); -} - -void DecisionTreeNode::propagate_default() -{ - TRACE_FUNCTION_FR(*this, *this); - struct H { - static void handle_branch(Branch& b, const Branch& def) { - TU_IFLET(Branch, b, Subtree, be, - be->propagate_default(); - if( !def.is_Unset() ) + if( cur_start != 0 ) { - DEBUG("Unify " << *be << " with " << def); - be->unify_from(def); - be->propagate_default(); + DEBUG("- Equal range : " << cur_start << "+" << (slices[i].size() - cur_start)); + auto ns = slices[i].sub_slice(cur_start, slices[i].size() - cur_start); + this->gen_for_slice( mv$(ns), ofs+1, next); } - ) - } - }; - - TU_MATCHA( (m_branches), (e), - (Unset, - ), - (Bool, - DEBUG("- false"); - H::handle_branch(e.false_branch, m_default); - DEBUG("- true"); - H::handle_branch(e.true_branch, m_default); - ), - (Variant, - for(auto& branch : e) { - DEBUG("- V " << branch.first); - H::handle_branch(branch.second, m_default); - } - ), - (Unsigned, - for(auto& branch : e) { - DEBUG("- U " << branch.first); - H::handle_branch(branch.second, m_default); - } - ), - (Signed, - for(auto& branch : e) { - DEBUG("- S " << branch.first); - H::handle_branch(branch.second, m_default); - } - ), - (Float, - for(auto& branch : e) { - DEBUG("- " << branch.first); - H::handle_branch(branch.second, m_default); - } - ), - (String, - for(auto& branch : e) { - DEBUG("- '" << branch.first << "'"); - H::handle_branch(branch.second, m_default); - } - ), - (Slice, - for(auto& branch : e.fixed_arms) { - DEBUG("- [_;" << branch.first << "]"); - H::handle_branch(branch.second, m_default); - } - ) - ) - DEBUG("- default"); - TU_IFLET(Branch, m_default, Subtree, be, - be->propagate_default(); - - if( be->m_default.is_Unset() ) { - // Propagate default from value branches - TU_MATCHA( (m_branches), (e), - (Unset, - ), - (Bool, - be->unify_from(e.false_branch); - be->unify_from(e.true_branch); - ), - (Variant, - for(auto& branch : e) { - be->unify_from(branch.second); - } - ), - (Unsigned, - for(auto& branch : e) { - be->unify_from(branch.second); - } - ), - (Signed, - for(auto& branch : e) { - be->unify_from(branch.second); - } - ), - (Float, - for(auto& branch : e) { - be->unify_from(branch.second); - } - ), - (String, - for(auto& branch : e) { - be->unify_from(branch.second); - } - ), - (Slice, - for(auto& branch : e.fixed_arms) { - be->unify_from(branch.second); + else + { + this->gen_for_slice(slices[i], ofs+1, next); } - ) - ) - } - ) -} - -namespace { - static void unify_branch(DecisionTreeNode::Branch& dst, const DecisionTreeNode::Branch& src) { - if( dst.is_Unset() ) { - dst = DecisionTreeNode::clone(src); - } - else if( dst.is_Subtree() ) { - dst.as_Subtree()->unify_from(src); - } - else { - // Terminal, no unify - } - } - - template<typename T> - void unify_from_vals_range(::std::vector< ::std::pair<T, DecisionTreeNode::Branch>>& dst, const ::std::vector< ::std::pair<T, DecisionTreeNode::Branch>>& src) - { - for(const auto& srcv : src) - { - // Find the first entry with an end greater than or equal to the start of this entry - auto it = ::std::find_if( dst.begin(), dst.end(), [&](const auto& x){ return x.first.end >= srcv.first.start; }); - // Not found? Insert a new branch - if( it == dst.end() ) { - it = dst.insert(it, ::std::make_pair(srcv.first, DecisionTreeNode::clone(srcv.second))); } - // If the found entry doesn't overlap (the start of `*it` is after the end of `srcv`) - else if( it->first.start > srcv.first.end ) { - it = dst.insert(it, ::std::make_pair(srcv.first, DecisionTreeNode::clone(srcv.second))); - } - else if( it->first == srcv.first ) { - unify_branch( it->second, srcv.second ); - } - else { - // NOTE: Overlapping doesn't get handled here - } - } - } - template<typename T> - void unify_from_vals_pt(::std::vector< ::std::pair<T, DecisionTreeNode::Branch>>& dst, const ::std::vector< ::std::pair<T, DecisionTreeNode::Branch>>& src) - { - // Insert items not already present, merge present items - for(const auto& srcv : src) - { - auto it = ::std::find_if( dst.begin(), dst.end(), [&](const auto& x){ return x.first >= srcv.first; }); - // Not found? Insert a new branch - if( it == dst.end() || it->first != srcv.first ) { - it = dst.insert(it, ::std::make_pair(srcv.first, DecisionTreeNode::clone(srcv.second))); - } - else { - unify_branch( it->second, srcv.second ); - } - } - } -} + m_builder.set_cur_block(cur_blk); -void DecisionTreeNode::unify_from(const Branch& b) -{ - TRACE_FUNCTION_FR(*this << " with " << b, *this); + // Generate decision code + this->gen_dispatch(slices, ofs, arm_blocks, next); - assert( b.is_Terminal() || b.is_Subtree() ); - - if( m_default.is_Unset() ) { - if( b.is_Terminal() ) { - m_default = clone(b); - } - else { - m_default = clone(b.as_Subtree()->m_default); + if(has_default) + { + m_builder.set_cur_block(next); + } } - } - if( b.is_Subtree() && b.as_Subtree()->m_branches.tag() != m_branches.tag() ) { - // Is this a bug, or expected (and don't unify in?) - DEBUG("TODO - Unify mismatched arms? - " << b.as_Subtree()->m_branches.tag_str() << " and " << m_branches.tag_str()); - return ; - } - bool should_unify_subtree = b.is_Subtree() && this->m_field_path == b.as_Subtree()->m_field_path; - //if( b.is_Subtree() ) { - // ASSERT_BUG(Span(), this->m_field_path == b.as_Subtree()->m_field_path, "Unifiying DTNs with mismatched paths - " << this->m_field_path << " != " << b.as_Subtree()->m_field_path); - //} + // Collate matching blocks at `first_any` + assert(first_any == idx); + if( first_any < arm_rules.size() && arm_rules[idx].size() > ofs ) + { + // Collate all equal rules + while(idx < arm_rules.size() && arm_rules[idx][ofs] == arm_rules[first_any][ofs]) + idx ++; + DEBUG(first_any << "-" << idx << ": Multi-match"); - TU_MATCHA( (m_branches), (dst), - (Unset, - if( b.is_Subtree() ) { - assert( b.as_Subtree()->m_branches.is_Unset() ); - } - else { - // Huh? Terminal matching against an unset branch? - } - ), - (Bool, - auto* src = (b.is_Subtree() ? &b.as_Subtree()->m_branches.as_Bool() : nullptr); + bool has_next = idx < arm_rules.size(); + auto next = (has_next ? m_builder.new_bb_unlinked() : default_arm); - unify_branch( dst.false_branch, (src ? src->false_branch : b) ); - unify_branch( dst.true_branch , (src ? src->true_branch : b) ); - ), - (Variant, - if( should_unify_subtree ) { - auto& sb = b.as_Subtree()->m_branches; - ASSERT_BUG(Span(), sb.is_Variant(), "Unifying Variant with " << sb.tag_str()); - unify_from_vals_pt(dst, sb.as_Variant()); - } - else { - // Unify all with terminal branch - for(auto& dstv : dst) + const auto& rule = arm_rules[first_any][ofs]; + if(const auto* e = rule.opt_ValueRange()) { - unify_branch(dstv.second, b); + // Generate branch based on range + this->gen_dispatch_range(arm_rules[first_any][ofs].field_path, e->first, e->last, next); } - } - ), - (Unsigned, - if( should_unify_subtree ) { - auto& sb = b.as_Subtree()->m_branches; - ASSERT_BUG(Span(), sb.is_Unsigned(), "Unifying Unsigned with " << sb.tag_str()); - unify_from_vals_range(dst, sb.as_Unsigned()); - } - else { - for(auto& dstv : dst) + else if(const auto* e = rule.opt_SplitSlice()) { - unify_branch(dstv.second, b); + // Generate branch based on slice length being at least required. + this->gen_dispatch_splitslice(rule.field_path, *e, next); } - } - ), - (Signed, - if( should_unify_subtree ) { - auto& sb = b.as_Subtree()->m_branches; - ASSERT_BUG(Span(), sb.is_Signed(), "Unifying Signed with " << sb.tag_str()); - unify_from_vals_range(dst, sb.as_Signed()); - } - else { - for(auto& dstv : dst) + else { - unify_branch(dstv.second, b); - } - } - ), - (Float, - if( should_unify_subtree ) { - auto& sb = b.as_Subtree()->m_branches; - ASSERT_BUG(Span(), sb.is_Float(), "Unifying Float with " << sb.tag_str()); - unify_from_vals_range(dst, sb.as_Float()); - } - else { - for(auto& dstv : dst) { - unify_branch(dstv.second, b); - } - } - ), - (String, - if( should_unify_subtree ) { - auto& sb = b.as_Subtree()->m_branches; - ASSERT_BUG(Span(), sb.is_String(), "Unifying String with " << sb.tag_str()); - unify_from_vals_pt(dst, sb.as_String()); - } - else { - for(auto& dstv : dst) { - unify_branch( dstv.second, b ); + ASSERT_BUG(sp, rule.is_Any(), "Didn't expect non-Any rule here, got " << rule.tag_str() << " " << rule); } - } - ), - (Slice, - if( should_unify_subtree ) { - auto& sb = b.as_Subtree()->m_branches; - ASSERT_BUG(Span(), sb.is_Slice(), "Unifying Slice with " << sb.tag_str()); - const auto& src = sb.as_Slice(); - unify_from_vals_pt(dst.fixed_arms, src.fixed_arms); - } - else { - for(auto& dstv : dst.fixed_arms) { - unify_branch( dstv.second, b ); - } - } - ) - ) -} + // Step deeper into these arms + auto slice = arm_rules.sub_slice(first_any, idx - first_any); + this->gen_for_slice(mv$(slice), ofs+1, next); -::std::ostream& operator<<(::std::ostream& os, const DecisionTreeNode::Branch& x) { - TU_MATCHA( (x), (be), - (Unset, - os << "!"; - ), - (Terminal, - os << "ARM " << be; - ), - (Subtree, - os << *be; - ) - ) - return os; -} -::std::ostream& operator<<(::std::ostream& os, const DecisionTreeNode& x) { - os << "DTN [" << x.m_field_path << "] { "; - TU_MATCHA( (x.m_branches), (e), - (Unset, - os << "!, "; - ), - (Bool, - os << "false = " << e.false_branch << ", true = " << e.true_branch << ", "; - ), - (Variant, - os << "V "; - for(const auto& branch : e) { - os << branch.first << " = " << branch.second << ", "; - } - ), - (Unsigned, - os << "U "; - for(const auto& branch : e) { - const auto& range = branch.first; - if( range.start == range.end ) { - os << range.start; - } - else { - os << range.start << "..." << range.end; - } - os << " = " << branch.second << ", "; - } - ), - (Signed, - os << "S "; - for(const auto& branch : e) { - const auto& range = branch.first; - if( range.start == range.end ) { - os << range.start; - } - else { - os << range.start << "..." << range.end; - } - os << " = " << branch.second << ", "; - } - ), - (Float, - os << "F "; - for(const auto& branch : e) { - const auto& range = branch.first; - if( range.start == range.end ) { - os << range.start; - } - else { - os << range.start << "..." << range.end; + if(has_next) + { + m_builder.set_cur_block(next); } - os << " = " << branch.second << ", "; - } - ), - (String, - for(const auto& branch : e) { - os << "\"" << branch.first << "\"" << " = " << branch.second << ", "; - } - ), - (Slice, - os << "len "; - for(const auto& branch : e.fixed_arms) { - os << "=" << branch.first << " = " << branch.second << ", "; } - ) - ) + } - os << "* = " << x.m_default; - os << " }"; - return os; + ASSERT_BUG(sp, ! m_builder.block_active(), "Block left active after match group"); } - -// ---------------------------- -// DecisionTreeGen -// ---------------------------- - -void DecisionTreeGen::generate_tree_code( - const Span& sp, - const DecisionTreeNode& node, - const ::HIR::TypeRef& top_ty, unsigned int field_path_ofs, const ::MIR::LValue& top_val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) +void MatchGenGrouped::gen_dispatch(const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk) { - TRACE_FUNCTION_F("top_ty=" << top_ty << ", field_path_ofs=" << field_path_ofs << ", top_val=" << top_val << ", node=" << node); + const auto& field_path = rules[0][0][ofs].field_path; + TRACE_FUNCTION_F("rules=["<<rules <<"], ofs=" << ofs <<", field_path=" << field_path); + + { + size_t n = 0; + for(size_t i = 0; i < rules.size(); i++) + { + for(size_t j = 0; j < rules[i].size(); j++) + { + ASSERT_BUG(sp, rules[i][j][ofs].field_path == field_path, "Field path mismatch, " << rules[i][j][ofs].field_path << " != " << field_path); + n ++; + } + } + ASSERT_BUG(sp, arm_targets.size() == n, "Arm target count mismatch - " << n << " != " << arm_targets.size()); + } ::MIR::LValue val; ::HIR::TypeRef ty; - - get_ty_and_val(sp, m_builder.resolve(), top_ty, top_val, node.m_field_path, field_path_ofs, ty, val); + get_ty_and_val(sp, m_builder.resolve(), m_top_ty, m_top_val, field_path, m_field_path_ofs, ty, val); DEBUG("ty = " << ty << ", val = " << val); - TU_MATCHA( (ty.m_data), (e), - (Infer, BUG(sp, "Ivar for in match type"); ), - (Diverge, BUG(sp, "Diverge in match type"); ), - (Primitive, - switch(e) - { - case ::HIR::CoreType::Bool: - ASSERT_BUG(sp, node.m_branches.is_Bool(), "Tree for bool isn't a _Bool - node="<<node); - this->generate_branches_Bool(sp, node.m_default, node.m_branches.as_Bool(), ty, mv$(val), mv$(and_then)); - break; - case ::HIR::CoreType::U8: - case ::HIR::CoreType::U16: - case ::HIR::CoreType::U32: - case ::HIR::CoreType::U64: - case ::HIR::CoreType::U128: - case ::HIR::CoreType::Usize: - ASSERT_BUG(sp, node.m_branches.is_Unsigned(), "Tree for unsigned isn't a _Unsigned - node="<<node); - this->generate_branches_Unsigned(sp, node.m_default, node.m_branches.as_Unsigned(), ty, mv$(val), mv$(and_then)); - break; - case ::HIR::CoreType::I8: - case ::HIR::CoreType::I16: - case ::HIR::CoreType::I32: - case ::HIR::CoreType::I64: - case ::HIR::CoreType::I128: - case ::HIR::CoreType::Isize: - ASSERT_BUG(sp, node.m_branches.is_Signed(), "Tree for unsigned isn't a _Signed - node="<<node); - this->generate_branches_Signed(sp, node.m_default, node.m_branches.as_Signed(), ty, mv$(val), mv$(and_then)); - break; - case ::HIR::CoreType::Char: - ASSERT_BUG(sp, node.m_branches.is_Unsigned(), "Tree for char isn't a _Unsigned - node="<<node); - this->generate_branches_Char(sp, node.m_default, node.m_branches.as_Unsigned(), ty, mv$(val), mv$(and_then)); - break; - case ::HIR::CoreType::Str: - ASSERT_BUG(sp, node.m_branches.is_String(), "Tree for &str isn't a _String - node="<<node); - this->generate_branches_Borrow_str(sp, node.m_default, node.m_branches.as_String(), ty, mv$(val), mv$(and_then)); - break; - case ::HIR::CoreType::F32: - case ::HIR::CoreType::F64: - ASSERT_BUG(sp, node.m_branches.is_Float(), "Tree for float isn't a _Float - node="<<node); - this->generate_branches_Float(sp, node.m_default, node.m_branches.as_Float(), ty, mv$(val), mv$(and_then)); - break; - default: - TODO(sp, "Primitive - " << ty); - break; - } + TU_MATCHA( (ty.m_data), (te), + (Infer, + BUG(sp, "Hit _ in type - " << ty); ), - (Tuple, - BUG(sp, "Decision node on tuple - node=" << node); + (Diverge, + BUG(sp, "Matching over !"); + ), + (Primitive, + this->gen_dispatch__primitive(mv$(ty), mv$(val), rules, ofs, arm_targets, def_blk); ), (Path, - // This is either a struct destructure or an enum - TU_MATCHA( (e.binding), (pbe), - (Unbound, - BUG(sp, "Encounterd unbound path - " << e.path); - ), - (Opaque, - and_then(node); - ), - (Struct, - assert(pbe); - TU_MATCHA( (pbe->m_data), (fields), - (Unit, - and_then(node); + // Matching over a path can only happen with an enum. + // TODO: What about `box` destructures? + // - They're handled via hidden derefs + if( !te.binding.is_Enum() ) { + TU_MATCHA( (te.binding), (pbe), + (Unbound, + BUG(sp, "Encounterd unbound path - " << te.path); ), - (Tuple, - BUG(sp, "Decision node on tuple struct"); + (Opaque, + BUG(sp, "Attempting to match over opaque type - " << ty); ), - (Named, - BUG(sp, "Decision node on struct"); + (Struct, + const auto& str_data = pbe->m_data; + TU_MATCHA( (str_data), (sd), + (Unit, + BUG(sp, "Attempting to match over unit type - " << ty); + ), + (Tuple, + TODO(sp, "Matching on tuple-like struct?"); + ), + (Named, + TODO(sp, "Matching on struct?"); + ) + ) + ), + (Union, + TODO(sp, "Match over Union"); + ), + (Enum, ) ) - ), - (Union, - TODO(sp, "Decision node on Union"); - ), - (Enum, - ASSERT_BUG(sp, node.m_branches.is_Variant(), "Tree for enum isn't a Variant - node="<<node); - assert(pbe); - this->generate_branches_Enum(sp, node.m_default, node.m_branches.as_Variant(), node.m_field_path, ty, mv$(val), mv$(and_then)); - ) - ) + } + + this->gen_dispatch__enum(mv$(ty), mv$(val), rules, ofs, arm_targets, def_blk); ), (Generic, - and_then(node); + BUG(sp, "Attempting to match a generic"); ), (TraitObject, - ERROR(sp, E0000, "Attempting to match over a trait object"); + BUG(sp, "Attempting to match a trait object"); ), (ErasedType, - ERROR(sp, E0000, "Attempting to match over an erased type"); + BUG(sp, "Attempting to match an erased type"); ), (Array, - // TODO: Slice patterns, sequential comparison/sub-match - TODO(sp, "Match over array"); + BUG(sp, "Attempting to match on an Array (should have been destructured)"); ), (Slice, - ASSERT_BUG(sp, node.m_branches.is_Slice(), "Tree for [T] isn't a _Slice - node="<<node); - this->generate_branches_Slice(sp, node.m_default, node.m_branches.as_Slice(), node.m_field_path, ty, mv$(val), mv$(and_then)); + // TODO: Slice size matches! + this->gen_dispatch__slice(mv$(ty), mv$(val), rules, ofs, arm_targets, def_blk); + ), + (Tuple, + BUG(sp, "Match directly on tuple"); ), (Borrow, - if( *e.inner == ::HIR::CoreType::Str ) { - TODO(sp, "Match over &str"); - } - else { - BUG(sp, "Decision node on non-str/[T] borrow - " << ty); - } + BUG(sp, "Match directly on borrow"); ), (Pointer, - ERROR(sp, E0000, "Attempting to match over a pointer"); + // TODO: Could this actually be valid? + BUG(sp, "Attempting to match a pointer - " << ty); ), (Function, - ERROR(sp, E0000, "Attempting to match over a functon pointer"); + // TODO: Could this actually be valid? + BUG(sp, "Attempting to match a function pointer - " << ty); ), (Closure, - ERROR(sp, E0000, "Attempting to match over a closure"); + BUG(sp, "Attempting to match a closure"); ) ) } -void DecisionTreeGen::generate_branch(const DecisionTreeNode::Branch& branch, ::std::function<void(const DecisionTreeNode&)> cb) -{ - assert( !branch.is_Unset() ); - if( branch.is_Terminal() ) { - this->m_builder.end_block( ::MIR::Terminator::make_Goto( this->get_block_for_rule( branch.as_Terminal() ) ) ); - } - else { - assert( branch.is_Subtree() ); - const auto& subnode = *branch.as_Subtree(); - - cb(subnode); - } -} - -::MIR::LValue DecisionTreeGen::push_compare(const Span& sp, ::MIR::LValue left, ::MIR::eBinOp op, ::MIR::Param right) -{ - return m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, - ::MIR::RValue::make_BinOp({ mv$(left), op, mv$(right) }) - ); -} - -// TODO: Unify logic for these two, and support simpler checks for sequential values -void DecisionTreeGen::generate_branches_Signed( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Signed& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) -{ - auto ity = ty.m_data.as_Primitive(); - auto default_block = m_builder.new_bb_unlinked(); - - // TODO: Convert into an integer switch w/ offset instead of chained comparisons - - for( const auto& branch : branches ) - { - auto next_block = (&branch == &branches.back() ? default_block : m_builder.new_bb_unlinked()); - - auto cmp_gt_block = m_builder.new_bb_unlinked(); - auto val_cmp_lt = push_compare(sp, val.clone(), ::MIR::eBinOp::LT, ::MIR::Constant::make_Int({ branch.first.start, ity })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_lt), default_block, cmp_gt_block }) ); - m_builder.set_cur_block( cmp_gt_block ); - - auto success_block = m_builder.new_bb_unlinked(); - auto val_cmp_gt = push_compare(sp, val.clone(), ::MIR::eBinOp::GT, ::MIR::Constant::make_Int({ branch.first.end, ity })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_gt), next_block, success_block }) ); - - m_builder.set_cur_block( success_block ); - this->generate_branch(branch.second, and_then); - - m_builder.set_cur_block( next_block ); - } - assert( m_builder.block_active() ); - - if( default_branch.is_Unset() ) { - // TODO: Emit error if non-exhaustive - m_builder.end_block( ::MIR::Terminator::make_Diverge({}) ); - } - else { - this->generate_branch(default_branch, and_then); - } -} - -void DecisionTreeGen::generate_branches_Unsigned( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Unsigned& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) -{ - auto ity = ty.m_data.as_Primitive(); - auto default_block = m_builder.new_bb_unlinked(); - - // TODO: Convert into an integer switch w/ offset instead of chained comparisons - - for( const auto& branch : branches ) - { - auto next_block = (&branch == &branches.back() ? default_block : m_builder.new_bb_unlinked()); - - auto cmp_gt_block = m_builder.new_bb_unlinked(); - auto val_cmp_lt = push_compare(sp, val.clone(), ::MIR::eBinOp::LT, ::MIR::Constant::make_Uint({ branch.first.start, ity })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_lt), default_block, cmp_gt_block }) ); - m_builder.set_cur_block( cmp_gt_block ); - - auto success_block = m_builder.new_bb_unlinked(); - auto val_cmp_gt = push_compare(sp, val.clone(), ::MIR::eBinOp::GT, ::MIR::Constant::make_Uint({ branch.first.end, ity })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_gt), next_block, success_block }) ); - - m_builder.set_cur_block( success_block ); - this->generate_branch(branch.second, and_then); - - m_builder.set_cur_block( next_block ); - } - assert( m_builder.block_active() ); - - if( default_branch.is_Unset() ) { - // TODO: Emit error if non-exhaustive - m_builder.end_block( ::MIR::Terminator::make_Diverge({}) ); - } - else { - this->generate_branch(default_branch, and_then); - } -} - -void DecisionTreeGen::generate_branches_Float( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Float& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) -{ - auto ity = ty.m_data.as_Primitive(); - auto default_block = m_builder.new_bb_unlinked(); - - for( const auto& branch : branches ) - { - auto next_block = (&branch == &branches.back() ? default_block : m_builder.new_bb_unlinked()); - - auto cmp_gt_block = m_builder.new_bb_unlinked(); - auto val_cmp_lt = push_compare(sp, val.clone(), ::MIR::eBinOp::LT, ::MIR::Constant::make_Float({ branch.first.start, ity })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_lt), default_block, cmp_gt_block }) ); - m_builder.set_cur_block( cmp_gt_block ); - - auto success_block = m_builder.new_bb_unlinked(); - auto val_cmp_gt = push_compare(sp, val.clone(), ::MIR::eBinOp::GT, ::MIR::Constant::make_Float({ branch.first.end, ity })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_gt), next_block, success_block }) ); - - m_builder.set_cur_block( success_block ); - this->generate_branch(branch.second, and_then); - - m_builder.set_cur_block( next_block ); - } - assert( m_builder.block_active() ); - - if( default_branch.is_Unset() ) { - ERROR(sp, E0000, "Match over floating point with no `_` arm"); - } - else { - this->generate_branch(default_branch, and_then); - } -} - -void DecisionTreeGen::generate_branches_Char( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Unsigned& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) +void MatchGenGrouped::gen_dispatch__primitive(::HIR::TypeRef ty, ::MIR::LValue val, const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk) { - auto default_block = m_builder.new_bb_unlinked(); - - // TODO: Convert into an integer switch w/ offset instead of chained comparisons - - for( const auto& branch : branches ) + auto te = ty.m_data.as_Primitive(); + switch(te) { - auto next_block = (&branch == &branches.back() ? default_block : m_builder.new_bb_unlinked()); - - auto cmp_gt_block = m_builder.new_bb_unlinked(); - auto val_cmp_lt = push_compare(sp, val.clone(), ::MIR::eBinOp::LT, ::MIR::Constant::make_Uint({ branch.first.start, ::HIR::CoreType::Char })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_lt), default_block, cmp_gt_block }) ); - m_builder.set_cur_block( cmp_gt_block ); - - auto success_block = m_builder.new_bb_unlinked(); - auto val_cmp_gt = push_compare(sp, val.clone(), ::MIR::eBinOp::GT, ::MIR::Constant::make_Uint({ branch.first.end, ::HIR::CoreType::Char })); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_gt), next_block, success_block }) ); + case ::HIR::CoreType::Bool: { + ASSERT_BUG(sp, rules.size() <= 2, "More than 2 rules for boolean"); + for(size_t i = 0; i < rules.size(); i++) + { + ASSERT_BUG(sp, rules[i][0][ofs].is_Bool(), "PatternRule for bool isn't _Bool"); + } + + // False sorts before true. + auto fail_bb = rules.size() == 2 ? arm_targets[ 0] : (rules[0][0][ofs].as_Bool() ? def_blk : arm_targets[0]); + auto succ_bb = rules.size() == 2 ? arm_targets[rules[0].size()] : (rules[0][0][ofs].as_Bool() ? arm_targets[0] : def_blk); + + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val), succ_bb, fail_bb }) ); + } break; + case ::HIR::CoreType::U8: + case ::HIR::CoreType::U16: + case ::HIR::CoreType::U32: + case ::HIR::CoreType::U64: + case ::HIR::CoreType::U128: + case ::HIR::CoreType::Usize: + + case ::HIR::CoreType::I8: + case ::HIR::CoreType::I16: + case ::HIR::CoreType::I32: + case ::HIR::CoreType::I64: + case ::HIR::CoreType::I128: + case ::HIR::CoreType::Isize: + + case ::HIR::CoreType::Char: + if( rules.size() == 1 ) + { + // Special case, single option, equality only + const auto& r = rules[0][0][ofs]; + ASSERT_BUG(sp, r.is_Value(), "Matching without _Value pattern - " << r.tag_str()); + const auto& re = r.as_Value(); + auto test_val = ::MIR::Param(re.clone()); + auto cmp_lval = m_builder.get_rval_in_if_cond(sp, ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::EQ, mv$(test_val) })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval), arm_targets[0], def_blk }) ); + } + else + { + // TODO: Add a SwitchInt terminator for use with this. (Or just a SwitchVal terminator?) - m_builder.set_cur_block( success_block ); - this->generate_branch(branch.second, and_then); + // NOTE: Rules are currently sorted + // TODO: If there are Constant::Const values in the list, they need to come first! (with equality checks) - m_builder.set_cur_block( next_block ); - } - assert( m_builder.block_active() ); + // Does a sorted linear search. Binary search would be nicer but is harder to implement. + size_t tgt_ofs = 0; + for(size_t i = 0; i < rules.size(); i++) + { + for(size_t j = 1; j < rules[i].size(); j ++) + ASSERT_BUG(sp, arm_targets[tgt_ofs] == arm_targets[tgt_ofs+j], "Mismatched target blocks for Value match"); + + const auto& r = rules[i][0][ofs]; + ASSERT_BUG(sp, r.is_Value(), "Matching without _Value pattern - " << r.tag_str()); + const auto& re = r.as_Value(); + if(re.is_Const()) + TODO(sp, "Handle Constant::Const in match"); + + // IF v < tst : def_blk + // Skip if the previous value was the imediat predecesor + bool is_succ = i != 0 && (re.is_Uint() + ? re.as_Uint().v == rules[i-1][0][ofs].as_Value().as_Uint().v + 1 + : re.as_Int().v == rules[i-1][0][ofs].as_Value().as_Int().v + 1 + ); + if( !is_succ ) + { + auto cmp_eq_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_lt = this->push_compare(val.clone(), ::MIR::eBinOp::LT, ::MIR::Param(re.clone())); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_lt), def_blk, cmp_eq_blk }) ); + m_builder.set_cur_block(cmp_eq_blk); + } - if( default_branch.is_Unset() ) { - // TODO: Error if not exhaustive. - m_builder.end_block( ::MIR::Terminator::make_Diverge({}) ); - } - else { - this->generate_branch(default_branch, and_then); - } -} -void DecisionTreeGen::generate_branches_Bool( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Bool& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) -{ - //assert( ty.m_data.is_Boolean() ); + // IF v == tst : target + { + auto next_cmp_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_eq = this->push_compare( val.clone(), ::MIR::eBinOp::EQ, ::MIR::Param(re.clone()) ); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_eq), arm_targets[tgt_ofs], next_cmp_blk }) ); + m_builder.set_cur_block(next_cmp_blk); + } - if( default_branch.is_Unset() ) - { - if( branches.false_branch.is_Unset() || branches.true_branch.is_Unset() ) { - // Non-exhaustive match - ERROR - } - } - else - { - if( branches.false_branch.is_Unset() && branches.true_branch.is_Unset() ) { - // Unreachable default (NOTE: Not an error here) + tgt_ofs += rules[i].size(); + } + m_builder.end_block( ::MIR::Terminator::make_Goto(def_blk) ); } - } - - // Emit an if based on the route taken - auto bb_false = m_builder.new_bb_unlinked(); - auto bb_true = m_builder.new_bb_unlinked(); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val), bb_true, bb_false }) ); + break; + case ::HIR::CoreType::F32: + case ::HIR::CoreType::F64: { + // NOTE: Rules are currently sorted + // TODO: If there are Constant::Const values in the list, they need to come first! + size_t tgt_ofs = 0; + for(size_t i = 0; i < rules.size(); i++) + { + for(size_t j = 1; j < rules[i].size(); j ++) + ASSERT_BUG(sp, arm_targets[tgt_ofs] == arm_targets[tgt_ofs+j], "Mismatched target blocks for Value match"); - // Recurse into sub-patterns - const auto& branch_false = ( !branches.false_branch.is_Unset() ? branches.false_branch : default_branch ); - const auto& branch_true = ( !branches. true_branch.is_Unset() ? branches. true_branch : default_branch ); + const auto& r = rules[i][0][ofs]; + ASSERT_BUG(sp, r.is_Value(), "Matching without _Value pattern - " << r.tag_str()); + const auto& re = r.as_Value(); + if(re.is_Const()) + TODO(sp, "Handle Constant::Const in match"); - m_builder.set_cur_block(bb_true ); - this->generate_branch(branch_true , and_then); - m_builder.set_cur_block(bb_false); - this->generate_branch(branch_false, and_then); -} - -void DecisionTreeGen::generate_branches_Borrow_str( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_String& branches, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) -{ - // TODO: Chained comparisons with ordering. - // - Would this just emit a eBinOp? That implies deep codegen support for strings. - // - rustc emits calls to PartialEq::eq for this and for slices. mrustc could use PartialOrd and fall back to PartialEq if unavaliable? - // > Requires crate access here! - A memcmp call is probably better, probably via a binop - // NOTE: The below implementation gets the final codegen to call memcmp on the strings by emitting eBinOp::{LT,GT} - - // - Remove the wrapping Deref (which must be there) - ASSERT_BUG(sp, val.is_Deref(), "Match over str without a deref - " << val); - auto tmp = mv$( *val.as_Deref().val ); - val = mv$(tmp); - - auto default_bb = m_builder.new_bb_unlinked(); - - // TODO: Binary search? Perfect-Hash-Function? - assert( !branches.empty() ); - for(const auto& branch : branches) - { - auto next_bb = (&branch == &branches.back() ? default_bb : m_builder.new_bb_unlinked()); + // IF v < tst : def_blk + { + auto cmp_eq_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_lt = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::LT, ::MIR::Param(re.clone()) })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_lt), def_blk, cmp_eq_blk }) ); + m_builder.set_cur_block(cmp_eq_blk); + } - auto cmp_gt_bb = m_builder.new_bb_unlinked(); + // IF v == tst : target + { + auto next_cmp_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_eq = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::EQ, ::MIR::Param(re.clone()) })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_eq), arm_targets[tgt_ofs], next_cmp_blk }) ); + m_builder.set_cur_block(next_cmp_blk); + } + + tgt_ofs += rules[i].size(); + } + m_builder.end_block( ::MIR::Terminator::make_Goto(def_blk) ); + } break; + case ::HIR::CoreType::Str: + // Remove the deref on the &str + auto oval = mv$(val); + auto val = mv$(*oval.as_Deref().val); + // NOTE: Rules are currently sorted + // TODO: If there are Constant::Const values in the list, they need to come first! + size_t tgt_ofs = 0; + for(size_t i = 0; i < rules.size(); i++) + { + for(size_t j = 1; j < rules[i].size(); j ++) + ASSERT_BUG(sp, arm_targets[tgt_ofs] == arm_targets[tgt_ofs+j], "Mismatched target blocks for Value match"); - auto lt_val = push_compare(sp, val.clone(), ::MIR::eBinOp::LT, ::MIR::Constant(branch.first) ); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(lt_val), default_bb, cmp_gt_bb }) ); - m_builder.set_cur_block(cmp_gt_bb); + const auto& r = rules[i][0][ofs]; + ASSERT_BUG(sp, r.is_Value(), "Matching without _Value pattern - " << r.tag_str()); + const auto& re = r.as_Value(); + if(re.is_Const()) + TODO(sp, "Handle Constant::Const in match"); - auto eq_bb = m_builder.new_bb_unlinked(); - auto gt_val = push_compare(sp, val.clone(), ::MIR::eBinOp::GT, ::MIR::Constant(branch.first) ); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(gt_val), next_bb, eq_bb }) ); - m_builder.set_cur_block(eq_bb); + // IF v < tst : def_blk + { + auto cmp_eq_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_lt = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::LT, ::MIR::Param(re.clone()) })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_lt), def_blk, cmp_eq_blk }) ); + m_builder.set_cur_block(cmp_eq_blk); + } - this->generate_branch(branch.second, and_then); + // IF v == tst : target + { + auto next_cmp_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_eq = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ val.clone(), ::MIR::eBinOp::EQ, ::MIR::Param(re.clone()) })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_eq), arm_targets[tgt_ofs], next_cmp_blk }) ); + m_builder.set_cur_block(next_cmp_blk); + } - m_builder.set_cur_block(next_bb); + tgt_ofs += rules[i].size(); + } + m_builder.end_block( ::MIR::Terminator::make_Goto(def_blk) ); + break; } - this->generate_branch(default_branch, and_then); } -void DecisionTreeGen::generate_branches_Enum( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Variant& branches, - const field_path_t& field_path, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) +void MatchGenGrouped::gen_dispatch__enum(::HIR::TypeRef ty, ::MIR::LValue val, const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk) { - const auto& enum_ref = *ty.m_data.as_Path().binding.as_Enum(); - const auto& enum_path = ty.m_data.as_Path().path.m_data.as_Generic(); - const auto& variants = enum_ref.m_variants; - auto variant_count = variants.size(); - bool has_any = ! default_branch.is_Unset(); - - if( branches.size() < variant_count && ! has_any ) { - ERROR(sp, E0000, "Non-exhaustive match over " << ty << " - " << branches.size() << " out of " << variant_count << " present"); - } - // DISABLED: Some complex matches don't directly use some defaults - //if( branches.size() == variant_count && has_any ) { - // ERROR(sp, E0000, "Unreachable _ arm - " << branches.size() << " variants in " << enum_path); - //} - - auto any_block = (has_any ? m_builder.new_bb_unlinked() : 0); - - // Emit a switch over the variant - ::std::vector< ::MIR::BasicBlockId> variant_blocks; - variant_blocks.reserve( variant_count ); - for( const auto& branch : branches ) + TRACE_FUNCTION; + auto& te = ty.m_data.as_Path(); + const auto& pbe = te.binding.as_Enum(); + + auto decison_arm = m_builder.pause_cur_block(); + + auto monomorph = [&](const auto& ty) { + auto rv = monomorphise_type(sp, pbe->m_params, te.path.m_data.as_Generic().m_params, ty); + m_builder.resolve().expand_associated_types(sp, rv); + return rv; + }; + auto var_count = pbe->m_variants.size(); + size_t arm_idx = 0; + ::std::vector< ::MIR::BasicBlockId> arms(var_count, def_blk); + for(size_t i = 0; i < rules.size(); i ++) { - if( variant_blocks.size() != branch.first ) { - assert( variant_blocks.size() < branch.first ); - assert( has_any ); - variant_blocks.resize( branch.first, any_block ); + ASSERT_BUG(sp, rules[i][0][ofs].is_Variant(), "Rule for enum isn't Any or Variant - " << rules[i][0][ofs].tag_str()); + const auto& re = rules[i][0][ofs].as_Variant(); + unsigned int var_idx = re.idx; + arms[var_idx] = m_builder.new_bb_unlinked(); + DEBUG("Variant " << var_idx); + + // Create new rule collection based on the variants + t_rules_subset inner_set { rules[i].size(), /*is_arm_indexes=*/false }; + for(size_t j = 0; j < rules[i].size(); j ++) + { + const auto& r = rules[i][j]; + inner_set.push_bb(r[ofs].as_Variant().sub_rules, arm_targets[arm_idx]); + arm_idx ++; } - variant_blocks.push_back( m_builder.new_bb_unlinked() ); - } - if( variant_blocks.size() != variant_count ) - { - ASSERT_BUG(sp, variant_blocks.size() < variant_count, "Branch count (" << variant_blocks.size() << ") > variant count (" << variant_count << ") in match of " << ty); - ASSERT_BUG(sp, has_any, "Non-exhaustive match and no any arm"); - variant_blocks.resize( variant_count, any_block ); - } - bool any_arm_used = ::std::any_of( variant_blocks.begin(), variant_blocks.end(), [any_block](const auto& blk){ return blk == any_block; } ); + ::HIR::TypeRef fake_tup; - m_builder.end_block( ::MIR::Terminator::make_Switch({ - val.clone(), variant_blocks // NOTE: Copies the list, so it can be used lower down - }) ); - - // Emit sub-patterns, looping over variants - for( const auto& branch : branches ) - { - auto bb = variant_blocks[branch.first]; - const auto& var = variants[branch.first]; - DEBUG(branch.first << " " << var.first << " = " << branch.second); - - auto var_lval = ::MIR::LValue::make_Downcast({ box$(val.clone()), branch.first }); - - ::HIR::TypeRef fake_ty; - - TU_MATCHA( (var.second), (e), + const auto& var_data = pbe->m_variants.at(re.idx).second; + TU_MATCHA( (var_data), (ve), (Unit, - DEBUG("- Unit"); + // Nothing to recurse ), (Value, - DEBUG("- Value"); + // Nothing to recurse ), (Tuple, - // Make a fake tuple - ::std::vector< ::HIR::TypeRef> ents; - for( const auto& fld : e ) + // Create a dummy tuple to contain the inner types. + ::std::vector< ::HIR::TypeRef> fake_ty_ents; + fake_ty_ents.reserve( ve.size() ); + for(unsigned int i = 0; i < ve.size(); i ++) { - ents.push_back( monomorphise_type(sp, enum_ref.m_params, enum_path.m_params, fld.ent) ); + fake_ty_ents.push_back( monomorph(ve[i].ent) ); } - fake_ty = ::HIR::TypeRef( mv$(ents) ); - m_builder.resolve().expand_associated_types(sp, fake_ty); - DEBUG("- Tuple - " << fake_ty); + fake_tup = ::HIR::TypeRef( mv$(fake_ty_ents) ); ), (Struct, - ::std::vector< ::HIR::TypeRef> ents; - for( const auto& fld : e ) + // Create a dummy tuple to contain the inner types. + ::std::vector< ::HIR::TypeRef> fake_ty_ents; + fake_ty_ents.reserve( ve.size() ); + for(unsigned int i = 0; i < ve.size(); i ++) { - ents.push_back( monomorphise_type(sp, enum_ref.m_params, enum_path.m_params, fld.second.ent) ); + fake_ty_ents.push_back( monomorph(ve[i].second.ent) ); } - fake_ty = ::HIR::TypeRef( mv$(ents) ); - m_builder.resolve().expand_associated_types(sp, fake_ty); - DEBUG("- Struct - " << fake_ty); + fake_tup = ::HIR::TypeRef( mv$(fake_ty_ents) ); ) ) - m_builder.set_cur_block( bb ); - if( fake_ty == ::HIR::TypeRef() || fake_ty.m_data.as_Tuple().size() == 0 ) { - this->generate_branch(branch.second, and_then); - } - else { - this->generate_branch(branch.second, [&](auto& subnode) { - // Call special handler to determine when the enum is over - this->generate_tree_code__enum(sp, subnode, fake_ty, var_lval, field_path, and_then); - }); - } - } + m_builder.set_cur_block(arms[var_idx]); + // Recurse with the new ruleset + // - On success, these should jump to targets[i] - if( any_arm_used ) - { - DEBUG("_ = " << default_branch); - if( !default_branch.is_Unset() ) - { - m_builder.set_cur_block(any_block); - this->generate_branch(default_branch, and_then); - } - } - else - { - DEBUG("_ = UNUSED - " << default_branch); + auto new_lval = ::MIR::LValue::make_Downcast({ box$(val.clone()), var_idx }); + auto inst = MatchGenGrouped { m_builder, sp, fake_tup, new_lval, {}, rules[0][0][ofs].field_path.size() }; + inst.gen_for_slice(inner_set, 0, def_blk); + ASSERT_BUG(sp, ! m_builder.block_active(), "Block still active after enum match generation"); } + + m_builder.set_cur_block(decison_arm); + m_builder.end_block( ::MIR::Terminator::make_Switch({ mv$(val), mv$(arms) }) ); } -void DecisionTreeGen::generate_branches_Slice( - const Span& sp, - const DecisionTreeNode::Branch& default_branch, - const DecisionTreeNode::Values::Data_Slice& branches, - const field_path_t& field_path, - const ::HIR::TypeRef& ty, ::MIR::LValue val, - ::std::function<void(const DecisionTreeNode&)> and_then - ) +void MatchGenGrouped::gen_dispatch__slice(::HIR::TypeRef ty, ::MIR::LValue val, const ::std::vector<t_rules_subset>& rules, size_t ofs, const ::std::vector<::MIR::BasicBlockId>& arm_targets, ::MIR::BasicBlockId def_blk) { - if( default_branch.is_Unset() ) { - ERROR(sp, E0000, "Non-exhaustive match over " << ty); - } - auto val_len = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Usize, ::MIR::RValue::make_DstMeta({ m_builder.get_ptr_to_dst(sp, val).clone() })); - // NOTE: Un-deref the slice - ASSERT_BUG(sp, val.is_Deref(), "slice matches must be passed a deref"); - auto tmp = mv$( *val.as_Deref().val ); - val = mv$(tmp); + // TODO: Re-sort the rules list to interleve Constant::Bytes and Slice - auto any_block = m_builder.new_bb_unlinked(); - - // TODO: Select one of three ways of picking the arm: - // - Integer switch (unimplemented) - // - Binary search - // - Sequential comparisons - - // TODO: Binary search instead? - for( const auto& branch : branches.fixed_arms ) + // Just needs to check the lengths, then dispatch. + size_t tgt_ofs = 0; + for(size_t i = 0; i < rules.size(); i++) { - auto val_des = ::MIR::Constant::make_Uint({ static_cast<uint64_t>(branch.first), ::HIR::CoreType::Usize }); + //for(size_t j = 1; j < rules[i].size(); j ++) + // ASSERT_BUG(sp, arm_targets[tgt_ofs] == arm_targets[tgt_ofs+j], "Mismatched target blocks for Slice match"); - // Special case - final just does equality - if( &branch == &branches.fixed_arms.back() ) + const auto& r = rules[i][0][ofs]; + if(const auto* re = r.opt_Slice()) { - auto val_cmp_eq = push_compare(sp, val_len.clone(), ::MIR::eBinOp::EQ, mv$(val_des)); + auto val_tst = ::MIR::Constant::make_Uint({ re->len, ::HIR::CoreType::Usize }); - auto success_block = m_builder.new_bb_unlinked(); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_eq), success_block, any_block }) ); + // IF v < tst : target + if( re->len > 0 ) + { + auto cmp_eq_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_lt = this->push_compare( val_len.clone(), ::MIR::eBinOp::LT, val_tst.clone() ); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_lt), def_blk, cmp_eq_blk }) ); + m_builder.set_cur_block(cmp_eq_blk); + } - m_builder.set_cur_block( success_block ); - this->generate_branch(branch.second, and_then); + // IF v == tst : target + { + auto succ_blk = m_builder.new_bb_unlinked(); + auto next_cmp_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_eq = this->push_compare( val_len.clone(), ::MIR::eBinOp::EQ, mv$(val_tst) ); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_eq), succ_blk, next_cmp_blk }) ); + m_builder.set_cur_block(succ_blk); + + // + t_rules_subset inner_set { rules[i].size(), /*is_arm_indexes=*/false }; + for(size_t j = 0; j < rules[i].size(); j ++) + { + const auto& r = rules[i][j]; + inner_set.push_bb(r[ofs].as_Slice().sub_rules, arm_targets[tgt_ofs + j]); + } + auto inst = MatchGenGrouped { m_builder, sp, ty, val, {}, rules[0][0][ofs].field_path.size() }; + inst.gen_for_slice(inner_set, 0, def_blk); - m_builder.set_cur_block( any_block ); + m_builder.set_cur_block(next_cmp_blk); + } } - // Special case for zero (which can't have a LT) - else if( branch.first == 0 ) + else if(const auto* re = r.opt_Value()) { - auto next_block = m_builder.new_bb_unlinked(); - auto val_cmp_eq = push_compare(sp, val_len.clone(), ::MIR::eBinOp::EQ, mv$(val_des)); + ASSERT_BUG(sp, re->is_Bytes(), "Slice with non-Bytes value - " << *re); + const auto& b = re->as_Bytes(); + + auto val_tst = ::MIR::Constant::make_Uint({ b.size(), ::HIR::CoreType::Usize }); + auto cmp_slice_val = m_builder.lvalue_or_temp(sp, + ::HIR::TypeRef::new_borrow( ::HIR::BorrowType::Shared, ::HIR::TypeRef::new_slice(::HIR::CoreType::U8) ), + ::MIR::RValue::make_MakeDst({ ::MIR::Param(re->clone()), val_tst.clone() }) + ); + + if( b.size() > 0 ) + { + auto cmp_eq_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_lt = this->push_compare( val_len.clone(), ::MIR::eBinOp::LT, val_tst.clone() ); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_lt), def_blk, cmp_eq_blk }) ); + m_builder.set_cur_block(cmp_eq_blk); + } - auto success_block = m_builder.new_bb_unlinked(); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_eq), success_block, next_block }) ); + // IF v == tst : target + { + auto succ_blk = m_builder.new_bb_unlinked(); + auto next_cmp_blk = m_builder.new_bb_unlinked(); + auto cmp_lval_eq = this->push_compare( val_len.clone(), ::MIR::eBinOp::EQ, mv$(val_tst) ); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_eq), succ_blk, next_cmp_blk }) ); + m_builder.set_cur_block(succ_blk); - m_builder.set_cur_block( success_block ); - this->generate_branch(branch.second, and_then); + // TODO: What if `val` isn't a Deref? + ASSERT_BUG(sp, val.is_Deref(), "TODO: Handle non-Deref matches of byte strings"); + cmp_lval_eq = this->push_compare( val.as_Deref().val->clone(), ::MIR::eBinOp::EQ, mv$(cmp_slice_val) ); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lval_eq), arm_targets[tgt_ofs], def_blk }) ); - m_builder.set_cur_block( next_block ); + m_builder.set_cur_block(next_cmp_blk); + } } - // General case, with two comparisons else { - auto next_block = m_builder.new_bb_unlinked(); - - auto cmp_gt_block = m_builder.new_bb_unlinked(); - auto val_cmp_lt = push_compare(sp, val_len.clone(), ::MIR::eBinOp::LT, val_des.clone()); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_lt), any_block, cmp_gt_block }) ); - m_builder.set_cur_block( cmp_gt_block ); - - auto success_block = m_builder.new_bb_unlinked(); - auto val_cmp_gt = push_compare(sp, val_len.clone(), ::MIR::eBinOp::GT, mv$(val_des)); - m_builder.end_block( ::MIR::Terminator::make_If({ mv$(val_cmp_gt), next_block, success_block }) ); - - m_builder.set_cur_block( success_block ); - this->generate_branch(branch.second, and_then); - - m_builder.set_cur_block( next_block ); + BUG(sp, "Matching without _Slice pattern - " << r.tag_str() << " - " << r); } - } - assert( m_builder.block_active() ); - if( default_branch.is_Unset() ) { - // TODO: Emit error if non-exhaustive - m_builder.end_block( ::MIR::Terminator::make_Diverge({}) ); - } - else { - this->generate_branch(default_branch, and_then); + tgt_ofs += rules[i].size(); } + m_builder.end_block( ::MIR::Terminator::make_Goto(def_blk) ); } -namespace { - bool path_starts_with(const field_path_t& test, const field_path_t& prefix) + +void MatchGenGrouped::gen_dispatch_range(const field_path_t& field_path, const ::MIR::Constant& first, const ::MIR::Constant& last, ::MIR::BasicBlockId def_blk) +{ + TRACE_FUNCTION_F("field_path="<<field_path<<", " << first << " ... " << last); + ::MIR::LValue val; + ::HIR::TypeRef ty; + get_ty_and_val(sp, m_builder.resolve(), m_top_ty, m_top_val, field_path, m_field_path_ofs, ty, val); + DEBUG("ty = " << ty << ", val = " << val); + + if( const auto* tep = ty.m_data.opt_Primitive() ) { - //DEBUG("test="<<test<<", prefix="<<prefix); - if( test.size() < prefix.size() ) + auto te = *tep; + + bool lower_possible = true; + bool upper_possible = true; + + switch(te) { - return false; + case ::HIR::CoreType::Bool: + BUG(sp, "Range match over Bool"); + break; + case ::HIR::CoreType::Str: + BUG(sp, "Range match over Str - is this valid?"); + break; + case ::HIR::CoreType::U8: + case ::HIR::CoreType::U16: + case ::HIR::CoreType::U32: + case ::HIR::CoreType::U64: + case ::HIR::CoreType::U128: + case ::HIR::CoreType::Usize: + lower_possible = (first.as_Uint().v > 0); + // TODO: Should this also check for the end being the max value of the type? + // - Can just leave that to the optimiser + upper_possible = true; + break; + case ::HIR::CoreType::I8: + case ::HIR::CoreType::I16: + case ::HIR::CoreType::I32: + case ::HIR::CoreType::I64: + case ::HIR::CoreType::I128: + case ::HIR::CoreType::Isize: + lower_possible = true; + upper_possible = true; + break; + case ::HIR::CoreType::Char: + lower_possible = (first.as_Uint().v > 0); + upper_possible = (first.as_Uint().v <= 0x10FFFF); + break; + case ::HIR::CoreType::F32: + case ::HIR::CoreType::F64: + // NOTE: No upper or lower limits + break; } - else if( ! ::std::equal(prefix.data.begin(), prefix.data.end(), test.data.begin()) ) + + if( lower_possible ) { - return false; + auto test_bb_2 = m_builder.new_bb_unlinked(); + // IF `val` < `first` : fail_bb + auto cmp_lt_lval = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ ::MIR::Param(val.clone()), ::MIR::eBinOp::LT, ::MIR::Param(first.clone()) })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_lt_lval), def_blk, test_bb_2 }) ); + + m_builder.set_cur_block(test_bb_2); } - else + + + if( upper_possible ) { - return true; + auto succ_bb = m_builder.new_bb_unlinked(); + + // IF `val` > `last` : fail_bb + auto cmp_gt_lval = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Bool, ::MIR::RValue::make_BinOp({ ::MIR::Param(val.clone()), ::MIR::eBinOp::GT, ::MIR::Param(last.clone()) })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_gt_lval), def_blk, succ_bb }) ); + + m_builder.set_cur_block(succ_bb); } } + else + { + TODO(sp, "ValueRange on " << ty); + } } - -void DecisionTreeGen::generate_tree_code__enum( - const Span& sp, - const DecisionTreeNode& node, const ::HIR::TypeRef& fake_ty, const ::MIR::LValue& val, - const field_path_t& path_prefix, - ::std::function<void(const DecisionTreeNode&)> and_then - ) +void MatchGenGrouped::gen_dispatch_splitslice(const field_path_t& field_path, const PatternRule::Data_SplitSlice& e, ::MIR::BasicBlockId def_blk) { - if( ! path_starts_with(node.m_field_path, path_prefix) ) + TRACE_FUNCTION_F("field_path="<<field_path<<", [" << e.leading << ", .., " << e.trailing << "]"); + ::MIR::LValue val; + ::HIR::TypeRef ty; + get_ty_and_val(sp, m_builder.resolve(), m_top_ty, m_top_val, field_path, m_field_path_ofs, ty, val); + DEBUG("ty = " << ty << ", val = " << val); + + ASSERT_BUG(sp, ty.m_data.is_Slice(), "SplitSlice pattern on non-slice - " << ty); + + // Obtain slice length + auto val_len = m_builder.lvalue_or_temp(sp, ::HIR::CoreType::Usize, ::MIR::RValue::make_DstMeta({ m_builder.get_ptr_to_dst(sp, val).clone() })); + + // 1. Check that length is sufficient for the pattern to be used + // `IF len < min_len : def_blk, next { - and_then(node); + auto next = m_builder.new_bb_unlinked(); + auto cmp_val = this->push_compare(val_len.clone(), ::MIR::eBinOp::LT, ::MIR::Constant::make_Uint({ e.min_len, ::HIR::CoreType::Usize })); + m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_val), def_blk, next }) ); + m_builder.set_cur_block(next); } - else + + // 2. Recurse into leading patterns. + if( !e.leading.empty() ) { - this->generate_tree_code(sp, node, fake_ty, path_prefix.size(), val, - [&](const auto& next_node) { - if( ! path_starts_with(next_node.m_field_path, path_prefix) ) - { - and_then(next_node); - } - else - { - this->generate_tree_code__enum(sp, next_node, fake_ty, val, path_prefix, and_then); - } - }); + auto next = m_builder.new_bb_unlinked(); + auto inner_set = t_rules_subset { 1, /*is_arm_indexes=*/false }; + inner_set.push_bb( e.leading, next ); + auto inst = MatchGenGrouped { m_builder, sp, ty, val, {}, field_path.size() }; + inst.gen_for_slice(inner_set, 0, def_blk); + + m_builder.set_cur_block(next); + } + + if( !e.trailing.empty() ) + { + TODO(sp, "Handle trailing rules in SplitSlice - " << e.trailing); } } diff --git a/src/mir/helpers.cpp b/src/mir/helpers.cpp index 9dac1567..9242ccb7 100644 --- a/src/mir/helpers.cpp +++ b/src/mir/helpers.cpp @@ -10,6 +10,7 @@ #include <hir/hir.hpp> #include <hir/type.hpp> #include <mir/mir.hpp> +#include <algorithm> // ::std::find void ::MIR::TypeResolve::fmt_pos(::std::ostream& os) const { @@ -33,6 +34,13 @@ void ::MIR::TypeResolve::print_msg(const char* tag, ::std::function<void(::std:: //throw CheckFailure {}; } +unsigned int ::MIR::TypeResolve::get_cur_stmt_ofs() const +{ + if( this->stmt_idx == STMT_TERM ) + return m_fcn.blocks.at(this->bb_idx).statements.size(); + else + return this->stmt_idx; +} const ::MIR::BasicBlock& ::MIR::TypeResolve::get_block(::MIR::BasicBlockId id) const { MIR_ASSERT(*this, id < m_fcn.blocks.size(), "Block ID " << id << " out of range"); @@ -271,3 +279,1287 @@ const ::HIR::TypeRef* ::MIR::TypeResolve::is_type_owned_box(const ::HIR::TypeRef { return m_resolve.is_type_owned_box(ty); } + +using namespace MIR::visit; + +// -------------------------------------------------------------------- +// MIR_Helper_GetLifetimes +// -------------------------------------------------------------------- +namespace MIR { +namespace visit { + bool visit_mir_lvalue(const ::MIR::LValue& lv, ValUsage u, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb) + { + if( cb(lv, u) ) + return true; + TU_MATCHA( (lv), (e), + (Variable, + ), + (Argument, + ), + (Temporary, + ), + (Static, + ), + (Return, + ), + (Field, + return visit_mir_lvalue(*e.val, u, cb); + ), + (Deref, + return visit_mir_lvalue(*e.val, ValUsage::Read, cb); + ), + (Index, + bool rv = false; + rv |= visit_mir_lvalue(*e.val, u, cb); + rv |= visit_mir_lvalue(*e.idx, ValUsage::Read, cb); + return rv; + ), + (Downcast, + return visit_mir_lvalue(*e.val, u, cb); + ) + ) + return false; + } + + bool visit_mir_lvalue(const ::MIR::Param& p, ValUsage u, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb) + { + if( const auto* e = p.opt_LValue() ) + { + if(cb(*e, ValUsage::Move)) + return true; + return visit_mir_lvalue(*e, u, cb); + } + else + { + return false; + } + } + + bool visit_mir_lvalues(const ::MIR::RValue& rval, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb) + { + bool rv = false; + TU_MATCHA( (rval), (se), + (Use, + if(cb(se, ValUsage::Move)) + return true; + rv |= visit_mir_lvalue(se, ValUsage::Read, cb); + ), + (Constant, + ), + (SizedArray, + rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb); + ), + (Borrow, + rv |= visit_mir_lvalue(se.val, ValUsage::Borrow, cb); + ), + (Cast, + rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb); + ), + (BinOp, + rv |= visit_mir_lvalue(se.val_l, ValUsage::Read, cb); + rv |= visit_mir_lvalue(se.val_r, ValUsage::Read, cb); + ), + (UniOp, + rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb); + ), + (DstMeta, + rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb); + ), + (DstPtr, + rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb); + ), + (MakeDst, + rv |= visit_mir_lvalue(se.ptr_val, ValUsage::Read, cb); + rv |= visit_mir_lvalue(se.meta_val, ValUsage::Read, cb); + ), + (Tuple, + for(auto& v : se.vals) + rv |= visit_mir_lvalue(v, ValUsage::Read, cb); + ), + (Array, + for(auto& v : se.vals) + rv |= visit_mir_lvalue(v, ValUsage::Read, cb); + ), + (Variant, + rv |= visit_mir_lvalue(se.val, ValUsage::Read, cb); + ), + (Struct, + for(auto& v : se.vals) + rv |= visit_mir_lvalue(v, ValUsage::Read, cb); + ) + ) + return rv; + } + + bool visit_mir_lvalues(const ::MIR::Statement& stmt, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb) + { + bool rv = false; + TU_MATCHA( (stmt), (e), + (Assign, + rv |= visit_mir_lvalues(e.src, cb); + rv |= visit_mir_lvalue(e.dst, ValUsage::Write, cb); + ), + (Asm, + for(auto& v : e.inputs) + rv |= visit_mir_lvalue(v.second, ValUsage::Read, cb); + for(auto& v : e.outputs) + rv |= visit_mir_lvalue(v.second, ValUsage::Write, cb); + ), + (SetDropFlag, + ), + (Drop, + rv |= visit_mir_lvalue(e.slot, ValUsage::Move, cb); + ), + (ScopeEnd, + ) + ) + return rv; + } + + bool visit_mir_lvalues(const ::MIR::Terminator& term, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb) + { + bool rv = false; + TU_MATCHA( (term), (e), + (Incomplete, + ), + (Return, + ), + (Diverge, + ), + (Goto, + ), + (Panic, + ), + (If, + rv |= visit_mir_lvalue(e.cond, ValUsage::Read, cb); + ), + (Switch, + rv |= visit_mir_lvalue(e.val, ValUsage::Read, cb); + ), + (Call, + if( e.fcn.is_Value() ) { + rv |= visit_mir_lvalue(e.fcn.as_Value(), ValUsage::Read, cb); + } + for(auto& v : e.args) + rv |= visit_mir_lvalue(v, ValUsage::Read, cb); + rv |= visit_mir_lvalue(e.ret_val, ValUsage::Write, cb); + ) + ) + return rv; + } + /* + void visit_mir_lvalues_mut(::MIR::TypeResolve& state, ::MIR::Function& fcn, ::std::function<bool(::MIR::LValue& , ValUsage)> cb) + { + for(unsigned int block_idx = 0; block_idx < fcn.blocks.size(); block_idx ++) + { + auto& block = fcn.blocks[block_idx]; + for(auto& stmt : block.statements) + { + state.set_cur_stmt(block_idx, (&stmt - &block.statements.front())); + visit_mir_lvalues_mut(stmt, cb); + } + if( block.terminator.tag() == ::MIR::Terminator::TAGDEAD ) + continue ; + state.set_cur_stmt_term(block_idx); + visit_mir_lvalues_mut(block.terminator, cb); + } + } + void visit_mir_lvalues(::MIR::TypeResolve& state, const ::MIR::Function& fcn, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb) + { + visit_mir_lvalues_mut(state, const_cast<::MIR::Function&>(fcn), [&](auto& lv, auto im){ return cb(lv, im); }); + } + */ +} // namespace visit +} // namespace MIR +namespace +{ + struct ValueLifetime + { + ::std::vector<bool> stmt_bitmap; + ValueLifetime(size_t stmt_count): + stmt_bitmap(stmt_count) + {} + + void fill(const ::std::vector<size_t>& block_offsets, size_t bb, size_t first_stmt, size_t last_stmt) + { + size_t limit = block_offsets[bb+1] - block_offsets[bb] - 1; + DEBUG("bb" << bb << " : " << first_stmt << "--" << last_stmt); + assert(first_stmt <= limit); + assert(last_stmt <= limit); + for(size_t stmt = first_stmt; stmt <= last_stmt; stmt++) + { + stmt_bitmap[block_offsets[bb] + stmt] = true; + } + } + + void dump_debug(const char* suffix, unsigned i, const ::std::vector<size_t>& block_offsets) + { + ::std::string name = FMT(suffix << "$" << i); + while(name.size() < 3+1+3) + name += " "; + DEBUG(name << " : " << FMT_CB(os, + for(unsigned int j = 0; j < this->stmt_bitmap.size(); j++) + { + if(j != 0 && ::std::find(block_offsets.begin(), block_offsets.end(), j) != block_offsets.end()) + os << "|"; + os << (this->stmt_bitmap[j] ? "X" : " "); + } + )); + } + }; +} +#if 1 +void MIR_Helper_GetLifetimes_DetermineValueLifetime(::MIR::TypeResolve& state, const ::MIR::Function& fcn, size_t bb_idx, size_t stmt_idx, const ::MIR::LValue& lv, const ::std::vector<size_t>& block_offsets, ValueLifetime& vl); + +::MIR::ValueLifetimes MIR_Helper_GetLifetimes(::MIR::TypeResolve& state, const ::MIR::Function& fcn, bool dump_debug) +{ + TRACE_FUNCTION_F(state); + + size_t statement_count = 0; + ::std::vector<size_t> block_offsets; + block_offsets.reserve( fcn.blocks.size() ); + for(const auto& bb : fcn.blocks) + { + block_offsets.push_back(statement_count); + statement_count += bb.statements.size() + 1; // +1 for the terminator + } + block_offsets.push_back(statement_count); // Store the final limit for later code to use. + + ::std::vector<ValueLifetime> temporary_lifetimes( fcn.temporaries.size(), ValueLifetime(statement_count) ); + ::std::vector<ValueLifetime> variable_lifetimes( fcn.named_variables.size(), ValueLifetime(statement_count) ); + + + // Enumerate direct assignments of variables (linear iteration of BB list) + for(size_t bb_idx = 0; bb_idx < fcn.blocks.size(); bb_idx ++) + { + auto assigned_lvalue = [&](size_t bb_idx, size_t stmt_idx, const ::MIR::LValue& lv) { + // NOTE: Fills the first statement after running, just to ensure that any assigned value has _a_ lifetime + if( const auto* de = lv.opt_Variable() ) + { + MIR_Helper_GetLifetimes_DetermineValueLifetime(state, fcn, bb_idx, stmt_idx, lv, block_offsets, variable_lifetimes[*de]); + variable_lifetimes[*de].fill(block_offsets, bb_idx, stmt_idx, stmt_idx); + } + else if( const auto* de = lv.opt_Temporary() ) + { + MIR_Helper_GetLifetimes_DetermineValueLifetime(state, fcn, bb_idx, stmt_idx, lv, block_offsets, temporary_lifetimes[de->idx]); + temporary_lifetimes[de->idx].fill(block_offsets, bb_idx, stmt_idx, stmt_idx); + } + else + { + // Not a direct assignment of a slot + } + }; + + const auto& bb = fcn.blocks[bb_idx]; + for(size_t stmt_idx = 0; stmt_idx < bb.statements.size(); stmt_idx ++) + { + state.set_cur_stmt(bb_idx, stmt_idx); + const auto& stmt = bb.statements[stmt_idx]; + if( const auto* se = stmt.opt_Assign() ) + { + // For assigned variables, determine how long that value will live + assigned_lvalue(bb_idx, stmt_idx+1, se->dst); + } + else if( const auto* se = stmt.opt_Asm() ) + { + for(const auto& e : se->outputs) + { + assigned_lvalue(bb_idx, stmt_idx+1, e.second); + } + } + } + state.set_cur_stmt_term(bb_idx); + + // Only Call can assign a value + TU_IFLET(::MIR::Terminator, bb.terminator, Call, te, + assigned_lvalue(te.ret_block, 0, te.ret_val); + ) + } + + // Dump out variable lifetimes. + if( dump_debug ) + { + for(unsigned int i = 0; i < temporary_lifetimes.size(); i ++) + { + temporary_lifetimes[i].dump_debug("tmp", i, block_offsets); + } + for(unsigned int i = 0; i < variable_lifetimes.size(); i ++) + { + variable_lifetimes[i].dump_debug("var", i, block_offsets); + } + } + + + ::MIR::ValueLifetimes rv; + rv.m_block_offsets = mv$(block_offsets); + rv.m_temporaries.reserve( temporary_lifetimes.size() ); + for(auto& lft : temporary_lifetimes) + rv.m_temporaries.push_back( ::MIR::ValueLifetime(mv$(lft.stmt_bitmap)) ); + rv.m_variables.reserve( variable_lifetimes.size() ); + for(auto& lft : variable_lifetimes) + rv.m_variables.push_back( ::MIR::ValueLifetime(mv$(lft.stmt_bitmap)) ); + return rv; +} +void MIR_Helper_GetLifetimes_DetermineValueLifetime( + ::MIR::TypeResolve& mir_res, const ::MIR::Function& fcn, + size_t bb_idx, size_t stmt_idx, // First statement in which the value is valid (after the assignment) + const ::MIR::LValue& lv, const ::std::vector<size_t>& block_offsets, ValueLifetime& vl + ) +{ + TRACE_FUNCTION_F(mir_res << " " << lv); + // Walk the BB tree until: + // - Loopback + // - Assignment + // - Drop + + struct State + { + const ::std::vector<size_t>& m_block_offsets; + ValueLifetime& m_out_vl; + + ::std::vector<unsigned int> bb_history; + size_t last_read_ofs; // Statement index + bool m_is_borrowed; + + State(const ::std::vector<size_t>& block_offsets, ValueLifetime& vl, size_t init_bb_idx, size_t init_stmt_idx): + m_block_offsets(block_offsets), + m_out_vl(vl), + bb_history(), + last_read_ofs(init_stmt_idx), + m_is_borrowed(false) + { + bb_history.push_back(init_bb_idx); + } + State(State&& x): + m_block_offsets(x.m_block_offsets), + m_out_vl(x.m_out_vl), + bb_history( mv$(x.bb_history) ), + last_read_ofs( x.last_read_ofs ), + m_is_borrowed( x.m_is_borrowed ) + { + } + State& operator=(State&& x) { + this->bb_history = mv$(x.bb_history); + this->last_read_ofs = x.last_read_ofs; + this->m_is_borrowed = x.m_is_borrowed; + return *this; + } + + State clone() const { + State rv { m_block_offsets, m_out_vl, 0, last_read_ofs }; + rv.bb_history = bb_history; + rv.m_is_borrowed = m_is_borrowed; + return rv; + } + + // Returns true if the variable has been borrowed + bool is_borrowed() const { + return this->m_is_borrowed; + } + + void mark_borrowed(size_t stmt_idx) { + if( ! m_is_borrowed ) + { + m_is_borrowed = false; + this->fill_to(stmt_idx); + } + m_is_borrowed = true; + } + void mark_read(size_t stmt_idx) { + if( !m_is_borrowed ) + { + this->fill_to(stmt_idx); + } + else + { + m_is_borrowed = false; + this->fill_to(stmt_idx); + m_is_borrowed = true; + } + } + void fmt(::std::ostream& os) const { + os << "BB" << bb_history.front() << "/" << last_read_ofs << "--"; + os << "[" << bb_history << "]"; + } + void finalise(size_t stmt_idx) + { + if( m_is_borrowed ) + { + m_is_borrowed = false; + this->fill_to(stmt_idx); + m_is_borrowed = true; + } + } + private: + void fill_to(size_t stmt_idx) + { + TRACE_FUNCTION_F(FMT_CB(ss, this->fmt(ss);)); + assert( !m_is_borrowed ); + assert(bb_history.size() > 0); + if( bb_history.size() == 1 ) + { + // only one block + m_out_vl.fill(m_block_offsets, bb_history[0], last_read_ofs, stmt_idx); + } + else + { + // First block. + auto init_bb_idx = bb_history[0]; + auto limit_0 = m_block_offsets[init_bb_idx+1] - m_block_offsets[init_bb_idx] - 1; + m_out_vl.fill(m_block_offsets, init_bb_idx, last_read_ofs, limit_0); + + // Middle blocks + for(size_t i = 1; i < bb_history.size()-1; i++) + { + size_t bb_idx = bb_history[i]; + assert(bb_idx+1 < m_block_offsets.size()); + size_t limit = m_block_offsets[bb_idx+1] - m_block_offsets[bb_idx] - 1; + m_out_vl.fill(m_block_offsets, bb_idx, 0, limit); + } + + // Last block + auto bb_idx = bb_history.back(); + m_out_vl.fill(m_block_offsets, bb_idx, 0, stmt_idx); + } + + last_read_ofs = stmt_idx; + + auto cur = this->bb_history.back(); + this->bb_history.clear(); + this->bb_history.push_back(cur); + } + }; + + struct Runner + { + ::MIR::TypeResolve& m_mir_res; + const ::MIR::Function& m_fcn; + size_t m_init_bb_idx; + size_t m_init_stmt_idx; + const ::MIR::LValue& m_lv; + const ::std::vector<size_t>& m_block_offsets; + ValueLifetime& m_lifetimes; + bool m_is_copy; + + ::std::vector<bool> m_visited_statements; + + ::std::vector<::std::pair<size_t, State>> m_states_to_do; + + Runner(::MIR::TypeResolve& mir_res, const ::MIR::Function& fcn, size_t init_bb_idx, size_t init_stmt_idx, const ::MIR::LValue& lv, const ::std::vector<size_t>& block_offsets, ValueLifetime& vl): + m_mir_res(mir_res), + m_fcn(fcn), + m_init_bb_idx(init_bb_idx), + m_init_stmt_idx(init_stmt_idx), + m_lv(lv), + m_block_offsets(block_offsets), + m_lifetimes(vl), + + m_visited_statements( m_lifetimes.stmt_bitmap.size() ) + { + ::HIR::TypeRef tmp; + m_is_copy = m_mir_res.m_resolve.type_is_copy(mir_res.sp, m_mir_res.get_lvalue_type(tmp, lv)); + } + + void run_block(size_t bb_idx, size_t stmt_idx, State state) + { + const auto& bb = m_fcn.blocks.at(bb_idx); + assert(stmt_idx <= bb.statements.size()); + + bool was_moved = false; + bool was_updated = false; + auto visit_cb = [&](const auto& lv, auto vu) { + if(lv == m_lv) { + if( vu == ValUsage::Read ) { + DEBUG(m_mir_res << "Used"); + state.mark_read(stmt_idx); + was_updated = true; + } + if( vu == ValUsage::Move ) { + DEBUG(m_mir_res << (m_is_copy ? "Read" : "Moved")); + state.mark_read(stmt_idx); + was_moved = ! m_is_copy; + } + if( vu == ValUsage::Borrow ) { + DEBUG(m_mir_res << "Borrowed"); + state.mark_borrowed(stmt_idx); + was_updated = true; + } + return true; + } + return false; + }; + + for( ; stmt_idx < bb.statements.size(); stmt_idx ++) + { + const auto& stmt = bb.statements[stmt_idx]; + m_mir_res.set_cur_stmt(bb_idx, stmt_idx); + m_visited_statements[ m_block_offsets.at(bb_idx) + stmt_idx ] = true; + + // Visit and see if the value is read (setting the read flag or end depending on if the value is Copy) + visit_mir_lvalues(stmt, visit_cb); + + if( was_moved ) + { + // Moved: Update read position and apply + DEBUG(m_mir_res << "Moved, return"); + state.mark_read(stmt_idx); + state.finalise(stmt_idx); + return ; + } + + TU_MATCHA( (stmt), (se), + (Assign, + if( se.dst == m_lv ) + { + DEBUG(m_mir_res << "- Assigned to, return"); + // Value assigned, just apply + state.finalise(stmt_idx); + return ; + } + ), + (Drop, + visit_mir_lvalue(se.slot, ValUsage::Read, visit_cb); + if( se.slot == m_lv ) + { + // Value dropped, update read position and apply + DEBUG(m_mir_res << "- Dropped, return"); + // - If it was borrowed, it can't still be borrowed here. + // TODO: Enable this once it's known to not cause mis-optimisation. It could currently. + //if( state.is_borrowed() ) { + // state.clear_borrowed(); + //} + state.mark_read(stmt_idx); + state.finalise(stmt_idx); + return ; + } + ), + (Asm, + // + for(const auto& e : se.outputs) + { + if(e.second == m_lv) { + // Assigned, just apply + DEBUG(m_mir_res << "- Assigned (asm!), return"); + state.finalise(stmt_idx); + return ; + } + } + ), + (SetDropFlag, + // Ignore + ), + (ScopeEnd, + // Ignore + ) + ) + } + m_mir_res.set_cur_stmt_term(bb_idx); + m_visited_statements[ m_block_offsets.at(bb_idx) + stmt_idx ] = true; + + visit_mir_lvalues(bb.terminator, visit_cb); + + if( was_moved ) + { + // Moved: Update read position and apply + DEBUG(m_mir_res << "- Moved, return"); + state.mark_read(stmt_idx); + state.finalise(stmt_idx); + return ; + } + + // Terminator + TU_MATCHA( (bb.terminator), (te), + (Incomplete, + // TODO: Isn't this a bug? + DEBUG(m_mir_res << "Incomplete"); + state.finalise(stmt_idx); + ), + (Return, + DEBUG(m_mir_res << "Return"); + state.finalise(stmt_idx); + ), + (Diverge, + DEBUG(m_mir_res << "Diverge"); + state.finalise(stmt_idx); + ), + (Goto, + m_states_to_do.push_back( ::std::make_pair(te, mv$(state)) ); + ), + (Panic, + m_states_to_do.push_back( ::std::make_pair(te.dst, mv$(state)) ); + ), + (If, + m_states_to_do.push_back( ::std::make_pair(te.bb0, state.clone()) ); + m_states_to_do.push_back( ::std::make_pair(te.bb1, mv$(state)) ); + ), + (Switch, + for(size_t i = 0; i < te.targets.size(); i ++) + { + auto s = (i == te.targets.size()-1) + ? mv$(state) + : state.clone(); + m_states_to_do.push_back( ::std::make_pair(te.targets[i], mv$(s)) ); + } + ), + (Call, + if( te.ret_val == m_lv ) + { + DEBUG(m_mir_res << "Assigned (Call), return"); + // Value assigned, just apply + state.finalise(stmt_idx); + return ; + } + if( m_fcn.blocks.at(te.panic_block).statements.size() == 0 && m_fcn.blocks.at(te.panic_block).terminator.is_Diverge() ) { + // Shortcut: Don't create a new state if the panic target is Diverge + } + else { + m_states_to_do.push_back( ::std::make_pair(te.panic_block, state.clone()) ); + } + m_states_to_do.push_back( ::std::make_pair(te.ret_block, mv$(state)) ); + ) + ) + } + }; + + ::std::vector<bool> use_bitmap(vl.stmt_bitmap.size()); // Bitmap of locations where this value is used. + { + size_t pos = 0; + for(const auto& bb : fcn.blocks) + { + for(const auto& stmt : bb.statements) + { + use_bitmap[pos] = visit_mir_lvalues(stmt, [&](const ::MIR::LValue& tlv, auto vu){ return tlv == lv && vu != ValUsage::Write; }); + pos ++; + } + use_bitmap[pos] = visit_mir_lvalues(bb.terminator, [&](const ::MIR::LValue& tlv, auto vu){ return tlv == lv && vu != ValUsage::Write; }); + pos ++; + } + } + + Runner runner(mir_res, fcn, bb_idx, stmt_idx, lv, block_offsets, vl); + ::std::vector< ::std::pair<size_t,State>> post_check_list; + + // TODO: Have a bitmap of visited statements. If a visted statement is hit, stop the current state + // - Use the same rules as loopback. + + // Fill the first statement, to ensure that there is at least one bit set. + runner.run_block(bb_idx, stmt_idx, State(block_offsets, vl, bb_idx, stmt_idx)); + + while( ! runner.m_states_to_do.empty() ) + { + auto bb_idx = runner.m_states_to_do.back().first; + auto state = mv$(runner.m_states_to_do.back().second); + runner.m_states_to_do.pop_back(); + + DEBUG("state.bb_history=[" << state.bb_history << "], -> BB" << bb_idx); + state.bb_history.push_back(bb_idx); + + if( runner.m_visited_statements.at( block_offsets.at(bb_idx) + 0 ) ) + { + if( vl.stmt_bitmap.at( block_offsets.at(bb_idx) + 0) ) + { + DEBUG("Looped (to already valid)"); + state.mark_read(0); + state.finalise(0); + continue ; + } + else if( state.is_borrowed() ) + { + DEBUG("Looped (borrowed)"); + state.mark_read(0); + state.finalise(0); + continue ; + } + else + { + // Put this state elsewhere and check if the variable is known valid at that point. + DEBUG("Looped (after last read), push for later"); + post_check_list.push_back( ::std::make_pair(bb_idx, mv$(state)) ); + continue ; + } + } + +#if 0 + // TODO: Have a bitmap of if a BB mentions this value. If there are no unvisited BBs that mention this value, stop early. + // - CATCH: The original BB contains a reference, but might not have been visited (if it was the terminating call that triggered) + // - Also, we don't want to give up early (if we loop back to the start of the first block) + // - A per-statement bitmap would solve this. Return early if `!vl.stmt_bitmap & usage_stmt_bitmap == 0` + // > Requires filling the bitmap as we go (for maximum efficiency) + { + bool found_non_visited = false; + for(size_t i = 0; i < use_bitmap.size(); i ++) + { + // If a place where the value is used is not present in the output bitmap + if( !vl.stmt_bitmap[i] && use_bitmap[i] ) + { + DEBUG("- Still used at +" << i); + found_non_visited = true; + } + } + // If there were no uses of the variable that aren't covered by the lifetime bitmap + if( ! found_non_visited ) + { + // Terminate early + DEBUG("Early terminate - All possible lifetimes covered"); + state.finalise(0); + for(auto& s : runner.m_states_to_do) + { + s.second.bb_history.push_back(bb_idx); + s.second.finalise(0); + } + return ; + } + } +#endif + + // Special case for when doing multiple runs on the same output + if( vl.stmt_bitmap.at( block_offsets.at(bb_idx) + 0) ) + { + DEBUG("Already valid in BB" << bb_idx); + state.mark_read(0); + state.finalise(0); + continue; + } +#if 0 + // TODO: Have a way of knowing if a state will never find a use (the negative of the above) + // - Requires knowing for sure that a BB doesn't end up using the value. + // - IDEA: Store a fork count and counts of Yes/No for each BB. + // > If ForkCount == No, the value isn't used in that branch. + if( runner.m_bb_counts[bb_idx].visit_count > 0 + && runner.m_bb_counts[bb_idx].visit_count == runner.m_bb_counts[bb_idx].val_unused_count ) + { + DEBUG("Target BB known to be not valid"); + runner.apply_state(state, 0); + continue ; + } + runner.m_bb_counts[bb_idx].visit_count ++; +#endif + + runner.run_block(bb_idx, 0, mv$(state)); + } + + // Iterate while there are items in the post_check list + while( !post_check_list.empty() ) + { + bool change = false; + for(auto it = post_check_list.begin(); it != post_check_list.end(); ) + { + auto bb_idx = it->first; + auto& state = it->second; + // If the target of this loopback is valid, then the entire route to the loopback must have been valid + if( vl.stmt_bitmap.at( block_offsets.at(bb_idx) + 0) ) + { + change = true; + DEBUG("Looped (now valid)"); + state.mark_read(0); + state.finalise(0); + + it = post_check_list.erase(it); + } + else + { + ++ it; + } + } + // Keep going while changes happen + if( !change ) + break; + } +} + +#else + +::MIR::ValueLifetimes MIR_Helper_GetLifetimes(::MIR::TypeResolve& state, const ::MIR::Function& fcn, bool dump_debug) +{ + TRACE_FUNCTION_F(state); + // New algorithm notes: + // --- + // The lifetime of a value starts when it is written, and ends the last time it is read + // - When a variable is read, end any existing lifetime and start a new one. + // - When the value is read, update the end of its lifetime. + // --- + // A lifetime is a range in the call graph (with a start and end, including list of blocks) + // - Representation: Bitmap with a bit per statement. + // - Record the current block path in general state, along with known active lifetimes + + // TODO: If a value is borrowed, assume it lives forevermore + // - Ideally there would be borrow tracking to determine its actual required lifetime. + // - NOTE: This doesn't impact the borrows themselves, just the borrowee + + // TODO: Add a statement type StorageDead (or similar?) that indicates the point where a values scope ends + + // Scan through all possible paths in the graph (with loopback detection using a memory of the path) + // - If a loop is detected, determine if there were changes to the lifetime set during that pass + // > Changes are noticed by recording in the state structure when it triggers a change in the lifetime + // map. + struct Position + { + size_t path_index = 0; // index into the block path. + unsigned int stmt_idx = 0; + + bool operator==(const Position& x) const { + return path_index == x.path_index && stmt_idx == x.stmt_idx; + } + }; + struct ProtoLifetime + { + Position start; + Position end; + + bool is_empty() const { + return start == end; + } + bool is_borrowed() const { + return this->end == Position { ~0u, ~0u }; + } + }; + static unsigned NEXT_INDEX = 0; + struct State + { + unsigned int index = 0; + ::std::vector<unsigned int> block_path; + ::std::vector<unsigned int> block_change_idx; + unsigned int cur_change_idx = 0; + + // if read, update. If set, save and update + ::std::vector<ProtoLifetime> tmp_ends; + ::std::vector<ProtoLifetime> var_ends; + + State(const ::MIR::Function& fcn): + tmp_ends( fcn.temporaries.size(), ProtoLifetime() ), + var_ends( fcn.named_variables.size(), ProtoLifetime() ) + { + } + + State clone() const { + auto rv = *this; + rv.index = ++NEXT_INDEX; + return rv; + } + }; + NEXT_INDEX = 0; + + size_t statement_count = 0; + ::std::vector<size_t> block_offsets; + block_offsets.reserve( fcn.blocks.size() ); + for(const auto& bb : fcn.blocks) + { + block_offsets.push_back(statement_count); + statement_count += bb.statements.size() + 1; // +1 for the terminator + } + + ::std::vector<ValueLifetime> temporary_lifetimes( fcn.temporaries.size(), ValueLifetime(statement_count) ); + ::std::vector<ValueLifetime> variable_lifetimes( fcn.named_variables.size(), ValueLifetime(statement_count) ); + + struct BlockSeenLifetimes { + bool m_has_state = false; + const ::std::vector<size_t>& block_offsets; + ::std::vector< ::std::vector<unsigned int> > tmp; + ::std::vector< ::std::vector<unsigned int> > var; + + BlockSeenLifetimes(const ::std::vector<size_t>& block_offsets, const ::MIR::Function& fcn): + block_offsets( block_offsets ), + tmp( fcn.temporaries.size() ), + var( fcn.named_variables.size() ) + {} + + bool has_state() const + { + return m_has_state; + } + + bool try_merge(const State& val_state) const + { + // TODO: This logic isn't quite correct. Just becase a value's existing end is already marked as valid, + // doesn't mean that we have no new information. + // - Wait, doesn't it? + auto try_merge_lft = [&](const ProtoLifetime& lft, const ::std::vector<unsigned int>& seen)->bool { + if(lft.is_empty()) return false; + // TODO: What should be done for borrow flagged values + if(lft.is_borrowed()) return false; + auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx; + + auto it = ::std::find(seen.begin(), seen.end(), end_idx); + return (it == seen.end()); + }; + for(size_t i = 0; i < val_state.tmp_ends.size(); i++) + { + if( try_merge_lft(val_state.tmp_ends[i], this->tmp[i]) ) + return true; + } + for(size_t i = 0; i < val_state.var_ends.size(); i++) + { + if( try_merge_lft(val_state.var_ends[i], this->var[i]) ) + return true; + } + return false; + } + + bool merge(const State& val_state) + { + bool rv = false; + auto merge_lft = [&](const ProtoLifetime& lft, ::std::vector<unsigned int>& seen)->bool { + if(lft.is_empty()) return false; + // TODO: What should be done for borrow flagged values + if(lft.end == Position { ~0u, ~0u }) return false; + auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx; + + auto it = ::std::find(seen.begin(), seen.end(), end_idx); + if( it == seen.end() ) + { + seen.push_back( end_idx ); + return true; + } + else + { + return false; + } + }; + for(size_t i = 0; i < val_state.tmp_ends.size(); i++) + { + rv |= merge_lft(val_state.tmp_ends[i], this->tmp[i]); + } + for(size_t i = 0; i < val_state.var_ends.size(); i++) + { + rv |= merge_lft(val_state.var_ends[i], this->var[i]); + } + m_has_state = true; + return rv; + } + }; + ::std::vector<BlockSeenLifetimes> block_seen_lifetimes( fcn.blocks.size(), BlockSeenLifetimes(block_offsets, fcn) ); + + State init_state(fcn); + + ::std::vector<::std::pair<unsigned int, State>> todo_queue; + todo_queue.push_back(::std::make_pair( 0, mv$(init_state) )); + + while(!todo_queue.empty()) + { + auto bb_idx = todo_queue.back().first; + auto val_state = mv$(todo_queue.back().second); + todo_queue.pop_back(); + state.set_cur_stmt(bb_idx, 0); + + // Fill alive time in the bitmap + // TODO: Maybe also store the range (as a sequence of {block,start,end}) + auto add_lifetime_s = [&](State& val_state, const ::MIR::LValue& lv, const Position& start, const Position& end) { + assert(start.path_index <= end.path_index); + assert(start.path_index < end.path_index || start.stmt_idx <= end.stmt_idx); + if(start.path_index == end.path_index && start.stmt_idx == end.stmt_idx) + return; + DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ")"); + ValueLifetime* lft; + if(const auto* e = lv.opt_Temporary()) + { + lft = &temporary_lifetimes[e->idx]; + } + else if(const auto* e = lv.opt_Variable()) + { + lft = &variable_lifetimes[*e]; + } + else + { + MIR_TODO(state, "[add_lifetime] " << lv); + return; + } + + // Fill lifetime map for this temporary in the indicated range + bool did_set = false; + unsigned int j = start.stmt_idx; + unsigned int i = start.path_index; + while( i <= end.path_index && i < val_state.block_path.size() ) + { + auto bb_idx = val_state.block_path.at(i); + const auto& bb = fcn.blocks[bb_idx]; + MIR_ASSERT(state, j <= bb.statements.size(), ""); + MIR_ASSERT(state, bb_idx < block_offsets.size(), ""); + + auto block_base = block_offsets.at(bb_idx); + auto idx = block_base + j; + if( !lft->stmt_bitmap.at(idx) ) + { + lft->stmt_bitmap[idx] = true; + did_set = true; + } + + if( i == end.path_index && j == (end.stmt_idx != ~0u ? end.stmt_idx : bb.statements.size()) ) + break; + + // If the current index is the terminator (one after the size) + if(j == bb.statements.size()) + { + j = 0; + i++; + } + else + { + j ++; + } + } + + // - If the above set a new bit, increment `val_state.cur_change_idx` + if( did_set ) + { + DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ") - New information"); + val_state.cur_change_idx += 1; + } + }; + auto add_lifetime = [&](const ::MIR::LValue& lv, const Position& start, const Position& end) { + add_lifetime_s(val_state, lv, start, end); + }; + + auto apply_state = [&](State& state) { + // Apply all changes in this state, just in case there was new information + for(unsigned i = 0; i < fcn.temporaries.size(); i++) + add_lifetime_s( state, ::MIR::LValue::make_Temporary({i}), state.tmp_ends[i].start, state.tmp_ends[i].end ); + for(unsigned i = 0; i < fcn.named_variables.size(); i++) + add_lifetime_s( state, ::MIR::LValue::make_Variable({i}), state.var_ends[i].start, state.var_ends[i].end ); + }; + auto add_to_visit = [&](unsigned int new_bb_idx, State new_state) { + auto& bb_memory_ent = block_seen_lifetimes[new_bb_idx]; + if( !bb_memory_ent.has_state() ) + { + // No recorded state, needs to be visited + DEBUG(state << " state" << new_state.index << " -> bb" << new_bb_idx << " (no existing state)"); + } + else if( bb_memory_ent.try_merge(new_state) ) + { + // This state has new information, needs to be visited + DEBUG(state << " state" << new_state.index << " -> bb" << new_bb_idx << " (new info)"); + } + else + { + // Skip + // TODO: Acquire from the target block the actual end of any active lifetimes, then apply them. + DEBUG(state << " state" << new_state.index << " -> bb" << new_bb_idx << " - No new state, no push"); + // - For all variables currently active, check if they're valid in the first statement of the target block. + // - If so, mark as valid at the end of the current block + auto bm_idx = block_offsets[new_bb_idx]; + Position cur_pos; + cur_pos.path_index = val_state.block_path.size() - 1; + cur_pos.stmt_idx = fcn.blocks[bb_idx].statements.size(); + for(unsigned i = 0; i < fcn.temporaries.size(); i++) { + if( ! new_state.tmp_ends[i].is_empty() && temporary_lifetimes[i].stmt_bitmap[bm_idx] ) { + DEBUG("- tmp$" << i << " - Active in target, assume active"); + new_state.tmp_ends[i].end = cur_pos; + } + } + for(unsigned i = 0; i < fcn.named_variables.size(); i++) { + if( ! new_state.var_ends[i].is_empty() && variable_lifetimes[i].stmt_bitmap[bm_idx] ) { + DEBUG("- var$" << i << " - Active in target, assume active"); + new_state.var_ends[i].end = cur_pos; + } + } + // - Apply whatever state was still active + apply_state(new_state); + return ; + } + todo_queue.push_back(::std::make_pair( new_bb_idx, mv$(new_state) )); + }; + + // Compare this state to a composite list of lifetimes seen in this block + // - Just compares the end of each proto lifetime + { + auto& bb_memory_ent = block_seen_lifetimes[bb_idx]; + bool had_state = bb_memory_ent.has_state(); + bool has_new = bb_memory_ent.merge(val_state); + + if( !has_new && had_state ) + { + DEBUG(state << " state" << val_state.index << " - No new entry state"); + apply_state(val_state); + + continue ; + } + } + + // Check if this state has visited this block before, and if anything changed since last time + { + auto it = ::std::find(val_state.block_path.rbegin(), val_state.block_path.rend(), bb_idx); + if( it != val_state.block_path.rend() ) + { + auto idx = &*it - &val_state.block_path.front(); + if( val_state.block_change_idx[idx] == val_state.cur_change_idx ) + { + DEBUG(state << " " << val_state.index << " Loop and no change"); + continue ; + } + else + { + assert( val_state.block_change_idx[idx] < val_state.cur_change_idx ); + DEBUG(state << " " << val_state.index << " --- Loop, " << val_state.cur_change_idx - val_state.block_change_idx[idx] << " changes"); + } + } + else + { + DEBUG(state << " " << val_state.index << " ---"); + } + val_state.block_path.push_back(bb_idx); + val_state.block_change_idx.push_back( val_state.cur_change_idx ); + } + + Position cur_pos; + cur_pos.path_index = val_state.block_path.size() - 1; + cur_pos.stmt_idx = 0; + auto lvalue_read = [&](const ::MIR::LValue& lv) { + ProtoLifetime* slot; + if(const auto* e = lv.opt_Temporary()) { + slot = &val_state.tmp_ends.at(e->idx); + } + else if(const auto* e = lv.opt_Variable()) { + slot = &val_state.var_ends.at(*e); + } + else { + return ; + } + // Update the last read location + //DEBUG("Update END " << lv << " to " << cur_pos); + slot->end = cur_pos; + }; + auto lvalue_set = [&](const ::MIR::LValue& lv) { + ProtoLifetime* slot; + if(const auto* e = lv.opt_Temporary()) { + slot = &val_state.tmp_ends.at(e->idx); + } + else if(const auto* e = lv.opt_Variable()) { + slot = &val_state.var_ends.at(*e); + } + else { + return ; + } + // End whatever value was originally there, and insert this new one + slot->end = cur_pos; + add_lifetime(lv, slot->start, slot->end); + slot->start = cur_pos; + }; + auto lvalue_borrow = [&](const ::MIR::LValue& lv) { + ProtoLifetime* slot; + if(const auto* e = lv.opt_Temporary()) { + slot = &val_state.tmp_ends.at(e->idx); + } + else if(const auto* e = lv.opt_Variable()) { + slot = &val_state.var_ends.at(*e); + } + else { + return ; + } + // TODO: Flag this value as currently being borrowed (a flag that never clears) + slot->end = Position { ~0u, ~0u }; + }; + auto visit_lval_cb = [&](const auto& lv, ValUsage vu)->bool{ + if(vu == ValUsage::Read) + lvalue_read(lv); + if(vu == ValUsage::Borrow) + lvalue_borrow(lv); + if(vu == ValUsage::Write) + lvalue_set(lv); + return false; + }; + + // Run statements + for(const auto& stmt : fcn.blocks[bb_idx].statements) + { + auto stmt_idx = &stmt - &fcn.blocks[bb_idx].statements.front(); + cur_pos.stmt_idx = stmt_idx; + state.set_cur_stmt(bb_idx, stmt_idx); + DEBUG(state << " " << stmt); + + if( const auto* e = stmt.opt_Drop() ) + { + visit_mir_lvalues(stmt, [&](const auto& lv, ValUsage vu)->bool{ + if(vu == ValUsage::Read) + lvalue_read(lv); + return false; + }); + lvalue_read(e->slot); + lvalue_set(e->slot); + } + else + { + visit_mir_lvalues(stmt, visit_lval_cb); + } + } + cur_pos.stmt_idx = fcn.blocks[bb_idx].statements.size(); + + state.set_cur_stmt_term(bb_idx); + DEBUG(state << "TERM " << fcn.blocks[bb_idx].terminator); + TU_MATCH(::MIR::Terminator, (fcn.blocks[bb_idx].terminator), (e), + (Incomplete, + // Should be impossible here. + ), + (Return, + // End all active lifetimes at their previous location. + apply_state(val_state); + ), + (Diverge, + apply_state(val_state); + ), + (Goto, + add_to_visit(e, mv$(val_state)); + ), + (Panic, + // What should be done here? + ), + (If, + visit_mir_lvalue(e.cond, ValUsage::Read, visit_lval_cb); + + // Push blocks + add_to_visit(e.bb0, val_state.clone()); + add_to_visit(e.bb1, mv$(val_state)); + ), + (Switch, + visit_mir_lvalue(e.val, ValUsage::Read, visit_lval_cb); + ::std::set<unsigned int> tgts; + for(const auto& tgt : e.targets) + tgts.insert(tgt); + + for(const auto& tgt : tgts) + { + auto vs = (tgt == *tgts.rbegin() ? mv$(val_state) : val_state.clone()); + add_to_visit(tgt, mv$(vs)); + } + ), + (Call, + if( const auto* f = e.fcn.opt_Value() ) + visit_mir_lvalue(*f, ValUsage::Read, visit_lval_cb); + for(const auto& arg : e.args) + if( const auto* e = arg.opt_LValue() ) + visit_mir_lvalue(*e, ValUsage::Read, visit_lval_cb); + + // Push blocks (with return valid only in one) + add_to_visit(e.panic_block, val_state.clone()); + + // TODO: If the function returns !, don't follow the ret_block + lvalue_set(e.ret_val); + add_to_visit(e.ret_block, mv$(val_state)); + ) + ) + } + + // Dump out variable lifetimes. + if( dump_debug ) + { + for(unsigned int i = 0; i < temporary_lifetimes.size(); i ++) + { + temporary_lifetimes[i].dump_debug("tmp", i, block_offsets); + } + for(unsigned int i = 0; i < variable_lifetimes.size(); i ++) + { + variable_lifetimes[i].dump_debug("var", i, block_offsets); + } + } + + // Move lifetime bitmaps into the variable for the below code + ::MIR::ValueLifetimes rv; + rv.m_block_offsets = mv$(block_offsets); + rv.m_temporaries.reserve( temporary_lifetimes.size() ); + for(auto& lft : temporary_lifetimes) + rv.m_temporaries.push_back( ::MIR::ValueLifetime(mv$(lft.stmt_bitmap)) ); + rv.m_variables.reserve( variable_lifetimes.size() ); + for(auto& lft : variable_lifetimes) + rv.m_variables.push_back( ::MIR::ValueLifetime(mv$(lft.stmt_bitmap)) ); + + return rv; +} +#endif diff --git a/src/mir/helpers.hpp b/src/mir/helpers.hpp index e8f52651..d7aefe25 100644 --- a/src/mir/helpers.hpp +++ b/src/mir/helpers.hpp @@ -23,6 +23,10 @@ class Function; class LValue; class Constant; class BasicBlock; +class Terminator; +class Statement; +class RValue; +class Param; typedef unsigned int BasicBlockId; @@ -78,6 +82,7 @@ public: this->bb_idx = bb_idx; this->stmt_idx = stmt_idx; } + unsigned int get_cur_stmt_ofs() const; void set_cur_stmt_term(unsigned int bb_idx) { this->bb_idx = bb_idx; this->stmt_idx = STMT_TERM; @@ -107,4 +112,78 @@ public: } }; + +// -------------------------------------------------------------------- +// MIR_Helper_GetLifetimes +// -------------------------------------------------------------------- +class ValueLifetime +{ + ::std::vector<bool> statements; + +public: + ValueLifetime(::std::vector<bool> stmts): + statements( mv$(stmts) ) + {} + + bool valid_at(size_t ofs) const { + return statements.at(ofs); + } + + // true if this value is used at any point + bool is_used() const { + for(auto v : statements) + if( v ) + return true; + return false; + } + bool overlaps(const ValueLifetime& x) const { + assert(statements.size() == x.statements.size()); + for(unsigned int i = 0; i < statements.size(); i ++) + { + if( statements[i] && x.statements[i] ) + return true; + } + return false; + } + void unify(const ValueLifetime& x) { + assert(statements.size() == x.statements.size()); + for(unsigned int i = 0; i < statements.size(); i ++) + { + if( x.statements[i] ) + statements[i] = true; + } + } +}; + +struct ValueLifetimes +{ + ::std::vector<size_t> m_block_offsets; + ::std::vector<ValueLifetime> m_temporaries; + ::std::vector<ValueLifetime> m_variables; + + bool var_valid(unsigned var_idx, unsigned bb_idx, unsigned stmt_idx) const { + return m_variables.at(var_idx).valid_at( m_block_offsets[bb_idx] + stmt_idx ); + } + bool tmp_valid(unsigned tmp_idx, unsigned bb_idx, unsigned stmt_idx) const { + return m_temporaries.at(tmp_idx).valid_at( m_block_offsets[bb_idx] + stmt_idx ); + } +}; + +namespace visit { + enum class ValUsage { + Move, + Read, + Write, + Borrow, + }; + + extern bool visit_mir_lvalue(const ::MIR::LValue& lv, ValUsage u, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb); + extern bool visit_mir_lvalue(const ::MIR::Param& p, ValUsage u, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb); + extern bool visit_mir_lvalues(const ::MIR::RValue& rval, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb); + extern bool visit_mir_lvalues(const ::MIR::Statement& stmt, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb); + extern bool visit_mir_lvalues(const ::MIR::Terminator& term, ::std::function<bool(const ::MIR::LValue& , ValUsage)> cb); +} // namespace visit + } // namespace MIR + +extern ::MIR::ValueLifetimes MIR_Helper_GetLifetimes(::MIR::TypeResolve& state, const ::MIR::Function& fcn, bool dump_debug); diff --git a/src/mir/mir.cpp b/src/mir/mir.cpp index 30d94a3d..9edc925b 100644 --- a/src/mir/mir.cpp +++ b/src/mir/mir.cpp @@ -24,22 +24,22 @@ namespace MIR { os << (e.v ? "true" : "false"); ), (Bytes, - os << "["; + os << "b\""; os << ::std::hex; for(auto v : e) - os << static_cast<unsigned int>(v) << " "; - os << ::std::dec; - os << "]"; - ), - (StaticString, - os << "\""; - for(auto v : e) { + { if( ' ' <= v && v < 0x7F && v != '"' && v != '\\' ) os << v; + else if( v < 16 ) + os << "\\x0" << (unsigned int)v; else - os << "\\u{" << FMT(::std::hex << (unsigned int)v) << "}"; + os << "\\x" << ((unsigned int)v & 0xFF); } os << "\""; + os << ::std::dec; + ), + (StaticString, + os << "\"" << FmtEscaped(e) << "\""; ), (Const, os << e.p; @@ -50,34 +50,40 @@ namespace MIR { ) return os; } - bool Constant::operator==(const Constant& b) const + ::Ordering Constant::ord(const Constant& b) const { if( this->tag() != b.tag() ) - return false; + return ::ord( static_cast<unsigned int>(this->tag()), b.tag() ); TU_MATCHA( (*this,b), (ae,be), (Int, - return ae.v == be.v && ae.t == be.t; + if( ae.v != be.v ) + return ::ord(ae.v, be.v); + return ::ord((unsigned)ae.t, (unsigned)be.t); ), (Uint, - return ae.v == be.v && ae.t == be.t; + if( ae.v != be.v ) + return ::ord(ae.v, be.v); + return ::ord((unsigned)ae.t, (unsigned)be.t); ), (Float, - return ae.v == be.v && ae.t == be.t; + if( ae.v != be.v ) + return ::ord(ae.v, be.v); + return ::ord((unsigned)ae.t, (unsigned)be.t); ), (Bool, - return ae.v == be.v; + return ::ord(ae.v, be.v); ), (Bytes, - return ae == be; + return ::ord(ae, be); ), (StaticString, - return ae == be; + return ::ord(ae, be); ), (Const, - return ae.p == be.p; + return ::ord(ae.p, be.p); ), (ItemAddr, - return ae == be; + return ::ord(ae, be); ) ) throw ""; @@ -460,11 +466,30 @@ namespace MIR { os << "), clobbers=[" << e.clobbers << "], flags=[" << e.flags << "])"; ), (SetDropFlag, + os << "df$" << e.idx << " = "; + if( e.other == ~0u ) + { + os << e.new_val; + } + else + { + os << (e.new_val ? "!" : "") << "df$" << e.other; + } ), (Drop, os << "drop(" << e.slot; if(e.kind == ::MIR::eDropKind::SHALLOW) os << " SHALLOW"; + if(e.flag_idx != ~0u) + os << "IF df$" << e.flag_idx; + os << ")"; + ), + (ScopeEnd, + os << "ScopeEnd("; + for(auto idx : e.vars) + os << "var$" << idx << ","; + for(auto idx : e.tmps) + os << "tmp$" << idx << ","; os << ")"; ) ) diff --git a/src/mir/mir.hpp b/src/mir/mir.hpp index f31ede8d..dc33d673 100644 --- a/src/mir/mir.hpp +++ b/src/mir/mir.hpp @@ -112,10 +112,13 @@ TAGGED_UNION_EX(Constant, (), Int, ( (ItemAddr, ::HIR::Path) // address of a value ), (), (), ( friend ::std::ostream& operator<<(::std::ostream& os, const Constant& v); - bool operator==(const Constant& b) const; - inline bool operator!=(const Constant& b) const { - return !(*this == b); - } + ::Ordering ord(const Constant& b) const; + inline bool operator==(const Constant& b) const { return ord(b) == ::OrdEqual; } + inline bool operator!=(const Constant& b) const { return ord(b) != ::OrdEqual; } + inline bool operator<(const Constant& b) const { return ord(b) == ::OrdLess; } + inline bool operator<=(const Constant& b) const { return ord(b) != ::OrdGreater; } + inline bool operator>(const Constant& b) const { return ord(b) == ::OrdGreater; } + inline bool operator>=(const Constant& b) const { return ord(b) != ::OrdLess; } Constant clone() const; ) ); @@ -269,6 +272,10 @@ TAGGED_UNION(Statement, Assign, eDropKind kind; // NOTE: For the `box` primitive LValue slot; unsigned int flag_idx; // Valid if != ~0u + }), + (ScopeEnd, struct { + ::std::vector<unsigned> vars; + ::std::vector<unsigned> tmps; }) ); extern ::std::ostream& operator<<(::std::ostream& os, const Statement& x); @@ -283,6 +290,7 @@ struct BasicBlock class Function { public: + // TODO: Unify Variables, Temporaries, and Arguments ::std::vector< ::HIR::TypeRef> named_variables; ::std::vector< ::HIR::TypeRef> temporaries; ::std::vector<bool> drop_flags; diff --git a/src/mir/mir_builder.cpp b/src/mir/mir_builder.cpp index 96bb28b3..8d7825fc 100644 --- a/src/mir/mir_builder.cpp +++ b/src/mir/mir_builder.cpp @@ -32,8 +32,14 @@ MirBuilder::MirBuilder(const Span& sp, const StaticTraitResolve& resolve, const m_scopes.push_back( ScopeDef { sp, ScopeType::make_Temporaries({}) } ); m_scope_stack.push_back( 1 ); + + m_if_cond_lval = this->new_temporary(::HIR::CoreType::Bool); + + m_arg_states.reserve( args.size() ); + for(size_t i = 0; i < args.size(); i ++ ) + m_arg_states.push_back( VarState::make_Valid({}) ); m_variable_states.reserve( output.named_variables.size() ); - for(unsigned int i = 0; i < output.named_variables.size(); i ++ ) + for(size_t i = 0; i < output.named_variables.size(); i ++ ) m_variable_states.push_back( VarState::make_Invalid(InvalidType::Uninit) ); } MirBuilder::~MirBuilder() @@ -279,7 +285,7 @@ void MirBuilder::push_stmt_assign(const Span& sp, ::MIR::LValue dst, ::MIR::RVal // Doesn't move ), (MakeDst, - // Doesn't move ptr_val + moved_param(e.ptr_val); moved_param(e.meta_val); ), (Tuple, @@ -301,7 +307,7 @@ void MirBuilder::push_stmt_assign(const Span& sp, ::MIR::LValue dst, ::MIR::RVal // Drop target if populated mark_value_assigned(sp, dst); - m_output.blocks.at(m_current_block).statements.push_back( ::MIR::Statement::make_Assign({ mv$(dst), mv$(val) }) ); + this->push_stmt( sp, ::MIR::Statement::make_Assign({ mv$(dst), mv$(val) }) ); } void MirBuilder::push_stmt_drop(const Span& sp, ::MIR::LValue val, unsigned int flag/*=~0u*/) { @@ -313,11 +319,7 @@ void MirBuilder::push_stmt_drop(const Span& sp, ::MIR::LValue val, unsigned int return ; } - DEBUG("DROP " << val); - - auto stmt = ::MIR::Statement::make_Drop({ ::MIR::eDropKind::DEEP, mv$(val), flag }); - - m_output.blocks.at(m_current_block).statements.push_back( mv$(stmt) ); + this->push_stmt(sp, ::MIR::Statement::make_Drop({ ::MIR::eDropKind::DEEP, mv$(val), flag })); if( flag != ~0u ) { @@ -332,8 +334,7 @@ void MirBuilder::push_stmt_drop_shallow(const Span& sp, ::MIR::LValue val, unsig // TODO: Ensure that the type is a Box? - DEBUG("DROP shallow " << val); - m_output.blocks.at(m_current_block).statements.push_back( ::MIR::Statement::make_Drop({ ::MIR::eDropKind::SHALLOW, mv$(val), flag }) ); + this->push_stmt(sp, ::MIR::Statement::make_Drop({ ::MIR::eDropKind::SHALLOW, mv$(val), flag })); if( flag != ~0u ) { @@ -350,17 +351,25 @@ void MirBuilder::push_stmt_asm(const Span& sp, ::MIR::Statement::Data_Asm data) mark_value_assigned(sp, v.second); // 2. Push - m_output.blocks.at(m_current_block).statements.push_back( ::MIR::Statement::make_Asm( mv$(data) ) ); + this->push_stmt(sp, ::MIR::Statement::make_Asm( mv$(data) )); } void MirBuilder::push_stmt_set_dropflag_val(const Span& sp, unsigned int idx, bool value) { - ASSERT_BUG(sp, m_block_active, "Pushing statement with no active block"); - m_output.blocks.at(m_current_block).statements.push_back( ::MIR::Statement::make_SetDropFlag({ idx, value }) ); + this->push_stmt(sp, ::MIR::Statement::make_SetDropFlag({ idx, value })); } void MirBuilder::push_stmt_set_dropflag_other(const Span& sp, unsigned int idx, unsigned int other) { + this->push_stmt(sp, ::MIR::Statement::make_SetDropFlag({ idx, false, other })); +} +void MirBuilder::push_stmt_set_dropflag_default(const Span& sp, unsigned int idx) +{ + this->push_stmt(sp, ::MIR::Statement::make_SetDropFlag({ idx, this->get_drop_flag_default(sp, idx) })); +} +void MirBuilder::push_stmt(const Span& sp, ::MIR::Statement stmt) +{ ASSERT_BUG(sp, m_block_active, "Pushing statement with no active block"); - m_output.blocks.at(m_current_block).statements.push_back( ::MIR::Statement::make_SetDropFlag({ idx, false, other }) ); + DEBUG(stmt); + m_output.blocks.at(m_current_block).statements.push_back( mv$(stmt) ); } void MirBuilder::mark_value_assigned(const Span& sp, const ::MIR::LValue& dst) @@ -769,6 +778,21 @@ void MirBuilder::terminate_scope(const Span& sp, ScopeHandle scope, bool emit_cl { // 2. Emit drops for all non-moved variables (share with below) drop_scope_values(scope_def); + + // Emit ScopeEnd for all controlled values + ::MIR::Statement::Data_ScopeEnd se; + if(const auto* e = scope_def.data.opt_Variables() ) { + se.vars = e->vars; + } + else if(const auto* e = scope_def.data.opt_Temporaries()) { + se.tmps = e->temporaries; + } + else { + } + // Only push the ScopeEnd if there were variables to end + if( !se.vars.empty() || !se.tmps.empty() ) { + this->push_stmt(sp, ::MIR::Statement( mv$(se) )); + } } // 3. Pop scope (last because `drop_scope_values` uses the stack) @@ -830,7 +854,7 @@ namespace { static void merge_state(const Span& sp, MirBuilder& builder, const ::MIR::LValue& lv, VarState& old_state, const VarState& new_state) { - DEBUG(lv << " : " << old_state << " <= " << new_state); + TRACE_FUNCTION_FR(lv << " : " << old_state << " <= " << new_state, lv << " : " << old_state); switch(old_state.tag()) { case VarState::TAGDEAD: throw ""; @@ -852,6 +876,7 @@ namespace #if 1 auto new_flag = builder.new_drop_flag(false); builder.push_stmt_set_dropflag_other(sp, new_flag, flag_idx); + builder.push_stmt_set_dropflag_default(sp, flag_idx); old_state = VarState::make_Optional( new_flag ); #else // TODO: Rewrite history. I.e. visit all previous branches and set this drop flag to `false` in all of them @@ -863,48 +888,88 @@ namespace } return ; } - case VarState::TAG_Partial: { - const auto& nse = new_state.as_Partial(); + case VarState::TAG_MovedOut: { + const auto& nse = new_state.as_MovedOut(); + + // Create a new state that is internally valid and uses the same drop flag + old_state = VarState::make_MovedOut({ box$(old_state.clone()), nse.outer_flag }); + auto& ose = old_state.as_MovedOut(); + if( ose.outer_flag != ~0u ) + { + // If the flag's default isn't false, then create a new flag that does have such a default + // - Other arm (old_state) uses default, this arm (new_state) can be manipulated + if( builder.get_drop_flag_default(sp, ose.outer_flag) != false ) + { + auto new_flag = builder.new_drop_flag(false); + builder.push_stmt_set_dropflag_other(sp, new_flag, nse.outer_flag); + builder.push_stmt_set_dropflag_default(sp, nse.outer_flag); + ose.outer_flag = new_flag; + } + } + else + { + // In the old arm, the container isn't valid. Create a drop flag with a default of false and set it to true + ose.outer_flag = builder.new_drop_flag(false); + builder.push_stmt_set_dropflag_val(sp, ose.outer_flag, true); + } + bool is_box = false; - builder.with_val_type(sp, lv, [&](const auto& ty){ is_box = builder.is_type_owned_box(ty); }); - if( is_box ) { - ASSERT_BUG(sp, nse.inner_states.size() == 1, ""); + builder.with_val_type(sp, lv, [&](const auto& ty){ + is_box = builder.is_type_owned_box(ty); + }); + if( is_box ) + { + merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), *ose.inner_state, *nse.inner_state); } - if( nse.outer_flag != ~0u ) { - // Set the outer flag to `true` if its default isn't true - if( builder.get_drop_flag_default(sp, nse.outer_flag) != false ) { - builder.push_stmt_set_dropflag_val(sp, nse.outer_flag, false); - } + else + { + BUG(sp, "Handle MovedOut on non-Box"); } + return ; + } + case VarState::TAG_Partial: { + const auto& nse = new_state.as_Partial(); + bool is_enum = false; + builder.with_val_type(sp, lv, [&](const auto& ty){ + is_enum = ty.m_data.is_Path() && ty.m_data.as_Path().binding.is_Enum(); + }); - auto out = new_state.clone(); - auto& ose = out.as_Partial(); - if( ose.outer_flag == ~0u ) + // Create a partial filled with Invalid { - ose.outer_flag = builder.new_drop_flag_and_set(sp, true); // Only in this arm is the container valid + ::std::vector<VarState> inner; inner.reserve( nse.inner_states.size() ); + for(size_t i = 0; i < nse.inner_states.size(); i++) + inner.push_back( old_state.clone() ); + old_state = VarState::make_Partial({ mv$(inner) }); } - if( is_box ) { - merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), ose.inner_states[0], old_state); + auto& ose = old_state.as_Partial(); + if( is_enum ) { + for(size_t i = 0; i < ose.inner_states.size(); i ++) + { + merge_state(sp, builder, ::MIR::LValue::make_Downcast({ box$(lv.clone()), static_cast<unsigned int>(i) }), ose.inner_states[i], nse.inner_states[i]); + } } else { for(unsigned int i = 0; i < ose.inner_states.size(); i ++ ) { - merge_state(sp, builder, ::MIR::LValue::make_Field({ box$(lv.clone()), i }), ose.inner_states[i], old_state); + merge_state(sp, builder, ::MIR::LValue::make_Field({ box$(lv.clone()), i }), ose.inner_states[i], nse.inner_states[i]); } } - old_state = mv$(out); } return; } break; + // Valid <= ... case VarState::TAG_Valid: switch( new_state.tag() ) { case VarState::TAGDEAD: throw ""; + // Valid <= Invalid case VarState::TAG_Invalid: old_state = VarState::make_Optional( builder.new_drop_flag_and_set(sp, false) ); return ; + // Valid <= Valid case VarState::TAG_Valid: return ; + // Valid <= Optional case VarState::TAG_Optional: { auto flag_idx = new_state.as_Optional(); // Was valid, now optional. @@ -913,6 +978,7 @@ namespace #if 1 auto new_flag = builder.new_drop_flag(true); builder.push_stmt_set_dropflag_other(sp, new_flag, flag_idx); + builder.push_stmt_set_dropflag_default(sp, flag_idx); old_state = VarState::make_Optional( new_flag ); #else // OR: Push an assign of this flag to every other completed arm @@ -931,39 +997,79 @@ namespace } return ; } - case VarState::TAG_Partial: { - const auto& nse = new_state.as_Partial(); + // Valid <= MovedOut + case VarState::TAG_MovedOut: { + const auto& nse = new_state.as_MovedOut(); + + // Create a new staet that is internally valid and uses the same drop flag + old_state = VarState::make_MovedOut({ box$(VarState::make_Valid({})), nse.outer_flag }); + auto& ose = old_state.as_MovedOut(); + if( ose.outer_flag != ~0u ) + { + // If the flag's default isn't true, then create a new flag that does have such a default + // - Other arm (old_state) uses default, this arm (new_state) can be manipulated + if( builder.get_drop_flag_default(sp, ose.outer_flag) != true ) + { + auto new_flag = builder.new_drop_flag(true); + builder.push_stmt_set_dropflag_other(sp, new_flag, nse.outer_flag); + builder.push_stmt_set_dropflag_default(sp, nse.outer_flag); + ose.outer_flag = new_flag; + } + } + else + { + // In both arms, the container is valid. No need for a drop flag + } + bool is_box = false; - builder.with_val_type(sp, lv, [&](const auto& ty){ is_box = builder.is_type_owned_box(ty); }); + builder.with_val_type(sp, lv, [&](const auto& ty){ + is_box = builder.is_type_owned_box(ty); + }); + if( is_box ) { - ASSERT_BUG(sp, nse.inner_states.size() == 1, ""); + merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), *ose.inner_state, *nse.inner_state); } - if( nse.outer_flag != ~0u ) { - // Set the outer flag to `true` if its default isn't true - if( builder.get_drop_flag_default(sp, nse.outer_flag) != true ) { - builder.push_stmt_set_dropflag_val(sp, nse.outer_flag, true); - } + else { + BUG(sp, "MovedOut on non-Box"); } + return; + } + // Valid <= Partial + case VarState::TAG_Partial: { + const auto& nse = new_state.as_Partial(); + bool is_enum = false; + builder.with_val_type(sp, lv, [&](const auto& ty){ + is_enum = ty.m_data.is_Path() && ty.m_data.as_Path().binding.is_Enum(); + }); - auto out = new_state.clone(); - auto& ose = out.as_Partial(); - if( ose.outer_flag == ~0u ) + // Create a partial filled with Valid { - ose.outer_flag = builder.new_drop_flag(true); // In both arms, the container is valid + ::std::vector<VarState> inner; inner.reserve( nse.inner_states.size() ); + for(size_t i = 0; i < nse.inner_states.size(); i++) + inner.push_back( VarState::make_Valid({}) ); + old_state = VarState::make_Partial({ mv$(inner) }); } - if( is_box ) { - merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), ose.inner_states[0], old_state); + auto& ose = old_state.as_Partial(); + if( is_enum ) { + auto ilv = ::MIR::LValue::make_Downcast({ box$(lv.clone()), 0 }); + for(size_t i = 0; i < ose.inner_states.size(); i ++) + { + merge_state(sp, builder, ilv, ose.inner_states[i], nse.inner_states[i]); + ilv.as_Downcast().variant_index ++; + } } else { + auto ilv = ::MIR::LValue::make_Field({ box$(lv.clone()), 0 }); for(unsigned int i = 0; i < ose.inner_states.size(); i ++ ) { - merge_state(sp, builder, ::MIR::LValue::make_Field({ box$(lv.clone()), i }), ose.inner_states[i], old_state); + merge_state(sp, builder, ilv, ose.inner_states[i], nse.inner_states[i]); + ilv.as_Field().field_index ++; } } - old_state = mv$(out); } return; } break; + // Optional <= ... case VarState::TAG_Optional: switch( new_state.tag() ) { @@ -978,46 +1084,134 @@ namespace if( old_state.as_Optional() != new_state.as_Optional() ) { #if 1 builder.push_stmt_set_dropflag_other(sp, old_state.as_Optional(), new_state.as_Optional()); + builder.push_stmt_set_dropflag_default(sp, new_state.as_Optional()); #else // TODO: Rewrite history replacing one flag with another (if they have the same default) #endif } return ; - case VarState::TAG_Partial: - TODO(sp, "Handle Optional->Partial in split scope"); + case VarState::TAG_MovedOut: + TODO(sp, "Handle Optional->MovedOut in split scope"); + case VarState::TAG_Partial: { + const auto& nse = new_state.as_Partial(); + bool is_enum = false; + builder.with_val_type(sp, lv, [&](const auto& ty){ + assert( !builder.is_type_owned_box(ty) ); + is_enum = ty.m_data.is_Path() && ty.m_data.as_Path().binding.is_Enum(); + }); + // Create a Partial filled with copies of the Optional + { + ::std::vector<VarState> inner; + inner.reserve( nse.inner_states.size() ); + for(size_t i = 0; i < nse.inner_states.size(); i ++) + inner.push_back(old_state.clone()); + old_state = VarState::make_Partial({ mv$(inner) }); + } + auto& ose = old_state.as_Partial(); + // Propagate to inners + if( is_enum ) { + for(size_t i = 0; i < ose.inner_states.size(); i ++) + { + merge_state(sp, builder, ::MIR::LValue::make_Downcast({ box$(lv.clone()), static_cast<unsigned int>(i) }), ose.inner_states[i], nse.inner_states[i]); + } + } + else { + for(unsigned int i = 0; i < ose.inner_states.size(); i ++ ) + { + merge_state(sp, builder, ::MIR::LValue::make_Field({ box$(lv.clone()), i }), ose.inner_states[i], nse.inner_states[i]); + } + } + return; } } break; - case VarState::TAG_Partial: { - auto& ose = old_state.as_Partial(); + case VarState::TAG_MovedOut: { + auto& ose = old_state.as_MovedOut(); bool is_box = false; - builder.with_val_type(sp, lv, [&](const auto& ty){ is_box = builder.is_type_owned_box(ty); }); - if( is_box ) { - ASSERT_BUG(sp, ose.inner_states.size() == 1, ""); + builder.with_val_type(sp, lv, [&](const auto& ty){ + is_box = builder.is_type_owned_box(ty); + }); + if( !is_box ) { + BUG(sp, "MovedOut on non-Box"); } - // Need to tag for conditional shallow drop? Or just do that at the end of the split? - // - End of the split means that the only optional state is outer drop. switch( new_state.tag() ) { case VarState::TAGDEAD: throw ""; case VarState::TAG_Invalid: - if( ose.outer_flag != ~0u ) { - // Set the outer flag to `false` if its default isn't false - if( builder.get_drop_flag_default(sp, ose.outer_flag) != false ) { + case VarState::TAG_Valid: { + bool is_valid = new_state.is_Valid(); + if( ose.outer_flag == ~0u ) + { + // If not valid in new arm, then the outer state is conditional + if( !is_valid ) + { + ose.outer_flag = builder.new_drop_flag(true); builder.push_stmt_set_dropflag_val(sp, ose.outer_flag, false); } } - if( 0 ) - // - Fall through - case VarState::TAG_Valid: - if( ose.outer_flag != ~0u ) { - // Set the outer flag to `true` if its default isn't true - if( builder.get_drop_flag_default(sp, ose.outer_flag) != true ) { - builder.push_stmt_set_dropflag_val(sp, ose.outer_flag, true); + else + { + builder.push_stmt_set_dropflag_val(sp, ose.outer_flag, is_valid); + } + + merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), *ose.inner_state, new_state); + return ; } + case VarState::TAG_Optional: { + const auto& nse = new_state.as_Optional(); + if( ose.outer_flag == ~0u ) + { + if( ! builder.get_drop_flag_default(sp, nse) ) + { + // Default wasn't true, need to make a new flag that does have a default of true + auto new_flag = builder.new_drop_flag(true); + builder.push_stmt_set_dropflag_other(sp, new_flag, nse); + builder.push_stmt_set_dropflag_default(sp, nse); + ose.outer_flag = new_flag; + } + else + { + ose.outer_flag = nse; } } - - if( is_box ) { - merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), ose.inner_states[0], new_state); + else + { + // In this arm, assign the outer state to this drop flag + builder.push_stmt_set_dropflag_other(sp, ose.outer_flag, nse); + builder.push_stmt_set_dropflag_default(sp, nse); + } + merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), *ose.inner_state, new_state); + return; } + case VarState::TAG_MovedOut: { + const auto& nse = new_state.as_MovedOut(); + if( ose.outer_flag != nse.outer_flag ) + { + TODO(sp, "Handle mismatched flags in MovedOut"); + } + merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), *ose.inner_state, *nse.inner_state); + return; } + case VarState::TAG_Partial: + BUG(sp, "MovedOut->Partial not valid"); + } + break; } + case VarState::TAG_Partial: { + auto& ose = old_state.as_Partial(); + bool is_enum = false; + builder.with_val_type(sp, lv, [&](const auto& ty){ + assert( !builder.is_type_owned_box(ty) ); + is_enum = ty.m_data.is_Path() && ty.m_data.as_Path().binding.is_Enum(); + }); + // Need to tag for conditional shallow drop? Or just do that at the end of the split? + // - End of the split means that the only optional state is outer drop. + switch( new_state.tag() ) + { + case VarState::TAGDEAD: throw ""; + case VarState::TAG_Invalid: + case VarState::TAG_Valid: + case VarState::TAG_Optional: + if( is_enum ) { + for(size_t i = 0; i < ose.inner_states.size(); i ++) + { + merge_state(sp, builder, ::MIR::LValue::make_Downcast({ box$(lv.clone()), static_cast<unsigned int>(i) }), ose.inner_states[i], new_state); + } } else { for(unsigned int i = 0; i < ose.inner_states.size(); i ++ ) @@ -1026,17 +1220,16 @@ namespace } } return ; - case VarState::TAG_Optional: { - //auto flag_idx = new_state.as_Optional(); - TODO(sp, "Handle Partial->Optional in split scope"); - } return; + case VarState::TAG_MovedOut: + BUG(sp, "Partial->MovedOut not valid"); case VarState::TAG_Partial: { const auto& nse = new_state.as_Partial(); ASSERT_BUG(sp, ose.inner_states.size() == nse.inner_states.size(), "Partial->Partial with mismatched sizes - " << old_state << " <= " << new_state); - ASSERT_BUG(sp, ose.outer_flag == nse.outer_flag, "Partial->Partial with mismatched drop flags - " << old_state << " <= " << new_state); - if( is_box ) { - ASSERT_BUG(sp, nse.inner_states.size() == 1, ""); - merge_state(sp, builder, ::MIR::LValue::make_Deref({ box$(lv.clone()) }), ose.inner_states[0], nse.inner_states[0]); + if( is_enum ) { + for(size_t i = 0; i < ose.inner_states.size(); i ++) + { + merge_state(sp, builder, ::MIR::LValue::make_Downcast({ box$(lv.clone()), static_cast<unsigned int>(i) }), ose.inner_states[i], nse.inner_states[i]); + } } else { for(unsigned int i = 0; i < ose.inner_states.size(); i ++ ) @@ -1397,37 +1590,62 @@ void MirBuilder::with_val_type(const Span& sp, const ::MIR::LValue& val, ::std:: BUG(sp, "Downcast on unexpected type - " << ty); ), (Path, - //ASSERT_BUG(sp, !te.binding.is_Unbound(), "Unbound path " << ty << " encountered"); - ASSERT_BUG(sp, te.binding.is_Enum(), "Downcast on non-Enum - " << ty << " for " << val); - const auto& enm = *te.binding.as_Enum(); - const auto& variants = enm.m_variants; - ASSERT_BUG(sp, e.variant_index < variants.size(), "Variant index out of range"); - const auto& variant = variants[e.variant_index]; - // TODO: Make data variants refer to associated types (unify enum and struct handling) - TU_MATCHA( (variant.second), (ve), - (Value, - ), - (Unit, - ), - (Tuple, - // HACK! Create tuple. - ::std::vector< ::HIR::TypeRef> tys; - for(const auto& fld : ve) - tys.push_back( monomorphise_type(sp, enm.m_params, te.path.m_data.as_Generic().m_params, fld.ent) ); - ::HIR::TypeRef tup( mv$(tys) ); - m_resolve.expand_associated_types(sp, tup); - cb(tup); - ), - (Struct, - // HACK! Create tuple. - ::std::vector< ::HIR::TypeRef> tys; - for(const auto& fld : ve) - tys.push_back( monomorphise_type(sp, enm.m_params, te.path.m_data.as_Generic().m_params, fld.second.ent) ); - ::HIR::TypeRef tup( mv$(tys) ); - m_resolve.expand_associated_types(sp, tup); - cb(tup); + // TODO: Union? + if( const auto* pbe = te.binding.opt_Enum() ) + { + const auto& enm = **pbe; + const auto& variants = enm.m_variants; + ASSERT_BUG(sp, e.variant_index < variants.size(), "Variant index out of range"); + const auto& variant = variants[e.variant_index]; + // TODO: Make data variants refer to associated types (unify enum and struct handling) + TU_MATCHA( (variant.second), (ve), + (Value, + DEBUG(""); + cb(::HIR::TypeRef::new_unit()); + ), + (Unit, + cb(::HIR::TypeRef::new_unit()); + ), + (Tuple, + // HACK! Create tuple. + ::std::vector< ::HIR::TypeRef> tys; + for(const auto& fld : ve) + tys.push_back( monomorphise_type(sp, enm.m_params, te.path.m_data.as_Generic().m_params, fld.ent) ); + ::HIR::TypeRef tup( mv$(tys) ); + m_resolve.expand_associated_types(sp, tup); + cb(tup); + ), + (Struct, + // HACK! Create tuple. + ::std::vector< ::HIR::TypeRef> tys; + for(const auto& fld : ve) + tys.push_back( monomorphise_type(sp, enm.m_params, te.path.m_data.as_Generic().m_params, fld.second.ent) ); + ::HIR::TypeRef tup( mv$(tys) ); + m_resolve.expand_associated_types(sp, tup); + cb(tup); + ) ) - ) + } + else if( const auto* pbe = te.binding.opt_Union() ) + { + const auto& unm = **pbe; + ASSERT_BUG(sp, e.variant_index < unm.m_variants.size(), "Variant index out of range"); + const auto& variant = unm.m_variants.at(e.variant_index); + const auto& fld = variant.second; + + if( monomorphise_type_needed(fld.ent) ) { + auto sty = monomorphise_type(sp, unm.m_params, te.path.m_data.as_Generic().m_params, fld.ent); + m_resolve.expand_associated_types(sp, sty); + cb(sty); + } + else { + cb(fld.ent); + } + } + else + { + BUG(sp, "Downcast on non-Enum/Union - " << ty << " for " << val); + } ) ) }); @@ -1442,185 +1660,380 @@ bool MirBuilder::lvalue_is_copy(const Span& sp, const ::MIR::LValue& val) const DEBUG("[lvalue_is_copy] ty="<<ty); rv = (m_resolve.type_is_copy(sp, ty) ? 2 : 1); }); - assert(rv != 0); + ASSERT_BUG(sp, rv != 0, "Type for " << val << " can't be determined"); return rv == 2; } -const VarState& MirBuilder::get_variable_state(const Span& sp, unsigned int idx, unsigned int skip_count) const +const VarState& MirBuilder::get_slot_state(const Span& sp, VarGroup ty, unsigned int idx, unsigned int skip_count/*=0*/) const { + // 1. Find an applicable Split scope for( auto scope_idx : ::reverse(m_scope_stack) ) { const auto& scope_def = m_scopes.at(scope_idx); TU_MATCH_DEF( ScopeType, (scope_def.data), (e), ( ), + (Temporaries, + if( ty == VarGroup::Temporary ) + { + auto it = ::std::find(e.temporaries.begin(), e.temporaries.end(), idx); + if( it != e.temporaries.end() ) { + break ; + } + } + ), (Variables, - auto it = ::std::find(e.vars.begin(), e.vars.end(), idx); - if( it != e.vars.end() ) { - // If controlled by this block, exit early (won't find it elsewhere) - break ; + if( ty == VarGroup::Variable ) + { + auto it = ::std::find(e.vars.begin(), e.vars.end(), idx); + if( it != e.vars.end() ) { + // If controlled by this block, exit early (won't find it elsewhere) + break ; + } } ), (Split, const auto& cur_arm = e.arms.back(); - auto it = cur_arm.var_states.find(idx); - if( it != cur_arm.var_states.end() ) + if( ty == VarGroup::Variable ) { - if( ! skip_count -- ) + auto it = cur_arm.var_states.find(idx); + if( it != cur_arm.var_states.end() ) { - return it->second; + if( ! skip_count -- ) + { + return it->second; + } + } + } + else if( ty == VarGroup::Temporary ) + { + auto it = cur_arm.tmp_states.find(idx); + if( it != cur_arm.tmp_states.end() ) + { + if( ! skip_count -- ) + { + return it->second; + } } } ) ) } - - ASSERT_BUG(sp, idx < m_variable_states.size(), "Variable " << idx << " out of range for state table"); - return m_variable_states[idx]; + switch(ty) + { + case VarGroup::Return: + return m_return_state; + case VarGroup::Argument: + ASSERT_BUG(sp, idx < m_arg_states.size(), "Argument " << idx << " out of range for state table"); + return m_arg_states.at(idx); + case VarGroup::Variable: + ASSERT_BUG(sp, idx < m_variable_states.size(), "Variable " << idx << " out of range for state table"); + return m_variable_states[idx]; + case VarGroup::Temporary: + ASSERT_BUG(sp, idx < m_temporary_states.size(), "Temporary " << idx << " out of range for state table"); + return m_temporary_states[idx]; + } + BUG(sp, "Fell off the end of get_slot_state"); } -VarState& MirBuilder::get_variable_state_mut(const Span& sp, unsigned int idx) +VarState& MirBuilder::get_slot_state_mut(const Span& sp, VarGroup ty, unsigned int idx) { VarState* ret = nullptr; for( auto scope_idx : ::reverse(m_scope_stack) ) { auto& scope_def = m_scopes.at(scope_idx); - if( scope_def.data.is_Variables() ) + if( const auto* e = scope_def.data.opt_Variables() ) { - const auto& e = scope_def.data.as_Variables(); - auto it = ::std::find(e.vars.begin(), e.vars.end(), idx); - if( it != e.vars.end() ) { - break ; + if( ty == VarGroup::Variable ) + { + auto it = ::std::find(e->vars.begin(), e->vars.end(), idx); + if( it != e->vars.end() ) { + break ; + } + } + } + else if( const auto* e = scope_def.data.opt_Temporaries() ) + { + if( ty == VarGroup::Temporary ) + { + auto it = ::std::find(e->temporaries.begin(), e->temporaries.end(), idx); + if( it != e->temporaries.end() ) { + break ; + } } } else if( scope_def.data.is_Split() ) { + auto& e = scope_def.data.as_Split(); + auto& cur_arm = e.arms.back(); if( ! ret ) { - auto& e = scope_def.data.as_Split(); - auto& cur_arm = e.arms.back(); - auto it = cur_arm.var_states.find(idx); - if( it == cur_arm.var_states.end() ) + ::std::map<unsigned int, VarState>* states; + switch(ty) { - DEBUG("Split new (scope " << scope_idx << ")"); - ret = &(cur_arm.var_states[idx] = get_variable_state(sp, idx).clone()); + case VarGroup::Return: states = nullptr; break; + case VarGroup::Argument: BUG(sp, "Mutating state of argument"); break; + case VarGroup::Variable: states = &cur_arm.var_states; break; + case VarGroup::Temporary: states = &cur_arm.tmp_states; break; } - else + + if( states ) { - DEBUG("Split existing (scope " << scope_idx << ")"); - ret = &it->second; + auto it = states->find(idx); + if( it == states->end() ) + { + DEBUG("Split new (scope " << scope_idx << ")"); + ret = &( (*states)[idx] = get_slot_state(sp, ty, idx).clone() ); + } + else + { + DEBUG("Split existing (scope " << scope_idx << ")"); + ret = &it->second; + } } } } else if( scope_def.data.is_Loop() ) { auto& e = scope_def.data.as_Loop(); - if( e.changed_vars.count(idx) == 0 ) + ::std::map<unsigned int, VarState>* states = nullptr; + switch(ty) { - auto state = e.exit_state_valid ? get_variable_state(sp, idx).clone() : VarState::make_Valid({}); - e.changed_vars.insert(::std::make_pair( idx, mv$(state) )); + case VarGroup::Return: states = nullptr; break; + case VarGroup::Argument: BUG(sp, "Mutating state of argument"); break; + case VarGroup::Variable: states = &e.changed_vars; break; + case VarGroup::Temporary: states = &e.changed_tmps; break; + } + + if( states ) + { + if( states->count(idx) == 0 ) + { + auto state = e.exit_state_valid ? get_slot_state(sp, ty, idx).clone() : VarState::make_Valid({}); + states->insert(::std::make_pair( idx, mv$(state) )); + } } } else { } } - - if( !ret ) + if( ret ) { - DEBUG("Outer"); - ASSERT_BUG(sp, idx < m_variable_states.size(), "Variable " << idx << " out of range for state table"); - return m_variable_states[idx]; + return *ret; } else { - return *ret; + switch(ty) + { + case VarGroup::Return: + return m_return_state; + case VarGroup::Argument: + ASSERT_BUG(sp, idx < m_arg_states.size(), "Argument " << idx << " out of range for state table"); + return m_arg_states.at(idx); + case VarGroup::Variable: + ASSERT_BUG(sp, idx < m_variable_states.size(), "Variable " << idx << " out of range for state table"); + return m_variable_states[idx]; + case VarGroup::Temporary: + ASSERT_BUG(sp, idx < m_temporary_states.size(), "Temporary " << idx << " out of range for state table"); + return m_temporary_states[idx]; + } + BUG(sp, "Fell off the end of get_slot_state_mut"); } } +const VarState& MirBuilder::get_variable_state(const Span& sp, unsigned int idx, unsigned int skip_count) const +{ + return get_slot_state(sp, VarGroup::Variable, idx, skip_count); +} +VarState& MirBuilder::get_variable_state_mut(const Span& sp, unsigned int idx) +{ + return get_slot_state_mut(sp, VarGroup::Variable, idx); +} const VarState& MirBuilder::get_temp_state(const Span& sp, unsigned int idx, unsigned int skip_count) const { - for( auto scope_idx : ::reverse(m_scope_stack) ) - { - const auto& scope_def = m_scopes.at(scope_idx); - if( scope_def.data.is_Temporaries() ) + return get_slot_state(sp, VarGroup::Temporary, idx, skip_count); +} +VarState& MirBuilder::get_temp_state_mut(const Span& sp, unsigned int idx) +{ + return get_slot_state_mut(sp, VarGroup::Temporary, idx); +} + +const VarState& MirBuilder::get_val_state(const Span& sp, const ::MIR::LValue& lv, unsigned int skip_count) +{ + TODO(sp, ""); +} +VarState& MirBuilder::get_val_state_mut(const Span& sp, const ::MIR::LValue& lv) +{ + TRACE_FUNCTION_F(lv); + TU_MATCHA( (lv), (e), + (Variable, + return get_slot_state_mut(sp, VarGroup::Variable, e); + ), + (Temporary, + return get_slot_state_mut(sp, VarGroup::Temporary, e.idx); + ), + (Argument, + return get_slot_state_mut(sp, VarGroup::Argument, e.idx); + ), + (Static, + BUG(sp, "Attempting to mutate state of a static"); + ), + (Return, + BUG(sp, "Move of return value"); + return get_slot_state_mut(sp, VarGroup::Return, 0); + ), + (Field, + auto& ivs = get_val_state_mut(sp, *e.val); + VarState tpl; + TU_MATCHA( (ivs), (ivse), + (Invalid, + //BUG(sp, "Mutating inner state of an invalidated composite - " << lv); + tpl = VarState::make_Valid({}); + ), + (MovedOut, + BUG(sp, "Field on value with MovedOut state - " << lv); + ), + (Partial, + ), + (Optional, + tpl = ivs.clone(); + ), + (Valid, + tpl = VarState::make_Valid({}); + ) + ) + if( !ivs.is_Partial() ) { - const auto& e = scope_def.data.as_Temporaries(); - auto it = ::std::find(e.temporaries.begin(), e.temporaries.end(), idx); - if( it != e.temporaries.end() ) { - break ; - } + size_t n_flds = 0; + with_val_type(sp, *e.val, [&](const auto& ty) { + DEBUG("ty = " << ty); + if(const auto* e = ty.m_data.opt_Path()) { + ASSERT_BUG(sp, e->binding.is_Struct(), ""); + const auto& str = *e->binding.as_Struct(); + TU_MATCHA( (str.m_data), (se), + (Unit, + BUG(sp, "Field access of unit-like struct"); + ), + (Tuple, + n_flds = se.size(); + ), + (Named, + n_flds = se.size(); + ) + ) + } + else if(const auto* e = ty.m_data.opt_Tuple()) { + n_flds = e->size(); + } + else if(const auto* e = ty.m_data.opt_Array()) { + n_flds = e->size_val; + } + else { + TODO(sp, "Determine field count for " << ty); + } + }); + ::std::vector<VarState> inner_vs; inner_vs.reserve(n_flds); + for(size_t i = 0; i < n_flds; i++) + inner_vs.push_back( tpl.clone() ); + ivs = VarState::make_Partial({ mv$(inner_vs) }); } - else if( scope_def.data.is_Split() ) + return ivs.as_Partial().inner_states.at(e.field_index); + ), + (Deref, + // HACK: If the dereferenced type is a Box ("owned_box") then hack in move and shallow drop + bool is_box = false; + if( this->m_lang_Box ) { - const auto& e = scope_def.data.as_Split(); - const auto& cur_arm = e.arms.back(); - auto it = cur_arm.tmp_states.find(idx); - if( it != cur_arm.tmp_states.end() ) - { - if( ! skip_count -- ) - { - return it->second; - } - } + with_val_type(sp, *e.val, [&](const auto& ty){ + DEBUG("ty = " << ty); + is_box = this->is_type_owned_box(ty); + }); } - } - ASSERT_BUG(sp, idx < m_temporary_states.size(), "Temporary " << idx << " out of range for state table"); - return m_temporary_states[idx]; -} -VarState& MirBuilder::get_temp_state_mut(const Span& sp, unsigned int idx) -{ - VarState* ret = nullptr; - for( auto scope_idx : ::reverse(m_scope_stack) ) - { - auto& scope_def = m_scopes.at(scope_idx); - if( scope_def.data.is_Temporaries() ) + + if( is_box ) { - const auto& e = scope_def.data.as_Temporaries(); - auto it = ::std::find(e.temporaries.begin(), e.temporaries.end(), idx); - if( it != e.temporaries.end() ) { - break ; + ::MIR::LValue inner_lv; + // 1. If the inner lvalue isn't a slot with move information, move out of the lvalue into a temporary (with standard temp scope) + TU_MATCH_DEF( ::MIR::LValue, (*e.val), (ei), + ( + with_val_type(sp, *e.val, [&](const auto& ty){ inner_lv = this->new_temporary(ty); }); + this->push_stmt_assign(sp, inner_lv.clone(), ::MIR::RValue( mv$(*e.val) )); + *e.val = inner_lv.clone(); + ), + (Variable, + inner_lv = ::MIR::LValue(ei); + ), + (Temporary, + inner_lv = ::MIR::LValue(ei); + ), + (Argument, + inner_lv = ::MIR::LValue(ei); + ) + ) + // 2. Mark the slot as requiring only a shallow drop + ::std::vector<VarState> inner; + inner.push_back(VarState::make_Valid({})); + auto& ivs = get_val_state_mut(sp, inner_lv); + if( ! ivs.is_MovedOut() ) + { + unsigned int drop_flag = (ivs.is_Optional() ? ivs.as_Optional() : ~0u); + ivs = VarState::make_MovedOut({ box$(VarState::make_Valid({})), drop_flag }); } + return *ivs.as_MovedOut().inner_state; } - else if( scope_def.data.is_Split() ) + else { - if( ! ret ) - { - auto& e = scope_def.data.as_Split(); - auto& cur_arm = e.arms.back(); - auto it = cur_arm.tmp_states.find(idx); - if(it == cur_arm.tmp_states.end()) + BUG(sp, "Move out of deref with non-Copy values - &move? - " << lv << " : " << FMT_CB(ss, this->with_val_type(sp, lv, [&](const auto& ty){ss<<ty;});) ); + } + ), + (Index, + BUG(sp, "Move out of index with non-Copy values - Partial move?"); + ), + (Downcast, + // TODO: What if the inner is Copy? What if the inner is a hidden pointer? + auto& ivs = get_val_state_mut(sp, *e.val); + //static VarState ivs; ivs = VarState::make_Valid({}); + + if( !ivs.is_Partial() ) + { + ASSERT_BUG(sp, !ivs.is_MovedOut(), "Downcast of a MovedOut value"); + + size_t var_count = 0; + with_val_type(sp, *e.val, [&](const auto& ty){ + DEBUG("ty = " << ty); + ASSERT_BUG(sp, ty.m_data.is_Path(), "Downcast on non-Path type - " << ty); + const auto& pb = ty.m_data.as_Path().binding; + // TODO: What about unions? + // - Iirc, you can't move out of them so they will never have state mutated + if( pb.is_Enum() ) + { + const auto& enm = *pb.as_Enum(); + var_count = enm.m_variants.size(); + } + else if( const auto* pbe = pb.opt_Union() ) { - ret = &(cur_arm.tmp_states[idx] = get_temp_state(sp, idx).clone()); + const auto& unm = **pbe; + var_count = unm.m_variants.size(); } else { - ret = &it->second; + BUG(sp, "Downcast on non-Enum/Union - " << ty); } - } - } - else if( scope_def.data.is_Loop() ) - { - auto& e = scope_def.data.as_Loop(); - if( e.changed_tmps.count(idx) == 0 ) + }); + + ::std::vector<VarState> inner; + for(size_t i = 0; i < var_count; i ++) { - auto state = e.exit_state_valid ? get_temp_state(sp, idx).clone() : VarState::make_Valid({}); - e.changed_tmps.insert(::std::make_pair( idx, mv$(state) )); + inner.push_back( VarState::make_Invalid(InvalidType::Uninit) ); } + inner[e.variant_index] = mv$(ivs); + ivs = VarState::make_Partial({ mv$(inner) }); } - else - { - } - } - if( !ret ) - { - ASSERT_BUG(sp, idx < m_temporary_states.size(), "Temporary " << idx << " out of range for state table"); - return m_temporary_states[idx]; - } - else - { - return *ret; - } + return ivs.as_Partial().inner_states.at(e.variant_index); + ) + ) + BUG(sp, "Fell off send of get_val_state_mut"); } void MirBuilder::drop_value_from_state(const Span& sp, const VarState& vs, ::MIR::LValue lv) @@ -1631,12 +2044,41 @@ void MirBuilder::drop_value_from_state(const Span& sp, const VarState& vs, ::MIR (Valid, push_stmt_drop(sp, mv$(lv)); ), + (MovedOut, + bool is_box = false; + with_val_type(sp, lv, [&](const auto& ty){ + is_box = this->is_type_owned_box(ty); + }); + if( is_box ) + { + drop_value_from_state(sp, *vse.inner_state, ::MIR::LValue::make_Deref({ box$(lv.clone()) })); + push_stmt_drop_shallow(sp, mv$(lv), vse.outer_flag); + } + else + { + TODO(sp, ""); + } + ), (Partial, - // TODO: Actual destructuring based on the type - with_val_type(sp, lv, [&](const auto& ty){ ASSERT_BUG(sp, this->is_type_owned_box(ty), "TODO: Partial on non-Box"); }); - assert( vse.inner_states.size() == 1 ); - drop_value_from_state(sp, vse.inner_states[0], ::MIR::LValue::make_Deref({ box$(lv.clone()) })); - push_stmt_drop_shallow(sp, mv$(lv), vse.outer_flag); + bool is_enum = false; + with_val_type(sp, lv, [&](const auto& ty){ + is_enum = ty.m_data.is_Path() && ty.m_data.as_Path().binding.is_Enum(); + }); + if(is_enum) + { + DEBUG("TODO: Switch based on enum value"); + //for(size_t i = 0; i < vse.inner_states.size(); i ++) + //{ + // drop_value_from_state(sp, vse.inner_states[i], ::MIR::LValue::make_Downcast({ box$(lv.clone()), static_cast<unsigned int>(i) })); + //} + } + else + { + for(size_t i = 0; i < vse.inner_states.size(); i ++) + { + drop_value_from_state(sp, vse.inner_states[i], ::MIR::LValue::make_Field({ box$(lv.clone()), static_cast<unsigned int>(i) })); + } + } ), (Optional, push_stmt_drop(sp, mv$(lv), vse); @@ -1672,113 +2114,13 @@ void MirBuilder::drop_scope_values(const ScopeDef& sd) ) } + void MirBuilder::moved_lvalue(const Span& sp, const ::MIR::LValue& lv) { - TRACE_FUNCTION_F(lv); - TU_MATCHA( (lv), (e), - (Variable, - if( !lvalue_is_copy(sp, lv) ) { - DEBUG("var" << e << " = MOVED"); - get_variable_state_mut(sp, e) = VarState::make_Invalid(InvalidType::Moved); - } - ), - (Temporary, - if( !lvalue_is_copy(sp, lv) ) { - DEBUG("tmp" << e.idx << " = MOVED"); - get_temp_state_mut(sp, e.idx) = VarState::make_Invalid(InvalidType::Moved); - } - ), - (Argument, - //TODO(sp, "Mark argument as moved"); - ), - (Static, - //TODO(sp, "Static - Assert that type is Copy"); - ), - (Return, - BUG(sp, "Read of return value"); - ), - (Field, - if( lvalue_is_copy(sp, lv) ) { - } - else { - // TODO: Partial moves of fields. - moved_lvalue(sp, *e.val); - } - ), - (Deref, - if( lvalue_is_copy(sp, lv) ) { - } - else { - // HACK: If the dereferenced type is a Box ("owned_box") then hack in move and shallow drop - if( this->m_lang_Box ) - { - bool is_box = false; - with_val_type(sp, *e.val, [&](const auto& ty){ - DEBUG("ty = " << ty); - is_box = this->is_type_owned_box(ty); - }); - if( is_box ) - { - ::MIR::LValue inner_lv; - // 1. If the inner lvalue isn't a slot with move information, move out of the lvalue into a temporary (with standard temp scope) - TU_MATCH_DEF( ::MIR::LValue, (*e.val), (ei), - ( - with_val_type(sp, *e.val, [&](const auto& ty){ inner_lv = this->new_temporary(ty); }); - this->push_stmt_assign(sp, inner_lv.clone(), ::MIR::RValue( mv$(*e.val) )); - *e.val = inner_lv.clone(); - ), - (Variable, - inner_lv = ::MIR::LValue(ei); - ), - (Temporary, - inner_lv = ::MIR::LValue(ei); - ), - (Argument, - inner_lv = ::MIR::LValue(ei); - ) - ) - // 2. Mark the slot as requiring only a shallow drop - // - TODO: Have a drop flag attached to the - ::std::vector<VarState> ivs; - ivs.push_back(VarState::make_Invalid(InvalidType::Moved)); - TU_MATCH_DEF( ::MIR::LValue, (inner_lv), (ei), - ( - BUG(sp, "Box move out of invalid LValue " << inner_lv << " - should have been moved"); - ), - (Variable, - DEBUG("var" << ei << " = PARTIAL"); - get_variable_state_mut(sp, ei) = VarState::make_Partial({ mv$(ivs) }); - ), - (Temporary, - DEBUG("tmp" << ei.idx << " = PARTIAL"); - get_temp_state_mut(sp, ei.idx) = VarState::make_Partial({ mv$(ivs) }); - ), - (Argument, - TODO(sp, "Mark arg " << ei.idx << " for shallow drop"); - ) - ) - // Early return! - return ; - } - } - BUG(sp, "Move out of deref with non-Copy values - &move? - " << lv << " : " << FMT_CB(ss, this->with_val_type(sp, lv, [&](const auto& ty){ss<<ty;});) ); - moved_lvalue(sp, *e.val); - } - ), - (Index, - if( lvalue_is_copy(sp, lv) ) { - } - else { - BUG(sp, "Move out of index with non-Copy values - Partial move?"); - moved_lvalue(sp, *e.val); - } - moved_lvalue(sp, *e.idx); - ), - (Downcast, - // TODO: What if the inner is Copy? What if the inner is a hidden pointer? - moved_lvalue(sp, *e.val); - ) - ) + if( !lvalue_is_copy(sp, lv) ) { + auto& vs = get_val_state_mut(sp, lv); + vs = VarState::make_Invalid(InvalidType::Moved); + } } const ::MIR::LValue& MirBuilder::get_ptr_to_dst(const Span& sp, const ::MIR::LValue& lv) const @@ -1823,12 +2165,15 @@ VarState VarState::clone() const (Optional, return VarState(e); ), + (MovedOut, + return VarState::make_MovedOut({ box$(e.inner_state->clone()), e.outer_flag }); + ), (Partial, ::std::vector<VarState> n; n.reserve(e.inner_states.size()); for(const auto& a : e.inner_states) n.push_back( a.clone() ); - return VarState::make_Partial({ mv$(n), e.outer_flag }); + return VarState::make_Partial({ mv$(n) }); ) ) throw ""; @@ -1847,9 +2192,12 @@ bool VarState::operator==(VarState& x) const (Optional, return te == xe; ), - (Partial, + (MovedOut, if( te.outer_flag != xe.outer_flag ) return false; + return *te.inner_state == *xe.inner_state; + ), + (Partial, if( te.inner_states.size() != xe.inner_states.size() ) return false; for(unsigned int i = 0; i < te.inner_states.size(); i ++) @@ -1879,12 +2227,16 @@ bool VarState::operator==(VarState& x) const (Optional, os << "Optional(" << e << ")"; ), - (Partial, - os << "Partial("; + (MovedOut, + os << "MovedOut("; if( e.outer_flag == ~0u ) os << "-"; else os << "df" << e.outer_flag; + os << " " << *e.inner_state <<")"; + ), + (Partial, + os << "Partial("; os << ", [" << e.inner_states << "])"; ) ) diff --git a/src/mir/optimise.cpp b/src/mir/optimise.cpp index 2e823821..81574afd 100644 --- a/src/mir/optimise.cpp +++ b/src/mir/optimise.cpp @@ -17,6 +17,13 @@ #include <iomanip> #include <trans/target.hpp> +#define DUMP_BEFORE_ALL 0 +#define DUMP_BEFORE_CONSTPROPAGATE 0 +#define CHECK_AFTER_PASS 0 + +#define DUMP_AFTER_DONE 0 +#define CHECK_AFTER_DONE 1 + namespace { ::MIR::BasicBlockId get_new_target(const ::MIR::TypeResolve& state, ::MIR::BasicBlockId bb) { @@ -185,6 +192,8 @@ namespace { (Drop, // Well, it mutates... rv |= visit_mir_lvalue_mut(e.slot, ValUsage::Write, cb); + ), + (ScopeEnd, ) ) return rv; @@ -436,26 +445,32 @@ void MIR_Optimise(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path // >> Combine Duplicate Blocks change_happened |= MIR_Optimise_UnifyBlocks(state, fcn); - #if 0 if( change_happened ) { - //MIR_Dump_Fcn(::std::cout, fcn); + #if DUMP_AFTER_PASS + if( debug_enabled() ) { + MIR_Dump_Fcn(::std::cout, fcn); + } + #endif + #if CHECK_AFTER_PASS MIR_Validate(resolve, path, fcn, args, ret_type); + #endif } - #endif MIR_Optimise_GarbageCollect_Partial(state, fcn); pass_num += 1; } while( change_happened ); - #if 1 + #if DUMP_AFTER_DONE if( debug_enabled() ) { MIR_Dump_Fcn(::std::cout, fcn); } #endif + #if CHECK_AFTER_DONE // DEFENCE: Run validation _before_ GC (so validation errors refer to the pre-gc numbers) MIR_Validate(resolve, path, fcn, args, ret_type); + #endif // GC pass on blocks and variables // - Find unused blocks, then delete and rewrite all references. MIR_Optimise_GarbageCollect(state, fcn); @@ -469,6 +484,31 @@ bool MIR_Optimise_BlockSimplify(::MIR::TypeResolve& state, ::MIR::Function& fcn) // >> Replace targets that point to a block that is just a goto for(auto& block : fcn.blocks) { + // Unify sequential ScopeEnd statements + if( block.statements.size() > 1 ) + { + for(auto it = block.statements.begin() + 1; it != block.statements.end(); ) + { + if( (it-1)->is_ScopeEnd() && it->is_ScopeEnd() ) + { + auto& dst = (it-1)->as_ScopeEnd(); + const auto& src = it->as_ScopeEnd(); + DEBUG("Unify " << *(it-1) << " and " << *it); + for(auto v : src.vars) + dst.vars.push_back(v); + for(auto v : src.tmps) + dst.tmps.push_back(v); + ::std::sort(dst.vars.begin(), dst.vars.end()); + ::std::sort(dst.tmps.begin(), dst.tmps.end()); + it = block.statements.erase(it); + } + else + { + ++ it; + } + } + } + TU_MATCHA( (block.terminator), (e), (Incomplete, ), @@ -589,7 +629,7 @@ bool MIR_Optimise_BlockSimplify(::MIR::TypeResolve& state, ::MIR::Function& fcn) bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn) { TRACE_FUNCTION; - + struct H { static bool can_inline(const ::HIR::Path& path, const ::MIR::Function& fcn) @@ -686,22 +726,21 @@ bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn) return rv; } - ::MIR::BasicBlock clone_bb(const ::MIR::BasicBlock& src) const + ::MIR::BasicBlock clone_bb(const ::MIR::BasicBlock& src, unsigned src_idx, unsigned new_idx) const { ::MIR::BasicBlock rv; rv.statements.reserve( src.statements.size() ); for(const auto& stmt : src.statements) { + DEBUG("BB" << src_idx << "->BB" << new_idx << "/" << rv.statements.size() << ": " << stmt); TU_MATCHA( (stmt), (se), (Assign, - DEBUG(se.dst << " = " << se.src); rv.statements.push_back( ::MIR::Statement::make_Assign({ this->clone_lval(se.dst), this->clone_rval(se.src) }) ); ), (Asm, - DEBUG("asm!"); rv.statements.push_back( ::MIR::Statement::make_Asm({ se.tpl, this->clone_name_lval_vec(se.outputs), @@ -711,7 +750,6 @@ bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn) }) ); ), (SetDropFlag, - DEBUG("df" << se.idx << " = "); rv.statements.push_back( ::MIR::Statement::make_SetDropFlag({ this->df_base + se.idx, se.new_val, @@ -719,17 +757,27 @@ bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn) }) ); ), (Drop, - DEBUG("drop " << se.slot); rv.statements.push_back( ::MIR::Statement::make_Drop({ se.kind, this->clone_lval(se.slot), se.flag_idx == ~0u ? ~0u : this->df_base + se.flag_idx }) ); + ), + (ScopeEnd, + ::MIR::Statement::Data_ScopeEnd new_se; + new_se.vars.reserve(se.vars.size()); + for(auto idx : se.vars) + new_se.vars.push_back(this->var_base + idx); + new_se.tmps.reserve(se.tmps.size()); + for(auto idx : se.tmps) + new_se.tmps.push_back(this->tmp_base + idx); + rv.statements.push_back(::MIR::Statement( mv$(new_se) )); ) ) } - DEBUG(src.terminator); + DEBUG("BB" << src_idx << "->BB" << new_idx << "/" << rv.statements.size() << ": " << src.terminator); rv.terminator = this->clone_term(src.terminator); + DEBUG("-> " << rv.terminator); return rv; } ::MIR::Terminator clone_term(const ::MIR::Terminator& src) const @@ -981,7 +1029,7 @@ bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn) new_blocks.reserve( called_mir->blocks.size() ); for(const auto& bb : called_mir->blocks) { - new_blocks.push_back( cloner.clone_bb(bb) ); + new_blocks.push_back( cloner.clone_bb(bb, (&bb - called_mir->blocks.data()), fcn.blocks.size() + new_blocks.size()) ); } // > Append new temporaries for(auto& val : cloner.const_assignments) @@ -1006,6 +1054,7 @@ bool MIR_Optimise_Inlining(::MIR::TypeResolve& state, ::MIR::Function& fcn) return inline_happened; } + // -------------------------------------------------------------------- // If two temporaries don't overlap in lifetime (blocks in which they're valid), unify the two // -------------------------------------------------------------------- @@ -1036,444 +1085,9 @@ bool MIR_Optimise_UnifyTemporaries(::MIR::TypeResolve& state, ::MIR::Function& f return false; } - class VarLifetime - { - ::std::vector<bool> statements; - - public: - VarLifetime(::std::vector<bool> stmts): - statements( mv$(stmts) ) - {} - - // true if this value is used at any point - bool is_used() const { - for(auto v : statements) - if( v ) - return true; - return false; - } - bool overlaps(const VarLifetime& x) const { - assert(statements.size() == x.statements.size()); - for(unsigned int i = 0; i < statements.size(); i ++) - { - if( statements[i] && x.statements[i] ) - return true; - } - return false; - } - void unify(const VarLifetime& x) { - assert(statements.size() == x.statements.size()); - for(unsigned int i = 0; i < statements.size(); i ++) - { - if( x.statements[i] ) - statements[i] = true; - } - } - }; - //::std::vector<VarLifetime> var_lifetimes; - ::std::vector<VarLifetime> tmp_lifetimes; - - - // New algorithm notes: - // --- - // The lifetime of a value starts when it is written, and ends the last time it is read - // - When a variable is read, end any existing lifetime and start a new one. - // - When the value is read, update the end of its lifetime. - // --- - // A lifetime is a range in the call graph (with a start and end, including list of blocks) - // - Representation: Bitmap with a bit per statement. - // - Record the current block path in general state, along with known active lifetimes - - // TODO: Move all this code to a helper to other passes can use it. - { - // Scan through all possible paths in the graph (with loopback detection using a memory of the path) - // - If a loop is detected, determine if there were changes to the lifetime set during that pass - // > Changes are noticed by recording in the state structure when it triggers a change in the lifetime - // map. - struct Position - { - size_t path_index = 0; // index into the block path. - unsigned int stmt_idx = 0; - - bool operator==(const Position& x) const { - return path_index == x.path_index && stmt_idx == x.stmt_idx; - } - }; - struct ProtoLifetime - { - Position start; - Position end; - - bool is_empty() const { - return start == end; - } - }; - static unsigned NEXT_INDEX = 0; - struct State - { - unsigned int index = 0; - ::std::vector<unsigned int> block_path; - ::std::vector<unsigned int> block_change_idx; - unsigned int cur_change_idx = 0; - - // if read, update. If set, save and update - ::std::vector<ProtoLifetime> tmp_ends; - //::std::vector<Position> var_ends; - - State clone() const { - auto rv = *this; - rv.index = ++NEXT_INDEX; - return rv; - } - }; - NEXT_INDEX = 0; - - struct ValueLifetime - { - ::std::vector<bool> stmt_bitmap; - ValueLifetime(size_t stmt_count): - stmt_bitmap(stmt_count) - {} - }; - - size_t statement_count = 0; - ::std::vector<size_t> block_offsets; - block_offsets.reserve( fcn.blocks.size() ); - for(const auto& bb : fcn.blocks) - { - block_offsets.push_back(statement_count); - statement_count += bb.statements.size() + 1; // +1 for the terminator - } - - ::std::vector<ValueLifetime> temporary_lifetimes( fcn.temporaries.size(), ValueLifetime(statement_count) ); - - struct BlockSeenLifetimes { - const ::std::vector<size_t>& block_offsets; - ::std::vector< ::std::vector<unsigned int> > tmp; - - BlockSeenLifetimes(const ::std::vector<size_t>& block_offsets, const ::MIR::Function& fcn): - block_offsets( block_offsets ), - tmp( fcn.temporaries.size() ) - {} - - bool has_state() const - { - return ::std::any_of(tmp.begin(), tmp.end(), [&](const auto& x){ return !x.empty(); }); - } - - bool try_merge(const State& val_state) const - { - for(size_t i = 0; i < val_state.tmp_ends.size(); i++) - { - const auto& lft = val_state.tmp_ends[i]; - const auto& seen = this->tmp[i]; - if(lft.is_empty()) continue ; - auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx; - - auto it = ::std::find(seen.begin(), seen.end(), end_idx); - if( it == seen.end() ) - { - return true; - } - } - return false; - } - bool merge(const State& val_state) - { - bool rv = false; - for(size_t i = 0; i < val_state.tmp_ends.size(); i++) - { - const auto& lft = val_state.tmp_ends[i]; - auto& seen = this->tmp[i]; - if(lft.is_empty()) continue ; - auto end_idx = block_offsets.at( val_state.block_path.at(lft.end.path_index) ) + lft.end.stmt_idx; - - auto it = ::std::find(seen.begin(), seen.end(), end_idx); - if( it == seen.end() ) - { - rv = true; - seen.push_back( end_idx ); - } - } - return rv; - } - }; - ::std::vector<BlockSeenLifetimes> block_seen_lifetimes( fcn.blocks.size(), BlockSeenLifetimes(block_offsets, fcn) ); - - State init_state; - init_state.tmp_ends.resize( fcn.temporaries.size(), ProtoLifetime() ); - - ::std::vector<::std::pair<unsigned int, State>> todo_queue; - todo_queue.push_back(::std::make_pair( 0, mv$(init_state) )); - - while(!todo_queue.empty()) - { - auto bb_idx = todo_queue.back().first; - auto val_state = mv$(todo_queue.back().second); - todo_queue.pop_back(); - state.set_cur_stmt(bb_idx, 0); - - // Fill alive time in the bitmap - // TODO: Maybe also store the range (as a sequence of {block,start,end}) - auto add_lifetime_s = [&](State& val_state, const ::MIR::LValue& lv, const Position& start, const Position& end) { - assert(start.path_index <= end.path_index); - assert(start.path_index < end.path_index || start.stmt_idx <= end.stmt_idx); - if(start.path_index == end.path_index && start.stmt_idx == end.stmt_idx) - return; - //DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ")"); - ValueLifetime* lft; - if(const auto* e = lv.opt_Temporary()) - { - lft = &temporary_lifetimes[e->idx]; - } - else - { - MIR_TODO(state, "[add_lifetime] " << lv); - return; - } - - // Fill lifetime map for this temporary in the indicated range - bool did_set = false; - unsigned int j = start.stmt_idx; - unsigned int i = start.path_index; - while( i <= end.path_index ) - { - auto bb_idx = val_state.block_path.at(i); - const auto& bb = fcn.blocks[bb_idx]; - MIR_ASSERT(state, j <= bb.statements.size(), ""); - MIR_ASSERT(state, bb_idx < block_offsets.size(), ""); - - auto block_base = block_offsets.at(bb_idx); - auto idx = block_base + j; - if( !lft->stmt_bitmap.at(idx) ) - { - lft->stmt_bitmap[idx] = true; - did_set = true; - } - - if( i == end.path_index && j == end.stmt_idx ) - break; - - // If the current index is the terminator (one after the size) - if(j == bb.statements.size()) - { - j = 0; - i++; - } - else - { - j ++; - } - } - - // - If the above set a new bit, increment `val_state.cur_change_idx` - if( did_set ) - { - DEBUG("[add_lifetime] " << lv << " (" << start.path_index << "," << start.stmt_idx << ") -- (" << end.path_index << "," << end.stmt_idx << ") - New information"); - val_state.cur_change_idx += 1; - } - }; - auto add_lifetime = [&](const ::MIR::LValue& lv, const Position& start, const Position& end) { - add_lifetime_s(val_state, lv, start, end); - }; - - auto apply_state = [&](State& state) { - // Apply all changes in this state, just in case there was new information - for(unsigned i = 0; i < fcn.temporaries.size(); i++) - add_lifetime_s( state, ::MIR::LValue::make_Temporary({i}), state.tmp_ends[i].start, state.tmp_ends[i].end ); - }; - auto add_to_visit = [&](unsigned int new_bb_idx, State new_state) { - auto& bb_memory_ent = block_seen_lifetimes[new_bb_idx]; - /*if( !bb_memory_ent.has_state() ) - { - // No recorded state, needs to be visited - DEBUG(state << " " << new_state.index << " -> bb" << new_bb_idx << " (no existing state)"); - } - else*/ if( bb_memory_ent.try_merge(new_state) ) - { - // This state has new information, needs to be visited - DEBUG(state << " " << new_state.index << " -> bb" << new_bb_idx << " (new info)"); - } - else - { - // Skip - DEBUG(state << " " << new_state.index << " No new state before push (to bb" << new_bb_idx << "), applying"); - apply_state(new_state); - return ; - } - todo_queue.push_back(::std::make_pair( new_bb_idx, mv$(new_state) )); - }; - - // Compare this state to a composite list of lifetimes seen in this block - // - Just compares the end of each proto lifetime - { - auto& bb_memory_ent = block_seen_lifetimes[bb_idx]; - bool has_new = bb_memory_ent.merge(val_state); - - if( !has_new && bb_memory_ent.has_state() ) - { - DEBUG(state << " " << val_state.index << " No new entry state"); - apply_state(val_state); - - continue ; - } - } - - // Check if this state has visited this block before, and if anything changed since last time - { - auto it = ::std::find(val_state.block_path.rbegin(), val_state.block_path.rend(), bb_idx); - if( it != val_state.block_path.rend() ) - { - auto idx = &*it - &val_state.block_path.front(); - if( val_state.block_change_idx[idx] == val_state.cur_change_idx ) - { - DEBUG(state << " " << val_state.index << " Loop and no change"); - continue ; - } - else - { - assert( val_state.block_change_idx[idx] < val_state.cur_change_idx ); - DEBUG(state << " " << val_state.index << " --- Loop, " << val_state.cur_change_idx - val_state.block_change_idx[idx] << " changes"); - } - } - else - { - DEBUG(state << " " << val_state.index << " ---"); - } - val_state.block_path.push_back(bb_idx); - val_state.block_change_idx.push_back( val_state.cur_change_idx ); - } - - Position cur_pos; - cur_pos.path_index = val_state.block_path.size() - 1; - cur_pos.stmt_idx = 0; - auto lvalue_read = [&](const ::MIR::LValue& lv) { - if(const auto* e = lv.opt_Temporary()) - { - // Update the last read location - val_state.tmp_ends.at(e->idx).end = cur_pos; - } - }; - auto lvalue_set = [&](const ::MIR::LValue& lv) { - if(const auto* e = lv.opt_Temporary()) - { - // End whatever value was originally there, and insert this new one - val_state.tmp_ends.at(e->idx).end = cur_pos; - add_lifetime(lv, val_state.tmp_ends.at(e->idx).start, val_state.tmp_ends.at(e->idx).end); - val_state.tmp_ends.at(e->idx).start = cur_pos; - } - }; - - // Run statements - for(const auto& stmt : fcn.blocks[bb_idx].statements) - { - auto stmt_idx = &stmt - &fcn.blocks[bb_idx].statements.front(); - cur_pos.stmt_idx = stmt_idx; - state.set_cur_stmt(bb_idx, stmt_idx); - DEBUG(state << " " << stmt); - - if( const auto* e = stmt.opt_Drop() ) - { - visit_mir_lvalues(stmt, [&](const auto& lv, ValUsage vu)->bool{ - if(vu == ValUsage::Read) - lvalue_read(lv); - return false; - }); - lvalue_read(e->slot); - lvalue_set(e->slot); - } - else - { - visit_mir_lvalues(stmt, [&](const auto& lv, ValUsage vu)->bool{ - if(vu == ValUsage::Read) - lvalue_read(lv); - if(vu == ValUsage::Write) - lvalue_set(lv); - return false; - }); - } - } - cur_pos.stmt_idx = fcn.blocks[bb_idx].statements.size(); - - state.set_cur_stmt_term(bb_idx); - DEBUG(state << "TERM " << fcn.blocks[bb_idx].terminator); - TU_MATCH(::MIR::Terminator, (fcn.blocks[bb_idx].terminator), (e), - (Incomplete, - // Should be impossible here. - ), - (Return, - // End all active lifetimes at their previous location. - apply_state(val_state); - ), - (Diverge, - apply_state(val_state); - ), - (Goto, - add_to_visit(e, mv$(val_state)); - ), - (Panic, - // What should be done here? - ), - (If, - lvalue_read(e.cond); - - // Push blocks - add_to_visit(e.bb0, val_state.clone()); - add_to_visit(e.bb1, mv$(val_state)); - ), - (Switch, - lvalue_read(e.val); - ::std::set<unsigned int> tgts; - for(const auto& tgt : e.targets) - tgts.insert(tgt); - - for(const auto& tgt : tgts) - { - auto vs = (tgt == *tgts.rbegin() ? mv$(val_state) : val_state.clone()); - add_to_visit(tgt, mv$(vs)); - } - ), - (Call, - if( const auto* f = e.fcn.opt_Value() ) - lvalue_read(*f); - for(const auto& arg : e.args) - if( const auto* e = arg.opt_LValue() ) - lvalue_read(*e); - - // Push blocks (with return valid only in one) - add_to_visit(e.panic_block, val_state.clone()); - - // TODO: If the function returns !, don't follow the ret_block - lvalue_set(e.ret_val); - add_to_visit(e.ret_block, mv$(val_state)); - ) - ) - } - - // Dump out variable lifetimes. -#if 0 - for(unsigned int i = 0; i < temporary_lifetimes.size(); i ++) - { - const auto& lft = temporary_lifetimes[i]; - auto name = FMT("tmp$" << i); - while(name.size() < 3+1+3) - name += " "; - DEBUG(name << " : " << FMT_CB(os, - for(unsigned int j = 0; j < lft.stmt_bitmap.size(); j++) - { - if(j != 0 && ::std::find(block_offsets.begin(), block_offsets.end(), j) != block_offsets.end()) - os << "|"; - os << (lft.stmt_bitmap[j] ? "X" : " "); - } - )); - } -#endif - - // Move lifetime bitmaps into the variable for the below code - tmp_lifetimes.reserve( temporary_lifetimes.size() ); - for(auto& lft : temporary_lifetimes) - tmp_lifetimes.push_back( VarLifetime(mv$(lft.stmt_bitmap)) ); - } + auto lifetimes = MIR_Helper_GetLifetimes(state, fcn, /*dump_debug=*/true); + //::std::vector<::MIR::ValueLifetime> var_lifetimes = mv$(lifetimes.m_variables); + ::std::vector<::MIR::ValueLifetime> tmp_lifetimes = mv$(lifetimes.m_temporaries); // 2. Unify variables of the same type with distinct non-overlapping lifetimes ::std::map<unsigned int, unsigned int> replacements; @@ -1520,6 +1134,8 @@ bool MIR_Optimise_UnifyTemporaries(::MIR::TypeResolve& state, ::MIR::Function& f } return false; }); + + // TODO: Replace in ScopeEnd too? } return replacement_needed; @@ -1572,6 +1188,12 @@ bool MIR_Optimise_UnifyBlocks(::MIR::TypeResolve& state, ::MIR::Function& fcn) return false; if( ae.slot != be.slot ) return false; + ), + (ScopeEnd, + if( ae.vars != be.vars ) + return false; + if( ae.tmps == be.tmps ) + return false; ) ) } @@ -1664,7 +1286,7 @@ bool MIR_Optimise_UnifyBlocks(::MIR::TypeResolve& state, ::MIR::Function& fcn) if( ! replacements.empty() ) { //MIR_TODO(state, "Unify blocks - " << replacements); - DEBUG("Unify blocks - " << replacements); + DEBUG("Unify blocks (old: new) - " << replacements); auto patch_tgt = [&replacements](::MIR::BasicBlockId& tgt) { auto it = replacements.find(tgt); if( it != replacements.end() ) @@ -1726,6 +1348,9 @@ bool MIR_Optimise_UnifyBlocks(::MIR::TypeResolve& state, ::MIR::Function& fcn) // -------------------------------------------------------------------- bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) { +#if DUMP_BEFORE_ALL || DUMP_BEFORE_CONSTPROPAGATE + if( debug_enabled() ) MIR_Dump_Fcn(::std::cout, fcn); +#endif bool changed = false; TRACE_FUNCTION_FR("", changed); @@ -1760,8 +1385,8 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) changed = true; } } - // NOTE: Quick special-case for bswap<u8> (a no-op) - else if( tef.name == "bswap" && tef.params.m_types.at(0) == ::HIR::CoreType::U8 ) + // NOTE: Quick special-case for bswap<u8/i8> (a no-op) + else if( tef.name == "bswap" && (tef.params.m_types.at(0) == ::HIR::CoreType::U8 || tef.params.m_types.at(0) == ::HIR::CoreType::I8) ) { DEBUG("bswap<u8> is a no-op"); if( auto* e = te.args.at(0).opt_LValue() ) @@ -1771,12 +1396,14 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) bb.terminator = ::MIR::Terminator::make_Goto(te.ret_block); changed = true; } - //else if( tef.name == "get_dst_meta_slice" ) - //{ - // MIR_ASSERT(state, te.args.at(0).is_LValue(), "Argument to `get_dst_meta` must be a lvalue"); - // auto& e = te.args.at(0).as_LValue(); - // bb.statements.push_back(::MIR::Statement::make_Assign({ mv$(te.ret_val), ::MIR::RValue::make_DstMeta({ mv$(*e) }) })); - //} + else if( tef.name == "mrustc_slice_len" ) + { + MIR_ASSERT(state, te.args.at(0).is_LValue(), "Argument to `get_dst_meta` must be a lvalue"); + auto& e = te.args.at(0).as_LValue(); + bb.statements.push_back(::MIR::Statement::make_Assign({ mv$(te.ret_val), ::MIR::RValue::make_DstMeta({ mv$(e) }) })); + bb.terminator = ::MIR::Terminator::make_Goto(te.ret_block); + changed = true; + } else { // Ignore any other intrinsics @@ -1801,6 +1428,7 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) auto it = known_values.find(*pe); if( it != known_values.end() ) { + DEBUG(state << "Value " << *pe << " known to be " << it->second); p = it->second.clone(); } } @@ -1821,6 +1449,7 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) auto it = known_values.find(se); if( it != known_values.end() ) { + DEBUG(state << "Value " << se << " known to be" << it->second); e->src = it->second.clone(); } ), @@ -1840,7 +1469,41 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) if( se.val_l.is_Constant() && se.val_r.is_Constant() ) { - // TODO: Evaluate BinOp + const auto& val_l = se.val_l.as_Constant(); + const auto& val_r = se.val_r.as_Constant(); + + ::MIR::Constant new_value; + bool replace = false; + switch(se.op) + { + case ::MIR::eBinOp::EQ: + if( val_l.is_Const() ) + ; + else + { + replace = true; + new_value = ::MIR::Constant::make_Bool({val_l == val_r}); + } + break; + case ::MIR::eBinOp::NE: + if( val_l.is_Const() ) + ; + else + { + replace = true; + new_value = ::MIR::Constant::make_Bool({val_l != val_r}); + } + break; + // TODO: Other binary operations + default: + break; + } + + if( replace ) + { + DEBUG(state << " " << e->src << " = " << new_value); + e->src = mv$(new_value); + } } ), (UniOp, @@ -1848,6 +1511,8 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) if( it != known_values.end() ) { const auto& val = it->second; + ::MIR::Constant new_value; + bool replace = false; // TODO: Evaluate UniOp switch( se.op ) { @@ -1874,7 +1539,8 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) // Invalid type for Uint literal break; } - e->src = ::MIR::Constant::make_Uint({ val, ve.t }); + new_value = ::MIR::Constant::make_Uint({ val, ve.t }); + replace = true; ), (Int, // Is ! valid on Int? @@ -1883,7 +1549,8 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) // Not valid? ), (Bool, - e->src = ::MIR::Constant::make_Bool({ !ve.v }); + new_value = ::MIR::Constant::make_Bool({ !ve.v }); + replace = true; ), (Bytes, ), (StaticString, ), @@ -1900,10 +1567,12 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) // Not valid? ), (Int, - e->src = ::MIR::Constant::make_Int({ -ve.v, ve.t }); + new_value = ::MIR::Constant::make_Int({ -ve.v, ve.t }); + replace = true; ), (Float, - e->src = ::MIR::Constant::make_Float({ -ve.v, ve.t }); + new_value = ::MIR::Constant::make_Float({ -ve.v, ve.t }); + replace = true; ), (Bool, // Not valid? @@ -1918,6 +1587,11 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) ) break; } + if( replace ) + { + DEBUG(state << " " << e->src << " = " << new_value); + e->src = mv$(new_value); + } } ), (DstMeta, @@ -1948,18 +1622,19 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) // - If a known temporary is borrowed mutably or mutated somehow, clear its knowledge visit_mir_lvalues(stmt, [&known_values](const ::MIR::LValue& lv, ValUsage vu)->bool { if( vu == ValUsage::Write ) { - auto it = known_values.find(lv); - if(it != known_values.end()) - known_values.erase(it); + known_values.erase(lv); } return false; }); // - Locate `temp = SOME_CONST` and record value if( const auto* e = stmt.opt_Assign() ) { - if( e->dst.is_Temporary() && e->src.is_Constant() ) + if( e->dst.is_Temporary() || e->dst.is_Variable() ) { - known_values.insert(::std::make_pair( e->dst.clone(), e->src.as_Constant().clone() )); + if( const auto* ce = e->src.opt_Constant() ) + { + known_values.insert(::std::make_pair( e->dst.clone(), ce->clone() )); + } } } } @@ -2000,12 +1675,15 @@ bool MIR_Optimise_ConstPropagte(::MIR::TypeResolve& state, ::MIR::Function& fcn) if( se.dst != te.cond ) continue; - if( !se.src.is_Constant() ) - continue; - if( !se.src.as_Constant().is_Bool() ) - continue; - val_known = true; - known_val = se.src.as_Constant().as_Bool().v; + if( se.src.is_Constant() && se.src.as_Constant().is_Bool() ) + { + val_known = true; + known_val = se.src.as_Constant().as_Bool().v; + } + else + { + val_known = false; + } break; } else @@ -2673,6 +2351,9 @@ bool MIR_Optimise_GarbageCollect(::MIR::TypeResolve& state, ::MIR::Function& fcn DEBUG("GC Temporary(" << i << ")"); fcn.temporaries.erase(fcn.temporaries.begin() + j); } + else { + DEBUG("tmp$" << i << " => tmp$" << j); + } temp_rewrite_table.push_back( used_temps[i] ? j ++ : ~0u ); } ::std::vector<unsigned int> var_rewrite_table; @@ -2684,6 +2365,9 @@ bool MIR_Optimise_GarbageCollect(::MIR::TypeResolve& state, ::MIR::Function& fcn DEBUG("GC Variable(" << i << ")"); fcn.named_variables.erase(fcn.named_variables.begin() + j); } + else { + DEBUG("var$" << i << " => var$" << j); + } var_rewrite_table.push_back( used_vars[i] ? j ++ : ~0u ); } ::std::vector<unsigned int> df_rewrite_table; @@ -2745,6 +2429,35 @@ bool MIR_Optimise_GarbageCollect(::MIR::TypeResolve& state, ::MIR::Function& fcn if( se->other != ~0u ) se->other = df_rewrite_table[se->other]; } + else if( auto* se = stmt_it->opt_ScopeEnd() ) + { + for(auto it = se->vars.begin(); it != se->vars.end(); ) + { + if( var_rewrite_table.at(*it) == ~0u ) { + it = se->vars.erase(it); + } + else { + *it = var_rewrite_table.at(*it); + ++ it; + } + } + for(auto it = se->tmps.begin(); it != se->tmps.end(); ) + { + if( temp_rewrite_table.at(*it) == ~0u ) { + it = se->tmps.erase(it); + } + else { + *it = temp_rewrite_table.at(*it); + ++ it; + } + } + + if( se->vars.empty() && se->tmps.empty() ) { + DEBUG("- Delete ScopeEnd (now empty)"); + stmt_it = it->statements.erase(stmt_it); + -- stmt_it; + } + } } state.set_cur_stmt_term(i); // Rewrite and advance diff --git a/src/parse/expr.cpp b/src/parse/expr.cpp index d4edb208..15a3e179 100644 --- a/src/parse/expr.cpp +++ b/src/parse/expr.cpp @@ -669,6 +669,9 @@ bool Parse_IsTokValue(eTokenType tok_type) case TOK_PAREN_OPEN: case TOK_SQUARE_OPEN: + case TOK_INTERPOLATED_PATH: + case TOK_INTERPOLATED_EXPR: + case TOK_MACRO: case TOK_PIPE: diff --git a/src/parse/lex.cpp b/src/parse/lex.cpp index 7b99c433..57a0bedc 100644 --- a/src/parse/lex.cpp +++ b/src/parse/lex.cpp @@ -1010,12 +1010,12 @@ bool Codepoint::isxdigit() const { s += (char)(0xC0 | ((cp.v >> 6) & 0x1F)); s += (char)(0x80 | ((cp.v >> 0) & 0x3F)); } - else if( cp.v <= (0x0F+1)<<(2*6) ) { + else if( cp.v < (0x0F+1)<<(2*6) ) { s += (char)(0xE0 | ((cp.v >> 12) & 0x0F)); s += (char)(0x80 | ((cp.v >> 6) & 0x3F)); s += (char)(0x80 | ((cp.v >> 0) & 0x3F)); } - else if( cp.v <= (0x07+1)<<(3*6) ) { + else if( cp.v < (0x07+1)<<(3*6) ) { s += (char)(0xF0 | ((cp.v >> 18) & 0x07)); s += (char)(0x80 | ((cp.v >> 12) & 0x3F)); s += (char)(0x80 | ((cp.v >> 6) & 0x3F)); @@ -1035,12 +1035,12 @@ bool Codepoint::isxdigit() const { os << (char)(0xC0 | ((cp.v >> 6) & 0x1F)); os << (char)(0x80 | ((cp.v >> 0) & 0x3F)); } - else if( cp.v <= (0x0F+1)<<(2*6) ) { + else if( cp.v < (0x0F+1)<<(2*6) ) { os << (char)(0xE0 | ((cp.v >> 12) & 0x0F)); os << (char)(0x80 | ((cp.v >> 6) & 0x3F)); os << (char)(0x80 | ((cp.v >> 0) & 0x3F)); } - else if( cp.v <= (0x07+1)<<(2*6) ) { + else if( cp.v < (0x07+1)<<(2*6) ) { os << (char)(0xF0 | ((cp.v >> 18) & 0x07)); os << (char)(0x80 | ((cp.v >> 12) & 0x3F)); os << (char)(0x80 | ((cp.v >> 6) & 0x3F)); diff --git a/src/trans/codegen_c.cpp b/src/trans/codegen_c.cpp index e74231e8..1ca8c4ae 100644 --- a/src/trans/codegen_c.cpp +++ b/src/trans/codegen_c.cpp @@ -84,7 +84,7 @@ namespace { << "\treturn (v >> 32 != 0 ? __builtin_clz(v>>32) : 32 + __builtin_clz(v));\n" << "}\n" << "static inline uint64_t __builtin_ctz64(uint64_t v) {\n" - << "\treturn (v&0xFFFFFFFF == 0 ? __builtin_ctz(v>>32) + 32 : __builtin_ctz(v));\n" + << "\treturn ((v&0xFFFFFFFF) == 0 ? __builtin_ctz(v>>32) + 32 : __builtin_ctz(v));\n" << "}\n" << "static inline unsigned __int128 __builtin_bswap128(unsigned __int128 v) {\n" << "\tuint64_t lo = __builtin_bswap64((uint64_t)v);\n" @@ -95,7 +95,7 @@ namespace { << "\treturn (v >> 64 != 0 ? __builtin_clz64(v>>64) : 64 + __builtin_clz64(v));\n" << "}\n" << "static inline unsigned __int128 __builtin_ctz128(unsigned __int128 v) {\n" - << "\treturn (v&0xFFFFFFFFFFFFFFFF == 0 ? __builtin_ctz64(v>>64) + 64 : __builtin_ctz64(v));\n" + << "\treturn ((v&0xFFFFFFFFFFFFFFFF) == 0 ? __builtin_ctz64(v>>64) + 64 : __builtin_ctz64(v));\n" << "}\n" << "\n" << "static inline void noop_drop(void *p) {}\n" @@ -192,6 +192,7 @@ namespace { ::std::stringstream cmd_ss; for(const auto& arg : args) { + // TODO: use a formatter specific to shell escaping cmd_ss << "\"" << FmtEscaped(arg) << "\" "; } DEBUG("- " << cmd_ss.str()); @@ -1313,6 +1314,9 @@ namespace { switch( stmt.tag() ) { case ::MIR::Statement::TAGDEAD: throw ""; + case ::MIR::Statement::TAG_ScopeEnd: + m_of << "// " << stmt << "\n"; + break; case ::MIR::Statement::TAG_SetDropFlag: { const auto& e = stmt.as_SetDropFlag(); m_of << "\tdf" << e.idx << " = "; @@ -1328,7 +1332,7 @@ namespace { const auto& ty = mir_res.get_lvalue_type(tmp, e.slot); if( e.flag_idx != ~0u ) - m_of << "if( df" << e.flag_idx << " ) {\n"; + m_of << "\tif( df" << e.flag_idx << " ) {\n"; switch( e.kind ) { @@ -1351,7 +1355,7 @@ namespace { break; } if( e.flag_idx != ~0u ) - m_of << "}\n"; + m_of << "\t}\n"; break; } case ::MIR::Statement::TAG_Asm: { const auto& e = stmt.as_Asm(); @@ -1375,6 +1379,7 @@ namespace { m_of << "\t__asm__ "; if(is_volatile) m_of << "__volatile__"; // TODO: Convert format string? + // TODO: Use a C-specific escaper here. m_of << "(\"" << (is_intel ? ".syntax intel; " : "") << FmtEscaped(e.tpl) << (is_intel ? ".syntax att; " : "") << "\""; m_of << ": "; for(unsigned int i = 0; i < e.outputs.size(); i ++ ) @@ -2110,15 +2115,15 @@ namespace { else if( name == "needs_drop" ) { // Returns `true` if the actual type given as `T` requires drop glue; // returns `false` if the actual type provided for `T` implements `Copy`. (Either otherwise) + // NOTE: libarena assumes that this returns `true` iff T doesn't require drop glue. const auto& ty = params.m_types.at(0); emit_lvalue(e.ret_val); m_of << " = "; - if( m_resolve.type_is_copy(Span(), ty) ) { - m_of << "false"; + if( m_resolve.type_needs_drop_glue(mir_res.sp, ty) ) { + m_of << "true"; } - // If T: !Copy, return true else { - m_of << "true"; + m_of << "false"; } } else if( name == "uninit" ) { @@ -2717,7 +2722,11 @@ namespace { if( ' ' <= v && v < 0x7F && v != '"' && v != '\\' ) m_of << v; else - m_of << "\\" << (unsigned int)v; + { + m_of << "\\" << ((unsigned int)v & 0xFF); + if( isdigit( *(&v+1) ) ) + m_of << "\"\""; + } } m_of << "\"" << ::std::dec; m_of << ";\n\t"; @@ -2923,7 +2932,7 @@ namespace { if( ' ' <= v && v < 0x7F && v != '"' && v != '\\' ) m_of << v; else - m_of << "\\" << (unsigned int)v; + m_of << "\\" << ((unsigned int)v & 0xFF); } m_of << "\"" << ::std::dec; ), @@ -2933,7 +2942,7 @@ namespace { if( ' ' <= v && v < 0x7F && v != '"' && v != '\\' ) m_of << v; else - m_of << "\\" << (unsigned int)v; + m_of << "\\" << ((unsigned int)v & 0xFF); } m_of << "\", " << ::std::dec << c.size() << ")"; ), diff --git a/src/trans/enumerate.cpp b/src/trans/enumerate.cpp index 481b275b..02116d18 100644 --- a/src/trans/enumerate.cpp +++ b/src/trans/enumerate.cpp @@ -875,6 +875,8 @@ void Trans_Enumerate_Types(EnumState& state) for(const auto& v : se.inputs) H::visit_lvalue(tv,pp,fcn, v.second); ), + (ScopeEnd, + ), (Assign, H::visit_lvalue(tv,pp,fcn, se.dst); TU_MATCHA( (se.src), (re), @@ -1461,6 +1463,8 @@ void Trans_Enumerate_FillFrom_MIR(EnumState& state, const ::MIR::Function& code, ), (SetDropFlag, ), + (ScopeEnd, + ), (Drop, DEBUG("- DROP " << se.slot); Trans_Enumerate_FillFrom_MIR_LValue(state, se.slot, pp); diff --git a/src/trans/main_bindings.hpp b/src/trans/main_bindings.hpp index 2878cc66..59933863 100644 --- a/src/trans/main_bindings.hpp +++ b/src/trans/main_bindings.hpp @@ -23,6 +23,7 @@ struct TransOptions }; extern TransList Trans_Enumerate_Main(const ::HIR::Crate& crate); +extern TransList Trans_Enumerate_Test(const ::HIR::Crate& crate); // NOTE: This also sets the saveout flags extern TransList Trans_Enumerate_Public(::HIR::Crate& crate); diff --git a/src/trans/monomorphise.cpp b/src/trans/monomorphise.cpp index 1688a8f5..1a7d8e79 100644 --- a/src/trans/monomorphise.cpp +++ b/src/trans/monomorphise.cpp @@ -150,6 +150,9 @@ namespace { case ::MIR::Statement::TAG_SetDropFlag: statements.push_back( ::MIR::Statement( stmt.as_SetDropFlag() ) ); break; + case ::MIR::Statement::TAG_ScopeEnd: + statements.push_back( ::MIR::Statement( stmt.as_ScopeEnd() ) ); + break; case ::MIR::Statement::TAG_Drop: { const auto& e = stmt.as_Drop(); DEBUG("- DROP " << e.slot); |