/* * MRustC - Rust Compiler * - By John Hodge (Mutabah/thePowersGang) * * mir/from_hir.cpp * - Construction of MIR from the HIR expression tree */ #include // for TU_MATCHA #include #include "mir.hpp" #include "mir_ptr.hpp" #include #include #include #include // monomorphise_type #include "main_bindings.hpp" #include "from_hir.hpp" namespace { class ExprVisitor_Conv: public MirConverter { public: MirBuilder m_builder; private: const ::std::vector< ::HIR::TypeRef>& m_variable_types; struct LoopDesc { ScopeHandle scope; ::std::string label; unsigned int cur; unsigned int next; }; ::std::vector m_loop_stack; public: ExprVisitor_Conv(::MIR::Function& output, const ::std::vector< ::HIR::TypeRef>& var_types): m_builder(output), m_variable_types(var_types) { } void destructure_from(const Span& sp, const ::HIR::Pattern& pat, ::MIR::LValue lval, bool allow_refutable=false) override { destructure_from_ex(sp, pat, mv$(lval), (allow_refutable ? 1 : 0)); } // Brings variables defined in `pat` into scope void define_vars_from(const Span& sp, const ::HIR::Pattern& pat) override { if( pat.m_binding.is_valid() ) { m_builder.define_variable( pat.m_binding.m_slot ); } TU_MATCHA( (pat.m_data), (e), (Any, ), (Box, TODO(sp, "Destructure using " << pat); ), (Ref, define_vars_from(sp, *e.sub); ), (Tuple, for(unsigned int i = 0; i < e.sub_patterns.size(); i ++ ) { define_vars_from(sp, e.sub_patterns[i]); } ), (SplitTuple, BUG(sp, "Tuple .. should be eliminated"); ), (StructValue, // Nothing. ), (StructTuple, for(unsigned int i = 0; i < e.sub_patterns.size(); i ++ ) { define_vars_from(sp, e.sub_patterns[i]); } ), (Struct, for(const auto& fld_pat : e.sub_patterns) { define_vars_from(sp, fld_pat.second); } ), // Refutable (Value, ), (Range, ), (EnumValue, ), (EnumTuple, for(unsigned int i = 0; i < e.sub_patterns.size(); i ++ ) { define_vars_from(sp, e.sub_patterns[i]); } ), (EnumStruct, for(const auto& fld_pat : e.sub_patterns) { define_vars_from(sp, fld_pat.second); } ), (Slice, TODO(sp, "Destructure using " << pat); ), (SplitSlice, TODO(sp, "Destructure using " << pat); ) ) } void destructure_from_ex(const Span& sp, const ::HIR::Pattern& pat, ::MIR::LValue lval, int allow_refutable=0) // 1 : yes, 2 : disallow binding { if( allow_refutable != 3 && pat.m_binding.is_valid() ) { if( allow_refutable == 2 ) { BUG(sp, "Binding when not expected"); } else if( allow_refutable == 0 ) { ASSERT_BUG(sp, pat.m_data.is_Any(), "Destructure patterns can't bind and match"); m_builder.push_stmt_assign( ::MIR::LValue::make_Variable(pat.m_binding.m_slot), mv$(lval) ); } else { // Refutable and binding allowed m_builder.push_stmt_assign( ::MIR::LValue::make_Variable(pat.m_binding.m_slot), lval.clone() ); destructure_from_ex(sp, pat, mv$(lval), 3); } return; } if( allow_refutable == 3 ) { allow_refutable = 2; } TU_MATCHA( (pat.m_data), (e), (Any, ), (Box, TODO(sp, "Destructure using " << pat); ), (Ref, destructure_from_ex(sp, *e.sub, ::MIR::LValue::make_Deref({ box$( mv$(lval) ) }), allow_refutable); ), (Tuple, for(unsigned int i = 0; i < e.sub_patterns.size(); i ++ ) { destructure_from_ex(sp, e.sub_patterns[i], ::MIR::LValue::make_Field({ box$( lval.clone() ), i}), allow_refutable); } ), (SplitTuple, BUG(sp, "Tuple .. should be eliminated"); ), (StructValue, // Nothing. ), (StructTuple, for(unsigned int i = 0; i < e.sub_patterns.size(); i ++ ) { destructure_from_ex(sp, e.sub_patterns[i], ::MIR::LValue::make_Field({ box$( lval.clone() ), i}), allow_refutable); } ), (Struct, const auto& str = *e.binding; const auto& fields = str.m_data.as_Named(); for(const auto& fld_pat : e.sub_patterns) { unsigned idx = ::std::find_if( fields.begin(), fields.end(), [&](const auto&x){ return x.first == fld_pat.first; } ) - fields.begin(); destructure_from_ex(sp, fld_pat.second, ::MIR::LValue::make_Field({ box$( lval.clone() ), idx}), allow_refutable); } ), // Refutable (Value, ASSERT_BUG(sp, allow_refutable, "Refutable pattern not expected - " << pat); ), (Range, ASSERT_BUG(sp, allow_refutable, "Refutable pattern not expected - " << pat); ), (EnumValue, ASSERT_BUG(sp, allow_refutable, "Refutable pattern not expected - " << pat); ), (EnumTuple, ASSERT_BUG(sp, allow_refutable, "Refutable pattern not expected - " << pat); auto lval_var = ::MIR::LValue::make_Downcast({ box$(mv$(lval)), e.binding_idx }); for(unsigned int i = 0; i < e.sub_patterns.size(); i ++ ) { destructure_from_ex(sp, e.sub_patterns[i], ::MIR::LValue::make_Field({ box$( lval_var.clone() ), i}), allow_refutable); } ), (EnumStruct, ASSERT_BUG(sp, allow_refutable, "Refutable pattern not expected - " << pat); const auto& enm = *e.binding_ptr; const auto& fields = enm.m_variants[e.binding_idx].second.as_Struct(); auto lval_var = ::MIR::LValue::make_Downcast({ box$(mv$(lval)), e.binding_idx }); for(const auto& fld_pat : e.sub_patterns) { unsigned idx = ::std::find_if( fields.begin(), fields.end(), [&](const auto&x){ return x.first == fld_pat.first; } ) - fields.begin(); destructure_from_ex(sp, fld_pat.second, ::MIR::LValue::make_Field({ box$( lval_var.clone() ), idx}), allow_refutable); } ), (Slice, ASSERT_BUG(sp, allow_refutable, "Refutable pattern not expected - " << pat); TODO(sp, "Destructure using " << pat); ), (SplitSlice, ASSERT_BUG(sp, allow_refutable, "Refutable pattern not expected - " << pat); TODO(sp, "Destructure using " << pat); ) ) } // -- ExprVisitor void visit(::HIR::ExprNode_Block& node) override { TRACE_FUNCTION_F("_Block"); // NOTE: This doesn't create a BB, as BBs are not needed for scoping if( node.m_nodes.size() > 0 ) { bool res_valid; auto res = m_builder.new_temporary( node.m_res_type ); auto scope = m_builder.new_scope_var(node.span()); for(unsigned int i = 0; i < node.m_nodes.size()-1; i ++) { auto& subnode = node.m_nodes[i]; const Span& sp = subnode->span(); auto stmt_scope = m_builder.new_scope_temp(sp); this->visit_node_ptr(subnode); if( m_builder.has_result() ) { m_builder.get_result(sp); } if( m_builder.block_active() ) { m_builder.terminate_scope(sp, mv$(stmt_scope)); } else { auto _ = mv$(stmt_scope); } } // - For the last node, don't bother with a statement scope { auto& subnode = node.m_nodes.back(); const Span& sp = subnode->span(); auto stmt_scope = m_builder.new_scope_temp(sp); this->visit_node_ptr(subnode); if( m_builder.has_result() || m_builder.block_active() ) { m_builder.push_stmt_assign( res.clone(), m_builder.get_result(sp) ); m_builder.terminate_scope(sp, mv$(stmt_scope)); res_valid = true; } else { auto _ = mv$(stmt_scope); res_valid = false; } } // Drop all bindings introduced during this block. if( m_builder.block_active() ) { m_builder.terminate_scope( node.span(), mv$(scope) ); } else { auto _ = mv$(scope); } // Result from last node (if it didn't diverge) if( res_valid ) { m_builder.set_result( node.span(), mv$(res) ); } } else { m_builder.set_result(node.span(), ::MIR::RValue::make_Tuple({})); } } void visit(::HIR::ExprNode_Return& node) override { TRACE_FUNCTION_F("_Return"); this->visit_node_ptr(node.m_value); m_builder.push_stmt_assign( ::MIR::LValue::make_Return({}), m_builder.get_result(node.span()) ); // TODO: Insert drop of all current scopes m_builder.terminate_scope_early( node.span(), m_builder.fcn_scope() ); m_builder.end_block( ::MIR::Terminator::make_Return({}) ); } void visit(::HIR::ExprNode_Let& node) override { TRACE_FUNCTION_F("_Let"); this->define_vars_from(node.span(), node.m_pattern); if( node.m_value ) { this->visit_node_ptr(node.m_value); this->destructure_from(node.span(), node.m_pattern, m_builder.lvalue_or_temp(node.m_type, m_builder.get_result(node.span()) )); } m_builder.set_result(node.span(), ::MIR::RValue::make_Tuple({})); } void visit(::HIR::ExprNode_Loop& node) override { TRACE_FUNCTION_F("_Loop"); auto loop_body_scope = m_builder.new_scope_loop(node.span()); auto loop_block = m_builder.new_bb_linked(); auto loop_next = m_builder.new_bb_unlinked(); m_loop_stack.push_back( LoopDesc { mv$(loop_body_scope), node.m_label, loop_block, loop_next } ); this->visit_node_ptr(node.m_code); auto loop_scope = mv$(m_loop_stack.back().scope); m_loop_stack.pop_back(); // If there's a stray result, drop it if( m_builder.has_result() ) { assert( m_builder.block_active() ); } // Terminate block with a jump back to the start // - Also inserts the jump if this didn't uncondtionally diverge if( m_builder.block_active() ) { // TODO: Insert drop of all scopes within the current scope m_builder.terminate_scope( node.span(), mv$(loop_scope) ); m_builder.end_block( ::MIR::Terminator::make_Goto(loop_block) ); // - m_builder.set_cur_block(loop_next); } } void visit(::HIR::ExprNode_LoopControl& node) override { TRACE_FUNCTION_F("_LoopControl"); if( m_loop_stack.size() == 0 ) { BUG(node.span(), "Loop control outside of a loop"); } const auto* target_block = &m_loop_stack.back(); if( node.m_label != "" ) { auto it = ::std::find_if(m_loop_stack.rbegin(), m_loop_stack.rend(), [&](const auto& x){ return x.label == node.m_label; }); if( it == m_loop_stack.rend() ) { BUG(node.span(), "Named loop '" << node.m_label << " doesn't exist"); } target_block = &*it; } // TODO: Insert drop of all active scopes within the loop m_builder.terminate_scope_early( node.span(), target_block->scope ); if( node.m_continue ) { m_builder.end_block( ::MIR::Terminator::make_Goto(target_block->cur) ); } else { m_builder.end_block( ::MIR::Terminator::make_Goto(target_block->next) ); } } void visit(::HIR::ExprNode_Match& node) override { TRACE_FUNCTION_F("_Match"); this->visit_node_ptr(node.m_value); auto match_val = m_builder.lvalue_or_temp(node.m_value->m_res_type, m_builder.get_result(node.m_value->span())); if( node.m_arms.size() == 0 ) { // Nothing TODO(node.span(), "Handle zero-arm match - can only match over ! or similar"); } else if( node.m_arms.size() == 1 && node.m_arms[0].m_patterns.size() == 1 && ! node.m_arms[0].m_cond ) { // - Shortcut: Single-arm match auto scope = m_builder.new_scope_var( node.span() ); this->define_vars_from(node.span(), node.m_arms[0].m_patterns[0]); this->destructure_from(node.span(), node.m_arms[0].m_patterns[0], mv$(match_val)); this->visit_node_ptr(node.m_arms[0].m_code); if( m_builder.block_active() ) { m_builder.terminate_scope( node.span(), mv$(scope) ); } else { auto _ = mv$(scope); } } else { MIR_LowerHIR_Match(m_builder, *this, node, mv$(match_val)); } } // ExprNode_Match void visit(::HIR::ExprNode_If& node) override { TRACE_FUNCTION_F("_If"); this->visit_node_ptr(node.m_cond); auto decision_val = m_builder.lvalue_or_temp(node.m_cond->m_res_type, m_builder.get_result(node.m_cond->span()) ); auto true_branch = m_builder.new_bb_unlinked(); auto false_branch = m_builder.new_bb_unlinked(); auto next_block = m_builder.new_bb_unlinked(); m_builder.end_block( ::MIR::Terminator::make_If({ mv$(decision_val), true_branch, false_branch }) ); auto result_val = m_builder.new_temporary(node.m_res_type); // Scope handles cases where one arm moves a value but the other doesn't auto scope = m_builder.new_scope_split( node.m_true->span() ); // 'true' branch { m_builder.set_cur_block(true_branch); this->visit_node_ptr(node.m_true); if( m_builder.block_active() || m_builder.has_result() ) { m_builder.push_stmt_assign( result_val.clone(), m_builder.get_result(node.m_true->span()) ); m_builder.end_block( ::MIR::Terminator::make_Goto(next_block) ); m_builder.end_split_arm(node.span(), scope, true); } else { m_builder.end_split_arm(node.span(), scope, false); } } // 'false' branch m_builder.set_cur_block(false_branch); if( node.m_false ) { this->visit_node_ptr(node.m_false); if( m_builder.block_active() ) { m_builder.push_stmt_assign( result_val.clone(), m_builder.get_result(node.m_false->span()) ); m_builder.end_block( ::MIR::Terminator::make_Goto(next_block) ); m_builder.end_split_arm(node.span(), scope, true); } else { m_builder.end_split_arm(node.span(), scope, false); } } else { // Assign `()` to the result m_builder.push_stmt_assign( result_val.clone(), ::MIR::RValue::make_Tuple({}) ); m_builder.end_block( ::MIR::Terminator::make_Goto(next_block) ); m_builder.end_split_arm(node.span(), scope, true); } m_builder.set_cur_block(next_block); m_builder.terminate_scope( node.span(), mv$(scope) ); m_builder.set_result( node.span(), mv$(result_val) ); } void generate_checked_binop(const Span& sp, ::MIR::LValue res_slot, ::MIR::eBinOp op, ::MIR::LValue val_l, const ::HIR::TypeRef& ty_l, ::MIR::LValue val_r, const ::HIR::TypeRef& ty_r) { switch(op) { case ::MIR::eBinOp::EQ: case ::MIR::eBinOp::NE: case ::MIR::eBinOp::LT: case ::MIR::eBinOp::LE: case ::MIR::eBinOp::GT: case ::MIR::eBinOp::GE: ASSERT_BUG(sp, ty_l == ty_r, "Types in comparison operators must be equal - " << ty_l << " != " << ty_r); // Defensive assert that the type is a valid MIR comparison TU_MATCH_DEF(::HIR::TypeRef::Data, (ty_l.m_data), (e), ( BUG(sp, "Invalid type in comparison - " << ty_l); ), (Pointer, // Valid ), // TODO: Should straight comparisons on &str be supported here? (Primitive, if( e == ::HIR::CoreType::Str ) { BUG(sp, "Invalid type in comparison - " << ty_l); } ) ) m_builder.push_stmt_assign(mv$(res_slot), ::MIR::RValue::make_BinOp({ mv$(val_l), op, mv$(val_r) })); break; // Bitwise masking operations: Require equal integer types or bool case ::MIR::eBinOp::BIT_XOR: case ::MIR::eBinOp::BIT_OR : case ::MIR::eBinOp::BIT_AND: ASSERT_BUG(sp, ty_l == ty_r, "Types in bitwise operators must be equal - " << ty_l << " != " << ty_r); ASSERT_BUG(sp, ty_l.m_data.is_Primitive(), "Only primitives allowed in bitwise operators"); switch(ty_l.m_data.as_Primitive()) { case ::HIR::CoreType::Str: case ::HIR::CoreType::Char: case ::HIR::CoreType::F32: case ::HIR::CoreType::F64: BUG(sp, "Invalid type for bitwise operator - " << ty_l); default: break; } m_builder.push_stmt_assign(mv$(res_slot), ::MIR::RValue::make_BinOp({ mv$(val_l), op, mv$(val_r) })); break; case ::MIR::eBinOp::ADD: case ::MIR::eBinOp::ADD_OV: case ::MIR::eBinOp::SUB: case ::MIR::eBinOp::SUB_OV: case ::MIR::eBinOp::MUL: case ::MIR::eBinOp::MUL_OV: case ::MIR::eBinOp::DIV: case ::MIR::eBinOp::DIV_OV: case ::MIR::eBinOp::MOD: ASSERT_BUG(sp, ty_l == ty_r, "Types in arithmatic operators must be equal - " << ty_l << " != " << ty_r); ASSERT_BUG(sp, ty_l.m_data.is_Primitive(), "Only primitives allowed in arithmatic operators"); switch(ty_l.m_data.as_Primitive()) { case ::HIR::CoreType::Str: case ::HIR::CoreType::Char: case ::HIR::CoreType::Bool: BUG(sp, "Invalid type for arithmatic operator - " << ty_l); default: break; } // TODO: Overflow checks (none for eBinOp::MOD) m_builder.push_stmt_assign(mv$(res_slot), ::MIR::RValue::make_BinOp({ mv$(val_l), op, mv$(val_r) })); break; case ::MIR::eBinOp::BIT_SHL: case ::MIR::eBinOp::BIT_SHR: ; ASSERT_BUG(sp, ty_l.m_data.is_Primitive(), "Only primitives allowed in arithmatic operators"); ASSERT_BUG(sp, ty_r.m_data.is_Primitive(), "Only primitives allowed in arithmatic operators"); switch(ty_l.m_data.as_Primitive()) { case ::HIR::CoreType::Str: case ::HIR::CoreType::Char: case ::HIR::CoreType::F32: case ::HIR::CoreType::F64: BUG(sp, "Invalid type for shift op-assignment - " << ty_l); default: break; } switch(ty_r.m_data.as_Primitive()) { case ::HIR::CoreType::Str: case ::HIR::CoreType::Char: case ::HIR::CoreType::F32: case ::HIR::CoreType::F64: BUG(sp, "Invalid type for shift op-assignment - " << ty_r); default: break; } // TODO: Overflow check m_builder.push_stmt_assign(mv$(res_slot), ::MIR::RValue::make_BinOp({ mv$(val_l), op, mv$(val_r) })); break; } } void visit(::HIR::ExprNode_Assign& node) override { TRACE_FUNCTION_F("_Assign"); const auto& sp = node.span(); this->visit_node_ptr(node.m_value); auto val = m_builder.get_result(sp); this->visit_node_ptr(node.m_slot); auto dst = m_builder.get_result_lvalue(sp); const auto& ty_slot = node.m_slot->m_res_type; const auto& ty_val = node.m_value->m_res_type; if( node.m_op != ::HIR::ExprNode_Assign::Op::None ) { auto dst_clone = dst.clone(); auto val_lv = m_builder.lvalue_or_temp( ty_val, mv$(val) ); ASSERT_BUG(sp, ty_slot.m_data.is_Primitive(), "Assignment operator overloads are only valid on primitives - ty_slot="<generate_checked_binop(sp, mv$(dst), op, mv$(dst_clone), ty_slot, mv$(val_lv), ty_val); break; case _(Xor): op = ::MIR::eBinOp::BIT_XOR; if(0) case _(Or ): op = ::MIR::eBinOp::BIT_OR ; if(0) case _(And): op = ::MIR::eBinOp::BIT_AND; if(0) ; this->generate_checked_binop(sp, mv$(dst), op, mv$(dst_clone), ty_slot, mv$(val_lv), ty_val); break; case _(Shl): op = ::MIR::eBinOp::BIT_SHL; if(0) case _(Shr): op = ::MIR::eBinOp::BIT_SHR; if(0) ; this->generate_checked_binop(sp, mv$(dst), op, mv$(dst_clone), ty_slot, mv$(val_lv), ty_val); break; } #undef _ } else { ASSERT_BUG(sp, ty_slot == ty_val, "Types must match for assignment - " << ty_slot << " != " << ty_val); m_builder.push_stmt_assign(mv$(dst), mv$(val)); } m_builder.set_result(node.span(), ::MIR::RValue::make_Tuple({})); } void visit(::HIR::ExprNode_BinOp& node) override { const auto& sp = node.span(); TRACE_FUNCTION_F("_BinOp"); const auto& ty_l = node.m_left->m_res_type; this->visit_node_ptr(node.m_left); auto left = m_builder.lvalue_or_temp( ty_l, m_builder.get_result(node.m_left->span()) ); const auto& ty_r = node.m_right->m_res_type; this->visit_node_ptr(node.m_right); auto right = m_builder.lvalue_or_temp( ty_r, m_builder.get_result(node.m_right->span()) ); auto res = m_builder.new_temporary(node.m_res_type); ::MIR::eBinOp op; switch(node.m_op) { case ::HIR::ExprNode_BinOp::Op::CmpEqu: op = ::MIR::eBinOp::EQ; if(0) case ::HIR::ExprNode_BinOp::Op::CmpNEqu:op = ::MIR::eBinOp::NE; if(0) case ::HIR::ExprNode_BinOp::Op::CmpLt: op = ::MIR::eBinOp::LT; if(0) case ::HIR::ExprNode_BinOp::Op::CmpLtE: op = ::MIR::eBinOp::LE; if(0) case ::HIR::ExprNode_BinOp::Op::CmpGt: op = ::MIR::eBinOp::GT; if(0) case ::HIR::ExprNode_BinOp::Op::CmpGtE: op = ::MIR::eBinOp::GE; this->generate_checked_binop(sp, res.clone(), op, mv$(left), ty_l, mv$(right), ty_r); break; case ::HIR::ExprNode_BinOp::Op::Xor: op = ::MIR::eBinOp::BIT_XOR; if(0) case ::HIR::ExprNode_BinOp::Op::Or : op = ::MIR::eBinOp::BIT_OR ; if(0) case ::HIR::ExprNode_BinOp::Op::And: op = ::MIR::eBinOp::BIT_AND; this->generate_checked_binop(sp, res.clone(), op, mv$(left), ty_l, mv$(right), ty_r); break; case ::HIR::ExprNode_BinOp::Op::Shr: op = ::MIR::eBinOp::BIT_SHR; if(0) case ::HIR::ExprNode_BinOp::Op::Shl: op = ::MIR::eBinOp::BIT_SHL; this->generate_checked_binop(sp, res.clone(), op, mv$(left), ty_l, mv$(right), ty_r); break; case ::HIR::ExprNode_BinOp::Op::Add: op = ::MIR::eBinOp::ADD; if(0) case ::HIR::ExprNode_BinOp::Op::Sub: op = ::MIR::eBinOp::SUB; if(0) case ::HIR::ExprNode_BinOp::Op::Mul: op = ::MIR::eBinOp::MUL; if(0) case ::HIR::ExprNode_BinOp::Op::Div: op = ::MIR::eBinOp::DIV; if(0) case ::HIR::ExprNode_BinOp::Op::Mod: op = ::MIR::eBinOp::MOD; this->generate_checked_binop(sp, res.clone(), op, mv$(left), ty_l, mv$(right), ty_r); break; case ::HIR::ExprNode_BinOp::Op::BoolAnd: { auto bb_next = m_builder.new_bb_unlinked(); auto bb_true = m_builder.new_bb_unlinked(); auto bb_false = m_builder.new_bb_unlinked(); m_builder.end_block( ::MIR::Terminator::make_If({ mv$(left), bb_true, bb_false }) ); // If left is false, assign result false and return m_builder.set_cur_block( bb_false ); m_builder.push_stmt_assign(res.clone(), ::MIR::RValue( ::MIR::Constant::make_Bool(false) )); m_builder.end_block( ::MIR::Terminator::make_Goto(bb_next) ); // If left is true, assign result to right m_builder.set_cur_block( bb_true ); m_builder.push_stmt_assign(res.clone(), mv$(right)); // TODO: Right doens't need to be an LValue here. m_builder.end_block( ::MIR::Terminator::make_Goto(bb_next) ); m_builder.set_cur_block( bb_next ); } break; case ::HIR::ExprNode_BinOp::Op::BoolOr: { auto bb_next = m_builder.new_bb_unlinked(); auto bb_true = m_builder.new_bb_unlinked(); auto bb_false = m_builder.new_bb_unlinked(); m_builder.end_block( ::MIR::Terminator::make_If({ mv$(left), bb_true, bb_false }) ); // If left is true, assign result true and return m_builder.set_cur_block( bb_true ); m_builder.push_stmt_assign(res.clone(), ::MIR::RValue( ::MIR::Constant::make_Bool(true) )); m_builder.end_block( ::MIR::Terminator::make_Goto(bb_next) ); // If left is false, assign result to right m_builder.set_cur_block( bb_false ); m_builder.push_stmt_assign(res.clone(), mv$(right)); // TODO: Right doens't need to be an LValue here. m_builder.end_block( ::MIR::Terminator::make_Goto(bb_next) ); m_builder.set_cur_block( bb_next ); } break; } m_builder.set_result( node.span(), mv$(res) ); } void visit(::HIR::ExprNode_UniOp& node) override { TRACE_FUNCTION_F("_UniOp"); const auto& ty_val = node.m_value->m_res_type; this->visit_node_ptr(node.m_value); auto val = m_builder.lvalue_or_temp( ty_val, m_builder.get_result(node.m_value->span()) ); auto res = m_builder.new_temporary(node.m_res_type); switch(node.m_op) { case ::HIR::ExprNode_UniOp::Op::Invert: if( ty_val.m_data.is_Primitive() ) { switch( ty_val.m_data.as_Primitive() ) { case ::HIR::CoreType::Str: case ::HIR::CoreType::Char: case ::HIR::CoreType::F32: case ::HIR::CoreType::F64: BUG(node.span(), "`!` operator on invalid type - " << ty_val); break; default: break; } } else { BUG(node.span(), "`!` operator on invalid type - " << ty_val); } m_builder.push_stmt_assign(res.as_Temporary(), ::MIR::RValue::make_UniOp({ mv$(val), ::MIR::eUniOp::INV })); break; case ::HIR::ExprNode_UniOp::Op::Negate: if( ty_val.m_data.is_Primitive() ) { switch( ty_val.m_data.as_Primitive() ) { case ::HIR::CoreType::Str: case ::HIR::CoreType::Char: case ::HIR::CoreType::Bool: BUG(node.span(), "`-` operator on invalid type - " << ty_val); break; case ::HIR::CoreType::U8: case ::HIR::CoreType::U16: case ::HIR::CoreType::U32: case ::HIR::CoreType::U64: case ::HIR::CoreType::Usize: BUG(node.span(), "`-` operator on unsigned integer - " << ty_val); break; default: break; } } else { BUG(node.span(), "`!` operator on invalid type - " << ty_val); } m_builder.push_stmt_assign(res.as_Temporary(), ::MIR::RValue::make_UniOp({ mv$(val), ::MIR::eUniOp::NEG })); break; } m_builder.set_result( node.span(), mv$(res) ); } void visit(::HIR::ExprNode_Borrow& node) override { TRACE_FUNCTION_F("_Borrow"); const auto& ty_val = node.m_value->m_res_type; this->visit_node_ptr(node.m_value); auto val = m_builder.lvalue_or_temp( ty_val, m_builder.get_result(node.m_value->span()) ); auto res = m_builder.new_temporary(node.m_res_type); m_builder.push_stmt_assign(res.as_Temporary(), ::MIR::RValue::make_Borrow({ 0, node.m_type, mv$(val) })); m_builder.set_result( node.span(), mv$(res) ); } void visit(::HIR::ExprNode_Cast& node) override { TRACE_FUNCTION_F("_Cast"); this->visit_node_ptr(node.m_value); auto val = m_builder.lvalue_or_temp( node.m_value->m_res_type, m_builder.get_result(node.m_value->span()) ); const auto& ty_out = node.m_res_type; const auto& ty_in = node.m_value->m_res_type; TU_MATCH_DEF( ::HIR::TypeRef::Data, (ty_out.m_data), (de), ( BUG(node.span(), "Invalid cast to " << ty_out); ), (Pointer, if( ty_in.m_data.is_Primitive() && ty_in.m_data.as_Primitive() == ::HIR::CoreType::Usize ) { // TODO: Only valid if T: Sized in *{const/mut/move} T } else TU_IFLET( ::HIR::TypeRef::Data, ty_in.m_data, Borrow, se, if( *de.inner != *se.inner ) { BUG(node.span(), "Cannot cast to " << ty_out << " from " << ty_in); } // Valid ) else TU_IFLET( ::HIR::TypeRef::Data, ty_in.m_data, Function, se, if( *de.inner != ::HIR::TypeRef::new_unit() ) { BUG(node.span(), "Cannot cast to " << ty_out << " from " << ty_in); } // Valid ) else TU_IFLET( ::HIR::TypeRef::Data, ty_in.m_data, Pointer, se, // Valid ) else { BUG(node.span(), "Cannot cast to pointer from " << ty_in); } ), (Primitive, switch(de) { case ::HIR::CoreType::Str: BUG(node.span(), "Cannot cast to str"); break; case ::HIR::CoreType::Char: if( ty_in.m_data.is_Primitive() && ty_in.m_data.as_Primitive() == ::HIR::CoreType::U8 ) { // Valid } else { BUG(node.span(), "Cannot cast to char from " << ty_in); } break; case ::HIR::CoreType::Bool: BUG(node.span(), "Cannot cast to bool"); break; case ::HIR::CoreType::F32: case ::HIR::CoreType::F64: TU_IFLET(::HIR::TypeRef::Data, ty_in.m_data, Primitive, se, switch(de) { case ::HIR::CoreType::Str: case ::HIR::CoreType::Char: case ::HIR::CoreType::Bool: BUG(node.span(), "Cannot cast to " << ty_out << " from " << ty_in); break; default: // Valid break; } ) else { BUG(node.span(), "Cannot cast to " << ty_out << " from " << ty_in); } break; default: TU_IFLET(::HIR::TypeRef::Data, ty_in.m_data, Primitive, se, switch(de) { case ::HIR::CoreType::Str: BUG(node.span(), "Cannot cast to " << ty_out << " from " << ty_in); default: // Valid break; } ) else TU_IFLET(::HIR::TypeRef::Data, ty_in.m_data, Path, se, TU_IFLET(::HIR::TypeRef::TypePathBinding, se.binding, Enum, pbe, // TODO: Check if it's a repr(ty/C) enum - and if the type matches ) else { BUG(node.span(), "Cannot cast to " << ty_out << " from " << ty_in); } ) else if( de == ::HIR::CoreType::Usize && ty_in.m_data.is_Pointer() ) { // TODO: Only valid for T: Sized? } else if( de == ::HIR::CoreType::Usize && ty_in.m_data.is_Function() ) { // TODO: Always valid? } else { BUG(node.span(), "Cannot cast to " << ty_out << " from " << ty_in); } break; } ) ) auto res = m_builder.new_temporary(node.m_res_type); m_builder.push_stmt_assign(res.clone(), ::MIR::RValue::make_Cast({ mv$(val), node.m_res_type.clone() })); m_builder.set_result( node.span(), mv$(res) ); } void visit(::HIR::ExprNode_Unsize& node) override { TRACE_FUNCTION_F("_Unsize"); this->visit_node_ptr(node.m_value); auto ptr_lval = m_builder.lvalue_or_temp( node.m_value->m_res_type, m_builder.get_result(node.span()) ); const auto& ty_out = node.m_res_type; const auto& ty_in = node.m_value->m_res_type; TU_MATCH_DEF( ::HIR::TypeRef::Data, (ty_out.m_data), (e), ( TODO(node.span(), "MIR _Unsize to " << ty_out); ), // TODO: Unsize custom types containing a ?Size generic - See the Unsize trait //(Path, // ), //(Generic, // ), (Slice, if( ty_in.m_data.is_Array() ) { const auto& in_array = ty_in.m_data.as_Array(); auto size_lval = m_builder.lvalue_or_temp( ::HIR::TypeRef(::HIR::CoreType::Usize), ::MIR::Constant( static_cast(in_array.size_val) ) ); m_builder.set_result( node.span(), ::MIR::RValue::make_MakeDst({ mv$(ptr_lval), mv$(size_lval) }) ); } else if( ty_in.m_data.is_Generic() ) { // HACK: FixedSizeArray uses `A: Unsize<[T]>` which will lead to the above code not working (as the size isn't known). // - Maybe _Meta on the `&A` would work as a stopgap (since A: Sized, it won't collide with &[T] or similar) auto size_lval = m_builder.lvalue_or_temp( ::HIR::TypeRef(::HIR::CoreType::Usize), ::MIR::RValue::make_DstMeta({ ptr_lval.clone() }) ); m_builder.set_result( node.span(), ::MIR::RValue::make_MakeDst({ mv$(ptr_lval), mv$(size_lval) }) ); } else { ASSERT_BUG(node.span(), ty_in.m_data.is_Array(), "Unsize to slice from non-array - " << ty_in); } ), (TraitObject, // TODO: Obtain the vtable if the destination is a trait object // vtable exists as an unnamable associated type ::HIR::Path vtable { ty_in.clone(), e.m_trait.m_path.clone(), "#vtable" }; ::HIR::TypeRef vtable_type { {} }; auto vtable_lval = m_builder.lvalue_or_temp( ::HIR::TypeRef::new_borrow(::HIR::BorrowType::Shared, mv$(vtable_type)), ::MIR::RValue( ::MIR::Constant::make_ItemAddr(mv$(vtable)) ) ); m_builder.set_result( node.span(), ::MIR::RValue::make_MakeDst({ mv$(ptr_lval), mv$(vtable_lval) }) ); ) ) } void visit(::HIR::ExprNode_Index& node) override { TRACE_FUNCTION_F("_Index"); // NOTE: Calculate the index first (so if it borrows from the source, it's over by the time that's needed) const auto& ty_idx = node.m_index->m_res_type; this->visit_node_ptr(node.m_index); auto index = m_builder.lvalue_or_temp( ty_idx, m_builder.get_result(node.m_index->span()) ); const auto& ty_val = node.m_value->m_res_type; this->visit_node_ptr(node.m_value); auto value = m_builder.lvalue_or_temp( ty_val, m_builder.get_result(node.m_value->span()) ); ::MIR::RValue limit_val; TU_MATCH_DEF(::HIR::TypeRef::Data, (ty_val.m_data), (e), ( BUG(node.span(), "Indexing unsupported type " << ty_val); ), (Array, limit_val = ::MIR::Constant( e.size_val ); ), (Slice, limit_val = ::MIR::RValue::make_DstMeta({ value.clone() }); ) ) TU_MATCH_DEF(::HIR::TypeRef::Data, (ty_idx.m_data), (e), ( BUG(node.span(), "Indexing using unsupported index type " << ty_idx); ), (Primitive, if( e != ::HIR::CoreType::Usize ) { BUG(node.span(), "Indexing using unsupported index type " << ty_idx); } ) ) // Range checking (DISABLED) if( false ) { auto limit_lval = m_builder.lvalue_or_temp(node.m_index->m_res_type, mv$(limit_val)); auto cmp_res = m_builder.new_temporary( ::HIR::CoreType::Bool ); m_builder.push_stmt_assign(cmp_res.clone(), ::MIR::RValue::make_BinOp({ index.clone(), ::MIR::eBinOp::GE, mv$(limit_lval) })); auto arm_panic = m_builder.new_bb_unlinked(); auto arm_continue = m_builder.new_bb_unlinked(); m_builder.end_block( ::MIR::Terminator::make_If({ mv$(cmp_res), arm_panic, arm_continue }) ); m_builder.set_cur_block( arm_panic ); // TODO: Call an "index fail" method which always panics. //m_builder.end_block( ::MIR::Terminator::make_Panic({}) ); m_builder.end_block( ::MIR::Terminator::make_Diverge({}) ); m_builder.set_cur_block( arm_continue ); } m_builder.set_result( node.span(), ::MIR::LValue::make_Index({ box$(index), box$(value) }) ); } void visit(::HIR::ExprNode_Deref& node) override { const Span& sp = node.span(); TRACE_FUNCTION_F("_Deref"); const auto& ty_val = node.m_value->m_res_type; this->visit_node_ptr(node.m_value); auto val = m_builder.lvalue_or_temp( ty_val, m_builder.get_result(node.m_value->span()) ); TU_MATCH_DEF( ::HIR::TypeRef::Data, (ty_val.m_data), (te), ( BUG(sp, "Deref on unsupported type - " << ty_val); ), //(Array, // ), (Pointer, ), (Borrow, ) ) m_builder.set_result( node.span(), ::MIR::LValue::make_Deref({ box$(val) }) ); } void visit(::HIR::ExprNode_TupleVariant& node) override { TRACE_FUNCTION_F("_TupleVariant"); ::std::vector< ::MIR::LValue> values; values.reserve( node.m_args.size() ); for(auto& arg : node.m_args) { this->visit_node_ptr(arg); values.push_back( m_builder.lvalue_or_temp( arg->m_res_type, m_builder.get_result(arg->span()) ) ); } m_builder.set_result( node.span(), ::MIR::RValue::make_Struct({ node.m_path.clone(), mv$(values) }) ); } void visit(::HIR::ExprNode_CallPath& node) override { TRACE_FUNCTION_F("_CallPath " << node.m_path); ::std::vector< ::MIR::LValue> values; values.reserve( node.m_args.size() ); for(auto& arg : node.m_args) { this->visit_node_ptr(arg); values.push_back( m_builder.lvalue_or_temp( arg->m_res_type, m_builder.get_result(arg->span()) ) ); } // TODO: Obtain function type for this function auto fcn_ty_data = ::HIR::FunctionType { false, "", box$( node.m_cache.m_arg_types.back().clone() ), {} }; for(unsigned int i = 0; i < node.m_cache.m_arg_types.size() - 1; i ++) { fcn_ty_data.m_arg_types.push_back( node.m_cache.m_arg_types[i].clone() ); } auto fcn_val = m_builder.new_temporary( ::HIR::TypeRef(mv$(fcn_ty_data)) ); m_builder.push_stmt_assign( fcn_val.clone(), ::MIR::RValue::make_Constant( ::MIR::Constant(node.m_path.clone()) ) ); auto panic_block = m_builder.new_bb_unlinked(); auto next_block = m_builder.new_bb_unlinked(); auto res = m_builder.new_temporary( node.m_res_type ); m_builder.end_block(::MIR::Terminator::make_Call({ next_block, panic_block, res.clone(), mv$(fcn_val), mv$(values) })); m_builder.set_cur_block(panic_block); // TODO: Proper panic handling, including scope destruction m_builder.end_block( ::MIR::Terminator::make_Diverge({}) ); m_builder.set_cur_block( next_block ); m_builder.set_result( node.span(), mv$(res) ); } void visit(::HIR::ExprNode_CallValue& node) override { TRACE_FUNCTION_F("_CallValue " << node.m_value->m_res_type); // _CallValue is ONLY valid on function pointers (all others must be desugared) ASSERT_BUG(node.span(), node.m_value->m_res_type.m_data.is_Function(), "Leftover _CallValue on a non-fn()"); this->visit_node_ptr(node.m_value); auto fcn_val = m_builder.lvalue_or_temp( node.m_value->m_res_type, m_builder.get_result(node.m_value->span()) ); ::std::vector< ::MIR::LValue> values; values.reserve( node.m_args.size() ); for(auto& arg : node.m_args) { this->visit_node_ptr(arg); values.push_back( m_builder.lvalue_or_temp( arg->m_res_type, m_builder.get_result(arg->span()) ) ); } auto panic_block = m_builder.new_bb_unlinked(); auto next_block = m_builder.new_bb_unlinked(); auto res = m_builder.new_temporary( node.m_res_type ); m_builder.end_block(::MIR::Terminator::make_Call({ next_block, panic_block, res.clone(), mv$(fcn_val), mv$(values) })); m_builder.set_cur_block(panic_block); // TODO: Proper panic handling m_builder.end_block( ::MIR::Terminator::make_Diverge({}) ); m_builder.set_cur_block( next_block ); m_builder.set_result( node.span(), mv$(res) ); } void visit(::HIR::ExprNode_CallMethod& node) override { // TODO: Allow use on trait objects? May not be needed, depends. BUG(node.span(), "Leftover _CallMethod"); } void visit(::HIR::ExprNode_Field& node) override { TRACE_FUNCTION_F("_Field"); this->visit_node_ptr(node.m_value); auto val = m_builder.get_result_lvalue(node.m_value->span()); unsigned int idx; if( '0' <= node.m_field[0] && node.m_field[0] <= '9' ) { ::std::stringstream(node.m_field) >> idx; } else { const auto& str = *node.m_value->m_res_type.m_data.as_Path().binding.as_Struct(); const auto& fields = str.m_data.as_Named(); idx = ::std::find_if( fields.begin(), fields.end(), [&](const auto& x){ return x.first == node.m_field; } ) - fields.begin(); } m_builder.set_result( node.span(), ::MIR::LValue::make_Field({ box$(val), idx }) ); } void visit(::HIR::ExprNode_Literal& node) override { TRACE_FUNCTION_F("_Literal"); TU_MATCHA( (node.m_data), (e), (Integer, switch(node.m_res_type.m_data.as_Primitive()) { case ::HIR::CoreType::U8: case ::HIR::CoreType::U16: case ::HIR::CoreType::U32: case ::HIR::CoreType::U64: case ::HIR::CoreType::Usize: m_builder.set_result(node.span(), ::MIR::RValue( ::MIR::Constant(e.m_value) )); break; case ::HIR::CoreType::I8: case ::HIR::CoreType::I16: case ::HIR::CoreType::I32: case ::HIR::CoreType::I64: case ::HIR::CoreType::Isize: m_builder.set_result(node.span(), ::MIR::RValue( ::MIR::Constant( static_cast(e.m_value) ) )); break; case ::HIR::CoreType::Char: m_builder.set_result(node.span(), ::MIR::RValue( ::MIR::Constant( static_cast(e.m_value) ) )); break; default: BUG(node.span(), "Integer literal with unexpected type - " << node.m_res_type); } ), (Float, m_builder.set_result(node.span(), ::MIR::RValue::make_Constant( ::MIR::Constant(e.m_value) )); ), (Boolean, m_builder.set_result(node.span(), ::MIR::RValue::make_Constant( ::MIR::Constant(e) )); ), (String, m_builder.set_result(node.span(), ::MIR::RValue::make_Constant( ::MIR::Constant(e) )); ), (ByteString, auto v = mv$( *reinterpret_cast< ::std::vector*>( &e) ); m_builder.set_result(node.span(), ::MIR::RValue::make_Constant( ::MIR::Constant(mv$(v)) )); ) ) } void visit(::HIR::ExprNode_UnitVariant& node) override { TRACE_FUNCTION_F("_UnitVariant"); m_builder.set_result( node.span(), ::MIR::RValue::make_Struct({ node.m_path.clone(), {} }) ); } void visit(::HIR::ExprNode_PathValue& node) override { TRACE_FUNCTION_F("_PathValue - " << node.m_path); m_builder.set_result( node.span(), ::MIR::LValue::make_Static(node.m_path.clone()) ); } void visit(::HIR::ExprNode_Variable& node) override { TRACE_FUNCTION_F("_Variable - " << node.m_name << " #" << node.m_slot); m_builder.set_result( node.span(), ::MIR::LValue::make_Variable(node.m_slot) ); } void visit(::HIR::ExprNode_StructLiteral& node) override { TRACE_FUNCTION_F("_StructLiteral"); ::MIR::LValue base_val; if( node.m_base_value ) { this->visit_node_ptr(node.m_base_value); base_val = m_builder.get_result_lvalue(node.m_base_value->span()); } const ::HIR::t_struct_fields* fields_ptr = nullptr; TU_MATCH(::HIR::TypeRef::TypePathBinding, (node.m_res_type.m_data.as_Path().binding), (e), (Unbound, ), (Opaque, ), (Enum, const auto& var_name = node.m_path.m_path.m_components.back(); const auto& enm = *e; auto it = ::std::find_if(enm.m_variants.begin(), enm.m_variants.end(), [&](const auto&v)->auto{ return v.first == var_name; }); assert(it != enm.m_variants.end()); fields_ptr = &it->second.as_Struct(); ), (Struct, fields_ptr = &e->m_data.as_Named(); ) ) assert(fields_ptr); const ::HIR::t_struct_fields& fields = *fields_ptr; ::std::vector values_set; ::std::vector< ::MIR::LValue> values; values.resize( fields.size() ); values_set.resize( fields.size() ); for(auto& ent : node.m_values) { auto idx = ::std::find_if(fields.begin(), fields.end(), [&](const auto&x){ return x.first == ent.first; }) - fields.begin(); assert( !values_set[idx] ); values_set[idx] = true; this->visit_node_ptr(ent.second); values.at(idx) = m_builder.lvalue_or_temp( ent.second->m_res_type, m_builder.get_result(ent.second->span()) ); } for(unsigned int i = 0; i < values.size(); i ++) { if( !values_set[i] ) { if( !node.m_base_value) { ERROR(node.span(), E0000, "Field '" << fields[i].first << "' not specified"); } values[i] = ::MIR::LValue::make_Field({ box$( base_val.clone() ), i }); } else { // Partial move support will handle dropping the rest? } } m_builder.set_result( node.span(), ::MIR::RValue::make_Struct({ node.m_path.clone(), mv$(values) }) ); } void visit(::HIR::ExprNode_Tuple& node) override { TRACE_FUNCTION_F("_Tuple"); ::std::vector< ::MIR::LValue> values; values.reserve( node.m_vals.size() ); for(auto& arg : node.m_vals) { this->visit_node_ptr(arg); values.push_back( m_builder.lvalue_or_temp( arg->m_res_type, m_builder.get_result(arg->span()) ) ); } m_builder.set_result( node.span(), ::MIR::RValue::make_Tuple({ mv$(values) }) ); } void visit(::HIR::ExprNode_ArrayList& node) override { TRACE_FUNCTION_F("_ArrayList"); ::std::vector< ::MIR::LValue> values; values.reserve( node.m_vals.size() ); for(auto& arg : node.m_vals) { this->visit_node_ptr(arg); values.push_back( m_builder.lvalue_or_temp( arg->m_res_type, m_builder.get_result(arg->span()) ) ); } m_builder.set_result( node.span(), ::MIR::RValue::make_Array({ mv$(values) }) ); } void visit(::HIR::ExprNode_ArraySized& node) override { TRACE_FUNCTION_F("_ArraySized"); this->visit_node_ptr( node.m_val ); auto value = m_builder.lvalue_or_temp( node.m_val->m_res_type, m_builder.get_result(node.m_val->span()) ); m_builder.set_result( node.span(), ::MIR::RValue::make_SizedArray({ mv$(value), static_cast(node.m_size_val) }) ); } void visit(::HIR::ExprNode_Closure& node) override { TRACE_FUNCTION_F("_Closure - " << node.m_obj_path); // Emit construction of the closure. ::std::vector< ::MIR::LValue> vals; for( const auto cap_idx : node.m_var_captures ) { ASSERT_BUG(node.span(), cap_idx < m_variable_types.size(), "Capture #" << cap_idx << " not in variable set (" << m_variable_types.size() << " total)"); if( node.m_is_move ) { vals.push_back( ::MIR::LValue::make_Variable(cap_idx) ); } else { auto borrow_ty = ::HIR::BorrowType::Shared; auto lval = m_builder.lvalue_or_temp( ::HIR::TypeRef::new_borrow(borrow_ty, m_variable_types[cap_idx].clone()), ::MIR::RValue::make_Borrow({ 0, borrow_ty, ::MIR::LValue::make_Variable(cap_idx) }) ); vals.push_back( mv$(lval) ); } } m_builder.set_result( node.span(), ::MIR::RValue::make_Struct({ node.m_obj_path.clone(), mv$(vals) }) ); } }; } ::MIR::FunctionPointer LowerMIR(const ::HIR::ExprPtr& ptr, const ::std::vector< ::std::pair< ::HIR::Pattern, ::HIR::TypeRef> >& args) { TRACE_FUNCTION; ::MIR::Function fcn; fcn.named_variables.reserve(ptr.m_bindings.size()); for(const auto& t : ptr.m_bindings) fcn.named_variables.push_back( t.clone() ); // TODO: Construct builder here and lend to ExprVisitor_Conv { ExprVisitor_Conv ev { fcn, ptr.m_bindings }; // 1. Apply destructuring to arguments unsigned int i = 0; for( const auto& arg : args ) { ev.define_vars_from(ptr->span(), arg.first); ev.destructure_from(ptr->span(), arg.first, ::MIR::LValue::make_Argument({i})); } // 2. Destructure code ::HIR::ExprNode& root_node = const_cast<::HIR::ExprNode&>(*ptr); root_node.visit( ev ); } return ::MIR::FunctionPointer(new ::MIR::Function(mv$(fcn))); } namespace { class OuterVisitor: public ::HIR::Visitor { public: OuterVisitor(const ::HIR::Crate& crate) {} // NOTE: This is left here to ensure that any expressions that aren't handled by higher code cause a failure void visit_expr(::HIR::ExprPtr& exp) override { BUG(Span(), "visit_expr hit in OuterVisitor"); } void visit_type(::HIR::TypeRef& ty) override { TU_IFLET(::HIR::TypeRef::Data, ty.m_data, Array, e, this->visit_type( *e.inner ); DEBUG("Array size " << ty); if( e.size ) { auto fcn = LowerMIR(e.size, {}); e.size.m_mir = mv$(fcn); } ) else { ::HIR::Visitor::visit_type(ty); } } // ------ // Code-containing items // ------ void visit_function(::HIR::ItemPath p, ::HIR::Function& item) override { if( item.m_code ) { DEBUG("Function code " << p); item.m_code.m_mir = LowerMIR(item.m_code, item.m_args); } else { DEBUG("Function code " << p << " (none)"); } } void visit_static(::HIR::ItemPath p, ::HIR::Static& item) override { if( item.m_value ) { DEBUG("`static` value " << p); item.m_value.m_mir = LowerMIR(item.m_value, {}); } } void visit_constant(::HIR::ItemPath p, ::HIR::Constant& item) override { if( item.m_value ) { DEBUG("`const` value " << p); item.m_value.m_mir = LowerMIR(item.m_value, {}); } } void visit_enum(::HIR::ItemPath p, ::HIR::Enum& item) override { //auto enum_type = ::HIR::TypeRef(::HIR::CoreType::Isize); for(auto& var : item.m_variants) { TU_IFLET(::HIR::Enum::Variant, var.second, Value, e, //DEBUG("Enum value " << p << " - " << var.first); //::std::vector< ::HIR::TypeRef> tmp; //ExprVisitor_Extract ev(m_resolve, tmp, m_new_trait_impls); //ev.visit_root(*e); ) } } }; } // -------------------------------------------------------------------- // MirBuilder // -------------------------------------------------------------------- MirBuilder::MirBuilder(::MIR::Function& output): m_output(output), m_block_active(false), m_result_valid(false), m_fcn_scope(*this, 0) { set_cur_block( new_bb_unlinked() ); m_scopes.push_back( ScopeDef {} ); m_scope_stack.push_back( 0 ); m_scopes.push_back( ScopeDef { false, ScopeType::make_Temporaries({}) } ); m_scope_stack.push_back( 1 ); } MirBuilder::~MirBuilder() { if( has_result() ) { push_stmt_assign( ::MIR::LValue::make_Return({}), get_result(Span()) ); } if( block_active() ) { terminate_scope( Span(), ScopeHandle { *this, 1 } ); terminate_scope( Span(), mv$(m_fcn_scope) ); end_block( ::MIR::Terminator::make_Return({}) ); } } void MirBuilder::define_variable(unsigned int idx) { DEBUG("DEFINE var" << idx /* << ": " << m_output.named_variables.at(idx)*/); for( auto scope_idx : ::reverse(m_scope_stack) ) { auto& scope_def = m_scopes.at(scope_idx); TU_MATCH_DEF( ScopeType, (scope_def.data), (e), ( ), (Variables, auto it = ::std::find(e.vars.begin(), e.vars.end(), idx); assert(it == e.vars.end()); e.vars.push_back( idx ); e.var_states.push_back( VarState::Uninit ); return ; ), (Split, BUG(Span(), "Variable " << idx << " introduced within a Split"); ) ) } BUG(Span(), "Variable " << idx << " introduced with no Variable scope"); } ::MIR::LValue MirBuilder::new_temporary(const ::HIR::TypeRef& ty) { unsigned int rv = m_output.temporaries.size(); DEBUG("DEFINE tmp" << rv << ": " << ty); m_output.temporaries.push_back( ty.clone() ); temporaries_valid.push_back(false); ScopeDef* top_scope = nullptr; for(unsigned int i = m_scope_stack.size(); i --; ) { auto idx = m_scope_stack[i]; if( m_scopes.at( idx ).data.is_Temporaries() ) { top_scope = &m_scopes.at(idx); break ; } } assert( top_scope ); auto& tmp_scope = top_scope->data.as_Temporaries(); tmp_scope.temporaries.push_back( rv ); tmp_scope.states.push_back( VarState::Uninit ); return ::MIR::LValue::make_Temporary({rv}); } ::MIR::LValue MirBuilder::lvalue_or_temp(const ::HIR::TypeRef& ty, ::MIR::RValue val) { TU_IFLET(::MIR::RValue, val, Use, e, return mv$(e); ) else { auto temp = new_temporary(ty); push_stmt_assign( ::MIR::LValue(temp.as_Temporary()), mv$(val) ); return temp; } } ::MIR::RValue MirBuilder::get_result(const Span& sp) { if(!m_result_valid) { BUG(sp, "No value avaliable"); } auto rv = mv$(m_result); m_result_valid = false; return rv; } ::MIR::LValue MirBuilder::get_result_lvalue(const Span& sp) { auto rv = get_result(sp); TU_IFLET(::MIR::RValue, rv, Use, e, return mv$(e); ) else { BUG(sp, "LValue expected, got RValue"); } } void MirBuilder::set_result(const Span& sp, ::MIR::RValue val) { if(m_result_valid) { BUG(sp, "Pushing a result over an existing result"); } m_result = mv$(val); m_result_valid = true; } void MirBuilder::push_stmt_assign(::MIR::LValue dst, ::MIR::RValue val) { ASSERT_BUG(Span(), m_block_active, "Pushing statement with no active block"); ASSERT_BUG(Span(), dst.tag() != ::MIR::LValue::TAGDEAD, ""); ASSERT_BUG(Span(), val.tag() != ::MIR::RValue::TAGDEAD, ""); TU_MATCHA( (val), (e), (Use, this->moved_lvalue(e); ), (Constant, ), (SizedArray, this->moved_lvalue(e.val); ), (Borrow, if( e.type == ::HIR::BorrowType::Owned ) { TODO(Span(), "Move using &move"); // Likely would require a marker that ensures that the memory isn't reused. this->moved_lvalue(e.val); } else { // Doesn't move } ), (Cast, this->moved_lvalue(e.val); ), (BinOp, switch(e.op) { case ::MIR::eBinOp::EQ: case ::MIR::eBinOp::NE: case ::MIR::eBinOp::GT: case ::MIR::eBinOp::GE: case ::MIR::eBinOp::LT: case ::MIR::eBinOp::LE: // Takes an implicit borrow... and only works on copy, so why is this block here? break; default: this->moved_lvalue(e.val_l); this->moved_lvalue(e.val_r); break; } ), (UniOp, this->moved_lvalue(e.val); ), (DstMeta, // Doesn't move ), (MakeDst, // Doesn't move ptr_val this->moved_lvalue(e.meta_val); ), (Tuple, for(const auto& val : e.vals) this->moved_lvalue(val); ), (Array, for(const auto& val : e.vals) this->moved_lvalue(val); ), (Struct, for(const auto& val : e.vals) this->moved_lvalue(val); ) ) // Drop target if populated TU_MATCH_DEF(::MIR::LValue, (dst), (e), ( ), (Temporary, switch(get_temp_state(e.idx)) { case VarState::Uninit: break; case VarState::Dropped: // ERROR? break; case VarState::Moved: case VarState::MaybeMoved: // ERROR? break; case VarState::Init: // ERROR. break; } set_temp_state(e.idx, VarState::Init); ), (Return, // Don't drop. //m_return_valid = true; ), (Variable, switch( get_variable_state(e) ) { case VarState::Uninit: case VarState::Moved: break; case VarState::Dropped: // TODO: Is this an error? break; case VarState::Init: // 1. Must be mut // 2. Drop push_stmt_drop( dst.clone() ); break; case VarState::MaybeMoved: // TODO: Conditional drop break; } set_variable_state(e, VarState::Init); ) ) m_output.blocks.at(m_current_block).statements.push_back( ::MIR::Statement::make_Assign({ mv$(dst), mv$(val) }) ); } void MirBuilder::push_stmt_drop(::MIR::LValue val) { ASSERT_BUG(Span(), m_block_active, "Pushing statement with no active block"); ASSERT_BUG(Span(), val.tag() != ::MIR::LValue::TAGDEAD, ""); m_output.blocks.at(m_current_block).statements.push_back( ::MIR::Statement::make_Drop({ ::MIR::eDropKind::DEEP, mv$(val) }) ); } void MirBuilder::set_cur_block(unsigned int new_block) { if( m_block_active ) { BUG(Span(), "Updating block when previous is active"); } DEBUG("BB" << new_block << " START"); m_current_block = new_block; m_block_active = true; } void MirBuilder::end_block(::MIR::Terminator term) { if( !m_block_active ) { BUG(Span(), "Terminating block when none active"); } DEBUG("BB" << m_current_block << " END -> " << term); m_output.blocks.at(m_current_block).terminator = mv$(term); m_block_active = false; m_current_block = 0; } void MirBuilder::pause_cur_block() { if( !m_block_active ) { BUG(Span(), "Pausing block when none active"); } DEBUG("BB" << m_current_block << " PAUSE"); m_block_active = false; m_current_block = 0; } ::MIR::BasicBlockId MirBuilder::new_bb_linked() { auto rv = new_bb_unlinked(); DEBUG("BB" << rv); end_block( ::MIR::Terminator::make_Goto(rv) ); set_cur_block(rv); return rv; } ::MIR::BasicBlockId MirBuilder::new_bb_unlinked() { auto rv = m_output.blocks.size(); DEBUG("BB" << rv); m_output.blocks.push_back({}); return rv; } ScopeHandle MirBuilder::new_scope_var(const Span& sp) { unsigned int idx = m_scopes.size(); m_scopes.push_back( {false, ScopeType::make_Variables({})} ); m_scope_stack.push_back( idx ); DEBUG("START (var) scope " << idx); return ScopeHandle { *this, idx }; } ScopeHandle MirBuilder::new_scope_temp(const Span& sp) { unsigned int idx = m_scopes.size(); m_scopes.push_back( {false, ScopeType::make_Temporaries({})} ); m_scope_stack.push_back( idx ); DEBUG("START (temp) scope " << idx); return ScopeHandle { *this, idx }; } ScopeHandle MirBuilder::new_scope_split(const Span& sp) { unsigned int idx = m_scopes.size(); m_scopes.push_back( {false, ScopeType::make_Split({})} ); m_scopes.back().data.as_Split().arms.push_back( {} ); m_scope_stack.push_back( idx ); DEBUG("START (split) scope " << idx); return ScopeHandle { *this, idx }; } ScopeHandle MirBuilder::new_scope_loop(const Span& sp) { unsigned int idx = m_scopes.size(); m_scopes.push_back( {false, ScopeType::make_Loop({})} ); m_scope_stack.push_back( idx ); DEBUG("START (loop) scope " << idx); return ScopeHandle { *this, idx }; } void MirBuilder::terminate_scope(const Span& sp, ScopeHandle scope) { DEBUG("DONE scope " << scope.idx); // 1. Check that this is the current scope (at the top of the stack) if( m_scope_stack.back() != scope.idx ) { DEBUG("m_scope_stack = " << m_scope_stack); auto it = ::std::find( m_scope_stack.begin(), m_scope_stack.end(), scope.idx ); if( it == m_scope_stack.end() ) BUG(sp, "Terminating scope not on the stack - scope " << scope.idx); BUG(sp, "Terminating scope " << scope.idx << " when not at top of stack, " << (m_scope_stack.end() - it - 1) << " scopes in the way"); } auto& scope_def = m_scopes.at(scope.idx); ASSERT_BUG( sp, scope_def.complete == false, "Terminating scope which is already terminated" ); complete_scope(scope_def); // 2. Emit drops for all non-moved variables (share with below) drop_scope_values(scope_def); // 3. Pop scope (last because `drop_scope_values` uses the stack) m_scope_stack.pop_back(); } void MirBuilder::terminate_scope_early(const Span& sp, const ScopeHandle& scope) { DEBUG("EARLY scope " << scope.idx); // 1. Ensure that this block is in the stack auto it = ::std::find( m_scope_stack.begin(), m_scope_stack.end(), scope.idx ); if( it == m_scope_stack.end() ) { BUG(sp, "Early-terminating scope not on the stack"); } unsigned int slot = it - m_scope_stack.begin(); bool is_conditional = false; for(unsigned int i = m_scope_stack.size(); i-- > slot; ) { auto idx = m_scope_stack[i]; auto& scope_def = m_scopes.at( idx ); // If a conditional block is hit, prevent full termination of the rest if( scope_def.data.is_Split() ) is_conditional = true; if( !is_conditional ) { DEBUG("Complete scope " << idx); complete_scope(scope_def); drop_scope_values(scope_def); m_scope_stack.pop_back(); } else { // Mark patial within this scope? DEBUG("Drop part of scope " << idx); // Emit drops for dropped values within this scope drop_scope_values(scope_def); // Inform the scope that it's been early-exited TU_IFLET( ScopeType, scope_def.data, Split, e, e.arms.back().has_early_terminated = true; ) } } } void MirBuilder::end_split_arm(const Span& sp, const ScopeHandle& handle, bool reachable) { ASSERT_BUG(sp, handle.idx < m_scopes.size(), ""); auto& sd = m_scopes.at( handle.idx ); ASSERT_BUG(sp, sd.data.is_Split(), ""); auto& sd_split = sd.data.as_Split(); ASSERT_BUG(sp, !sd_split.arms.empty(), ""); sd_split.arms.back().always_early_terminated = /*sd_split.arms.back().has_early_terminated &&*/ !reachable; // TODO: Undo moves from within the other scope sd_split.arms.push_back( {} ); } void MirBuilder::complete_scope(ScopeDef& sd) { sd.complete = true; TU_MATCHA( (sd.data), (e), (Temporaries, DEBUG("Temporaries " << e.temporaries); ), (Variables, DEBUG("Variables " << e.vars); ), (Loop, DEBUG("Loop"); ), (Split, assert( e.arms.size() > 1 ); DEBUG("Split - " << (e.arms.size() - 1) << " arms"); e.arms.pop_back(); // Merge all arms and apply upwards size_t var_count = 0; for(const auto& arm : e.arms) { var_count = ::std::max(var_count, arm.var_states.size()); } for(const auto& arm : e.arms) { DEBUG("><"); assert( arm.changed_var_states.size() == arm.var_states.size() ); for(unsigned int i = 0; i < arm.var_states.size(); i ++ ) { if( arm.changed_var_states[i] ) { auto new_state = arm.var_states[i]; // - NOTE: This scope should be off the stack now, so this call will get the original state auto old_state = get_variable_state(i); DEBUG("old_state = " << old_state << ", new_state = " << new_state); // TODO: Need to build up a merged copy of the states? Or just apply upwards directly? } } } ) ) } VarState MirBuilder::get_variable_state(unsigned int idx) const { 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), ( ), (Variables, auto it = ::std::find(e.vars.begin(), e.vars.end(), idx); if( it != e.vars.end() ) { unsigned int sub_idx = it - e.vars.begin(); assert(sub_idx < e.var_states.size()); return e.var_states[sub_idx]; } ), (Split, const auto& cur_arm = e.arms.back(); if( idx < cur_arm.changed_var_states.size() && cur_arm.changed_var_states[idx] ) { assert( idx < cur_arm.var_states.size() ); return cur_arm.var_states[idx]; } ) ) } BUG(Span(), "Variable " << idx << " not found in stack"); } void MirBuilder::set_variable_state(unsigned int idx, VarState state) { for( auto scope_idx : ::reverse(m_scope_stack) ) { auto& scope_def = m_scopes.at(scope_idx); TU_MATCH_DEF( ScopeType, (scope_def.data), (e), ( ), (Variables, auto it = ::std::find(e.vars.begin(), e.vars.end(), idx); if( it != e.vars.end() ) { unsigned int sub_idx = it - e.vars.begin(); assert(sub_idx < e.var_states.size()); e.var_states[sub_idx] = state; return ; } ), (Split, auto& cur_arm = e.arms.back(); if( idx < cur_arm.changed_var_states.size() ) { assert( idx < cur_arm.var_states.size() ); cur_arm.changed_var_states[idx] = true; cur_arm.var_states[idx] = state; return ; } ) ) } BUG(Span(), "Variable " << idx << " not found in stack"); } VarState MirBuilder::get_temp_state(unsigned int idx) const { 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, auto it = ::std::find(e.temporaries.begin(), e.temporaries.end(), idx); if( it != e.temporaries.end() ) { unsigned int sub_idx = it - e.temporaries.begin(); ASSERT_BUG(Span(), sub_idx < e.states.size(), "Temporary list sizes invalid - " << sub_idx << " >= " << e.states.size()); return e.states[sub_idx]; } ), (Split, // TODO: Does split account for temps? It should. ) ) } BUG(Span(), "Temporary " << idx << " not found in stack"); } void MirBuilder::set_temp_state(unsigned int idx, VarState state) { for( auto scope_idx : ::reverse(m_scope_stack) ) { auto& scope_def = m_scopes.at(scope_idx); TU_MATCH_DEF( ScopeType, (scope_def.data), (e), ( ), (Temporaries, auto it = ::std::find(e.temporaries.begin(), e.temporaries.end(), idx); if( it != e.temporaries.end() ) { unsigned int sub_idx = it - e.temporaries.begin(); assert(sub_idx < e.states.size()); e.states[sub_idx] = state; return ; } ), (Split, // TODO: Does split account for temps? It should. ) ) } } void MirBuilder::drop_scope_values(const ScopeDef& sd) { TU_MATCHA( (sd.data), (e), (Temporaries, for(auto tmp_idx : e.temporaries) { if( get_temp_state(tmp_idx) == VarState::Init ) { push_stmt_drop( ::MIR::LValue::make_Temporary({ tmp_idx }) ); set_temp_state(tmp_idx, VarState::Dropped); } } ), (Variables, for(auto var_idx : e.vars) { switch( get_variable_state(var_idx) ) { case VarState::Uninit: case VarState::Dropped: case VarState::Moved: break; case VarState::Init: push_stmt_drop( ::MIR::LValue::make_Variable(var_idx) ); break; case VarState::MaybeMoved: // TODO: Drop flags break; } } ), (Split, // No values, controls parent ), (Loop, // No values ) ) } void MirBuilder::moved_lvalue(const ::MIR::LValue& lv) { TU_MATCHA( (lv), (e), (Variable, set_variable_state(e, VarState::Moved); ), (Temporary, //TODO(Span(), "Mark temp as moved"); ), (Argument, //TODO(Span(), "Mark argument as moved"); ), (Static, //TODO(Span(), "Static - Assert that type is Copy"); ), (Return, BUG(Span(), "Read of return value"); ), (Field, //TODO(Span(), "Mark field of an lvalue as moved (if not Copy) - Partially moved structs?"); //moved_lvalue(*e.val); ), (Deref, //TODO(Span(), "Mark deref of an lvalue as moved (if not Copy) - Req. DerefMove/&move otherwise"); //moved_lvalue(*e.val); ), (Index, //TODO(Span(), "Mark index of an lvalue as moved (if not Copy) - Req. IndexGet/IndexMove"); //moved_lvalue(*e.val); moved_lvalue(*e.idx); ), (Downcast, // TODO: What if the inner is Copy? What if the inner is a hidden pointer? moved_lvalue(*e.val); ) ) } ScopeHandle::~ScopeHandle() { if( idx != ~0u ) { ASSERT_BUG(Span(), m_builder.m_scopes.size() > idx, "Scope invalid"); ASSERT_BUG(Span(), m_builder.m_scopes.at(idx).complete, "Scope " << idx << " not completed"); } } ::std::ostream& operator<<(::std::ostream& os, VarState x) { switch(x) { #define _(V) case VarState::V: os << #V; break; _(Uninit) _(Init) _(MaybeMoved) _(Moved) _(Dropped) #undef _ } return os; } // -------------------------------------------------------------------- void HIR_GenerateMIR(::HIR::Crate& crate) { OuterVisitor ov(crate); ov.visit_crate( crate ); }