diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mir/check.cpp | 408 |
1 files changed, 400 insertions, 8 deletions
diff --git a/src/mir/check.cpp b/src/mir/check.cpp index fd06efc3..e8b9cd46 100644 --- a/src/mir/check.cpp +++ b/src/mir/check.cpp @@ -10,10 +10,274 @@ #include <hir/visitor.hpp> #include <hir_typeck/static.hpp> -void MIR_Validate(const StaticTraitResolve& resolve, const ::MIR::Function& fcn, const ::std::vector< ::std::pair< ::HIR::Pattern, ::HIR::TypeRef> >& args) +namespace { + const unsigned int STMT_TERM = ~0u; + + #define MIR_BUG(state, ...) ( (state).print_bug( [&](auto& _os){_os << __VA_ARGS__; } ) ) + #define MIR_ASSERT(state, cnd, ...) do { if( !(cnd) ) (state).print_bug( [&](auto& _os){_os << "ASSERT " #cnd " failed - " << __VA_ARGS__; } ); } while(0) + #define MIR_TODO(state, ...) ( (state).print_todo( [&](auto& _os){_os << __VA_ARGS__; } ) ) + + struct MirCheckFailure { + }; + + struct State { + typedef ::std::vector< ::std::pair< ::HIR::Pattern, ::HIR::TypeRef> > t_args; + + const StaticTraitResolve& resolve; + const ::HIR::ItemPath& path; + const ::MIR::Function& fcn; + const t_args& args; + const ::HIR::TypeRef& ret_type; + + unsigned int bb_idx = 0; + unsigned int stmt_idx = 0; + const ::HIR::SimplePath* m_lang_Box; + + Span sp; + + State(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path, const ::MIR::Function& fcn, const t_args& args, const ::HIR::TypeRef& ret_type): + resolve(resolve), + path(path), + fcn(fcn), + args(args), + ret_type(ret_type) + { + if( resolve.m_crate.m_lang_items.count("owned_box") > 0 ) { + m_lang_Box = &resolve.m_crate.m_lang_items.at("owned_box"); + } + } + + void set_cur_stmt(unsigned int bb_idx, unsigned int stmt_idx) { + this->bb_idx = bb_idx; + this->stmt_idx = stmt_idx; + } + void set_cur_stmt_term(unsigned int bb_idx) { + this->bb_idx = bb_idx; + this->stmt_idx = STMT_TERM; + } + + void print_bug(::std::function<void(::std::ostream& os)> cb) const { + print_msg("ERROR", cb); + } + void print_todo(::std::function<void(::std::ostream& os)> cb) const { + print_msg("TODO", cb); + } + void print_msg(const char* tag, ::std::function<void(::std::ostream& os)> cb) const + { + auto& os = ::std::cerr; + os << "MIR " << tag << ": " << this->path << " BB" << this->bb_idx << "/"; + if( this->stmt_idx == STMT_TERM ) { + os << "TERM"; + } + else { + os << this->stmt_idx; + } + os << ": "; + cb(os); + os << ::std::endl; + // TODO: Throw something? Or mark an error so the rest of the function can be checked. + throw MirCheckFailure {}; + } + + const ::HIR::TypeRef& get_lvalue_type(::HIR::TypeRef& tmp, const ::MIR::LValue& val) const + { + TU_MATCH(::MIR::LValue, (val), (e), + (Variable, + return this->fcn.named_variables.at(e); + ), + (Temporary, + return this->fcn.temporaries.at(e.idx); + ), + (Argument, + return this->args.at(e.idx).second; + ), + (Static, + TU_MATCHA( (e.m_data), (pe), + (Generic, + MIR_ASSERT(*this, pe.m_params.m_types.empty(), "Path params on static"); + const auto& s = resolve.m_crate.get_static_by_path(sp, pe.m_path); + return s.m_type; + ), + (UfcsKnown, + MIR_TODO(*this, "LValue::Static - UfcsKnown - " << e); + ), + (UfcsUnknown, + MIR_BUG(*this, "Encountered UfcsUnknown in LValue::Static - " << e); + ), + (UfcsInherent, + MIR_TODO(*this, "LValue::Static - UfcsInherent - " << e); + ) + ) + ), + (Return, + return this->ret_type; + ), + (Field, + const auto& ty = this->get_lvalue_type(tmp, *e.val); + TU_MATCH_DEF( ::HIR::TypeRef::Data, (ty.m_data), (te), + ( + MIR_BUG(*this, "Field access on unexpected type - " << ty); + ), + // Array and Slice use LValue::Field when the index is constant and known-good + (Array, + return *te.inner; + ), + (Slice, + return *te.inner; + ), + (Tuple, + MIR_ASSERT(*this, e.field_index < te.size(), "Field index out of range in tuple " << e.field_index << " >= " << te.size()); + return te[e.field_index]; + ), + (Path, + MIR_ASSERT(*this, te.binding.is_Struct(), "Field on non-Struct - " << ty); + const auto& str = *te.binding.as_Struct(); + TU_MATCHA( (str.m_data), (se), + (Unit, + MIR_BUG(*this, "Field on unit-like struct - " << ty); + ), + (Tuple, + MIR_ASSERT(*this, e.field_index < se.size(), "Field index out of range in tuple-struct"); + const auto& fld = se[e.field_index]; + if( monomorphise_type_needed(fld.ent) ) { + tmp = monomorphise_type(sp, str.m_params, te.path.m_data.as_Generic().m_params, fld.ent); + this->resolve.expand_associated_types(sp, tmp); + return tmp; + } + else { + return fld.ent; + } + ), + (Named, + MIR_ASSERT(*this, e.field_index < se.size(), "Field index out of range in struct"); + const auto& fld = se[e.field_index].second; + if( monomorphise_type_needed(fld.ent) ) { + tmp = monomorphise_type(sp, str.m_params, te.path.m_data.as_Generic().m_params, fld.ent); + this->resolve.expand_associated_types(sp, tmp); + return tmp; + } + else { + return fld.ent; + } + ) + ) + ) + ) + ), + (Deref, + const auto& ty = this->get_lvalue_type(tmp, *e.val); + TU_MATCH_DEF( ::HIR::TypeRef::Data, (ty.m_data), (te), + ( + MIR_BUG(*this, "Deref on unexpected type - " << ty); + ), + (Path, + if( const auto* inner_ptr = this->is_type_owned_box(ty) ) + { + return *inner_ptr; + } + else { + MIR_BUG(*this, "Deref on unexpected type - " << ty); + } + ), + (Pointer, + return *te.inner; + ), + (Borrow, + return *te.inner; + ) + ) + ), + (Index, + const auto& ty = this->get_lvalue_type(tmp, *e.val); + TU_MATCH_DEF( ::HIR::TypeRef::Data, (ty.m_data), (te), + ( + MIR_BUG(*this, "Index on unexpected type - " << ty); + ), + (Slice, + return *te.inner; + ), + (Array, + return *te.inner; + ) + ) + ), + (Downcast, + const auto& ty = this->get_lvalue_type(tmp, *e.val); + TU_MATCH_DEF( ::HIR::TypeRef::Data, (ty.m_data), (te), + ( + MIR_BUG(*this, "Downcast on unexpected type - " << ty); + ), + (Path, + MIR_ASSERT(*this, te.binding.is_Enum(), "Downcast on non-Enum"); + const auto& enm = *te.binding.as_Enum(); + const auto& variants = enm.m_variants; + MIR_ASSERT(*this, 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) ); + tmp = ::HIR::TypeRef( mv$(tys) ); + return tmp; + ), + (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) ); + tmp = ::HIR::TypeRef( mv$(tys) ); + return tmp; + ) + ) + ) + ) + ) + ) + throw ""; + } + + const ::HIR::TypeRef* is_type_owned_box(const ::HIR::TypeRef& ty) const + { + if( m_lang_Box ) + { + if( ! ty.m_data.is_Path() ) { + return nullptr; + } + const auto& te = ty.m_data.as_Path(); + + if( ! te.path.m_data.is_Generic() ) { + return nullptr; + } + const auto& pe = te.path.m_data.as_Generic(); + + if( pe.m_path != *m_lang_Box ) { + return nullptr; + } + // TODO: Properly assert? + return &pe.m_params.m_types.at(0); + } + else + { + return nullptr; + } + } + }; +} + +void MIR_Validate(const StaticTraitResolve& resolve, const ::HIR::ItemPath& path, const ::MIR::Function& fcn, const State::t_args& args, const ::HIR::TypeRef& ret_type) { Span sp; + State state { resolve, path, fcn, args, ret_type }; // Validation rules: + + // [CFA] = Control Flow Analysis // - [CFA] All code paths from bb0 must end with either a return or a diverge (or loop) // - Requires checking the links between basic blocks, with a bitmap to catch loops/multipath { @@ -31,15 +295,18 @@ void MIR_Validate(const StaticTraitResolve& resolve, const ::MIR::Function& fcn, } visited_bbs[block] = true; + + state.set_cur_stmt_term(block); + #define PUSH_BB(idx, desc) do {\ - ASSERT_BUG(sp, idx < fcn.blocks.size(), "Invalid target block on bb" << block << " - " << desc << " bb" << idx);\ + if( !(idx < fcn.blocks.size() ) ) MIR_BUG(state, "Invalid target block - " << desc << " bb" << idx);\ if( visited_bbs[idx] == false ) {\ to_visit_blocks.push_back(idx); \ }\ } while(0) TU_MATCH(::MIR::Terminator, (fcn.blocks[block].terminator), (e), (Incomplete, - BUG(sp, "Encounterd `Incomplete` block in control flow - bb" << block); + MIR_BUG(state, "Encounterd `Incomplete` block in control flow"); ), (Return, returns = true; @@ -74,11 +341,135 @@ void MIR_Validate(const StaticTraitResolve& resolve, const ::MIR::Function& fcn, } } + // [ValState] = Value state tracking (use after move, uninit, ...) // - [ValState] No drops or usage of uninitalised values (Uninit, Moved, or Dropped) // - [ValState] Temporaries are write-once. // - Requires maintaining state information for all variables/temporaries with support for loops + // - + + // [Flat] = Basic checks (just iterates BBs) // - [Flat] Types must be valid (correct type for slot etc.) // - Simple check of all assignments/calls/... + { + for(unsigned int bb_idx = 0; bb_idx < fcn.blocks.size(); bb_idx ++) + { + const auto& bb = fcn.blocks[bb_idx]; + for(unsigned int stmt_idx = 0; stmt_idx < bb.statements.size(); stmt_idx ++) + { + const auto& stmt = bb.statements[stmt_idx]; + state.set_cur_stmt(bb_idx, stmt_idx); + + switch( stmt.tag() ) + { + case ::MIR::Statement::TAGDEAD: + throw ""; + case ::MIR::Statement::TAG_Assign: { + const auto& a = stmt.as_Assign(); + + auto check_type = [&](const auto& src_ty) { + ::HIR::TypeRef tmp; + const auto& dst_ty = state.get_lvalue_type(tmp, a.dst); + if( src_ty == ::HIR::TypeRef::new_diverge() ) { + } + else if( src_ty == dst_ty ) { + } + else { + MIR_BUG(state, "Type mismatch, destination is " << dst_ty << ", source is " << src_ty); + } + }; + TU_MATCH(::MIR::RValue, (a.src), (e), + (Use, + ::HIR::TypeRef tmp; + check_type( state.get_lvalue_type(tmp, e) ); + ), + (Constant, + // TODO: Check constant types. + ), + (SizedArray, + // TODO: Check that return type is an array + // TODO: Check that the input type is Copy + ), + (Borrow, + // TODO: Check return type + ), + (Cast, + // TODO: Check return type + // TODO: Check suitability of source type (COMPLEX) + ), + (BinOp, + ::HIR::TypeRef tmp_l, tmp_r; + const auto& ty_l = state.get_lvalue_type(tmp_l, e.val_l); + const auto& ty_r = state.get_lvalue_type(tmp_r, e.val_r); + // TODO: Check that operation is valid on these types + switch( e.op ) + { + case ::MIR::eBinOp::BIT_SHR: + case ::MIR::eBinOp::BIT_SHL: + break; + default: + // Check argument types are equal + if( ty_l != ty_r ) + MIR_BUG(state, "Type mismatch in binop, " << ty_l << " != " << ty_r); + } + // TODO: Check return type + ), + (UniOp, + // TODO: Check that operation is valid on this type + // TODO: Check return type + ), + (DstMeta, + // TODO: Ensure that the input type is a: Generic, Array, or DST + // TODO: Check return type + ), + (MakeDst, + ), + (Tuple, + // TODO: Check return type + ), + (Array, + // TODO: Check return type + ), + (Struct, + // TODO: Check return type + ) + ) + } break; + case ::MIR::Statement::TAG_Drop: + // TODO: Anything need checking here? + break; + } + } + + state.set_cur_stmt_term(bb_idx); + TU_MATCH(::MIR::Terminator, (bb.terminator), (e), + (Incomplete, + ), + (Return, + // TODO: Check if the function can return (i.e. if its return type isn't an empty type) + ), + (Diverge, + ), + (Goto, + ), + (Panic, + ), + (If, + // Check that condition lvalue is a bool + ::HIR::TypeRef tmp; + const auto& ty = state.get_lvalue_type(tmp, e.cond); + if( ty != ::HIR::CoreType::Bool ) { + MIR_BUG(state, "Type mismatch in `If` - expected bool, got " << ty); + } + ), + (Switch, + // Check that the condition is an enum + ), + (Call, + // Typecheck arguments and return value + ) + ) + } + } } namespace { @@ -103,7 +494,7 @@ namespace { this->visit_type( *e.inner ); DEBUG("Array size " << ty); if( e.size ) { - MIR_Validate(m_resolve, *e.size.m_mir, {}); + MIR_Validate(m_resolve, ::HIR::ItemPath(), *e.size.m_mir, {}, ::HIR::TypeRef(::HIR::CoreType::Usize)); } ) else { @@ -118,19 +509,19 @@ namespace { auto _ = this->m_resolve.set_item_generics(item.m_params); if( item.m_code ) { DEBUG("Function code " << p); - MIR_Validate(m_resolve, *item.m_code.m_mir, item.m_args); + MIR_Validate(m_resolve, p, *item.m_code.m_mir, item.m_args, item.m_return); } } void visit_static(::HIR::ItemPath p, ::HIR::Static& item) override { if( item.m_value ) { DEBUG("`static` value " << p); - MIR_Validate(m_resolve, *item.m_value.m_mir, {}); + MIR_Validate(m_resolve, p, *item.m_value.m_mir, {}, item.m_type); } } void visit_constant(::HIR::ItemPath p, ::HIR::Constant& item) override { if( item.m_value ) { DEBUG("`const` value " << p); - MIR_Validate(m_resolve, *item.m_value.m_mir, {}); + MIR_Validate(m_resolve, p, *item.m_value.m_mir, {}, item.m_type); } } void visit_enum(::HIR::ItemPath p, ::HIR::Enum& item) override { @@ -138,7 +529,8 @@ namespace { for(auto& var : item.m_variants) { TU_IFLET(::HIR::Enum::Variant, var.second, Value, e, - MIR_Validate(m_resolve, *e.expr.m_mir, {}); + // TODO: Get the repr type + MIR_Validate(m_resolve, p + var.first, *e.expr.m_mir, {}, ::HIR::TypeRef(::HIR::CoreType::Usize)); ) } } |