Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/mir/lower.rs')
| -rw-r--r-- | crates/hir-ty/src/mir/lower.rs | 1209 |
1 files changed, 1209 insertions, 0 deletions
diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs new file mode 100644 index 0000000000..1fa21e230c --- /dev/null +++ b/crates/hir-ty/src/mir/lower.rs @@ -0,0 +1,1209 @@ +//! This module generates a polymorphic MIR from a hir body + +use std::{iter, mem, sync::Arc}; + +use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind}; +use hir_def::{ + body::Body, + expr::{ + Array, BindingAnnotation, ExprId, LabelId, Literal, MatchArm, Pat, PatId, RecordLitField, + }, + layout::LayoutError, + resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, + DefWithBodyId, EnumVariantId, HasModule, +}; +use la_arena::ArenaMap; + +use crate::{ + consteval::ConstEvalError, db::HirDatabase, layout::layout_of_ty, mapping::ToChalk, + utils::generics, Adjust, AutoBorrow, CallableDefId, TyBuilder, TyExt, +}; + +use super::*; + +#[derive(Debug, Clone, Copy)] +struct LoopBlocks { + begin: BasicBlockId, + end: BasicBlockId, +} + +struct MirLowerCtx<'a> { + result: MirBody, + owner: DefWithBodyId, + binding_locals: ArenaMap<PatId, LocalId>, + current_loop_blocks: Option<LoopBlocks>, + discr_temp: Option<Place>, + db: &'a dyn HirDatabase, + body: &'a Body, + infer: &'a InferenceResult, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MirLowerError { + ConstEvalError(Box<ConstEvalError>), + LayoutError(LayoutError), + IncompleteExpr, + UnresolvedName, + MissingFunctionDefinition, + TypeError(&'static str), + NotSupported(String), + ContinueWithoutLoop, + BreakWithoutLoop, + Loop, +} + +macro_rules! not_supported { + ($x: expr) => { + return Err(MirLowerError::NotSupported(format!($x))) + }; +} + +impl From<ConstEvalError> for MirLowerError { + fn from(value: ConstEvalError) -> Self { + match value { + ConstEvalError::MirLowerError(e) => e, + _ => MirLowerError::ConstEvalError(Box::new(value)), + } + } +} + +impl From<LayoutError> for MirLowerError { + fn from(value: LayoutError) -> Self { + MirLowerError::LayoutError(value) + } +} + +type Result<T> = std::result::Result<T, MirLowerError>; + +impl MirLowerCtx<'_> { + fn temp(&mut self, ty: Ty) -> Result<LocalId> { + if matches!(ty.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) { + not_supported!("unsized temporaries"); + } + Ok(self.result.locals.alloc(Local { mutability: Mutability::Not, ty })) + } + + fn lower_expr_as_place(&self, expr_id: ExprId) -> Option<Place> { + let adjustments = self.infer.expr_adjustments.get(&expr_id); + let mut r = self.lower_expr_as_place_without_adjust(expr_id)?; + for adjustment in adjustments.iter().flat_map(|x| x.iter()) { + match adjustment.kind { + Adjust::NeverToAny => return Some(r), + Adjust::Deref(None) => { + r.projection.push(ProjectionElem::Deref); + } + Adjust::Deref(Some(_)) => return None, + Adjust::Borrow(_) => return None, + Adjust::Pointer(_) => return None, + } + } + Some(r) + } + + fn lower_expr_as_place_without_adjust(&self, expr_id: ExprId) -> Option<Place> { + match &self.body.exprs[expr_id] { + Expr::Path(p) => { + let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); + let pr = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path())?; + let pr = match pr { + ResolveValueResult::ValueNs(v) => v, + ResolveValueResult::Partial(..) => return None, + }; + match pr { + ValueNs::LocalBinding(pat_id) => Some(self.binding_locals[pat_id].into()), + _ => None, + } + } + Expr::UnaryOp { expr, op } => match op { + hir_def::expr::UnaryOp::Deref => { + let mut r = self.lower_expr_as_place(*expr)?; + r.projection.push(ProjectionElem::Deref); + Some(r) + } + _ => None, + }, + _ => None, + } + } + + fn lower_expr_to_some_operand( + &mut self, + expr_id: ExprId, + current: BasicBlockId, + ) -> Result<(Operand, BasicBlockId)> { + if !self.has_adjustments(expr_id) { + match &self.body.exprs[expr_id] { + Expr::Literal(l) => { + let ty = self.expr_ty(expr_id); + return Ok((self.lower_literal_to_operand(ty, l)?, current)); + } + _ => (), + } + } + let (p, current) = self.lower_expr_to_some_place(expr_id, current)?; + Ok((Operand::Copy(p), current)) + } + + fn lower_expr_to_some_place( + &mut self, + expr_id: ExprId, + prev_block: BasicBlockId, + ) -> Result<(Place, BasicBlockId)> { + if let Some(p) = self.lower_expr_as_place(expr_id) { + return Ok((p, prev_block)); + } + let mut ty = self.expr_ty(expr_id); + if let Some(x) = self.infer.expr_adjustments.get(&expr_id) { + if let Some(x) = x.last() { + ty = x.target.clone(); + } + } + let place = self.temp(ty)?; + Ok((place.into(), self.lower_expr_to_place(expr_id, place.into(), prev_block)?)) + } + + fn lower_expr_to_place( + &mut self, + expr_id: ExprId, + place: Place, + prev_block: BasicBlockId, + ) -> Result<BasicBlockId> { + if let Some(x) = self.infer.expr_adjustments.get(&expr_id) { + if x.len() > 0 { + let tmp = self.temp(self.expr_ty(expr_id))?; + let current = + self.lower_expr_to_place_without_adjust(expr_id, tmp.into(), prev_block)?; + let mut r = Place::from(tmp); + for adjustment in x { + match &adjustment.kind { + Adjust::NeverToAny => (), + Adjust::Deref(None) => { + r.projection.push(ProjectionElem::Deref); + } + Adjust::Deref(Some(_)) => not_supported!("overloaded dereference"), + Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => { + let tmp = self.temp(adjustment.target.clone())?; + self.push_assignment( + current, + tmp.into(), + Rvalue::Ref(BorrowKind::from_chalk(*m), r), + ); + r = tmp.into(); + } + Adjust::Pointer(cast) => { + let target = &adjustment.target; + let tmp = self.temp(target.clone())?; + self.push_assignment( + current, + tmp.into(), + Rvalue::Cast( + CastKind::Pointer(cast.clone()), + Operand::Copy(r).into(), + target.clone(), + ), + ); + r = tmp.into(); + } + } + } + self.push_assignment(current, place, Operand::Copy(r).into()); + return Ok(current); + } + } + self.lower_expr_to_place_without_adjust(expr_id, place, prev_block) + } + + fn lower_expr_to_place_without_adjust( + &mut self, + expr_id: ExprId, + place: Place, + mut current: BasicBlockId, + ) -> Result<BasicBlockId> { + match &self.body.exprs[expr_id] { + Expr::Missing => Err(MirLowerError::IncompleteExpr), + Expr::Path(p) => { + let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); + let pr = resolver + .resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) + .ok_or(MirLowerError::UnresolvedName)?; + let pr = match pr { + ResolveValueResult::ValueNs(v) => v, + ResolveValueResult::Partial(..) => { + return match self + .infer + .assoc_resolutions_for_expr(expr_id) + .ok_or(MirLowerError::UnresolvedName)? + .0 + //.ok_or(ConstEvalError::SemanticError("unresolved assoc item"))? + { + hir_def::AssocItemId::ConstId(c) => self.lower_const(c, current, place), + _ => return Err(MirLowerError::UnresolvedName), + }; + } + }; + match pr { + ValueNs::LocalBinding(pat_id) => { + self.push_assignment( + current, + place, + Operand::Copy(self.binding_locals[pat_id].into()).into(), + ); + Ok(current) + } + ValueNs::ConstId(const_id) => self.lower_const(const_id, current, place), + ValueNs::EnumVariantId(variant_id) => { + let ty = self.infer.type_of_expr[expr_id].clone(); + self.lower_enum_variant(variant_id, current, place, ty, vec![]) + } + ValueNs::GenericParam(p) => { + let Some(def) = self.owner.as_generic_def_id() else { + not_supported!("owner without generic def id"); + }; + let gen = generics(self.db.upcast(), def); + let ty = self.expr_ty(expr_id); + self.push_assignment( + current, + place, + Operand::Constant( + ConstData { + ty, + value: chalk_ir::ConstValue::BoundVar(BoundVar::new( + DebruijnIndex::INNERMOST, + gen.param_idx(p.into()).ok_or(MirLowerError::TypeError( + "fail to lower const generic param", + ))?, + )), + } + .intern(Interner), + ) + .into(), + ); + Ok(current) + } + ValueNs::StructId(_) => { + // It's probably a unit struct or a zero sized function, so no action is needed. + Ok(current) + } + x => { + not_supported!("unknown name {x:?} in value name space"); + } + } + } + Expr::If { condition, then_branch, else_branch } => { + let (discr, current) = self.lower_expr_to_some_operand(*condition, current)?; + let start_of_then = self.new_basic_block(); + let end = self.new_basic_block(); + let end_of_then = + self.lower_expr_to_place(*then_branch, place.clone(), start_of_then)?; + self.set_goto(end_of_then, end); + let mut start_of_else = end; + if let Some(else_branch) = else_branch { + start_of_else = self.new_basic_block(); + let end_of_else = + self.lower_expr_to_place(*else_branch, place, start_of_else)?; + self.set_goto(end_of_else, end); + } + self.set_terminator( + current, + Terminator::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, start_of_then, start_of_else), + }, + ); + Ok(end) + } + Expr::Let { pat, expr } => { + let (cond_place, current) = self.lower_expr_to_some_place(*expr, current)?; + let result = self.new_basic_block(); + let (then_target, else_target) = self.pattern_match( + current, + None, + cond_place, + self.expr_ty(*expr), + *pat, + BindingAnnotation::Unannotated, + )?; + self.write_bytes_to_place(then_target, place.clone(), vec![1], TyBuilder::bool())?; + self.set_goto(then_target, result); + if let Some(else_target) = else_target { + self.write_bytes_to_place(else_target, place, vec![0], TyBuilder::bool())?; + self.set_goto(else_target, result); + } + Ok(result) + } + Expr::Block { id: _, statements, tail, label } => { + if label.is_some() { + not_supported!("block with label"); + } + for statement in statements.iter() { + match statement { + hir_def::expr::Statement::Let { + pat, + initializer, + else_branch, + type_ref: _, + } => match initializer { + Some(expr_id) => { + let else_block; + let init_place; + (init_place, current) = + self.lower_expr_to_some_place(*expr_id, current)?; + (current, else_block) = self.pattern_match( + current, + None, + init_place, + self.expr_ty(*expr_id), + *pat, + BindingAnnotation::Unannotated, + )?; + match (else_block, else_branch) { + (None, _) => (), + (Some(else_block), None) => { + self.set_terminator(else_block, Terminator::Unreachable); + } + (Some(else_block), Some(else_branch)) => { + let (_, b) = self + .lower_expr_to_some_place(*else_branch, else_block)?; + self.set_terminator(b, Terminator::Unreachable); + } + } + } + None => continue, + }, + hir_def::expr::Statement::Expr { expr, has_semi: _ } => { + let ty = self.expr_ty(*expr); + let temp = self.temp(ty)?; + current = self.lower_expr_to_place(*expr, temp.into(), current)?; + } + } + } + match tail { + Some(tail) => self.lower_expr_to_place(*tail, place, current), + None => Ok(current), + } + } + Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin, _| { + let (_, block) = this.lower_expr_to_some_place(*body, begin)?; + this.set_goto(block, begin); + Ok(()) + }), + Expr::While { condition, body, label } => { + self.lower_loop(current, *label, |this, begin, end| { + let (discr, to_switch) = this.lower_expr_to_some_operand(*condition, begin)?; + let after_cond = this.new_basic_block(); + this.set_terminator( + to_switch, + Terminator::SwitchInt { + discr, + targets: SwitchTargets::static_if(1, after_cond, end), + }, + ); + let (_, block) = this.lower_expr_to_some_place(*body, after_cond)?; + this.set_goto(block, begin); + Ok(()) + }) + } + Expr::For { .. } => not_supported!("for loop"), + Expr::Call { callee, args, .. } => { + let callee_ty = self.expr_ty(*callee); + match &callee_ty.data(Interner).kind { + chalk_ir::TyKind::FnDef(..) => { + let func = Operand::from_bytes(vec![], callee_ty.clone()); + self.lower_call(func, args.iter().copied(), place, current) + } + TyKind::Scalar(_) + | TyKind::Tuple(_, _) + | TyKind::Array(_, _) + | TyKind::Adt(_, _) + | TyKind::Str + | TyKind::Foreign(_) + | TyKind::Slice(_) => { + return Err(MirLowerError::TypeError("function call on data type")) + } + TyKind::Error => return Err(MirLowerError::MissingFunctionDefinition), + TyKind::AssociatedType(_, _) + | TyKind::Raw(_, _) + | TyKind::Ref(_, _, _) + | TyKind::OpaqueType(_, _) + | TyKind::Never + | TyKind::Closure(_, _) + | TyKind::Generator(_, _) + | TyKind::GeneratorWitness(_, _) + | TyKind::Placeholder(_) + | TyKind::Dyn(_) + | TyKind::Alias(_) + | TyKind::Function(_) + | TyKind::BoundVar(_) + | TyKind::InferenceVar(_, _) => not_supported!("dynamic function call"), + } + } + Expr::MethodCall { receiver, args, .. } => { + let (func_id, generic_args) = + self.infer.method_resolution(expr_id).ok_or(MirLowerError::UnresolvedName)?; + let ty = chalk_ir::TyKind::FnDef( + CallableDefId::FunctionId(func_id).to_chalk(self.db), + generic_args, + ) + .intern(Interner); + let func = Operand::from_bytes(vec![], ty); + self.lower_call( + func, + iter::once(*receiver).chain(args.iter().copied()), + place, + current, + ) + } + Expr::Match { expr, arms } => { + let (cond_place, mut current) = self.lower_expr_to_some_place(*expr, current)?; + let cond_ty = self.expr_ty(*expr); + let end = self.new_basic_block(); + for MatchArm { pat, guard, expr } in arms.iter() { + if guard.is_some() { + not_supported!("pattern matching with guard"); + } + let (then, otherwise) = self.pattern_match( + current, + None, + cond_place.clone(), + cond_ty.clone(), + *pat, + BindingAnnotation::Unannotated, + )?; + let block = self.lower_expr_to_place(*expr, place.clone(), then)?; + self.set_goto(block, end); + match otherwise { + Some(o) => current = o, + None => { + // The current pattern was irrefutable, so there is no need to generate code + // for the rest of patterns + break; + } + } + } + if self.is_unterminated(current) { + self.set_terminator(current, Terminator::Unreachable); + } + Ok(end) + } + Expr::Continue { label } => match label { + Some(_) => not_supported!("continue with label"), + None => { + let loop_data = + self.current_loop_blocks.ok_or(MirLowerError::ContinueWithoutLoop)?; + self.set_goto(current, loop_data.begin); + let otherwise = self.new_basic_block(); + Ok(otherwise) + } + }, + Expr::Break { expr, label } => { + if expr.is_some() { + not_supported!("break with value"); + } + match label { + Some(_) => not_supported!("break with label"), + None => { + let loop_data = + self.current_loop_blocks.ok_or(MirLowerError::BreakWithoutLoop)?; + self.set_goto(current, loop_data.end); + Ok(self.new_basic_block()) + } + } + } + Expr::Return { expr } => { + if let Some(expr) = expr { + current = self.lower_expr_to_place(*expr, return_slot().into(), current)?; + } + self.set_terminator(current, Terminator::Return); + Ok(self.new_basic_block()) + } + Expr::Yield { .. } => not_supported!("yield"), + Expr::RecordLit { fields, .. } => { + let variant_id = self + .infer + .variant_resolution_for_expr(expr_id) + .ok_or(MirLowerError::UnresolvedName)?; + let subst = match self.expr_ty(expr_id).kind(Interner) { + TyKind::Adt(_, s) => s.clone(), + _ => not_supported!("Non ADT record literal"), + }; + let variant_data = variant_id.variant_data(self.db.upcast()); + match variant_id { + VariantId::EnumVariantId(_) | VariantId::StructId(_) => { + let mut operands = vec![None; variant_data.fields().len()]; + for RecordLitField { name, expr } in fields.iter() { + let field_id = + variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?; + let op; + (op, current) = self.lower_expr_to_some_operand(*expr, current)?; + operands[u32::from(field_id.into_raw()) as usize] = Some(op); + } + self.push_assignment( + current, + place, + Rvalue::Aggregate( + AggregateKind::Adt(variant_id, subst), + operands.into_iter().map(|x| x).collect::<Option<_>>().ok_or( + MirLowerError::TypeError("missing field in record literal"), + )?, + ), + ); + Ok(current) + } + VariantId::UnionId(union_id) => { + let [RecordLitField { name, expr }] = fields.as_ref() else { + not_supported!("Union record literal with more than one field"); + }; + let local_id = + variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?; + let mut place = place; + place + .projection + .push(PlaceElem::Field(FieldId { parent: union_id.into(), local_id })); + self.lower_expr_to_place(*expr, place, current) + } + } + } + Expr::Field { expr, name } => { + let (mut current_place, current) = self.lower_expr_to_some_place(*expr, current)?; + if let TyKind::Tuple(..) = self.expr_ty(*expr).kind(Interner) { + let index = name + .as_tuple_index() + .ok_or(MirLowerError::TypeError("named field on tuple"))?; + current_place.projection.push(ProjectionElem::TupleField(index)) + } else { + let field = self + .infer + .field_resolution(expr_id) + .ok_or(MirLowerError::UnresolvedName)?; + current_place.projection.push(ProjectionElem::Field(field)); + } + self.push_assignment(current, place, Operand::Copy(current_place).into()); + Ok(current) + } + Expr::Await { .. } => not_supported!("await"), + Expr::Try { .. } => not_supported!("? operator"), + Expr::Yeet { .. } => not_supported!("yeet"), + Expr::TryBlock { .. } => not_supported!("try block"), + Expr::Async { .. } => not_supported!("async block"), + Expr::Const { .. } => not_supported!("anonymous const block"), + Expr::Cast { expr, type_ref: _ } => { + let (x, current) = self.lower_expr_to_some_operand(*expr, current)?; + let source_ty = self.infer[*expr].clone(); + let target_ty = self.infer[expr_id].clone(); + self.push_assignment( + current, + place, + Rvalue::Cast(cast_kind(&source_ty, &target_ty)?, x, target_ty), + ); + Ok(current) + } + Expr::Ref { expr, rawness: _, mutability } => { + let p; + (p, current) = self.lower_expr_to_some_place(*expr, current)?; + let bk = BorrowKind::from_hir(*mutability); + self.push_assignment(current, place, Rvalue::Ref(bk, p)); + Ok(current) + } + Expr::Box { .. } => not_supported!("box expression"), + Expr::UnaryOp { expr, op } => match op { + hir_def::expr::UnaryOp::Deref => { + let (mut tmp, current) = self.lower_expr_to_some_place(*expr, current)?; + tmp.projection.push(ProjectionElem::Deref); + self.push_assignment(current, place, Operand::Copy(tmp).into()); + Ok(current) + } + hir_def::expr::UnaryOp::Not => { + let (op, current) = self.lower_expr_to_some_operand(*expr, current)?; + self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Not, op)); + Ok(current) + } + hir_def::expr::UnaryOp::Neg => { + let (op, current) = self.lower_expr_to_some_operand(*expr, current)?; + self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Neg, op)); + Ok(current) + } + }, + Expr::BinaryOp { lhs, rhs, op } => { + let op = op.ok_or(MirLowerError::IncompleteExpr)?; + if let hir_def::expr::BinaryOp::Assignment { op } = op { + if op.is_some() { + not_supported!("assignment with arith op (like +=)"); + } + let Some(lhs_place) = self.lower_expr_as_place(*lhs) else { + not_supported!("assignment to complex place"); + }; + let rhs_op; + (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?; + self.push_assignment(current, lhs_place, rhs_op.into()); + return Ok(current); + } + let lhs_op; + (lhs_op, current) = self.lower_expr_to_some_operand(*lhs, current)?; + let rhs_op; + (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?; + self.push_assignment( + current, + place, + Rvalue::CheckedBinaryOp( + match op { + hir_def::expr::BinaryOp::LogicOp(op) => match op { + hir_def::expr::LogicOp::And => BinOp::BitAnd, // FIXME: make these short circuit + hir_def::expr::LogicOp::Or => BinOp::BitOr, + }, + hir_def::expr::BinaryOp::ArithOp(op) => BinOp::from(op), + hir_def::expr::BinaryOp::CmpOp(op) => BinOp::from(op), + hir_def::expr::BinaryOp::Assignment { .. } => unreachable!(), // handled above + }, + lhs_op, + rhs_op, + ), + ); + Ok(current) + } + Expr::Range { .. } => not_supported!("range"), + Expr::Index { base, index } => { + let mut p_base; + (p_base, current) = self.lower_expr_to_some_place(*base, current)?; + let l_index = self.temp(self.expr_ty(*index))?; + current = self.lower_expr_to_place(*index, l_index.into(), current)?; + p_base.projection.push(ProjectionElem::Index(l_index)); + self.push_assignment(current, place, Operand::Copy(p_base).into()); + Ok(current) + } + Expr::Closure { .. } => not_supported!("closure"), + Expr::Tuple { exprs, is_assignee_expr: _ } => { + let r = Rvalue::Aggregate( + AggregateKind::Tuple(self.expr_ty(expr_id)), + exprs + .iter() + .map(|x| { + let o; + (o, current) = self.lower_expr_to_some_operand(*x, current)?; + Ok(o) + }) + .collect::<Result<_>>()?, + ); + self.push_assignment(current, place, r); + Ok(current) + } + Expr::Unsafe { body } => self.lower_expr_to_place(*body, place, current), + Expr::Array(l) => match l { + Array::ElementList { elements, .. } => { + let elem_ty = match &self.expr_ty(expr_id).data(Interner).kind { + TyKind::Array(ty, _) => ty.clone(), + _ => { + return Err(MirLowerError::TypeError( + "Array expression with non array type", + )) + } + }; + let r = Rvalue::Aggregate( + AggregateKind::Array(elem_ty), + elements + .iter() + .map(|x| { + let o; + (o, current) = self.lower_expr_to_some_operand(*x, current)?; + Ok(o) + }) + .collect::<Result<_>>()?, + ); + self.push_assignment(current, place, r); + Ok(current) + } + Array::Repeat { .. } => not_supported!("array repeat"), + }, + Expr::Literal(l) => { + let ty = self.expr_ty(expr_id); + let op = self.lower_literal_to_operand(ty, l)?; + self.push_assignment(current, place, op.into()); + Ok(current) + } + Expr::Underscore => not_supported!("underscore"), + } + } + + fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result<Operand> { + let size = layout_of_ty(self.db, &ty, self.owner.module(self.db.upcast()).krate())? + .size + .bytes_usize(); + let bytes = match l { + hir_def::expr::Literal::String(b) => { + let b = b.as_bytes(); + let mut data = vec![]; + data.extend(0usize.to_le_bytes()); + data.extend(b.len().to_le_bytes()); + let mut mm = MemoryMap::default(); + mm.insert(0, b.to_vec()); + return Ok(Operand::from_concrete_const(data, mm, ty)); + } + hir_def::expr::Literal::ByteString(b) => { + let mut data = vec![]; + data.extend(0usize.to_le_bytes()); + data.extend(b.len().to_le_bytes()); + let mut mm = MemoryMap::default(); + mm.insert(0, b.to_vec()); + return Ok(Operand::from_concrete_const(data, mm, ty)); + } + hir_def::expr::Literal::Char(c) => u32::from(*c).to_le_bytes().into(), + hir_def::expr::Literal::Bool(b) => vec![*b as u8], + hir_def::expr::Literal::Int(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::expr::Literal::Uint(x, _) => x.to_le_bytes()[0..size].into(), + hir_def::expr::Literal::Float(f, _) => match size { + 8 => f.into_f64().to_le_bytes().into(), + 4 => f.into_f32().to_le_bytes().into(), + _ => { + return Err(MirLowerError::TypeError("float with size other than 4 or 8 bytes")) + } + }, + }; + Ok(Operand::from_concrete_const(bytes, MemoryMap::default(), ty)) + } + + fn new_basic_block(&mut self) -> BasicBlockId { + self.result.basic_blocks.alloc(BasicBlock::default()) + } + + fn lower_const( + &mut self, + const_id: hir_def::ConstId, + prev_block: BasicBlockId, + place: Place, + ) -> Result<BasicBlockId> { + let c = self.db.const_eval(const_id)?; + self.write_const_to_place(c, prev_block, place) + } + + fn write_const_to_place( + &mut self, + c: Const, + prev_block: BasicBlockId, + place: Place, + ) -> Result<BasicBlockId> { + self.push_assignment(prev_block, place, Operand::Constant(c).into()); + Ok(prev_block) + } + + fn write_bytes_to_place( + &mut self, + prev_block: BasicBlockId, + place: Place, + cv: Vec<u8>, + ty: Ty, + ) -> Result<BasicBlockId> { + self.push_assignment(prev_block, place, Operand::from_bytes(cv, ty).into()); + Ok(prev_block) + } + + fn lower_enum_variant( + &mut self, + variant_id: EnumVariantId, + prev_block: BasicBlockId, + place: Place, + ty: Ty, + fields: Vec<Operand>, + ) -> Result<BasicBlockId> { + let subst = match ty.kind(Interner) { + TyKind::Adt(_, subst) => subst.clone(), + _ => not_supported!("Non ADT enum"), + }; + self.push_assignment( + prev_block, + place, + Rvalue::Aggregate(AggregateKind::Adt(variant_id.into(), subst), fields), + ); + Ok(prev_block) + } + + fn lower_call( + &mut self, + func: Operand, + args: impl Iterator<Item = ExprId>, + place: Place, + mut current: BasicBlockId, + ) -> Result<BasicBlockId> { + let args = args + .map(|arg| { + let temp; + (temp, current) = self.lower_expr_to_some_operand(arg, current)?; + Ok(temp) + }) + .collect::<Result<Vec<_>>>()?; + let b = self.result.basic_blocks.alloc(BasicBlock { + statements: vec![], + terminator: None, + is_cleanup: false, + }); + self.set_terminator( + current, + Terminator::Call { + func, + args, + destination: place, + target: Some(b), + cleanup: None, + from_hir_call: true, + }, + ); + Ok(b) + } + + fn is_unterminated(&mut self, source: BasicBlockId) -> bool { + self.result.basic_blocks[source].terminator.is_none() + } + + fn set_terminator(&mut self, source: BasicBlockId, terminator: Terminator) { + self.result.basic_blocks[source].terminator = Some(terminator); + } + + fn set_goto(&mut self, source: BasicBlockId, target: BasicBlockId) { + self.set_terminator(source, Terminator::Goto { target }); + } + + fn expr_ty(&self, e: ExprId) -> Ty { + self.infer[e].clone() + } + + fn push_assignment(&mut self, block: BasicBlockId, place: Place, rvalue: Rvalue) { + self.result.basic_blocks[block].statements.push(Statement::Assign(place, rvalue)); + } + + /// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if + /// the pattern matches and write bindings, and returns two unterminated blocks, one for the matched path (which + /// can be the `current` block) and one for the mismatched path. If the input pattern is irrefutable, the + /// mismatched path block is `None`. + /// + /// By default, it will create a new block for mismatched path. If you already have one, you can provide it with + /// `current_else` argument to save an unneccessary jump. If `current_else` isn't `None`, the result mismatched path + /// wouldn't be `None` as well. Note that this function will add jumps to the beginning of the `current_else` block, + /// so it should be an empty block. + fn pattern_match( + &mut self, + mut current: BasicBlockId, + mut current_else: Option<BasicBlockId>, + mut cond_place: Place, + mut cond_ty: Ty, + pattern: PatId, + mut binding_mode: BindingAnnotation, + ) -> Result<(BasicBlockId, Option<BasicBlockId>)> { + Ok(match &self.body.pats[pattern] { + Pat::Missing => return Err(MirLowerError::IncompleteExpr), + Pat::Wild => (current, current_else), + Pat::Tuple { args, ellipsis } => { + pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); + let subst = match cond_ty.kind(Interner) { + TyKind::Tuple(_, s) => s, + _ => { + return Err(MirLowerError::TypeError( + "non tuple type matched with tuple pattern", + )) + } + }; + self.pattern_match_tuple_like( + current, + current_else, + args.iter().enumerate().map(|(i, x)| { + ( + PlaceElem::TupleField(i), + *x, + subst.at(Interner, i).assert_ty_ref(Interner).clone(), + ) + }), + *ellipsis, + &cond_place, + binding_mode, + )? + } + Pat::Or(_) => not_supported!("or pattern"), + Pat::Record { .. } => not_supported!("record pattern"), + Pat::Range { .. } => not_supported!("range pattern"), + Pat::Slice { .. } => not_supported!("slice pattern"), + Pat::Path(_) => not_supported!("path pattern"), + Pat::Lit(l) => { + let then_target = self.new_basic_block(); + let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); + match &self.body.exprs[*l] { + Expr::Literal(l) => match l { + hir_def::expr::Literal::Int(x, _) => { + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(cond_place), + targets: SwitchTargets::static_if( + *x as u128, + then_target, + else_target, + ), + }, + ); + } + hir_def::expr::Literal::Uint(x, _) => { + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(cond_place), + targets: SwitchTargets::static_if(*x, then_target, else_target), + }, + ); + } + _ => not_supported!("non int path literal"), + }, + _ => not_supported!("expression path literal"), + } + (then_target, Some(else_target)) + } + Pat::Bind { mode, name: _, subpat } => { + let target_place = self.binding_locals[pattern]; + if let Some(subpat) = subpat { + (current, current_else) = self.pattern_match( + current, + current_else, + cond_place.clone(), + cond_ty, + *subpat, + binding_mode, + )? + } + if matches!(mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) { + binding_mode = *mode; + } + self.push_assignment( + current, + target_place.into(), + match binding_mode { + BindingAnnotation::Unannotated | BindingAnnotation::Mutable => { + Operand::Copy(cond_place).into() + } + BindingAnnotation::Ref => Rvalue::Ref(BorrowKind::Shared, cond_place), + BindingAnnotation::RefMut => Rvalue::Ref( + BorrowKind::Mut { allow_two_phase_borrow: false }, + cond_place, + ), + }, + ); + (current, current_else) + } + Pat::TupleStruct { path: _, args, ellipsis } => { + let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { + not_supported!("unresolved variant"); + }; + pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); + let subst = match cond_ty.kind(Interner) { + TyKind::Adt(_, s) => s, + _ => { + return Err(MirLowerError::TypeError( + "non adt type matched with tuple struct", + )) + } + }; + let fields_type = self.db.field_types(variant); + match variant { + VariantId::EnumVariantId(v) => { + let e = self.db.const_eval_discriminant(v)? as u128; + let next = self.new_basic_block(); + let tmp = self.discr_temp_place(); + self.push_assignment( + current, + tmp.clone(), + Rvalue::Discriminant(cond_place.clone()), + ); + let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(tmp), + targets: SwitchTargets::static_if(e, next, else_target), + }, + ); + let enum_data = self.db.enum_data(v.parent); + let fields = + enum_data.variants[v.local_id].variant_data.fields().iter().map( + |(x, _)| { + ( + PlaceElem::Field(FieldId { parent: v.into(), local_id: x }), + fields_type[x].clone().substitute(Interner, subst), + ) + }, + ); + self.pattern_match_tuple_like( + next, + Some(else_target), + args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), + *ellipsis, + &cond_place, + binding_mode, + )? + } + VariantId::StructId(s) => { + let struct_data = self.db.struct_data(s); + let fields = struct_data.variant_data.fields().iter().map(|(x, _)| { + ( + PlaceElem::Field(FieldId { parent: s.into(), local_id: x }), + fields_type[x].clone().substitute(Interner, subst), + ) + }); + self.pattern_match_tuple_like( + current, + current_else, + args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), + *ellipsis, + &cond_place, + binding_mode, + )? + } + VariantId::UnionId(_) => { + return Err(MirLowerError::TypeError("pattern matching on union")) + } + } + } + Pat::Ref { .. } => not_supported!("& pattern"), + Pat::Box { .. } => not_supported!("box pattern"), + Pat::ConstBlock(_) => not_supported!("const block pattern"), + }) + } + + fn pattern_match_tuple_like( + &mut self, + mut current: BasicBlockId, + mut current_else: Option<BasicBlockId>, + args: impl Iterator<Item = (PlaceElem, PatId, Ty)>, + ellipsis: Option<usize>, + cond_place: &Place, + binding_mode: BindingAnnotation, + ) -> Result<(BasicBlockId, Option<BasicBlockId>)> { + if ellipsis.is_some() { + not_supported!("tuple like pattern with ellipsis"); + } + for (proj, arg, ty) in args { + let mut cond_place = cond_place.clone(); + cond_place.projection.push(proj); + (current, current_else) = + self.pattern_match(current, current_else, cond_place, ty, arg, binding_mode)?; + } + Ok((current, current_else)) + } + + fn discr_temp_place(&mut self) -> Place { + match &self.discr_temp { + Some(x) => x.clone(), + None => { + let tmp: Place = + self.temp(TyBuilder::discr_ty()).expect("discr_ty is never unsized").into(); + self.discr_temp = Some(tmp.clone()); + tmp + } + } + } + + fn lower_loop( + &mut self, + prev_block: BasicBlockId, + label: Option<LabelId>, + f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId, BasicBlockId) -> Result<()>, + ) -> Result<BasicBlockId> { + if label.is_some() { + not_supported!("loop with label"); + } + let begin = self.new_basic_block(); + let end = self.new_basic_block(); + let prev = mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end })); + self.set_goto(prev_block, begin); + f(self, begin, end)?; + self.current_loop_blocks = prev; + Ok(end) + } + + fn has_adjustments(&self, expr_id: ExprId) -> bool { + !self.infer.expr_adjustments.get(&expr_id).map(|x| x.is_empty()).unwrap_or(true) + } +} + +fn pattern_matching_dereference( + cond_ty: &mut Ty, + binding_mode: &mut BindingAnnotation, + cond_place: &mut Place, +) { + while let Some((ty, _, mu)) = cond_ty.as_reference() { + if mu == Mutability::Mut && *binding_mode != BindingAnnotation::Ref { + *binding_mode = BindingAnnotation::RefMut; + } else { + *binding_mode = BindingAnnotation::Ref; + } + *cond_ty = ty.clone(); + cond_place.projection.push(ProjectionElem::Deref); + } +} + +fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> { + Ok(match (source_ty.kind(Interner), target_ty.kind(Interner)) { + (TyKind::Scalar(s), TyKind::Scalar(t)) => match (s, t) { + (chalk_ir::Scalar::Float(_), chalk_ir::Scalar::Float(_)) => CastKind::FloatToFloat, + (chalk_ir::Scalar::Float(_), _) => CastKind::FloatToInt, + (_, chalk_ir::Scalar::Float(_)) => CastKind::IntToFloat, + (_, _) => CastKind::IntToInt, + }, + // Enum to int casts + (TyKind::Scalar(_), TyKind::Adt(..)) | (TyKind::Adt(..), TyKind::Scalar(_)) => { + CastKind::IntToInt + } + (a, b) => not_supported!("Unknown cast between {a:?} and {b:?}"), + }) +} + +pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result<Arc<MirBody>> { + let body = db.body(def); + let infer = db.infer(def); + Ok(Arc::new(lower_to_mir(db, def, &body, &infer, body.body_expr)?)) +} + +pub fn mir_body_recover( + _db: &dyn HirDatabase, + _cycle: &[String], + _def: &DefWithBodyId, +) -> Result<Arc<MirBody>> { + Err(MirLowerError::Loop) +} + +pub fn lower_to_mir( + db: &dyn HirDatabase, + owner: DefWithBodyId, + body: &Body, + infer: &InferenceResult, + // FIXME: root_expr should always be the body.body_expr, but since `X` in `[(); X]` doesn't have its own specific body yet, we + // need to take this input explicitly. + root_expr: ExprId, +) -> Result<MirBody> { + let mut basic_blocks = Arena::new(); + let start_block = + basic_blocks.alloc(BasicBlock { statements: vec![], terminator: None, is_cleanup: false }); + let mut locals = Arena::new(); + // 0 is return local + locals.alloc(Local { mutability: Mutability::Mut, ty: infer[root_expr].clone() }); + let mut create_local_of_path = |p: PatId| { + // FIXME: mutablity is broken + locals.alloc(Local { mutability: Mutability::Not, ty: infer[p].clone() }) + }; + // 1 to param_len is for params + let mut binding_locals: ArenaMap<PatId, LocalId> = + body.params.iter().map(|&x| (x, create_local_of_path(x))).collect(); + // and then rest of bindings + for (pat_id, _) in body.pats.iter() { + if !binding_locals.contains_idx(pat_id) { + binding_locals.insert(pat_id, create_local_of_path(pat_id)); + } + } + let mir = MirBody { basic_blocks, locals, start_block, owner, arg_count: body.params.len() }; + let mut ctx = MirLowerCtx { + result: mir, + db, + infer, + body, + binding_locals, + owner, + current_loop_blocks: None, + discr_temp: None, + }; + let b = ctx.lower_expr_to_place(root_expr, return_slot().into(), start_block)?; + ctx.result.basic_blocks[b].terminator = Some(Terminator::Return); + Ok(ctx.result) +} |