diff --git a/rr_frontend/.cargo/config.toml b/rr_frontend/.cargo/config.toml index eb40ad9d1483d47410153fee839dd6193952f539..916cbb3dbec008ee56b0fd4df62f70ac70e7a33c 100644 --- a/rr_frontend/.cargo/config.toml +++ b/rr_frontend/.cargo/config.toml @@ -31,6 +31,7 @@ rustflags = [ "-Aclippy::panic_in_result_fn", "-Aclippy::unwrap_in_result", "-Aclippy::separated_literal_suffix", + "-Aclippy::pub_use", # clippy::style "-Aclippy::new_without_default", diff --git a/rr_frontend/translation/src/attrs.rs b/rr_frontend/translation/src/attrs.rs new file mode 100644 index 0000000000000000000000000000000000000000..43040e5a1fbac70f1a01612f49015cf4b79f68c0 --- /dev/null +++ b/rr_frontend/translation/src/attrs.rs @@ -0,0 +1,82 @@ +// © 2023, The RefinedRust Develcpers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Utility functions for obtaining relevant attributes. + +use std::mem; + +use log::{info, trace}; +use rr_rustc_interface::ast::ast; +use rr_rustc_interface::data_structures::fx::FxHashSet; +use rr_rustc_interface::hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rr_rustc_interface::middle::mir; +use rr_rustc_interface::middle::ty::{self, TyCtxt}; +use rr_rustc_interface::{hir, middle, span}; +use serde::{Deserialize, Serialize}; + +use crate::spec_parsers::get_export_as_attr; +use crate::{force_matches, types, Environment}; + +/// Check if `<tool>::<name>` is among the attributes, where `tool` is determined by the +/// `spec_hotword` config. +/// Any arguments of the attribute are ignored. +pub fn has_tool_attr(attrs: &[ast::Attribute], name: &str) -> bool { + get_tool_attr(attrs, name).is_some() +} + +/// Get the arguments for a tool attribute, if it exists. +pub fn get_tool_attr<'a>(attrs: &'a [ast::Attribute], name: &str) -> Option<&'a ast::AttrArgs> { + attrs.iter().find_map(|attr| match &attr.kind { + ast::AttrKind::Normal(na) => { + let segments = &na.item.path.segments; + let args = &na.item.args; + (segments.len() == 2 + && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str() + && segments[1].ident.as_str() == name) + .then_some(args) + }, + _ => None, + }) +} + +/// Check if `<tool>::name` is among the filtered attributes. +pub fn has_tool_attr_filtered(attrs: &[&ast::AttrItem], name: &str) -> bool { + attrs.iter().any(|item| { + let segments = &item.path.segments; + segments.len() == 2 + && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str() + && segments[1].ident.as_str() == name + }) +} + +/// Check if any attribute starting with `<tool>` is among the attributes. +pub fn has_any_tool_attr(attrs: &[ast::Attribute]) -> bool { + attrs.iter().any(|attr| match &attr.kind { + ast::AttrKind::Normal(na) => { + let segments = &na.item.path.segments; + segments[0].ident.as_str() == rrconfig::spec_hotword().as_str() + }, + _ => false, + }) +} + +/// Get all tool attributes, i.e. attributes of the shape `<tool>::attr`, where `tool` is +/// determined by the `spec_hotword` config. +pub fn filter_for_tool(attrs: &[ast::Attribute]) -> Vec<&ast::AttrItem> { + attrs + .iter() + .filter_map(|attr| match &attr.kind { + ast::AttrKind::Normal(na) => { + let item = &na.item; + + let seg = item.path.segments.get(0)?; + + (seg.ident.name.as_str() == rrconfig::spec_hotword()).then_some(item) + }, + _ => None, + }) + .collect() +} diff --git a/rr_frontend/translation/src/base.rs b/rr_frontend/translation/src/base.rs index b23f4fb3abd3fc1de1dc35573907457830272ff7..2d6197749cf0a65ba04512e552a26502c7333fff 100644 --- a/rr_frontend/translation/src/base.rs +++ b/rr_frontend/translation/src/base.rs @@ -11,7 +11,16 @@ use rr_rustc_interface::middle::ty; use rr_rustc_interface::polonius_engine::FactTypes; use crate::environment::borrowck::facts; -use crate::trait_registry::Error; +use crate::traits; + +/// Strip symbols from an identifier to be compatible with Coq. +/// In particular things like ' or ::. +pub fn strip_coq_ident(s: &str) -> String { + String::from(s) + .replace('\'', "") + .replace("::", "_") + .replace(|c: char| !(c.is_alphanumeric() || c == '_'), "") +} pub type Region = <RustcFacts as FactTypes>::Origin; pub type Loan = <RustcFacts as FactTypes>::Loan; @@ -62,15 +71,15 @@ pub enum TranslationError<'tcx> { #[display("Trait could not be resolved: {}", _0)] TraitResolution(String), #[display("Trait translation failed: {}", _0)] - TraitTranslation(Error<'tcx>), + TraitTranslation(traits::Error<'tcx>), #[display("Procedure could not be registered: {}", _0)] ProcedureRegistry(String), #[display("Lookup in a dummy scope: {}", _0)] DummyScope(String), } -impl<'tcx> From<Error<'tcx>> for TranslationError<'tcx> { - fn from(x: Error<'tcx>) -> Self { +impl<'tcx> From<traits::Error<'tcx>> for TranslationError<'tcx> { + fn from(x: traits::Error<'tcx>) -> Self { Self::TraitTranslation(x) } } diff --git a/rr_frontend/translation/src/checked_op_analysis.rs b/rr_frontend/translation/src/body/checked_op_analysis.rs similarity index 100% rename from rr_frontend/translation/src/checked_op_analysis.rs rename to rr_frontend/translation/src/body/checked_op_analysis.rs diff --git a/rr_frontend/translation/src/body/mod.rs b/rr_frontend/translation/src/body/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..52c25f7db2d73b54f9593c7bc2ab4226044fc7e1 --- /dev/null +++ b/rr_frontend/translation/src/body/mod.rs @@ -0,0 +1,129 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +mod checked_op_analysis; + +use std::collections::{btree_map, BTreeMap, HashMap, HashSet}; + +use log::{info, trace, warn}; +use radium::coq; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; +use typed_arena::Arena; + +use crate::base::*; +use crate::environment::borrowck::facts; +use crate::environment::polonius_info::PoloniusInfo; +use crate::environment::procedure::Procedure; +use crate::environment::{dump_borrowck_info, polonius_info, Environment}; +use crate::spec_parsers::parse_utils::ParamLookup; +use crate::spec_parsers::verbose_function_spec_parser::{ + ClosureMetaInfo, FunctionRequirements, FunctionSpecParser, VerboseFunctionSpecParser, +}; +use crate::traits::{registry, resolution}; +use crate::{base, consts, procedures, regions, search, traits, types}; + +pub mod signature; +mod translation; + +/// Get the syntypes of function arguments for a procedure call. +pub fn get_arg_syntypes_for_procedure_call<'tcx, 'def>( + tcx: ty::TyCtxt<'tcx>, + ty_translator: &types::LocalTX<'def, 'tcx>, + callee_did: DefId, + ty_params: &[ty::GenericArg<'tcx>], +) -> Result<Vec<radium::SynType>, TranslationError<'tcx>> { + let caller_did = ty_translator.get_proc_did(); + + // Get the type of the callee, fully instantiated + let full_ty: ty::EarlyBinder<Ty<'tcx>> = tcx.type_of(callee_did); + let full_ty = full_ty.instantiate(tcx, ty_params); + + // We create a dummy scope in order to make the lifetime lookups succeed, since we only want + // syntactic types here. + // Since we do the substitution of the generics above, we should translate generics and + // traits in the caller's scope. + let scope = ty_translator.scope.borrow(); + let param_env: ty::ParamEnv<'tcx> = tcx.param_env(scope.did); + let callee_state = types::CalleeState::new(¶m_env, &scope.generic_scope); + let mut dummy_state = types::STInner::CalleeTranslation(callee_state); + + let mut syntypes = Vec::new(); + match full_ty.kind() { + ty::TyKind::FnDef(_, _) => { + let sig = full_ty.fn_sig(tcx); + for ty in sig.inputs().skip_binder() { + let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?; + syntypes.push(st); + } + }, + ty::TyKind::Closure(_, args) => { + let clos_args = args.as_closure(); + let sig = clos_args.sig(); + let pre_sig = sig.skip_binder(); + // we also need to add the closure argument here + + let tuple_ty = clos_args.tupled_upvars_ty(); + match clos_args.kind() { + ty::ClosureKind::Fn | ty::ClosureKind::FnMut => { + syntypes.push(radium::SynType::Ptr); + }, + ty::ClosureKind::FnOnce => { + let st = + ty_translator.translator.translate_type_to_syn_type(tuple_ty, &mut dummy_state)?; + syntypes.push(st); + }, + } + for ty in pre_sig.inputs() { + let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?; + syntypes.push(st); + } + }, + _ => unimplemented!(), + } + + Ok(syntypes) +} + +/// A scope of trait attributes mapping to Coq names to be used in a function's spec +struct TraitSpecNameScope { + attrs: HashMap<String, String>, +} + +/// When translating a function spec where attributes of a trait are in scope, +/// we create a wrapper to lookup references to the trait's attributes when parsing function specs. +struct FunctionSpecScope<'a, T> { + generics: &'a T, + attrs: &'a TraitSpecNameScope, +} +impl<'a, T: ParamLookup> ParamLookup for FunctionSpecScope<'a, T> { + fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> { + self.generics.lookup_ty_param(path) + } + + fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> { + self.generics.lookup_lft(lft) + } + + fn lookup_literal(&self, path: &[&str]) -> Option<&str> { + if path.len() == 1 { + if let Some(lit) = self.attrs.attrs.get(path[0]) { + return Some(lit); + } + } + self.generics.lookup_literal(path) + } +} diff --git a/rr_frontend/translation/src/body/signature.rs b/rr_frontend/translation/src/body/signature.rs new file mode 100644 index 0000000000000000000000000000000000000000..a00d22a8b60416c38cb61d21b7bb2ee1770d62cc --- /dev/null +++ b/rr_frontend/translation/src/body/signature.rs @@ -0,0 +1,870 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Part of the function translation responsible for translating the signature and specification. + +use std::collections::{btree_map, BTreeMap, HashMap, HashSet}; + +use log::{info, trace, warn}; +use radium::coq; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; +use typed_arena::Arena; + +use crate::base::*; +use crate::body::checked_op_analysis::CheckedOpLocalAnalysis; +use crate::body::translation; +use crate::environment::borrowck::facts; +use crate::environment::polonius_info::PoloniusInfo; +use crate::environment::procedure::Procedure; +use crate::environment::{dump_borrowck_info, polonius_info, Environment}; +use crate::regions::inclusion_tracker::InclusionTracker; +use crate::spec_parsers::parse_utils::ParamLookup; +use crate::spec_parsers::verbose_function_spec_parser::{ + ClosureMetaInfo, FunctionRequirements, FunctionSpecParser, VerboseFunctionSpecParser, +}; +use crate::traits::{registry, resolution}; +use crate::{base, consts, procedures, regions, search, traits, types}; + +/// A scope of trait attributes mapping to Coq names to be used in a function's spec +struct TraitSpecNameScope { + attrs: HashMap<String, String>, +} + +/// When translating a function spec where attributes of a trait are in scope, +/// we create a wrapper to lookup references to the trait's attributes when parsing function specs. +struct FunctionSpecScope<'a, T> { + generics: &'a T, + attrs: &'a TraitSpecNameScope, +} +impl<'a, T: ParamLookup> ParamLookup for FunctionSpecScope<'a, T> { + fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> { + self.generics.lookup_ty_param(path) + } + + fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> { + self.generics.lookup_lft(lft) + } + + fn lookup_literal(&self, path: &[&str]) -> Option<&str> { + if path.len() == 1 { + if let Some(lit) = self.attrs.attrs.get(path[0]) { + return Some(lit); + } + } + self.generics.lookup_literal(path) + } +} + +pub struct TX<'a, 'def, 'tcx> { + env: &'def Environment<'tcx>, + /// this needs to be annotated with the right borrowck things + proc: &'def Procedure<'tcx>, + /// the Caesium function buildder + translated_fn: radium::FunctionBuilder<'def>, + /// tracking lifetime inclusions for the generation of lifetime inclusions + inclusion_tracker: InclusionTracker<'a, 'tcx>, + + /// registry of other procedures + procedure_registry: &'a procedures::Scope<'def>, + /// registry of consts + const_registry: &'a consts::Scope<'def>, + /// attributes on this function + attrs: &'a [&'a ast::ast::AttrItem], + /// polonius info for this function + info: &'a PoloniusInfo<'a, 'tcx>, + /// translator for types + ty_translator: types::LocalTX<'def, 'tcx>, + /// trait registry in the current scope + trait_registry: &'def registry::TR<'tcx, 'def>, + /// argument types (from the signature, with generics substituted) + inputs: Vec<Ty<'tcx>>, +} + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Generate a spec for a trait method. + pub fn spec_for_trait_method( + env: &'def Environment<'tcx>, + proc_did: DefId, + name: &str, + spec_name: &str, + attrs: &'a [&'a ast::ast::AttrItem], + ty_translator: &'def types::TX<'def, 'tcx>, + trait_registry: &'def registry::TR<'tcx, 'def>, + ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> { + let mut translated_fn = radium::FunctionBuilder::new(name, spec_name); + + let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc_did); + let ty = ty.instantiate_identity(); + // substs are the generic args of this function (including lifetimes) + // sig is the function signature + let sig = match ty.kind() { + TyKind::FnDef(_def, _args) => { + assert!(ty.is_fn()); + let sig = ty.fn_sig(env.tcx()); + sig + }, + _ => panic!("can not handle non-fns"), + }; + info!("Function signature: {:?}", sig); + + let params = Self::get_proc_ty_params(env.tcx(), proc_did); + info!("Function generic args: {:?}", params); + + let (inputs, output, region_substitution) = + regions::init::replace_fnsig_args_with_polonius_vars(env, params, sig); + info!("inputs: {:?}, output: {:?}", inputs, output); + + let (type_scope, trait_attrs) = Self::setup_local_scope( + env, + ty_translator, + trait_registry, + proc_did, + params.as_slice(), + &mut translated_fn, + region_substitution, + false, + None, + )?; + let type_translator = types::LocalTX::new(ty_translator, type_scope); + + // TODO: add universal constraints (ideally in setup_local_scope) + + let spec_builder = Self::process_attrs( + attrs, + &type_translator, + &mut translated_fn, + &trait_attrs, + inputs.as_slice(), + output, + )?; + translated_fn.add_function_spec_from_builder(spec_builder); + + translated_fn.try_into().map_err(TranslationError::AttributeError) + } + + /// Create a translation instance for a closure. + pub fn new_closure( + env: &'def Environment<'tcx>, + meta: &procedures::Meta, + proc: Procedure<'tcx>, + attrs: &'a [&'a ast::ast::AttrItem], + ty_translator: &'def types::TX<'def, 'tcx>, + trait_registry: &'def registry::TR<'tcx, 'def>, + proc_registry: &'a procedures::Scope<'def>, + const_registry: &'a consts::Scope<'def>, + ) -> Result<Self, TranslationError<'tcx>> { + let mut translated_fn = radium::FunctionBuilder::new(meta.get_name(), meta.get_spec_name()); + + // TODO can we avoid the leak + let proc: &'def Procedure = &*Box::leak(Box::new(proc)); + let body = proc.get_mir(); + Self::dump_body(body); + + let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id()); + let ty = ty.instantiate_identity(); + let closure_kind = match ty.kind() { + TyKind::Closure(_def, closure_args) => { + assert!(ty.is_closure()); + let clos = closure_args.as_closure(); + clos.kind() + }, + _ => panic!("can not handle non-closures"), + }; + + let local_decls = &body.local_decls; + let closure_arg = local_decls.get(Local::from_usize(1)).unwrap(); + let closure_ty; + + match closure_kind { + ty::ClosureKind::Fn => { + if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() { + closure_ty = ty; + } else { + unreachable!(); + } + }, + ty::ClosureKind::FnMut => { + if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() { + closure_ty = ty; + } else { + unreachable!("unexpected type {:?}", closure_arg.ty); + } + }, + ty::ClosureKind::FnOnce => { + closure_ty = &closure_arg.ty; + }, + } + + let parent_args; + let mut capture_regions = Vec::new(); + let sig; + let captures; + let upvars_tys; + if let ty::TyKind::Closure(did, closure_args) = closure_ty.kind() { + let clos = closure_args.as_closure(); + + let tupled_upvars_tys = clos.tupled_upvars_ty(); + upvars_tys = clos.upvar_tys(); + parent_args = clos.parent_args(); + let unnormalized_sig = clos.sig(); + sig = unnormalized_sig; + info!("closure sig: {:?}", sig); + + captures = env.tcx().closure_captures(did.as_local().unwrap()); + info!("Closure has captures: {:?}", captures); + + // find additional lifetime parameters + for (place, ty) in captures.iter().zip(clos.upvar_tys().iter()) { + if place.region.is_some() { + // find region from ty + if let ty::TyKind::Ref(region, _, _) = ty.kind() { + capture_regions.push(*region); + } + } + } + info!("Closure capture regions: {:?}", capture_regions); + + info!("Closure arg upvar_tys: {:?}", tupled_upvars_tys); + info!("Function signature: {:?}", sig); + info!("Closure generic args: {:?}", parent_args); + } else { + unreachable!(); + } + + match PoloniusInfo::new(env, proc) { + Ok(info) => { + // TODO: avoid leak + let info: &'def PoloniusInfo = &*Box::leak(Box::new(info)); + + // For closures, we only handle the parent's args here! + // TODO: do we need to do something special for the parent's late-bound region + // parameters? + // TODO: should we always take the lifetime parameters? + let params = parent_args; + //proc.get_type_params(); + info!("Function generic args: {:?}", params); + + // dump graphviz files + // color code: red: dying loan, pink: becoming a zombie; green: is zombie + if rrconfig::dump_borrowck_info() { + dump_borrowck_info(env, proc.get_id(), info); + } + + let (tupled_inputs, output, mut region_substitution) = + regions::init::replace_fnsig_args_with_polonius_vars(env, params, sig); + + // Process the lifetime parameters that come from the captures + for r in capture_regions { + // TODO: problem: we're introducing inconsistent names here. + if let ty::RegionKind::ReVar(r) = r.kind() { + let lft = info.mk_atomic_region(r); + let name = regions::format_atomic_region_direct(&lft, None); + region_substitution.region_names.insert(r, name); + // TODO: add to region_substitution? + } else { + unreachable!(); + } + } + // also add the lifetime for the outer reference + let mut maybe_outer_lifetime = None; + if let ty::TyKind::Ref(r, _, _) = closure_arg.ty.kind() { + if let ty::RegionKind::ReVar(r) = r.kind() { + // We need to do some hacks here to find the right Polonius region: + // `r` is the non-placeholder region that the variable gets, but we are + // looking for the corresponding placeholder region + let r2 = regions::init::find_placeholder_region_for(r, info).unwrap(); + + info!("using lifetime {:?} for closure universal", r2); + let lft = info.mk_atomic_region(r2); + let name = regions::format_atomic_region_direct(&lft, None); + region_substitution.region_names.insert(r2, name); + + maybe_outer_lifetime = Some(r2); + } else { + unreachable!(); + } + } + + // detuple the inputs + assert!(tupled_inputs.len() == 1); + let input_tuple_ty = tupled_inputs[0]; + let mut inputs = Vec::new(); + + // push the closure as the first argument + /* + if let Some(r2) = maybe_outer_lifetime { + // in this case, we need to patch the region first + if let ty::TyKind::Ref(_, ty, m) = closure_arg.ty.kind() { + let new_region = ty::Region::new_var(env.tcx(), r2); + inputs.push(env.tcx().mk_ty_from_kind(ty::TyKind::Ref(new_region, *ty, *m))); + } + } + else { + inputs.push(closure_arg.ty); + } + */ + + if let ty::TyKind::Tuple(args) = input_tuple_ty.kind() { + inputs.extend(args.iter()); + } + + info!("inputs({}): {:?}, output: {:?}", inputs.len(), inputs, output); + + let mut inclusion_tracker = InclusionTracker::new(info); + // add placeholder subsets + let initial_point: facts::Point = facts::Point { + location: BasicBlock::from_u32(0).start_location(), + typ: facts::PointType::Start, + }; + for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset { + inclusion_tracker.add_static_inclusion( + *r1, + *r2, + info.interner.get_point_index(&initial_point), + ); + } + + let (type_scope, trait_attrs) = Self::setup_local_scope( + env, + ty_translator, + trait_registry, + proc.get_id(), + params, + &mut translated_fn, + region_substitution, + true, + Some(info), + )?; + let type_translator = types::LocalTX::new(ty_translator, type_scope); + + let mut t = Self { + env, + proc, + info, + translated_fn, + inclusion_tracker, + procedure_registry: proc_registry, + attrs, + ty_translator: type_translator, + trait_registry, + const_registry, + inputs: inputs.clone(), + }; + + // compute meta information needed to generate the spec + let mut translated_upvars_types = Vec::new(); + for ty in upvars_tys { + let translated_ty = t.ty_translator.translate_type(ty)?; + translated_upvars_types.push(translated_ty); + } + let meta; + { + let scope = t.ty_translator.scope.borrow(); + meta = ClosureMetaInfo { + kind: closure_kind, + captures, + capture_tys: &translated_upvars_types, + closure_lifetime: maybe_outer_lifetime + .map(|x| scope.lifetime_scope.lookup_region(x).unwrap().to_owned()), + }; + } + + // process attributes + t.process_closure_attrs(&trait_attrs, &inputs, output, meta)?; + Ok(t) + }, + Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))), + } + } + + /// Translate the body of a function. + pub fn new( + env: &'def Environment<'tcx>, + meta: &procedures::Meta, + proc: Procedure<'tcx>, + attrs: &'a [&'a ast::ast::AttrItem], + ty_translator: &'def types::TX<'def, 'tcx>, + trait_registry: &'def registry::TR<'tcx, 'def>, + proc_registry: &'a procedures::Scope<'def>, + const_registry: &'a consts::Scope<'def>, + ) -> Result<Self, TranslationError<'tcx>> { + let mut translated_fn = radium::FunctionBuilder::new(meta.get_name(), meta.get_spec_name()); + + // TODO can we avoid the leak + let proc: &'def Procedure = &*Box::leak(Box::new(proc)); + + let body = proc.get_mir(); + Self::dump_body(body); + + let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id()); + let ty = ty.instantiate_identity(); + // substs are the generic args of this function (including lifetimes) + // sig is the function signature + let sig = match ty.kind() { + TyKind::FnDef(_def, _args) => { + assert!(ty.is_fn()); + let sig = ty.fn_sig(env.tcx()); + sig + }, + _ => panic!("can not handle non-fns"), + }; + + info!("Function signature: {:?}", sig); + + match PoloniusInfo::new(env, proc) { + Ok(info) => { + // TODO: avoid leak + let info: &'def PoloniusInfo = &*Box::leak(Box::new(info)); + + let params = Self::get_proc_ty_params(env.tcx(), proc.get_id()); + info!("Function generic args: {:?}", params); + + // dump graphviz files + // color code: red: dying loan, pink: becoming a zombie; green: is zombie + if rrconfig::dump_borrowck_info() { + dump_borrowck_info(env, proc.get_id(), info); + } + + let (inputs, output, region_substitution) = + regions::init::replace_fnsig_args_with_polonius_vars(env, params, sig); + info!("inputs: {:?}, output: {:?}", inputs, output); + + let mut inclusion_tracker = InclusionTracker::new(info); + // add placeholder subsets + let initial_point: facts::Point = facts::Point { + location: BasicBlock::from_u32(0).start_location(), + typ: facts::PointType::Start, + }; + for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset { + inclusion_tracker.add_static_inclusion( + *r1, + *r2, + info.interner.get_point_index(&initial_point), + ); + } + + let (type_scope, trait_attrs) = Self::setup_local_scope( + env, + ty_translator, + trait_registry, + proc.get_id(), + params.as_slice(), + &mut translated_fn, + region_substitution, + true, + Some(info), + )?; + let type_translator = types::LocalTX::new(ty_translator, type_scope); + + // process attributes + let mut spec_builder = Self::process_attrs( + attrs, + &type_translator, + &mut translated_fn, + &trait_attrs, + inputs.as_slice(), + output, + )?; + + let mut t = Self { + env, + proc, + info, + translated_fn, + inclusion_tracker, + procedure_registry: proc_registry, + attrs, + ty_translator: type_translator, + trait_registry, + const_registry, + inputs: inputs.clone(), + }; + + if spec_builder.has_spec() { + // add universal constraints + { + let scope = t.ty_translator.scope.borrow(); + let universal_constraints = regions::init::get_relevant_universal_constraints( + &scope.lifetime_scope, + &mut t.inclusion_tracker, + t.info, + ); + info!("univeral constraints: {:?}", universal_constraints); + for (lft1, lft2) in universal_constraints { + spec_builder.add_lifetime_constraint(lft1, lft2); + } + } + + t.translated_fn.add_function_spec_from_builder(spec_builder); + } else { + let spec = t.make_trait_instance_spec()?; + if let Some(spec) = spec { + t.translated_fn.add_trait_function_spec(spec); + } else { + return Err(TranslationError::AttributeError( + "No valid specification provided".to_owned(), + )); + } + } + + Ok(t) + }, + Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))), + } + } + + /// Translate the body of the function. + pub fn translate( + mut self, + spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>, + ) -> Result<radium::Function<'def>, TranslationError<'tcx>> { + let translator = translation::TX::new( + self.env, + self.procedure_registry, + self.const_registry, + self.trait_registry, + self.ty_translator, + self.proc, + self.attrs, + self.info, + &self.inputs, + self.inclusion_tracker, + self.translated_fn, + )?; + translator.translate(spec_arena) + } + + /// Translation that only generates a specification. + pub fn generate_spec( + self, + ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> { + self.translated_fn.try_into().map_err(TranslationError::AttributeError) + } +} + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Get type parameters of the given procedure. + fn get_proc_ty_params(tcx: ty::TyCtxt<'tcx>, did: DefId) -> ty::GenericArgsRef<'tcx> { + let ty = tcx.type_of(did).instantiate_identity(); + match ty.kind() { + ty::TyKind::FnDef(_, params) => params, + ty::TyKind::Closure(_, closure_args) => { + assert!(ty.is_closure()); + let clos = closure_args.as_closure(); + let parent_args = clos.parent_args(); + + // TODO: this doesn't include lifetime parameters specific to this closure... + tcx.mk_args(parent_args) + }, + _ => panic!("Procedure::new called on a procedure whose type is not TyKind::FnDef!"), + } + } + + /// Set up the local generic scope of the function, including type parameters, lifetime + /// parameters, and trait constraints. + fn setup_local_scope( + env: &Environment<'tcx>, + ty_translator: &'def types::TX<'def, 'tcx>, + trait_registry: &'def registry::TR<'tcx, 'def>, + proc_did: DefId, + params: &[ty::GenericArg<'tcx>], + translated_fn: &mut radium::FunctionBuilder<'def>, + region_substitution: regions::EarlyLateRegionMap, + add_trait_specs: bool, + info: Option<&'def PoloniusInfo<'def, 'tcx>>, + ) -> Result<(types::FunctionState<'tcx, 'def>, TraitSpecNameScope), TranslationError<'tcx>> { + // add universals to the function + // important: these need to be in the right order! + for (vid, name) in ®ion_substitution.region_names { + translated_fn.add_universal_lifetime(name.to_owned()); + } + + // enter the procedure + let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(proc_did); + let type_scope = types::FunctionState::new_with_traits( + proc_did, + env, + env.tcx().mk_args(params), + region_substitution, + param_env, + ty_translator, + trait_registry, + info, + )?; + + // add generic args to the fn + let generics = &type_scope.generic_scope; + for t in generics.tyvars() { + translated_fn.add_ty_param(t); + } + + // add specs for traits of generics + let trait_uses = type_scope.generic_scope.trait_scope().get_trait_uses(); + for ((did, params), trait_ref) in trait_uses { + let trait_use_ref = trait_ref.trait_use.borrow(); + let trait_use = trait_use_ref.as_ref().unwrap(); + translated_fn.add_trait_requirement(trait_use.clone()); + } + + // check if we are in the implementation of a trait or trait impl + let mut trait_attr_map = HashMap::new(); + if let Some(trait_did) = env.tcx().trait_of_item(proc_did) { + // we are in a trait declaration + if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) { + // make the parameter for the attrs that the function is parametric over + if let Some(trait_use_ref) = type_scope.generic_scope.trait_scope().get_self_trait_use() { + let trait_use_ref = trait_use_ref.trait_use.borrow(); + let trait_use = trait_use_ref.as_ref().unwrap(); + let param_name = trait_use.make_spec_attrs_param_name(); + // add the corresponding record entries to the map + for attr in &trait_ref.declared_attrs { + let record_item = trait_ref.make_spec_attr_name(attr); + trait_attr_map.insert(attr.to_owned(), format!("{param_name}.({record_item})")); + } + } + } + } + if let Some(impl_did) = env.tcx().impl_of_method(proc_did) { + if let Some(trait_did) = env.tcx().trait_id_of_impl(impl_did) { + // we are in a trait impl + if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) { + let attrs = trait_registry.get_impl_attrs_term(impl_did)?; + // add the corresponding record entries to the map + for attr in &trait_ref.declared_attrs { + let record_item = trait_ref.make_spec_attr_name(attr); + trait_attr_map.insert(attr.to_owned(), format!("({attrs}).({record_item})")); + } + } + } + } + let trait_scope = TraitSpecNameScope { + attrs: trait_attr_map, + }; + + // TODO: can we also setup the lifetime constraints here? + // TODO: understand better how these clauses relate to Polonius + // Note: these constraints do not seem to include implied bounds. + /* + let clauses = param_env.caller_bounds(); + info!("looking for outlives clauses"); + for cl in clauses.iter() { + let cl_kind = cl.kind(); + let cl_kind = cl_kind.skip_binder(); + + // only look for trait predicates for now + if let ty::ClauseKind::RegionOutlives(region_pred) = cl_kind { + info!("region outlives: {:?} {:?}", region_pred.0, region_pred.1); + } + if let ty::ClauseKind::TypeOutlives(outlives_pred) = cl_kind { + info!("type outlives: {:?} {:?}", outlives_pred.0, outlives_pred.1); + } + } + */ + + Ok((type_scope, trait_scope)) + } + + /// Process extra requirements annotated on a function spec. + fn process_function_requirements( + fn_builder: &mut radium::FunctionBuilder<'def>, + requirements: FunctionRequirements, + ) { + for e in requirements.early_coq_params { + fn_builder.add_early_coq_param(e); + } + for e in requirements.late_coq_params { + fn_builder.add_late_coq_param(e); + } + for e in requirements.proof_info.linktime_assumptions { + fn_builder.add_linktime_assumption(e); + } + for e in requirements.proof_info.sidecond_tactics { + fn_builder.add_manual_tactic(e); + } + } + + /// Parse and process attributes of this closure. + fn process_closure_attrs<'b>( + &mut self, + trait_attrs: &TraitSpecNameScope, + inputs: &[Ty<'tcx>], + output: Ty<'tcx>, + meta: ClosureMetaInfo<'b, 'tcx, 'def>, + ) -> Result<(), TranslationError<'tcx>> { + trace!("entering process_closure_attrs"); + let v = self.attrs; + + // Translate signature + info!("inputs: {:?}, output: {:?}", inputs, output); + let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new(); + for arg in inputs { + let translated: radium::Type<'def> = self.ty_translator.translate_type(*arg)?; + translated_arg_types.push(translated); + } + let translated_ret_type: radium::Type<'def> = self.ty_translator.translate_type(output)?; + info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type); + + // Determine parser + let parser = rrconfig::attribute_parser(); + if parser.as_str() != "verbose" { + trace!("leaving process_closure_attrs"); + return Err(TranslationError::UnknownAttributeParser(parser)); + } + + let mut spec_builder = radium::LiteralFunctionSpecBuilder::new(); + + // add universal constraints + { + let scope = self.ty_translator.scope.borrow(); + let universal_constraints = regions::init::get_relevant_universal_constraints( + &scope.lifetime_scope, + &mut self.inclusion_tracker, + self.info, + ); + info!("universal constraints: {:?}", universal_constraints); + for (lft1, lft2) in universal_constraints { + spec_builder.add_lifetime_constraint(lft1, lft2); + } + } + + let ty_translator = &self.ty_translator; + // Hack: create indirection by tracking the tuple uses we create in here. + // (We need a read reference to the scope, so we can't write to it at the same time) + let mut tuple_uses = HashMap::new(); + { + let scope = ty_translator.scope.borrow(); + let scope = FunctionSpecScope { + generics: &*scope, + attrs: trait_attrs, + }; + let mut parser = + VerboseFunctionSpecParser::new(&translated_arg_types, &translated_ret_type, &scope, |lit| { + ty_translator.translator.intern_literal(lit) + }); + + parser + .parse_closure_spec(v, &mut spec_builder, meta, |x| { + ty_translator.make_tuple_use(x, Some(&mut tuple_uses)) + }) + .map_err(TranslationError::AttributeError)?; + + Self::process_function_requirements(&mut self.translated_fn, parser.into()); + } + let mut scope = ty_translator.scope.borrow_mut(); + scope.tuple_uses.extend(tuple_uses); + self.translated_fn.add_function_spec_from_builder(spec_builder); + + trace!("leaving process_closure_attrs"); + Ok(()) + } + + /// Parse and process attributes of this function. + fn process_attrs( + attrs: &[&ast::ast::AttrItem], + ty_translator: &types::LocalTX<'def, 'tcx>, + translator: &mut radium::FunctionBuilder<'def>, + trait_attrs: &TraitSpecNameScope, + inputs: &[Ty<'tcx>], + output: Ty<'tcx>, + ) -> Result<radium::LiteralFunctionSpecBuilder<'def>, TranslationError<'tcx>> { + info!("inputs: {:?}, output: {:?}", inputs, output); + + let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new(); + for arg in inputs { + let translated: radium::Type<'def> = ty_translator.translate_type(*arg)?; + translated_arg_types.push(translated); + } + let translated_ret_type: radium::Type<'def> = ty_translator.translate_type(output)?; + info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type); + + let mut spec_builder = radium::LiteralFunctionSpecBuilder::new(); + + let parser = rrconfig::attribute_parser(); + match parser.as_str() { + "verbose" => { + { + let scope = ty_translator.scope.borrow(); + let scope = FunctionSpecScope { + generics: &*scope, + attrs: trait_attrs, + }; + let mut parser: VerboseFunctionSpecParser<'_, 'def, _, _> = + VerboseFunctionSpecParser::new( + &translated_arg_types, + &translated_ret_type, + &scope, + |lit| ty_translator.translator.intern_literal(lit), + ); + + parser + .parse_function_spec(attrs, &mut spec_builder) + .map_err(TranslationError::AttributeError)?; + Self::process_function_requirements(translator, parser.into()); + } + + Ok(spec_builder) + }, + _ => Err(TranslationError::UnknownAttributeParser(parser)), + } + } + + /// Make a specification for a method of a trait instance derived from the trait's default spec. + fn make_trait_instance_spec( + &self, + ) -> Result<Option<radium::InstantiatedTraitFunctionSpec<'def>>, TranslationError<'tcx>> { + let did = self.proc.get_id(); + if let Some(impl_did) = self.env.tcx().impl_of_method(did) { + if let Some(trait_did) = self.env.tcx().trait_id_of_impl(impl_did) { + let trait_ref = self + .trait_registry + .lookup_trait(trait_did) + .ok_or_else(|| TranslationError::TraitResolution(format!("{trait_did:?}")))?; + let fn_name = base::strip_coq_ident(self.env.tcx().item_name(self.proc.get_id()).as_str()); + + let trait_info = self.trait_registry.get_trait_impl_info(impl_did)?; + //self.trait_registry.lookup_impl(impl_did)?; + let attr_term = self.trait_registry.get_impl_attrs_term(impl_did)?; + return Ok(Some(radium::InstantiatedTraitFunctionSpec::new(trait_info, fn_name, attr_term))); + } + } + + Ok(None) + } + + fn dump_body(body: &Body) { + // TODO: print to file + //for dec in &body.local_decls { + //info!("Local decl: {:?}", dec); + //} + + let basic_blocks = &body.basic_blocks; + for (bb_idx, bb) in basic_blocks.iter_enumerated() { + Self::dump_basic_block(bb_idx, bb); + } + } + + /// Dump a basic block as info debug output. + fn dump_basic_block(bb_idx: BasicBlock, bb: &BasicBlockData) { + info!("Basic block {:?}:", bb_idx); + let mut i = 0; + for s in &bb.statements { + info!("{}\t{:?}", i, s); + i += 1; + } + info!("{}\t{:?}", i, bb.terminator()); + } +} diff --git a/rr_frontend/translation/src/body/translation/basicblock.rs b/rr_frontend/translation/src/body/translation/basicblock.rs new file mode 100644 index 0000000000000000000000000000000000000000..2f74d8776976a90e159ccc8dd50febab8af132f0 --- /dev/null +++ b/rr_frontend/translation/src/body/translation/basicblock.rs @@ -0,0 +1,245 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use std::collections::{btree_map, BTreeMap, HashMap, HashSet}; + +use log::info; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; + +use super::TX; +use crate::base::*; +use crate::traits::{registry, resolution}; +use crate::{base, consts, procedures, regions, search, traits, types}; + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Translate a single basic block. + pub(super) fn translate_basic_block( + &mut self, + bb_idx: BasicBlock, + bb: &BasicBlockData<'tcx>, + ) -> Result<radium::Stmt, TranslationError<'tcx>> { + // we translate from back to front, starting with the terminator, since Caesium statements + // have a continuation (the next statement to execute) + + // first do the endlfts for the things right before the terminator + let mut idx = bb.statements.len(); + let loc = Location { + block: bb_idx, + statement_index: idx, + }; + let dying = self.info.get_dying_loans(loc); + // TODO zombie? + let _dying_zombie = self.info.get_dying_zombie_loans(loc); + let mut cont_stmt: radium::Stmt = self.translate_terminator(bb.terminator(), loc, dying)?; + + //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying); + //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying_zombie); + + for stmt in bb.statements.iter().rev() { + idx -= 1; + let loc = Location { + block: bb_idx, + statement_index: idx, + }; + + // get all dying loans, and emit endlfts for these. + // We loop over all predecessor locations, since some loans may end at the start of a + // basic block (in particular related to NLL stuff) + let pred = self.get_loc_predecessors(loc); + let mut dying_loans = HashSet::new(); + for p in pred { + let dying_between = self.info.get_loans_dying_between(p, loc, false); + for l in &dying_between { + dying_loans.insert(*l); + } + // also include zombies + let dying_between = self.info.get_loans_dying_between(p, loc, true); + for l in &dying_between { + dying_loans.insert(*l); + } + } + // we prepend them before the current statement + + match &stmt.kind { + StatementKind::Assign(b) => { + let (plc, val) = b.as_ref(); + + if (self.is_spec_closure_local(plc.local)?).is_some() { + info!("skipping assignment to spec closure local: {:?}", plc); + } else if let Some(rewritten_ty) = self.checked_op_temporaries.get(&plc.local) { + // if this is a checked op, be sure to remember it + info!("rewriting assignment to checked op: {:?}", plc); + + let synty = self.ty_translator.translate_type_to_syn_type(*rewritten_ty)?; + + let translated_val = self.translate_rvalue(loc, val)?; + let translated_place = self.translate_place(plc)?; + + // this should be a temporary + assert!(plc.projection.is_empty()); + + let ot = synty.into(); + cont_stmt = radium::Stmt::Assign { + ot, + e1: translated_place, + e2: translated_val, + s: Box::new(cont_stmt), + }; + } else { + let plc_ty = self.get_type_of_place(plc); + let rhs_ty = val.ty(&self.proc.get_mir().local_decls, self.env.tcx()); + + let borrow_annots = regions::assignment::get_assignment_loan_annots( + &mut self.inclusion_tracker, &self.ty_translator, + loc, val); + + let plc_ty = self.get_type_of_place(plc); + let plc_strongly_writeable = !self.check_place_below_reference(plc); + let (expr_annot, pre_stmt_annots, post_stmt_annots) = + regions::assignment::get_assignment_annots( + self.env, &mut self.inclusion_tracker, &self.ty_translator, + loc, plc_strongly_writeable, plc_ty, rhs_ty); + + // TODO; maybe move this to rvalue + let composite_annots = regions::composite::get_composite_rvalue_creation_annots( + self.env, &mut self.inclusion_tracker, &self.ty_translator, loc, rhs_ty); + + cont_stmt = radium::Stmt::with_annotations( + cont_stmt, + post_stmt_annots, + &Some("post-assignment".to_owned()), + ); + + let translated_val = radium::Expr::with_optional_annotation( + self.translate_rvalue(loc, val)?, + expr_annot, + Some("assignment".to_owned()), + ); + let translated_place = self.translate_place(plc)?; + let synty = self.ty_translator.translate_type_to_syn_type(plc_ty.ty)?; + cont_stmt = radium::Stmt::Assign { + ot: synty.into(), + e1: translated_place, + e2: translated_val, + s: Box::new(cont_stmt), + }; + cont_stmt = radium::Stmt::with_annotations( + cont_stmt, + pre_stmt_annots, + &Some("assignment".to_owned()), + ); + cont_stmt = radium::Stmt::with_annotations( + cont_stmt, + borrow_annots, + &Some("borrow".to_owned()), + ); + cont_stmt = radium::Stmt::with_annotations( + cont_stmt, + composite_annots, + &Some("composite".to_owned()), + ); + } + }, + + StatementKind::Deinit(_) => { + // TODO: find out where this is emitted + return Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support Deinit".to_owned(), + }); + }, + + StatementKind::FakeRead(b) => { + // we can probably ignore this, but I'm not sure + info!("Ignoring FakeRead: {:?}", b); + }, + + StatementKind::Intrinsic(intrinsic) => { + match intrinsic.as_ref() { + NonDivergingIntrinsic::Assume(_) => { + // ignore + info!("Ignoring Assume: {:?}", intrinsic); + }, + NonDivergingIntrinsic::CopyNonOverlapping(_) => { + return Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support the CopyNonOverlapping Intrinsic".to_owned(), + }); + }, + } + }, + + StatementKind::PlaceMention(place) => { + // TODO: this is missed UB + info!("Ignoring PlaceMention: {:?}", place); + }, + + StatementKind::SetDiscriminant { + place: _place, + variant_index: _variant_index, + } => { + // TODO + return Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support SetDiscriminant".to_owned(), + }); + }, + + // don't need that info + | StatementKind::AscribeUserType(_, _) + // don't need that + | StatementKind::Coverage(_) + // no-op + | StatementKind::ConstEvalCounter + // ignore + | StatementKind::Nop + // just ignore + | StatementKind::StorageLive(_) + // just ignore + | StatementKind::StorageDead(_) + // just ignore retags + | StatementKind::Retag(_, _) => (), + } + + cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.into_iter()); + } + + Ok(cont_stmt) + } + + /// Get predecessors in the CFG. + fn get_loc_predecessors(&self, loc: Location) -> Vec<Location> { + if loc.statement_index > 0 { + let pred = Location { + block: loc.block, + statement_index: loc.statement_index - 1, + }; + vec![pred] + } else { + // check for gotos that go to this basic block + let pred_bbs = self.proc.predecessors(loc.block); + let basic_blocks = &self.proc.get_mir().basic_blocks; + pred_bbs + .iter() + .map(|bb| { + let data = &basic_blocks[*bb]; + Location { + block: *bb, + statement_index: data.statements.len(), + } + }) + .collect() + } + } +} diff --git a/rr_frontend/translation/src/body/translation/calls.rs b/rr_frontend/translation/src/body/translation/calls.rs new file mode 100644 index 0000000000000000000000000000000000000000..5229e096f89e83c286d0df33400b403f10fdfdf8 --- /dev/null +++ b/rr_frontend/translation/src/body/translation/calls.rs @@ -0,0 +1,613 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use log::{info, trace}; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; + +use super::TX; +use crate::base::*; +use crate::environment::borrowck::facts; +use crate::traits::resolution; +use crate::{regions, types}; + +/// Get the syntypes of function arguments for a procedure call. +fn get_arg_syntypes_for_procedure_call<'tcx, 'def>( + tcx: ty::TyCtxt<'tcx>, + ty_translator: &types::LocalTX<'def, 'tcx>, + callee_did: DefId, + ty_params: &[ty::GenericArg<'tcx>], +) -> Result<Vec<radium::SynType>, TranslationError<'tcx>> { + let caller_did = ty_translator.get_proc_did(); + + // Get the type of the callee, fully instantiated + let full_ty: ty::EarlyBinder<Ty<'tcx>> = tcx.type_of(callee_did); + let full_ty = full_ty.instantiate(tcx, ty_params); + + // We create a dummy scope in order to make the lifetime lookups succeed, since we only want + // syntactic types here. + // Since we do the substitution of the generics above, we should translate generics and + // traits in the caller's scope. + let scope = ty_translator.scope.borrow(); + let param_env: ty::ParamEnv<'tcx> = tcx.param_env(scope.did); + let callee_state = types::CalleeState::new(¶m_env, &scope.generic_scope); + let mut dummy_state = types::STInner::CalleeTranslation(callee_state); + + let mut syntypes = Vec::new(); + match full_ty.kind() { + ty::TyKind::FnDef(_, _) => { + let sig = full_ty.fn_sig(tcx); + for ty in sig.inputs().skip_binder() { + let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?; + syntypes.push(st); + } + }, + ty::TyKind::Closure(_, args) => { + let clos_args = args.as_closure(); + let sig = clos_args.sig(); + let pre_sig = sig.skip_binder(); + // we also need to add the closure argument here + + let tuple_ty = clos_args.tupled_upvars_ty(); + match clos_args.kind() { + ty::ClosureKind::Fn | ty::ClosureKind::FnMut => { + syntypes.push(radium::SynType::Ptr); + }, + ty::ClosureKind::FnOnce => { + let st = + ty_translator.translator.translate_type_to_syn_type(tuple_ty, &mut dummy_state)?; + syntypes.push(st); + }, + } + for ty in pre_sig.inputs() { + let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?; + syntypes.push(st); + } + }, + _ => unimplemented!(), + } + + Ok(syntypes) +} + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Internally register that we have used a procedure with a particular instantiation of generics, and + /// return the code parameter name. + /// Arguments: + /// - `callee_did`: the `DefId` of the callee + /// - `ty_params`: the instantiation for the callee's type parameters + /// - `trait_spec_terms`: if the callee has any trait assumptions, these are specification parameter terms + /// for these traits + /// - `trait_assoc_tys`: if the callee has any trait assumptions, these are the instantiations for all + /// associated types + fn register_use_procedure( + &mut self, + callee_did: DefId, + extra_spec_args: Vec<String>, + ty_params: ty::GenericArgsRef<'tcx>, + trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>, + ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> { + trace!("enter register_use_procedure callee_did={callee_did:?} ty_params={ty_params:?}"); + // The key does not include the associated types, as the resolution of the associated types + // should always be unique for one combination of type parameters, as long as we remain in + // the same environment (which is the case within this procedure). + // Therefore, already the type parameters are sufficient to distinguish different + // instantiations. + let key = types::generate_args_inst_key(self.env.tcx(), ty_params)?; + + // re-quantify + let quantified_args = self.ty_translator.get_generic_abstraction_for_procedure( + callee_did, + ty_params, + &trait_specs, + true, + )?; + + let tup = (callee_did, key); + let res; + if let Some(proc_use) = self.collected_procedures.get(&tup) { + res = proc_use.loc_name.clone(); + } else { + let meta = self + .procedure_registry + .lookup_function(callee_did) + .ok_or_else(|| TranslationError::UnknownProcedure(format!("{:?}", callee_did)))?; + // explicit instantiation is needed sometimes + let spec_name = format!("{} (RRGS:=RRGS)", meta.get_spec_name()); + let code_name = meta.get_name(); + let loc_name = format!("{}_loc", types::mangle_name_with_tys(code_name, tup.1.as_slice())); + + let syntypes = get_arg_syntypes_for_procedure_call( + self.env.tcx(), + &self.ty_translator, + callee_did, + ty_params.as_slice(), + )?; + + let mut translated_params = quantified_args.fn_ty_param_inst; + + info!( + "Registered procedure instance {} of {:?} with {:?} and layouts {:?}", + loc_name, callee_did, translated_params, syntypes + ); + + let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect(); + + let proc_use = radium::UsedProcedure::new( + loc_name, + spec_name, + extra_spec_args, + quantified_args.scope, + specs, + translated_params, + quantified_args.fn_lft_param_inst, + syntypes, + ); + + res = proc_use.loc_name.clone(); + self.collected_procedures.insert(tup, proc_use); + } + trace!("leave register_use_procedure"); + Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst)) + } + + /// Internally register that we have used a trait method with a particular instantiation of + /// generics, and return the code parameter name. + fn register_use_trait_method<'c>( + &'c mut self, + callee_did: DefId, + ty_params: ty::GenericArgsRef<'tcx>, + trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>, + ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> { + trace!("enter register_use_trait_method did={:?} ty_params={:?}", callee_did, ty_params); + // Does not include the associated types in the key; see `register_use_procedure` for an + // explanation. + let key = types::generate_args_inst_key(self.env.tcx(), ty_params)?; + + let (method_loc_name, method_spec_term, method_params) = + self.ty_translator.register_use_trait_procedure(self.env, callee_did, ty_params)?; + // re-quantify + let quantified_args = self.ty_translator.get_generic_abstraction_for_procedure( + callee_did, + method_params, + &trait_specs, + false, + )?; + + let tup = (callee_did, key); + let res; + if let Some(proc_use) = self.collected_procedures.get(&tup) { + res = proc_use.loc_name.clone(); + } else { + // TODO: should we use ty_params or method_params? + let syntypes = get_arg_syntypes_for_procedure_call( + self.env.tcx(), + &self.ty_translator, + callee_did, + ty_params.as_slice(), + )?; + + let mut translated_params = quantified_args.fn_ty_param_inst; + + info!( + "Registered procedure instance {} of {:?} with {:?} and layouts {:?}", + method_loc_name, callee_did, translated_params, syntypes + ); + + let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect(); + let proc_use = radium::UsedProcedure::new( + method_loc_name, + method_spec_term, + vec![], + quantified_args.scope, + specs, + translated_params, + quantified_args.fn_lft_param_inst, + syntypes, + ); + + res = proc_use.loc_name.clone(); + self.collected_procedures.insert(tup, proc_use); + } + trace!("leave register_use_procedure"); + Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst)) + } + + /// Resolve the trait requirements of a function call. + /// The target of the call, [did], should have been resolved as much as possible, + /// as the requirements of a call can be different depending on which impl we consider. + fn resolve_trait_requirements_of_call( + &self, + did: DefId, + params: ty::GenericArgsRef<'tcx>, + ) -> Result<Vec<radium::TraitReqInst<'def, Ty<'tcx>>>, TranslationError<'tcx>> { + let mut scope = self.ty_translator.scope.borrow_mut(); + let mut state = types::STInner::InFunction(&mut scope); + self.trait_registry.resolve_trait_requirements_in_state(&mut state, did, params) + } + + /// Translate the use of an `FnDef`, registering that the current function needs to link against + /// a particular monomorphization of the used function. + /// Is guaranteed to return a `radium::Expr::CallTarget` with the parameter instantiation of + /// this function annotated. + pub(super) fn translate_fn_def_use( + &mut self, + ty: Ty<'tcx>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + let TyKind::FnDef(defid, params) = ty.kind() else { + return Err(TranslationError::UnknownError("not a FnDef type".to_owned())); + }; + + let current_param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id()); + + // Check whether we are calling into a trait method. + // This works since we did not resolve concrete instances, so this is always an abstract + // reference to the trait. + let calling_trait = self.env.tcx().trait_of_item(*defid); + + // Check whether we are calling a plain function or a trait method + let Some(calling_trait) = calling_trait else { + // resolve the trait requirements + let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?; + + // track that we are using this function and generate the Coq location name + let (code_param_name, ty_hint, lft_hint) = + self.register_use_procedure(*defid, vec![], params, trait_spec_terms)?; + + let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); + + return Ok(radium::Expr::CallTarget(code_param_name, ty_hint, lft_hint)); + }; + + // Otherwise, we are calling a trait method + // Resolve the trait instance using trait selection + let Some((resolved_did, resolved_params, kind)) = + resolution::resolve_assoc_item(self.env.tcx(), current_param_env, *defid, params) + else { + return Err(TranslationError::TraitResolution(format!("Could not resolve trait {:?}", defid))); + }; + + info!( + "Resolved trait method {:?} as {:?} with substs {:?} and kind {:?}", + defid, resolved_did, resolved_params, kind + ); + + match kind { + resolution::TraitResolutionKind::UserDefined => { + // We can statically resolve the particular trait instance, + // but need to apply the spec to the instance's spec attributes + + // resolve the trait requirements + let trait_spec_terms = + self.resolve_trait_requirements_of_call(resolved_did, resolved_params)?; + + let (param_name, ty_hint, lft_hint) = + self.register_use_procedure(resolved_did, vec![], resolved_params, trait_spec_terms)?; + let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); + + Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint)) + }, + + resolution::TraitResolutionKind::Param => { + // In this case, we have already applied it to the spec attribute + + // resolve the trait requirements + let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?; + + let (param_name, ty_hint, lft_hint) = + self.register_use_trait_method(resolved_did, resolved_params, trait_spec_terms)?; + let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); + + Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint)) + }, + + resolution::TraitResolutionKind::Closure => { + // TODO: here, we should first generate an instance of the trait + //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::Item(resolved_did)); + //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::FnPtrShim(*defid, ty)); + //info!("closure body: {:?}", body); + + //FunctionTranslator::dump_body(body); + + //let res_result = ty::Instance::resolve(self.env.tcx(), callee_param_env, *defid, params); + //info!("Resolution {:?}", res_result); + + // the args are just the closure args. We can ignore them. + let _clos_args = resolved_params.as_closure(); + + // resolve the trait requirements + let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?; + + let (param_name, ty_hint, lft_hint) = + self.register_use_procedure(resolved_did, vec![], ty::List::empty(), trait_spec_terms)?; + let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); + + Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint)) + }, + } + } + + /// Split the type of a function operand of a call expression to a base type and an instantiation for + /// generics. + fn call_expr_op_split_inst( + &self, + constant: &Constant<'tcx>, + ) -> Result< + (DefId, ty::PolyFnSig<'tcx>, ty::GenericArgsRef<'tcx>, ty::PolyFnSig<'tcx>), + TranslationError<'tcx>, + > { + match constant.literal { + ConstantKind::Ty(c) => { + match c.ty().kind() { + TyKind::FnDef(def, args) => { + let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def); + let ty_ident = ty.instantiate_identity(); + assert!(ty_ident.is_fn()); + let ident_sig = ty_ident.fn_sig(self.env.tcx()); + + let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice()); + let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx()); + + Ok((*def, ident_sig, args, instantiated_sig)) + }, + // TODO handle FnPtr, closure + _ => Err(TranslationError::Unimplemented { + description: "implement function pointers".to_owned(), + }), + } + }, + ConstantKind::Val(_, ty) => { + match ty.kind() { + TyKind::FnDef(def, args) => { + let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def); + + let ty_ident = ty.instantiate_identity(); + assert!(ty_ident.is_fn()); + let ident_sig = ty_ident.fn_sig(self.env.tcx()); + + let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice()); + let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx()); + + Ok((*def, ident_sig, args, instantiated_sig)) + }, + // TODO handle FnPtr, closure + _ => Err(TranslationError::Unimplemented { + description: "implement function pointers".to_owned(), + }), + } + }, + ConstantKind::Unevaluated(_, _) => Err(TranslationError::Unimplemented { + description: "implement ConstantKind::Unevaluated".to_owned(), + }), + } + } + + pub(super) fn translate_function_call( + &mut self, + func: &Operand<'tcx>, + args: &[Operand<'tcx>], + destination: &Place<'tcx>, + target: Option<middle::mir::BasicBlock>, + loc: Location, + dying_loans: &[facts::Loan], + ) -> Result<radium::Stmt, TranslationError<'tcx>> { + let startpoint = self.info.interner.get_point_index(&facts::Point { + location: loc, + typ: facts::PointType::Start, + }); + + let Operand::Constant(box func_constant) = func else { + return Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support this kind of call operand (got: {:?})", + func + ), + }); + }; + + // Get the type of the return value from the function + let (target_did, sig, generic_args, inst_sig) = self.call_expr_op_split_inst(func_constant)?; + info!("calling function {:?}", target_did); + info!("call substs: {:?} = {:?}, {:?}", func, sig, generic_args); + + // for lifetime annotations: + // 1. get the regions involved here. for that, get the instantiation of the function. + // + if it's a FnDef type, that should be easy. + // + for a function pointer: ? + // + for a closure: ? + // (Polonius does not seem to distinguish early/late bound in any way, except + // that they are numbered in different passes) + // 2. find the constraints here involving the regions. + // 3. solve for the regions. + // + transitively propagate the constraints + // + check for equalities + // + otherwise compute intersection. singleton intersection just becomes an equality def. + // 4. add annotations accordingly + // + either a startlft + // + or a copy name + // 5. add shortenlft annotations to line up arguments. + // + for that, we need the type of the LHS, and what the argument types (with + // substituted regions) should be. + // 6. annotate the return value on assignment and establish constraints. + + let classification = + regions::calls::compute_call_regions(self.env, &self.inclusion_tracker, generic_args, loc); + + // update the inclusion tracker with the new regions we have introduced + // We just add the inclusions and ignore that we resolve it in a "tight" way. + // the cases where we need the reverse inclusion should be really rare. + for (r, c) in &classification.classification { + match c { + regions::calls::CallRegionKind::EqR(r2) => { + // put it at the start point, because the inclusions come into effect + // at the point right before. + self.inclusion_tracker.add_static_inclusion(*r, *r2, startpoint); + self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint); + }, + regions::calls::CallRegionKind::Intersection(lfts) => { + // all the regions represented by lfts need to be included in r + for r2 in lfts { + self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint); + } + }, + } + } + + // translate the function expression. + let func_expr = self.translate_operand(func, false)?; + // We expect this to be an Expr::CallTarget, being annotated with the type parameters we + // instantiate it with. + let radium::Expr::CallTarget(func_lit, ty_param_annots, mut lft_param_annots) = func_expr else { + unreachable!("Logic error in call target translation"); + }; + let func_expr = radium::Expr::MetaParam(func_lit); + + // translate the arguments + let mut translated_args = Vec::new(); + for arg in args { + // to_ty is the type the function expects + + //let ty = arg.ty(&self.proc.get_mir().local_decls, self.env.tcx()); + let translated_arg = self.translate_operand(arg, true)?; + translated_args.push(translated_arg); + } + + // We have to add the late regions, since we do not requantify over them. + for late in &classification.late_regions { + let lft = self.format_region(*late); + lft_param_annots.push(lft); + } + info!("Call lifetime instantiation (early): {:?}", classification.early_regions); + info!("Call lifetime instantiation (late): {:?}", classification.late_regions); + + // TODO: do we need to do something with late bounds? + let output_ty = inst_sig.output().skip_binder(); + info!("call has instantiated type {:?}", inst_sig); + + // compute the resulting annotations + let lhs_ty = self.get_type_of_place(destination); + let lhs_strongly_writeable = !self.check_place_below_reference(destination); + let (rhs_annots, pre_stmt_annots, post_stmt_annots) = regions::assignment::get_assignment_annots( + self.env, + &mut self.inclusion_tracker, + &self.ty_translator, + loc, + lhs_strongly_writeable, + lhs_ty, + output_ty, + ); + info!( + "assignment annots after call: expr: {:?}, pre-stmt: {:?}, post-stmt: {:?}", + rhs_annots, pre_stmt_annots, post_stmt_annots + ); + + // TODO: add annotations for the assignment + // for that: + // - get the type of the place + // - enforce constraints as necessary. this might spawn dyninclusions with some of the new regions => + // In Coq, also the aliases should get proper endlft events to resolve the dyninclusions. + // - update the name map + let call_expr = radium::Expr::Call { + f: Box::new(func_expr), + lfts: lft_param_annots, + tys: ty_param_annots, + args: translated_args, + }; + let stmt = match target { + Some(target) => { + let mut cont_stmt = self.translate_goto_like(&loc, target)?; + // end loans before the goto, but after the call. + // TODO: may cause duplications? + cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.iter().copied()); + + let cont_stmt = radium::Stmt::with_annotations( + cont_stmt, + post_stmt_annots, + &Some("post_function_call".to_owned()), + ); + + // assign stmt with call; then jump to bb + let place_ty = self.get_type_of_place(destination); + let place_st = self.ty_translator.translate_type_to_syn_type(place_ty.ty)?; + let place_expr = self.translate_place(destination)?; + let ot = place_st.into(); + + let annotated_rhs = radium::Expr::with_optional_annotation( + call_expr, + rhs_annots, + Some("function_call".to_owned()), + ); + let assign_stmt = radium::Stmt::Assign { + ot, + e1: place_expr, + e2: annotated_rhs, + s: Box::new(cont_stmt), + }; + radium::Stmt::with_annotations( + assign_stmt, + pre_stmt_annots, + &Some("function_call".to_owned()), + ) + }, + None => { + // expr stmt with call; then stuck (we have not provided a continuation, after all) + radium::Stmt::ExprS { + e: call_expr, + s: Box::new(radium::Stmt::Stuck), + } + }, + }; + + let mut stmt_annots = Vec::new(); + + // add annotations to initialize the regions for the call (before the call) + for (r, class) in &classification.classification { + let lft = self.format_region(*r); + match class { + regions::calls::CallRegionKind::EqR(r2) => { + let lft2 = self.format_region(*r2); + stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft)); + }, + + regions::calls::CallRegionKind::Intersection(rs) => { + match rs.len() { + 0 => { + return Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support unconstrained lifetime" + .to_owned(), + }); + }, + 1 => { + // this is really just an equality constraint + if let Some(r2) = rs.iter().next() { + let lft2 = self.format_region(*r2); + stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft)); + } + }, + _ => { + // a proper intersection + let lfts: Vec<_> = rs.iter().map(|r| self.format_region(*r)).collect(); + stmt_annots.push(radium::Annotation::AliasLftIntersection(lft, lfts)); + }, + }; + }, + } + } + + let stmt = radium::Stmt::with_annotations(stmt, stmt_annots, &Some("function_call".to_owned())); + Ok(stmt) + } +} diff --git a/rr_frontend/translation/src/body/translation/constants.rs b/rr_frontend/translation/src/body/translation/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..dd9a5206d002e7f5ea1395aa50e58363722c7382 --- /dev/null +++ b/rr_frontend/translation/src/body/translation/constants.rs @@ -0,0 +1,195 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use log::info; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; + +use super::TX; +use crate::base::*; + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Translate a scalar at a specific type to a `radium::Expr`. + // TODO: Use `TryFrom` instead + fn translate_scalar( + &mut self, + sc: &Scalar, + ty: Ty<'tcx>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + // TODO: Use `TryFrom` instead + fn translate_literal<'tcx, T, U>( + sc: Result<T, U>, + fptr: fn(T) -> radium::Literal, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + sc.map_or(Err(TranslationError::InvalidLayout), |lit| Ok(radium::Expr::Literal(fptr(lit)))) + } + + match ty.kind() { + TyKind::Bool => translate_literal(sc.to_bool(), radium::Literal::Bool), + + TyKind::Int(it) => match it { + ty::IntTy::I8 => translate_literal(sc.to_i8(), radium::Literal::I8), + ty::IntTy::I16 => translate_literal(sc.to_i16(), radium::Literal::I16), + ty::IntTy::I32 => translate_literal(sc.to_i32(), radium::Literal::I32), + ty::IntTy::I128 => translate_literal(sc.to_i128(), radium::Literal::I128), + + // For Radium, the pointer size is 8 bytes + ty::IntTy::I64 | ty::IntTy::Isize => translate_literal(sc.to_i64(), radium::Literal::I64), + }, + + TyKind::Uint(it) => match it { + ty::UintTy::U8 => translate_literal(sc.to_u8(), radium::Literal::U8), + ty::UintTy::U16 => translate_literal(sc.to_u16(), radium::Literal::U16), + ty::UintTy::U32 => translate_literal(sc.to_u32(), radium::Literal::U32), + ty::UintTy::U128 => translate_literal(sc.to_u128(), radium::Literal::U128), + + // For Radium, the pointer is 8 bytes + ty::UintTy::U64 | ty::UintTy::Usize => translate_literal(sc.to_u64(), radium::Literal::U64), + }, + + TyKind::Char => translate_literal(sc.to_char(), radium::Literal::Char), + + TyKind::FnDef(_, _) => self.translate_fn_def_use(ty), + + TyKind::Tuple(tys) => { + if tys.is_empty() { + return Ok(radium::Expr::Literal(radium::Literal::ZST)); + } + + Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support compound construction of tuples using literals (got: {:?})", + ty + ), + }) + }, + + TyKind::Ref(_, _, _) => match sc { + Scalar::Int(_) => unreachable!(), + + Scalar::Ptr(pointer, _) => { + let glob_alloc = self.env.tcx().global_alloc(pointer.provenance); + match glob_alloc { + middle::mir::interpret::GlobalAlloc::Static(did) => { + info!( + "Found static GlobalAlloc {:?} for Ref scalar {:?} at type {:?}", + did, sc, ty + ); + + let s = self.const_registry.get_static(did)?; + self.collected_statics.insert(did); + Ok(radium::Expr::Literal(radium::Literal::Loc(s.loc_name.clone()))) + }, + middle::mir::interpret::GlobalAlloc::Memory(alloc) => { + // TODO: this is needed + Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}", + glob_alloc, sc, ty + ), + }) + }, + _ => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}", + glob_alloc, sc, ty + ), + }), + } + }, + }, + + _ => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support layout for const value: (got: {:?})", + ty + ), + }), + } + } + + /// Translate a constant value from const evaluation. + fn translate_constant_value( + &mut self, + v: mir::interpret::ConstValue<'tcx>, + ty: Ty<'tcx>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + match v { + ConstValue::Scalar(sc) => self.translate_scalar(&sc, ty), + ConstValue::ZeroSized => { + // TODO are there more special cases we need to handle somehow? + match ty.kind() { + TyKind::FnDef(_, _) => { + info!("Translating ZST val for function call target: {:?}", ty); + self.translate_fn_def_use(ty) + }, + _ => Ok(radium::Expr::Literal(radium::Literal::ZST)), + } + }, + _ => { + // TODO: do we actually care about this case or is this just something that can + // appear as part of CTFE/MIRI? + Err(TranslationError::UnsupportedFeature { + description: format!("Unsupported Constant: ConstValue; {:?}", v), + }) + }, + } + } + + /// Translate a Constant to a `radium::Expr`. + pub(super) fn translate_constant( + &mut self, + constant: &Constant<'tcx>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + match constant.literal { + ConstantKind::Ty(v) => { + let const_ty = v.ty(); + + match v.kind() { + ConstKind::Value(v) => { + // this doesn't contain the necessary structure anymore. Need to reconstruct using the + // type. + match v.try_to_scalar() { + Some(sc) => self.translate_scalar(&sc, const_ty), + _ => Err(TranslationError::UnsupportedFeature { + description: format!("const value not supported: {:?}", v), + }), + } + }, + _ => Err(TranslationError::UnsupportedFeature { + description: "Unsupported ConstKind".to_owned(), + }), + } + }, + ConstantKind::Val(val, ty) => self.translate_constant_value(val, ty), + ConstantKind::Unevaluated(c, ty) => { + // call const evaluation + let param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id()); + match self.env.tcx().const_eval_resolve(param_env, c, None) { + Ok(res) => self.translate_constant_value(res, ty), + Err(e) => match e { + ErrorHandled::Reported(_) => Err(TranslationError::UnsupportedFeature { + description: "Cannot interpret constant".to_owned(), + }), + ErrorHandled::TooGeneric => Err(TranslationError::UnsupportedFeature { + description: "Const use is too generic".to_owned(), + }), + }, + } + }, + } + } +} diff --git a/rr_frontend/translation/src/body/translation/loops.rs b/rr_frontend/translation/src/body/translation/loops.rs new file mode 100644 index 0000000000000000000000000000000000000000..e6bcc1f0ce8c55da512f81e32e77c4653a4a9f4d --- /dev/null +++ b/rr_frontend/translation/src/body/translation/loops.rs @@ -0,0 +1,122 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use log::info; +use radium::coq; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; + +use super::TX; +use crate::base::*; + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Parse the attributes on spec closure `did` as loop annotations and add it as an invariant + /// to the generated code. + pub(super) fn parse_attributes_on_loop_spec_closure( + &self, + loop_head: BasicBlock, + did: Option<DefId>, + ) -> radium::LoopSpec { + // for now: just make invariants True. + + // need to do: + // - find out the locals in the right order, make parameter names for them. based on their type and + // initialization status, get the refinement type. + // - output/pretty-print this map when generating the typing proof of each function. [done] + // + should not be a separate definition, but rather a "set (.. := ...)" with a marker type so + // automation can find it. + + // representation of loop invariants: + // - introduce parameters for them. + + let mut rfn_binders = Vec::new(); + let prop_body = radium::IProp::True; + + // determine invariant on initialization: + // - we need this both for the refinement invariant (though this could be removed if we make uninit + // generic over the refinement) + // - in order to establish the initialization invariant in each loop iteration, since we don't have + // proper subtyping for uninit => maybe we could fix this too by making uninit variant in the + // refinement type? then we could have proper subtyping lemmas. + // + to bring it to the right refinement type initially, maybe have some automation / + // annotation + // TODO: consider changing it like that. + // + // Note that StorageDead will not help us for determining initialization/ making it invariant, since + // it only applies to full stack slots, not individual paths. one thing that makes it more + // complicated in the frontend: initialization may in practice also be path-dependent. + // - this does not cause issues with posing a too strong loop invariant, + // - but this poses an issue for annotations + // + // + + // get locals + for (_, name, ty) in &self.fn_locals { + // get the refinement type + let mut rfn_ty = ty.get_rfn_type(); + // wrap it in place_rfn, since we reason about places + rfn_ty = coq::term::Type::PlaceRfn(Box::new(rfn_ty)); + + // determine their initialization status + //let initialized = true; // TODO + // determine the actual refinement type for the current initialization status. + + let rfn_name = format!("r_{}", name); + rfn_binders.push(coq::binder::Binder::new(Some(rfn_name), rfn_ty)); + } + + // TODO what do we do about stuff connecting borrows? + if let Some(did) = did { + let attrs = self.env.get_attributes(did); + info!("attrs for loop {:?}: {:?}", loop_head, attrs); + } else { + info!("no attrs for loop {:?}", loop_head); + } + + let pred = radium::IPropPredicate::new(rfn_binders, prop_body); + radium::LoopSpec { + func_predicate: pred, + } + } + + /// Find the optional `DefId` of the closure giving the invariant for the loop with head `head_bb`. + pub(super) fn find_loop_spec_closure( + &self, + head_bb: BasicBlock, + ) -> Result<Option<DefId>, TranslationError<'tcx>> { + let bodies = self.proc.loop_info().get_loop_body(head_bb); + let basic_blocks = &self.proc.get_mir().basic_blocks; + + // we go in order through the bodies in order to not stumble upon an annotation for a + // nested loop! + for body in bodies { + // check that we did not go below a nested loop + if self.proc.loop_info().get_loop_head(*body) == Some(head_bb) { + // check the statements for an assignment + let data = basic_blocks.get(*body).unwrap(); + for stmt in &data.statements { + if let StatementKind::Assign(box (pl, _)) = stmt.kind { + if let Some(did) = self.is_spec_closure_local(pl.local)? { + return Ok(Some(did)); + } + } + } + } + } + + Ok(None) + } +} diff --git a/rr_frontend/translation/src/body/translation/mod.rs b/rr_frontend/translation/src/body/translation/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..8372bba3b2e7b6d4db714128358aa8e33b5c2105 --- /dev/null +++ b/rr_frontend/translation/src/body/translation/mod.rs @@ -0,0 +1,493 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +mod basicblock; +mod calls; +mod constants; +mod loops; +mod place; +mod rvalue; +mod terminator; + +use std::collections::{HashMap, HashSet}; + +use log::info; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; +use typed_arena::Arena; + +use crate::base::*; +use crate::body::checked_op_analysis::CheckedOpLocalAnalysis; +use crate::environment::borrowck::facts; +use crate::environment::polonius_info::PoloniusInfo; +use crate::environment::procedure::Procedure; +use crate::environment::{polonius_info, Environment}; +use crate::regions::inclusion_tracker::InclusionTracker; +use crate::traits::registry; +use crate::{base, consts, procedures, regions, types}; + +/// Struct that keeps track of all information necessary to translate a MIR Body to a `radium::Function`. +/// `'a` is the lifetime of the translator and ends after translation has finished. +/// `'def` is the lifetime of the generated code (the code may refer to struct defs). +/// `'tcx` is the lifetime of the rustc tctx. +pub struct TX<'a, 'def, 'tcx> { + env: &'def Environment<'tcx>, + /// registry of other procedures + procedure_registry: &'a procedures::Scope<'def>, + /// scope of used consts + const_registry: &'a consts::Scope<'def>, + /// trait registry + trait_registry: &'def registry::TR<'tcx, 'def>, + /// translator for types + ty_translator: types::LocalTX<'def, 'tcx>, + + /// this needs to be annotated with the right borrowck things + proc: &'def Procedure<'tcx>, + /// attributes on this function + attrs: &'a [&'a ast::ast::AttrItem], + /// polonius info for this function + info: &'a PoloniusInfo<'a, 'tcx>, + + /// maps locals to variable names + variable_map: HashMap<Local, String>, + + /// name of the return variable + return_name: String, + /// syntactic type of the thing to return + return_synty: radium::SynType, + /// all the other procedures used by this function, and: + /// (code_loc_parameter_name, spec_name, type_inst, syntype_of_all_args) + collected_procedures: HashMap<(DefId, types::GenericsKey<'tcx>), radium::UsedProcedure<'def>>, + /// used statics + collected_statics: HashSet<DefId>, + + /// tracking lifetime inclusions for the generation of lifetime inclusions + inclusion_tracker: InclusionTracker<'a, 'tcx>, + /// initial Polonius constraints that hold at the start of the function + initial_constraints: Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)>, + + /// local lifetimes: the LHS is the lifetime name, the RHS are the super lifetimes + local_lifetimes: Vec<(radium::specs::Lft, Vec<radium::specs::Lft>)>, + /// data structures for tracking which basic blocks still need to be translated + /// (we only translate the basic blocks which are actually reachable, in particular when + /// skipping unwinding) + bb_queue: Vec<BasicBlock>, + /// set of already processed blocks + processed_bbs: HashSet<BasicBlock>, + + /// map of loop heads to their optional spec closure defid + loop_specs: HashMap<BasicBlock, Option<DefId>>, + + /// relevant locals: (local, name, type) + fn_locals: Vec<(Local, String, radium::Type<'def>)>, + + /// result temporaries of checked ops that we rewrite + /// we assume that this place is typed at (result_type, bool) + /// and rewrite accesses to the first component to directly use the place, + /// while rewriting accesses to the second component to true. + /// TODO: once we handle panics properly, we should use a different translation. + /// NOTE: we only rewrite for uses, as these are the only places these are used. + checked_op_temporaries: HashMap<Local, Ty<'tcx>>, + + /// the Caesium function buildder + translated_fn: radium::FunctionBuilder<'def>, +} + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + pub fn new( + env: &'def Environment<'tcx>, + procedure_registry: &'a procedures::Scope<'def>, + const_registry: &'a consts::Scope<'def>, + trait_registry: &'def registry::TR<'tcx, 'def>, + ty_translator: types::LocalTX<'def, 'tcx>, + + proc: &'def Procedure<'tcx>, + attrs: &'a [&'a ast::ast::AttrItem], + info: &'a PoloniusInfo<'a, 'tcx>, + inputs: &[ty::Ty<'tcx>], + + mut inclusion_tracker: InclusionTracker<'a, 'tcx>, + mut translated_fn: radium::FunctionBuilder<'def>, + ) -> Result<Self, TranslationError<'tcx>> { + let body = proc.get_mir(); + + // analyze which locals are used for the result of checked-ops, because we will + // override their types (eliminating the tuples) + let mut checked_op_analyzer = CheckedOpLocalAnalysis::new(env.tcx(), body); + checked_op_analyzer.analyze(); + let checked_op_temporaries = checked_op_analyzer.results(); + + // map to translate between locals and the string names we use in radium:: + let mut variable_map: HashMap<Local, String> = HashMap::new(); + + let local_decls = &body.local_decls; + info!("Have {} local decls\n", local_decls.len()); + + let debug_info = &body.var_debug_info; + info!("using debug info: {:?}", debug_info); + + let mut return_synty = radium::SynType::Unit; // default + let mut fn_locals = Vec::new(); + let mut opt_return_name = + Err(TranslationError::UnknownError("could not find local for return value".to_owned())); + let mut used_names = HashSet::new(); + let mut arg_tys = Vec::new(); + + // go over local_decls and create the right radium:: stack layout + for (local, local_decl) in local_decls.iter_enumerated() { + let kind = body.local_kind(local); + let ty: Ty<'tcx>; + if let Some(rewritten_ty) = checked_op_temporaries.get(&local) { + ty = *rewritten_ty; + } else { + ty = local_decl.ty; + } + + // check if the type is of a spec fn -- in this case, we can skip this temporary + if let TyKind::Closure(id, _) = ty.kind() { + if procedure_registry.lookup_function_mode(*id).map_or(false, procedures::Mode::is_ignore) { + // this is a spec fn + info!("skipping local which has specfn closure type: {:?}", local); + continue; + } + } + + // type: + info!("Trying to translate type of local {local:?}: {:?}", ty); + let tr_ty = ty_translator.translate_type(ty)?; + let st = (&tr_ty).into(); + + let name = Self::make_local_name(body, local, &mut used_names); + variable_map.insert(local, name.clone()); + + fn_locals.push((local, name.clone(), tr_ty)); + + match kind { + LocalKind::Arg => { + translated_fn.code.add_argument(&name, st); + arg_tys.push(ty); + }, + //LocalKind::Var => translated_fn.code.add_local(&name, st), + LocalKind::Temp => translated_fn.code.add_local(&name, st), + LocalKind::ReturnPointer => { + return_synty = st.clone(); + translated_fn.code.add_local(&name, st); + opt_return_name = Ok(name); + }, + } + } + info!("radium name map: {:?}", variable_map); + // create the function + let return_name = opt_return_name?; + + // add lifetime parameters to the map + let initial_constraints = regions::init::get_initial_universal_arg_constraints( + info, + &mut inclusion_tracker, + inputs, + arg_tys.as_slice(), + ); + info!("initial constraints: {:?}", initial_constraints); + + Ok(Self { + env, + proc, + info, + variable_map, + translated_fn, + return_name, + return_synty, + inclusion_tracker, + collected_procedures: HashMap::new(), + procedure_registry, + attrs, + local_lifetimes: Vec::new(), + bb_queue: Vec::new(), + processed_bbs: HashSet::new(), + ty_translator, + loop_specs: HashMap::new(), + fn_locals, + checked_op_temporaries, + initial_constraints, + const_registry, + trait_registry, + collected_statics: HashSet::new(), + }) + } + + /// Main translation function that actually does the translation and returns a `radium::Function` + /// if successful. + pub fn translate( + mut self, + spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>, + ) -> Result<radium::Function<'def>, TranslationError<'tcx>> { + // add loop info + let loop_info = self.proc.loop_info(); + loop_info.dump_body_head(); + + // translate the function's basic blocks + let basic_blocks = &self.proc.get_mir().basic_blocks; + + // first translate the initial basic block; we add some additional annotations to the front + let initial_bb_idx = BasicBlock::from_u32(0); + if let Some(bb) = basic_blocks.get(initial_bb_idx) { + let mut translated_bb = self.translate_basic_block(initial_bb_idx, bb)?; + // push annotation for initial constraints that relate argument's place regions to universals + for (r1, r2) in &self.initial_constraints { + translated_bb = radium::Stmt::Annot { + a: radium::Annotation::CopyLftName( + self.ty_translator.format_atomic_region(r1), + self.ty_translator.format_atomic_region(r2), + ), + s: Box::new(translated_bb), + why: Some("initialization".to_owned()), + }; + } + self.translated_fn.code.add_basic_block(initial_bb_idx.as_usize(), translated_bb); + } else { + info!("No basic blocks"); + } + + while let Some(bb_idx) = self.bb_queue.pop() { + let bb = &basic_blocks[bb_idx]; + let translated_bb = self.translate_basic_block(bb_idx, bb)?; + self.translated_fn.code.add_basic_block(bb_idx.as_usize(), translated_bb); + } + + // assume that all generics are layoutable + { + let scope = self.ty_translator.scope.borrow(); + for ty in scope.generic_scope.tyvars() { + self.translated_fn.assume_synty_layoutable(radium::SynType::Literal(ty.syn_type)); + } + } + // assume that all used literals are layoutable + for g in self.ty_translator.scope.borrow().shim_uses.values() { + self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term()); + } + // assume that all used tuples are layoutable + for g in self.ty_translator.scope.borrow().tuple_uses.values() { + self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term()); + } + + // TODO: process self.loop_specs + // - collect the relevant bb -> def_id mappings for this function (so we can eventually generate the + // definition) + // - have a function that takes the def_id and then parses the attributes into a loop invariant + for (head, did) in &self.loop_specs { + let spec = self.parse_attributes_on_loop_spec_closure(*head, *did); + self.translated_fn.register_loop_invariant(head.as_usize(), spec); + } + + // generate dependencies on other procedures. + for used_proc in self.collected_procedures.values() { + self.translated_fn.require_function(used_proc.clone()); + } + + // generate dependencies on statics + for did in &self.collected_statics { + let s = self.const_registry.get_static(*did)?; + self.translated_fn.require_static(s.to_owned()); + } + + Ok(self.translated_fn.into_function(spec_arena)) + } +} + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Generate a string identifier for a Local. + /// Tries to find the Rust source code name of the local, otherwise simply enumerates. + /// `used_names` keeps track of the Rust source code names that have already been used. + fn make_local_name(mir_body: &Body<'tcx>, local: Local, used_names: &mut HashSet<String>) -> String { + if let Some(mir_name) = Self::find_name_for_local(mir_body, local, used_names) { + let name = base::strip_coq_ident(&mir_name); + used_names.insert(mir_name); + name + } else { + let mut name = "__".to_owned(); + name.push_str(&local.index().to_string()); + name + } + } + + /// Find a source name for a local of a MIR body, if possible. + fn find_name_for_local( + body: &mir::Body<'tcx>, + local: mir::Local, + used_names: &HashSet<String>, + ) -> Option<String> { + let debug_info = &body.var_debug_info; + + for dbg in debug_info { + let name = &dbg.name; + let val = &dbg.value; + if let VarDebugInfoContents::Place(l) = *val { + // make sure that the place projection is empty -- otherwise this might just + // refer to the capture of a closure + if let Some(this_local) = l.as_local() { + if this_local == local { + // around closures, multiple symbols may be mapped to the same name. + // To prevent this from happening, we check that the name hasn't been + // used already + // TODO: find better solution + if !used_names.contains(name.as_str()) { + return Some(name.as_str().to_owned()); + } + } + } + } else { + // is this case used when constant propagation happens during MIR construction? + } + } + + None + } + + fn format_region(&self, r: facts::Region) -> String { + let lft = self.info.mk_atomic_region(r); + self.ty_translator.format_atomic_region(&lft) + } + + /// Checks whether a place access descends below a reference. + fn check_place_below_reference(&self, place: &Place<'tcx>) -> bool { + if self.checked_op_temporaries.contains_key(&place.local) { + // temporaries are never below references + return false; + } + + for (pl, _) in place.iter_projections() { + // check if the current ty is a reference that we then descend under with proj + let cur_ty_kind = pl.ty(&self.proc.get_mir().local_decls, self.env.tcx()).ty.kind(); + if let TyKind::Ref(_, _, _) = cur_ty_kind { + return true; + } + } + + false + } + + /// Registers a drop shim for a particular type for the translation. + #[allow(clippy::unused_self)] + const fn register_drop_shim_for(&self, _ty: Ty<'tcx>) { + // TODO! + //let drop_in_place_did: DefId = search::try_resolve_did(self.env.tcx(), &["std", "ptr", + // "drop_in_place"]).unwrap(); + + //let x: ty::InstanceDef = ty::InstanceDef::DropGlue(drop_in_place_did, Some(ty)); + //let body: &'tcx mir::Body = self.env.tcx().mir_shims(x); + + //info!("Generated drop shim for {:?}", ty); + //Self::dump_body(body); + } + + /// Translate a goto-like jump to `target`. + fn translate_goto_like( + &mut self, + _loc: &Location, + target: BasicBlock, + ) -> Result<radium::Stmt, TranslationError<'tcx>> { + self.enqueue_basic_block(target); + let res_stmt = radium::Stmt::GotoBlock(target.as_usize()); + + let loop_info = self.proc.loop_info(); + if loop_info.is_loop_head(target) && !self.loop_specs.contains_key(&target) { + let spec_defid = self.find_loop_spec_closure(target)?; + self.loop_specs.insert(target, spec_defid); + } + + Ok(res_stmt) + } + + /// Enqueues a basic block for processing, if it has not already been processed, + /// and marks it as having been processed. + fn enqueue_basic_block(&mut self, bb: BasicBlock) { + if !self.processed_bbs.contains(&bb) { + self.bb_queue.push(bb); + self.processed_bbs.insert(bb); + } + } + + /// Prepend endlft annotations for dying loans to a statement. + fn prepend_endlfts<I>(&self, st: radium::Stmt, dying: I) -> radium::Stmt + where + I: ExactSizeIterator<Item = facts::Loan>, + { + let mut cont_stmt = st; + if dying.len() > 0 { + //info!("Dying at {:?}: {:?}", loc, dying); + for d in dying { + let lft = self.info.atomic_region_of_loan(d); + cont_stmt = radium::Stmt::Annot { + a: radium::Annotation::EndLft(self.ty_translator.format_atomic_region(&lft)), + s: Box::new(cont_stmt), + why: Some("endlft".to_owned()), + }; + } + } + cont_stmt + } + + /// Make a trivial place accessing `local`. + fn make_local_place(&self, local: Local) -> Place<'tcx> { + Place { + local, + projection: self.env.tcx().mk_place_elems(&[]), + } + } + + /// Get the type of a local in a body. + fn get_type_of_local(&self, local: Local) -> Result<Ty<'tcx>, TranslationError<'tcx>> { + self.proc + .get_mir() + .local_decls + .get(local) + .map(|decl| decl.ty) + .ok_or_else(|| TranslationError::UnknownVar(String::new())) + } + + /// Get the type of a place expression. + fn get_type_of_place(&self, pl: &Place<'tcx>) -> PlaceTy<'tcx> { + pl.ty(&self.proc.get_mir().local_decls, self.env.tcx()) + } + + /// Get the type of a const. + fn get_type_of_const(cst: &Constant<'tcx>) -> Ty<'tcx> { + match cst.literal { + ConstantKind::Ty(cst) => cst.ty(), + ConstantKind::Val(_, ty) | ConstantKind::Unevaluated(_, ty) => ty, + } + } + + /// Get the type of an operand. + fn get_type_of_operand(&self, op: &Operand<'tcx>) -> Ty<'tcx> { + op.ty(&self.proc.get_mir().local_decls, self.env.tcx()) + } + + /// Check if a local is used for a spec closure. + fn is_spec_closure_local(&self, l: Local) -> Result<Option<DefId>, TranslationError<'tcx>> { + // check if we should ignore this + let local_type = self.get_type_of_local(l)?; + + let TyKind::Closure(did, _) = local_type.kind() else { + return Ok(None); + }; + + Ok(self + .procedure_registry + .lookup_function_mode(*did) + .and_then(|m| m.is_ignore().then_some(*did))) + } +} diff --git a/rr_frontend/translation/src/body/translation/place.rs b/rr_frontend/translation/src/body/translation/place.rs new file mode 100644 index 0000000000000000000000000000000000000000..4625823b03f7283d1065e585743350956b7b383a --- /dev/null +++ b/rr_frontend/translation/src/body/translation/place.rs @@ -0,0 +1,124 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use log::info; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; + +use super::TX; +use crate::base::*; +use crate::types; + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Translate a place to a Caesium lvalue. + pub(super) fn translate_place( + &mut self, + pl: &Place<'tcx>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + // Get the type of the underlying local. We will use this to + // get the necessary layout information for dereferencing + let mut cur_ty = self.get_type_of_local(pl.local).map(PlaceTy::from_ty)?; + + let local_name = self + .variable_map + .get(&pl.local) + .ok_or_else(|| TranslationError::UnknownVar(format!("{:?}", pl.local)))?; + + let mut acc_expr = radium::Expr::Var(local_name.to_string()); + + // iterate in evaluation order + for it in pl.projection { + match &it { + ProjectionElem::Deref => { + // use the type of the dereferencee + let st = self.ty_translator.translate_type_to_syn_type(cur_ty.ty)?; + acc_expr = radium::Expr::Deref { + ot: st.into(), + e: Box::new(acc_expr), + }; + }, + ProjectionElem::Field(f, _) => { + // `t` is the type of the field we are accessing! + let lit = self.ty_translator.generate_structlike_use(cur_ty.ty, cur_ty.variant_index)?; + // TODO: does not do the right thing for accesses to fields of zero-sized objects. + let struct_sls = lit.map_or(radium::SynType::Unit, |x| x.generate_raw_syn_type_term()); + let name = self.ty_translator.translator.get_field_name_of( + *f, + cur_ty.ty, + cur_ty.variant_index.map(abi::VariantIdx::as_usize), + )?; + + acc_expr = radium::Expr::FieldOf { + e: Box::new(acc_expr), + name, + sls: struct_sls.to_string(), + }; + }, + ProjectionElem::Index(_v) => { + //TODO + return Err(TranslationError::UnsupportedFeature { + description: "places: implement index access".to_owned(), + }); + }, + ProjectionElem::ConstantIndex { .. } => { + //TODO + return Err(TranslationError::UnsupportedFeature { + description: "places: implement const index access".to_owned(), + }); + }, + ProjectionElem::Subslice { .. } => { + return Err(TranslationError::UnsupportedFeature { + description: "places: implement subslicing".to_owned(), + }); + }, + ProjectionElem::Downcast(_, variant_idx) => { + info!("Downcast of ty {:?} to {:?}", cur_ty, variant_idx); + if let ty::TyKind::Adt(def, args) = cur_ty.ty.kind() { + if def.is_enum() { + let enum_use = self.ty_translator.generate_enum_use(*def, args.iter())?; + let els = enum_use.generate_raw_syn_type_term(); + + let variant_name = types::TX::get_variant_name_of(cur_ty.ty, *variant_idx)?; + + acc_expr = radium::Expr::EnumData { + els: els.to_string(), + variant: variant_name, + e: Box::new(acc_expr), + } + } else { + return Err(TranslationError::UnknownError( + "places: ADT downcasting on non-enum type".to_owned(), + )); + } + } else { + return Err(TranslationError::UnknownError( + "places: ADT downcasting on non-enum type".to_owned(), + )); + } + }, + ProjectionElem::OpaqueCast(_) => { + return Err(TranslationError::UnsupportedFeature { + description: "places: implement opaque casts".to_owned(), + }); + }, + }; + // update cur_ty + cur_ty = cur_ty.projection_ty(self.env.tcx(), it); + } + info!("translating place {:?} to {:?}", pl, acc_expr); + Ok(acc_expr) + } +} diff --git a/rr_frontend/translation/src/body/translation/rvalue.rs b/rr_frontend/translation/src/body/translation/rvalue.rs new file mode 100644 index 0000000000000000000000000000000000000000..0ea174c498930b39ef7ed484d1ac00da486fa79c --- /dev/null +++ b/rr_frontend/translation/src/body/translation/rvalue.rs @@ -0,0 +1,624 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use log::{info, trace}; +use radium::coq; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::index::IndexVec; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; + +use super::TX; +use crate::base::*; +use crate::regions; + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Translate an aggregate expression. + fn translate_aggregate( + &mut self, + loc: Location, + kind: &mir::AggregateKind<'tcx>, + op: &IndexVec<abi::FieldIdx, mir::Operand<'tcx>>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + // translate operands + let mut translated_ops: Vec<radium::Expr> = Vec::new(); + let mut operand_types: Vec<Ty<'tcx>> = Vec::new(); + + for o in op { + let translated_o = self.translate_operand(o, true)?; + let type_of_o = self.get_type_of_operand(o); + translated_ops.push(translated_o); + operand_types.push(type_of_o); + } + + match *kind { + mir::AggregateKind::Tuple => { + if operand_types.is_empty() { + // translate to unit literal + return Ok(radium::Expr::Literal(radium::Literal::ZST)); + } + + let struct_use = self.ty_translator.generate_tuple_use(operand_types.iter().copied())?; + let sl = struct_use.generate_raw_syn_type_term(); + let initializers: Vec<_> = + translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect(); + + Ok(radium::Expr::StructInitE { + sls: coq::term::App::new_lhs(sl.to_string()), + components: initializers, + }) + }, + + mir::AggregateKind::Adt(did, variant, args, ..) => { + // get the adt def + let adt_def: ty::AdtDef<'tcx> = self.env.tcx().adt_def(did); + + if adt_def.is_struct() { + let variant = adt_def.variant(variant); + let struct_use = self.ty_translator.generate_struct_use(variant.def_id, args)?; + + let Some(struct_use) = struct_use else { + // if not, it's replaced by unit + return Ok(radium::Expr::Literal(radium::Literal::ZST)); + }; + + let sl = struct_use.generate_raw_syn_type_term(); + let initializers: Vec<_> = translated_ops + .into_iter() + .zip(variant.fields.iter()) + .map(|(o, field)| (field.name.to_string(), o)) + .collect(); + + return Ok(radium::Expr::StructInitE { + sls: coq::term::App::new_lhs(sl.to_string()), + components: initializers, + }); + } + + if adt_def.is_enum() { + let variant_def = adt_def.variant(variant); + + let struct_use = + self.ty_translator.generate_enum_variant_use(variant_def.def_id, args)?; + let sl = struct_use.generate_raw_syn_type_term(); + + let initializers: Vec<_> = translated_ops + .into_iter() + .zip(variant_def.fields.iter()) + .map(|(o, field)| (field.name.to_string(), o)) + .collect(); + + let variant_e = radium::Expr::StructInitE { + sls: coq::term::App::new_lhs(sl.to_string()), + components: initializers, + }; + + let enum_use = self.ty_translator.generate_enum_use(adt_def, args)?; + let els = enum_use.generate_raw_syn_type_term(); + + info!("generating enum annotation for type {:?}", enum_use); + let ty = radium::RustType::of_type(&radium::Type::Literal(enum_use)); + let variant_name = variant_def.name.to_string(); + + return Ok(radium::Expr::EnumInitE { + els: coq::term::App::new_lhs(els.to_string()), + variant: variant_name, + ty, + initializer: Box::new(variant_e), + }); + } + + // TODO + Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support aggregate rvalue for other ADTs (got: {kind:?}, {op:?})" + ), + }) + }, + mir::AggregateKind::Closure(def, _args) => { + trace!("Translating Closure aggregate value for {:?}", def); + + // We basically translate this to a tuple + if operand_types.is_empty() { + // translate to unit literal + return Ok(radium::Expr::Literal(radium::Literal::ZST)); + } + + let struct_use = self.ty_translator.generate_tuple_use(operand_types.iter().copied())?; + let sl = struct_use.generate_raw_syn_type_term(); + + let initializers: Vec<_> = + translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect(); + + Ok(radium::Expr::StructInitE { + sls: coq::term::App::new_lhs(sl.to_string()), + components: initializers, + }) + }, + + _ => { + // TODO + Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support this kind of aggregate rvalue (got: {kind:?}, {op:?})" + ), + }) + }, + } + } + + fn translate_cast( + &mut self, + kind: mir::CastKind, + op: &mir::Operand<'tcx>, + to_ty: ty::Ty<'tcx>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + let op_ty = self.get_type_of_operand(op); + let op_st = self.ty_translator.translate_type_to_syn_type(op_ty)?; + let op_ot = op_st.into(); + + let translated_op = self.translate_operand(op, true)?; + + let target_st = self.ty_translator.translate_type_to_syn_type(to_ty)?; + let target_ot = target_st.into(); + + match kind { + mir::CastKind::PointerCoercion(x) => { + match x { + ty::adjustment::PointerCoercion::MutToConstPointer => { + // this is a NOP in our model + Ok(translated_op) + }, + + ty::adjustment::PointerCoercion::ArrayToPointer + | ty::adjustment::PointerCoercion::ClosureFnPointer(_) + | ty::adjustment::PointerCoercion::ReifyFnPointer + | ty::adjustment::PointerCoercion::UnsafeFnPointer + | ty::adjustment::PointerCoercion::Unsize => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support this kind of pointer coercion (got: {kind:?})" + ), + }), + } + }, + + mir::CastKind::DynStar => Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support dyn* cast".to_owned(), + }), + + mir::CastKind::IntToInt => { + // Cast integer to integer + Ok(radium::Expr::UnOp { + o: radium::Unop::Cast(target_ot), + ot: op_ot, + e: Box::new(translated_op), + }) + }, + + mir::CastKind::IntToFloat => Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support int-to-float cast".to_owned(), + }), + + mir::CastKind::FloatToInt => Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support float-to-int cast".to_owned(), + }), + + mir::CastKind::FloatToFloat => Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support float-to-float cast".to_owned(), + }), + + mir::CastKind::PtrToPtr => { + match (op_ty.kind(), to_ty.kind()) { + (TyKind::RawPtr(_), TyKind::RawPtr(_)) => { + // Casts between raw pointers are NOPs for us + Ok(translated_op) + }, + + _ => { + // TODO: any other cases we should handle? + Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support ptr-to-ptr cast (got: {kind:?}, {op:?}, {to_ty:?})" + ), + }) + }, + } + }, + + mir::CastKind::FnPtrToPtr => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support fnptr-to-ptr cast (got: {kind:?}, {op:?}, {to_ty:?})" + ), + }), + + mir::CastKind::Transmute => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support transmute cast (got: {kind:?}, {op:?}, {to_ty:?})" + ), + }), + + mir::CastKind::PointerExposeAddress => { + // Cast pointer to integer + Ok(radium::Expr::UnOp { + o: radium::Unop::Cast(target_ot), + ot: radium::OpType::Ptr, + e: Box::new(translated_op), + }) + }, + + mir::CastKind::PointerFromExposedAddress => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support this kind of cast (got: {kind:?}, {op:?}, {to_ty:?})" + ), + }), + } + } + + /// Translate an operand. + /// This will either generate an lvalue (in case of Move or Copy) or an rvalue (in most cases + /// of Constant). How this is used depends on the context. (e.g., Use of an integer constant + /// does not typecheck, and produces a stuck program). + pub(super) fn translate_operand( + &mut self, + op: &Operand<'tcx>, + to_rvalue: bool, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + match op { + // In Caesium: typed_place needs deref (not use) for place accesses. + // use is used top-level to convert an lvalue to an rvalue, which is why we use it here. + Operand::Copy(place) | Operand::Move(place) => { + // check if this goes to a temporary of a checked op + let place_kind = if self.checked_op_temporaries.contains_key(&place.local) { + assert!(place.projection.len() == 1); + + let ProjectionElem::Field(f, _0) = place.projection[0] else { + unreachable!("invariant violation for access to checked op temporary"); + }; + + if f.index() != 0 { + // make this a constant false -- our semantics directly checks for overflows + // and otherwise throws UB. + return Ok(radium::Expr::Literal(radium::Literal::Bool(false))); + } + + // access to the result of the op + self.make_local_place(place.local) + } else { + *place + }; + + let translated_place = self.translate_place(&place_kind)?; + let ty = self.get_type_of_place(place); + + let st = self.ty_translator.translate_type_to_syn_type(ty.ty)?; + + if to_rvalue { + Ok(radium::Expr::Use { + ot: st.into(), + e: Box::new(translated_place), + }) + } else { + Ok(translated_place) + } + }, + Operand::Constant(constant) => { + // TODO: possibly need different handling of the rvalue flag + // when this also handles string literals etc. + return self.translate_constant(constant.as_ref()); + }, + } + } + + /// Translates an Rvalue. + pub(super) fn translate_rvalue( + &mut self, + loc: Location, + rval: &Rvalue<'tcx>, + ) -> Result<radium::Expr, TranslationError<'tcx>> { + match rval { + Rvalue::Use(op) => { + // converts an lvalue to an rvalue + self.translate_operand(op, true) + }, + + Rvalue::Ref(region, bk, pl) => { + let translated_pl = self.translate_place(pl)?; + let translated_bk = TX::translate_borrow_kind(*bk)?; + let ty_annot = self.get_type_annotation_for_borrow(*bk, pl)?; + + if let Some(loan) = self.info.get_optional_loan_at_location(loc) { + let atomic_region = self.info.atomic_region_of_loan(loan); + let lft = self.ty_translator.format_atomic_region(&atomic_region); + Ok(radium::Expr::Borrow { + lft, + bk: translated_bk, + ty: ty_annot, + e: Box::new(translated_pl), + }) + } else { + info!("Didn't find loan at {:?}: {:?}; region {:?}", loc, rval, region); + let region = regions::region_to_region_vid(*region); + let lft = self.format_region(region); + + Ok(radium::Expr::Borrow { + lft, + bk: translated_bk, + ty: ty_annot, + e: Box::new(translated_pl), + }) + } + }, + + Rvalue::AddressOf(mt, pl) => { + let translated_pl = self.translate_place(pl)?; + let translated_mt = TX::translate_mutability(*mt); + + Ok(radium::Expr::AddressOf { + mt: translated_mt, + e: Box::new(translated_pl), + }) + }, + + Rvalue::BinaryOp(op, operands) => { + let e1 = &operands.as_ref().0; + let e2 = &operands.as_ref().1; + + let e1_ty = self.get_type_of_operand(e1); + let e2_ty = self.get_type_of_operand(e2); + let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?; + let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?; + + let translated_e1 = self.translate_operand(e1, true)?; + let translated_e2 = self.translate_operand(e2, true)?; + let translated_op = self.translate_binop(*op, &operands.as_ref().0, &operands.as_ref().1)?; + + Ok(radium::Expr::BinOp { + o: translated_op, + ot1: e1_st.into(), + ot2: e2_st.into(), + e1: Box::new(translated_e1), + e2: Box::new(translated_e2), + }) + }, + + Rvalue::CheckedBinaryOp(op, operands) => { + let e1 = &operands.as_ref().0; + let e2 = &operands.as_ref().1; + + let e1_ty = self.get_type_of_operand(e1); + let e2_ty = self.get_type_of_operand(e2); + let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?; + let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?; + + let translated_e1 = self.translate_operand(e1, true)?; + let translated_e2 = self.translate_operand(e2, true)?; + let translated_op = TX::translate_checked_binop(*op)?; + + Ok(radium::Expr::BinOp { + o: translated_op, + ot1: e1_st.into(), + ot2: e2_st.into(), + e1: Box::new(translated_e1), + e2: Box::new(translated_e2), + }) + }, + + Rvalue::UnaryOp(op, operand) => { + let translated_e1 = self.translate_operand(operand, true)?; + let e1_ty = self.get_type_of_operand(operand); + let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?; + let translated_op = TX::translate_unop(*op, e1_ty)?; + + Ok(radium::Expr::UnOp { + o: translated_op, + ot: e1_st.into(), + e: Box::new(translated_e1), + }) + }, + + Rvalue::NullaryOp(op, _ty) => { + // TODO: SizeOf + Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support nullary ops (AlignOf, Sizeof)" + .to_owned(), + }) + }, + + Rvalue::Discriminant(pl) => { + let ty = self.get_type_of_place(pl); + let translated_pl = self.translate_place(pl)?; + info!("getting discriminant of {:?} at type {:?}", pl, ty); + + let ty::TyKind::Adt(adt_def, args) = ty.ty.kind() else { + return Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support discriminant accesses on non-enum types ({:?}, got {:?})", + rval, ty.ty + ), + }); + }; + + let enum_use = self.ty_translator.generate_enum_use(*adt_def, args.iter())?; + let els = enum_use.generate_raw_syn_type_term(); + + let discriminant_acc = radium::Expr::EnumDiscriminant { + els: els.to_string(), + e: Box::new(translated_pl), + }; + + // need to do a load from this place + let it = ty.ty.discriminant_ty(self.env.tcx()); + let translated_it = self.ty_translator.translate_type(it)?; + + let radium::Type::Int(translated_it) = translated_it else { + return Err(TranslationError::UnknownError(format!( + "type of discriminant is not an integer type {:?}", + it + ))); + }; + + let ot = radium::OpType::Int(translated_it); + + Ok(radium::Expr::Use { + ot, + e: Box::new(discriminant_acc), + }) + }, + + Rvalue::Aggregate(kind, op) => self.translate_aggregate(loc, kind.as_ref(), op), + + Rvalue::Cast(kind, op, to_ty) => self.translate_cast(*kind, op, *to_ty), + + Rvalue::CopyForDeref(_) + | Rvalue::Len(..) + | Rvalue::Repeat(..) + | Rvalue::ThreadLocalRef(..) + | Rvalue::ShallowInitBox(_, _) => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support this kind of rvalue (got: {:?})", + rval + ), + }), + } + } + + /// Get the type to annotate a borrow with. + fn get_type_annotation_for_borrow( + &self, + bk: BorrowKind, + pl: &Place<'tcx>, + ) -> Result<Option<radium::RustType>, TranslationError<'tcx>> { + let BorrowKind::Mut { .. } = bk else { + return Ok(None); + }; + + let ty = self.get_type_of_place(pl); + + // For borrows, we can safely ignore the downcast type -- we cannot borrow a particularly variant + let translated_ty = self.ty_translator.translate_type(ty.ty)?; + let annot_ty = radium::RustType::of_type(&translated_ty); + + Ok(Some(annot_ty)) + } + + /// Translate binary operators. + /// We need access to the operands, too, to handle the offset operator and get the right + /// Caesium layout annotation. + fn translate_binop( + &self, + op: BinOp, + e1: &Operand<'tcx>, + _e2: &Operand<'tcx>, + ) -> Result<radium::Binop, TranslationError<'tcx>> { + match op { + BinOp::Add | BinOp::AddUnchecked => Ok(radium::Binop::Add), + BinOp::Sub | BinOp::SubUnchecked => Ok(radium::Binop::Sub), + BinOp::Mul | BinOp::MulUnchecked => Ok(radium::Binop::Mul), + BinOp::Div => Ok(radium::Binop::Div), + BinOp::Rem => Ok(radium::Binop::Mod), + + BinOp::BitXor => Ok(radium::Binop::BitXor), + BinOp::BitAnd => Ok(radium::Binop::BitAnd), + BinOp::BitOr => Ok(radium::Binop::BitOr), + BinOp::Shl | BinOp::ShlUnchecked => Ok(radium::Binop::Shl), + BinOp::Shr | BinOp::ShrUnchecked => Ok(radium::Binop::Shr), + + BinOp::Eq => Ok(radium::Binop::Eq), + BinOp::Lt => Ok(radium::Binop::Lt), + BinOp::Le => Ok(radium::Binop::Le), + BinOp::Ne => Ok(radium::Binop::Ne), + BinOp::Ge => Ok(radium::Binop::Ge), + BinOp::Gt => Ok(radium::Binop::Gt), + + BinOp::Offset => { + // we need to get the layout of the thing we're offsetting + // try to get the type of e1. + let e1_ty = self.get_type_of_operand(e1); + let off_ty = TX::get_offset_ty(e1_ty)?; + let st = self.ty_translator.translate_type_to_syn_type(off_ty)?; + let ly = st.into(); + Ok(radium::Binop::PtrOffset(ly)) + }, + } + } + + /// Get the inner type of a type to which we can apply the offset operator. + fn get_offset_ty(ty: Ty<'tcx>) -> Result<Ty<'tcx>, TranslationError<'tcx>> { + match ty.kind() { + TyKind::Array(t, _) | TyKind::Slice(t) | TyKind::Ref(_, t, _) => Ok(*t), + TyKind::RawPtr(tm) => Ok(tm.ty), + _ => Err(TranslationError::UnknownError(format!("cannot take offset of {}", ty))), + } + } + + /// Translate checked binary operators. + /// We need access to the operands, too, to handle the offset operator and get the right + /// Caesium layout annotation. + fn translate_checked_binop(op: BinOp) -> Result<radium::Binop, TranslationError<'tcx>> { + match op { + BinOp::Add => Ok(radium::Binop::CheckedAdd), + BinOp::Sub => Ok(radium::Binop::CheckedSub), + BinOp::Mul => Ok(radium::Binop::CheckedMul), + BinOp::Shl => Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support checked Shl".to_owned(), + }), + BinOp::Shr => Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support checked Shr".to_owned(), + }), + _ => Err(TranslationError::UnknownError( + "unexpected checked binop that is not Add, Sub, Mul, Shl, or Shr".to_owned(), + )), + } + } + + /// Translate unary operators. + fn translate_unop(op: UnOp, ty: Ty<'tcx>) -> Result<radium::Unop, TranslationError<'tcx>> { + match op { + UnOp::Not => match ty.kind() { + ty::TyKind::Bool => Ok(radium::Unop::NotBool), + ty::TyKind::Int(_) | ty::TyKind::Uint(_) => Ok(radium::Unop::NotInt), + _ => Err(TranslationError::UnknownError( + "application of UnOp::Not to non-{Int, Bool}".to_owned(), + )), + }, + UnOp::Neg => Ok(radium::Unop::Neg), + } + } + + /// Translate a `BorrowKind`. + fn translate_borrow_kind(kind: BorrowKind) -> Result<radium::BorKind, TranslationError<'tcx>> { + match kind { + BorrowKind::Shared => Ok(radium::BorKind::Shared), + BorrowKind::Shallow => { + // TODO: figure out what to do with this + // arises in match lowering + Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does currently not support shallow borrows".to_owned(), + }) + }, + BorrowKind::Mut { .. } => { + // TODO: handle two-phase borrows? + Ok(radium::BorKind::Mutable) + }, + } + } + + const fn translate_mutability(mt: Mutability) -> radium::Mutability { + match mt { + Mutability::Mut => radium::Mutability::Mut, + Mutability::Not => radium::Mutability::Shared, + } + } +} diff --git a/rr_frontend/translation/src/body/translation/terminator.rs b/rr_frontend/translation/src/body/translation/terminator.rs new file mode 100644 index 0000000000000000000000000000000000000000..ca749be7a0f50adee360245bb0fc008a2aab3ef2 --- /dev/null +++ b/rr_frontend/translation/src/body/translation/terminator.rs @@ -0,0 +1,242 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use std::collections::HashMap; + +use log::{info, trace, warn}; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{ + BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, + Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, + TerminatorKind, UnOp, VarDebugInfoContents, +}; +use rr_rustc_interface::middle::ty::fold::TypeFolder; +use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind}; +use rr_rustc_interface::middle::{mir, ty}; +use rr_rustc_interface::{abi, ast, middle}; + +use super::TX; +use crate::base::*; +use crate::environment::borrowck::facts; +use crate::search; + +impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> { + /// Check if a call goes to `std::rt::begin_panic` + fn is_call_destination_panic(&mut self, func: &Operand) -> bool { + let Operand::Constant(box c) = func else { + return false; + }; + + let ConstantKind::Val(_, ty) = c.literal else { + return false; + }; + + let TyKind::FnDef(did, _) = ty.kind() else { + return false; + }; + + if let Some(panic_id_std) = + search::try_resolve_did(self.env.tcx(), &["std", "panicking", "begin_panic"]) + { + if panic_id_std == *did { + return true; + } + } else { + warn!("Failed to determine DefId of std::panicking::begin_panic"); + } + + if let Some(panic_id_core) = search::try_resolve_did(self.env.tcx(), &["core", "panicking", "panic"]) + { + if panic_id_core == *did { + return true; + } + } else { + warn!("Failed to determine DefId of core::panicking::panic"); + } + + false + } + + /// Translate a terminator. + /// We pass the dying loans during this terminator. They need to be added at the right + /// intermediate point. + pub(super) fn translate_terminator( + &mut self, + term: &Terminator<'tcx>, + loc: Location, + dying_loans: Vec<facts::Loan>, + ) -> Result<radium::Stmt, TranslationError<'tcx>> { + match &term.kind { + TerminatorKind::Goto { target } => self.translate_goto_like(&loc, *target), + + TerminatorKind::Call { + func, + args, + destination, + target, + .. + } => { + trace!("translating Call {:?}", term); + if self.is_call_destination_panic(func) { + info!("Replacing call to std::panicking::begin_panic with Stuck"); + return Ok(radium::Stmt::Stuck); + } + + self.translate_function_call(func, args, destination, *target, loc, dying_loans.as_slice()) + }, + + TerminatorKind::Return => { + // TODO: this requires additional handling for reborrows + + // read from the return place + // Is this semantics accurate wrt what the intended MIR semantics is? + // Possibly handle this differently by making the first argument of a function a dedicated + // return place? See also discussion at https://github.com/rust-lang/rust/issues/71117 + let stmt = radium::Stmt::Return(radium::Expr::Use { + ot: (&self.return_synty).into(), + e: Box::new(radium::Expr::Var(self.return_name.clone())), + }); + + // TODO is this right? + Ok(self.prepend_endlfts(stmt, dying_loans.into_iter())) + }, + + //TerminatorKind::Abort => { + //res_stmt = radium::Stmt::Stuck; + //res_stmt = self.prepend_endlfts(res_stmt, dying_loans.into_iter()); + //}, + TerminatorKind::SwitchInt { discr, targets } => { + let operand = self.translate_operand(discr, true)?; + let all_targets: &[BasicBlock] = targets.all_targets(); + + if self.get_type_of_operand(discr).is_bool() { + // we currently special-case this as Caesium has a built-in if and this is more + // convenient to handle for the type-checker + + // implementation detail: the first index is the `false` branch, the second the + // `true` branch + let true_target = all_targets[1]; + let false_target = all_targets[0]; + + let true_branch = self.translate_goto_like(&loc, true_target)?; + let false_branch = self.translate_goto_like(&loc, false_target)?; + + let stmt = radium::Stmt::If { + e: operand, + ot: radium::OpType::Bool, + s1: Box::new(true_branch), + s2: Box::new(false_branch), + }; + + // TODO: is this right? + return Ok(self.prepend_endlfts(stmt, dying_loans.into_iter())); + } + + //info!("switchint: {:?}", term.kind); + let operand = self.translate_operand(discr, true)?; + let ty = self.get_type_of_operand(discr); + + let mut target_map: HashMap<u128, usize> = HashMap::new(); + let mut translated_targets: Vec<radium::Stmt> = Vec::new(); + + for (idx, (tgt, bb)) in targets.iter().enumerate() { + let bb: BasicBlock = bb; + let translated_target = self.translate_goto_like(&loc, bb)?; + + target_map.insert(tgt, idx); + translated_targets.push(translated_target); + } + + let translated_default = self.translate_goto_like(&loc, targets.otherwise())?; + // TODO: need to put endlfts infront of gotos? + + let translated_ty = self.ty_translator.translate_type(ty)?; + let radium::Type::Int(it) = translated_ty else { + return Err(TranslationError::UnknownError( + "SwitchInt switching on non-integer type".to_owned(), + )); + }; + + Ok(radium::Stmt::Switch { + e: operand, + it, + index_map: target_map, + bs: translated_targets, + def: Box::new(translated_default), + }) + }, + + TerminatorKind::Assert { + cond, + expected, + target, + .. + } => { + // this translation gets stuck on failure + let cond_translated = self.translate_operand(cond, true)?; + let comp = radium::Expr::BinOp { + o: radium::Binop::Eq, + ot1: radium::OpType::Bool, + ot2: radium::OpType::Bool, + e1: Box::new(cond_translated), + e2: Box::new(radium::Expr::Literal(radium::Literal::Bool(*expected))), + }; + + let stmt = self.translate_goto_like(&loc, *target)?; + + // TODO: should we really have this? + let stmt = self.prepend_endlfts(stmt, dying_loans.into_iter()); + + Ok(radium::Stmt::AssertS { + e: comp, + s: Box::new(stmt), + }) + }, + + TerminatorKind::Drop { place, target, .. } => { + let ty = self.get_type_of_place(place); + self.register_drop_shim_for(ty.ty); + + let place_translated = self.translate_place(place)?; + let _drope = radium::Expr::DropE(Box::new(place_translated)); + + let stmt = self.translate_goto_like(&loc, *target)?; + + Ok(self.prepend_endlfts(stmt, dying_loans.into_iter())) + + //res_stmt = radium::Stmt::ExprS { e: drope, s: Box::new(res_stmt)}; + }, + + // just a goto for our purposes + TerminatorKind::FalseEdge { real_target, .. } + // this is just a virtual edge for the borrowchecker, we can translate this to a normal goto + | TerminatorKind::FalseUnwind { real_target, .. } => { + self.translate_goto_like(&loc, *real_target) + }, + + TerminatorKind::Unreachable => Ok(radium::Stmt::Stuck), + + TerminatorKind::UnwindResume => Err(TranslationError::Unimplemented { + description: "implement UnwindResume".to_owned(), + }), + + TerminatorKind::UnwindTerminate(_) => Err(TranslationError::Unimplemented { + description: "implement UnwindTerminate".to_owned(), + }), + + TerminatorKind::GeneratorDrop + | TerminatorKind::InlineAsm { .. } + | TerminatorKind::Yield { .. } => Err(TranslationError::UnsupportedFeature { + description: format!( + "RefinedRust does currently not support this kind of terminator (got: {:?})", + term + ), + }), + } + } +} diff --git a/rr_frontend/translation/src/consts.rs b/rr_frontend/translation/src/consts.rs new file mode 100644 index 0000000000000000000000000000000000000000..42727d726f48ae7e5fce57aa9405cbe43cba0aba --- /dev/null +++ b/rr_frontend/translation/src/consts.rs @@ -0,0 +1,49 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use std::collections::{btree_map, BTreeMap, HashMap, HashSet}; + +use rr_rustc_interface::hir::def_id::DefId; + +use crate::base::TranslationError; + +/// Scope of consts that are available +pub struct Scope<'def> { + // statics are explicitly declared + statics: HashMap<DefId, radium::StaticMeta<'def>>, + // const places are constants that lie in a static memory segment because they are referred to + // by-ref + const_places: HashMap<DefId, radium::ConstPlaceMeta<'def>>, +} + +impl<'def> Scope<'def> { + /// Create a new const scope. + pub fn empty() -> Self { + Self { + statics: HashMap::new(), + const_places: HashMap::new(), + } + } + + /// Register a static + pub fn register_static(&mut self, did: DefId, meta: radium::StaticMeta<'def>) { + self.statics.insert(did, meta); + } + + /// Register a const place + pub fn register_const_place(&mut self, did: DefId, meta: radium::ConstPlaceMeta<'def>) { + self.const_places.insert(did, meta); + } + + pub fn get_static<'tcx>(&self, did: DefId) -> Result<&radium::StaticMeta<'def>, TranslationError<'tcx>> { + self.statics.get(&did).ok_or_else(|| { + TranslationError::UnknownError(format!( + "Did not find a registered static for did {did:?}; registered: {:?}", + self.statics + )) + }) + } +} diff --git a/rr_frontend/translation/src/environment/mod.rs b/rr_frontend/translation/src/environment/mod.rs index e46f0606ad3237987e21a8c102b24eebdf101c86..65f89976b13f642d27d13a05e5a97b1575420d03 100644 --- a/rr_frontend/translation/src/environment/mod.rs +++ b/rr_frontend/translation/src/environment/mod.rs @@ -32,13 +32,13 @@ use rr_rustc_interface::span::Span; use rr_rustc_interface::trait_selection::infer::{InferCtxtExt, TyCtxtInferExt}; use rr_rustc_interface::{ast, span}; +use crate::attrs; use crate::data::ProcedureDefId; use crate::environment::borrowck::facts; use crate::environment::collect_closure_defs_visitor::CollectClosureDefsVisitor; use crate::environment::collect_prusti_spec_visitor::CollectPrustiSpecVisitor; use crate::environment::loops::{PlaceAccess, PlaceAccessKind, ProcedureLoops}; use crate::environment::procedure::{BasicBlockIndex, Procedure}; -use crate::utils; /// Facade to the Rust compiler. // #[derive(Copy, Clone)] @@ -211,7 +211,7 @@ impl<'tcx> Environment<'tcx> { pub fn has_tool_attribute(&self, def_id: ProcedureDefId, name: &str) -> bool { let tcx = self.tcx(); // TODO: migrate to get_attrs - utils::has_tool_attr(tcx.get_attrs_unchecked(def_id), name) + attrs::has_tool_attr(tcx.get_attrs_unchecked(def_id), name) } /// Find whether the procedure has a particular `[tool]::<name>` attribute; if so, return its @@ -219,14 +219,14 @@ impl<'tcx> Environment<'tcx> { pub fn get_tool_attribute<'a>(&'a self, def_id: ProcedureDefId, name: &str) -> Option<&'a ast::AttrArgs> { let tcx = self.tcx(); // TODO: migrate to get_attrs - utils::get_tool_attr(tcx.get_attrs_unchecked(def_id), name) + attrs::get_tool_attr(tcx.get_attrs_unchecked(def_id), name) } /// Check whether the procedure has any `[tool]` attribute. pub fn has_any_tool_attribute(&self, def_id: ProcedureDefId) -> bool { let tcx = self.tcx(); // TODO: migrate to get_attrs - utils::has_any_tool_attr(tcx.get_attrs_unchecked(def_id)) + attrs::has_any_tool_attr(tcx.get_attrs_unchecked(def_id)) } /// Get the attributes of an item (e.g. procedures). @@ -245,11 +245,11 @@ impl<'tcx> Environment<'tcx> { F: for<'a> Fn(&'a ast::ast::AttrItem) -> bool, { let attrs = self.get_attributes(did); - let mut filtered_attrs = utils::filter_tool_attrs(attrs); + let mut filtered_attrs = attrs::filter_for_tool(attrs); // also add selected attributes from the surrounding impl if let Some(impl_did) = self.tcx().impl_of_method(did) { let impl_attrs = self.get_attributes(impl_did); - let filtered_impl_attrs = utils::filter_tool_attrs(impl_attrs); + let filtered_impl_attrs = attrs::filter_for_tool(impl_attrs); filtered_attrs.extend(filtered_impl_attrs.into_iter().filter(|x| propagate_from_impl(x))); } diff --git a/rr_frontend/translation/src/function_body.rs b/rr_frontend/translation/src/function_body.rs deleted file mode 100644 index ea6749283b24661dc8600f505685fef18eb96865..0000000000000000000000000000000000000000 --- a/rr_frontend/translation/src/function_body.rs +++ /dev/null @@ -1,4511 +0,0 @@ -// © 2023, The RefinedRust Developers and Contributors -// -// This Source Code Form is subject to the terms of the BSD-3-clause License. -// If a copy of the BSD-3-clause license was not distributed with this -// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. - -use std::collections::{btree_map, BTreeMap, HashMap, HashSet}; -use std::fmt::Write; - -use log::{info, trace, warn}; -use radium::coq; -use rr_rustc_interface::hir::def_id::DefId; -use rr_rustc_interface::middle::mir::interpret::{ConstValue, ErrorHandled, Scalar}; -use rr_rustc_interface::middle::mir::tcx::PlaceTy; -use rr_rustc_interface::middle::mir::{ - BasicBlock, BasicBlockData, BinOp, Body, BorrowKind, Constant, ConstantKind, Local, LocalKind, Location, - Mutability, NonDivergingIntrinsic, Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, - TerminatorKind, UnOp, VarDebugInfoContents, -}; -use rr_rustc_interface::middle::ty::fold::TypeFolder; -use rr_rustc_interface::middle::ty::{ConstKind, Ty, TyKind, TypeFoldable}; -use rr_rustc_interface::middle::{mir, ty}; -use rr_rustc_interface::{abi, ast, middle}; -use typed_arena::Arena; - -use crate::arg_folder::*; -use crate::base::*; -use crate::checked_op_analysis::CheckedOpLocalAnalysis; -use crate::environment::borrowck::facts; -use crate::environment::polonius_info::PoloniusInfo; -use crate::environment::procedure::Procedure; -use crate::environment::{dump_borrowck_info, polonius_info, Environment}; -use crate::inclusion_tracker::*; -use crate::spec_parsers::parse_utils::ParamLookup; -use crate::spec_parsers::verbose_function_spec_parser::{ - ClosureMetaInfo, FunctionRequirements, FunctionSpecParser, VerboseFunctionSpecParser, -}; -use crate::trait_registry::TraitRegistry; -use crate::type_translator::*; -use crate::tyvars::*; -use crate::{traits, utils}; - -/// Mangle a name by appending type parameters to it. -pub fn mangle_name_with_tys(method_name: &str, args: &[Ty<'_>]) -> String { - // TODO: maybe come up with some better way to generate names - let mut mangled_name = method_name.to_owned(); - for arg in args { - mangled_name.push_str(format!("_{}", arg).as_str()); - } - strip_coq_ident(&mangled_name) -} - -/// Mangle a name by appending generic args to it. -pub fn mangle_name_with_args(name: &str, args: &[ty::GenericArg<'_>]) -> String { - let mut mangled_base = name.to_owned(); - for arg in args { - if let ty::GenericArgKind::Type(ty) = arg.unpack() { - write!(mangled_base, "_{}", strip_coq_ident(&format!("{ty}"))).unwrap(); - } - } - mangled_base -} - -/// Get the syntypes of function arguments for a procedure call. -pub fn get_arg_syntypes_for_procedure_call<'tcx, 'def>( - tcx: ty::TyCtxt<'tcx>, - ty_translator: &LocalTypeTranslator<'def, 'tcx>, - callee_did: DefId, - ty_params: &[ty::GenericArg<'tcx>], -) -> Result<Vec<radium::SynType>, TranslationError<'tcx>> { - let caller_did = ty_translator.get_proc_did(); - - // Get the type of the callee, fully instantiated - let full_ty: ty::EarlyBinder<Ty<'tcx>> = tcx.type_of(callee_did); - let full_ty = full_ty.instantiate(tcx, ty_params); - - // We create a dummy scope in order to make the lifetime lookups succeed, since we only want - // syntactic types here. - // Since we do the substitution of the generics above, we should translate generics and - // traits in the caller's scope. - let scope = ty_translator.scope.borrow(); - let param_env: ty::ParamEnv<'tcx> = tcx.param_env(scope.did); - let callee_state = CalleeTranslationState::new(¶m_env, &scope.generic_scope); - let mut dummy_state = TranslationStateInner::CalleeTranslation(callee_state); - - let mut syntypes = Vec::new(); - match full_ty.kind() { - ty::TyKind::FnDef(_, _) => { - let sig = full_ty.fn_sig(tcx); - for ty in sig.inputs().skip_binder() { - let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?; - syntypes.push(st); - } - }, - ty::TyKind::Closure(_, args) => { - let clos_args = args.as_closure(); - let sig = clos_args.sig(); - let pre_sig = sig.skip_binder(); - // we also need to add the closure argument here - - let tuple_ty = clos_args.tupled_upvars_ty(); - match clos_args.kind() { - ty::ClosureKind::Fn | ty::ClosureKind::FnMut => { - syntypes.push(radium::SynType::Ptr); - }, - ty::ClosureKind::FnOnce => { - let st = - ty_translator.translator.translate_type_to_syn_type(tuple_ty, &mut dummy_state)?; - syntypes.push(st); - }, - } - for ty in pre_sig.inputs() { - let st = ty_translator.translator.translate_type_to_syn_type(*ty, &mut dummy_state)?; - syntypes.push(st); - } - }, - _ => unimplemented!(), - } - - Ok(syntypes) -} - -/** - * Tracks the functions we translated and the Coq names they are available under. - * To account for dependencies between functions, we may register translated names before we have - * actually translated the function. - */ - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum ProcedureMode { - Prove, - OnlySpec, - TrustMe, - Shim, - Ignore, -} - -impl ProcedureMode { - pub fn is_prove(self) -> bool { - self == Self::Prove - } - - pub fn is_only_spec(self) -> bool { - self == Self::OnlySpec - } - - pub fn is_trust_me(self) -> bool { - self == Self::TrustMe - } - - pub fn is_shim(self) -> bool { - self == Self::Shim - } - - pub fn is_ignore(self) -> bool { - self == Self::Ignore - } - - pub fn needs_proof(self) -> bool { - self == Self::Prove - } - - pub fn needs_def(self) -> bool { - self == Self::Prove || self == Self::TrustMe - } - - pub fn needs_spec(self) -> bool { - self == Self::Prove || self == Self::TrustMe || self == Self::OnlySpec - } -} - -#[derive(Clone)] -pub struct ProcedureMeta { - spec_name: String, - name: String, - mode: ProcedureMode, -} - -impl ProcedureMeta { - pub const fn new(spec_name: String, name: String, mode: ProcedureMode) -> Self { - Self { - spec_name, - name, - mode, - } - } - - pub fn get_spec_name(&self) -> &str { - &self.spec_name - } - - pub fn get_name(&self) -> &str { - &self.name - } - - pub const fn get_mode(&self) -> ProcedureMode { - self.mode - } -} - -pub struct ProcedureScope<'def> { - /// maps the defid to (code_name, spec_name, name) - name_map: BTreeMap<DefId, ProcedureMeta>, - /// track the actually translated functions - translated_functions: BTreeMap<DefId, radium::Function<'def>>, - /// track the functions with just a specification (rr::only_spec) - specced_functions: BTreeMap<DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>, -} - -impl<'def> ProcedureScope<'def> { - pub fn new() -> Self { - Self { - name_map: BTreeMap::new(), - translated_functions: BTreeMap::new(), - specced_functions: BTreeMap::new(), - } - } - - /// Lookup the meta information of a function. - pub fn lookup_function(&self, did: DefId) -> Option<ProcedureMeta> { - self.name_map.get(&did).cloned() - } - - /// Lookup a translated function spec - pub fn lookup_function_spec( - &self, - did: DefId, - ) -> Option<&'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> { - if let Some(translated_fn) = self.translated_functions.get(&did) { - Some(translated_fn.spec) - } else if let Some(translated_spec) = self.specced_functions.get(&did) { - Some(translated_spec) - } else { - None - } - } - - /// Lookup the Coq spec name for a function. - pub fn lookup_function_spec_name(&self, did: DefId) -> Option<&str> { - self.name_map.get(&did).map(ProcedureMeta::get_spec_name) - } - - /// Lookup the name for a function. - pub fn lookup_function_mangled_name(&self, did: DefId) -> Option<&str> { - self.name_map.get(&did).map(ProcedureMeta::get_name) - } - - /// Lookup the mode for a function. - pub fn lookup_function_mode(&self, did: DefId) -> Option<ProcedureMode> { - self.name_map.get(&did).map(ProcedureMeta::get_mode) - } - - /// Register a function. - pub fn register_function<'tcx>( - &mut self, - did: DefId, - meta: ProcedureMeta, - ) -> Result<(), TranslationError<'tcx>> { - if self.name_map.insert(did, meta).is_some() { - Err(TranslationError::ProcedureRegistry(format!( - "function for defid {:?} has already been registered", - did - ))) - } else { - Ok(()) - } - } - - /// Provide the code for a translated function. - pub fn provide_translated_function(&mut self, did: DefId, trf: radium::Function<'def>) { - let meta = &self.name_map[&did]; - assert!(meta.get_mode().needs_def()); - assert!(self.translated_functions.insert(did, trf).is_none()); - } - - /// Provide the specification for an `only_spec` function. - pub fn provide_specced_function( - &mut self, - did: DefId, - spec: &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, - ) { - let meta = &self.name_map[&did]; - assert!(meta.get_mode().is_only_spec()); - assert!(self.specced_functions.insert(did, spec).is_none()); - } - - /// Iterate over the functions we have generated code for. - pub fn iter_code(&self) -> btree_map::Iter<'_, DefId, radium::Function<'def>> { - self.translated_functions.iter() - } - - /// Iterate over the functions we have generated only specs for. - pub fn iter_only_spec( - &self, - ) -> btree_map::Iter<'_, DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> { - self.specced_functions.iter() - } -} - -/// Scope of consts that are available -pub struct ConstScope<'def> { - // statics are explicitly declared - statics: HashMap<DefId, radium::StaticMeta<'def>>, - // const places are constants that lie in a static memory segment because they are referred to - // by-ref - const_places: HashMap<DefId, radium::ConstPlaceMeta<'def>>, -} - -impl<'def> ConstScope<'def> { - /// Create a new const scope. - pub fn empty() -> Self { - Self { - statics: HashMap::new(), - const_places: HashMap::new(), - } - } - - /// Register a static - pub fn register_static(&mut self, did: DefId, meta: radium::StaticMeta<'def>) { - self.statics.insert(did, meta); - } - - /// Register a const place - pub fn register_const_place(&mut self, did: DefId, meta: radium::ConstPlaceMeta<'def>) { - self.const_places.insert(did, meta); - } -} - -// solve the constraints for the new_regions -// we first identify what kinds of constraints these new regions are subject to -#[derive(Debug)] -enum CallRegionKind { - // this is just an intersection of local regions. - Intersection(HashSet<Region>), - // this is equal to a specific region - EqR(Region), -} - -struct CallRegions { - pub early_regions: Vec<Region>, - pub late_regions: Vec<Region>, - pub classification: HashMap<Region, CallRegionKind>, -} - -/// Information we compute when calling a function from another function. -/// Determines how to specialize the callee's generics in our spec assumption. -struct AbstractedGenerics<'def> { - /// the scope with new generics to quantify over for the function's specialized spec - scope: radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>>, - /// instantiations for the specialized spec hint - callee_lft_param_inst: Vec<radium::Lft>, - callee_ty_param_inst: Vec<radium::Type<'def>>, - /// instantiations for the function use - fn_lft_param_inst: Vec<radium::Lft>, - fn_ty_param_inst: Vec<radium::Type<'def>>, -} - -/// A scope of trait attributes mapping to Coq names to be used in a function's spec -struct TraitSpecNameScope { - attrs: HashMap<String, String>, -} - -/// When translating a function spec where attributes of a trait are in scope, -/// we create a wrapper to lookup references to the trait's attributes when parsing function specs. -struct FunctionSpecScope<'a, T> { - generics: &'a T, - attrs: &'a TraitSpecNameScope, -} -impl<'a, T: ParamLookup> ParamLookup for FunctionSpecScope<'a, T> { - fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> { - self.generics.lookup_ty_param(path) - } - - fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> { - self.generics.lookup_lft(lft) - } - - fn lookup_literal(&self, path: &[&str]) -> Option<&str> { - if path.len() == 1 { - if let Some(lit) = self.attrs.attrs.get(path[0]) { - return Some(lit); - } - } - self.generics.lookup_literal(path) - } -} - -pub struct FunctionTranslator<'a, 'def, 'tcx> { - env: &'def Environment<'tcx>, - /// this needs to be annotated with the right borrowck things - proc: &'def Procedure<'tcx>, - /// the Caesium function buildder - translated_fn: radium::FunctionBuilder<'def>, - /// tracking lifetime inclusions for the generation of lifetime inclusions - inclusion_tracker: InclusionTracker<'a, 'tcx>, - - /// registry of other procedures - procedure_registry: &'a ProcedureScope<'def>, - /// registry of consts - const_registry: &'a ConstScope<'def>, - /// attributes on this function - attrs: &'a [&'a ast::ast::AttrItem], - /// polonius info for this function - info: &'a PoloniusInfo<'a, 'tcx>, - /// translator for types - ty_translator: LocalTypeTranslator<'def, 'tcx>, - /// trait registry in the current scope - trait_registry: &'def TraitRegistry<'tcx, 'def>, - /// argument types (from the signature, with generics substituted) - inputs: Vec<Ty<'tcx>>, -} - -impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> { - /// Generate a spec for a trait method. - pub fn spec_for_trait_method( - env: &'def Environment<'tcx>, - proc_did: DefId, - name: &str, - spec_name: &str, - attrs: &'a [&'a ast::ast::AttrItem], - ty_translator: &'def TypeTranslator<'def, 'tcx>, - trait_registry: &'def TraitRegistry<'tcx, 'def>, - ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> { - let mut translated_fn = radium::FunctionBuilder::new(name, spec_name); - - let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc_did); - let ty = ty.instantiate_identity(); - // substs are the generic args of this function (including lifetimes) - // sig is the function signature - let sig = match ty.kind() { - TyKind::FnDef(_def, _args) => { - assert!(ty.is_fn()); - let sig = ty.fn_sig(env.tcx()); - sig - }, - _ => panic!("can not handle non-fns"), - }; - info!("Function signature: {:?}", sig); - - let params = Self::get_proc_ty_params(env.tcx(), proc_did); - info!("Function generic args: {:?}", params); - - let (inputs, output, region_substitution) = Self::process_lifetimes_of_args(env, params, sig); - info!("inputs: {:?}, output: {:?}", inputs, output); - - let (type_scope, trait_attrs) = Self::setup_local_scope( - env, - ty_translator, - trait_registry, - proc_did, - params.as_slice(), - &mut translated_fn, - region_substitution, - false, - None, - )?; - let type_translator = LocalTypeTranslator::new(ty_translator, type_scope); - - // TODO: add universal constraints (ideally in setup_local_scope) - - let spec_builder = Self::process_attrs( - attrs, - &type_translator, - &mut translated_fn, - &trait_attrs, - inputs.as_slice(), - output, - )?; - translated_fn.add_function_spec_from_builder(spec_builder); - - translated_fn.try_into().map_err(TranslationError::AttributeError) - } - - /// Create a translation instance for a closure. - pub fn new_closure( - env: &'def Environment<'tcx>, - meta: &ProcedureMeta, - proc: Procedure<'tcx>, - attrs: &'a [&'a ast::ast::AttrItem], - ty_translator: &'def TypeTranslator<'def, 'tcx>, - trait_registry: &'def TraitRegistry<'tcx, 'def>, - proc_registry: &'a ProcedureScope<'def>, - const_registry: &'a ConstScope<'def>, - ) -> Result<Self, TranslationError<'tcx>> { - let mut translated_fn = radium::FunctionBuilder::new(&meta.name, &meta.spec_name); - - // TODO can we avoid the leak - let proc: &'def Procedure = &*Box::leak(Box::new(proc)); - let body = proc.get_mir(); - Self::dump_body(body); - - let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id()); - let ty = ty.instantiate_identity(); - let closure_kind = match ty.kind() { - TyKind::Closure(_def, closure_args) => { - assert!(ty.is_closure()); - let clos = closure_args.as_closure(); - clos.kind() - }, - _ => panic!("can not handle non-closures"), - }; - - let local_decls = &body.local_decls; - let closure_arg = local_decls.get(Local::from_usize(1)).unwrap(); - let closure_ty; - - match closure_kind { - ty::ClosureKind::Fn => { - if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() { - closure_ty = ty; - } else { - unreachable!(); - } - }, - ty::ClosureKind::FnMut => { - if let ty::TyKind::Ref(_, ty, _) = closure_arg.ty.kind() { - closure_ty = ty; - } else { - unreachable!("unexpected type {:?}", closure_arg.ty); - } - }, - ty::ClosureKind::FnOnce => { - closure_ty = &closure_arg.ty; - }, - } - - let parent_args; - let mut capture_regions = Vec::new(); - let sig; - let captures; - let upvars_tys; - if let ty::TyKind::Closure(did, closure_args) = closure_ty.kind() { - let clos = closure_args.as_closure(); - - let tupled_upvars_tys = clos.tupled_upvars_ty(); - upvars_tys = clos.upvar_tys(); - parent_args = clos.parent_args(); - let unnormalized_sig = clos.sig(); - sig = unnormalized_sig; - info!("closure sig: {:?}", sig); - - captures = env.tcx().closure_captures(did.as_local().unwrap()); - info!("Closure has captures: {:?}", captures); - - // find additional lifetime parameters - for (place, ty) in captures.iter().zip(clos.upvar_tys().iter()) { - if place.region.is_some() { - // find region from ty - if let ty::TyKind::Ref(region, _, _) = ty.kind() { - capture_regions.push(*region); - } - } - } - info!("Closure capture regions: {:?}", capture_regions); - - info!("Closure arg upvar_tys: {:?}", tupled_upvars_tys); - info!("Function signature: {:?}", sig); - info!("Closure generic args: {:?}", parent_args); - } else { - unreachable!(); - } - - match PoloniusInfo::new(env, proc) { - Ok(info) => { - // TODO: avoid leak - let info: &'def PoloniusInfo = &*Box::leak(Box::new(info)); - - // For closures, we only handle the parent's args here! - // TODO: do we need to do something special for the parent's late-bound region - // parameters? - // TODO: should we always take the lifetime parameters? - let params = parent_args; - //proc.get_type_params(); - info!("Function generic args: {:?}", params); - - // dump graphviz files - // color code: red: dying loan, pink: becoming a zombie; green: is zombie - if rrconfig::dump_borrowck_info() { - dump_borrowck_info(env, proc.get_id(), info); - } - - let (tupled_inputs, output, mut region_substitution) = - Self::process_lifetimes_of_args(env, params, sig); - - // Process the lifetime parameters that come from the captures - for r in capture_regions { - // TODO: problem: we're introducing inconsistent names here. - if let ty::RegionKind::ReVar(r) = r.kind() { - let lft = info.mk_atomic_region(r); - let name = format_atomic_region_direct(&lft, None); - region_substitution.region_names.insert(r, name); - // TODO: add to region_substitution? - } else { - unreachable!(); - } - } - // also add the lifetime for the outer reference - let mut maybe_outer_lifetime = None; - if let ty::TyKind::Ref(r, _, _) = closure_arg.ty.kind() { - if let ty::RegionKind::ReVar(r) = r.kind() { - // We need to do some hacks here to find the right Polonius region: - // `r` is the non-placeholder region that the variable gets, but we are - // looking for the corresponding placeholder region - let r2 = Self::find_placeholder_region_for(r, info).unwrap(); - - info!("using lifetime {:?} for closure universal", r2); - let lft = info.mk_atomic_region(r2); - let name = format_atomic_region_direct(&lft, None); - region_substitution.region_names.insert(r2, name); - - maybe_outer_lifetime = Some(r2); - } else { - unreachable!(); - } - } - - // detuple the inputs - assert!(tupled_inputs.len() == 1); - let input_tuple_ty = tupled_inputs[0]; - let mut inputs = Vec::new(); - - // push the closure as the first argument - /* - if let Some(r2) = maybe_outer_lifetime { - // in this case, we need to patch the region first - if let ty::TyKind::Ref(_, ty, m) = closure_arg.ty.kind() { - let new_region = ty::Region::new_var(env.tcx(), r2); - inputs.push(env.tcx().mk_ty_from_kind(ty::TyKind::Ref(new_region, *ty, *m))); - } - } - else { - inputs.push(closure_arg.ty); - } - */ - - if let ty::TyKind::Tuple(args) = input_tuple_ty.kind() { - inputs.extend(args.iter()); - } - - info!("inputs({}): {:?}, output: {:?}", inputs.len(), inputs, output); - - let mut inclusion_tracker = InclusionTracker::new(info); - // add placeholder subsets - let initial_point: facts::Point = facts::Point { - location: BasicBlock::from_u32(0).start_location(), - typ: facts::PointType::Start, - }; - for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset { - inclusion_tracker.add_static_inclusion( - *r1, - *r2, - info.interner.get_point_index(&initial_point), - ); - } - - let (type_scope, trait_attrs) = Self::setup_local_scope( - env, - ty_translator, - trait_registry, - proc.get_id(), - params, - &mut translated_fn, - region_substitution, - true, - Some(info), - )?; - let type_translator = LocalTypeTranslator::new(ty_translator, type_scope); - - let mut t = Self { - env, - proc, - info, - translated_fn, - inclusion_tracker, - procedure_registry: proc_registry, - attrs, - ty_translator: type_translator, - trait_registry, - const_registry, - inputs: inputs.clone(), - }; - - // compute meta information needed to generate the spec - let mut translated_upvars_types = Vec::new(); - for ty in upvars_tys { - let translated_ty = t.ty_translator.translate_type(ty)?; - translated_upvars_types.push(translated_ty); - } - let meta; - { - let scope = t.ty_translator.scope.borrow(); - meta = ClosureMetaInfo { - kind: closure_kind, - captures, - capture_tys: &translated_upvars_types, - closure_lifetime: maybe_outer_lifetime - .map(|x| scope.lifetime_scope.lookup_region(x).unwrap().to_owned()), - }; - } - - // process attributes - t.process_closure_attrs(&trait_attrs, &inputs, output, meta)?; - Ok(t) - }, - Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))), - } - } - - /// Translate the body of a function. - pub fn new( - env: &'def Environment<'tcx>, - meta: &ProcedureMeta, - proc: Procedure<'tcx>, - attrs: &'a [&'a ast::ast::AttrItem], - ty_translator: &'def TypeTranslator<'def, 'tcx>, - trait_registry: &'def TraitRegistry<'tcx, 'def>, - proc_registry: &'a ProcedureScope<'def>, - const_registry: &'a ConstScope<'def>, - ) -> Result<Self, TranslationError<'tcx>> { - let mut translated_fn = radium::FunctionBuilder::new(&meta.name, &meta.spec_name); - - // TODO can we avoid the leak - let proc: &'def Procedure = &*Box::leak(Box::new(proc)); - - let body = proc.get_mir(); - Self::dump_body(body); - - let ty: ty::EarlyBinder<Ty<'tcx>> = env.tcx().type_of(proc.get_id()); - let ty = ty.instantiate_identity(); - // substs are the generic args of this function (including lifetimes) - // sig is the function signature - let sig = match ty.kind() { - TyKind::FnDef(_def, _args) => { - assert!(ty.is_fn()); - let sig = ty.fn_sig(env.tcx()); - sig - }, - _ => panic!("can not handle non-fns"), - }; - - info!("Function signature: {:?}", sig); - - match PoloniusInfo::new(env, proc) { - Ok(info) => { - // TODO: avoid leak - let info: &'def PoloniusInfo = &*Box::leak(Box::new(info)); - - let params = Self::get_proc_ty_params(env.tcx(), proc.get_id()); - info!("Function generic args: {:?}", params); - - // dump graphviz files - // color code: red: dying loan, pink: becoming a zombie; green: is zombie - if rrconfig::dump_borrowck_info() { - dump_borrowck_info(env, proc.get_id(), info); - } - - let (inputs, output, region_substitution) = Self::process_lifetimes_of_args(env, params, sig); - info!("inputs: {:?}, output: {:?}", inputs, output); - - let mut inclusion_tracker = InclusionTracker::new(info); - // add placeholder subsets - let initial_point: facts::Point = facts::Point { - location: BasicBlock::from_u32(0).start_location(), - typ: facts::PointType::Start, - }; - for (r1, r2) in &info.borrowck_in_facts.known_placeholder_subset { - inclusion_tracker.add_static_inclusion( - *r1, - *r2, - info.interner.get_point_index(&initial_point), - ); - } - - let (type_scope, trait_attrs) = Self::setup_local_scope( - env, - ty_translator, - trait_registry, - proc.get_id(), - params.as_slice(), - &mut translated_fn, - region_substitution, - true, - Some(info), - )?; - let type_translator = LocalTypeTranslator::new(ty_translator, type_scope); - - // process attributes - let mut spec_builder = Self::process_attrs( - attrs, - &type_translator, - &mut translated_fn, - &trait_attrs, - inputs.as_slice(), - output, - )?; - - let mut t = Self { - env, - proc, - info, - translated_fn, - inclusion_tracker, - procedure_registry: proc_registry, - attrs, - ty_translator: type_translator, - trait_registry, - const_registry, - inputs: inputs.clone(), - }; - - if spec_builder.has_spec() { - // add universal constraints - let universal_constraints = t.get_relevant_universal_constraints(); - info!("univeral constraints: {:?}", universal_constraints); - for (lft1, lft2) in universal_constraints { - spec_builder.add_lifetime_constraint(lft1, lft2); - } - - t.translated_fn.add_function_spec_from_builder(spec_builder); - } else { - let spec = t.make_trait_instance_spec()?; - if let Some(spec) = spec { - t.translated_fn.add_trait_function_spec(spec); - } else { - return Err(TranslationError::AttributeError( - "No valid specification provided".to_owned(), - )); - } - } - - Ok(t) - }, - Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))), - } - } - - pub fn translate( - mut self, - spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>, - ) -> Result<radium::Function<'def>, TranslationError<'tcx>> { - let body = self.proc.get_mir(); - - // analyze which locals are used for the result of checked-ops, because we will - // override their types (eliminating the tuples) - let mut checked_op_analyzer = CheckedOpLocalAnalysis::new(self.env.tcx(), body); - checked_op_analyzer.analyze(); - let checked_op_locals = checked_op_analyzer.results(); - - // map to translate between locals and the string names we use in radium:: - let mut radium_name_map: HashMap<Local, String> = HashMap::new(); - - let local_decls = &body.local_decls; - info!("Have {} local decls\n", local_decls.len()); - - let debug_info = &body.var_debug_info; - info!("using debug info: {:?}", debug_info); - - let mut return_synty = radium::SynType::Unit; // default - let mut fn_locals = Vec::new(); - let mut opt_return_name = - Err(TranslationError::UnknownError("could not find local for return value".to_owned())); - let mut used_names = HashSet::new(); - let mut arg_tys = Vec::new(); - - // go over local_decls and create the right radium:: stack layout - for (local, local_decl) in local_decls.iter_enumerated() { - let kind = body.local_kind(local); - let ty: Ty<'tcx>; - if let Some(rewritten_ty) = checked_op_locals.get(&local) { - ty = *rewritten_ty; - } else { - ty = local_decl.ty; - } - - // check if the type is of a spec fn -- in this case, we can skip this temporary - if let TyKind::Closure(id, _) = ty.kind() { - if self.procedure_registry.lookup_function_mode(*id).map_or(false, ProcedureMode::is_ignore) { - // this is a spec fn - info!("skipping local which has specfn closure type: {:?}", local); - continue; - } - } - - // type: - info!("Trying to translate type of local {local:?}: {:?}", ty); - let tr_ty = self.ty_translator.translate_type(ty)?; - let st = (&tr_ty).into(); - - let name = Self::make_local_name(body, local, &mut used_names); - radium_name_map.insert(local, name.clone()); - - fn_locals.push((local, name.clone(), tr_ty)); - - match kind { - LocalKind::Arg => { - self.translated_fn.code.add_argument(&name, st); - arg_tys.push(ty); - }, - //LocalKind::Var => translated_fn.code.add_local(&name, st), - LocalKind::Temp => self.translated_fn.code.add_local(&name, st), - LocalKind::ReturnPointer => { - return_synty = st.clone(); - self.translated_fn.code.add_local(&name, st); - opt_return_name = Ok(name); - }, - } - } - info!("radium name map: {:?}", radium_name_map); - // create the function - let return_name = opt_return_name?; - - // add lifetime parameters to the map - let inputs2 = self.inputs.clone(); - let initial_constraints = - self.get_initial_universal_arg_constraints(inputs2.as_slice(), arg_tys.as_slice()); - info!("initial constraints: {:?}", initial_constraints); - - let translator = BodyTranslator { - env: self.env, - proc: self.proc, - info: self.info, - variable_map: radium_name_map, - translated_fn: self.translated_fn, - return_name, - return_synty, - inclusion_tracker: self.inclusion_tracker, - collected_procedures: HashMap::new(), - procedure_registry: self.procedure_registry, - attrs: self.attrs, - local_lifetimes: Vec::new(), - bb_queue: Vec::new(), - processed_bbs: HashSet::new(), - ty_translator: self.ty_translator, - loop_specs: HashMap::new(), - fn_locals, - checked_op_temporaries: checked_op_locals, - const_registry: self.const_registry, - trait_registry: self.trait_registry, - collected_statics: HashSet::new(), - }; - translator.translate(initial_constraints, spec_arena) - } -} - -impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> { - /// Get type parameters of the given procedure. - fn get_proc_ty_params(tcx: ty::TyCtxt<'tcx>, did: DefId) -> ty::GenericArgsRef<'tcx> { - let ty = tcx.type_of(did).instantiate_identity(); - match ty.kind() { - ty::TyKind::FnDef(_, params) => params, - ty::TyKind::Closure(_, closure_args) => { - assert!(ty.is_closure()); - let clos = closure_args.as_closure(); - let parent_args = clos.parent_args(); - - // TODO: this doesn't include lifetime parameters specific to this closure... - tcx.mk_args(parent_args) - }, - _ => panic!("Procedure::new called on a procedure whose type is not TyKind::FnDef!"), - } - } - - fn process_lifetimes_of_args( - env: &Environment<'tcx>, - params: &[ty::GenericArg<'tcx>], - sig: ty::Binder<'tcx, ty::FnSig<'tcx>>, - ) -> (Vec<ty::Ty<'tcx>>, ty::Ty<'tcx>, EarlyLateRegionMap) { - // a mapping of Polonius region IDs to names - let mut universal_lifetimes = BTreeMap::new(); - let mut lifetime_names = HashMap::new(); - - let mut region_substitution_early: Vec<Option<ty::RegionVid>> = Vec::new(); - - // we create a substitution that replaces early bound regions with their Polonius - // region variables - let mut subst_early_bounds: Vec<ty::GenericArg<'tcx>> = Vec::new(); - let mut num_early_bounds = 0; - for a in params { - if let ty::GenericArgKind::Lifetime(r) = a.unpack() { - // skip over 0 = static - let next_id = ty::RegionVid::from_u32(num_early_bounds + 1); - let revar = ty::Region::new_var(env.tcx(), next_id); - num_early_bounds += 1; - subst_early_bounds.push(ty::GenericArg::from(revar)); - - region_substitution_early.push(Some(next_id)); - - match *r { - ty::RegionKind::ReEarlyBound(r) => { - let name = strip_coq_ident(r.name.as_str()); - universal_lifetimes.insert(next_id, format!("ulft_{}", name)); - lifetime_names.insert(name, next_id); - }, - _ => { - universal_lifetimes.insert(next_id, format!("ulft_{}", num_early_bounds)); - }, - } - } else { - subst_early_bounds.push(*a); - region_substitution_early.push(None); - } - } - let subst_early_bounds = env.tcx().mk_args(&subst_early_bounds); - - info!("Computed early region map {region_substitution_early:?}"); - - // add names for late bound region variables - let mut num_late_bounds = 0; - let mut region_substitution_late = Vec::new(); - for b in sig.bound_vars() { - let next_id = ty::RegionVid::from_u32(num_early_bounds + num_late_bounds + 1); - - let ty::BoundVariableKind::Region(r) = b else { - continue; - }; - - region_substitution_late.push(next_id); - - match r { - ty::BoundRegionKind::BrNamed(_, sym) => { - let mut region_name = strip_coq_ident(sym.as_str()); - if region_name == "_" { - region_name = next_id.as_usize().to_string(); - universal_lifetimes.insert(next_id, format!("ulft_{}", region_name)); - } else { - universal_lifetimes.insert(next_id, format!("ulft_{}", region_name)); - lifetime_names.insert(region_name, next_id); - } - }, - ty::BoundRegionKind::BrAnon(_) => { - universal_lifetimes.insert(next_id, format!("ulft_{}", next_id.as_usize())); - }, - _ => (), - } - - num_late_bounds += 1; - } - - // replace late-bound region variables by re-enumerating them in the same way as the MIR - // type checker does (that this happens in the same way is important to make the names - // line up!) - let mut next_index = num_early_bounds + 1; // skip over one additional due to static - let mut folder = |_| { - let cur_index = next_index; - next_index += 1; - ty::Region::new_var(env.tcx(), ty::RegionVid::from_u32(cur_index)) - }; - let (late_sig, _late_region_map) = env.tcx().replace_late_bound_regions(sig, &mut folder); - - // replace early bound variables - let inputs: Vec<_> = late_sig - .inputs() - .iter() - .map(|ty| ty_instantiate(*ty, env.tcx(), subst_early_bounds)) - .collect(); - - let output = ty_instantiate(late_sig.output(), env.tcx(), subst_early_bounds); - - info!("Computed late region map {region_substitution_late:?}"); - - let region_map = EarlyLateRegionMap::new( - region_substitution_early, - region_substitution_late, - universal_lifetimes, - lifetime_names, - ); - (inputs, output, region_map) - } - - /// At the start of the function, there's a universal (placeholder) region for reference argument. - /// These get subsequently relabeled. - /// Given the relabeled region, find the original placeholder region. - fn find_placeholder_region_for(r: ty::RegionVid, info: &PoloniusInfo) -> Option<ty::RegionVid> { - let root_location = Location { - block: BasicBlock::from_u32(0), - statement_index: 0, - }; - let root_point = info.interner.get_point_index(&facts::Point { - location: root_location, - typ: facts::PointType::Start, - }); - - for (r1, r2, p) in &info.borrowck_in_facts.subset_base { - if *p == root_point && *r2 == r { - info!("find placeholder region for: {:?} ⊑ {:?} = r = {:?}", r1, r2, r); - return Some(*r1); - } - } - None - } - - /// Set up the local generic scope of the function, including type parameters, lifetime - /// parameters, and trait constraints. - fn setup_local_scope( - env: &Environment<'tcx>, - ty_translator: &'def TypeTranslator<'def, 'tcx>, - trait_registry: &'def TraitRegistry<'tcx, 'def>, - proc_did: DefId, - params: &[ty::GenericArg<'tcx>], - translated_fn: &mut radium::FunctionBuilder<'def>, - region_substitution: EarlyLateRegionMap, - add_trait_specs: bool, - info: Option<&'def PoloniusInfo<'def, 'tcx>>, - ) -> Result<(TypeTranslationScope<'tcx, 'def>, TraitSpecNameScope), TranslationError<'tcx>> { - // add universals to the function - // important: these need to be in the right order! - for (vid, name) in ®ion_substitution.region_names { - translated_fn.add_universal_lifetime(name.to_owned()); - } - - // enter the procedure - let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(proc_did); - let type_scope = TypeTranslationScope::new_with_traits( - proc_did, - env, - env.tcx().mk_args(params), - region_substitution, - param_env, - ty_translator, - trait_registry, - info, - )?; - - // add generic args to the fn - let generics = &type_scope.generic_scope; - for t in generics.tyvars() { - translated_fn.add_ty_param(t); - } - - // add specs for traits of generics - let trait_uses = type_scope.generic_scope.trait_scope.get_trait_uses(); - for ((did, params), trait_ref) in trait_uses { - let trait_use_ref = trait_ref.trait_use.borrow(); - let trait_use = trait_use_ref.as_ref().unwrap(); - translated_fn.add_trait_requirement(trait_use.clone()); - } - - // check if we are in the implementation of a trait or trait impl - let mut trait_attr_map = HashMap::new(); - if let Some(trait_did) = env.tcx().trait_of_item(proc_did) { - // we are in a trait declaration - if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) { - // make the parameter for the attrs that the function is parametric over - if let Some(trait_use_ref) = type_scope.generic_scope.trait_scope.get_self_trait_use() { - let trait_use_ref = trait_use_ref.trait_use.borrow(); - let trait_use = trait_use_ref.as_ref().unwrap(); - let param_name = trait_use.make_spec_attrs_param_name(); - // add the corresponding record entries to the map - for attr in &trait_ref.declared_attrs { - let record_item = trait_ref.make_spec_attr_name(attr); - trait_attr_map.insert(attr.to_owned(), format!("{param_name}.({record_item})")); - } - } - } - } - if let Some(impl_did) = env.tcx().impl_of_method(proc_did) { - if let Some(trait_did) = env.tcx().trait_id_of_impl(impl_did) { - // we are in a trait impl - if let Some(trait_ref) = trait_registry.lookup_trait(trait_did) { - let attrs = trait_registry.get_impl_attrs_term(impl_did)?; - // add the corresponding record entries to the map - for attr in &trait_ref.declared_attrs { - let record_item = trait_ref.make_spec_attr_name(attr); - trait_attr_map.insert(attr.to_owned(), format!("({attrs}).({record_item})")); - } - } - } - } - let trait_scope = TraitSpecNameScope { - attrs: trait_attr_map, - }; - - // TODO: can we also setup the lifetime constraints here? - // TODO: understand better how these clauses relate to Polonius - // Note: these constraints do not seem to include implied bounds. - /* - let clauses = param_env.caller_bounds(); - info!("looking for outlives clauses"); - for cl in clauses.iter() { - let cl_kind = cl.kind(); - let cl_kind = cl_kind.skip_binder(); - - // only look for trait predicates for now - if let ty::ClauseKind::RegionOutlives(region_pred) = cl_kind { - info!("region outlives: {:?} {:?}", region_pred.0, region_pred.1); - } - if let ty::ClauseKind::TypeOutlives(outlives_pred) = cl_kind { - info!("type outlives: {:?} {:?}", outlives_pred.0, outlives_pred.1); - } - } - */ - - Ok((type_scope, trait_scope)) - } - - /// Filter the "interesting" constraints between universal lifetimes that need to hold - /// (this does not include the constraints that need to hold for all universal lifetimes, - /// e.g. that they outlive the function lifetime and are outlived by 'static). - fn get_relevant_universal_constraints(&mut self) -> Vec<(radium::UniversalLft, radium::UniversalLft)> { - let info = &self.info; - let input_facts = &info.borrowck_in_facts; - let placeholder_subset = &input_facts.known_placeholder_subset; - - let root_location = Location { - block: BasicBlock::from_u32(0), - statement_index: 0, - }; - let root_point = self.info.interner.get_point_index(&facts::Point { - location: root_location, - typ: facts::PointType::Start, - }); - - let mut universal_constraints = Vec::new(); - - for (r1, r2) in placeholder_subset { - if let polonius_info::RegionKind::Universal(uk1) = info.get_region_kind(*r1) { - if let polonius_info::RegionKind::Universal(uk2) = info.get_region_kind(*r2) { - // if LHS is static, ignore. - if uk1 == polonius_info::UniversalRegionKind::Static { - continue; - } - // if RHS is the function lifetime, ignore - if uk2 == polonius_info::UniversalRegionKind::Function { - continue; - } - - let to_universal = || { - let x = self.to_universal_lft(uk1, *r2)?; - let y = self.to_universal_lft(uk2, *r1)?; - Some((x, y)) - }; - // else, add this constraint - // for the purpose of this analysis, it is fine to treat it as a dynamic inclusion - if let Some((x, y)) = to_universal() { - self.inclusion_tracker.add_dynamic_inclusion(*r1, *r2, root_point); - universal_constraints.push((x, y)); - }; - } - } - } - universal_constraints - } - - fn process_function_requirements( - fn_builder: &mut radium::FunctionBuilder<'def>, - requirements: FunctionRequirements, - ) { - for e in requirements.early_coq_params { - fn_builder.add_early_coq_param(e); - } - for e in requirements.late_coq_params { - fn_builder.add_late_coq_param(e); - } - for e in requirements.proof_info.linktime_assumptions { - fn_builder.add_linktime_assumption(e); - } - for e in requirements.proof_info.sidecond_tactics { - fn_builder.add_manual_tactic(e); - } - } - - /// Parse and process attributes of this closure. - fn process_closure_attrs<'b>( - &mut self, - trait_attrs: &TraitSpecNameScope, - inputs: &[Ty<'tcx>], - output: Ty<'tcx>, - meta: ClosureMetaInfo<'b, 'tcx, 'def>, - ) -> Result<(), TranslationError<'tcx>> { - trace!("entering process_closure_attrs"); - let v = self.attrs; - - // Translate signature - info!("inputs: {:?}, output: {:?}", inputs, output); - let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new(); - for arg in inputs { - let translated: radium::Type<'def> = self.ty_translator.translate_type(*arg)?; - translated_arg_types.push(translated); - } - let translated_ret_type: radium::Type<'def> = self.ty_translator.translate_type(output)?; - info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type); - - // Determine parser - let parser = rrconfig::attribute_parser(); - if parser.as_str() != "verbose" { - trace!("leaving process_closure_attrs"); - return Err(TranslationError::UnknownAttributeParser(parser)); - } - - let mut spec_builder = radium::LiteralFunctionSpecBuilder::new(); - - // add universal constraints - let universal_constraints = self.get_relevant_universal_constraints(); - info!("universal constraints: {:?}", universal_constraints); - for (lft1, lft2) in universal_constraints { - spec_builder.add_lifetime_constraint(lft1, lft2); - } - - let ty_translator = &self.ty_translator; - // Hack: create indirection by tracking the tuple uses we create in here. - // (We need a read reference to the scope, so we can't write to it at the same time) - let mut tuple_uses = HashMap::new(); - { - let scope = ty_translator.scope.borrow(); - let scope = FunctionSpecScope { - generics: &*scope, - attrs: trait_attrs, - }; - let mut parser = - VerboseFunctionSpecParser::new(&translated_arg_types, &translated_ret_type, &scope, |lit| { - ty_translator.translator.intern_literal(lit) - }); - - parser - .parse_closure_spec(v, &mut spec_builder, meta, |x| { - ty_translator.make_tuple_use(x, Some(&mut tuple_uses)) - }) - .map_err(TranslationError::AttributeError)?; - - Self::process_function_requirements(&mut self.translated_fn, parser.into()); - } - let mut scope = ty_translator.scope.borrow_mut(); - scope.tuple_uses.extend(tuple_uses); - self.translated_fn.add_function_spec_from_builder(spec_builder); - - trace!("leaving process_closure_attrs"); - Ok(()) - } - - /// Parse and process attributes of this function. - fn process_attrs( - attrs: &[&ast::ast::AttrItem], - ty_translator: &LocalTypeTranslator<'def, 'tcx>, - translator: &mut radium::FunctionBuilder<'def>, - trait_attrs: &TraitSpecNameScope, - inputs: &[Ty<'tcx>], - output: Ty<'tcx>, - ) -> Result<radium::LiteralFunctionSpecBuilder<'def>, TranslationError<'tcx>> { - info!("inputs: {:?}, output: {:?}", inputs, output); - - let mut translated_arg_types: Vec<radium::Type<'def>> = Vec::new(); - for arg in inputs { - let translated: radium::Type<'def> = ty_translator.translate_type(*arg)?; - translated_arg_types.push(translated); - } - let translated_ret_type: radium::Type<'def> = ty_translator.translate_type(output)?; - info!("translated function type: {:?} → {}", translated_arg_types, translated_ret_type); - - let mut spec_builder = radium::LiteralFunctionSpecBuilder::new(); - - let parser = rrconfig::attribute_parser(); - match parser.as_str() { - "verbose" => { - { - let scope = ty_translator.scope.borrow(); - let scope = FunctionSpecScope { - generics: &*scope, - attrs: trait_attrs, - }; - let mut parser: VerboseFunctionSpecParser<'_, 'def, _, _> = - VerboseFunctionSpecParser::new( - &translated_arg_types, - &translated_ret_type, - &scope, - |lit| ty_translator.translator.intern_literal(lit), - ); - - parser - .parse_function_spec(attrs, &mut spec_builder) - .map_err(TranslationError::AttributeError)?; - Self::process_function_requirements(translator, parser.into()); - } - - Ok(spec_builder) - }, - _ => Err(TranslationError::UnknownAttributeParser(parser)), - } - } - - /// Make a specification for a trait instance derived from the trait's default spec. - fn make_trait_instance_spec( - &self, - ) -> Result<Option<radium::InstantiatedTraitFunctionSpec<'def>>, TranslationError<'tcx>> { - let did = self.proc.get_id(); - if let Some(impl_did) = self.env.tcx().impl_of_method(did) { - if let Some(trait_did) = self.env.tcx().trait_id_of_impl(impl_did) { - let trait_ref = self - .trait_registry - .lookup_trait(trait_did) - .ok_or_else(|| TranslationError::TraitResolution(format!("{trait_did:?}")))?; - let fn_name = strip_coq_ident(self.env.tcx().item_name(self.proc.get_id()).as_str()); - - let trait_info = self.trait_registry.get_trait_impl_info(impl_did)?; - //self.trait_registry.lookup_impl(impl_did)?; - let attr_term = self.trait_registry.get_impl_attrs_term(impl_did)?; - return Ok(Some(radium::InstantiatedTraitFunctionSpec::new(trait_info, fn_name, attr_term))); - } - } - - Ok(None) - } - - // TODO refactor/ move - fn to_universal_lft( - &self, - k: polonius_info::UniversalRegionKind, - r: Region, - ) -> Option<radium::UniversalLft> { - match k { - polonius_info::UniversalRegionKind::Function => Some(radium::UniversalLft::Function), - polonius_info::UniversalRegionKind::Static => Some(radium::UniversalLft::Static), - - polonius_info::UniversalRegionKind::Local => self - .ty_translator - .scope - .borrow() - .lifetime_scope - .lookup_region(r) - .map(|x| radium::UniversalLft::Local(x.to_owned())), - - polonius_info::UniversalRegionKind::External => self - .ty_translator - .scope - .borrow() - .lifetime_scope - .lookup_region(r) - .map(|x| radium::UniversalLft::External(x.to_owned())), - } - } - - /// Translation that only generates a specification. - pub fn generate_spec( - self, - ) -> Result<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, TranslationError<'tcx>> { - self.translated_fn.try_into().map_err(TranslationError::AttributeError) - } - - /// Generate a string identifier for a Local. - /// Tries to find the Rust source code name of the local, otherwise simply enumerates. - /// `used_names` keeps track of the Rust source code names that have already been used. - fn make_local_name(mir_body: &Body<'tcx>, local: Local, used_names: &mut HashSet<String>) -> String { - if let Some(mir_name) = Self::find_name_for_local(mir_body, local, used_names) { - let name = strip_coq_ident(&mir_name); - used_names.insert(mir_name); - name - } else { - let mut name = "__".to_owned(); - name.push_str(&local.index().to_string()); - name - } - } - - /// Find a source name for a local of a MIR body, if possible. - fn find_name_for_local( - body: &mir::Body<'tcx>, - local: mir::Local, - used_names: &HashSet<String>, - ) -> Option<String> { - let debug_info = &body.var_debug_info; - - for dbg in debug_info { - let name = &dbg.name; - let val = &dbg.value; - if let VarDebugInfoContents::Place(l) = *val { - // make sure that the place projection is empty -- otherwise this might just - // refer to the capture of a closure - if let Some(this_local) = l.as_local() { - if this_local == local { - // around closures, multiple symbols may be mapped to the same name. - // To prevent this from happening, we check that the name hasn't been - // used already - // TODO: find better solution - if !used_names.contains(name.as_str()) { - return Some(name.as_str().to_owned()); - } - } - } - } else { - // is this case used when constant propagation happens during MIR construction? - } - } - - None - } - - fn dump_body(body: &Body) { - // TODO: print to file - //for dec in &body.local_decls { - //info!("Local decl: {:?}", dec); - //} - - let basic_blocks = &body.basic_blocks; - for (bb_idx, bb) in basic_blocks.iter_enumerated() { - Self::dump_basic_block(bb_idx, bb); - } - } - - /// Dump a basic block as info debug output. - fn dump_basic_block(bb_idx: BasicBlock, bb: &BasicBlockData) { - info!("Basic block {:?}:", bb_idx); - let mut i = 0; - for s in &bb.statements { - info!("{}\t{:?}", i, s); - i += 1; - } - info!("{}\t{:?}", i, bb.terminator()); - } - - /// Determine initial constraints between universal regions and local place regions. - /// Returns an initial mapping for the name _map that initializes place regions of arguments - /// with universals. - fn get_initial_universal_arg_constraints( - &mut self, - _sig_args: &[Ty<'tcx>], - _local_args: &[Ty<'tcx>], - ) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> { - // Polonius generates a base subset constraint uregion ⊑ pregion. - // We turn that into pregion = uregion, as we do strong updates at the top-level. - let info = &self.info; - let input_facts = &info.borrowck_in_facts; - let subset_base = &input_facts.subset_base; - - let root_location = Location { - block: BasicBlock::from_u32(0), - statement_index: 0, - }; - let root_point = self.info.interner.get_point_index(&facts::Point { - location: root_location, - typ: facts::PointType::Start, - }); - - // TODO: for nested references, this doesn't really seem to work. - // Problem is that we don't have constraints for the mapping of nested references. - // Potentially, we should instead just equalize the types - - let mut initial_arg_mapping = Vec::new(); - for (r1, r2, _) in subset_base { - let lft1 = self.info.mk_atomic_region(*r1); - let lft2 = self.info.mk_atomic_region(*r2); - - let polonius_info::AtomicRegion::Universal(polonius_info::UniversalRegionKind::Local, _) = lft1 - else { - continue; - }; - - // this is a constraint we care about here, add it - if self.inclusion_tracker.check_inclusion(*r1, *r2, root_point) { - continue; - } - - self.inclusion_tracker.add_static_inclusion(*r1, *r2, root_point); - self.inclusion_tracker.add_static_inclusion(*r2, *r1, root_point); - - assert!(matches!(lft2, polonius_info::AtomicRegion::PlaceRegion(_))); - - initial_arg_mapping.push((lft1, lft2)); - } - initial_arg_mapping - } - - #[allow(clippy::unused_self)] - fn get_initial_universal_arg_constraints2( - &mut self, - sig_args: &[Ty<'tcx>], - local_args: &[Ty<'tcx>], - ) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> { - // Polonius generates a base subset constraint uregion ⊑ pregion. - // We turn that into pregion = uregion, as we do strong updates at the top-level. - assert!(sig_args.len() == local_args.len()); - - // TODO: implement a bitypefolder to solve this issue. - Vec::new() - } -} - -/** - * Struct that keeps track of all information necessary to translate a MIR Body to a `radium::Function`. - * `'a` is the lifetime of the translator and ends after translation has finished. - * `'def` is the lifetime of the generated code (the code may refer to struct defs). - * `'tcx` is the lifetime of the rustc tctx. - */ -struct BodyTranslator<'a, 'def, 'tcx> { - env: &'def Environment<'tcx>, - /// this needs to be annotated with the right borrowck things - proc: &'def Procedure<'tcx>, - /// maps locals to variable names - variable_map: HashMap<Local, String>, - /// the Caesium function buildder - translated_fn: radium::FunctionBuilder<'def>, - /// name of the return variable - return_name: String, - /// syntactic type of the thing to return - return_synty: radium::SynType, - /// all the other procedures used by this function, and: - /// (code_loc_parameter_name, spec_name, type_inst, syntype_of_all_args) - collected_procedures: HashMap<(DefId, GenericsKey<'tcx>), radium::UsedProcedure<'def>>, - /// used statics - collected_statics: HashSet<DefId>, - - /// tracking lifetime inclusions for the generation of lifetime inclusions - inclusion_tracker: InclusionTracker<'a, 'tcx>, - - /// registry of other procedures - procedure_registry: &'a ProcedureScope<'def>, - /// scope of used consts - const_registry: &'a ConstScope<'def>, - /// trait registry - trait_registry: &'def TraitRegistry<'tcx, 'def>, - /// attributes on this function - attrs: &'a [&'a ast::ast::AttrItem], - /// polonius info for this function - info: &'a PoloniusInfo<'a, 'tcx>, - /// local lifetimes: the LHS is the lifetime name, the RHS are the super lifetimes - local_lifetimes: Vec<(radium::specs::Lft, Vec<radium::specs::Lft>)>, - /// data structures for tracking which basic blocks still need to be translated - /// (we only translate the basic blocks which are actually reachable, in particular when - /// skipping unwinding) - bb_queue: Vec<BasicBlock>, - /// set of already processed blocks - processed_bbs: HashSet<BasicBlock>, - /// translator for types - ty_translator: LocalTypeTranslator<'def, 'tcx>, - - /// map of loop heads to their optional spec closure defid - loop_specs: HashMap<BasicBlock, Option<DefId>>, - - /// relevant locals: (local, name, type) - fn_locals: Vec<(Local, String, radium::Type<'def>)>, - - /// result temporaries of checked ops that we rewrite - /// we assume that this place is typed at (result_type, bool) - /// and rewrite accesses to the first component to directly use the place, - /// while rewriting accesses to the second component to true. - /// TODO: once we handle panics properly, we should use a different translation. - /// NOTE: we only rewrite for uses, as these are the only places these are used. - checked_op_temporaries: HashMap<Local, Ty<'tcx>>, -} - -impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> { - /// Main translation function that actually does the translation and returns a `radium::Function` - /// if successful. - pub fn translate( - mut self, - initial_constraints: Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)>, - spec_arena: &'def Arena<radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>, - ) -> Result<radium::Function<'def>, TranslationError<'tcx>> { - // add loop info - let loop_info = self.proc.loop_info(); - loop_info.dump_body_head(); - - // translate the function's basic blocks - let basic_blocks = &self.proc.get_mir().basic_blocks; - - // first translate the initial basic block; we add some additional annotations to the front - let initial_bb_idx = BasicBlock::from_u32(0); - if let Some(bb) = basic_blocks.get(initial_bb_idx) { - let mut translated_bb = self.translate_basic_block(initial_bb_idx, bb)?; - // push annotation for initial constraints that relate argument's place regions to universals - for (r1, r2) in initial_constraints { - translated_bb = radium::Stmt::Annot { - a: radium::Annotation::CopyLftName( - self.format_atomic_region(&r1), - self.format_atomic_region(&r2), - ), - s: Box::new(translated_bb), - why: Some("initialization".to_owned()), - }; - } - self.translated_fn.code.add_basic_block(initial_bb_idx.as_usize(), translated_bb); - } else { - info!("No basic blocks"); - } - - while let Some(bb_idx) = self.bb_queue.pop() { - let bb = &basic_blocks[bb_idx]; - let translated_bb = self.translate_basic_block(bb_idx, bb)?; - self.translated_fn.code.add_basic_block(bb_idx.as_usize(), translated_bb); - } - - // assume that all generics are layoutable - { - let scope = self.ty_translator.scope.borrow(); - for ty in scope.generic_scope.tyvars() { - self.translated_fn.assume_synty_layoutable(radium::SynType::Literal(ty.syn_type)); - } - } - // assume that all used literals are layoutable - for g in self.ty_translator.scope.borrow().shim_uses.values() { - self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term()); - } - // assume that all used tuples are layoutable - for g in self.ty_translator.scope.borrow().tuple_uses.values() { - self.translated_fn.assume_synty_layoutable(g.generate_syn_type_term()); - } - - // TODO: process self.loop_specs - // - collect the relevant bb -> def_id mappings for this function (so we can eventually generate the - // definition) - // - have a function that takes the def_id and then parses the attributes into a loop invariant - for (head, did) in &self.loop_specs { - let spec = self.parse_attributes_on_loop_spec_closure(*head, *did); - self.translated_fn.register_loop_invariant(head.as_usize(), spec); - } - - // generate dependencies on other procedures. - for used_proc in self.collected_procedures.values() { - self.translated_fn.require_function(used_proc.clone()); - } - - // generate dependencies on statics - for did in &self.collected_statics { - let s = &self.const_registry.statics[did]; - self.translated_fn.require_static(s.clone()); - } - - Ok(self.translated_fn.into_function(spec_arena)) - } - - /// Determine initial constraints between universal regions and local place regions. - /// Returns an initial mapping for the name _map that initializes place regions of arguments - /// with universals. - fn get_initial_universal_arg_constraints( - &mut self, - _sig_args: &[Ty<'tcx>], - _local_args: &[Ty<'tcx>], - ) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> { - // Polonius generates a base subset constraint uregion ⊑ pregion. - // We turn that into pregion = uregion, as we do strong updates at the top-level. - let info = &self.info; - let input_facts = &info.borrowck_in_facts; - let subset_base = &input_facts.subset_base; - - let root_location = Location { - block: BasicBlock::from_u32(0), - statement_index: 0, - }; - let root_point = self.info.interner.get_point_index(&facts::Point { - location: root_location, - typ: facts::PointType::Start, - }); - - // TODO: for nested references, this doesn't really seem to work. - // Problem is that we don't have constraints for the mapping of nested references. - // Potentially, we should instead just equalize the types - - let mut initial_arg_mapping = Vec::new(); - for (r1, r2, _) in subset_base { - let lft1 = self.info.mk_atomic_region(*r1); - let lft2 = self.info.mk_atomic_region(*r2); - - let polonius_info::AtomicRegion::Universal(polonius_info::UniversalRegionKind::Local, _) = lft1 - else { - continue; - }; - - // this is a constraint we care about here, add it - if self.inclusion_tracker.check_inclusion(*r1, *r2, root_point) { - continue; - } - - self.inclusion_tracker.add_static_inclusion(*r1, *r2, root_point); - self.inclusion_tracker.add_static_inclusion(*r2, *r1, root_point); - - assert!(matches!(lft2, polonius_info::AtomicRegion::PlaceRegion(_))); - - initial_arg_mapping.push((lft1, lft2)); - } - initial_arg_mapping - } - - /// Abstract over the generics of a function and partially instantiate them. - /// Assumption: `trait_reqs` is appropriately sorted, i.e. surrounding requirements come first. - /// `with_surrounding_deps` determines whether we should distinguish surrounding and direct - /// params. - fn get_generic_abstraction_for_procedure( - &self, - callee_did: DefId, - ty_params: ty::GenericArgsRef<'tcx>, - trait_reqs: &[radium::TraitReqInst<'def, ty::Ty<'tcx>>], - with_surrounding_deps: bool, - ) -> Result<AbstractedGenerics<'def>, TranslationError<'tcx>> { - // get all the regions and type variables appearing that generics are instantiated with - let mut tyvar_folder = TyVarFolder::new(self.env.tcx()); - let mut lft_folder = TyRegionCollectFolder::new(self.env.tcx()); - - // also count the number of regions of the function itself - let mut num_param_regions = 0; - - let mut callee_lft_param_inst: Vec<radium::Lft> = Vec::new(); - let mut callee_ty_param_inst = Vec::new(); - for v in ty_params { - if let Some(ty) = v.as_type() { - tyvar_folder.fold_ty(ty); - lft_folder.fold_ty(ty); - } - if let Some(region) = v.as_region() { - num_param_regions += 1; - - let lft_name = self.ty_translator.translate_region(region)?; - callee_lft_param_inst.push(lft_name); - } - } - // also find generics in the associated types - for req in trait_reqs { - for ty in &req.assoc_ty_inst { - tyvar_folder.fold_ty(*ty); - lft_folder.fold_ty(*ty); - } - } - - let tyvars = tyvar_folder.get_result(); - let regions = lft_folder.get_regions(); - - let mut scope = radium::GenericScope::empty(); - - // instantiations for the function spec's parameters - let mut fn_lft_param_inst = Vec::new(); - let mut fn_ty_param_inst = Vec::new(); - - // re-bind the function's lifetime parameters - for i in 0..num_param_regions { - let lft_name = format!("ulft_{i}"); - scope.add_lft_param(lft_name.clone()); - fn_lft_param_inst.push(lft_name); - } - - // bind the additional lifetime parameters - let mut next_lft = num_param_regions; - for region in regions { - // Use the name the region has inside the function as the binder name, so that the - // names work out when translating the types below - let lft_name = - self.ty_translator.translate_region_var(region).unwrap_or(format!("ulft_{next_lft}")); - scope.add_lft_param(lft_name.clone()); - - next_lft += 1; - - callee_lft_param_inst.push(lft_name); - } - - // bind the generics we use - for param in &tyvars { - // NOTE: this should have the same name as the using occurrences - let lit = radium::LiteralTyParam::new(param.name.as_str(), param.name.as_str()); - callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone())); - scope.add_ty_param(lit); - } - // also bind associated types which we translate as generics - for req in trait_reqs { - for ty in &req.assoc_ty_inst { - // we should check if it there is a parameter in the current scope for it - let translated_ty = self.ty_translator.translate_type(*ty)?; - if let radium::Type::LiteralParam(mut lit) = translated_ty { - lit.set_origin(req.origin); - - scope.add_ty_param(lit.clone()); - callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone())); - } - } - } - - // NOTE: we need to be careful with the order here. - // - the ty_params are all the generics the function has. - // - the trait_reqs are also all the associated types the function has - // We need to distinguish these between direct and surrounding. - let num_surrounding_params = - ParamScope::determine_number_of_surrounding_params(callee_did, self.env.tcx()); - info!("num_surrounding_params={num_surrounding_params:?}, ty_params={ty_params:?}"); - - // figure out instantiation for the function's generics - // first the surrounding parameters - if with_surrounding_deps { - for v in &ty_params.as_slice()[..num_surrounding_params] { - if let Some(ty) = v.as_type() { - let translated_ty = self.ty_translator.translate_type(ty)?; - fn_ty_param_inst.push(translated_ty); - } - } - // same for the associated types this function depends on - for req in trait_reqs { - if req.origin != radium::TyParamOrigin::Direct { - for ty in &req.assoc_ty_inst { - let translated_ty = self.ty_translator.translate_type(*ty)?; - fn_ty_param_inst.push(translated_ty); - } - } - } - - // now the direct parameters - for v in &ty_params.as_slice()[num_surrounding_params..] { - if let Some(ty) = v.as_type() { - let translated_ty = self.ty_translator.translate_type(ty)?; - fn_ty_param_inst.push(translated_ty); - } - } - // same for the associated types this function depends on - for req in trait_reqs { - if req.origin == radium::TyParamOrigin::Direct { - for ty in &req.assoc_ty_inst { - let translated_ty = self.ty_translator.translate_type(*ty)?; - fn_ty_param_inst.push(translated_ty); - } - } - } - } else { - // now the direct parameters - for v in ty_params { - if let Some(ty) = v.as_type() { - let translated_ty = self.ty_translator.translate_type(ty)?; - fn_ty_param_inst.push(translated_ty); - } - } - // same for the associated types this function depends on - for req in trait_reqs { - if req.origin == radium::TyParamOrigin::Direct { - for ty in &req.assoc_ty_inst { - let translated_ty = self.ty_translator.translate_type(*ty)?; - fn_ty_param_inst.push(translated_ty); - } - } - } - } - - info!("Abstraction scope: {:?}", scope); - info!("Fn instantiation: {:?}, {:?}", fn_lft_param_inst, fn_ty_param_inst); - info!("Callee instantiation: {:?}, {:?}", callee_lft_param_inst, callee_ty_param_inst); - - let res = AbstractedGenerics { - scope, - callee_lft_param_inst, - callee_ty_param_inst, - fn_lft_param_inst, - fn_ty_param_inst, - }; - - Ok(res) - } - - /// Internally register that we have used a procedure with a particular instantiation of generics, and - /// return the code parameter name. - /// Arguments: - /// - `callee_did`: the `DefId` of the callee - /// - `ty_params`: the instantiation for the callee's type parameters - /// - `trait_spec_terms`: if the callee has any trait assumptions, these are specification parameter terms - /// for these traits - /// - `trait_assoc_tys`: if the callee has any trait assumptions, these are the instantiations for all - /// associated types - fn register_use_procedure( - &mut self, - callee_did: DefId, - extra_spec_args: Vec<String>, - ty_params: ty::GenericArgsRef<'tcx>, - trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>, - ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> { - trace!("enter register_use_procedure callee_did={callee_did:?} ty_params={ty_params:?}"); - // The key does not include the associated types, as the resolution of the associated types - // should always be unique for one combination of type parameters, as long as we remain in - // the same environment (which is the case within this procedure). - // Therefore, already the type parameters are sufficient to distinguish different - // instantiations. - let key = generate_args_inst_key(self.env.tcx(), ty_params)?; - - // re-quantify - let quantified_args = - self.get_generic_abstraction_for_procedure(callee_did, ty_params, &trait_specs, true)?; - - let tup = (callee_did, key); - let res; - if let Some(proc_use) = self.collected_procedures.get(&tup) { - res = proc_use.loc_name.clone(); - } else { - let meta = self - .procedure_registry - .lookup_function(callee_did) - .ok_or_else(|| TranslationError::UnknownProcedure(format!("{:?}", callee_did)))?; - // explicit instantiation is needed sometimes - let spec_name = format!("{} (RRGS:=RRGS)", meta.get_spec_name()); - let code_name = meta.get_name(); - let loc_name = format!("{}_loc", mangle_name_with_tys(code_name, tup.1.as_slice())); - - let syntypes = get_arg_syntypes_for_procedure_call( - self.env.tcx(), - &self.ty_translator, - callee_did, - ty_params.as_slice(), - )?; - - let mut translated_params = quantified_args.fn_ty_param_inst; - - info!( - "Registered procedure instance {} of {:?} with {:?} and layouts {:?}", - loc_name, callee_did, translated_params, syntypes - ); - - let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect(); - - let proc_use = radium::UsedProcedure::new( - loc_name, - spec_name, - extra_spec_args, - quantified_args.scope, - specs, - translated_params, - quantified_args.fn_lft_param_inst, - syntypes, - ); - - res = proc_use.loc_name.clone(); - self.collected_procedures.insert(tup, proc_use); - } - trace!("leave register_use_procedure"); - Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst)) - } - - /// Internally register that we have used a trait method with a particular instantiation of - /// generics, and return the code parameter name. - fn register_use_trait_method<'c>( - &'c mut self, - callee_did: DefId, - ty_params: ty::GenericArgsRef<'tcx>, - trait_specs: Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>, - ) -> Result<(String, Vec<radium::Type<'def>>, Vec<radium::Lft>), TranslationError<'tcx>> { - trace!("enter register_use_trait_method did={:?} ty_params={:?}", callee_did, ty_params); - // Does not include the associated types in the key; see `register_use_procedure` for an - // explanation. - let key = generate_args_inst_key(self.env.tcx(), ty_params)?; - - let (method_loc_name, method_spec_term, method_params) = - self.ty_translator.register_use_trait_procedure(self.env, callee_did, ty_params)?; - // re-quantify - let quantified_args = - self.get_generic_abstraction_for_procedure(callee_did, method_params, &trait_specs, false)?; - - let tup = (callee_did, key); - let res; - if let Some(proc_use) = self.collected_procedures.get(&tup) { - res = proc_use.loc_name.clone(); - } else { - // TODO: should we use ty_params or method_params? - let syntypes = get_arg_syntypes_for_procedure_call( - self.env.tcx(), - &self.ty_translator, - callee_did, - ty_params.as_slice(), - )?; - - let mut translated_params = quantified_args.fn_ty_param_inst; - - info!( - "Registered procedure instance {} of {:?} with {:?} and layouts {:?}", - method_loc_name, callee_did, translated_params, syntypes - ); - - let specs = trait_specs.into_iter().map(|x| x.try_into().unwrap()).collect(); - let proc_use = radium::UsedProcedure::new( - method_loc_name, - method_spec_term, - vec![], - quantified_args.scope, - specs, - translated_params, - quantified_args.fn_lft_param_inst, - syntypes, - ); - - res = proc_use.loc_name.clone(); - self.collected_procedures.insert(tup, proc_use); - } - trace!("leave register_use_procedure"); - Ok((res, quantified_args.callee_ty_param_inst, quantified_args.callee_lft_param_inst)) - } - - /// Enqueues a basic block for processing, if it has not already been processed, - /// and marks it as having been processed. - fn enqueue_basic_block(&mut self, bb: BasicBlock) { - if !self.processed_bbs.contains(&bb) { - self.bb_queue.push(bb); - self.processed_bbs.insert(bb); - } - } - - /// Format an atomic region, using the naming info for universal lifetimes available in the current - /// context. - fn format_atomic_region(&self, r: &polonius_info::AtomicRegion) -> String { - self.ty_translator.format_atomic_region(r) - } - - fn format_region(&self, r: facts::Region) -> String { - let lft = self.info.mk_atomic_region(r); - self.format_atomic_region(&lft) - } - - /// Parse the attributes on spec closure `did` as loop annotations and add it as an invariant - /// to the generated code. - fn parse_attributes_on_loop_spec_closure( - &self, - loop_head: BasicBlock, - did: Option<DefId>, - ) -> radium::LoopSpec { - // for now: just make invariants True. - - // need to do: - // - find out the locals in the right order, make parameter names for them. based on their type and - // initialization status, get the refinement type. - // - output/pretty-print this map when generating the typing proof of each function. [done] - // + should not be a separate definition, but rather a "set (.. := ...)" with a marker type so - // automation can find it. - - // representation of loop invariants: - // - introduce parameters for them. - - let mut rfn_binders = Vec::new(); - let prop_body = radium::IProp::True; - - // determine invariant on initialization: - // - we need this both for the refinement invariant (though this could be removed if we make uninit - // generic over the refinement) - // - in order to establish the initialization invariant in each loop iteration, since we don't have - // proper subtyping for uninit => maybe we could fix this too by making uninit variant in the - // refinement type? then we could have proper subtyping lemmas. - // + to bring it to the right refinement type initially, maybe have some automation / - // annotation - // TODO: consider changing it like that. - // - // Note that StorageDead will not help us for determining initialization/ making it invariant, since - // it only applies to full stack slots, not individual paths. one thing that makes it more - // complicated in the frontend: initialization may in practice also be path-dependent. - // - this does not cause issues with posing a too strong loop invariant, - // - but this poses an issue for annotations - // - // - - // get locals - for (_, name, ty) in &self.fn_locals { - // get the refinement type - let mut rfn_ty = ty.get_rfn_type(); - // wrap it in place_rfn, since we reason about places - rfn_ty = coq::term::Type::PlaceRfn(Box::new(rfn_ty)); - - // determine their initialization status - //let initialized = true; // TODO - // determine the actual refinement type for the current initialization status. - - let rfn_name = format!("r_{}", name); - rfn_binders.push(coq::binder::Binder::new(Some(rfn_name), rfn_ty)); - } - - // TODO what do we do about stuff connecting borrows? - if let Some(did) = did { - let attrs = self.env.get_attributes(did); - info!("attrs for loop {:?}: {:?}", loop_head, attrs); - } else { - info!("no attrs for loop {:?}", loop_head); - } - - let pred = radium::IPropPredicate::new(rfn_binders, prop_body); - radium::LoopSpec { - func_predicate: pred, - } - } - - /// Checks whether a place access descends below a reference. - fn check_place_below_reference(&self, place: &Place<'tcx>) -> bool { - if self.checked_op_temporaries.contains_key(&place.local) { - // temporaries are never below references - return false; - } - - for (pl, _) in place.iter_projections() { - // check if the current ty is a reference that we then descend under with proj - let cur_ty_kind = pl.ty(&self.proc.get_mir().local_decls, self.env.tcx()).ty.kind(); - if let TyKind::Ref(_, _, _) = cur_ty_kind { - return true; - } - } - - false - } - - fn get_assignment_strong_update_constraints( - &mut self, - loc: Location, - ) -> HashSet<(Region, Region, PointIndex)> { - let info = &self.info; - let input_facts = &info.borrowck_in_facts; - let subset_base = &input_facts.subset_base; - - let mut constraints = HashSet::new(); - // Polonius subset constraint are spawned for the midpoint - let midpoint = self.info.interner.get_point_index(&facts::Point { - location: loc, - typ: facts::PointType::Mid, - }); - - // for strong update: emit mutual equalities - // TODO: alternative implementation: structurally compare regions in LHS type and RHS type - for (s1, s2, point) in subset_base { - if *point == midpoint { - let lft1 = self.info.mk_atomic_region(*s1); - let lft2 = self.info.mk_atomic_region(*s2); - - // We only care about inclusions into a place lifetime. - // Moreover, we want to filter out the universal inclusions which are always - // replicated at every point. - if lft2.is_place() && !lft1.is_universal() { - // take this constraint and the reverse constraint - constraints.insert((*s1, *s2, *point)); - //constraints.insert((*s2, *s1, *point)); - } - } - } - constraints - } - - fn get_assignment_weak_update_constraints( - &mut self, - loc: Location, - ) -> HashSet<(Region, Region, PointIndex)> { - let info = &self.info; - let input_facts = &info.borrowck_in_facts; - let subset_base = &input_facts.subset_base; - - let mut constraints = HashSet::new(); - // Polonius subset constraint are spawned for the midpoint - let midpoint = self.info.interner.get_point_index(&facts::Point { - location: loc, - typ: facts::PointType::Mid, - }); - - // for weak updates: should mirror the constraints generated by Polonius - for (s1, s2, point) in subset_base { - if *point == midpoint { - // take this constraint - // TODO should there be exceptions to this? - - if !self.inclusion_tracker.check_inclusion(*s1, *s2, *point) { - // only add it if it does not hold already, since we will enforce this - // constraint dynamically. - constraints.insert((*s1, *s2, *point)); - } - } - } - constraints - } - - /// Split the type of a function operand of a call expression to a base type and an instantiation for - /// generics. - fn call_expr_op_split_inst( - &self, - constant: &Constant<'tcx>, - ) -> Result< - (DefId, ty::PolyFnSig<'tcx>, ty::GenericArgsRef<'tcx>, ty::PolyFnSig<'tcx>), - TranslationError<'tcx>, - > { - match constant.literal { - ConstantKind::Ty(c) => { - match c.ty().kind() { - TyKind::FnDef(def, args) => { - let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def); - let ty_ident = ty.instantiate_identity(); - assert!(ty_ident.is_fn()); - let ident_sig = ty_ident.fn_sig(self.env.tcx()); - - let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice()); - let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx()); - - Ok((*def, ident_sig, args, instantiated_sig)) - }, - // TODO handle FnPtr, closure - _ => Err(TranslationError::Unimplemented { - description: "implement function pointers".to_owned(), - }), - } - }, - ConstantKind::Val(_, ty) => { - match ty.kind() { - TyKind::FnDef(def, args) => { - let ty: ty::EarlyBinder<Ty<'tcx>> = self.env.tcx().type_of(def); - - let ty_ident = ty.instantiate_identity(); - assert!(ty_ident.is_fn()); - let ident_sig = ty_ident.fn_sig(self.env.tcx()); - - let ty_instantiated = ty.instantiate(self.env.tcx(), args.as_slice()); - let instantiated_sig = ty_instantiated.fn_sig(self.env.tcx()); - - Ok((*def, ident_sig, args, instantiated_sig)) - }, - // TODO handle FnPtr, closure - _ => Err(TranslationError::Unimplemented { - description: "implement function pointers".to_owned(), - }), - } - }, - ConstantKind::Unevaluated(_, _) => Err(TranslationError::Unimplemented { - description: "implement ConstantKind::Unevaluated".to_owned(), - }), - } - } - - /// Find the optional `DefId` of the closure giving the invariant for the loop with head `head_bb`. - fn find_loop_spec_closure(&self, head_bb: BasicBlock) -> Result<Option<DefId>, TranslationError<'tcx>> { - let bodies = self.proc.loop_info().get_loop_body(head_bb); - let basic_blocks = &self.proc.get_mir().basic_blocks; - - // we go in order through the bodies in order to not stumble upon an annotation for a - // nested loop! - for body in bodies { - // check that we did not go below a nested loop - if self.proc.loop_info().get_loop_head(*body) == Some(head_bb) { - // check the statements for an assignment - let data = basic_blocks.get(*body).unwrap(); - for stmt in &data.statements { - if let StatementKind::Assign(box (pl, _)) = stmt.kind { - if let Some(did) = self.is_spec_closure_local(pl.local)? { - return Ok(Some(did)); - } - } - } - } - } - - Ok(None) - } - - /// Translate a goto-like jump to `target`. - fn translate_goto_like( - &mut self, - _loc: &Location, - target: BasicBlock, - ) -> Result<radium::Stmt, TranslationError<'tcx>> { - self.enqueue_basic_block(target); - let res_stmt = radium::Stmt::GotoBlock(target.as_usize()); - - let loop_info = self.proc.loop_info(); - if loop_info.is_loop_head(target) && !self.loop_specs.contains_key(&target) { - let spec_defid = self.find_loop_spec_closure(target)?; - self.loop_specs.insert(target, spec_defid); - } - - Ok(res_stmt) - } - - /// Check if a call goes to `std::rt::begin_panic` - fn is_call_destination_panic(&mut self, func: &Operand) -> bool { - let Operand::Constant(box c) = func else { - return false; - }; - - let ConstantKind::Val(_, ty) = c.literal else { - return false; - }; - - let TyKind::FnDef(did, _) = ty.kind() else { - return false; - }; - - if let Some(panic_id_std) = - utils::try_resolve_did(self.env.tcx(), &["std", "panicking", "begin_panic"]) - { - if panic_id_std == *did { - return true; - } - } else { - warn!("Failed to determine DefId of std::panicking::begin_panic"); - } - - if let Some(panic_id_core) = utils::try_resolve_did(self.env.tcx(), &["core", "panicking", "panic"]) { - if panic_id_core == *did { - return true; - } - } else { - warn!("Failed to determine DefId of core::panicking::panic"); - } - - false - } - - /// Registers a drop shim for a particular type for the translation. - #[allow(clippy::unused_self)] - const fn register_drop_shim_for(&self, _ty: Ty<'tcx>) { - // TODO! - //let drop_in_place_did: DefId = utils::try_resolve_did(self.env.tcx(), &["std", "ptr", - // "drop_in_place"]).unwrap(); - - //let x: ty::InstanceDef = ty::InstanceDef::DropGlue(drop_in_place_did, Some(ty)); - //let body: &'tcx mir::Body = self.env.tcx().mir_shims(x); - - //info!("Generated drop shim for {:?}", ty); - //Self::dump_body(body); - } - - fn compute_call_regions( - &self, - func: &Constant<'tcx>, - loc: Location, - ) -> Result<CallRegions, TranslationError<'tcx>> { - let midpoint = self.info.interner.get_point_index(&facts::Point { - location: loc, - typ: facts::PointType::Mid, - }); - - // first identify substitutions for the early-bound regions - let (target_did, sig, substs, _) = self.call_expr_op_split_inst(func)?; - info!("calling function {:?}", target_did); - let mut early_regions = Vec::new(); - info!("call substs: {:?} = {:?}, {:?}", func, sig, substs); - for a in substs { - if let ty::GenericArgKind::Lifetime(r) = a.unpack() { - if let ty::RegionKind::ReVar(r) = r.kind() { - early_regions.push(r); - } - } - } - info!("call region instantiations (early): {:?}", early_regions); - - // this is a hack to identify the inference variables introduced for the - // call's late-bound universals. - // TODO: Can we get this information in a less hacky way? - // One approach: compute the early + late bound regions for a given DefId, similarly to how - // we do it when starting to translate a function - // Problem: this doesn't give a straightforward way to compute their instantiation - - // now find all the regions that appear in type parameters we instantiate. - // These are regions that the callee doesn't know about. - let mut generic_regions = HashSet::new(); - let mut clos = |r: ty::Region<'tcx>, _| match r.kind() { - ty::RegionKind::ReVar(rv) => { - generic_regions.insert(rv); - r - }, - _ => r, - }; - - for a in substs { - if let ty::GenericArgKind::Type(c) = a.unpack() { - let mut folder = ty::fold::RegionFolder::new(self.env.tcx(), &mut clos); - folder.fold_ty(c); - } - } - info!("Regions of generic args: {:?}", generic_regions); - - // go over all region constraints initiated at this location - let new_constraints = self.info.get_new_subset_constraints_at_point(midpoint); - let mut new_regions = HashSet::new(); - let mut relevant_constraints = Vec::new(); - for (r1, r2) in &new_constraints { - if matches!(self.info.get_region_kind(*r1), polonius_info::RegionKind::Unknown) { - // this is probably a inference variable for the call - new_regions.insert(*r1); - relevant_constraints.push((*r1, *r2)); - } - if matches!(self.info.get_region_kind(*r2), polonius_info::RegionKind::Unknown) { - new_regions.insert(*r2); - relevant_constraints.push((*r1, *r2)); - } - } - // first sort this to enable cycle resolution - let mut new_regions_sorted: Vec<Region> = new_regions.iter().copied().collect(); - new_regions_sorted.sort(); - - // identify the late-bound regions - let mut late_regions = Vec::new(); - for r in &new_regions_sorted { - // only take the ones which are not early bound and - // which are not due to a generic (the callee doesn't care about generic regions) - if !early_regions.contains(r) && !generic_regions.contains(r) { - late_regions.push(*r); - } - } - info!("call region instantiations (late): {:?}", late_regions); - - // Notes: - // - if two of the call regions need to be equal due to constraints on the function, we define the one - // with the larger id in terms of the other one - // - we ignore unidirectional subset constraints between call regions (these do not help in finding a - // solution if we take the transitive closure beforehand) - // - if a call region needs to be equal to a local region, we directly define it in terms of the local - // region - // - otherwise, it will be an intersection of local regions - let mut new_regions_classification = HashMap::new(); - // compute transitive closure of constraints - let relevant_constraints = polonius_info::compute_transitive_closure(relevant_constraints); - for r in &new_regions_sorted { - for (r1, r2) in &relevant_constraints { - if *r2 != *r { - continue; - } - - // i.e. (flipping it around when we are talking about lifetimes), - // r needs to be a sublft of r1 - if relevant_constraints.contains(&(*r2, *r1)) { - // if r1 is also a new region and r2 is ordered before it, we will - // just define r1 in terms of r2 - if new_regions.contains(r1) && r2.as_u32() < r1.as_u32() { - continue; - } - // need an equality constraint - new_regions_classification.insert(*r, CallRegionKind::EqR(*r1)); - // do not consider the rest of the constraints as r is already - // fully specified - break; - } - - // the intersection also needs to contain r1 - if new_regions.contains(r1) { - // we do not need this constraint, since we already computed the - // transitive closure. - continue; - } - - let kind = new_regions_classification - .entry(*r) - .or_insert(CallRegionKind::Intersection(HashSet::new())); - - let CallRegionKind::Intersection(s) = kind else { - unreachable!(); - }; - - s.insert(*r1); - } - } - info!("call arg classification: {:?}", new_regions_classification); - - Ok(CallRegions { - early_regions, - late_regions, - classification: new_regions_classification, - }) - } - - fn translate_function_call( - &mut self, - func: &Operand<'tcx>, - args: &[Operand<'tcx>], - destination: &Place<'tcx>, - target: Option<middle::mir::BasicBlock>, - loc: Location, - dying_loans: &[facts::Loan], - ) -> Result<radium::Stmt, TranslationError<'tcx>> { - let startpoint = self.info.interner.get_point_index(&facts::Point { - location: loc, - typ: facts::PointType::Start, - }); - - let Operand::Constant(box func_constant) = func else { - return Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support this kind of call operand (got: {:?})", - func - ), - }); - }; - - // for lifetime annotations: - // 1. get the regions involved here. for that, get the instantiation of the function. - // + if it's a FnDef type, that should be easy. - // + for a function pointer: ? - // + for a closure: ? - // (Polonius does not seem to distinguish early/late bound in any way, except - // that they are numbered in different passes) - // 2. find the constraints here involving the regions. - // 3. solve for the regions. - // + transitively propagate the constraints - // + check for equalities - // + otherwise compute intersection. singleton intersection just becomes an equality def. - // 4. add annotations accordingly - // + either a startlft - // + or a copy name - // 5. add shortenlft annotations to line up arguments. - // + for that, we need the type of the LHS, and what the argument types (with - // substituted regions) should be. - // 6. annotate the return value on assignment and establish constraints. - - let classification = self.compute_call_regions(func_constant, loc)?; - - // update the inclusion tracker with the new regions we have introduced - // We just add the inclusions and ignore that we resolve it in a "tight" way. - // the cases where we need the reverse inclusion should be really rare. - for (r, c) in &classification.classification { - match c { - CallRegionKind::EqR(r2) => { - // put it at the start point, because the inclusions come into effect - // at the point right before. - self.inclusion_tracker.add_static_inclusion(*r, *r2, startpoint); - self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint); - }, - CallRegionKind::Intersection(lfts) => { - // all the regions represented by lfts need to be included in r - for r2 in lfts { - self.inclusion_tracker.add_static_inclusion(*r2, *r, startpoint); - } - }, - } - } - - // translate the function expression. - let func_expr = self.translate_operand(func, false)?; - // We expect this to be an Expr::CallTarget, being annotated with the type parameters we - // instantiate it with. - let radium::Expr::CallTarget(func_lit, ty_param_annots, mut lft_param_annots) = func_expr else { - unreachable!("Logic error in call target translation"); - }; - let func_expr = radium::Expr::MetaParam(func_lit); - - // translate the arguments - let mut translated_args = Vec::new(); - for arg in args { - // to_ty is the type the function expects - - //let ty = arg.ty(&self.proc.get_mir().local_decls, self.env.tcx()); - let translated_arg = self.translate_operand(arg, true)?; - translated_args.push(translated_arg); - } - - // We have to add the late regions, since we do not requantify over them. - for late in &classification.late_regions { - let lft = self.format_region(*late); - lft_param_annots.push(lft); - } - info!("Call lifetime instantiation (early): {:?}", classification.early_regions); - info!("Call lifetime instantiation (late): {:?}", classification.late_regions); - - // Get the type of the return value from the function - let (_, _, _, inst_sig) = self.call_expr_op_split_inst(func_constant)?; - // TODO: do we need to do something with late bounds? - let output_ty = inst_sig.output().skip_binder(); - info!("call has instantiated type {:?}", inst_sig); - - // compute the resulting annotations - let (rhs_annots, pre_stmt_annots, post_stmt_annots) = - self.get_assignment_annots(loc, destination, output_ty); - info!( - "assignment annots after call: expr: {:?}, pre-stmt: {:?}, post-stmt: {:?}", - rhs_annots, pre_stmt_annots, post_stmt_annots - ); - - // TODO: add annotations for the assignment - // for that: - // - get the type of the place - // - enforce constraints as necessary. this might spawn dyninclusions with some of the new regions => - // In Coq, also the aliases should get proper endlft events to resolve the dyninclusions. - // - update the name map - let call_expr = radium::Expr::Call { - f: Box::new(func_expr), - lfts: lft_param_annots, - tys: ty_param_annots, - args: translated_args, - }; - let stmt = match target { - Some(target) => { - let mut cont_stmt = self.translate_goto_like(&loc, target)?; - // end loans before the goto, but after the call. - // TODO: may cause duplications? - cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.iter().copied()); - - let cont_stmt = radium::Stmt::with_annotations( - cont_stmt, - post_stmt_annots, - &Some("post_function_call".to_owned()), - ); - - // assign stmt with call; then jump to bb - let place_ty = self.get_type_of_place(destination); - let place_st = self.ty_translator.translate_type_to_syn_type(place_ty.ty)?; - let place_expr = self.translate_place(destination)?; - let ot = place_st.into(); - - let annotated_rhs = radium::Expr::with_optional_annotation( - call_expr, - rhs_annots, - Some("function_call".to_owned()), - ); - let assign_stmt = radium::Stmt::Assign { - ot, - e1: place_expr, - e2: annotated_rhs, - s: Box::new(cont_stmt), - }; - radium::Stmt::with_annotations( - assign_stmt, - pre_stmt_annots, - &Some("function_call".to_owned()), - ) - }, - None => { - // expr stmt with call; then stuck (we have not provided a continuation, after all) - radium::Stmt::ExprS { - e: call_expr, - s: Box::new(radium::Stmt::Stuck), - } - }, - }; - - let mut stmt_annots = Vec::new(); - - // add annotations to initialize the regions for the call (before the call) - for (r, class) in &classification.classification { - let lft = self.format_region(*r); - match class { - CallRegionKind::EqR(r2) => { - let lft2 = self.format_region(*r2); - stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft)); - }, - - CallRegionKind::Intersection(rs) => { - match rs.len() { - 0 => { - return Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support unconstrained lifetime" - .to_owned(), - }); - }, - 1 => { - // this is really just an equality constraint - if let Some(r2) = rs.iter().next() { - let lft2 = self.format_region(*r2); - stmt_annots.push(radium::Annotation::CopyLftName(lft2, lft)); - } - }, - _ => { - // a proper intersection - let lfts: Vec<_> = rs.iter().map(|r| self.format_region(*r)).collect(); - stmt_annots.push(radium::Annotation::AliasLftIntersection(lft, lfts)); - }, - }; - }, - } - } - - let stmt = radium::Stmt::with_annotations(stmt, stmt_annots, &Some("function_call".to_owned())); - Ok(stmt) - } - - /// Translate a terminator. - /// We pass the dying loans during this terminator. They need to be added at the right - /// intermediate point. - fn translate_terminator( - &mut self, - term: &Terminator<'tcx>, - loc: Location, - dying_loans: Vec<facts::Loan>, - ) -> Result<radium::Stmt, TranslationError<'tcx>> { - match &term.kind { - TerminatorKind::Goto { target } => self.translate_goto_like(&loc, *target), - - TerminatorKind::Call { - func, - args, - destination, - target, - .. - } => { - trace!("translating Call {:?}", term); - if self.is_call_destination_panic(func) { - info!("Replacing call to std::panicking::begin_panic with Stuck"); - return Ok(radium::Stmt::Stuck); - } - - self.translate_function_call(func, args, destination, *target, loc, dying_loans.as_slice()) - }, - - TerminatorKind::Return => { - // TODO: this requires additional handling for reborrows - - // read from the return place - // Is this semantics accurate wrt what the intended MIR semantics is? - // Possibly handle this differently by making the first argument of a function a dedicated - // return place? See also discussion at https://github.com/rust-lang/rust/issues/71117 - let stmt = radium::Stmt::Return(radium::Expr::Use { - ot: (&self.return_synty).into(), - e: Box::new(radium::Expr::Var(self.return_name.clone())), - }); - - // TODO is this right? - Ok(self.prepend_endlfts(stmt, dying_loans.into_iter())) - }, - - //TerminatorKind::Abort => { - //res_stmt = radium::Stmt::Stuck; - //res_stmt = self.prepend_endlfts(res_stmt, dying_loans.into_iter()); - //}, - TerminatorKind::SwitchInt { discr, targets } => { - let operand = self.translate_operand(discr, true)?; - let all_targets: &[BasicBlock] = targets.all_targets(); - - if self.get_type_of_operand(discr).is_bool() { - // we currently special-case this as Caesium has a built-in if and this is more - // convenient to handle for the type-checker - - // implementation detail: the first index is the `false` branch, the second the - // `true` branch - let true_target = all_targets[1]; - let false_target = all_targets[0]; - - let true_branch = self.translate_goto_like(&loc, true_target)?; - let false_branch = self.translate_goto_like(&loc, false_target)?; - - let stmt = radium::Stmt::If { - e: operand, - ot: radium::OpType::Bool, - s1: Box::new(true_branch), - s2: Box::new(false_branch), - }; - - // TODO: is this right? - return Ok(self.prepend_endlfts(stmt, dying_loans.into_iter())); - } - - //info!("switchint: {:?}", term.kind); - let operand = self.translate_operand(discr, true)?; - let ty = self.get_type_of_operand(discr); - - let mut target_map: HashMap<u128, usize> = HashMap::new(); - let mut translated_targets: Vec<radium::Stmt> = Vec::new(); - - for (idx, (tgt, bb)) in targets.iter().enumerate() { - let bb: BasicBlock = bb; - let translated_target = self.translate_goto_like(&loc, bb)?; - - target_map.insert(tgt, idx); - translated_targets.push(translated_target); - } - - let translated_default = self.translate_goto_like(&loc, targets.otherwise())?; - // TODO: need to put endlfts infront of gotos? - - let translated_ty = self.ty_translator.translate_type(ty)?; - let radium::Type::Int(it) = translated_ty else { - return Err(TranslationError::UnknownError( - "SwitchInt switching on non-integer type".to_owned(), - )); - }; - - Ok(radium::Stmt::Switch { - e: operand, - it, - index_map: target_map, - bs: translated_targets, - def: Box::new(translated_default), - }) - }, - - TerminatorKind::Assert { - cond, - expected, - target, - .. - } => { - // this translation gets stuck on failure - let cond_translated = self.translate_operand(cond, true)?; - let comp = radium::Expr::BinOp { - o: radium::Binop::Eq, - ot1: radium::OpType::Bool, - ot2: radium::OpType::Bool, - e1: Box::new(cond_translated), - e2: Box::new(radium::Expr::Literal(radium::Literal::Bool(*expected))), - }; - - let stmt = self.translate_goto_like(&loc, *target)?; - - // TODO: should we really have this? - let stmt = self.prepend_endlfts(stmt, dying_loans.into_iter()); - - Ok(radium::Stmt::AssertS { - e: comp, - s: Box::new(stmt), - }) - }, - - TerminatorKind::Drop { place, target, .. } => { - let ty = self.get_type_of_place(place); - self.register_drop_shim_for(ty.ty); - - let place_translated = self.translate_place(place)?; - let _drope = radium::Expr::DropE(Box::new(place_translated)); - - let stmt = self.translate_goto_like(&loc, *target)?; - - Ok(self.prepend_endlfts(stmt, dying_loans.into_iter())) - - //res_stmt = radium::Stmt::ExprS { e: drope, s: Box::new(res_stmt)}; - }, - - // just a goto for our purposes - TerminatorKind::FalseEdge { real_target, .. } - // this is just a virtual edge for the borrowchecker, we can translate this to a normal goto - | TerminatorKind::FalseUnwind { real_target, .. } => { - self.translate_goto_like(&loc, *real_target) - }, - - TerminatorKind::Unreachable => Ok(radium::Stmt::Stuck), - - TerminatorKind::UnwindResume => Err(TranslationError::Unimplemented { - description: "implement UnwindResume".to_owned(), - }), - - TerminatorKind::UnwindTerminate(_) => Err(TranslationError::Unimplemented { - description: "implement UnwindTerminate".to_owned(), - }), - - TerminatorKind::GeneratorDrop - | TerminatorKind::InlineAsm { .. } - | TerminatorKind::Yield { .. } => Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support this kind of terminator (got: {:?})", - term - ), - }), - } - } - - /// Prepend endlft annotations for dying loans to a statement. - fn prepend_endlfts<I>(&self, st: radium::Stmt, dying: I) -> radium::Stmt - where - I: ExactSizeIterator<Item = facts::Loan>, - { - let mut cont_stmt = st; - if dying.len() > 0 { - //info!("Dying at {:?}: {:?}", loc, dying); - for d in dying { - let lft = self.info.atomic_region_of_loan(d); - cont_stmt = radium::Stmt::Annot { - a: radium::Annotation::EndLft(self.format_atomic_region(&lft)), - s: Box::new(cont_stmt), - why: Some("endlft".to_owned()), - }; - } - } - cont_stmt - } - - /// Get predecessors in the CFG. - fn get_loc_predecessors(&self, loc: Location) -> Vec<Location> { - if loc.statement_index > 0 { - let pred = Location { - block: loc.block, - statement_index: loc.statement_index - 1, - }; - vec![pred] - } else { - // check for gotos that go to this basic block - let pred_bbs = self.proc.predecessors(loc.block); - let basic_blocks = &self.proc.get_mir().basic_blocks; - pred_bbs - .iter() - .map(|bb| { - let data = &basic_blocks[*bb]; - Location { - block: *bb, - statement_index: data.statements.len(), - } - }) - .collect() - } - } - - /// Collect all the regions appearing in a type. - fn find_region_variables_of_place_type(&self, ty: PlaceTy<'tcx>) -> HashSet<Region> { - let mut collector = TyRegionCollectFolder::new(self.env.tcx()); - if ty.variant_index.is_some() { - panic!("find_region_variables_of_place_type: don't support enums"); - } - - ty.ty.fold_with(&mut collector); - collector.get_regions() - } - - /// Generate an annotation on an expression needed to update the region name map. - fn generate_strong_update_annot(&self, ty: PlaceTy<'tcx>) -> Option<radium::Annotation> { - let (interesting, tree) = self.generate_strong_update_annot_rec(ty.ty); - interesting.then(|| radium::Annotation::GetLftNames(tree)) - } - - /// Returns a tree for giving names to Coq lifetimes based on RR types. - /// The boolean indicates whether the tree is "interesting", i.e. whether it names at least one - /// lifetime. - fn generate_strong_update_annot_rec(&self, ty: Ty<'tcx>) -> (bool, radium::LftNameTree) { - // TODO for now this just handles nested references - match ty.kind() { - ty::TyKind::Ref(r, ty, _) => match r.kind() { - ty::RegionKind::ReVar(r) => { - let name = self.format_region(r); - let (_, ty_tree) = self.generate_strong_update_annot_rec(*ty); - (true, radium::LftNameTree::Ref(name, Box::new(ty_tree))) - }, - _ => { - panic!("generate_strong_update_annot: expected region variable"); - }, - }, - _ => (false, radium::LftNameTree::Leaf), - } - } - - /// Generate an annotation to adapt the type of `expr` to `target_ty` from type `current_ty` by - /// means of shortening lifetimes. - fn generate_shortenlft_annot( - &self, - target_ty: Ty<'tcx>, - _current_ty: Ty<'tcx>, - mut expr: radium::Expr, - ) -> radium::Expr { - // this is not so different from the strong update annotation - let (interesting, tree) = self.generate_strong_update_annot_rec(target_ty); - if interesting { - expr = radium::Expr::Annot { - a: radium::Annotation::ShortenLft(tree), - e: Box::new(expr), - why: None, - }; - } - expr - } - - /// Find all regions that need to outlive a loan region at its point of creation, and - /// add the corresponding constraints to the inclusion tracker. - fn get_outliving_regions_on_loan(&mut self, r: Region, loan_point: PointIndex) -> Vec<Region> { - // get all base subset constraints r' ⊆ r - let info = &self.info; - let input_facts = &info.borrowck_in_facts; - let mut outliving = Vec::new(); - - let subset_base = &input_facts.subset_base; - for (r1, r2, p) in subset_base { - if *p == loan_point && *r2 == r { - self.inclusion_tracker.add_static_inclusion(*r1, *r2, *p); - outliving.push(*r1); - } - // other subset constraints at this point are due to (for instance) the assignment of - // the loan to a place and are handled there. - } - outliving - } - - /// Check if a local is used for a spec closure. - fn is_spec_closure_local(&self, l: Local) -> Result<Option<DefId>, TranslationError<'tcx>> { - // check if we should ignore this - let local_type = self.get_type_of_local(l)?; - - let TyKind::Closure(did, _) = local_type.kind() else { - return Ok(None); - }; - - Ok(self - .procedure_registry - .lookup_function_mode(*did) - .and_then(|m| m.is_ignore().then_some(*did))) - } - - fn region_to_region_vid(r: ty::Region<'tcx>) -> facts::Region { - match r.kind() { - ty::RegionKind::ReVar(vid) => vid, - _ => panic!(), - } - } - - /// Generate a dynamic inclusion of r1 in r2 at point p. Prepends annotations for doing so to `cont`. - fn generate_dyn_inclusion( - &mut self, - stmt_annots: &mut Vec<radium::Annotation>, - r1: Region, - r2: Region, - p: PointIndex, - ) { - // check if inclusion already holds - if !self.inclusion_tracker.check_inclusion(r1, r2, p) { - // check if the reverse inclusion already holds - if self.inclusion_tracker.check_inclusion(r2, r1, p) { - // our invariant is that this must be a static inclusion - assert!(self.inclusion_tracker.check_static_inclusion(r2, r1, p)); - self.inclusion_tracker.add_dynamic_inclusion(r1, r2, p); - - // we generate an extendlft instruction - // for this, we need to figure out a path to make this inclusion true, i.e. we need - // an explanation of why it is syntactically included. - // TODO: for now, we just assume that r1 ⊑ₗ [r2] (in terms of Coq lifetime inclusion) - stmt_annots.push(radium::Annotation::ExtendLft(self.format_region(r1))); - } else { - self.inclusion_tracker.add_dynamic_inclusion(r1, r2, p); - // we generate a dynamic inclusion instruction - // we flip this around because the annotations are talking about lifetimes, which are oriented - // the other way around. - stmt_annots - .push(radium::Annotation::DynIncludeLft(self.format_region(r2), self.format_region(r1))); - } - } - } - - /// Generates dynamic inclusions for the set of inclusions in `incls`. - /// These inclusions should not hold yet. - /// Skips mutual inclusions -- we cannot interpret these. - fn generate_dyn_inclusions( - &mut self, - incls: &HashSet<(Region, Region, PointIndex)>, - ) -> Vec<radium::Annotation> { - // before executing the assignment, first enforce dynamic inclusions - info!("Generating dynamic inclusions {:?}", incls); - let mut stmt_annots = Vec::new(); - - for (r1, r2, p) in incls { - if incls.contains(&(*r2, *r1, *p)) { - warn!("Skipping impossible dynamic inclusion {:?} ⊑ {:?} at {:?}", r1, r2, p); - continue; - } - - self.generate_dyn_inclusion(&mut stmt_annots, *r1, *r2, *p); - } - - stmt_annots - } - - /// Get the annotations due to borrows appearing on the RHS of an assignment. - fn get_assignment_loan_annots(&mut self, loc: Location, rhs: &Rvalue<'tcx>) -> Vec<radium::Annotation> { - let mut stmt_annots = Vec::new(); - - // if we create a new loan here, start a new lifetime for it - let loan_point = self.info.get_point(loc, facts::PointType::Mid); - if let Some(loan) = self.info.get_optional_loan_at_location(loc) { - // TODO: is this fine for aggregates? I suppose, if I create a loan for an - // aggregate, I want to use the same atomic region for all of its components - // anyways. - - let lft = self.info.atomic_region_of_loan(loan); - let r = lft.get_region(); - - // get the static inclusions we need to generate here and add them to the - // inclusion tracker - let outliving = self.get_outliving_regions_on_loan(r, loan_point); - - // add statement for issuing the loan - stmt_annots.insert( - 0, - radium::Annotation::StartLft( - self.format_atomic_region(&lft), - outliving.iter().map(|r| self.format_region(*r)).collect(), - ), - ); - - let a = self.info.get_region_kind(r); - info!("Issuing loan at {:?} with kind {:?}: {:?}; outliving: {:?}", loc, a, loan, outliving); - } else if let Rvalue::Ref(region, BorrowKind::Shared, _) = rhs { - // for shared reborrows, Polonius does not create a new loan, and so the - // previous case did not match. - // However, we still need to track the region created for the reborrow in an - // annotation. - - let region = BodyTranslator::region_to_region_vid(*region); - - // find inclusion ?r1 ⊑ region -- we will actually enforce region = r1 - let new_constrs: Vec<(facts::Region, facts::Region)> = - self.info.get_new_subset_constraints_at_point(loan_point); - info!("Shared reborrow at {:?} with new constrs: {:?}", region, new_constrs); - let mut included_region = None; - for (r1, r2) in &new_constrs { - if *r2 == region { - included_region = Some(r1); - break; - } - } - if let Some(r) = included_region { - //info!("Found inclusion {:?}⊑ {:?}", r, region); - stmt_annots.push(radium::Annotation::CopyLftName( - self.format_region(*r), - self.format_region(region), - )); - - // also add this to the inclusion checker - self.inclusion_tracker.add_static_inclusion(*r, region, loan_point); - } else { - // This happens e.g. when borrowing from a raw pointer etc. - info!("Found unconstrained shared borrow for {:?}", region); - let inferred_constrained = vec![]; - - // add statement for issuing the loan - stmt_annots - .push(radium::Annotation::StartLft(self.format_region(region), inferred_constrained)); - } - } - - stmt_annots - } - - /// Compute the annotations for an assignment: an annotation for the rhs value, and a list of - /// annotations to prepend to the statement, and a list of annotations to put after the - /// statement. - fn get_assignment_annots( - &mut self, - loc: Location, - lhs: &Place<'tcx>, - _rhs_ty: Ty<'tcx>, - ) -> (Option<radium::Annotation>, Vec<radium::Annotation>, Vec<radium::Annotation>) { - // check if the place is strongly writeable - let strongly_writeable = !self.check_place_below_reference(lhs); - let plc_ty = self.get_type_of_place(lhs); - - let new_dyn_inclusions; - let expr_annot; - let stmt_annot; - if strongly_writeable { - // we are going to update the region mapping through annotations, - // and hence put up a barrier for propagation of region constraints - - // structurally go over the type and find region variables. - // for each of the variables, issue a barrier. - // also track them together with the PlaceItems needed to reach them. - // from the latter, we can generate the necessary annotations - let regions = self.find_region_variables_of_place_type(plc_ty); - - // put up a barrier at the Mid point - let barrier_point_index = self.info.interner.get_point_index(&facts::Point { - location: loc, - typ: facts::PointType::Mid, - }); - for r in ®ions { - self.inclusion_tracker.add_barrier(*r, barrier_point_index); - } - // get new constraints that should be enforced - let new_constraints = self.get_assignment_strong_update_constraints(loc); - stmt_annot = Vec::new(); - for (r1, r2, p) in &new_constraints { - self.inclusion_tracker.add_static_inclusion(*r1, *r2, *p); - self.inclusion_tracker.add_static_inclusion(*r2, *r1, *p); - - // TODO: use this instead of the expr_annot below - //stmt_annot.push( - //radium::Annotation::CopyLftName( - //self.format_region(*r1), - //self.format_region(*r2), - //)); - } - - // TODO: get rid of this - // similarly generate an annotation that encodes these constraints in the RR - // type system - expr_annot = self.generate_strong_update_annot(plc_ty); - //expr_annot = None; - - new_dyn_inclusions = HashSet::new(); - } else { - // need to filter out the constraints that are relevant here. - // incrementally go through them. - new_dyn_inclusions = self.get_assignment_weak_update_constraints(loc); - expr_annot = None; - stmt_annot = Vec::new(); - } - - // First enforce the new inclusions, then do the other annotations - let new_dyn_inclusions = self.generate_dyn_inclusions(&new_dyn_inclusions); - (expr_annot, new_dyn_inclusions, stmt_annot) - } - - /// Get the regions appearing in a type. - fn get_regions_of_ty(&self, ty: Ty<'tcx>) -> HashSet<ty::RegionVid> { - let mut regions = HashSet::new(); - let mut clos = |r: ty::Region<'tcx>, _| match r.kind() { - ty::RegionKind::ReVar(rv) => { - regions.insert(rv); - r - }, - _ => r, - }; - let mut folder = ty::fold::RegionFolder::new(self.env.tcx(), &mut clos); - folder.fold_ty(ty); - regions - } - - /// On creating a composite value (e.g. a struct or enum), the composite value gets its own - /// Polonius regions. We need to map these regions properly to the respective lifetimes. - fn get_composite_rvalue_creation_annots( - &mut self, - loc: Location, - rhs_ty: ty::Ty<'tcx>, - ) -> Vec<radium::Annotation> { - let info = &self.info; - let input_facts = &info.borrowck_in_facts; - let subset_base = &input_facts.subset_base; - - let regions_of_ty = self.get_regions_of_ty(rhs_ty); - - let mut annots = Vec::new(); - - // Polonius subset constraint are spawned for the midpoint - let midpoint = self.info.interner.get_point_index(&facts::Point { - location: loc, - typ: facts::PointType::Mid, - }); - - for (s1, s2, point) in subset_base { - if *point == midpoint { - let lft1 = self.info.mk_atomic_region(*s1); - let lft2 = self.info.mk_atomic_region(*s2); - - // a place lifetime is included in a value lifetime - if lft2.is_value() && lft1.is_place() { - // make sure it's not due to an assignment constraint - if regions_of_ty.contains(s2) && !subset_base.contains(&(*s2, *s1, midpoint)) { - // we enforce this inclusion by setting the lifetimes to be equal - self.inclusion_tracker.add_static_inclusion(*s1, *s2, midpoint); - self.inclusion_tracker.add_static_inclusion(*s2, *s1, midpoint); - - let annot = radium::Annotation::CopyLftName( - self.format_atomic_region(&lft1), - self.format_atomic_region(&lft2), - ); - annots.push(annot); - } - } - } - } - annots - } - - /** - * Translate a single basic block. - */ - fn translate_basic_block( - &mut self, - bb_idx: BasicBlock, - bb: &BasicBlockData<'tcx>, - ) -> Result<radium::Stmt, TranslationError<'tcx>> { - // we translate from back to front, starting with the terminator, since Caesium statements - // have a continuation (the next statement to execute) - - // first do the endlfts for the things right before the terminator - let mut idx = bb.statements.len(); - let loc = Location { - block: bb_idx, - statement_index: idx, - }; - let dying = self.info.get_dying_loans(loc); - // TODO zombie? - let _dying_zombie = self.info.get_dying_zombie_loans(loc); - let mut cont_stmt: radium::Stmt = self.translate_terminator(bb.terminator(), loc, dying)?; - - //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying); - //cont_stmt = self.prepend_endlfts(cont_stmt, loc, dying_zombie); - - for stmt in bb.statements.iter().rev() { - idx -= 1; - let loc = Location { - block: bb_idx, - statement_index: idx, - }; - - // get all dying loans, and emit endlfts for these. - // We loop over all predecessor locations, since some loans may end at the start of a - // basic block (in particular related to NLL stuff) - let pred = self.get_loc_predecessors(loc); - let mut dying_loans = HashSet::new(); - for p in pred { - let dying_between = self.info.get_loans_dying_between(p, loc, false); - for l in &dying_between { - dying_loans.insert(*l); - } - // also include zombies - let dying_between = self.info.get_loans_dying_between(p, loc, true); - for l in &dying_between { - dying_loans.insert(*l); - } - } - // we prepend them before the current statement - - match &stmt.kind { - StatementKind::Assign(b) => { - let (plc, val) = b.as_ref(); - - if (self.is_spec_closure_local(plc.local)?).is_some() { - info!("skipping assignment to spec closure local: {:?}", plc); - } else if let Some(rewritten_ty) = self.checked_op_temporaries.get(&plc.local) { - // if this is a checked op, be sure to remember it - info!("rewriting assignment to checked op: {:?}", plc); - - let synty = self.ty_translator.translate_type_to_syn_type(*rewritten_ty)?; - - let translated_val = self.translate_rvalue(loc, val)?; - let translated_place = self.translate_place(plc)?; - - // this should be a temporary - assert!(plc.projection.is_empty()); - - let ot = synty.into(); - cont_stmt = radium::Stmt::Assign { - ot, - e1: translated_place, - e2: translated_val, - s: Box::new(cont_stmt), - }; - } else { - let plc_ty = self.get_type_of_place(plc); - let rhs_ty = val.ty(&self.proc.get_mir().local_decls, self.env.tcx()); - - let borrow_annots = self.get_assignment_loan_annots(loc, val); - let (expr_annot, pre_stmt_annots, post_stmt_annots) = - self.get_assignment_annots(loc, plc, rhs_ty); - - // TODO; maybe move this to rvalue - let composite_annots = self.get_composite_rvalue_creation_annots(loc, rhs_ty); - - cont_stmt = radium::Stmt::with_annotations( - cont_stmt, - post_stmt_annots, - &Some("post-assignment".to_owned()), - ); - - let translated_val = radium::Expr::with_optional_annotation( - self.translate_rvalue(loc, val)?, - expr_annot, - Some("assignment".to_owned()), - ); - let translated_place = self.translate_place(plc)?; - let synty = self.ty_translator.translate_type_to_syn_type(plc_ty.ty)?; - cont_stmt = radium::Stmt::Assign { - ot: synty.into(), - e1: translated_place, - e2: translated_val, - s: Box::new(cont_stmt), - }; - cont_stmt = radium::Stmt::with_annotations( - cont_stmt, - pre_stmt_annots, - &Some("assignment".to_owned()), - ); - cont_stmt = radium::Stmt::with_annotations( - cont_stmt, - borrow_annots, - &Some("borrow".to_owned()), - ); - cont_stmt = radium::Stmt::with_annotations( - cont_stmt, - composite_annots, - &Some("composite".to_owned()), - ); - } - }, - - StatementKind::Deinit(_) => { - // TODO: find out where this is emitted - return Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support Deinit".to_owned(), - }); - }, - - StatementKind::FakeRead(b) => { - // we can probably ignore this, but I'm not sure - info!("Ignoring FakeRead: {:?}", b); - }, - - StatementKind::Intrinsic(intrinsic) => { - match intrinsic.as_ref() { - NonDivergingIntrinsic::Assume(_) => { - // ignore - info!("Ignoring Assume: {:?}", intrinsic); - }, - NonDivergingIntrinsic::CopyNonOverlapping(_) => { - return Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support the CopyNonOverlapping Intrinsic".to_owned(), - }); - }, - } - }, - - StatementKind::PlaceMention(place) => { - // TODO: this is missed UB - info!("Ignoring PlaceMention: {:?}", place); - }, - - StatementKind::SetDiscriminant { - place: _place, - variant_index: _variant_index, - } => { - // TODO - return Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support SetDiscriminant".to_owned(), - }); - }, - - // don't need that info - | StatementKind::AscribeUserType(_, _) - // don't need that - | StatementKind::Coverage(_) - // no-op - | StatementKind::ConstEvalCounter - // ignore - | StatementKind::Nop - // just ignore - | StatementKind::StorageLive(_) - // just ignore - | StatementKind::StorageDead(_) - // just ignore retags - | StatementKind::Retag(_, _) => (), - } - - cont_stmt = self.prepend_endlfts(cont_stmt, dying_loans.into_iter()); - } - - Ok(cont_stmt) - } - - /// Translate a `BorrowKind`. - fn translate_borrow_kind(kind: BorrowKind) -> Result<radium::BorKind, TranslationError<'tcx>> { - match kind { - BorrowKind::Shared => Ok(radium::BorKind::Shared), - BorrowKind::Shallow => { - // TODO: figure out what to do with this - // arises in match lowering - Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support shallow borrows".to_owned(), - }) - }, - BorrowKind::Mut { .. } => { - // TODO: handle two-phase borrows? - Ok(radium::BorKind::Mutable) - }, - } - } - - const fn translate_mutability(mt: Mutability) -> radium::Mutability { - match mt { - Mutability::Mut => radium::Mutability::Mut, - Mutability::Not => radium::Mutability::Shared, - } - } - - /// Get the inner type of a type to which we can apply the offset operator. - fn get_offset_ty(ty: Ty<'tcx>) -> Result<Ty<'tcx>, TranslationError<'tcx>> { - match ty.kind() { - TyKind::Array(t, _) | TyKind::Slice(t) | TyKind::Ref(_, t, _) => Ok(*t), - TyKind::RawPtr(tm) => Ok(tm.ty), - _ => Err(TranslationError::UnknownError(format!("cannot take offset of {}", ty))), - } - } - - /// Translate binary operators. - /// We need access to the operands, too, to handle the offset operator and get the right - /// Caesium layout annotation. - fn translate_binop( - &self, - op: BinOp, - e1: &Operand<'tcx>, - _e2: &Operand<'tcx>, - ) -> Result<radium::Binop, TranslationError<'tcx>> { - match op { - BinOp::Add | BinOp::AddUnchecked => Ok(radium::Binop::Add), - BinOp::Sub | BinOp::SubUnchecked => Ok(radium::Binop::Sub), - BinOp::Mul | BinOp::MulUnchecked => Ok(radium::Binop::Mul), - BinOp::Div => Ok(radium::Binop::Div), - BinOp::Rem => Ok(radium::Binop::Mod), - - BinOp::BitXor => Ok(radium::Binop::BitXor), - BinOp::BitAnd => Ok(radium::Binop::BitAnd), - BinOp::BitOr => Ok(radium::Binop::BitOr), - BinOp::Shl | BinOp::ShlUnchecked => Ok(radium::Binop::Shl), - BinOp::Shr | BinOp::ShrUnchecked => Ok(radium::Binop::Shr), - - BinOp::Eq => Ok(radium::Binop::Eq), - BinOp::Lt => Ok(radium::Binop::Lt), - BinOp::Le => Ok(radium::Binop::Le), - BinOp::Ne => Ok(radium::Binop::Ne), - BinOp::Ge => Ok(radium::Binop::Ge), - BinOp::Gt => Ok(radium::Binop::Gt), - - BinOp::Offset => { - // we need to get the layout of the thing we're offsetting - // try to get the type of e1. - let e1_ty = self.get_type_of_operand(e1); - let off_ty = BodyTranslator::get_offset_ty(e1_ty)?; - let st = self.ty_translator.translate_type_to_syn_type(off_ty)?; - let ly = st.into(); - Ok(radium::Binop::PtrOffset(ly)) - }, - } - } - - /// Translate checked binary operators. - /// We need access to the operands, too, to handle the offset operator and get the right - /// Caesium layout annotation. - fn translate_checked_binop(op: BinOp) -> Result<radium::Binop, TranslationError<'tcx>> { - match op { - BinOp::Add => Ok(radium::Binop::CheckedAdd), - BinOp::Sub => Ok(radium::Binop::CheckedSub), - BinOp::Mul => Ok(radium::Binop::CheckedMul), - BinOp::Shl => Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support checked Shl".to_owned(), - }), - BinOp::Shr => Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support checked Shr".to_owned(), - }), - _ => Err(TranslationError::UnknownError( - "unexpected checked binop that is not Add, Sub, Mul, Shl, or Shr".to_owned(), - )), - } - } - - /// Translate unary operators. - fn translate_unop(op: UnOp, ty: Ty<'tcx>) -> Result<radium::Unop, TranslationError<'tcx>> { - match op { - UnOp::Not => match ty.kind() { - ty::TyKind::Bool => Ok(radium::Unop::NotBool), - ty::TyKind::Int(_) | ty::TyKind::Uint(_) => Ok(radium::Unop::NotInt), - _ => Err(TranslationError::UnknownError( - "application of UnOp::Not to non-{Int, Bool}".to_owned(), - )), - }, - UnOp::Neg => Ok(radium::Unop::Neg), - } - } - - /// Get the type to annotate a borrow with. - fn get_type_annotation_for_borrow( - &self, - bk: BorrowKind, - pl: &Place<'tcx>, - ) -> Result<Option<radium::RustType>, TranslationError<'tcx>> { - let BorrowKind::Mut { .. } = bk else { - return Ok(None); - }; - - let ty = self.get_type_of_place(pl); - - // For borrows, we can safely ignore the downcast type -- we cannot borrow a particularly variant - let translated_ty = self.ty_translator.translate_type(ty.ty)?; - let annot_ty = radium::RustType::of_type(&translated_ty); - - Ok(Some(annot_ty)) - } - - /// Translates an Rvalue. - fn translate_rvalue( - &mut self, - loc: Location, - rval: &Rvalue<'tcx>, - ) -> Result<radium::Expr, TranslationError<'tcx>> { - match rval { - Rvalue::Use(op) => { - // converts an lvalue to an rvalue - self.translate_operand(op, true) - }, - - Rvalue::Ref(region, bk, pl) => { - let translated_pl = self.translate_place(pl)?; - let translated_bk = BodyTranslator::translate_borrow_kind(*bk)?; - let ty_annot = self.get_type_annotation_for_borrow(*bk, pl)?; - - if let Some(loan) = self.info.get_optional_loan_at_location(loc) { - let atomic_region = self.info.atomic_region_of_loan(loan); - let lft = self.format_atomic_region(&atomic_region); - Ok(radium::Expr::Borrow { - lft, - bk: translated_bk, - ty: ty_annot, - e: Box::new(translated_pl), - }) - } else { - info!("Didn't find loan at {:?}: {:?}; region {:?}", loc, rval, region); - let region = BodyTranslator::region_to_region_vid(*region); - let lft = self.format_region(region); - - Ok(radium::Expr::Borrow { - lft, - bk: translated_bk, - ty: ty_annot, - e: Box::new(translated_pl), - }) - } - }, - - Rvalue::AddressOf(mt, pl) => { - let translated_pl = self.translate_place(pl)?; - let translated_mt = BodyTranslator::translate_mutability(*mt); - - Ok(radium::Expr::AddressOf { - mt: translated_mt, - e: Box::new(translated_pl), - }) - }, - - Rvalue::BinaryOp(op, operands) => { - let e1 = &operands.as_ref().0; - let e2 = &operands.as_ref().1; - - let e1_ty = self.get_type_of_operand(e1); - let e2_ty = self.get_type_of_operand(e2); - let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?; - let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?; - - let translated_e1 = self.translate_operand(e1, true)?; - let translated_e2 = self.translate_operand(e2, true)?; - let translated_op = self.translate_binop(*op, &operands.as_ref().0, &operands.as_ref().1)?; - - Ok(radium::Expr::BinOp { - o: translated_op, - ot1: e1_st.into(), - ot2: e2_st.into(), - e1: Box::new(translated_e1), - e2: Box::new(translated_e2), - }) - }, - - Rvalue::CheckedBinaryOp(op, operands) => { - let e1 = &operands.as_ref().0; - let e2 = &operands.as_ref().1; - - let e1_ty = self.get_type_of_operand(e1); - let e2_ty = self.get_type_of_operand(e2); - let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?; - let e2_st = self.ty_translator.translate_type_to_syn_type(e2_ty)?; - - let translated_e1 = self.translate_operand(e1, true)?; - let translated_e2 = self.translate_operand(e2, true)?; - let translated_op = BodyTranslator::translate_checked_binop(*op)?; - - Ok(radium::Expr::BinOp { - o: translated_op, - ot1: e1_st.into(), - ot2: e2_st.into(), - e1: Box::new(translated_e1), - e2: Box::new(translated_e2), - }) - }, - - Rvalue::UnaryOp(op, operand) => { - let translated_e1 = self.translate_operand(operand, true)?; - let e1_ty = self.get_type_of_operand(operand); - let e1_st = self.ty_translator.translate_type_to_syn_type(e1_ty)?; - let translated_op = BodyTranslator::translate_unop(*op, e1_ty)?; - - Ok(radium::Expr::UnOp { - o: translated_op, - ot: e1_st.into(), - e: Box::new(translated_e1), - }) - }, - - Rvalue::NullaryOp(op, _ty) => { - // TODO: SizeOf - Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support nullary ops (AlignOf, Sizeof)" - .to_owned(), - }) - }, - - Rvalue::Discriminant(pl) => { - let ty = self.get_type_of_place(pl); - let translated_pl = self.translate_place(pl)?; - info!("getting discriminant of {:?} at type {:?}", pl, ty); - - let ty::TyKind::Adt(adt_def, args) = ty.ty.kind() else { - return Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support discriminant accesses on non-enum types ({:?}, got {:?})", - rval, ty.ty - ), - }); - }; - - let enum_use = self.ty_translator.generate_enum_use(*adt_def, args.iter())?; - let els = enum_use.generate_raw_syn_type_term(); - - let discriminant_acc = radium::Expr::EnumDiscriminant { - els: els.to_string(), - e: Box::new(translated_pl), - }; - - // need to do a load from this place - let it = ty.ty.discriminant_ty(self.env.tcx()); - let translated_it = self.ty_translator.translate_type(it)?; - - let radium::Type::Int(translated_it) = translated_it else { - return Err(TranslationError::UnknownError(format!( - "type of discriminant is not an integer type {:?}", - it - ))); - }; - - let ot = radium::OpType::Int(translated_it); - - Ok(radium::Expr::Use { - ot, - e: Box::new(discriminant_acc), - }) - }, - - Rvalue::Aggregate(kind, op) => { - // translate operands - let mut translated_ops: Vec<radium::Expr> = Vec::new(); - let mut operand_types: Vec<Ty<'tcx>> = Vec::new(); - - for o in op { - let translated_o = self.translate_operand(o, true)?; - let type_of_o = self.get_type_of_operand(o); - translated_ops.push(translated_o); - operand_types.push(type_of_o); - } - - match *kind { - box mir::AggregateKind::Tuple => { - if operand_types.is_empty() { - // translate to unit literal - return Ok(radium::Expr::Literal(radium::Literal::ZST)); - } - - let struct_use = - self.ty_translator.generate_tuple_use(operand_types.iter().copied())?; - let sl = struct_use.generate_raw_syn_type_term(); - let initializers: Vec<_> = - translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect(); - - Ok(radium::Expr::StructInitE { - sls: coq::term::App::new_lhs(sl.to_string()), - components: initializers, - }) - }, - - box mir::AggregateKind::Adt(did, variant, args, ..) => { - // get the adt def - let adt_def: ty::AdtDef<'tcx> = self.env.tcx().adt_def(did); - - if adt_def.is_struct() { - let variant = adt_def.variant(variant); - let struct_use = self.ty_translator.generate_struct_use(variant.def_id, args)?; - - let Some(struct_use) = struct_use else { - // if not, it's replaced by unit - return Ok(radium::Expr::Literal(radium::Literal::ZST)); - }; - - let sl = struct_use.generate_raw_syn_type_term(); - let initializers: Vec<_> = translated_ops - .into_iter() - .zip(variant.fields.iter()) - .map(|(o, field)| (field.name.to_string(), o)) - .collect(); - - return Ok(radium::Expr::StructInitE { - sls: coq::term::App::new_lhs(sl.to_string()), - components: initializers, - }); - } - - if adt_def.is_enum() { - let variant_def = adt_def.variant(variant); - - let struct_use = - self.ty_translator.generate_enum_variant_use(variant_def.def_id, args)?; - let sl = struct_use.generate_raw_syn_type_term(); - - let initializers: Vec<_> = translated_ops - .into_iter() - .zip(variant_def.fields.iter()) - .map(|(o, field)| (field.name.to_string(), o)) - .collect(); - - let variant_e = radium::Expr::StructInitE { - sls: coq::term::App::new_lhs(sl.to_string()), - components: initializers, - }; - - let enum_use = self.ty_translator.generate_enum_use(adt_def, args)?; - let els = enum_use.generate_raw_syn_type_term(); - - info!("generating enum annotation for type {:?}", enum_use); - let ty = radium::RustType::of_type(&radium::Type::Literal(enum_use)); - let variant_name = variant_def.name.to_string(); - - return Ok(radium::Expr::EnumInitE { - els: coq::term::App::new_lhs(els.to_string()), - variant: variant_name, - ty, - initializer: Box::new(variant_e), - }); - } - - // TODO - Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support aggregate rvalue for other ADTs (got: {:?})", - rval - ), - }) - }, - box mir::AggregateKind::Closure(def, _args) => { - trace!("Translating Closure aggregate value for {:?}", def); - - // We basically translate this to a tuple - if operand_types.is_empty() { - // translate to unit literal - return Ok(radium::Expr::Literal(radium::Literal::ZST)); - } - - let struct_use = - self.ty_translator.generate_tuple_use(operand_types.iter().copied())?; - let sl = struct_use.generate_raw_syn_type_term(); - - let initializers: Vec<_> = - translated_ops.into_iter().enumerate().map(|(i, o)| (i.to_string(), o)).collect(); - - Ok(radium::Expr::StructInitE { - sls: coq::term::App::new_lhs(sl.to_string()), - components: initializers, - }) - }, - - _ => { - // TODO - Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support this kind of aggregate rvalue (got: {:?})", - rval - ), - }) - }, - } - }, - - Rvalue::Cast(kind, op, to_ty) => { - let op_ty = self.get_type_of_operand(op); - let op_st = self.ty_translator.translate_type_to_syn_type(op_ty)?; - let op_ot = op_st.into(); - - let translated_op = self.translate_operand(op, true)?; - - let target_st = self.ty_translator.translate_type_to_syn_type(*to_ty)?; - let target_ot = target_st.into(); - - match kind { - mir::CastKind::PointerCoercion(x) => { - match x { - ty::adjustment::PointerCoercion::MutToConstPointer => { - // this is a NOP in our model - Ok(translated_op) - }, - - ty::adjustment::PointerCoercion::ArrayToPointer - | ty::adjustment::PointerCoercion::ClosureFnPointer(_) - | ty::adjustment::PointerCoercion::ReifyFnPointer - | ty::adjustment::PointerCoercion::UnsafeFnPointer - | ty::adjustment::PointerCoercion::Unsize => { - Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support this kind of pointer coercion (got: {:?})", - rval - ), - }) - }, - } - }, - - mir::CastKind::DynStar => Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support dyn* cast".to_owned(), - }), - - mir::CastKind::IntToInt => { - // Cast integer to integer - Ok(radium::Expr::UnOp { - o: radium::Unop::Cast(target_ot), - ot: op_ot, - e: Box::new(translated_op), - }) - }, - - mir::CastKind::IntToFloat => Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support int-to-float cast".to_owned(), - }), - - mir::CastKind::FloatToInt => Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support float-to-int cast".to_owned(), - }), - - mir::CastKind::FloatToFloat => Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does currently not support float-to-float cast".to_owned(), - }), - - mir::CastKind::PtrToPtr => { - match (op_ty.kind(), to_ty.kind()) { - (TyKind::RawPtr(_), TyKind::RawPtr(_)) => { - // Casts between raw pointers are NOPs for us - Ok(translated_op) - }, - - _ => { - // TODO: any other cases we should handle? - Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support ptr-to-ptr cast (got: {:?})", - rval - ), - }) - }, - } - }, - - mir::CastKind::FnPtrToPtr => Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support fnptr-to-ptr cast (got: {:?})", - rval - ), - }), - - mir::CastKind::Transmute => Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support transmute cast (got: {:?})", - rval - ), - }), - - mir::CastKind::PointerExposeAddress => { - // Cast pointer to integer - Ok(radium::Expr::UnOp { - o: radium::Unop::Cast(target_ot), - ot: radium::OpType::Ptr, - e: Box::new(translated_op), - }) - }, - - mir::CastKind::PointerFromExposedAddress => Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support this kind of cast (got: {:?})", - rval - ), - }), - } - }, - - Rvalue::CopyForDeref(_) - | Rvalue::Len(..) - | Rvalue::Repeat(..) - | Rvalue::ThreadLocalRef(..) - | Rvalue::ShallowInitBox(_, _) => Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support this kind of rvalue (got: {:?})", - rval - ), - }), - } - } - - /// Make a trivial place accessing `local`. - fn make_local_place(&self, local: Local) -> Place<'tcx> { - Place { - local, - projection: self.env.tcx().mk_place_elems(&[]), - } - } - - /// Translate an operand. - /// This will either generate an lvalue (in case of Move or Copy) or an rvalue (in most cases - /// of Constant). How this is used depends on the context. (e.g., Use of an integer constant - /// does not typecheck, and produces a stuck program). - fn translate_operand( - &mut self, - op: &Operand<'tcx>, - to_rvalue: bool, - ) -> Result<radium::Expr, TranslationError<'tcx>> { - match op { - // In Caesium: typed_place needs deref (not use) for place accesses. - // use is used top-level to convert an lvalue to an rvalue, which is why we use it here. - Operand::Copy(place) | Operand::Move(place) => { - // check if this goes to a temporary of a checked op - let place_kind = if self.checked_op_temporaries.contains_key(&place.local) { - assert!(place.projection.len() == 1); - - let ProjectionElem::Field(f, _0) = place.projection[0] else { - unreachable!("invariant violation for access to checked op temporary"); - }; - - if f.index() != 0 { - // make this a constant false -- our semantics directly checks for overflows - // and otherwise throws UB. - return Ok(radium::Expr::Literal(radium::Literal::Bool(false))); - } - - // access to the result of the op - self.make_local_place(place.local) - } else { - *place - }; - - let translated_place = self.translate_place(&place_kind)?; - let ty = self.get_type_of_place(place); - - let st = self.ty_translator.translate_type_to_syn_type(ty.ty)?; - - if to_rvalue { - Ok(radium::Expr::Use { - ot: st.into(), - e: Box::new(translated_place), - }) - } else { - Ok(translated_place) - } - }, - Operand::Constant(constant) => { - // TODO: possibly need different handling of the rvalue flag - // when this also handles string literals etc. - return self.translate_constant(constant.as_ref()); - }, - } - } - - /// Resolve the trait requirements of a function call. - /// The target of the call, [did], should have been resolved as much as possible, - /// as the requirements of a call can be different depending on which impl we consider. - fn resolve_trait_requirements_of_call( - &self, - did: DefId, - params: ty::GenericArgsRef<'tcx>, - ) -> Result<Vec<radium::TraitReqInst<'def, Ty<'tcx>>>, TranslationError<'tcx>> { - let mut scope = self.ty_translator.scope.borrow_mut(); - let mut state = TranslationStateInner::InFunction(&mut scope); - self.trait_registry.resolve_trait_requirements_in_state(&mut state, did, params) - } - - /// Translate the use of an `FnDef`, registering that the current function needs to link against - /// a particular monomorphization of the used function. - /// Is guaranteed to return a `radium::Expr::CallTarget` with the parameter instantiation of - /// this function annotated. - fn translate_fn_def_use(&mut self, ty: Ty<'tcx>) -> Result<radium::Expr, TranslationError<'tcx>> { - let TyKind::FnDef(defid, params) = ty.kind() else { - return Err(TranslationError::UnknownError("not a FnDef type".to_owned())); - }; - - let current_param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id()); - - // Check whether we are calling into a trait method. - // This works since we did not resolve concrete instances, so this is always an abstract - // reference to the trait. - let calling_trait = self.env.tcx().trait_of_item(*defid); - - // Check whether we are calling a plain function or a trait method - let Some(calling_trait) = calling_trait else { - // resolve the trait requirements - let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?; - - // track that we are using this function and generate the Coq location name - let (code_param_name, ty_hint, lft_hint) = - self.register_use_procedure(*defid, vec![], params, trait_spec_terms)?; - - let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); - - return Ok(radium::Expr::CallTarget(code_param_name, ty_hint, lft_hint)); - }; - - // Otherwise, we are calling a trait method - // Resolve the trait instance using trait selection - let Some((resolved_did, resolved_params, kind)) = - traits::resolve_assoc_item(self.env.tcx(), current_param_env, *defid, params) - else { - return Err(TranslationError::TraitResolution(format!("Could not resolve trait {:?}", defid))); - }; - - info!( - "Resolved trait method {:?} as {:?} with substs {:?} and kind {:?}", - defid, resolved_did, resolved_params, kind - ); - - match kind { - traits::TraitResolutionKind::UserDefined => { - // We can statically resolve the particular trait instance, - // but need to apply the spec to the instance's spec attributes - - // resolve the trait requirements - let trait_spec_terms = - self.resolve_trait_requirements_of_call(resolved_did, resolved_params)?; - - let (param_name, ty_hint, lft_hint) = - self.register_use_procedure(resolved_did, vec![], resolved_params, trait_spec_terms)?; - let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); - - Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint)) - }, - - traits::TraitResolutionKind::Param => { - // In this case, we have already applied it to the spec attribute - - // resolve the trait requirements - let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?; - - let (param_name, ty_hint, lft_hint) = - self.register_use_trait_method(resolved_did, resolved_params, trait_spec_terms)?; - let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); - - Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint)) - }, - - traits::TraitResolutionKind::Closure => { - // TODO: here, we should first generate an instance of the trait - //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::Item(resolved_did)); - //let body = self.env.tcx().instance_mir(middle::ty::InstanceDef::FnPtrShim(*defid, ty)); - //info!("closure body: {:?}", body); - - //FunctionTranslator::dump_body(body); - - //let res_result = ty::Instance::resolve(self.env.tcx(), callee_param_env, *defid, params); - //info!("Resolution {:?}", res_result); - - // the args are just the closure args. We can ignore them. - let _clos_args = resolved_params.as_closure(); - - // resolve the trait requirements - let trait_spec_terms = self.resolve_trait_requirements_of_call(*defid, params)?; - - let (param_name, ty_hint, lft_hint) = - self.register_use_procedure(resolved_did, vec![], ty::List::empty(), trait_spec_terms)?; - let ty_hint = ty_hint.into_iter().map(|x| radium::RustType::of_type(&x)).collect(); - - Ok(radium::Expr::CallTarget(param_name, ty_hint, lft_hint)) - }, - } - } - - /// Translate a scalar at a specific type to a `radium::Expr`. - // TODO: Use `TryFrom` instead - fn translate_scalar( - &mut self, - sc: &Scalar, - ty: Ty<'tcx>, - ) -> Result<radium::Expr, TranslationError<'tcx>> { - // TODO: Use `TryFrom` instead - fn translate_literal<'tcx, T, U>( - sc: Result<T, U>, - fptr: fn(T) -> radium::Literal, - ) -> Result<radium::Expr, TranslationError<'tcx>> { - sc.map_or(Err(TranslationError::InvalidLayout), |lit| Ok(radium::Expr::Literal(fptr(lit)))) - } - - match ty.kind() { - TyKind::Bool => translate_literal(sc.to_bool(), radium::Literal::Bool), - - TyKind::Int(it) => match it { - ty::IntTy::I8 => translate_literal(sc.to_i8(), radium::Literal::I8), - ty::IntTy::I16 => translate_literal(sc.to_i16(), radium::Literal::I16), - ty::IntTy::I32 => translate_literal(sc.to_i32(), radium::Literal::I32), - ty::IntTy::I128 => translate_literal(sc.to_i128(), radium::Literal::I128), - - // For Radium, the pointer size is 8 bytes - ty::IntTy::I64 | ty::IntTy::Isize => translate_literal(sc.to_i64(), radium::Literal::I64), - }, - - TyKind::Uint(it) => match it { - ty::UintTy::U8 => translate_literal(sc.to_u8(), radium::Literal::U8), - ty::UintTy::U16 => translate_literal(sc.to_u16(), radium::Literal::U16), - ty::UintTy::U32 => translate_literal(sc.to_u32(), radium::Literal::U32), - ty::UintTy::U128 => translate_literal(sc.to_u128(), radium::Literal::U128), - - // For Radium, the pointer is 8 bytes - ty::UintTy::U64 | ty::UintTy::Usize => translate_literal(sc.to_u64(), radium::Literal::U64), - }, - - TyKind::Char => translate_literal(sc.to_char(), radium::Literal::Char), - - TyKind::FnDef(_, _) => self.translate_fn_def_use(ty), - - TyKind::Tuple(tys) => { - if tys.is_empty() { - return Ok(radium::Expr::Literal(radium::Literal::ZST)); - } - - Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support compound construction of tuples using literals (got: {:?})", - ty - ), - }) - }, - - TyKind::Ref(_, _, _) => match sc { - Scalar::Int(_) => unreachable!(), - - Scalar::Ptr(pointer, _) => { - let glob_alloc = self.env.tcx().global_alloc(pointer.provenance); - match glob_alloc { - middle::mir::interpret::GlobalAlloc::Static(did) => { - info!( - "Found static GlobalAlloc {:?} for Ref scalar {:?} at type {:?}", - did, sc, ty - ); - - let Some(s) = self.const_registry.statics.get(&did) else { - return Err(TranslationError::UnknownError(format!( - "Did not find a registered static for GlobalAlloc {:?} for scalar {:?} at type {:?}; registered: {:?}", - glob_alloc, sc, ty, self.const_registry.statics - ))); - }; - - self.collected_statics.insert(did); - Ok(radium::Expr::Literal(radium::Literal::Loc(s.loc_name.clone()))) - }, - middle::mir::interpret::GlobalAlloc::Memory(alloc) => { - // TODO: this is needed - Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}", - glob_alloc, sc, ty - ), - }) - }, - _ => Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support GlobalAlloc {:?} for scalar {:?} at type {:?}", - glob_alloc, sc, ty - ), - }), - } - }, - }, - - _ => Err(TranslationError::UnsupportedFeature { - description: format!( - "RefinedRust does currently not support layout for const value: (got: {:?})", - ty - ), - }), - } - } - - /// Translate a constant value from const evaluation. - fn translate_constant_value( - &mut self, - v: mir::interpret::ConstValue<'tcx>, - ty: Ty<'tcx>, - ) -> Result<radium::Expr, TranslationError<'tcx>> { - match v { - ConstValue::Scalar(sc) => self.translate_scalar(&sc, ty), - ConstValue::ZeroSized => { - // TODO are there more special cases we need to handle somehow? - match ty.kind() { - TyKind::FnDef(_, _) => { - info!("Translating ZST val for function call target: {:?}", ty); - self.translate_fn_def_use(ty) - }, - _ => Ok(radium::Expr::Literal(radium::Literal::ZST)), - } - }, - _ => { - // TODO: do we actually care about this case or is this just something that can - // appear as part of CTFE/MIRI? - Err(TranslationError::UnsupportedFeature { - description: format!("Unsupported Constant: ConstValue; {:?}", v), - }) - }, - } - } - - /// Translate a Constant to a `radium::Expr`. - fn translate_constant( - &mut self, - constant: &Constant<'tcx>, - ) -> Result<radium::Expr, TranslationError<'tcx>> { - match constant.literal { - ConstantKind::Ty(v) => { - let const_ty = v.ty(); - - match v.kind() { - ConstKind::Value(v) => { - // this doesn't contain the necessary structure anymore. Need to reconstruct using the - // type. - match v.try_to_scalar() { - Some(sc) => self.translate_scalar(&sc, const_ty), - _ => Err(TranslationError::UnsupportedFeature { - description: format!("const value not supported: {:?}", v), - }), - } - }, - _ => Err(TranslationError::UnsupportedFeature { - description: "Unsupported ConstKind".to_owned(), - }), - } - }, - ConstantKind::Val(val, ty) => self.translate_constant_value(val, ty), - ConstantKind::Unevaluated(c, ty) => { - // call const evaluation - let param_env: ty::ParamEnv<'tcx> = self.env.tcx().param_env(self.proc.get_id()); - match self.env.tcx().const_eval_resolve(param_env, c, None) { - Ok(res) => self.translate_constant_value(res, ty), - Err(e) => match e { - ErrorHandled::Reported(_) => Err(TranslationError::UnsupportedFeature { - description: "Cannot interpret constant".to_owned(), - }), - ErrorHandled::TooGeneric => Err(TranslationError::UnsupportedFeature { - description: "Const use is too generic".to_owned(), - }), - }, - } - }, - } - } - - /// Translate a place to a Caesium lvalue. - fn translate_place(&mut self, pl: &Place<'tcx>) -> Result<radium::Expr, TranslationError<'tcx>> { - // Get the type of the underlying local. We will use this to - // get the necessary layout information for dereferencing - let mut cur_ty = self.get_type_of_local(pl.local).map(PlaceTy::from_ty)?; - - let local_name = self - .variable_map - .get(&pl.local) - .ok_or_else(|| TranslationError::UnknownVar(format!("{:?}", pl.local)))?; - - let mut acc_expr = radium::Expr::Var(local_name.to_string()); - - // iterate in evaluation order - for it in pl.projection { - match &it { - ProjectionElem::Deref => { - // use the type of the dereferencee - let st = self.ty_translator.translate_type_to_syn_type(cur_ty.ty)?; - acc_expr = radium::Expr::Deref { - ot: st.into(), - e: Box::new(acc_expr), - }; - }, - ProjectionElem::Field(f, _) => { - // `t` is the type of the field we are accessing! - let lit = self.ty_translator.generate_structlike_use(cur_ty.ty, cur_ty.variant_index)?; - // TODO: does not do the right thing for accesses to fields of zero-sized objects. - let struct_sls = lit.map_or(radium::SynType::Unit, |x| x.generate_raw_syn_type_term()); - let name = self.ty_translator.translator.get_field_name_of( - *f, - cur_ty.ty, - cur_ty.variant_index.map(abi::VariantIdx::as_usize), - )?; - - acc_expr = radium::Expr::FieldOf { - e: Box::new(acc_expr), - name, - sls: struct_sls.to_string(), - }; - }, - ProjectionElem::Index(_v) => { - //TODO - return Err(TranslationError::UnsupportedFeature { - description: "places: implement index access".to_owned(), - }); - }, - ProjectionElem::ConstantIndex { .. } => { - //TODO - return Err(TranslationError::UnsupportedFeature { - description: "places: implement const index access".to_owned(), - }); - }, - ProjectionElem::Subslice { .. } => { - return Err(TranslationError::UnsupportedFeature { - description: "places: implement subslicing".to_owned(), - }); - }, - ProjectionElem::Downcast(_, variant_idx) => { - info!("Downcast of ty {:?} to {:?}", cur_ty, variant_idx); - if let ty::TyKind::Adt(def, args) = cur_ty.ty.kind() { - if def.is_enum() { - let enum_use = self.ty_translator.generate_enum_use(*def, args.iter())?; - let els = enum_use.generate_raw_syn_type_term(); - - let variant_name = TypeTranslator::get_variant_name_of(cur_ty.ty, *variant_idx)?; - - acc_expr = radium::Expr::EnumData { - els: els.to_string(), - variant: variant_name, - e: Box::new(acc_expr), - } - } else { - return Err(TranslationError::UnknownError( - "places: ADT downcasting on non-enum type".to_owned(), - )); - } - } else { - return Err(TranslationError::UnknownError( - "places: ADT downcasting on non-enum type".to_owned(), - )); - } - }, - ProjectionElem::OpaqueCast(_) => { - return Err(TranslationError::UnsupportedFeature { - description: "places: implement opaque casts".to_owned(), - }); - }, - }; - // update cur_ty - cur_ty = cur_ty.projection_ty(self.env.tcx(), it); - } - info!("translating place {:?} to {:?}", pl, acc_expr); - Ok(acc_expr) - } - - /// Get the type of a local in a body. - fn get_type_of_local(&self, local: Local) -> Result<Ty<'tcx>, TranslationError<'tcx>> { - self.proc - .get_mir() - .local_decls - .get(local) - .map(|decl| decl.ty) - .ok_or_else(|| TranslationError::UnknownVar(String::new())) - } - - /// Get the type of a place expression. - fn get_type_of_place(&self, pl: &Place<'tcx>) -> PlaceTy<'tcx> { - pl.ty(&self.proc.get_mir().local_decls, self.env.tcx()) - } - - /// Get the type of a const. - fn get_type_of_const(cst: &Constant<'tcx>) -> Ty<'tcx> { - match cst.literal { - ConstantKind::Ty(cst) => cst.ty(), - ConstantKind::Val(_, ty) | ConstantKind::Unevaluated(_, ty) => ty, - } - } - - /// Get the type of an operand. - fn get_type_of_operand(&self, op: &Operand<'tcx>) -> Ty<'tcx> { - op.ty(&self.proc.get_mir().local_decls, self.env.tcx()) - } -} diff --git a/rr_frontend/translation/src/lib.rs b/rr_frontend/translation/src/lib.rs index 9a47e257a9cc90c6facc8965a3d19a72abf73d1e..b704a46a28b7796eb81c62c272492cd06feb37b6 100644 --- a/rr_frontend/translation/src/lib.rs +++ b/rr_frontend/translation/src/lib.rs @@ -1,4 +1,4 @@ -// © 2023, The RefinedRust Developers and Contributors +// © 2023, The RefinedRust Develcpers and Contributors // // This Source Code Form is subject to the terms of the BSD-3-clause License. // If a copy of the BSD-3-clause license was not distributed with this @@ -8,20 +8,21 @@ #![feature(box_patterns)] #![feature(let_chains)] #![feature(rustc_private)] -mod arg_folder; + +mod attrs; mod base; -mod checked_op_analysis; +mod body; +mod consts; mod data; pub mod environment; mod force_matches_macro; -mod function_body; -mod inclusion_tracker; -mod shim_registry; +mod procedures; +mod regions; +mod search; +mod shims; mod spec_parsers; -mod trait_registry; mod traits; -mod type_translator; -mod tyvars; +mod types; mod utils; use std::collections::{HashMap, HashSet}; @@ -38,14 +39,16 @@ use rr_rustc_interface::{ast, hir, span}; use topological_sort::TopologicalSort; use typed_arena::Arena; +use crate::body::signature; use crate::environment::Environment; -use crate::function_body::{ConstScope, FunctionTranslator, ProcedureMode, ProcedureScope}; +use crate::procedures::{Mode, Scope}; +use crate::shims::registry as shim_registry; use crate::spec_parsers::const_attr_parser::{ConstAttrParser, VerboseConstAttrParser}; use crate::spec_parsers::crate_attr_parser::{CrateAttrParser, VerboseCrateAttrParser}; use crate::spec_parsers::module_attr_parser::{ModuleAttrParser, ModuleAttrs, VerboseModuleAttrParser}; use crate::spec_parsers::*; -use crate::trait_registry::TraitRegistry; -use crate::type_translator::{normalize_in_function, ParamScope, TypeTranslator}; +use crate::traits::registry; +use crate::types::{normalize_in_function, scope}; /// Order ADT definitions topologically. fn order_adt_defs(deps: &HashMap<DefId, HashSet<DefId>>) -> Vec<DefId> { @@ -78,10 +81,10 @@ fn order_adt_defs(deps: &HashMap<DefId, HashSet<DefId>>) -> Vec<DefId> { pub struct VerificationCtxt<'tcx, 'rcx> { env: &'rcx Environment<'tcx>, - procedure_registry: ProcedureScope<'rcx>, - const_registry: ConstScope<'rcx>, - type_translator: &'rcx TypeTranslator<'rcx, 'tcx>, - trait_registry: &'rcx TraitRegistry<'tcx, 'rcx>, + procedure_registry: procedures::Scope<'rcx>, + const_registry: consts::Scope<'rcx>, + type_translator: &'rcx types::TX<'rcx, 'tcx>, + trait_registry: &'rcx registry::TR<'tcx, 'rcx>, functions: &'rcx [LocalDefId], fn_arena: &'rcx Arena<radium::FunctionSpec<'rcx, radium::InnerFunctionSpec<'rcx>>>, @@ -93,7 +96,7 @@ pub struct VerificationCtxt<'tcx, 'rcx> { coq_path_prefix: String, dune_package: Option<String>, - shim_registry: shim_registry::ShimRegistry<'rcx>, + shim_registry: shims::registry::SR<'rcx>, /// trait implementations we generated trait_impls: HashMap<DefId, radium::TraitImplSpec<'rcx>>, @@ -101,17 +104,20 @@ pub struct VerificationCtxt<'tcx, 'rcx> { impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { fn get_path_for_shim(&self, did: DefId) -> Vec<&str> { - let path = utils::get_export_path_for_did(self.env, did); + let path = shims::flat::get_export_path_for_did(self.env, did); let interned_path = self.shim_registry.intern_path(path); interned_path } - fn make_shim_function_entry(&self, did: DefId, spec_name: &str) -> Option<shim_registry::FunctionShim> { + fn make_shim_function_entry(&self, did: DefId, spec_name: &str) -> Option<shims::registry::FunctionShim> { let Some(mode) = self.procedure_registry.lookup_function_mode(did) else { return None; }; - if mode != ProcedureMode::Prove && mode != ProcedureMode::OnlySpec && mode != ProcedureMode::TrustMe { + if mode != procedures::Mode::Prove + && mode != procedures::Mode::OnlySpec + && mode != procedures::Mode::TrustMe + { return None; } @@ -124,7 +130,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { let is_method = self.env.tcx().impl_of_method(did).is_some(); let interned_path = self.get_path_for_shim(did); - let name = type_translator::strip_coq_ident(&self.env.get_item_name(did)); + let name = base::strip_coq_ident(&self.env.get_item_name(did)); info!("Found function path {:?} for did {:?} with name {:?}", interned_path, did, name); Some(shim_registry::FunctionShim { @@ -147,7 +153,10 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { return None; }; - if mode != ProcedureMode::Prove && mode != ProcedureMode::OnlySpec && mode != ProcedureMode::TrustMe { + if mode != procedures::Mode::Prove + && mode != procedures::Mode::OnlySpec + && mode != procedures::Mode::TrustMe + { trace!("leave make_shim_trait_method_entry (failed)"); return None; } @@ -176,11 +185,11 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { let impl_for = args[0].expect_ty(); // flatten the trait reference - let trait_path = utils::PathWithArgs::from_item(self.env, trait_did, trait_args)?; + let trait_path = shims::flat::PathWithArgs::from_item(self.env, trait_did, trait_args)?; trace!("got trait path: {:?}", trait_path); // flatten the self type. - let Some(for_type) = utils::convert_ty_to_flat_type(self.env, impl_for) else { + let Some(for_type) = shims::flat::convert_ty_to_flat_type(self.env, impl_for) else { return None; }; @@ -193,7 +202,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { }; let method_ident = ident.as_str().to_owned(); - let name = type_translator::strip_coq_ident(&self.env.get_item_name(did)); + let name = base::strip_coq_ident(&self.env.get_item_name(did)); trace!("leave make_shim_trait_method_entry (success)"); Some(shim_registry::TraitMethodImplShim { @@ -237,11 +246,11 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { let impl_for = args[0].expect_ty(); // flatten the trait reference - let trait_path = utils::PathWithArgs::from_item(self.env, trait_did, trait_args)?; + let trait_path = shims::flat::PathWithArgs::from_item(self.env, trait_did, trait_args)?; trace!("got trait path: {:?}", trait_path); // flatten the self type. - let Some(for_type) = utils::convert_ty_to_flat_type(self.env, impl_for) else { + let Some(for_type) = shims::flat::convert_ty_to_flat_type(self.env, impl_for) else { trace!("leave make_impl_shim_entry (failed transating self type)"); return None; }; @@ -296,7 +305,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { if did.is_local() && ty::Visibility::Public == self.env.tcx().visibility(did) { // only export public items let interned_path = self.get_path_for_shim(did); - let name = type_translator::strip_coq_ident(&self.env.get_item_name(did)); + let name = base::strip_coq_ident(&self.env.get_item_name(did)); info!("Found adt path {:?} for did {:?} with name {:?}", interned_path, did, name); @@ -408,7 +417,7 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { info!("Generated module summary functions: {:?}", function_shims); let interface_file = File::create(path).unwrap(); - shim_registry::write_shims( + shims::registry::write_shims( interface_file, module_path, module, @@ -913,20 +922,17 @@ impl<'tcx, 'rcx> VerificationCtxt<'tcx, 'rcx> { fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base::TranslationError<'tcx>> { for shim in vcx.shim_registry.get_function_shims() { let did = if shim.is_method { - utils::try_resolve_method_did(vcx.env.tcx(), &shim.path) + search::try_resolve_method_did(vcx.env.tcx(), &shim.path) } else { - utils::try_resolve_did(vcx.env.tcx(), &shim.path) + search::try_resolve_did(vcx.env.tcx(), &shim.path) }; match did { Some(did) => { // register as usual in the procedure registry info!("registering shim for {:?}", shim.path); - let meta = function_body::ProcedureMeta::new( - shim.spec_name.clone(), - shim.name.clone(), - function_body::ProcedureMode::Shim, - ); + let meta = + procedures::Meta::new(shim.spec_name.clone(), shim.name.clone(), procedures::Mode::Shim); vcx.procedure_registry.register_function(did, meta)?; }, _ => { @@ -936,7 +942,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base } for shim in vcx.shim_registry.get_adt_shims() { - let Some(did) = utils::try_resolve_did(vcx.env.tcx(), &shim.path) else { + let Some(did) = search::try_resolve_did(vcx.env.tcx(), &shim.path) else { println!("Warning: cannot find defid for shim {:?}, skipping", shim.path); continue; }; @@ -956,7 +962,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base } for shim in vcx.shim_registry.get_trait_shims() { - if let Some(did) = utils::try_resolve_did(vcx.env.tcx(), &shim.path) { + if let Some(did) = search::try_resolve_did(vcx.env.tcx(), &shim.path) { let assoc_tys = vcx.trait_registry.get_associated_type_names(did); let spec = radium::LiteralTraitSpec { assoc_tys, @@ -994,7 +1000,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base continue; }; - let trait_impl_did = utils::try_resolve_trait_impl_did(vcx.env.tcx(), trait_did, &args, for_type); + let trait_impl_did = search::try_resolve_trait_impl_did(vcx.env.tcx(), trait_did, &args, for_type); let Some(did) = trait_impl_did else { println!( @@ -1029,11 +1035,7 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base shim.trait_path, method_name, for_type, method_did ); - let meta = function_body::ProcedureMeta::new( - spec_name.clone(), - name.clone(), - function_body::ProcedureMode::Shim, - ); + let meta = procedures::Meta::new(spec_name.clone(), name.clone(), procedures::Mode::Shim); vcx.procedure_registry.register_function(method_did, meta)?; } @@ -1049,31 +1051,28 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base Ok(()) } -fn get_most_restrictive_function_mode( - vcx: &VerificationCtxt<'_, '_>, - did: DefId, -) -> function_body::ProcedureMode { +fn get_most_restrictive_function_mode(vcx: &VerificationCtxt<'_, '_>, did: DefId) -> procedures::Mode { let attrs = vcx.env.get_attributes_of_function(did, &propagate_method_attr_from_impl); // check if this is a purely spec function; if so, skip. - if utils::has_tool_attr_filtered(attrs.as_slice(), "shim") { - return function_body::ProcedureMode::Shim; + if attrs::has_tool_attr_filtered(attrs.as_slice(), "shim") { + return procedures::Mode::Shim; } - if utils::has_tool_attr_filtered(attrs.as_slice(), "trust_me") { - return function_body::ProcedureMode::TrustMe; + if attrs::has_tool_attr_filtered(attrs.as_slice(), "trust_me") { + return procedures::Mode::TrustMe; } - if utils::has_tool_attr_filtered(attrs.as_slice(), "only_spec") { - return function_body::ProcedureMode::OnlySpec; + if attrs::has_tool_attr_filtered(attrs.as_slice(), "only_spec") { + return procedures::Mode::OnlySpec; } - if utils::has_tool_attr_filtered(attrs.as_slice(), "ignore") { + if attrs::has_tool_attr_filtered(attrs.as_slice(), "ignore") { info!("Function {:?} will be ignored", did); - return function_body::ProcedureMode::Ignore; + return procedures::Mode::Ignore; } - function_body::ProcedureMode::Prove + procedures::Mode::Prove } /// Register functions of the crate in the procedure registry. @@ -1083,10 +1082,10 @@ fn register_functions<'tcx>( for &f in vcx.functions { let mut mode = get_most_restrictive_function_mode(vcx, f.to_def_id()); - if mode == function_body::ProcedureMode::Shim { + if mode == procedures::Mode::Shim { // TODO better error message let attrs = vcx.env.get_attributes(f.to_def_id()); - let v = utils::filter_tool_attrs(attrs); + let v = attrs::filter_for_tool(attrs); let annot = spec_parsers::get_shim_attrs(v.as_slice()).unwrap(); info!( @@ -1095,29 +1094,25 @@ fn register_functions<'tcx>( annot.spec_name, annot.code_name ); - let meta = function_body::ProcedureMeta::new( - annot.spec_name, - annot.code_name, - function_body::ProcedureMode::Shim, - ); + let meta = procedures::Meta::new(annot.spec_name, annot.code_name, procedures::Mode::Shim); vcx.procedure_registry.register_function(f.to_def_id(), meta)?; continue; } - if mode == function_body::ProcedureMode::Prove && let Some(impl_did) = vcx.env.tcx().impl_of_method(f.to_def_id()) { + if mode == procedures::Mode::Prove && let Some(impl_did) = vcx.env.tcx().impl_of_method(f.to_def_id()) { mode = get_most_restrictive_function_mode(vcx, impl_did); } - if mode == function_body::ProcedureMode::Shim { + if mode == procedures::Mode::Shim { warn!("Nonsensical shim attribute on impl; ignoring"); - mode = function_body::ProcedureMode::Prove; + mode = procedures::Mode::Prove; } - let fname = type_translator::strip_coq_ident(&vcx.env.get_item_name(f.to_def_id())); + let fname = base::strip_coq_ident(&vcx.env.get_item_name(f.to_def_id())); let spec_name = format!("type_of_{}", fname); - let meta = function_body::ProcedureMeta::new(spec_name, fname, mode); + let meta = procedures::Meta::new(spec_name, fname, mode); vcx.procedure_registry.register_function(f.to_def_id(), meta)?; } @@ -1148,7 +1143,7 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) { let ty = ty.instantiate_identity(); let translator = match ty.kind() { - ty::TyKind::FnDef(_def, _args) => FunctionTranslator::new( + ty::TyKind::FnDef(_def, _args) => signature::TX::new( vcx.env, &meta, proc, @@ -1158,7 +1153,7 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) { &vcx.procedure_registry, &vcx.const_registry, ), - ty::TyKind::Closure(_, _) => FunctionTranslator::new_closure( + ty::TyKind::Closure(_, _) => signature::TX::new_closure( vcx.env, &meta, proc, @@ -1173,7 +1168,7 @@ fn translate_functions<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) { if mode.is_only_spec() { // Only generate a spec - match translator.and_then(FunctionTranslator::generate_spec) { + match translator.and_then(signature::TX::generate_spec) { Ok(spec) => { println!("Successfully generated spec for {}", fname); let spec_ref = vcx.fn_arena.alloc(spec); @@ -1273,16 +1268,16 @@ pub fn register_consts<'rcx, 'tcx>(vcx: &mut VerificationCtxt<'tcx, 'rcx>) -> Re for s in &statics { let ty: ty::EarlyBinder<ty::Ty<'tcx>> = vcx.env.tcx().type_of(s.to_def_id()); - let const_attrs = utils::filter_tool_attrs(vcx.env.get_attributes(s.to_def_id())); + let const_attrs = attrs::filter_for_tool(vcx.env.get_attributes(s.to_def_id())); if const_attrs.is_empty() { continue; } let ty = ty.skip_binder(); - let scope = ParamScope::default(); + let scope = scope::Params::default(); match vcx.type_translator.translate_type_in_scope(&scope, ty).map_err(|x| format!("{:?}", x)) { Ok(translated_ty) => { - let _full_name = type_translator::strip_coq_ident(&vcx.env.get_item_name(s.to_def_id())); + let _full_name = base::strip_coq_ident(&vcx.env.get_item_name(s.to_def_id())); let mut const_parser = VerboseConstAttrParser::new(); let const_spec = const_parser.parse_const_attrs(*s, &const_attrs)?; @@ -1377,7 +1372,7 @@ fn register_trait_impls(vcx: &VerificationCtxt<'_, '_>) -> Result<(), String> { } // make names for the spec and inclusion proof - let base_name = type_translator::strip_coq_ident(&vcx.env.get_item_name(did)); + let base_name = base::strip_coq_ident(&vcx.env.get_item_name(did)); let spec_name = format!("{base_name}_spec"); let spec_params_name = format!("{base_name}_spec_params"); let spec_attrs_name = format!("{base_name}_spec_attrs"); @@ -1398,7 +1393,7 @@ fn register_trait_impls(vcx: &VerificationCtxt<'_, '_>) -> Result<(), String> { Ok(()) } -/// Generate trait instances +/// Generate trait instances. fn assemble_trait_impls<'tcx, 'rcx>( vcx: &mut VerificationCtxt<'tcx, 'rcx>, ) -> Result<(), base::TranslationError<'tcx>> { @@ -1459,7 +1454,7 @@ pub fn get_module_attributes(env: &Environment<'_>) -> Result<HashMap<LocalDefId info!("collected modules: {:?}", modules); for m in &modules { - let module_attrs = utils::filter_tool_attrs(env.get_attributes(m.to_def_id())); + let module_attrs = attrs::filter_for_tool(env.get_attributes(m.to_def_id())); let mut module_parser = VerboseModuleAttrParser::new(); let module_spec = module_parser.parse_module_attrs(*m, &module_attrs)?; attrs.insert(*m, module_spec); @@ -1468,42 +1463,6 @@ pub fn get_module_attributes(env: &Environment<'_>) -> Result<HashMap<LocalDefId Ok(attrs) } -/// Find `RefinedRust` modules in the given loadpath. -fn scan_loadpath(path: &Path, storage: &mut HashMap<String, PathBuf>) -> io::Result<()> { - if path.is_dir() { - for entry in fs::read_dir(path)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - scan_loadpath(path.as_path(), storage)?; - } else if path.is_file() { - if let Some(ext) = path.extension() { - if Some("rrlib") == ext.to_str() { - // try to open this rrlib file - let f = File::open(path.clone())?; - if let Some(name) = shim_registry::is_valid_refinedrust_module(f) { - storage.insert(name, path); - } - } - } - } - } - } - - Ok(()) -} - -/// Find `RefinedRust` modules in the given loadpaths. -fn scan_loadpaths(paths: &[PathBuf]) -> io::Result<HashMap<String, PathBuf>> { - let mut found_lib_files: HashMap<String, PathBuf> = HashMap::new(); - - for path in paths { - scan_loadpath(path, &mut found_lib_files)?; - } - - Ok(found_lib_files) -} - /// Translate a crate, creating a `VerificationCtxt` in the process. pub fn generate_coq_code<'tcx, F>(tcx: ty::TyCtxt<'tcx>, continuation: F) -> Result<(), String> where @@ -1514,7 +1473,7 @@ where // get crate attributes let crate_attrs = tcx.hir().krate_attrs(); - let crate_attrs = utils::filter_tool_attrs(crate_attrs); + let crate_attrs = attrs::filter_for_tool(crate_attrs); info!("Found crate attributes: {:?}", crate_attrs); // parse crate attributes let mut crate_parser = VerboseCrateAttrParser::new(); @@ -1558,8 +1517,8 @@ where let trait_impl_arena = Arena::new(); let trait_use_arena = Arena::new(); let fn_spec_arena = Arena::new(); - let type_translator = TypeTranslator::new(env, &struct_arena, &enum_arena, &shim_arena); - let trait_registry = TraitRegistry::new( + let type_translator = types::TX::new(env, &struct_arena, &enum_arena, &shim_arena); + let trait_registry = registry::TR::new( env, &type_translator, &trait_arena, @@ -1567,14 +1526,14 @@ where &trait_use_arena, &fn_spec_arena, ); - let procedure_registry = ProcedureScope::new(); + let procedure_registry = procedures::Scope::new(); let shim_string_arena = Arena::new(); - let mut shim_registry = shim_registry::ShimRegistry::empty(&shim_string_arena); + let mut shim_registry = shim_registry::SR::empty(&shim_string_arena); // add includes to the shim registry let library_load_paths = rrconfig::lib_load_paths(); info!("Loading libraries from {:?}", library_load_paths); - let found_libs = scan_loadpaths(&library_load_paths).map_err(|e| e.to_string())?; + let found_libs = shims::scan_loadpaths(&library_load_paths).map_err(|e| e.to_string())?; info!("Found the following RefinedRust libraries in the loadpath: {:?}", found_libs); for incl in &includes { @@ -1608,7 +1567,7 @@ where coq_path_prefix: path_prefix, shim_registry, dune_package: package, - const_registry: ConstScope::empty(), + const_registry: consts::Scope::empty(), trait_impls: HashMap::new(), fn_arena: &fn_spec_arena, }; diff --git a/rr_frontend/translation/src/procedures.rs b/rr_frontend/translation/src/procedures.rs new file mode 100644 index 0000000000000000000000000000000000000000..28047eb7b921891b03b68faab64082f86b50896d --- /dev/null +++ b/rr_frontend/translation/src/procedures.rs @@ -0,0 +1,183 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use std::collections::{btree_map, BTreeMap, HashMap, HashSet}; + +use rr_rustc_interface::hir::def_id::DefId; + +use crate::base::TranslationError; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Mode { + Prove, + OnlySpec, + TrustMe, + Shim, + Ignore, +} + +impl Mode { + pub fn is_prove(self) -> bool { + self == Self::Prove + } + + pub fn is_only_spec(self) -> bool { + self == Self::OnlySpec + } + + pub fn is_trust_me(self) -> bool { + self == Self::TrustMe + } + + pub fn is_shim(self) -> bool { + self == Self::Shim + } + + pub fn is_ignore(self) -> bool { + self == Self::Ignore + } + + pub fn needs_proof(self) -> bool { + self == Self::Prove + } + + pub fn needs_def(self) -> bool { + self == Self::Prove || self == Self::TrustMe + } + + pub fn needs_spec(self) -> bool { + self == Self::Prove || self == Self::TrustMe || self == Self::OnlySpec + } +} + +#[derive(Clone)] +pub struct Meta { + spec_name: String, + name: String, + mode: Mode, +} + +impl Meta { + pub const fn new(spec_name: String, name: String, mode: Mode) -> Self { + Self { + spec_name, + name, + mode, + } + } + + pub fn get_spec_name(&self) -> &str { + &self.spec_name + } + + pub fn get_name(&self) -> &str { + &self.name + } + + pub const fn get_mode(&self) -> Mode { + self.mode + } +} + +/** + * Tracks the functions we translated and the Coq names they are available under. + * To account for dependencies between functions, we may register translated names before we have + * actually translated the function. + */ +pub struct Scope<'def> { + /// maps the defid to (code_name, spec_name, name) + name_map: BTreeMap<DefId, Meta>, + /// track the actually translated functions + translated_functions: BTreeMap<DefId, radium::Function<'def>>, + /// track the functions with just a specification (rr::only_spec) + specced_functions: BTreeMap<DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>>, +} + +impl<'def> Scope<'def> { + pub fn new() -> Self { + Self { + name_map: BTreeMap::new(), + translated_functions: BTreeMap::new(), + specced_functions: BTreeMap::new(), + } + } + + /// Lookup the meta information of a function. + pub fn lookup_function(&self, did: DefId) -> Option<Meta> { + self.name_map.get(&did).cloned() + } + + /// Lookup a translated function spec + pub fn lookup_function_spec( + &self, + did: DefId, + ) -> Option<&'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> { + if let Some(translated_fn) = self.translated_functions.get(&did) { + Some(translated_fn.spec) + } else if let Some(translated_spec) = self.specced_functions.get(&did) { + Some(translated_spec) + } else { + None + } + } + + /// Lookup the Coq spec name for a function. + pub fn lookup_function_spec_name(&self, did: DefId) -> Option<&str> { + self.name_map.get(&did).map(Meta::get_spec_name) + } + + /// Lookup the name for a function. + pub fn lookup_function_mangled_name(&self, did: DefId) -> Option<&str> { + self.name_map.get(&did).map(Meta::get_name) + } + + /// Lookup the mode for a function. + pub fn lookup_function_mode(&self, did: DefId) -> Option<Mode> { + self.name_map.get(&did).map(Meta::get_mode) + } + + /// Register a function. + pub fn register_function<'tcx>(&mut self, did: DefId, meta: Meta) -> Result<(), TranslationError<'tcx>> { + if self.name_map.insert(did, meta).is_some() { + Err(TranslationError::ProcedureRegistry(format!( + "function for defid {:?} has already been registered", + did + ))) + } else { + Ok(()) + } + } + + /// Provide the code for a translated function. + pub fn provide_translated_function(&mut self, did: DefId, trf: radium::Function<'def>) { + let meta = &self.name_map[&did]; + assert!(meta.get_mode().needs_def()); + assert!(self.translated_functions.insert(did, trf).is_none()); + } + + /// Provide the specification for an `only_spec` function. + pub fn provide_specced_function( + &mut self, + did: DefId, + spec: &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>, + ) { + let meta = &self.name_map[&did]; + assert!(meta.get_mode().is_only_spec()); + assert!(self.specced_functions.insert(did, spec).is_none()); + } + + /// Iterate over the functions we have generated code for. + pub fn iter_code(&self) -> btree_map::Iter<'_, DefId, radium::Function<'def>> { + self.translated_functions.iter() + } + + /// Iterate over the functions we have generated only specs for. + pub fn iter_only_spec( + &self, + ) -> btree_map::Iter<'_, DefId, &'def radium::FunctionSpec<'def, radium::InnerFunctionSpec<'def>>> { + self.specced_functions.iter() + } +} diff --git a/rr_frontend/translation/src/arg_folder.rs b/rr_frontend/translation/src/regions/arg_folder.rs similarity index 94% rename from rr_frontend/translation/src/arg_folder.rs rename to rr_frontend/translation/src/regions/arg_folder.rs index 691b44845490009a16c1b86e2fcc60688f8723f0..cd7bee8bf1c584c8206cc8c416af6a02e22ce1c2 100644 --- a/rr_frontend/translation/src/arg_folder.rs +++ b/rr_frontend/translation/src/regions/arg_folder.rs @@ -1,13 +1,26 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + use rr_rustc_interface::middle::ty::visit::*; -use rr_rustc_interface::middle::ty::{ - self, Binder, GenericArg, GenericArgKind, ParamConst, Ty, TyCtxt, TypeFolder, -}; +use rr_rustc_interface::middle::ty::{self, GenericArg, GenericArgKind, ParamConst, Ty, TyCtxt, TypeFolder}; use rr_rustc_interface::type_ir::fold::{TypeFoldable, TypeSuperFoldable}; -use rr_rustc_interface::type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor}; + +/// Instantiate a type with arguments. +/// The type may contain open region variables `ReVar`. +pub fn ty_instantiate<'tcx>(x: Ty<'tcx>, tcx: TyCtxt<'tcx>, args: &[GenericArg<'tcx>]) -> Ty<'tcx> { + let mut folder = ArgFolder { + tcx, + args, + binders_passed: 0, + }; + x.fold_with(&mut folder) +} /// A version of the `ArgFolder` in `rr_rustc_interface::middle::src::ty::generic_args` that skips over /// `ReVar` (instead of triggering a bug). - struct ArgFolder<'a, 'tcx> { tcx: TyCtxt<'tcx>, args: &'a [GenericArg<'tcx>], @@ -211,12 +224,3 @@ impl<'a, 'tcx> ArgFolder<'a, 'tcx> { ty::fold::shift_region(self.tcx, region, self.binders_passed) } } - -pub fn ty_instantiate<'tcx>(x: Ty<'tcx>, tcx: TyCtxt<'tcx>, args: &[GenericArg<'tcx>]) -> Ty<'tcx> { - let mut folder = ArgFolder { - tcx, - args, - binders_passed: 0, - }; - x.fold_with(&mut folder) -} diff --git a/rr_frontend/translation/src/regions/assignment.rs b/rr_frontend/translation/src/regions/assignment.rs new file mode 100644 index 0000000000000000000000000000000000000000..f7583c34e2f424939256ff6e4780f4d935d20bb2 --- /dev/null +++ b/rr_frontend/translation/src/regions/assignment.rs @@ -0,0 +1,359 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Provides functionality for generating lifetime annotations for assignments. + +use std::collections::HashSet; + +use log::{info, warn}; +use rr_rustc_interface::middle::mir::tcx::PlaceTy; +use rr_rustc_interface::middle::mir::{BorrowKind, Location, Rvalue}; +use rr_rustc_interface::middle::ty; +use rr_rustc_interface::middle::ty::{Ty, TypeFoldable}; + +use crate::base::{self, Region}; +use crate::environment::borrowck::facts; +use crate::environment::polonius_info::PoloniusInfo; +use crate::environment::Environment; +use crate::regions::inclusion_tracker::{self, InclusionTracker}; +use crate::{regions, types}; + +/// Generate inclusions for a strong update assignment. +fn get_assignment_strong_update_constraints( + inclusion_tracker: &InclusionTracker<'_, '_>, + loc: Location, +) -> HashSet<(Region, Region, base::PointIndex)> { + let info = inclusion_tracker.info(); + let input_facts = &info.borrowck_in_facts; + let subset_base = &input_facts.subset_base; + + let mut constraints = HashSet::new(); + // Polonius subset constraint are spawned for the midpoint + let midpoint = info.interner.get_point_index(&facts::Point { + location: loc, + typ: facts::PointType::Mid, + }); + + // for strong update: emit mutual equalities + // TODO: alternative implementation: structurally compare regions in LHS type and RHS type + for (s1, s2, point) in subset_base { + if *point == midpoint { + let lft1 = info.mk_atomic_region(*s1); + let lft2 = info.mk_atomic_region(*s2); + + // We only care about inclusions into a place lifetime. + // Moreover, we want to filter out the universal inclusions which are always + // replicated at every point. + if lft2.is_place() && !lft1.is_universal() { + // take this constraint and the reverse constraint + constraints.insert((*s1, *s2, *point)); + //constraints.insert((*s2, *s1, *point)); + } + } + } + constraints +} + +/// Generate inclusions for a weak update assignment. +fn get_assignment_weak_update_constraints( + inclusion_tracker: &mut InclusionTracker<'_, '_>, + loc: Location, +) -> HashSet<(Region, Region, base::PointIndex)> { + let info = inclusion_tracker.info(); + let input_facts = &info.borrowck_in_facts; + let subset_base = &input_facts.subset_base; + + let mut constraints = HashSet::new(); + // Polonius subset constraint are spawned for the midpoint + let midpoint = info.interner.get_point_index(&facts::Point { + location: loc, + typ: facts::PointType::Mid, + }); + + // for weak updates: should mirror the constraints generated by Polonius + for (s1, s2, point) in subset_base { + if *point == midpoint { + // take this constraint + // TODO should there be exceptions to this? + + if !inclusion_tracker.check_inclusion(*s1, *s2, *point) { + // only add it if it does not hold already, since we will enforce this + // constraint dynamically. + constraints.insert((*s1, *s2, *point)); + } + } + } + constraints +} + +/// Get all region variables mentioned in a place type. +fn find_region_variables_of_place_type<'tcx>(env: &Environment<'tcx>, ty: PlaceTy<'tcx>) -> HashSet<Region> { + let mut collector = regions::TyRegionCollectFolder::new(env.tcx()); + if ty.variant_index.is_some() { + panic!("find_region_variables_of_place_type: don't support enums"); + } + + ty.ty.fold_with(&mut collector); + collector.get_regions() +} + +/// Compute the annotations for an assignment: an annotation for the rhs value, and a list of +/// annotations to prepend to the statement, and a list of annotations to put after the +/// statement. +pub fn get_assignment_annots<'tcx>( + env: &Environment<'tcx>, + inclusion_tracker: &mut InclusionTracker<'_, 'tcx>, + ty_translator: &types::LocalTX<'_, 'tcx>, + loc: Location, + lhs_strongly_writeable: bool, + lhs_ty: PlaceTy<'tcx>, + _rhs_ty: Ty<'tcx>, +) -> (Option<radium::Annotation>, Vec<radium::Annotation>, Vec<radium::Annotation>) { + let info = inclusion_tracker.info(); + let new_dyn_inclusions; + let expr_annot; + let stmt_annot; + if lhs_strongly_writeable { + // we are going to update the region mapping through annotations, + // and hence put up a barrier for propagation of region constraints + + // structurally go over the type and find region variables. + // for each of the variables, issue a barrier. + // also track them together with the PlaceItems needed to reach them. + // from the latter, we can generate the necessary annotations + let regions = find_region_variables_of_place_type(env, lhs_ty); + + // put up a barrier at the Mid point + let barrier_point_index = info.interner.get_point_index(&facts::Point { + location: loc, + typ: facts::PointType::Mid, + }); + for r in ®ions { + inclusion_tracker.add_barrier(*r, barrier_point_index); + } + // get new constraints that should be enforced + let new_constraints = get_assignment_strong_update_constraints(inclusion_tracker, loc); + stmt_annot = Vec::new(); + for (r1, r2, p) in &new_constraints { + inclusion_tracker.add_static_inclusion(*r1, *r2, *p); + inclusion_tracker.add_static_inclusion(*r2, *r1, *p); + + // TODO: use this instead of the expr_annot below + //stmt_annot.push( + //radium::Annotation::CopyLftName( + //self.format_region(*r1), + //self.format_region(*r2), + //)); + } + + // TODO: get rid of this + // similarly generate an annotation that encodes these constraints in the RR + // type system + expr_annot = generate_strong_update_annot(ty_translator, info, lhs_ty); + //expr_annot = None; + + new_dyn_inclusions = HashSet::new(); + } else { + // need to filter out the constraints that are relevant here. + // incrementally go through them. + new_dyn_inclusions = get_assignment_weak_update_constraints(inclusion_tracker, loc); + expr_annot = None; + stmt_annot = Vec::new(); + } + + // First enforce the new inclusions, then do the other annotations + let new_dyn_inclusions = + generate_dyn_inclusion_annots(inclusion_tracker, ty_translator, &new_dyn_inclusions); + (expr_annot, new_dyn_inclusions, stmt_annot) +} + +/// Generates dynamic inclusions for the set of inclusions in `incls`. +/// These inclusions should not hold yet. +/// Skips mutual inclusions -- we cannot interpret these. +fn generate_dyn_inclusion_annots<'tcx>( + inclusion_tracker: &mut InclusionTracker<'_, 'tcx>, + ty_translator: &types::LocalTX<'_, 'tcx>, + incls: &HashSet<(Region, Region, base::PointIndex)>, +) -> Vec<radium::Annotation> { + // before executing the assignment, first enforce dynamic inclusions + info!("Generating dynamic inclusions {:?}", incls); + + let inclusions = inclusion_tracker.generate_dyn_inclusions(incls); + + inclusions + .into_iter() + .map(|incl| match incl { + inclusion_tracker::DynamicInclusion::ExtendLft(l) => { + radium::Annotation::ExtendLft(ty_translator.format_atomic_region(&l)) + }, + inclusion_tracker::DynamicInclusion::IncludeLft(l1, l2) => radium::Annotation::DynIncludeLft( + ty_translator.format_atomic_region(&l1), + ty_translator.format_atomic_region(&l2), + ), + }) + .collect() +} + +/// Generate an annotation on an expression needed to update the region name map. +fn generate_strong_update_annot<'tcx>( + ty_translator: &types::LocalTX<'_, 'tcx>, + info: &PoloniusInfo<'_, 'tcx>, + ty: PlaceTy<'tcx>, +) -> Option<radium::Annotation> { + let (interesting, tree) = generate_strong_update_annot_rec(ty_translator, info, ty.ty); + interesting.then(|| radium::Annotation::GetLftNames(tree)) +} + +/// Returns a tree for giving names to Coq lifetimes based on RR types. +/// The boolean indicates whether the tree is "interesting", i.e. whether it names at least one +/// lifetime. +fn generate_strong_update_annot_rec<'tcx>( + ty_translator: &types::LocalTX<'_, 'tcx>, + info: &PoloniusInfo<'_, 'tcx>, + ty: Ty<'tcx>, +) -> (bool, radium::LftNameTree) { + // TODO for now this just handles nested references + match ty.kind() { + ty::TyKind::Ref(r, ty, _) => match r.kind() { + ty::RegionKind::ReVar(r) => { + let name = ty_translator.format_atomic_region(&info.mk_atomic_region(r)); + let (_, ty_tree) = generate_strong_update_annot_rec(ty_translator, info, *ty); + (true, radium::LftNameTree::Ref(name, Box::new(ty_tree))) + }, + _ => { + panic!("generate_strong_update_annot: expected region variable"); + }, + }, + _ => (false, radium::LftNameTree::Leaf), + } +} + +/// Generate an annotation to adapt the type of `expr` to `target_ty` from type `current_ty` by +/// means of shortening lifetimes. +fn generate_shortenlft_annot<'tcx>( + ty_translator: &types::LocalTX<'_, 'tcx>, + info: &PoloniusInfo<'_, 'tcx>, + target_ty: Ty<'tcx>, + _current_ty: Ty<'tcx>, + mut expr: radium::Expr, +) -> radium::Expr { + // this is not so different from the strong update annotation + let (interesting, tree) = generate_strong_update_annot_rec(ty_translator, info, target_ty); + if interesting { + expr = radium::Expr::Annot { + a: radium::Annotation::ShortenLft(tree), + e: Box::new(expr), + why: None, + }; + } + expr +} + +/// Find all regions that need to outlive a loan region at its point of creation, and +/// add the corresponding constraints to the inclusion tracker. +fn get_outliving_regions_on_loan( + inclusion_tracker: &mut InclusionTracker<'_, '_>, + r: Region, + loan_point: base::PointIndex, +) -> Vec<Region> { + // get all base subset constraints r' ⊆ r + let info = inclusion_tracker.info(); + let input_facts = &info.borrowck_in_facts; + let mut outliving = Vec::new(); + + let subset_base = &input_facts.subset_base; + for (r1, r2, p) in subset_base { + if *p == loan_point && *r2 == r { + inclusion_tracker.add_static_inclusion(*r1, *r2, *p); + outliving.push(*r1); + } + // other subset constraints at this point are due to (for instance) the assignment of + // the loan to a place and are handled there. + } + outliving +} + +/// Get the annotations due to borrows appearing on the RHS of an assignment. +pub fn get_assignment_loan_annots<'tcx>( + inclusion_tracker: &mut InclusionTracker<'_, 'tcx>, + ty_translator: &types::LocalTX<'_, 'tcx>, + loc: Location, + rhs: &Rvalue<'tcx>, +) -> Vec<radium::Annotation> { + let info = inclusion_tracker.info(); + let mut stmt_annots = Vec::new(); + + // if we create a new loan here, start a new lifetime for it + let loan_point = info.get_point(loc, facts::PointType::Mid); + if let Some(loan) = info.get_optional_loan_at_location(loc) { + // TODO: is this fine for aggregates? I suppose, if I create a loan for an + // aggregate, I want to use the same atomic region for all of its components + // anyways. + + let lft = info.atomic_region_of_loan(loan); + let r = lft.get_region(); + + // get the static inclusions we need to generate here and add them to the + // inclusion tracker + let outliving = get_outliving_regions_on_loan(inclusion_tracker, r, loan_point); + + // add statement for issuing the loan + stmt_annots.insert( + 0, + radium::Annotation::StartLft( + ty_translator.format_atomic_region(&lft), + outliving + .iter() + .map(|r| ty_translator.format_atomic_region(&info.mk_atomic_region(*r))) + .collect(), + ), + ); + + let a = info.get_region_kind(r); + info!("Issuing loan at {:?} with kind {:?}: {:?}; outliving: {:?}", loc, a, loan, outliving); + } else if let Rvalue::Ref(region, BorrowKind::Shared, _) = rhs { + // for shared reborrows, Polonius does not create a new loan, and so the + // previous case did not match. + // However, we still need to track the region created for the reborrow in an + // annotation. + + let region = regions::region_to_region_vid(*region); + + // find inclusion ?r1 ⊑ region -- we will actually enforce region = r1 + let new_constrs: Vec<(facts::Region, facts::Region)> = + info.get_new_subset_constraints_at_point(loan_point); + info!("Shared reborrow at {:?} with new constrs: {:?}", region, new_constrs); + let mut included_region = None; + for (r1, r2) in &new_constrs { + if *r2 == region { + included_region = Some(r1); + break; + } + } + if let Some(r) = included_region { + //info!("Found inclusion {:?}⊑ {:?}", r, region); + stmt_annots.push(radium::Annotation::CopyLftName( + ty_translator.format_atomic_region(&info.mk_atomic_region(*r)), + ty_translator.format_atomic_region(&info.mk_atomic_region(region)), + )); + + // also add this to the inclusion checker + inclusion_tracker.add_static_inclusion(*r, region, loan_point); + } else { + // This happens e.g. when borrowing from a raw pointer etc. + info!("Found unconstrained shared borrow for {:?}", region); + let inferred_constrained = vec![]; + + // add statement for issuing the loan + stmt_annots.push(radium::Annotation::StartLft( + ty_translator.format_atomic_region(&info.mk_atomic_region(region)), + inferred_constrained, + )); + } + } + + stmt_annots +} diff --git a/rr_frontend/translation/src/regions/calls.rs b/rr_frontend/translation/src/regions/calls.rs new file mode 100644 index 0000000000000000000000000000000000000000..8e3b93148993d1a3ad22d1f324b4035d31faf907 --- /dev/null +++ b/rr_frontend/translation/src/regions/calls.rs @@ -0,0 +1,177 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Provides functionality for generating lifetime annotations for calls. + +use std::collections::{HashMap, HashSet}; + +use derive_more::Debug; +use log::{info, warn}; +use rr_rustc_interface::middle::mir::Location; +use rr_rustc_interface::middle::ty; +use rr_rustc_interface::middle::ty::TypeFolder; + +use crate::environment::borrowck::facts; +use crate::environment::polonius_info::PoloniusInfo; +use crate::environment::{polonius_info, Environment}; +use crate::regions::inclusion_tracker::InclusionTracker; +use crate::regions::EarlyLateRegionMap; +use crate::{base, regions, types}; + +// solve the constraints for the new_regions +// we first identify what kinds of constraints these new regions are subject to +#[derive(Debug)] +pub enum CallRegionKind { + // this is just an intersection of local regions. + Intersection(HashSet<base::Region>), + // this is equal to a specific region + EqR(base::Region), +} + +pub struct CallRegions { + pub early_regions: Vec<base::Region>, + pub late_regions: Vec<base::Region>, + pub classification: HashMap<base::Region, CallRegionKind>, +} + +// `substs` are the substitutions for the early-bound regions +pub fn compute_call_regions<'tcx>( + env: &Environment<'tcx>, + incl_tracker: &InclusionTracker<'_, '_>, + substs: &[ty::GenericArg<'tcx>], + loc: Location, +) -> CallRegions { + let info = incl_tracker.info(); + + let midpoint = info.interner.get_point_index(&facts::Point { + location: loc, + typ: facts::PointType::Mid, + }); + + let mut early_regions = Vec::new(); + for a in substs { + if let ty::GenericArgKind::Lifetime(r) = a.unpack() { + if let ty::RegionKind::ReVar(r) = r.kind() { + early_regions.push(r); + } + } + } + info!("call region instantiations (early): {:?}", early_regions); + + // this is a hack to identify the inference variables introduced for the + // call's late-bound universals. + // TODO: Can we get this information in a less hacky way? + // One approach: compute the early + late bound regions for a given DefId, similarly to how + // we do it when starting to translate a function + // Problem: this doesn't give a straightforward way to compute their instantiation + + // now find all the regions that appear in type parameters we instantiate. + // These are regions that the callee doesn't know about. + let mut generic_regions = HashSet::new(); + let mut clos = |r: ty::Region<'tcx>, _| match r.kind() { + ty::RegionKind::ReVar(rv) => { + generic_regions.insert(rv); + r + }, + _ => r, + }; + + for a in substs { + if let ty::GenericArgKind::Type(c) = a.unpack() { + let mut folder = ty::fold::RegionFolder::new(env.tcx(), &mut clos); + folder.fold_ty(c); + } + } + info!("Regions of generic args: {:?}", generic_regions); + + // go over all region constraints initiated at this location + let new_constraints = info.get_new_subset_constraints_at_point(midpoint); + let mut new_regions = HashSet::new(); + let mut relevant_constraints = Vec::new(); + for (r1, r2) in &new_constraints { + if matches!(info.get_region_kind(*r1), polonius_info::RegionKind::Unknown) { + // this is probably a inference variable for the call + new_regions.insert(*r1); + relevant_constraints.push((*r1, *r2)); + } + if matches!(info.get_region_kind(*r2), polonius_info::RegionKind::Unknown) { + new_regions.insert(*r2); + relevant_constraints.push((*r1, *r2)); + } + } + // first sort this to enable cycle resolution + let mut new_regions_sorted: Vec<base::Region> = new_regions.iter().copied().collect(); + new_regions_sorted.sort(); + + // identify the late-bound regions + let mut late_regions = Vec::new(); + for r in &new_regions_sorted { + // only take the ones which are not early bound and + // which are not due to a generic (the callee doesn't care about generic regions) + if !early_regions.contains(r) && !generic_regions.contains(r) { + late_regions.push(*r); + } + } + info!("call region instantiations (late): {:?}", late_regions); + + // Notes: + // - if two of the call regions need to be equal due to constraints on the function, we define the one + // with the larger id in terms of the other one + // - we ignore unidirectional subset constraints between call regions (these do not help in finding a + // solution if we take the transitive closure beforehand) + // - if a call region needs to be equal to a local region, we directly define it in terms of the local + // region + // - otherwise, it will be an intersection of local regions + let mut new_regions_classification = HashMap::new(); + // compute transitive closure of constraints + let relevant_constraints = polonius_info::compute_transitive_closure(relevant_constraints); + for r in &new_regions_sorted { + for (r1, r2) in &relevant_constraints { + if *r2 != *r { + continue; + } + + // i.e. (flipping it around when we are talking about lifetimes), + // r needs to be a sublft of r1 + if relevant_constraints.contains(&(*r2, *r1)) { + // if r1 is also a new region and r2 is ordered before it, we will + // just define r1 in terms of r2 + if new_regions.contains(r1) && r2.as_u32() < r1.as_u32() { + continue; + } + // need an equality constraint + new_regions_classification.insert(*r, CallRegionKind::EqR(*r1)); + // do not consider the rest of the constraints as r is already + // fully specified + break; + } + + // the intersection also needs to contain r1 + if new_regions.contains(r1) { + // we do not need this constraint, since we already computed the + // transitive closure. + continue; + } + + let kind = new_regions_classification + .entry(*r) + .or_insert(CallRegionKind::Intersection(HashSet::new())); + + let CallRegionKind::Intersection(s) = kind else { + unreachable!(); + }; + + s.insert(*r1); + } + } + info!("call arg classification: {:?}", new_regions_classification); + + CallRegions { + early_regions, + late_regions, + classification: new_regions_classification, + } +} diff --git a/rr_frontend/translation/src/regions/composite.rs b/rr_frontend/translation/src/regions/composite.rs new file mode 100644 index 0000000000000000000000000000000000000000..2865cc38f28adde37f50d1fedbe1e5643bf12304 --- /dev/null +++ b/rr_frontend/translation/src/regions/composite.rs @@ -0,0 +1,82 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Provides functionality for generating lifetime annotations for composite expressions. + +use std::collections::HashSet; + +use log::{info, warn}; +use rr_rustc_interface::middle::mir::Location; +use rr_rustc_interface::middle::ty; +use rr_rustc_interface::middle::ty::{Ty, TypeFolder}; + +use crate::environment::borrowck::facts; +use crate::environment::Environment; +use crate::regions::inclusion_tracker::InclusionTracker; +use crate::types; + +/// On creating a composite value (e.g. a struct or enum), the composite value gets its own +/// Polonius regions We need to map these regions properly to the respective lifetimes. +pub fn get_composite_rvalue_creation_annots<'tcx>( + env: &Environment<'tcx>, + inclusion_tracker: &mut InclusionTracker<'_, 'tcx>, + ty_translator: &types::LocalTX<'_, 'tcx>, + loc: Location, + rhs_ty: ty::Ty<'tcx>, +) -> Vec<radium::Annotation> { + let info = inclusion_tracker.info(); + let input_facts = &info.borrowck_in_facts; + let subset_base = &input_facts.subset_base; + + let regions_of_ty = get_regions_of_ty(env, rhs_ty); + + let mut annots = Vec::new(); + + // Polonius subset constraint are spawned for the midpoint + let midpoint = info.interner.get_point_index(&facts::Point { + location: loc, + typ: facts::PointType::Mid, + }); + + for (s1, s2, point) in subset_base { + if *point == midpoint { + let lft1 = info.mk_atomic_region(*s1); + let lft2 = info.mk_atomic_region(*s2); + + // a place lifetime is included in a value lifetime + if lft2.is_value() && lft1.is_place() { + // make sure it's not due to an assignment constraint + if regions_of_ty.contains(s2) && !subset_base.contains(&(*s2, *s1, midpoint)) { + // we enforce this inclusion by setting the lifetimes to be equal + inclusion_tracker.add_static_inclusion(*s1, *s2, midpoint); + inclusion_tracker.add_static_inclusion(*s2, *s1, midpoint); + + let annot = radium::Annotation::CopyLftName( + ty_translator.format_atomic_region(&lft1), + ty_translator.format_atomic_region(&lft2), + ); + annots.push(annot); + } + } + } + } + annots +} + +/// Get the regions appearing in a type. +fn get_regions_of_ty<'tcx>(env: &Environment<'tcx>, ty: Ty<'tcx>) -> HashSet<ty::RegionVid> { + let mut regions = HashSet::new(); + let mut clos = |r: ty::Region<'tcx>, _| match r.kind() { + ty::RegionKind::ReVar(rv) => { + regions.insert(rv); + r + }, + _ => r, + }; + let mut folder = ty::fold::RegionFolder::new(env.tcx(), &mut clos); + folder.fold_ty(ty); + regions +} diff --git a/rr_frontend/translation/src/inclusion_tracker.rs b/rr_frontend/translation/src/regions/inclusion_tracker.rs similarity index 72% rename from rr_frontend/translation/src/inclusion_tracker.rs rename to rr_frontend/translation/src/regions/inclusion_tracker.rs index 964280cfb5376fed11a33fe0f579f7e5087bb256..46c87c120f3fc4197e5d761360a5a056f42fb113 100644 --- a/rr_frontend/translation/src/inclusion_tracker.rs +++ b/rr_frontend/translation/src/regions/inclusion_tracker.rs @@ -4,8 +4,17 @@ // If a copy of the BSD-3-clause license was not distributed with this // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. +use std::collections::HashSet; + +use log::{info, warn}; + use crate::base::*; -use crate::environment::polonius_info::PoloniusInfo; +use crate::environment::polonius_info::{self, PoloniusInfo}; + +pub enum DynamicInclusion { + ExtendLft(polonius_info::AtomicRegion), + IncludeLft(polonius_info::AtomicRegion, polonius_info::AtomicRegion), +} /// The `InclusionTracker` maintains a set of dynamic lifetime inclusions holding in the `RefinedRust` /// type system at given program points. @@ -48,6 +57,10 @@ impl<'a, 'tcx: 'a> InclusionTracker<'a, 'tcx> { } } + pub const fn info(&self) -> &'a PoloniusInfo<'a, 'tcx> { + self.info + } + /// Add a basic static inclusion fact. pub fn add_static_inclusion(&mut self, a: Region, b: Region, p: PointIndex) { self.static_incl_base.push((a, b, p)); @@ -167,4 +180,62 @@ impl<'a, 'tcx: 'a> InclusionTracker<'a, 'tcx> { } self.static_incl.as_ref().unwrap().contains(&(r1, r2, p)) || r1 == r2 } + + /// Generate a dynamic inclusion of r1 in r2 at point p. Prepends annotations for doing so to + /// `stmt_annots`. + fn generate_dyn_inclusion( + &mut self, + stmt_annots: &mut Vec<DynamicInclusion>, + r1: Region, + r2: Region, + p: PointIndex, + ) { + // check if inclusion already holds + if !self.check_inclusion(r1, r2, p) { + // check if the reverse inclusion already holds + if self.check_inclusion(r2, r1, p) { + // our invariant is that this must be a static inclusion + assert!(self.check_static_inclusion(r2, r1, p)); + self.add_dynamic_inclusion(r1, r2, p); + + // we generate an extendlft instruction + // for this, we need to figure out a path to make this inclusion true, i.e. we need + // an explanation of why it is syntactically included. + // TODO: for now, we just assume that r1 ⊑ₗ [r2] (in terms of Coq lifetime inclusion) + stmt_annots.push(DynamicInclusion::ExtendLft(self.info.mk_atomic_region(r1))); + } else { + self.add_dynamic_inclusion(r1, r2, p); + // we generate a dynamic inclusion instruction + // we flip this around because the annotations are talking about lifetimes, which are oriented + // the other way around. + stmt_annots.push(DynamicInclusion::IncludeLft( + self.info.mk_atomic_region(r2), + self.info.mk_atomic_region(r1), + )); + } + } + } + + /// Generates dynamic inclusions for the set of inclusions in `incls`. + /// These inclusions should not hold yet. + /// Skips mutual inclusions -- we cannot interpret these. + pub fn generate_dyn_inclusions( + &mut self, + incls: &HashSet<(Region, Region, PointIndex)>, + ) -> Vec<DynamicInclusion> { + // before executing the assignment, first enforce dynamic inclusions + info!("Generating dynamic inclusions {:?}", incls); + let mut stmt_annots = Vec::new(); + + for (r1, r2, p) in incls { + if incls.contains(&(*r2, *r1, *p)) { + warn!("Skipping impossible dynamic inclusion {:?} ⊑ {:?} at {:?}", r1, r2, p); + continue; + } + + self.generate_dyn_inclusion(&mut stmt_annots, *r1, *r2, *p); + } + + stmt_annots + } } diff --git a/rr_frontend/translation/src/regions/init.rs b/rr_frontend/translation/src/regions/init.rs new file mode 100644 index 0000000000000000000000000000000000000000..571131f5274dc5d3b4da3695aeb6adc9edaa1e87 --- /dev/null +++ b/rr_frontend/translation/src/regions/init.rs @@ -0,0 +1,272 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Provides functionality for generating initial lifetime constraints. + +use std::collections::{BTreeMap, HashMap}; + +use log::{info, warn}; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::mir::{BasicBlock, Location}; +use rr_rustc_interface::middle::ty; +use rr_rustc_interface::middle::ty::Ty; + +use crate::base; +use crate::environment::borrowck::facts; +use crate::environment::polonius_info::PoloniusInfo; +use crate::environment::{polonius_info, Environment}; +use crate::regions::arg_folder::ty_instantiate; +use crate::regions::inclusion_tracker::InclusionTracker; +use crate::regions::EarlyLateRegionMap; + +/// Process the signature of a function by instantiating the region variables with their +/// Polonius variables. +/// Returns the argument and return type signature instantiated in this way. +/// Moreover, returns a `EarlyLateRegionMap` that contains the mapping of indices to Polonius +/// region variables. +pub fn replace_fnsig_args_with_polonius_vars<'tcx>( + env: &Environment<'tcx>, + params: &[ty::GenericArg<'tcx>], + sig: ty::Binder<'tcx, ty::FnSig<'tcx>>, +) -> (Vec<ty::Ty<'tcx>>, ty::Ty<'tcx>, EarlyLateRegionMap) { + // a mapping of Polonius region IDs to names + let mut universal_lifetimes = BTreeMap::new(); + let mut lifetime_names = HashMap::new(); + + let mut region_substitution_early: Vec<Option<ty::RegionVid>> = Vec::new(); + + // we create a substitution that replaces early bound regions with their Polonius + // region variables + let mut subst_early_bounds: Vec<ty::GenericArg<'tcx>> = Vec::new(); + let mut num_early_bounds = 0; + for a in params { + if let ty::GenericArgKind::Lifetime(r) = a.unpack() { + // skip over 0 = static + let next_id = ty::RegionVid::from_u32(num_early_bounds + 1); + let revar = ty::Region::new_var(env.tcx(), next_id); + num_early_bounds += 1; + subst_early_bounds.push(ty::GenericArg::from(revar)); + + region_substitution_early.push(Some(next_id)); + + match *r { + ty::RegionKind::ReEarlyBound(r) => { + let name = base::strip_coq_ident(r.name.as_str()); + universal_lifetimes.insert(next_id, format!("ulft_{}", name)); + lifetime_names.insert(name, next_id); + }, + _ => { + universal_lifetimes.insert(next_id, format!("ulft_{}", num_early_bounds)); + }, + } + } else { + subst_early_bounds.push(*a); + region_substitution_early.push(None); + } + } + let subst_early_bounds = env.tcx().mk_args(&subst_early_bounds); + + info!("Computed early region map {region_substitution_early:?}"); + + // add names for late bound region variables + let mut num_late_bounds = 0; + let mut region_substitution_late = Vec::new(); + for b in sig.bound_vars() { + let next_id = ty::RegionVid::from_u32(num_early_bounds + num_late_bounds + 1); + + let ty::BoundVariableKind::Region(r) = b else { + continue; + }; + + region_substitution_late.push(next_id); + + match r { + ty::BoundRegionKind::BrNamed(_, sym) => { + let mut region_name = base::strip_coq_ident(sym.as_str()); + if region_name == "_" { + region_name = next_id.as_usize().to_string(); + universal_lifetimes.insert(next_id, format!("ulft_{}", region_name)); + } else { + universal_lifetimes.insert(next_id, format!("ulft_{}", region_name)); + lifetime_names.insert(region_name, next_id); + } + }, + ty::BoundRegionKind::BrAnon(_) => { + universal_lifetimes.insert(next_id, format!("ulft_{}", next_id.as_usize())); + }, + _ => (), + } + + num_late_bounds += 1; + } + + // replace late-bound region variables by re-enumerating them in the same way as the MIR + // type checker does (that this happens in the same way is important to make the names + // line up!) + let mut next_index = num_early_bounds + 1; // skip over one additional due to static + let mut folder = |_| { + let cur_index = next_index; + next_index += 1; + ty::Region::new_var(env.tcx(), ty::RegionVid::from_u32(cur_index)) + }; + let (late_sig, _late_region_map) = env.tcx().replace_late_bound_regions(sig, &mut folder); + + // replace early bound variables + let inputs: Vec<_> = late_sig + .inputs() + .iter() + .map(|ty| ty_instantiate(*ty, env.tcx(), subst_early_bounds)) + .collect(); + + let output = ty_instantiate(late_sig.output(), env.tcx(), subst_early_bounds); + + info!("Computed late region map {region_substitution_late:?}"); + + let region_map = EarlyLateRegionMap::new( + region_substitution_early, + region_substitution_late, + universal_lifetimes, + lifetime_names, + ); + (inputs, output, region_map) +} + +/// At the start of the function, there's a universal (placeholder) region for reference argument. +/// These get subsequently relabeled. +/// Given the relabeled region, find the original placeholder region. +pub fn find_placeholder_region_for(r: ty::RegionVid, info: &PoloniusInfo) -> Option<ty::RegionVid> { + let root_location = Location { + block: BasicBlock::from_u32(0), + statement_index: 0, + }; + let root_point = info.interner.get_point_index(&facts::Point { + location: root_location, + typ: facts::PointType::Start, + }); + + for (r1, r2, p) in &info.borrowck_in_facts.subset_base { + if *p == root_point && *r2 == r { + info!("find placeholder region for: {:?} ⊑ {:?} = r = {:?}", r1, r2, r); + return Some(*r1); + } + } + None +} + +/// Filter the "interesting" constraints between universal lifetimes that need to hold +/// (this does not include the constraints that need to hold for all universal lifetimes, +/// e.g. that they outlive the function lifetime and are outlived by 'static). +pub fn get_relevant_universal_constraints<'a>( + lifetime_scope: &EarlyLateRegionMap, + inclusion_tracker: &mut InclusionTracker, + info: &'a PoloniusInfo<'a, '_>, +) -> Vec<(radium::UniversalLft, radium::UniversalLft)> { + let input_facts = &info.borrowck_in_facts; + let placeholder_subset = &input_facts.known_placeholder_subset; + + let root_location = Location { + block: BasicBlock::from_u32(0), + statement_index: 0, + }; + let root_point = info.interner.get_point_index(&facts::Point { + location: root_location, + typ: facts::PointType::Start, + }); + + let mut universal_constraints = Vec::new(); + + for (r1, r2) in placeholder_subset { + if let polonius_info::RegionKind::Universal(uk1) = info.get_region_kind(*r1) { + if let polonius_info::RegionKind::Universal(uk2) = info.get_region_kind(*r2) { + // if LHS is static, ignore. + if uk1 == polonius_info::UniversalRegionKind::Static { + continue; + } + // if RHS is the function lifetime, ignore + if uk2 == polonius_info::UniversalRegionKind::Function { + continue; + } + + let to_universal = || { + let x = lifetime_scope.lookup_region_with_kind(uk1, *r2)?; + let y = lifetime_scope.lookup_region_with_kind(uk2, *r1)?; + Some((x, y)) + }; + // else, add this constraint + // for the purpose of this analysis, it is fine to treat it as a dynamic inclusion + if let Some((x, y)) = to_universal() { + inclusion_tracker.add_dynamic_inclusion(*r1, *r2, root_point); + universal_constraints.push((x, y)); + }; + } + } + } + universal_constraints +} + +/// Determine initial constraints between universal regions and local place regions. +/// Returns an initial mapping for the name _map that initializes place regions of arguments +/// with universals. +pub fn get_initial_universal_arg_constraints<'a, 'tcx>( + info: &'a PoloniusInfo<'a, 'tcx>, + inclusion_tracker: &mut InclusionTracker<'a, 'tcx>, + _sig_args: &[Ty<'tcx>], + _local_args: &[Ty<'tcx>], +) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> { + // Polonius generates a base subset constraint uregion ⊑ pregion. + // We turn that into pregion = uregion, as we do strong updates at the top-level. + let input_facts = &info.borrowck_in_facts; + let subset_base = &input_facts.subset_base; + + let root_location = Location { + block: BasicBlock::from_u32(0), + statement_index: 0, + }; + let root_point = info.interner.get_point_index(&facts::Point { + location: root_location, + typ: facts::PointType::Start, + }); + + // TODO: for nested references, this doesn't really seem to work. + // Problem is that we don't have constraints for the mapping of nested references. + // Potentially, we should instead just equalize the types + + let mut initial_arg_mapping = Vec::new(); + for (r1, r2, _) in subset_base { + let lft1 = info.mk_atomic_region(*r1); + let lft2 = info.mk_atomic_region(*r2); + + let polonius_info::AtomicRegion::Universal(polonius_info::UniversalRegionKind::Local, _) = lft1 + else { + continue; + }; + + // this is a constraint we care about here, add it + if inclusion_tracker.check_inclusion(*r1, *r2, root_point) { + continue; + } + + inclusion_tracker.add_static_inclusion(*r1, *r2, root_point); + inclusion_tracker.add_static_inclusion(*r2, *r1, root_point); + + assert!(matches!(lft2, polonius_info::AtomicRegion::PlaceRegion(_))); + + initial_arg_mapping.push((lft1, lft2)); + } + initial_arg_mapping +} + +pub fn get_initial_universal_arg_constraints2<'tcx>( + sig_args: &[Ty<'tcx>], + local_args: &[Ty<'tcx>], +) -> Vec<(polonius_info::AtomicRegion, polonius_info::AtomicRegion)> { + // Polonius generates a base subset constraint uregion ⊑ pregion. + // We turn that into pregion = uregion, as we do strong updates at the top-level. + assert!(sig_args.len() == local_args.len()); + + // TODO: implement a bitypefolder to solve this issue. + Vec::new() +} diff --git a/rr_frontend/translation/src/regions/mod.rs b/rr_frontend/translation/src/regions/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3a6039d326cc15413ec3307a569ebfcbe58851bf --- /dev/null +++ b/rr_frontend/translation/src/regions/mod.rs @@ -0,0 +1,151 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Utilities for translating region information. + +mod arg_folder; +pub mod assignment; +pub mod calls; +pub mod composite; +pub mod inclusion_tracker; +pub mod init; + +use std::collections::{BTreeMap, HashMap, HashSet}; + +use derive_more::{Constructor, Debug}; +use log::{info, warn}; +use rr_rustc_interface::middle::ty; +use rr_rustc_interface::middle::ty::TyCtxt; +use ty::TypeSuperFoldable; + +use crate::base::Region; +use crate::environment::borrowck::facts; +use crate::environment::polonius_info; + +/// Collect all the regions appearing in a type. +/// Data structure that maps early and late region indices inside functions to Polonius regions. +#[derive(Constructor, Clone, Debug, Default)] +pub struct EarlyLateRegionMap { + // maps indices of early and late regions to Polonius region ids + pub early_regions: Vec<Option<ty::RegionVid>>, + pub late_regions: Vec<ty::RegionVid>, + + // maps Polonius region ids to names + pub region_names: BTreeMap<ty::RegionVid, radium::Lft>, + + // maps source-level universal lifetime names to region ids + pub lft_names: HashMap<String, ty::RegionVid>, +} +impl EarlyLateRegionMap { + /// Lookup a Polonius region with a given kind. + pub fn lookup_region_with_kind( + &self, + k: polonius_info::UniversalRegionKind, + r: Region, + ) -> Option<radium::UniversalLft> { + match k { + polonius_info::UniversalRegionKind::Function => Some(radium::UniversalLft::Function), + polonius_info::UniversalRegionKind::Static => Some(radium::UniversalLft::Static), + + polonius_info::UniversalRegionKind::Local => { + self.lookup_region(r).map(|x| radium::UniversalLft::Local(x.to_owned())) + }, + + polonius_info::UniversalRegionKind::External => { + self.lookup_region(r).map(|x| radium::UniversalLft::External(x.to_owned())) + }, + } + } + + pub fn lookup_region(&self, region: ty::RegionVid) -> Option<&radium::Lft> { + self.region_names.get(®ion) + } + + pub fn lookup_early_region(&self, idx: usize) -> Option<&radium::Lft> { + let ovid = self.early_regions.get(idx)?; + let vid = ovid.as_ref()?; + self.lookup_region(*vid) + } + + pub fn lookup_late_region(&self, idx: usize) -> Option<&radium::Lft> { + let vid = self.late_regions.get(idx)?; + self.lookup_region(*vid) + } + + pub fn translate_atomic_region(&self, r: &polonius_info::AtomicRegion) -> radium::Lft { + format_atomic_region_direct(r, Some(self)) + } +} + +/// Format the Coq representation of an atomic region. +pub fn format_atomic_region_direct( + r: &polonius_info::AtomicRegion, + scope: Option<&EarlyLateRegionMap>, +) -> String { + match r { + polonius_info::AtomicRegion::Loan(_, r) => format!("llft{}", r.index()), + polonius_info::AtomicRegion::PlaceRegion(r) => format!("plft{}", r.index()), + polonius_info::AtomicRegion::Unknown(r) => format!("vlft{}", r.index()), + + polonius_info::AtomicRegion::Universal(_, r) => { + let Some(scope) = scope else { + return format!("ulft{}", r.index()); + }; + + let Some(s) = scope.lookup_region(*r) else { + return format!("ulft{}", r.index()); + }; + + s.to_string() + }, + } +} + +pub fn region_to_region_vid(r: ty::Region<'_>) -> facts::Region { + match r.kind() { + ty::RegionKind::ReVar(vid) => vid, + _ => panic!(), + } +} + +/// A `TypeFolder` that finds all regions occurring in a type. +pub struct TyRegionCollectFolder<'tcx> { + tcx: TyCtxt<'tcx>, + regions: HashSet<Region>, +} +impl<'tcx> TyRegionCollectFolder<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>) -> Self { + TyRegionCollectFolder { + tcx, + regions: HashSet::new(), + } + } + + pub fn get_regions(self) -> HashSet<Region> { + self.regions + } +} +impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for TyRegionCollectFolder<'tcx> { + fn interner(&self) -> TyCtxt<'tcx> { + self.tcx + } + + // TODO: handle the case that we pass below binders + fn fold_binder<T>(&mut self, t: ty::Binder<'tcx, T>) -> ty::Binder<'tcx, T> + where + T: ty::TypeFoldable<TyCtxt<'tcx>>, + { + t.super_fold_with(self) + } + + fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { + if let ty::RegionKind::ReVar(r) = r.kind() { + self.regions.insert(r); + } + + r + } +} diff --git a/rr_frontend/translation/src/search.rs b/rr_frontend/translation/src/search.rs new file mode 100644 index 0000000000000000000000000000000000000000..f50038c4c5617380bfbb1ce7ed810df066d8aeb8 --- /dev/null +++ b/rr_frontend/translation/src/search.rs @@ -0,0 +1,318 @@ +// These functions have been adapted from Miri (https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48) under the MIT license. + +//! Utility functions for finding Rust source objects. + +use std::mem; + +use log::{info, trace}; +use rr_rustc_interface::hir::def_id::{DefId, CRATE_DEF_INDEX}; +use rr_rustc_interface::middle::ty::{self, TyCtxt}; +use rr_rustc_interface::{middle, span}; + +use crate::types; + +/// Gets an instance for a path. +/// Taken from Miri <https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48>. +pub fn try_resolve_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> +where + T: AsRef<str>, +{ + tcx.crates(()) + .iter() + .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref()) + .and_then(|krate| { + let krate = DefId { + krate: *krate, + index: CRATE_DEF_INDEX, + }; + + let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate); + let mut path_it = path.iter().skip(1).peekable(); + + while let Some(segment) = path_it.next() { + for item in mem::take(&mut items) { + let item: &middle::metadata::ModChild = item; + if item.ident.name.as_str() == segment.as_ref() { + if path_it.peek().is_none() { + return Some(item.res.def_id()); + } + + items = tcx.module_children(item.res.def_id()); + break; + } + } + } + None + }) +} + +pub fn try_resolve_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> +where + T: AsRef<str>, +{ + if let Some(did) = try_resolve_did_direct(tcx, path) { + return Some(did); + } + + // if the first component is "std", try if we can replace it with "alloc" or "core" + if path[0].as_ref() == "std" { + let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect(); + components[0] = "core".to_owned(); + if let Some(did) = try_resolve_did_direct(tcx, &components) { + return Some(did); + } + // try "alloc" + components[0] = "alloc".to_owned(); + try_resolve_did_direct(tcx, &components) + } else { + None + } +} + +/// Determine whether the two argument lists match for the type positions (ignoring consts and regions). +/// The first argument is the authority determining which argument positions are types. +/// The second argument may contain `None` for non-type positions. +fn args_match_types<'tcx>( + reference: &[ty::GenericArg<'tcx>], + compare: &[Option<ty::GenericArg<'tcx>>], +) -> bool { + if reference.len() != compare.len() { + return false; + } + + for (arg1, arg2) in reference.iter().zip(compare.iter()) { + if let Some(ty1) = arg1.as_type() { + if let Some(arg2) = arg2 { + if let Some(ty2) = arg2.as_type() { + if ty1 != ty2 { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + } + true +} + +/// Try to resolve the `DefId` of an implementation of a trait for a particular type. +/// Note that this does not, in general, find a unique solution, in case there are complex things +/// with different where clauses going on. +pub fn try_resolve_trait_impl_did<'tcx>( + tcx: TyCtxt<'tcx>, + trait_did: DefId, + trait_args: &[Option<ty::GenericArg<'tcx>>], + for_type: ty::Ty<'tcx>, +) -> Option<DefId> { + // get all impls of this trait + let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did); + + let simplified_type = + middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?; + let defs = impls.non_blanket_impls().get(&simplified_type)?; + info!("found implementations: {:?}", impls); + + let mut solution = None; + for did in defs { + let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity(); + let impl_self_ty = types::normalize_in_function(*did, tcx, impl_self_ty).unwrap(); + + // check if this is an implementation for the right type + // TODO: is this the right way to compare the types? + if impl_self_ty == for_type { + let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did); + + if let Some(impl_ref) = impl_ref { + let impl_ref = types::normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap(); + + let this_impl_args = impl_ref.args; + // filter by the generic instantiation for the trait + info!("found impl with args {:?}", this_impl_args); + // args has self at position 0 and generics of the trait at position 1.. + + // check if the generic argument types match up + if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) { + continue; + } + + info!("found impl {:?}", impl_ref); + if solution.is_some() { + println!( + "Warning: Ambiguous resolution for impl of trait {:?} on type {:?}; solution {:?} but found also {:?}", + trait_did, + for_type, + solution.unwrap(), + impl_ref.def_id, + ); + } else { + solution = Some(*did); + } + } + } + } + + solution +} + +/// Try to resolve the `DefId` of a method in an implementation of a trait for a particular type. +/// Note that this does not, in general, find a unique solution, in case there are complex things +/// with different where clauses going on. +pub fn try_resolve_trait_method_did<'tcx>( + tcx: TyCtxt<'tcx>, + trait_did: DefId, + trait_args: &[Option<ty::GenericArg<'tcx>>], + method_name: &str, + for_type: ty::Ty<'tcx>, +) -> Option<DefId> { + // get all impls of this trait + let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did); + + let simplified_type = + middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?; + let defs = impls.non_blanket_impls().get(&simplified_type)?; + info!("found implementations: {:?}", impls); + + let mut solution = None; + for did in defs { + let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity(); + let impl_self_ty = types::normalize_in_function(*did, tcx, impl_self_ty).unwrap(); + + // check if this is an implementation for the right type + // TODO: is this the right way to compare the types? + if impl_self_ty == for_type { + let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did); + + if let Some(impl_ref) = impl_ref { + let impl_ref = types::normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap(); + + let this_impl_args = impl_ref.args; + // filter by the generic instantiation for the trait + info!("found impl with args {:?}", this_impl_args); + // args has self at position 0 and generics of the trait at position 1.. + + // check if the generic argument types match up + if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) { + continue; + } + + let impl_assoc_items: &ty::AssocItems = tcx.associated_items(did); + // find the right item + if let Some(item) = impl_assoc_items.find_by_name_and_kind( + tcx, + span::symbol::Ident::from_str(method_name), + ty::AssocKind::Fn, + trait_did, + ) { + info!("found impl {:?} with item {:?}", impl_ref, item); + if solution.is_some() { + println!( + "Warning: Ambiguous resolution for method {method_name} of trait {:?} on type {:?}; solution {:?} but found also {:?}", + trait_did, + for_type, + solution.unwrap(), + item.def_id + ); + } else { + solution = Some(item.def_id); + } + } + } + } + } + + solution +} + +/// Try to get a defid of a method at the given path. +/// This does not handle trait methods. +/// This also does not handle overlapping method definitions/specialization well. +pub fn try_resolve_method_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> +where + T: AsRef<str>, +{ + tcx.crates(()) + .iter() + .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref()) + .and_then(|krate| { + let krate = DefId { + krate: *krate, + index: CRATE_DEF_INDEX, + }; + + let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate); + let mut path_it = path.iter().skip(1).peekable(); + + while let Some(segment) = path_it.next() { + //info!("items to look at: {:?}", items); + for item in mem::take(&mut items) { + let item: &middle::metadata::ModChild = item; + + if item.ident.name.as_str() != segment.as_ref() { + continue; + } + + info!("taking path: {:?}", segment.as_ref()); + if path_it.peek().is_none() { + return Some(item.res.def_id()); + } + + // just the method remaining + if path_it.len() != 1 { + items = tcx.module_children(item.res.def_id()); + break; + } + + let did: DefId = item.res.def_id(); + let impls: &[DefId] = tcx.inherent_impls(did); + info!("trying to find method among impls {:?}", impls); + + let find = path_it.next().unwrap(); + for impl_did in impls { + //let ty = tcx.type_of(*impl_did); + //info!("type of impl: {:?}", ty); + let items: &ty::AssocItems = tcx.associated_items(impl_did); + //info!("items here: {:?}", items); + // TODO more robust error handling if there are multiple matches. + for item in items.in_definition_order() { + //info!("comparing: {:?} with {:?}", item.name.as_str(), find); + if item.name.as_str() == find.as_ref() { + return Some(item.def_id); + } + } + //info!("impl items: {:?}", items); + } + + //info!("inherent impls for {:?}: {:?}", did, impls); + return None; + } + } + + None + }) +} + +pub fn try_resolve_method_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> +where + T: AsRef<str>, +{ + if let Some(did) = try_resolve_method_did_direct(tcx, path) { + return Some(did); + } + + // if the first component is "std", try if we can replace it with "alloc" or "core" + if path[0].as_ref() == "std" { + let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect(); + components[0] = "core".to_owned(); + if let Some(did) = try_resolve_method_did_direct(tcx, &components) { + return Some(did); + } + // try "alloc" + components[0] = "alloc".to_owned(); + try_resolve_method_did_direct(tcx, &components) + } else { + None + } +} diff --git a/rr_frontend/translation/src/shims/flat.rs b/rr_frontend/translation/src/shims/flat.rs new file mode 100644 index 0000000000000000000000000000000000000000..cdee3783b035e51c37c0047453e189a2a5d47a03 --- /dev/null +++ b/rr_frontend/translation/src/shims/flat.rs @@ -0,0 +1,230 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Provides a flat representation of types that is stable across compilations. + +use log::{info, trace}; +use rr_rustc_interface::hir; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::ty::{self, TyCtxt}; +use serde::{Deserialize, Serialize}; + +use crate::spec_parsers::get_export_as_attr; +use crate::{attrs, search, Environment}; + +/// An item path that receives generic arguments. +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +pub struct PathWithArgs { + path: Vec<String>, + /// An encoding of the GenericArgs for this definition. + /// This is `Some(ty)` if: + /// - the argument represents a type (not a constant or region) + /// - and the arg is not the trivial identity arg (in case of ADTs) + args: Vec<Option<Type>>, +} + +impl PathWithArgs { + pub fn to_item<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<(DefId, Vec<Option<ty::GenericArg<'tcx>>>)> { + let did = search::try_resolve_did(tcx, self.path.as_slice())?; + + let mut ty_args = Vec::new(); + + for arg in &self.args { + if let Some(arg) = arg { + let ty = arg.to_type(tcx)?; + ty_args.push(Some(ty::GenericArg::from(ty))); + } else { + ty_args.push(None); + } + } + + Some((did, ty_args)) + } + + /// `args` should be normalized already. + pub fn from_item<'tcx>( + env: &Environment<'tcx>, + did: DefId, + args: &[ty::GenericArg<'tcx>], + ) -> Option<Self> { + let path = get_export_path_for_did(env, did); + let mut flattened_args = Vec::new(); + // TODO: how to represent type variables in case the definition is open? + let mut index = 0; + info!("flattening args {:?}", args); + for arg in args { + if let Some(ty) = arg.as_type() { + // TODO not quite right yet (see above) + if !ty.is_param(index) { + let flattened_ty = convert_ty_to_flat_type(env, ty)?; + flattened_args.push(Some(flattened_ty)); + } + index += 1; + } else { + flattened_args.push(None); + } + } + Some(Self { + path, + args: flattened_args, + }) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "ty::IntTy")] +pub enum IntTyDef { + Isize, + I8, + I16, + I32, + I64, + I128, +} +#[derive(Serialize, Deserialize)] +#[serde(remote = "ty::UintTy")] +pub enum UintTyDef { + Usize, + U8, + U16, + U32, + U64, + U128, +} + +#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +/// A "flattened" representation of types that should be suitable serialized storage, and should be +/// stable enough to resolve to the same actual type across compilations. +/// Currently mostly geared to our trait resolution needs. +pub enum Type { + /// Path + generic args + /// empty args represents the identity substitution + Adt(PathWithArgs), + #[serde(with = "IntTyDef")] + Int(ty::IntTy), + #[serde(with = "UintTyDef")] + Uint(ty::UintTy), + Char, + Bool, + // TODO: more cases +} + +impl Type { + /// Try to convert a flat type to a type. + pub fn to_type<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<ty::Ty<'tcx>> { + match self { + Self::Adt(path_with_args) => { + let (did, flat_args) = path_with_args.to_item(tcx)?; + + let ty: ty::EarlyBinder<ty::Ty<'tcx>> = tcx.type_of(did); + let ty::TyKind::Adt(_, args) = ty.skip_binder().kind() else { + return None; + }; + + // build substitution + let mut substs = Vec::new(); + for (ty_arg, flat_arg) in args.iter().zip(flat_args.into_iter()) { + match ty_arg.unpack() { + ty::GenericArgKind::Type(_) => { + if let Some(flat_arg) = flat_arg { + substs.push(flat_arg); + } + }, + _ => { + substs.push(ty_arg); + }, + } + } + + // substitute + info!("substituting {:?} with {:?}", ty, substs); + let subst_ty = + if substs.is_empty() { ty.instantiate_identity() } else { ty.instantiate(tcx, &substs) }; + + Some(subst_ty) + }, + Self::Bool => Some(tcx.mk_ty_from_kind(ty::TyKind::Bool)), + Self::Char => Some(tcx.mk_ty_from_kind(ty::TyKind::Char)), + Self::Int(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Int(it.to_owned()))), + Self::Uint(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Uint(it.to_owned()))), + } + } +} + +/// Try to convert a type to a flat type. Assumes the type has been normalized already. +pub fn convert_ty_to_flat_type<'tcx>(env: &Environment<'tcx>, ty: ty::Ty<'tcx>) -> Option<Type> { + match ty.kind() { + ty::TyKind::Adt(def, args) => { + let did = def.did(); + // TODO: if this is downcast to a variant, this might not work + let path_with_args = PathWithArgs::from_item(env, did, args.as_slice())?; + Some(Type::Adt(path_with_args)) + }, + ty::TyKind::Bool => Some(Type::Bool), + ty::TyKind::Char => Some(Type::Char), + ty::TyKind::Int(it) => Some(Type::Int(it.to_owned())), + ty::TyKind::Uint(it) => Some(Type::Uint(it.to_owned())), + _ => None, + } +} + +pub fn get_cleaned_def_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> { + let def_path = tcx.def_path_str(did); + // we clean this up a bit and segment it + let mut components = Vec::new(); + for i in def_path.split("::") { + if i.starts_with('<') && i.ends_with('>') { + // this is a generic specialization, skip + continue; + } + components.push(i.to_owned()); + } + info!("split def path {:?} to {:?}", def_path, components); + + components +} + +// Alternative implementation of `get_cleaned_def_path`, but this doesn't always yield a rooted +// path (but only a suffix of the full path) +fn extract_def_path(path: &hir::definitions::DefPath) -> Vec<String> { + let mut components = Vec::new(); + for p in &path.data { + if let Some(name) = p.data.get_opt_name() { + components.push(name.as_str().to_owned()); + } + } + components +} + +/// Get the path we should export an item at. +pub fn get_export_path_for_did(env: &Environment, did: DefId) -> Vec<String> { + let attrs = env.get_attributes(did); + + if attrs::has_tool_attr(attrs, "export_as") { + let filtered_attrs = attrs::filter_for_tool(attrs); + + return get_export_as_attr(filtered_attrs.as_slice()).unwrap(); + } + + // Check for an annotation on the surrounding impl + if let Some(impl_did) = env.tcx().impl_of_method(did) { + let attrs = env.get_attributes(impl_did); + + if attrs::has_tool_attr(attrs, "export_as") { + let filtered_attrs = attrs::filter_for_tool(attrs); + let mut path_prefix = get_export_as_attr(filtered_attrs.as_slice()).unwrap(); + + // push the last component of this path + //let def_path = env.tcx().def_path(did); + let mut this_path = get_cleaned_def_path(env.tcx(), did); + path_prefix.push(this_path.pop().unwrap()); + + return path_prefix; + } + } + + get_cleaned_def_path(env.tcx(), did) +} diff --git a/rr_frontend/translation/src/shims/mod.rs b/rr_frontend/translation/src/shims/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..8fa4dea374af1515b90185ef896aad251b731b02 --- /dev/null +++ b/rr_frontend/translation/src/shims/mod.rs @@ -0,0 +1,49 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +pub mod flat; +pub mod registry; + +use std::collections::HashMap; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::{fs, io}; + +/// Find `RefinedRust` modules in the given loadpath. +fn scan_loadpath(path: &Path, storage: &mut HashMap<String, PathBuf>) -> io::Result<()> { + if path.is_dir() { + for entry in fs::read_dir(path)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + scan_loadpath(path.as_path(), storage)?; + } else if path.is_file() { + if let Some(ext) = path.extension() { + if Some("rrlib") == ext.to_str() { + // try to open this rrlib file + let f = File::open(path.clone())?; + if let Some(name) = registry::is_valid_refinedrust_module(f) { + storage.insert(name, path); + } + } + } + } + } + } + + Ok(()) +} + +/// Find `RefinedRust` modules in the given loadpaths. +pub fn scan_loadpaths(paths: &[PathBuf]) -> io::Result<HashMap<String, PathBuf>> { + let mut found_lib_files: HashMap<String, PathBuf> = HashMap::new(); + + for path in paths { + scan_loadpath(path, &mut found_lib_files)?; + } + + Ok(found_lib_files) +} diff --git a/rr_frontend/translation/src/shim_registry.rs b/rr_frontend/translation/src/shims/registry.rs similarity index 96% rename from rr_frontend/translation/src/shim_registry.rs rename to rr_frontend/translation/src/shims/registry.rs index e6e02472ea0c9a3bf3e6bbabda41ac06425d9aa6..a7a9cc40d40359c5e1557250033d5463840e57eb 100644 --- a/rr_frontend/translation/src/shim_registry.rs +++ b/rr_frontend/translation/src/shims/registry.rs @@ -4,10 +4,11 @@ // If a copy of the BSD-3-clause license was not distributed with this // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. +//! Registry of shims for Rust functions that get mapped to custom `RefinedRust` +//! implementations. +//! Provides deserialization from a JSON file defining this registry. + use std::collections::{HashMap, HashSet}; -/// Registry of shims for Rust functions that get mapped to custom `RefinedRust` -/// implementations. -/// Provides deserialization from a JSON file defining this registry. use std::fs::File; use std::io::{BufReader, BufWriter}; @@ -16,7 +17,7 @@ use radium::coq; use serde::{Deserialize, Serialize}; use typed_arena::Arena; -use crate::utils::*; +use crate::shims::flat; type Path<'a> = Vec<&'a str>; @@ -62,9 +63,9 @@ struct ShimTraitEntry { #[derive(Serialize, Deserialize)] struct ShimTraitImplEntry { /// The rustc path for the trait - trait_path: PathWithArgs, + trait_path: flat::PathWithArgs, /// for which type is this implementation? - for_type: FlatType, + for_type: flat::Type, // TODO: additional constraints like the required clauses for disambiguation /// a kind: always "trait_impl" kind: String, @@ -86,9 +87,9 @@ struct ShimTraitImplEntry { #[derive(Serialize, Deserialize)] struct ShimTraitMethodImplEntry { /// The rustc path for the trait - trait_path: PathWithArgs, + trait_path: flat::PathWithArgs, /// for which type is this implementation? - for_type: FlatType, + for_type: flat::Type, // TODO: additional constraints like the required clauses for disambiguation /// The method identifier method_ident: String, @@ -146,8 +147,8 @@ impl<'a> From<FunctionShim<'a>> for ShimFunctionEntry { #[derive(Clone, Eq, PartialEq, Debug)] pub struct TraitImplShim { - pub trait_path: PathWithArgs, - pub for_type: FlatType, + pub trait_path: flat::PathWithArgs, + pub for_type: flat::Type, pub method_specs: HashMap<String, (String, String)>, @@ -173,9 +174,9 @@ impl From<TraitImplShim> for ShimTraitImplEntry { #[derive(Clone, Eq, PartialEq, Debug)] pub struct TraitMethodImplShim { - pub trait_path: PathWithArgs, + pub trait_path: flat::PathWithArgs, pub method_ident: String, - pub for_type: FlatType, + pub for_type: flat::Type, pub name: String, pub spec_name: String, } @@ -243,16 +244,9 @@ impl<'a> From<AdtShim<'a>> for ShimAdtEntry { } } -pub struct ModuleSummary<'a> { - /// function/method shims - function_shims: Vec<FunctionShim<'a>>, - /// adt shims - adt_shims: Vec<AdtShim<'a>>, -} - /// Registry of function shims loaded by the user. Substitutes path to the function/method with a /// code definition name and a spec name. -pub struct ShimRegistry<'a> { +pub struct SR<'a> { arena: &'a Arena<String>, /// function/method shims @@ -277,7 +271,7 @@ pub struct ShimRegistry<'a> { dependencies: Vec<coq::module::DirPath>, } -impl<'a> ShimRegistry<'a> { +impl<'a> SR<'a> { fn get_shim_kind(v: &serde_json::Value) -> Result<ShimKind, String> { let obj = v.as_object().ok_or_else(|| "element is not an object".to_owned())?; let vk = obj.get("kind").ok_or_else(|| "object does not have \"kind\" attribute".to_owned())?; @@ -316,7 +310,7 @@ impl<'a> ShimRegistry<'a> { } } - pub fn new(arena: &'a Arena<String>) -> Result<ShimRegistry<'a>, String> { + pub fn new(arena: &'a Arena<String>) -> Result<SR<'a>, String> { let mut reg = Self::empty(arena); match rrconfig::shim_file() { diff --git a/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs index e64f1ae0a69bf603ba9a15246cd576b35c961b3e..6e303e39e694bacd7beb2fa8bfc9f6ee0cb27f7a 100644 --- a/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs +++ b/rr_frontend/translation/src/spec_parsers/enum_spec_parser.rs @@ -4,8 +4,6 @@ // If a copy of the BSD-3-clause license was not distributed with this // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. -use std::collections::HashMap; - use attribute_parse::{parse, MToken}; use parse::Peek; use radium::{coq, specs}; diff --git a/rr_frontend/translation/src/spec_parsers/parse_utils.rs b/rr_frontend/translation/src/spec_parsers/parse_utils.rs index 158ad51e8155e9d023be580155fd2a10e49862e6..74bb004643bf9afcd300310e57970c058d39a926 100644 --- a/rr_frontend/translation/src/spec_parsers/parse_utils.rs +++ b/rr_frontend/translation/src/spec_parsers/parse_utils.rs @@ -5,7 +5,7 @@ // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. /// This provides some general utilities for RefinedRust-specific attribute parsing. -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use attribute_parse::{parse, MToken}; use lazy_static::lazy_static; diff --git a/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs index bf48d7842e753aa556ce2d69d1016c2ebff95107..a7622a512f97b92e1daa125485102535c09989b3 100644 --- a/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs +++ b/rr_frontend/translation/src/spec_parsers/struct_spec_parser.rs @@ -4,7 +4,6 @@ // If a copy of the BSD-3-clause license was not distributed with this // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. -use std::collections::HashMap; use std::convert::Into; /// Parsing of `RefinedRust` struct specifications. diff --git a/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs b/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs index 6cc516061b316481da7d90a7306e01352ca01bad..b251ecaff8e3585110a0c528a198f6a9bbcc4703 100644 --- a/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs +++ b/rr_frontend/translation/src/spec_parsers/trait_attr_parser.rs @@ -10,7 +10,6 @@ use attribute_parse::{parse, MToken}; use derive_more::Constructor; use radium::coq; use rr_rustc_interface::ast::ast::AttrItem; -use rr_rustc_interface::hir::def_id::LocalDefId; use crate::spec_parsers::parse_utils::*; diff --git a/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs b/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs index 35ad963cfbe0a630fc199962259246d6f1d84956..d6afa9d0657fade8410ffab92aa8978f61e13e1a 100644 --- a/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs +++ b/rr_frontend/translation/src/spec_parsers/trait_impl_attr_parser.rs @@ -8,7 +8,6 @@ use std::collections::HashMap; use attribute_parse::{parse, MToken}; use rr_rustc_interface::ast::ast::AttrItem; -use rr_rustc_interface::hir::def_id::LocalDefId; use crate::spec_parsers::parse_utils::*; diff --git a/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs b/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs index 88e0c9a5962d3c2ffc6d7ae0553a4a262b34c7b6..76226758a0cdf266302e7774b521fc61e331a9e2 100644 --- a/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs +++ b/rr_frontend/translation/src/spec_parsers/verbose_function_spec_parser.rs @@ -242,6 +242,7 @@ pub struct VerboseFunctionSpecParser<'a, 'def, F, T> { got_ret: bool, } +/// Extra requirements of a function. #[derive(Default)] pub struct FunctionRequirements { /// additional late coq parameters diff --git a/rr_frontend/translation/src/traits/mod.rs b/rr_frontend/translation/src/traits/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..4fa27a4b07d25699c79384565cae83cb51a73307 --- /dev/null +++ b/rr_frontend/translation/src/traits/mod.rs @@ -0,0 +1,84 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +use std::collections::HashMap; + +use derive_more::Display; +use log::{info, trace}; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::ty; + +use crate::environment::Environment; + +pub mod registry; +pub mod requirements; +pub mod resolution; + +#[derive(Debug, Clone, Display)] +pub enum Error<'tcx> { + /// This DefId is not a trait + #[display("The given DefId {:?} is not a trait", _0)] + NotATrait(DefId), + /// This DefId is not an impl of a trait + #[display("The given DefId {:?} is not a trait implementation", _0)] + NotATraitImpl(DefId), + /// This DefId is not a trait method + #[display("The given DefId {:?} is not a trait method", _0)] + NotATraitMethod(DefId), + /// This DefId is not an assoc type + #[display("The given DefId {:?} is not an associated type", _0)] + NotAnAssocType(DefId), + /// This trait already exists + #[display("This trait {:?} already has been registered", _0)] + TraitAlreadyExists(DefId), + /// This trait impl already exists + #[display("This trait impl {:?} already has been registered", _0)] + ImplAlreadyExists(DefId), + /// Trait hasn't been registered yet but is used + #[display("This trait {:?} has not been registered yet", _0)] + UnregisteredTrait(DefId), + /// Trait impl hasn't been registered yet but is used + #[display("This trait impl {:?} has not been registered yet", _0)] + UnregisteredImpl(DefId), + /// Cannot find this trait instance in the local environment + #[display("An instance for this trait {:?} cannot by found with generic args {:?}", _0, _1)] + UnknownLocalInstance(DefId, ty::GenericArgsRef<'tcx>), + #[display("An error occurred when parsing the specification of the trait {:?}: {:?}", _0, _1)] + TraitSpec(DefId, String), + #[display("An error occurred when parsing the specification of the trait impl {:?}: {:?}", _0, _1)] + TraitImplSpec(DefId, String), + /// Unknown error + #[display("Unknown Error")] + Unknown, +} +pub type TraitResult<'tcx, T> = Result<T, Error<'tcx>>; + +/// Given a particular reference to a trait, get the associated type constraints for this trait reference. +pub fn get_trait_assoc_constraints<'tcx>( + env: &Environment<'tcx>, + param_env: ty::ParamEnv<'tcx>, + trait_ref: ty::TraitRef<'tcx>, +) -> HashMap<String, ty::Ty<'tcx>> { + let mut assoc_ty_map = HashMap::new(); + + // TODO: check if caller_bounds does the right thing for implied bounds + let clauses = param_env.caller_bounds(); + for cl in clauses { + let cl_kind = cl.kind(); + let cl_kind = cl_kind.skip_binder(); + + // only look for trait predicates for now + if let ty::ClauseKind::Projection(proj) = cl_kind { + if trait_ref.def_id == proj.trait_def_id(env.tcx()) && trait_ref.args == proj.projection_ty.args { + // same trait and same args + let name = env.get_assoc_item_name(proj.def_id()).unwrap(); + let ty = proj.term.ty().unwrap(); + assoc_ty_map.insert(name, ty); + } + } + } + assoc_ty_map +} diff --git a/rr_frontend/translation/src/trait_registry.rs b/rr_frontend/translation/src/traits/registry.rs similarity index 70% rename from rr_frontend/translation/src/trait_registry.rs rename to rr_frontend/translation/src/traits/registry.rs index 9e4835690c8b8dc5cebaf311635b5fa3b2e92059..81b97f1bec8f8943cf5752cf41c48a6466ef777f 100644 --- a/rr_frontend/translation/src/trait_registry.rs +++ b/rr_frontend/translation/src/traits/registry.rs @@ -1,112 +1,33 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + use std::cell::RefCell; -use std::cmp::Ordering; use std::collections::{HashMap, HashSet}; use std::fmt::Write; -use std::string::ToString; -use derive_more::{Constructor, Display}; use log::{info, trace}; use radium::{self, coq, specs}; use rr_rustc_interface::hir::def_id::{DefId, LocalDefId}; -use rr_rustc_interface::middle::{self, ty}; +use rr_rustc_interface::middle::ty; +use traits::{resolution, Error, TraitResult}; use typed_arena::Arena; use crate::base::TranslationError; +use crate::body::signature; use crate::environment::Environment; -use crate::function_body::{get_arg_syntypes_for_procedure_call, mangle_name_with_args, FunctionTranslator}; use crate::spec_parsers::propagate_method_attr_from_impl; use crate::spec_parsers::trait_attr_parser::{TraitAttrParser, VerboseTraitAttrParser}; use crate::spec_parsers::trait_impl_attr_parser::{TraitImplAttrParser, VerboseTraitImplAttrParser}; -use crate::type_translator::{ - generate_args_inst_key, strip_coq_ident, GenericsKey, LocalTypeTranslator, ParamScope, TranslationState, - TypeTranslator, -}; -use crate::{traits, utils}; - -#[derive(Debug, Clone, Display)] -pub enum Error<'tcx> { - /// This DefId is not a trait - #[display("The given DefId {:?} is not a trait", _0)] - NotATrait(DefId), - /// This DefId is not an impl of a trait - #[display("The given DefId {:?} is not a trait implementation", _0)] - NotATraitImpl(DefId), - /// This DefId is not a trait method - #[display("The given DefId {:?} is not a trait method", _0)] - NotATraitMethod(DefId), - /// This DefId is not an assoc type - #[display("The given DefId {:?} is not an associated type", _0)] - NotAnAssocType(DefId), - /// This trait already exists - #[display("This trait {:?} already has been registered", _0)] - TraitAlreadyExists(DefId), - /// This trait impl already exists - #[display("This trait impl {:?} already has been registered", _0)] - ImplAlreadyExists(DefId), - /// Trait hasn't been registered yet but is used - #[display("This trait {:?} has not been registered yet", _0)] - UnregisteredTrait(DefId), - /// Trait impl hasn't been registered yet but is used - #[display("This trait impl {:?} has not been registered yet", _0)] - UnregisteredImpl(DefId), - /// Cannot find this trait instance in the local environment - #[display("An instance for this trait {:?} cannot by found with generic args {:?}", _0, _1)] - UnknownLocalInstance(DefId, ty::GenericArgsRef<'tcx>), - #[display("An error occurred when parsing the specification of the trait {:?}: {:?}", _0, _1)] - TraitSpec(DefId, String), - #[display("An error occurred when parsing the specification of the trait impl {:?}: {:?}", _0, _1)] - TraitImplSpec(DefId, String), - /// Unknown error - #[display("Unknown Error")] - Unknown, -} -pub type TraitResult<'tcx, T> = Result<T, Error<'tcx>>; - -/// What does a trait registry need? -/// -/// for reach Rust trait: -/// - mapping to the Coq representation, i.e. functions with specifications (`FunctionSpec`?) as well as -/// types -/// - list of impls we have translated and which we will emit. -/// -/// for each generic in the function scope: (-> `LocalTraitRegistry`) -/// - list of identifiers to the trait instance, and means to add that to the spec later -/// + these are Param instances that we cannot statically resolve -/// -/// - a list of trait uses: next to the generic args, what trait implementations do we assume to be -/// available for types? e.g. we might require a trait instance of `Ord` for (T, i32) where T is a generic -/// arg. This instance might not be a parameter, but concretely resolved. i.e. we have a pair of a -/// location, but get the spec for the trait impl as part of the thing. -/// => or do we want to merge these two? -/// e.g., will the second part really be handled differently from the first kind? -/// The difference is: -/// + the second kind depends on the semantic type, the first one should not depend on the semantic -/// type. i.e. the first one will assume a function has a type at the linking level. while the -/// second one assumes the type assignment at the precondition-level. -/// + Note that the first one might still get type parameters for some instances, e.g. if I have an -/// instance for (T, i32). That should be fine and not create any complications. -/// => No, we should not merge these. -/// => Are the trait uses different from just using functions? -/// + They are only different when they directly involve one of the Param traits, otherwise -/// they are statically resolvable. -/// -/// -/// In the case of closures, we would add the fact that the closure implements the invocation -/// method as an assumption to the `ProcedureScope`. -/// We would generate the spec for that from the annotated spec. -/// In this case, since the instance is auto-generated usually, it can be statically resolved, -/// but will not have a specification or the obligation to generate it yet. -/// So we have to put the obligation on it. Add support to `ProcedureScope` for that. -/// (For now, we'll not resolve that obligation) -/// - generate a new name for that -/// - register a specification for that -/// + the specification is not provided in the form of annotations, but we should generate a -/// `FunctionSpec` nevertheless. -/// - -pub struct TraitRegistry<'tcx, 'def> { +use crate::types::scope; +use crate::{attrs, base, traits, types}; + +pub struct TR<'tcx, 'def> { /// environment env: &'def Environment<'tcx>, - type_translator: &'def TypeTranslator<'def, 'tcx>, + type_translator: &'def types::TX<'def, 'tcx>, /// trait declarations trait_decls: RefCell<HashMap<LocalDefId, radium::TraitSpecDecl<'def>>>, @@ -127,11 +48,11 @@ pub struct TraitRegistry<'tcx, 'def> { fn_spec_arena: &'def Arena<specs::FunctionSpec<'def, specs::InnerFunctionSpec<'def>>>, } -impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { +impl<'tcx, 'def> TR<'tcx, 'def> { /// Create an empty trait registry. pub fn new( env: &'def Environment<'tcx>, - ty_translator: &'def TypeTranslator<'def, 'tcx>, + ty_translator: &'def types::TX<'def, 'tcx>, trait_arena: &'def Arena<specs::LiteralTraitSpec>, impl_arena: &'def Arena<specs::LiteralTraitImpl>, trait_use_arena: &'def Arena<radium::LiteralTraitSpecUseCell<'def>>, @@ -244,7 +165,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { /// Register a new annotated trait in the local crate with the registry. pub fn register_trait(&'def self, did: LocalDefId) -> Result<(), TranslationError<'tcx>> { - trace!("enter TraitRegistry::register_trait for did={did:?}"); + trace!("enter TR::register_trait for did={did:?}"); { let scope = self.trait_decls.borrow(); @@ -259,13 +180,13 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { // get generics let trait_generics: &'tcx ty::Generics = self.env.tcx().generics_of(did.to_def_id()); - let mut param_scope = ParamScope::from(trait_generics.params.as_slice()); + let mut param_scope = scope::Params::from(trait_generics.params.as_slice()); param_scope.add_param_env(did.to_def_id(), self.env, self.type_translator, self)?; - let trait_name = strip_coq_ident(&self.env.get_absolute_item_name(did.to_def_id())); + let trait_name = base::strip_coq_ident(&self.env.get_absolute_item_name(did.to_def_id())); // parse trait spec - let trait_attrs = utils::filter_tool_attrs(self.env.get_attributes(did.into())); + let trait_attrs = attrs::filter_for_tool(self.env.get_attributes(did.into())); // As different attributes of the spec may depend on each other, we need to pass a closure // determining under which Coq name we are going to introduce them // Note: This needs to match up with `radium::LiteralTraitSpec.make_spec_attr_name`! @@ -296,13 +217,13 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { // get function name let method_name = self.env.get_assoc_item_name(c.def_id).ok_or(Error::NotATraitMethod(c.def_id))?; - let method_name = strip_coq_ident(&method_name); + let method_name = base::strip_coq_ident(&method_name); let name = format!("{trait_name}_{method_name}"); let spec_name = format!("{name}_base_spec"); // get spec - let spec = FunctionTranslator::spec_for_trait_method( + let spec = signature::TX::spec_for_trait_method( self.env, c.def_id, &name, @@ -318,7 +239,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { // get name let type_name = self.env.get_assoc_item_name(c.def_id).ok_or(Error::NotATraitMethod(c.def_id))?; - let type_name = strip_coq_ident(&type_name); + let type_name = base::strip_coq_ident(&type_name); let lit = radium::LiteralTyParam::new(&type_name, &type_name); assoc_types.push(lit); } @@ -337,7 +258,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { let mut scope = self.trait_decls.borrow_mut(); scope.insert(did, decl); - trace!("leave TraitRegistry::register_trait"); + trace!("leave TR::register_trait"); Ok(()) } @@ -423,13 +344,13 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { /// as well as the list of associated types. pub fn get_impl_spec_term( &self, - state: TranslationState<'_, '_, 'def, 'tcx>, + state: types::ST<'_, '_, 'def, 'tcx>, impl_did: DefId, impl_args: &[ty::GenericArg<'tcx>], trait_args: &[ty::GenericArg<'tcx>], ) -> Result<(radium::SpecializedTraitImpl<'def>, Vec<ty::Ty<'tcx>>), TranslationError<'tcx>> { trace!( - "enter TraitRegistry::get_impl_spec_term for impl_did={impl_did:?} impl_args={impl_args:?} trait_args={trait_args:?}" + "enter TR::get_impl_spec_term for impl_did={impl_did:?} impl_args={impl_args:?} trait_args={trait_args:?}" ); let trait_did = self.env.tcx().trait_id_of_impl(impl_did).ok_or(Error::NotATraitImpl(impl_did))?; @@ -466,7 +387,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { return Err(TranslationError::TraitTranslation(Error::UnregisteredImpl(impl_did))); }; - trace!("leave TraitRegistry::get_impl_spec_term"); + trace!("leave TR::get_impl_spec_term"); Ok((term, assoc_args)) } @@ -477,7 +398,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { /// declaration order. pub fn resolve_trait_requirements_in_state( &self, - state: TranslationState<'_, '_, 'def, 'tcx>, + state: types::ST<'_, '_, 'def, 'tcx>, did: DefId, params: ty::GenericArgsRef<'tcx>, ) -> Result<Vec<radium::TraitReqInst<'def, ty::Ty<'tcx>>>, TranslationError<'tcx>> { @@ -489,7 +410,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { trace!("callee param env {callee_param_env:?}"); // Get the trait requirements of the callee - let callee_requirements = ParamScope::get_trait_requirements_with_origin(self.env, did); + let callee_requirements = scope::Params::get_trait_requirements_with_origin(self.env, did); trace!("non-trivial callee requirements: {callee_requirements:?}"); trace!("subsituting with args {:?}", params); @@ -517,7 +438,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { if let Some(trait_did) = self.env.tcx().trait_of_item(did) { // Get the params of the trait we're calling let calling_trait_params = - LocalTypeTranslator::split_trait_method_args(self.env, trait_did, params).0; + types::LocalTX::split_trait_method_args(self.env, trait_did, params).0; if trait_ref.def_id == trait_did && subst_args == calling_trait_params.as_slice() { // if they match, this is the Self assumption, so skip continue; @@ -531,12 +452,12 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { let subst_args = self.env.tcx().mk_args(subst_args.as_slice()); trace!("Trying to resolve requirement def_id={:?} with args = {subst_args:?}", trait_ref.def_id); if let Some((impl_did, impl_args, kind)) = - traits::resolve_trait(self.env.tcx(), current_param_env, trait_ref.def_id, subst_args) + resolution::resolve_trait(self.env.tcx(), current_param_env, trait_ref.def_id, subst_args) { info!("resolved trait impl as {impl_did:?} with {args:?} {kind:?}"); let req_inst = match kind { - traits::TraitResolutionKind::UserDefined => { + resolution::TraitResolutionKind::UserDefined => { // we can resolve it to a concrete implementation of the trait that the // call links up against // therefore, we specialize it to the specification for this implementation @@ -556,7 +477,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { assoc_tys, ) }, - traits::TraitResolutionKind::Param => { + resolution::TraitResolutionKind::Param => { // Lookup in our current parameter environment to satisfy this trait // assumption let trait_did = trait_ref.def_id; @@ -585,7 +506,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { assoc_types, ) }, - traits::TraitResolutionKind::Closure => { + resolution::TraitResolutionKind::Closure => { // The callee requires a closure trait bound. // This happens when we pass a closure as an argument? return Err(TranslationError::UnsupportedFeature { @@ -614,6 +535,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { Ok(trait_spec_terms) } + /// Get information on a trait implementation and create its Radium encoding. pub fn get_trait_impl_info( &self, trait_impl_did: DefId, @@ -635,11 +557,11 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { // figure out the parameters this impl gets and make a scope let impl_generics: &'tcx ty::Generics = self.env.tcx().generics_of(trait_impl_did); - let mut param_scope = ParamScope::from(impl_generics.params.as_slice()); + let mut param_scope = scope::Params::from(impl_generics.params.as_slice()); param_scope.add_param_env(trait_impl_did, self.env, self.type_translator, self)?; // parse specification - let trait_impl_attrs = utils::filter_tool_attrs(self.env.get_attributes(trait_impl_did)); + let trait_impl_attrs = attrs::filter_for_tool(self.env.get_attributes(trait_impl_did)); let mut attr_parser = VerboseTraitImplAttrParser::new(¶m_scope); let impl_spec = attr_parser .parse_trait_impl_attrs(&trait_impl_attrs) @@ -656,7 +578,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { let ty = self.type_translator.translate_type_in_scope(¶m_scope, ty)?; params_inst.push(ty); } else if let Some(lft) = arg.as_region() { - let lft = TypeTranslator::translate_region_in_scope(¶m_scope, lft)?; + let lft = types::TX::translate_region_in_scope(¶m_scope, lft)?; lft_inst.push(lft); } } @@ -699,7 +621,51 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { unreachable!("Expected trait impl"); } } +} + +/// A using occurrence of a trait in the signature of the function. +#[derive(Debug, Clone)] +pub struct GenericTraitUse<'def> { + /// the DefId of the trait + pub did: DefId, + /// the self type this is implemented for + pub self_ty: ty::ParamTy, + /// the Coq-level trait use + pub trait_use: radium::LiteralTraitSpecUseRef<'def>, +} +impl<'def> GenericTraitUse<'def> { + /// Get the names of associated types of this trait. + pub fn get_associated_type_names(&self, env: &Environment<'_>) -> Vec<String> { + let mut assoc_tys = Vec::new(); + + // get associated types + let assoc_types = env.get_trait_assoc_types(self.did); + for ty_did in &assoc_types { + let ty_name = env.get_assoc_item_name(*ty_did).unwrap(); + assoc_tys.push(ty_name); + } + assoc_tys + } + + /// Get the associated type instantiations for this trait use. + pub fn get_associated_type_uses(&self, env: &Environment<'_>) -> Vec<radium::Type<'def>> { + let mut assoc_tys: Vec<radium::Type> = Vec::new(); + + // get associated types + let assoc_types = env.get_trait_assoc_types(self.did); + for ty_did in &assoc_types { + let ty_name = env.get_assoc_item_name(*ty_did).unwrap(); + let trait_use_ref = self.trait_use.borrow(); + let trait_use = trait_use_ref.as_ref().unwrap(); + let lit = trait_use.make_assoc_type_use(&base::strip_coq_ident(&ty_name)); + assoc_tys.push(lit); + } + assoc_tys + } +} + +impl<'tcx, 'def> TR<'tcx, 'def> { /// Allocate an empty trait use reference. pub fn make_empty_trait_use(&self, trait_ref: ty::TraitRef<'tcx>) -> GenericTraitUse<'def> { let dummy_trait_use = RefCell::new(None); @@ -723,12 +689,12 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { /// Fills an existing trait use. /// Does not compute the dependencies on other traits yet, - /// these, have to be filled later. + /// these have to be filled later. #[allow(clippy::unnecessary_wraps)] pub fn fill_trait_use( &self, trait_use: &GenericTraitUse<'def>, - scope: TranslationState<'_, '_, 'def, 'tcx>, + scope: types::ST<'_, '_, 'def, 'tcx>, trait_ref: ty::TraitRef<'tcx>, spec_ref: radium::LiteralTraitSpecRef<'def>, is_used_in_self_trait: bool, @@ -766,7 +732,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { let spec_override = None; // create a name for this instance by including the args - let mangled_base = mangle_name_with_args(&spec_ref.name, trait_ref.args.as_slice()); + let mangled_base = types::mangle_name_with_args(&spec_ref.name, trait_ref.args.as_slice()); let spec_use = radium::LiteralTraitSpecUse::new( spec_ref, translated_args, @@ -792,7 +758,7 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { pub fn finalize_trait_use( &self, trait_use: &GenericTraitUse<'def>, - scope: TranslationState<'_, '_, 'def, 'tcx>, + scope: types::ST<'_, '_, 'def, 'tcx>, trait_ref: ty::TraitRef<'tcx>, ) -> Result<(), TranslationError<'tcx>> { let trait_reqs = self.resolve_trait_requirements_in_state(scope, trait_ref.def_id, trait_ref.args)?; @@ -816,203 +782,3 @@ impl<'tcx, 'def> TraitRegistry<'tcx, 'def> { Ok(()) } } - -/// Check if this is a built-in trait -pub fn is_builtin_trait(tcx: ty::TyCtxt<'_>, trait_did: DefId) -> Option<bool> { - let sized_did = utils::try_resolve_did(tcx, &["core", "marker", "Sized"])?; - - // TODO: for these, should instead require the primitive encoding of our Coq formalization - let send_did = utils::try_resolve_did(tcx, &["core", "marker", "Send"])?; - let sync_did = utils::try_resolve_did(tcx, &["core", "marker", "Sync"])?; - let copy_did = utils::try_resolve_did(tcx, &["core", "marker", "Copy"])?; - - // used for closures - let copy_did = utils::try_resolve_did(tcx, &["core", "marker", "Tuple"])?; - - Some(trait_did == sized_did || trait_did == copy_did) -} - -/// Compare two `GenericArg` deterministically. -/// Should only be called on equal discriminants. -fn cmp_arg_ref<'tcx>(tcx: ty::TyCtxt<'tcx>, a: ty::GenericArg<'tcx>, b: ty::GenericArg<'tcx>) -> Ordering { - match (a.unpack(), b.unpack()) { - (ty::GenericArgKind::Const(c1), ty::GenericArgKind::Const(c2)) => c1.cmp(&c2), - (ty::GenericArgKind::Type(ty1), ty::GenericArgKind::Type(ty2)) => { - // we should make sure that this always orders the Self instance first. - match (ty1.kind(), ty2.kind()) { - (ty::TyKind::Param(p1), ty::TyKind::Param(p2)) => p1.cmp(p2), - (ty::TyKind::Param(p1), _) => Ordering::Less, - (_, _) => ty1.cmp(&ty2), - } - }, - (ty::GenericArgKind::Lifetime(r1), ty::GenericArgKind::Lifetime(r2)) => r1.cmp(&r2), - (_, _) => { - unreachable!("Comparing GenericArg with different discriminant"); - }, - } -} - -/// Compare two sequences of `GenericArg`s for the same `DefId`, where the discriminants are pairwise equal. -fn cmp_arg_refs<'tcx>( - tcx: ty::TyCtxt<'tcx>, - a: &[ty::GenericArg<'tcx>], - b: &[ty::GenericArg<'tcx>], -) -> Ordering { - match a.len().cmp(&b.len()) { - Ordering::Equal => { - // compare elements - for (x, y) in a.iter().zip(b.iter()) { - // the discriminants are the same as the DefId we are calling into is the same - let xy_cmp = cmp_arg_ref(tcx, *x, *y); - if xy_cmp != Ordering::Equal { - return xy_cmp; - } - } - Ordering::Equal - }, - o => o, - } -} - -/// Compare two `TraitRef`s deterministically, giving a -/// consistent order that is stable across compilations. -fn cmp_trait_ref<'tcx>( - tcx: ty::TyCtxt<'tcx>, - in_trait_decl: Option<DefId>, - a: &ty::TraitRef<'tcx>, - b: &ty::TraitRef<'tcx>, -) -> Ordering { - // if one of them is the self trait, that should be smaller. - if let Some(trait_did) = in_trait_decl { - if a.def_id == trait_did && a.args[0].expect_ty().is_param(0) { - return Ordering::Less; - } - if b.def_id == trait_did && b.args[0].expect_ty().is_param(0) { - return Ordering::Greater; - } - } - - let path_a = utils::get_cleaned_def_path(tcx, a.def_id); - let path_b = utils::get_cleaned_def_path(tcx, b.def_id); - let path_cmp = path_a.cmp(&path_b); - info!("cmp_trait_ref: comparing paths {path_a:?} and {path_b:?}"); - - if path_cmp == Ordering::Equal { - let args_a = a.args.as_slice(); - let args_b = b.args.as_slice(); - cmp_arg_refs(tcx, args_a, args_b) - } else { - path_cmp - } -} - -/// Get non-trivial trait requirements of a function's `ParamEnv`, -/// ordered deterministically. -pub fn get_nontrivial_trait_requirements<'tcx>( - tcx: ty::TyCtxt<'tcx>, - param_env: ty::ParamEnv<'tcx>, - in_trait_decl: Option<DefId>, -) -> Vec<ty::TraitRef<'tcx>> { - let mut trait_refs = Vec::new(); - trace!( - "Enter get_nontrivial_trait_requirements with param_env = {param_env:?}, in_trait_decl = {in_trait_decl:?}" - ); - - let clauses = param_env.caller_bounds(); - for cl in clauses { - let cl_kind = cl.kind(); - let cl_kind = cl_kind.skip_binder(); - - // only look for trait predicates for now - if let ty::ClauseKind::Trait(trait_pred) = cl_kind { - // We ignore negative polarities for now - if trait_pred.polarity == ty::ImplPolarity::Positive { - let trait_ref = trait_pred.trait_ref; - - // filter Sized, Copy, Send, Sync? - if Some(true) == is_builtin_trait(tcx, trait_ref.def_id) { - continue; - } - - // this is a nontrivial requirement - trait_refs.push(trait_ref); - } - } - } - - // Make sure the order is stable across compilations - trait_refs.sort_by(|a, b| cmp_trait_ref(tcx, in_trait_decl, a, b)); - - trace!("Leave get_nontrivial_trait_requirements with trait_refs = {trait_refs:?}"); - - trait_refs -} - -/// Given a particular reference to a trait, get the associated type constraints for this trait reference. -pub fn get_trait_assoc_constraints<'tcx>( - env: &Environment<'tcx>, - param_env: ty::ParamEnv<'tcx>, - trait_ref: ty::TraitRef<'tcx>, -) -> HashMap<String, ty::Ty<'tcx>> { - let mut assoc_ty_map = HashMap::new(); - - // TODO: check if caller_bounds does the right thing for implied bounds - let clauses = param_env.caller_bounds(); - for cl in clauses { - let cl_kind = cl.kind(); - let cl_kind = cl_kind.skip_binder(); - - // only look for trait predicates for now - if let ty::ClauseKind::Projection(proj) = cl_kind { - if trait_ref.def_id == proj.trait_def_id(env.tcx()) && trait_ref.args == proj.projection_ty.args { - // same trait and same args - let name = env.get_assoc_item_name(proj.def_id()).unwrap(); - let ty = proj.term.ty().unwrap(); - assoc_ty_map.insert(name, ty); - } - } - } - assoc_ty_map -} - -/// A using occurrence of a trait in the signature of the function. -#[derive(Debug, Clone)] -pub struct GenericTraitUse<'def> { - /// the DefId of the trait - pub did: DefId, - /// the self type this is implemented for - pub self_ty: ty::ParamTy, - /// the Coq-level trait use - pub trait_use: radium::LiteralTraitSpecUseRef<'def>, -} - -impl<'def> GenericTraitUse<'def> { - /// Get the names of associated types of this trait. - pub fn get_associated_type_names(&self, env: &Environment<'_>) -> Vec<String> { - let mut assoc_tys = Vec::new(); - - // get associated types - let assoc_types = env.get_trait_assoc_types(self.did); - for ty_did in &assoc_types { - let ty_name = env.get_assoc_item_name(*ty_did).unwrap(); - assoc_tys.push(ty_name); - } - assoc_tys - } - - /// Get the associated type instantiations for this trait use. - pub fn get_associated_type_uses(&self, env: &Environment<'_>) -> Vec<radium::Type<'def>> { - let mut assoc_tys: Vec<radium::Type> = Vec::new(); - - // get associated types - let assoc_types = env.get_trait_assoc_types(self.did); - for ty_did in &assoc_types { - let ty_name = env.get_assoc_item_name(*ty_did).unwrap(); - let trait_use_ref = self.trait_use.borrow(); - let trait_use = trait_use_ref.as_ref().unwrap(); - let lit = trait_use.make_assoc_type_use(&strip_coq_ident(&ty_name)); - assoc_tys.push(lit); - } - assoc_tys - } -} diff --git a/rr_frontend/translation/src/traits/requirements.rs b/rr_frontend/translation/src/traits/requirements.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d4875d7f1c52be429258ff26e1c48e99af79e3f --- /dev/null +++ b/rr_frontend/translation/src/traits/requirements.rs @@ -0,0 +1,152 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Get trait requirements of objects. + +use std::cmp::Ordering; + +use log::{info, trace}; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::ty; + +use crate::{search, shims}; + +/// Get non-trivial trait requirements of a `ParamEnv`, +/// ordered deterministically. +pub fn get_nontrivial<'tcx>( + tcx: ty::TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + in_trait_decl: Option<DefId>, +) -> Vec<ty::TraitRef<'tcx>> { + let mut trait_refs = Vec::new(); + trace!( + "Enter get_nontrivial_trait_requirements with param_env = {param_env:?}, in_trait_decl = {in_trait_decl:?}" + ); + + let clauses = param_env.caller_bounds(); + for cl in clauses { + let cl_kind = cl.kind(); + let cl_kind = cl_kind.skip_binder(); + + // only look for trait predicates for now + if let ty::ClauseKind::Trait(trait_pred) = cl_kind { + // We ignore negative polarities for now + if trait_pred.polarity == ty::ImplPolarity::Positive { + let trait_ref = trait_pred.trait_ref; + + // filter Sized, Copy, Send, Sync? + if Some(true) == is_builtin_trait(tcx, trait_ref.def_id) { + continue; + } + + // this is a nontrivial requirement + trait_refs.push(trait_ref); + } + } + } + + // Make sure the order is stable across compilations + trait_refs.sort_by(|a, b| cmp_trait_ref(tcx, in_trait_decl, a, b)); + + trace!("Leave get_nontrivial_trait_requirements with trait_refs = {trait_refs:?}"); + + trait_refs +} + +/// Check if this is a built-in trait +fn is_builtin_trait(tcx: ty::TyCtxt<'_>, trait_did: DefId) -> Option<bool> { + let sized_did = search::try_resolve_did(tcx, &["core", "marker", "Sized"])?; + + // TODO: for these, should instead require the primitive encoding of our Coq formalization + let send_did = search::try_resolve_did(tcx, &["core", "marker", "Send"])?; + let sync_did = search::try_resolve_did(tcx, &["core", "marker", "Sync"])?; + let copy_did = search::try_resolve_did(tcx, &["core", "marker", "Copy"])?; + + // used for closures + let tuple_did = search::try_resolve_did(tcx, &["core", "marker", "Tuple"])?; + + Some( + trait_did == sized_did + || trait_did == copy_did + || trait_did == tuple_did + || trait_did == send_did + || trait_did == sync_did, + ) +} + +/// Compare two `GenericArg` deterministically. +/// Should only be called on equal discriminants. +fn cmp_arg_ref<'tcx>(tcx: ty::TyCtxt<'tcx>, a: ty::GenericArg<'tcx>, b: ty::GenericArg<'tcx>) -> Ordering { + match (a.unpack(), b.unpack()) { + (ty::GenericArgKind::Const(c1), ty::GenericArgKind::Const(c2)) => c1.cmp(&c2), + (ty::GenericArgKind::Type(ty1), ty::GenericArgKind::Type(ty2)) => { + // we should make sure that this always orders the Self instance first. + match (ty1.kind(), ty2.kind()) { + (ty::TyKind::Param(p1), ty::TyKind::Param(p2)) => p1.cmp(p2), + (ty::TyKind::Param(p1), _) => Ordering::Less, + (_, _) => ty1.cmp(&ty2), + } + }, + (ty::GenericArgKind::Lifetime(r1), ty::GenericArgKind::Lifetime(r2)) => r1.cmp(&r2), + (_, _) => { + unreachable!("Comparing GenericArg with different discriminant"); + }, + } +} + +/// Compare two sequences of `GenericArg`s for the same `DefId`, where the discriminants are pairwise equal. +fn cmp_arg_refs<'tcx>( + tcx: ty::TyCtxt<'tcx>, + a: &[ty::GenericArg<'tcx>], + b: &[ty::GenericArg<'tcx>], +) -> Ordering { + match a.len().cmp(&b.len()) { + Ordering::Equal => { + // compare elements + for (x, y) in a.iter().zip(b.iter()) { + // the discriminants are the same as the DefId we are calling into is the same + let xy_cmp = cmp_arg_ref(tcx, *x, *y); + if xy_cmp != Ordering::Equal { + return xy_cmp; + } + } + Ordering::Equal + }, + o => o, + } +} + +/// Compare two `TraitRef`s deterministically, giving a +/// consistent order that is stable across compilations. +fn cmp_trait_ref<'tcx>( + tcx: ty::TyCtxt<'tcx>, + in_trait_decl: Option<DefId>, + a: &ty::TraitRef<'tcx>, + b: &ty::TraitRef<'tcx>, +) -> Ordering { + // if one of them is the self trait, that should be smaller. + if let Some(trait_did) = in_trait_decl { + if a.def_id == trait_did && a.args[0].expect_ty().is_param(0) { + return Ordering::Less; + } + if b.def_id == trait_did && b.args[0].expect_ty().is_param(0) { + return Ordering::Greater; + } + } + + let path_a = shims::flat::get_cleaned_def_path(tcx, a.def_id); + let path_b = shims::flat::get_cleaned_def_path(tcx, b.def_id); + let path_cmp = path_a.cmp(&path_b); + info!("cmp_trait_ref: comparing paths {path_a:?} and {path_b:?}"); + + if path_cmp == Ordering::Equal { + let args_a = a.args.as_slice(); + let args_b = b.args.as_slice(); + cmp_arg_refs(tcx, args_a, args_b) + } else { + path_cmp + } +} diff --git a/rr_frontend/translation/src/traits.rs b/rr_frontend/translation/src/traits/resolution.rs similarity index 98% rename from rr_frontend/translation/src/traits.rs rename to rr_frontend/translation/src/traits/resolution.rs index b38d6a21ced9792dc2eeadd1655e8148fcea54e9..30c8a63fffd30b25dc55f57150cf55847fc515fa 100644 --- a/rr_frontend/translation/src/traits.rs +++ b/rr_frontend/translation/src/traits/resolution.rs @@ -1,3 +1,4 @@ +//! Interface for resolving trait requirements using `rustc`'s trait resolution. /// Inspired by (in terms of rustc APIs used) by /// <https://github.com/xldenis/creusot/blob/9d8b1822cd0c43154a6d5d4d05460be56710399c/creusot/src/translation/traits.rs> use log::info; diff --git a/rr_frontend/translation/src/types/local.rs b/rr_frontend/translation/src/types/local.rs new file mode 100644 index 0000000000000000000000000000000000000000..d7b4074911dad1448ec65b8426a575ba37b057f5 --- /dev/null +++ b/rr_frontend/translation/src/types/local.rs @@ -0,0 +1,476 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! A wrapper around a `translator::TX` for the case we are translating the body of a function. + +use std::cell::RefCell; +use std::collections::HashMap; +use std::fmt::Write; + +use log::{info, trace, warn}; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::ty::{self, Ty, TypeFolder}; +use rr_rustc_interface::target; + +use crate::base::*; +use crate::environment::borrowck::facts; +use crate::environment::{polonius_info, Environment}; +use crate::regions::TyRegionCollectFolder; +use crate::traits::registry::GenericTraitUse; +use crate::traits::resolution; +use crate::types::translator::*; +use crate::types::tyvars::*; +use crate::types::{self, scope}; +use crate::{base, regions, traits}; + +/// Information we compute when calling a function from another function. +/// Determines how to specialize the callee's generics in our spec assumption. +pub struct AbstractedGenerics<'def> { + /// the scope with new generics to quantify over for the function's specialized spec + pub scope: radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>>, + /// instantiations for the specialized spec hint + pub callee_lft_param_inst: Vec<radium::Lft>, + pub callee_ty_param_inst: Vec<radium::Type<'def>>, + /// instantiations for the function use + pub fn_lft_param_inst: Vec<radium::Lft>, + pub fn_ty_param_inst: Vec<radium::Type<'def>>, +} + +/// Type translator bundling the function scope +#[allow(clippy::module_name_repetitions)] +pub struct LocalTX<'def, 'tcx> { + pub translator: &'def TX<'def, 'tcx>, + pub scope: RefCell<FunctionState<'tcx, 'def>>, +} + +impl<'def, 'tcx> LocalTX<'def, 'tcx> { + pub const fn new(translator: &'def TX<'def, 'tcx>, scope: FunctionState<'tcx, 'def>) -> Self { + Self { + translator, + scope: RefCell::new(scope), + } + } + + /// Get the `DefId` of the current function. + pub fn get_proc_did(&self) -> DefId { + let scope = self.scope.borrow(); + scope.did + } + + /// Translate a MIR type to the Radium syntactic type we need when storing an element of the type, + /// substituting all generics. + pub fn translate_type_to_syn_type( + &self, + ty: Ty<'tcx>, + ) -> Result<radium::SynType, TranslationError<'tcx>> { + let mut scope = self.scope.borrow_mut(); + let mut state = STInner::InFunction(&mut scope); + self.translator.translate_type_to_syn_type(ty, &mut state) + } + + /// Translate a region in the scope of the current function. + pub fn translate_region(&self, region: ty::Region<'tcx>) -> Result<radium::Lft, TranslationError<'tcx>> { + let mut scope = self.scope.borrow_mut(); + let mut scope = STInner::InFunction(&mut scope); + TX::translate_region(&mut scope, region) + } + + /// Translate a Polonius region variable in the scope of the current function. + pub fn translate_region_var(&self, region: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> { + let mut scope = self.scope.borrow_mut(); + let mut scope = STInner::InFunction(&mut scope); + scope.lookup_polonius_var(region) + } + + /// Translate type. + pub fn translate_type(&self, ty: Ty<'tcx>) -> Result<radium::Type<'def>, TranslationError<'tcx>> { + let mut scope = self.scope.borrow_mut(); + self.translator.translate_type(ty, &mut scope) + } + + /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently + /// registering a new ADT. + pub fn generate_structlike_use( + &self, + ty: Ty<'tcx>, + variant: Option<target::abi::VariantIdx>, + ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>> { + let mut scope = self.scope.borrow_mut(); + self.translator.generate_structlike_use(ty, variant, &mut scope) + } + + /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently + /// registering a new ADT. + pub fn generate_enum_use<F>( + &self, + adt_def: ty::AdtDef<'tcx>, + args: F, + ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>> + where + F: IntoIterator<Item = ty::GenericArg<'tcx>>, + { + let mut scope = self.scope.borrow_mut(); + self.translator.generate_enum_use(adt_def, args, &mut scope) + } + + /// Generate a struct use. + /// Returns None if this should be unit. + /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently + /// registering a new ADT. + pub fn generate_struct_use<F>( + &self, + variant_id: DefId, + args: F, + ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>> + where + F: IntoIterator<Item = ty::GenericArg<'tcx>>, + { + let mut scope = self.scope.borrow_mut(); + self.translator.generate_struct_use(variant_id, args, &mut scope) + } + + /// Generate a struct use. + /// Returns None if this should be unit. + pub fn generate_enum_variant_use<F>( + &self, + variant_id: DefId, + args: F, + ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>> + where + F: IntoIterator<Item = ty::GenericArg<'tcx>>, + { + let mut scope = self.scope.borrow_mut(); + self.translator.generate_enum_variant_use(variant_id, args, &mut scope) + } + + /// Make a tuple use. + /// Hack: This does not take the translation state but rather a direct reference to the tuple + /// use map so that we can this function when parsing closure specifications. + pub fn make_tuple_use( + &self, + translated_tys: Vec<radium::Type<'def>>, + uses: Option<&mut HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>>, + ) -> radium::Type<'def> { + self.translator.make_tuple_use(translated_tys, uses) + } + + pub fn generate_tuple_use<F>( + &self, + tys: F, + ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>> + where + F: IntoIterator<Item = Ty<'tcx>>, + { + let mut scope = self.scope.borrow_mut(); + self.translator.generate_tuple_use(tys, &mut STInner::InFunction(&mut scope)) + } + + /// Format the Coq representation of an atomic region. + pub fn format_atomic_region(&self, r: &polonius_info::AtomicRegion) -> String { + let scope = self.scope.borrow(); + scope.lifetime_scope.translate_atomic_region(r) + } + + /// Normalize a type in the given function environment. + pub fn normalize<T>(&self, ty: T) -> Result<T, TranslationError<'tcx>> + where + T: ty::TypeFoldable<ty::TyCtxt<'tcx>>, + { + let scope = self.scope.borrow(); + normalize_in_function(scope.did, self.translator.env().tcx(), ty) + } + + pub fn get_trait_of_method(env: &Environment<'tcx>, method_did: DefId) -> Option<DefId> { + if let Some(impl_did) = env.tcx().impl_of_method(method_did) { + env.tcx().trait_id_of_impl(impl_did) + } else { + // else expect it to be an abstract method of a trait decl + env.tcx().trait_of_item(method_did) + } + } + + /// Split the params of a trait method into params of the trait and params of the method + /// itself. + pub fn split_trait_method_args( + env: &Environment<'tcx>, + trait_did: DefId, + ty_params: ty::GenericArgsRef<'tcx>, + ) -> (ty::GenericArgsRef<'tcx>, ty::GenericArgsRef<'tcx>) { + // split args + let trait_generics: &'tcx ty::Generics = env.tcx().generics_of(trait_did); + let trait_generic_count = trait_generics.params.len(); + + let trait_args = &ty_params.as_slice()[..trait_generic_count]; + let method_args = &ty_params.as_slice()[trait_generic_count..]; + + (env.tcx().mk_args(trait_args), env.tcx().mk_args(method_args)) + } + + /// Lookup a trait parameter. + pub fn lookup_trait_param( + &self, + env: &Environment<'tcx>, + trait_did: DefId, + args: ty::GenericArgsRef<'tcx>, + ) -> Result<GenericTraitUse<'def>, traits::Error<'tcx>> { + let scope = self.scope.borrow(); + scope.generic_scope.trait_scope().lookup_trait_use(env.tcx(), trait_did, args).cloned() + } + + /// Register a procedure use of a trait method. + /// The given `ty_params` need to include the args to both the trait and the method. + /// Returns: + /// - the parameter name for the method loc + /// - the spec term for the method spec + /// - the arguments of the method + pub fn register_use_trait_procedure( + &self, + env: &Environment<'tcx>, + trait_method_did: DefId, + ty_params: ty::GenericArgsRef<'tcx>, + ) -> Result<(String, String, ty::GenericArgsRef<'tcx>), TranslationError<'tcx>> { + let trait_did = env + .tcx() + .trait_of_item(trait_method_did) + .ok_or(traits::Error::NotATrait(trait_method_did))?; + + // split args + let (trait_args, method_args) = Self::split_trait_method_args(env, trait_did, ty_params); + + let trait_spec_use = { + let scope = self.scope.borrow(); + let entry = scope.generic_scope.trait_scope().lookup_trait_use( + env.tcx(), + trait_did, + trait_args.as_slice(), + )?; + let trait_use_ref = entry.trait_use.borrow(); + trait_use_ref.as_ref().unwrap().clone() + }; + + // get name of the trait + let trait_name = trait_spec_use.trait_ref.name.clone(); + + // get name of the method + let method_name = env.get_assoc_item_name(trait_method_did).unwrap(); + let mangled_method_name = + types::mangle_name_with_args(&base::strip_coq_ident(&method_name), method_args.as_slice()); + + let method_loc_name = trait_spec_use.make_loc_name(&mangled_method_name); + + // get spec. the spec takes the generics of the method as arguments + let method_spec_term = trait_spec_use.make_method_spec_term(&method_name); + + Ok((method_loc_name, method_spec_term, method_args)) + } + + /// Abstract over the generics of a function and partially instantiate them. + /// Assumption: `trait_reqs` is appropriately sorted, i.e. surrounding requirements come first. + /// `with_surrounding_deps` determines whether we should distinguish surrounding and direct + /// params. + pub fn get_generic_abstraction_for_procedure( + &self, + callee_did: DefId, + ty_params: ty::GenericArgsRef<'tcx>, + trait_reqs: &[radium::TraitReqInst<'def, ty::Ty<'tcx>>], + with_surrounding_deps: bool, + ) -> Result<AbstractedGenerics<'def>, TranslationError<'tcx>> { + // get all the regions and type variables appearing that generics are instantiated with + let mut tyvar_folder = TyVarFolder::new(self.translator.env().tcx()); + let mut lft_folder = TyRegionCollectFolder::new(self.translator.env().tcx()); + + // also count the number of regions of the function itself + let mut num_param_regions = 0; + + let mut callee_lft_param_inst: Vec<radium::Lft> = Vec::new(); + let mut callee_ty_param_inst = Vec::new(); + for v in ty_params { + if let Some(ty) = v.as_type() { + tyvar_folder.fold_ty(ty); + lft_folder.fold_ty(ty); + } + if let Some(region) = v.as_region() { + num_param_regions += 1; + + let lft_name = self.translate_region(region)?; + callee_lft_param_inst.push(lft_name); + } + } + // also find generics in the associated types + for req in trait_reqs { + for ty in &req.assoc_ty_inst { + tyvar_folder.fold_ty(*ty); + lft_folder.fold_ty(*ty); + } + } + + let tyvars = tyvar_folder.get_result(); + let regions = lft_folder.get_regions(); + + let mut scope = radium::GenericScope::empty(); + + // instantiations for the function spec's parameters + let mut fn_lft_param_inst = Vec::new(); + let mut fn_ty_param_inst = Vec::new(); + + // re-bind the function's lifetime parameters + for i in 0..num_param_regions { + let lft_name = format!("ulft_{i}"); + scope.add_lft_param(lft_name.clone()); + fn_lft_param_inst.push(lft_name); + } + + // bind the additional lifetime parameters + let mut next_lft = num_param_regions; + for region in regions { + // Use the name the region has inside the function as the binder name, so that the + // names work out when translating the types below + let lft_name = self.translate_region_var(region).unwrap_or(format!("ulft_{next_lft}")); + scope.add_lft_param(lft_name.clone()); + + next_lft += 1; + + callee_lft_param_inst.push(lft_name); + } + + // bind the generics we use + for param in &tyvars { + // NOTE: this should have the same name as the using occurrences + let lit = radium::LiteralTyParam::new(param.name.as_str(), param.name.as_str()); + callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone())); + scope.add_ty_param(lit); + } + // also bind associated types which we translate as generics + for req in trait_reqs { + for ty in &req.assoc_ty_inst { + // we should check if it there is a parameter in the current scope for it + let translated_ty = self.translate_type(*ty)?; + if let radium::Type::LiteralParam(mut lit) = translated_ty { + lit.set_origin(req.origin); + + scope.add_ty_param(lit.clone()); + callee_ty_param_inst.push(radium::Type::LiteralParam(lit.clone())); + } + } + } + + // NOTE: we need to be careful with the order here. + // - the ty_params are all the generics the function has. + // - the trait_reqs are also all the associated types the function has + // We need to distinguish these between direct and surrounding. + let num_surrounding_params = + scope::Params::determine_number_of_surrounding_params(callee_did, self.translator.env().tcx()); + info!("num_surrounding_params={num_surrounding_params:?}, ty_params={ty_params:?}"); + + // figure out instantiation for the function's generics + // first the surrounding parameters + if with_surrounding_deps { + for v in &ty_params.as_slice()[..num_surrounding_params] { + if let Some(ty) = v.as_type() { + let translated_ty = self.translate_type(ty)?; + fn_ty_param_inst.push(translated_ty); + } + } + // same for the associated types this function depends on + for req in trait_reqs { + if req.origin != radium::TyParamOrigin::Direct { + for ty in &req.assoc_ty_inst { + let translated_ty = self.translate_type(*ty)?; + fn_ty_param_inst.push(translated_ty); + } + } + } + + // now the direct parameters + for v in &ty_params.as_slice()[num_surrounding_params..] { + if let Some(ty) = v.as_type() { + let translated_ty = self.translate_type(ty)?; + fn_ty_param_inst.push(translated_ty); + } + } + // same for the associated types this function depends on + for req in trait_reqs { + if req.origin == radium::TyParamOrigin::Direct { + for ty in &req.assoc_ty_inst { + let translated_ty = self.translate_type(*ty)?; + fn_ty_param_inst.push(translated_ty); + } + } + } + } else { + // now the direct parameters + for v in ty_params { + if let Some(ty) = v.as_type() { + let translated_ty = self.translate_type(ty)?; + fn_ty_param_inst.push(translated_ty); + } + } + // same for the associated types this function depends on + for req in trait_reqs { + if req.origin == radium::TyParamOrigin::Direct { + for ty in &req.assoc_ty_inst { + let translated_ty = self.translate_type(*ty)?; + fn_ty_param_inst.push(translated_ty); + } + } + } + } + + info!("Abstraction scope: {:?}", scope); + info!("Fn instantiation: {:?}, {:?}", fn_lft_param_inst, fn_ty_param_inst); + info!("Callee instantiation: {:?}, {:?}", callee_lft_param_inst, callee_ty_param_inst); + + let res = AbstractedGenerics { + scope, + callee_lft_param_inst, + callee_ty_param_inst, + fn_lft_param_inst, + fn_ty_param_inst, + }; + + Ok(res) + } +} + +/// Normalize a type in the given function environment. +pub fn normalize_in_function<'tcx, T>( + function_did: DefId, + tcx: ty::TyCtxt<'tcx>, + ty: T, +) -> Result<T, TranslationError<'tcx>> +where + T: ty::TypeFoldable<ty::TyCtxt<'tcx>>, +{ + let param_env = tcx.param_env(function_did); + info!("Normalizing type {ty:?} in env {param_env:?}"); + resolution::normalize_type(tcx, param_env, ty) + .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e))) +} + +pub fn normalize_erasing_regions_in_function<'tcx, T>( + function_did: DefId, + tcx: ty::TyCtxt<'tcx>, + ty: T, +) -> Result<T, TranslationError<'tcx>> +where + T: ty::TypeFoldable<ty::TyCtxt<'tcx>>, +{ + let param_env = tcx.param_env(function_did); + info!("Normalizing type {ty:?} in env {param_env:?}"); + tcx.try_normalize_erasing_regions(param_env, ty) + .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e))) +} + +pub fn normalize_projection_in_function<'tcx>( + function_did: DefId, + tcx: ty::TyCtxt<'tcx>, + ty: ty::AliasTy<'tcx>, +) -> Result<ty::Ty<'tcx>, TranslationError<'tcx>> { + let param_env = tcx.param_env(function_did); + info!("Normalizing type {ty:?} in env {param_env:?}"); + resolution::normalize_projection_type(tcx, param_env, ty) + .map_err(|e| TranslationError::TraitResolution(format!("could not normalize projection {ty:?}"))) +} diff --git a/rr_frontend/translation/src/types/mod.rs b/rr_frontend/translation/src/types/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..77ab789be0da146de93927f5eeee197f33a6151a --- /dev/null +++ b/rr_frontend/translation/src/types/mod.rs @@ -0,0 +1,43 @@ +// © 2023, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Utilities for translating Rust types into `RefinedRust` types. + +mod local; +pub mod scope; +mod translator; +mod tyvars; + +use std::fmt::Write; + +pub use local::{normalize_erasing_regions_in_function, normalize_in_function, LocalTX}; +/// We export these parts of the private modules +use rr_rustc_interface::middle::ty; +pub use scope::{generate_args_inst_key, GenericsKey}; +pub use translator::{AdtState, CalleeState, FunctionState, STInner, ST, TX}; + +use crate::base; + +/// Mangle a name by appending type parameters to it. +pub fn mangle_name_with_tys(method_name: &str, args: &[ty::Ty<'_>]) -> String { + // TODO: maybe come up with some better way to generate names + let mut mangled_name = method_name.to_owned(); + for arg in args { + mangled_name.push_str(format!("_{}", arg).as_str()); + } + base::strip_coq_ident(&mangled_name) +} + +/// Mangle a name by appending generic args to it. +pub fn mangle_name_with_args(name: &str, args: &[ty::GenericArg<'_>]) -> String { + let mut mangled_base = name.to_owned(); + for arg in args { + if let ty::GenericArgKind::Type(ty) = arg.unpack() { + write!(mangled_base, "_{}", base::strip_coq_ident(&format!("{ty}"))).unwrap(); + } + } + mangled_base +} diff --git a/rr_frontend/translation/src/types/scope.rs b/rr_frontend/translation/src/types/scope.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ed043ea4290d231e2cce8471bded5bfdbb6b203 --- /dev/null +++ b/rr_frontend/translation/src/types/scope.rs @@ -0,0 +1,572 @@ +// © 2024, The RefinedRust Developers and Contributors +// +// This Source Code Form is subject to the terms of the BSD-3-clause License. +// If a copy of the BSD-3-clause license was not distributed with this +// file, You can obtain one at https://opensource.org/license/bsd-3-clause/. + +//! Defines scopes for maintaining generics and trait requirements. + +use std::collections::{HashMap, HashSet}; +use std::fmt::Write; + +use derive_more::{Constructor, Debug}; +use log::{info, trace, warn}; +use rr_rustc_interface::hir::def_id::DefId; +use rr_rustc_interface::middle::ty; +use rr_rustc_interface::middle::ty::TypeFoldable; + +use crate::base; +use crate::base::*; +use crate::environment::Environment; +use crate::spec_parsers::parse_utils::ParamLookup; +use crate::traits::registry::GenericTraitUse; +use crate::traits::{self, registry}; +use crate::types::translator::*; +use crate::types::tyvars::*; + +/// Key used for resolving early-bound parameters for function calls. +/// Invariant: All regions contained in these types should be erased, as type parameter instantiation is +/// independent of lifetimes. +/// TODO: handle early-bound lifetimes? +pub type GenericsKey<'tcx> = Vec<ty::Ty<'tcx>>; + +/// Generate a key for indexing into structures indexed by `GenericArg`s. +pub fn generate_args_inst_key<'tcx>( + tcx: ty::TyCtxt<'tcx>, + ty_params: &[ty::GenericArg<'tcx>], +) -> Result<GenericsKey<'tcx>, TranslationError<'tcx>> { + // erase parameters to their syntactic types + let mut key = Vec::new(); + let mut region_eraser = TyRegionEraseFolder::new(tcx); + for p in ty_params { + match p.unpack() { + ty::GenericArgKind::Lifetime(_) => { + // lifetimes are not relevant here + }, + ty::GenericArgKind::Type(t) => { + // TODO: this should erase to the syntactic type. + // Is erasing regions enough for that? + let t_erased = t.fold_with(&mut region_eraser); + key.push(t_erased); + }, + ty::GenericArgKind::Const(_c) => { + return Err(TranslationError::UnsupportedFeature { + description: "RefinedRust does not support const generics".to_owned(), + }); + }, + } + } + Ok(key) +} + +/// Keys used to deduplicate adt uses for `syn_type` assumptions. +/// TODO maybe we should use `SimplifiedType` + `simplify_type` instead of the syntys? +/// Or types with erased regions? +#[derive(Eq, PartialEq, Hash, Debug)] +pub struct AdtUseKey { + pub base_did: DefId, + pub generics: Vec<radium::SynType>, +} + +impl AdtUseKey { + pub fn new(defid: DefId, params: &[radium::Type<'_>]) -> Self { + let generic_syntys: Vec<_> = params.iter().map(radium::SynType::from).collect(); + Self { + base_did: defid, + generics: generic_syntys, + } + } +} + +#[derive(Clone, Debug)] +enum Param { + Region(radium::Lft), + Ty(radium::LiteralTyParam), + // Note: we do not currently support Const params + Const, +} +impl Param { + const fn as_type(&self) -> Option<&radium::LiteralTyParam> { + match self { + Self::Ty(lit) => Some(lit), + _ => None, + } + } + + const fn as_region(&self) -> Option<&radium::Lft> { + match self { + Self::Region(lit) => Some(lit), + _ => None, + } + } +} + +/// Data structure that maps generic parameters for ADT/trait translation +#[derive(Constructor, Clone, Debug, Default)] +pub struct Params<'tcx, 'def> { + /// maps generic indices (De Bruijn) to the corresponding Coq names in the current environment + scope: Vec<Param>, + + /// conversely, map the declaration name of a lifetime to an index + lft_names: HashMap<String, usize>, + /// map types to their index + ty_names: HashMap<String, usize>, + + /// the trait instances which are in scope + trait_scope: Traits<'tcx, 'def>, +} + +#[allow(clippy::fallible_impl_from)] +impl<'tcx, 'def> From<Params<'tcx, 'def>> for radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>> { + fn from(mut x: Params<'tcx, 'def>) -> Self { + let mut scope = Self::empty(); + for x in x.scope { + match x { + Param::Region(lft) => { + scope.add_lft_param(lft); + }, + Param::Ty(ty) => { + scope.add_ty_param(ty); + }, + Param::Const => (), + } + } + for key in x.trait_scope.ordered_assumptions { + let trait_use = x.trait_scope.used_traits.remove(&key).unwrap().trait_use; + let trait_use = trait_use.borrow_mut().take().unwrap(); + scope.add_trait_requirement(trait_use); + } + scope + } +} +impl<'tcx, 'def> ParamLookup for Params<'tcx, 'def> { + fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> { + if path.len() == 1 { + let idx = self.ty_names.get(path[0])?; + self.lookup_ty_param_idx(*idx) + } else { + None + } + } + + fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> { + let idx = self.lft_names.get(lft)?; + self.lookup_region_idx(*idx) + } + + fn lookup_literal(&self, path: &[&str]) -> Option<&str> { + None + } +} +impl<'tcx, 'def> Params<'tcx, 'def> { + pub const fn trait_scope(&self) -> &Traits<'tcx, 'def> { + &self.trait_scope + } + + /// Create from generics, optionally annotating the type parameters with their origin. + pub fn new_from_generics( + x: ty::GenericArgsRef<'tcx>, + with_origin: Option<(ty::TyCtxt<'tcx>, DefId)>, + ) -> Self { + let mut scope = Vec::new(); + let mut region_count = 0; + + let mut ty_names = HashMap::new(); + let mut lft_names = HashMap::new(); + + for p in x { + if let Some(r) = p.as_region() { + if let Some(name) = r.get_name() { + lft_names.insert(name.as_str().to_owned(), scope.len()); + scope.push(Param::Region(base::strip_coq_ident(name.as_str()))); + } else { + let name = format!("ulft_{region_count}"); + region_count += 1; + scope.push(Param::Region(name)); + } + } else if let Some(ty) = p.as_type() { + if let ty::TyKind::Param(x) = ty.kind() { + ty_names.insert(x.name.as_str().to_owned(), scope.len()); + let name = base::strip_coq_ident(x.name.as_str()); + + let lit = if let Some((tcx, of_did)) = with_origin { + let origin = Self::determine_origin_of_param(of_did, tcx, *x); + radium::LiteralTyParam::new_with_origin(&name, &name, origin) + } else { + radium::LiteralTyParam::new(&name, &name) + }; + scope.push(Param::Ty(lit)); + } else { + unreachable!("Should not convert a non-parametric GenericArgsRef to a Params"); + } + } else if p.as_const().is_some() { + scope.push(Param::Const); + } + } + Self { + scope, + lft_names, + ty_names, + trait_scope: Traits::default(), + } + } + + /// Lookup a type parameter by its De Bruijn index. + #[must_use] + pub fn lookup_ty_param_idx(&self, idx: usize) -> Option<&radium::LiteralTyParam> { + let ty = self.scope.get(idx)?; + ty.as_type() + } + + /// Lookup a region parameter by its De Bruijn index. + #[must_use] + pub fn lookup_region_idx(&self, idx: usize) -> Option<&radium::Lft> { + let lft = self.scope.get(idx)?; + lft.as_region() + } + + /// Get all type parameters in scope. + #[must_use] + pub fn tyvars(&self) -> Vec<radium::LiteralTyParam> { + let mut tyvars = Vec::new(); + for x in &self.scope { + if let Param::Ty(ty) = x { + tyvars.push(ty.to_owned()); + } + } + tyvars + } + + /// Create a scope of typarams masked by a set of parameters. + /// The input must be sorted. + #[must_use] + pub fn mask_typarams(&self, used_params: &[ty::ParamTy]) -> Vec<radium::LiteralTyParam> { + let mut res = Vec::new(); + for x in used_params { + let ty = self.lookup_ty_param_idx(x.index as usize).unwrap(); + res.push(ty.to_owned()); + } + res + } + + /// Add a `ParamEnv` of a given `DefId` to the scope to process trait obligations. + pub fn add_param_env( + &mut self, + did: DefId, + env: &Environment<'tcx>, + type_translator: &TX<'def, 'tcx>, + trait_registry: ®istry::TR<'tcx, 'def>, + ) -> Result<(), TranslationError<'tcx>> { + trace!("Enter add_param_env for did = {did:?}"); + let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did); + + // TODO + // What happens when we encounter the Self requirement when registering a trait? + // Is it okay to skip that? + + // TODO: add scope for referring to associated types in specs + + let requirements = Self::get_trait_requirements_with_origin(env, did); + + // pre-register all the requirements, in order to resolve dependencies + for (trait_ref, _, _) in &requirements { + let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap()); + let dummy_trait_use = trait_registry.make_empty_trait_use(*trait_ref); + self.trait_scope.used_traits.insert(key.clone(), dummy_trait_use); + } + + for (trait_ref, origin, is_used_in_self_trait) in &requirements { + // lookup the trait in the trait registry + if let Some(trait_spec) = trait_registry.lookup_trait(trait_ref.def_id) { + let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap()); + let entry = &self.trait_scope.used_traits[&key]; + + let mut deps = HashSet::new(); + // the scope to translate the arguments in + let mut state = STInner::TranslateAdt(AdtState::new(&mut deps, &*self, ¶m_env)); + + trait_registry.fill_trait_use( + entry, + &mut state, + trait_ref.to_owned(), + trait_spec, + *is_used_in_self_trait, + // trait associated types are fully generic for now, we make a second pass + // below + HashMap::new(), + *origin, + )?; + + self.trait_scope.ordered_assumptions.push(key); + } else { + return Err(traits::Error::UnregisteredTrait(trait_ref.def_id).into()); + } + } + + // make a second pass to specify constraints on associated types + // We do this in a second pass so that we can refer to the other associated types + for (trait_ref, origin, _) in requirements { + let assoc_constraints = traits::get_trait_assoc_constraints(env, param_env, trait_ref); + + let translated_constraints: HashMap<_, _> = assoc_constraints + .into_iter() + .map(|(name, ty)| { + let translated_ty = type_translator.translate_type_in_scope(self, ty).unwrap(); + (name, translated_ty) + }) + .collect(); + + // lookup the trait use + let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap()); + let entry = &self.trait_scope.used_traits[&key]; + + { + let mut trait_use_ref = entry.trait_use.borrow_mut(); + let trait_use = trait_use_ref.as_mut().unwrap(); + // and add the constraints + for (name, constr) in translated_constraints { + trait_use.specialize_assoc_type(name, constr); + } + } + + // finalize the entry by adding dependencies on other trait parameters + let mut deps = HashSet::new(); + let mut state = STInner::TranslateAdt(AdtState::new(&mut deps, &*self, ¶m_env)); + trait_registry.finalize_trait_use(entry, &mut state, trait_ref)?; + } + + trace!("Leave add_param_env for did = {did:?}"); + Ok(()) + } +} +impl<'tcx, 'def> Params<'tcx, 'def> { + /// Determine the declaration origin of a type parameter of a function. + fn determine_origin_of_param( + did: DefId, + tcx: ty::TyCtxt<'tcx>, + param: ty::ParamTy, + ) -> radium::TyParamOrigin { + // Check if there is a surrounding trait decl that introduces this parameter + if let Some(trait_did) = tcx.trait_of_item(did) { + let generics: &'tcx ty::Generics = tcx.generics_of(trait_did); + + for this_param in &generics.params { + if this_param.name == param.name { + return radium::TyParamOrigin::SurroundingTrait; + } + } + } + // Check if there is a surrounding trait impl that introduces this parameter + if let Some(impl_did) = tcx.impl_of_method(did) { + let generics: &'tcx ty::Generics = tcx.generics_of(impl_did); + + for this_param in &generics.params { + if this_param.name == param.name { + return radium::TyParamOrigin::SurroundingImpl; + } + } + } + + radium::TyParamOrigin::Direct + } + + /// Determine the number of args of a surrounding trait or impl. + pub fn determine_number_of_surrounding_params(did: DefId, tcx: ty::TyCtxt<'tcx>) -> usize { + // Check if there is a surrounding trait decl that introduces this parameter + if let Some(trait_did) = tcx.trait_of_item(did) { + let generics: &'tcx ty::Generics = tcx.generics_of(trait_did); + + return generics.params.len(); + } + // Check if there is a surrounding trait impl that introduces this parameter + if let Some(impl_did) = tcx.impl_of_method(did) { + let generics: &'tcx ty::Generics = tcx.generics_of(impl_did); + + return generics.params.len(); + } + + 0 + } + + /// Determine the origin of a trait obligation. + /// `surrounding_reqs` are the requirements of a surrounding impl or decl. + fn determine_origin_of_trait_requirement( + did: DefId, + tcx: ty::TyCtxt<'tcx>, + surrounding_reqs: &Option<Vec<ty::TraitRef<'tcx>>>, + req: ty::TraitRef<'tcx>, + ) -> radium::TyParamOrigin { + if let Some(surrounding_reqs) = surrounding_reqs { + let in_trait_decl = tcx.trait_of_item(did); + + if surrounding_reqs.contains(&req) { + if in_trait_decl.is_some() { + return radium::TyParamOrigin::SurroundingTrait; + } + return radium::TyParamOrigin::SurroundingImpl; + } + } + radium::TyParamOrigin::Direct + } + + /// Get the trait requirements of a [did], also determining their origin relative to the [did]. + /// The requirements are sorted in a way that is stable across compilations. + pub fn get_trait_requirements_with_origin( + env: &Environment<'tcx>, + did: DefId, + ) -> Vec<(ty::TraitRef<'tcx>, radium::TyParamOrigin, bool)> { + trace!("Enter get_trait_requirements_with_origin for did={did:?}"); + let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did); + + // Are we declaring the scope of a trait? + let is_trait = env.tcx().is_trait(did); + + // Determine whether we are declaring the scope of a trait method or trait impl method + let in_trait_decl = env.tcx().trait_of_item(did); + let in_trait_impl = env.trait_impl_of_method(did); + + // if this has a surrounding scope, get the requirements declared on that, so that we can + // determine the origin of this requirement below + let surrounding_reqs = if let Some(trait_did) = in_trait_decl { + let trait_param_env = env.tcx().param_env(trait_did); + Some(traits::requirements::get_nontrivial(env.tcx(), trait_param_env, None)) + } else if let Some(impl_did) = in_trait_impl { + let impl_param_env = env.tcx().param_env(impl_did); + Some(traits::requirements::get_nontrivial(env.tcx(), impl_param_env, None)) + } else { + None + }; + + let clauses = param_env.caller_bounds(); + info!("Caller bounds: {:?}", clauses); + + let in_trait_decl = if is_trait { Some(did) } else { in_trait_decl }; + let requirements = traits::requirements::get_nontrivial(env.tcx(), param_env, in_trait_decl); + let mut annotated_requirements = Vec::new(); + + for trait_ref in requirements { + // check if we are in the process of translating a trait decl + let is_self = trait_ref.args[0].as_type().unwrap().is_param(0); + let mut is_used_in_self_trait = false; + if let Some(trait_decl_did) = in_trait_decl { + // is this a reference to the trait we are currently declaring + let is_use_of_self_trait = trait_decl_did == trait_ref.def_id; + + if is_use_of_self_trait && is_self { + // This is the self assumption of the trait we are currently implementing + // For a function spec in a trait decl, we remember this, as we do not require + // a quantified spec for the Self trait. + is_used_in_self_trait = true; + } + } + + // we are processing the Self requirement in the scope of a trait declaration, so skip + // this. + if is_trait && is_self { + continue; + } + + let origin = + Self::determine_origin_of_trait_requirement(did, env.tcx(), &surrounding_reqs, trait_ref); + info!("Determined origin of requirement {trait_ref:?} as {origin:?}"); + + annotated_requirements.push((trait_ref, origin, is_used_in_self_trait)); + } + + trace!( + "Leave get_trait_requirements_with_origin for did={did:?} with annotated_requirements={annotated_requirements:?}" + ); + annotated_requirements + } +} + +impl<'tcx, 'def> From<ty::GenericArgsRef<'tcx>> for Params<'tcx, 'def> { + fn from(x: ty::GenericArgsRef<'tcx>) -> Self { + Self::new_from_generics(x, None) + } +} + +impl<'a, 'tcx, 'def> From<&'a [ty::GenericParamDef]> for Params<'tcx, 'def> { + fn from(x: &[ty::GenericParamDef]) -> Self { + let mut scope = Vec::new(); + + let mut ty_names = HashMap::new(); + let mut lft_names = HashMap::new(); + + for p in x { + let name = base::strip_coq_ident(p.name.as_str()); + match p.kind { + ty::GenericParamDefKind::Const { .. } => { + scope.push(Param::Const); + }, + ty::GenericParamDefKind::Type { .. } => { + let lit = radium::LiteralTyParam::new(&name, &name); + ty_names.insert(p.name.as_str().to_owned(), scope.len()); + scope.push(Param::Ty(lit)); + }, + ty::GenericParamDefKind::Lifetime => { + let name = format!("ulft_{name}"); + lft_names.insert(p.name.as_str().to_owned(), scope.len()); + scope.push(Param::Region(name)); + }, + } + } + Self { + scope, + lft_names, + ty_names, + trait_scope: Traits::default(), + } + } +} + +/// A scope for translated trait requirements from `where` clauses. +#[derive(Clone, Debug, Default)] +pub struct Traits<'tcx, 'def> { + used_traits: HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>>, + ordered_assumptions: Vec<(DefId, GenericsKey<'tcx>)>, +} + +impl<'tcx, 'def> Traits<'tcx, 'def> { + /// Lookup the trait use for a specific trait with given parameters. + /// (here, args includes the self parameter as the first element) + pub fn lookup_trait_use( + &self, + tcx: ty::TyCtxt<'tcx>, + trait_did: DefId, + args: &[ty::GenericArg<'tcx>], + ) -> Result<&GenericTraitUse<'def>, traits::Error<'tcx>> { + if !tcx.is_trait(trait_did) { + return Err(traits::Error::NotATrait(trait_did)); + } + + let key = (trait_did, generate_args_inst_key(tcx, args).unwrap()); + trace!("looking up trait use {key:?} in {:?}", self.used_traits); + if let Some(trait_ref) = self.used_traits.get(&key) { + Ok(trait_ref) + } else { + Err(traits::Error::UnknownLocalInstance(trait_did, tcx.mk_args(args))) + } + } + + /// Get trait uses in the current scope. + pub const fn get_trait_uses(&self) -> &HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>> { + &self.used_traits + } + + /// Within a trait declaration, get the Self trait use. + pub fn get_self_trait_use(&self) -> Option<&GenericTraitUse<'def>> { + for trait_use in self.used_traits.values() { + // check if this is the Self trait use + { + let spec_use_ref = trait_use.trait_use.borrow(); + let spec_use = spec_use_ref.as_ref().unwrap(); + if !spec_use.is_used_in_self_trait { + continue; + } + } + return Some(trait_use); + } + None + } +} diff --git a/rr_frontend/translation/src/type_translator.rs b/rr_frontend/translation/src/types/translator.rs similarity index 63% rename from rr_frontend/translation/src/type_translator.rs rename to rr_frontend/translation/src/types/translator.rs index 5525d420a3a2816f83fb51082a587c25fc2778a5..9cdf7ab02d6645dba24e540befcf912a6d30418b 100644 --- a/rr_frontend/translation/src/type_translator.rs +++ b/rr_frontend/translation/src/types/translator.rs @@ -1,12 +1,13 @@ -// © 2023, The RefinedRust Developers and Contributors +// © 2024, The RefinedRust Developers and Contributors // // This Source Code Form is subject to the terms of the BSD-3-clause License. // If a copy of the BSD-3-clause license was not distributed with this // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. +//! The main translator for translating types within certain environments. + use std::cell::{OnceCell, RefCell}; -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fmt::Write; +use std::collections::{HashMap, HashSet}; use derive_more::{Constructor, Debug}; use log::{info, trace, warn}; @@ -19,614 +20,27 @@ use typed_arena::Arena; use crate::base::*; use crate::environment::borrowck::facts; -use crate::environment::polonius_info::{self, PoloniusInfo}; +use crate::environment::polonius_info::PoloniusInfo; use crate::environment::Environment; -use crate::function_body::{get_arg_syntypes_for_procedure_call, mangle_name_with_args}; +use crate::regions::{format_atomic_region_direct, EarlyLateRegionMap}; use crate::spec_parsers::enum_spec_parser::{parse_enum_refine_as, EnumSpecParser, VerboseEnumSpecParser}; use crate::spec_parsers::parse_utils::ParamLookup; use crate::spec_parsers::struct_spec_parser::{self, InvariantSpecParser, StructFieldSpecParser}; -use crate::trait_registry::{self, Error, GenericTraitUse, TraitRegistry, TraitResult}; -use crate::traits::{normalize_projection_type, normalize_type}; -use crate::tyvars::*; -use crate::utils; - -/// Strip symbols from an identifier to be compatible with Coq. -/// In particular things like ' or ::. -pub fn strip_coq_ident(s: &str) -> String { - String::from(s) - .replace('\'', "") - .replace("::", "_") - .replace(|c: char| !(c.is_alphanumeric() || c == '_'), "") -} - -/// Key used for resolving early-bound parameters for function calls. -/// Invariant: All regions contained in these types should be erased, as type parameter instantiation is -/// independent of lifetimes. -/// TODO: handle early-bound lifetimes? -pub type GenericsKey<'tcx> = Vec<ty::Ty<'tcx>>; - -/// Generate a key for indexing into structures indexed by `GenericArg`s. -pub fn generate_args_inst_key<'tcx>( - tcx: ty::TyCtxt<'tcx>, - ty_params: &[ty::GenericArg<'tcx>], -) -> Result<GenericsKey<'tcx>, TranslationError<'tcx>> { - // erase parameters to their syntactic types - let mut key = Vec::new(); - let mut region_eraser = TyRegionEraseFolder::new(tcx); - for p in ty_params { - match p.unpack() { - ty::GenericArgKind::Lifetime(_) => { - // lifetimes are not relevant here - }, - ty::GenericArgKind::Type(t) => { - // TODO: this should erase to the syntactic type. - // Is erasing regions enough for that? - let t_erased = t.fold_with(&mut region_eraser); - key.push(t_erased); - }, - ty::GenericArgKind::Const(_c) => { - return Err(TranslationError::UnsupportedFeature { - description: "RefinedRust does not support const generics".to_owned(), - }); - }, - } - } - Ok(key) -} - -/// Keys used to deduplicate adt uses for `syn_type` assumptions. -/// TODO maybe we should use `SimplifiedType` + `simplify_type` instead of the syntys? -/// Or types with erased regions? -#[derive(Eq, PartialEq, Hash, Debug)] -pub struct AdtUseKey { - pub base_did: DefId, - pub generics: Vec<radium::SynType>, -} - -impl AdtUseKey { - pub fn new(defid: DefId, params: &[radium::Type<'_>]) -> Self { - let generic_syntys: Vec<_> = params.iter().map(radium::SynType::from).collect(); - Self { - base_did: defid, - generics: generic_syntys, - } - } -} - -/// Data structure that maps early and late region indices to Polonius regions. -#[derive(Constructor, Clone, Debug, Default)] -pub struct EarlyLateRegionMap { - // maps indices of early and late regions to Polonius region ids - pub early_regions: Vec<Option<ty::RegionVid>>, - pub late_regions: Vec<ty::RegionVid>, - - // maps Polonius region ids to names - pub region_names: BTreeMap<ty::RegionVid, radium::Lft>, - - // maps source-level universal lifetime names to region ids - pub lft_names: HashMap<String, ty::RegionVid>, -} -impl EarlyLateRegionMap { - pub fn lookup_region(&self, region: ty::RegionVid) -> Option<&radium::Lft> { - self.region_names.get(®ion) - } - - pub fn lookup_early_region(&self, idx: usize) -> Option<&radium::Lft> { - let ovid = self.early_regions.get(idx)?; - let vid = ovid.as_ref()?; - self.lookup_region(*vid) - } - - pub fn lookup_late_region(&self, idx: usize) -> Option<&radium::Lft> { - let vid = self.late_regions.get(idx)?; - self.lookup_region(*vid) - } -} - -#[derive(Clone, Debug)] -pub enum Param { - Region(radium::Lft), - Ty(radium::LiteralTyParam), - // Note: we do not currently support Const params - Const, -} -impl Param { - const fn as_type(&self) -> Option<&radium::LiteralTyParam> { - match self { - Self::Ty(lit) => Some(lit), - _ => None, - } - } - - const fn as_region(&self) -> Option<&radium::Lft> { - match self { - Self::Region(lit) => Some(lit), - _ => None, - } - } -} - -/// Data structure that maps generic parameters for ADT/trait translation -#[derive(Constructor, Clone, Debug, Default)] -pub struct ParamScope<'tcx, 'def> { - /// maps generic indices (De Bruijn) to the corresponding Coq names in the current environment - pub(crate) scope: Vec<Param>, - - /// conversely, map the declaration name of a lifetime to an index - lft_names: HashMap<String, usize>, - /// map types to their index - ty_names: HashMap<String, usize>, - - /// the trait instances which are in scope - pub(crate) trait_scope: TraitScope<'tcx, 'def>, -} - -#[allow(clippy::fallible_impl_from)] -impl<'tcx, 'def> From<ParamScope<'tcx, 'def>> - for radium::GenericScope<'def, radium::LiteralTraitSpecUse<'def>> -{ - fn from(mut x: ParamScope<'tcx, 'def>) -> Self { - let mut scope = Self::empty(); - for x in x.scope { - match x { - Param::Region(lft) => { - scope.add_lft_param(lft); - }, - Param::Ty(ty) => { - scope.add_ty_param(ty); - }, - Param::Const => (), - } - } - for key in x.trait_scope.ordered_assumptions { - let trait_use = x.trait_scope.used_traits.remove(&key).unwrap().trait_use; - let trait_use = trait_use.borrow_mut().take().unwrap(); - scope.add_trait_requirement(trait_use); - } - scope - } -} -impl<'tcx, 'def> ParamLookup for ParamScope<'tcx, 'def> { - fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> { - if path.len() == 1 { - let idx = self.ty_names.get(path[0])?; - self.lookup_ty_param_idx(*idx) - } else { - None - } - } - - fn lookup_lft(&self, lft: &str) -> Option<&radium::Lft> { - let idx = self.lft_names.get(lft)?; - self.lookup_region_idx(*idx) - } - - fn lookup_literal(&self, path: &[&str]) -> Option<&str> { - None - } -} -impl<'tcx, 'def> ParamScope<'tcx, 'def> { - /// Get the trait requirements of a [did], also determining their origin relative to the [did]. - /// The requirements are sorted in a way that is stable across compilations. - pub fn get_trait_requirements_with_origin( - env: &Environment<'tcx>, - did: DefId, - ) -> Vec<(ty::TraitRef<'tcx>, radium::TyParamOrigin, bool)> { - trace!("Enter get_trait_requirements_with_origin for did={did:?}"); - let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did); - - // Are we declaring the scope of a trait? - let is_trait = env.tcx().is_trait(did); - - // Determine whether we are declaring the scope of a trait method or trait impl method - let in_trait_decl = env.tcx().trait_of_item(did); - let in_trait_impl = env.trait_impl_of_method(did); - - // if this has a surrounding scope, get the requirements declared on that, so that we can - // determine the origin of this requirement below - let surrounding_reqs = if let Some(trait_did) = in_trait_decl { - let trait_param_env = env.tcx().param_env(trait_did); - Some(trait_registry::get_nontrivial_trait_requirements(env.tcx(), trait_param_env, None)) - } else if let Some(impl_did) = in_trait_impl { - let impl_param_env = env.tcx().param_env(impl_did); - Some(trait_registry::get_nontrivial_trait_requirements(env.tcx(), impl_param_env, None)) - } else { - None - }; - - let clauses = param_env.caller_bounds(); - info!("Caller bounds: {:?}", clauses); - - let in_trait_decl = if is_trait { Some(did) } else { in_trait_decl }; - let requirements = - trait_registry::get_nontrivial_trait_requirements(env.tcx(), param_env, in_trait_decl); - let mut annotated_requirements = Vec::new(); - - for trait_ref in requirements { - // check if we are in the process of translating a trait decl - let is_self = trait_ref.args[0].as_type().unwrap().is_param(0); - let mut is_used_in_self_trait = false; - if let Some(trait_decl_did) = in_trait_decl { - // is this a reference to the trait we are currently declaring - let is_use_of_self_trait = trait_decl_did == trait_ref.def_id; - - if is_use_of_self_trait && is_self { - // This is the self assumption of the trait we are currently implementing - // For a function spec in a trait decl, we remember this, as we do not require - // a quantified spec for the Self trait. - is_used_in_self_trait = true; - } - } - - // we are processing the Self requirement in the scope of a trait declaration, so skip - // this. - if is_trait && is_self { - continue; - } - - let origin = - Self::determine_origin_of_trait_requirement(did, env.tcx(), &surrounding_reqs, trait_ref); - info!("Determined origin of requirement {trait_ref:?} as {origin:?}"); - - annotated_requirements.push((trait_ref, origin, is_used_in_self_trait)); - } - - trace!( - "Leave get_trait_requirements_with_origin for did={did:?} with annotated_requirements={annotated_requirements:?}" - ); - annotated_requirements - } - - /// Determine the declaration origin of a type parameter of a function. - fn determine_origin_of_param( - did: DefId, - tcx: ty::TyCtxt<'tcx>, - param: ty::ParamTy, - ) -> radium::TyParamOrigin { - // Check if there is a surrounding trait decl that introduces this parameter - if let Some(trait_did) = tcx.trait_of_item(did) { - let generics: &'tcx ty::Generics = tcx.generics_of(trait_did); - - for this_param in &generics.params { - if this_param.name == param.name { - return radium::TyParamOrigin::SurroundingTrait; - } - } - } - // Check if there is a surrounding trait impl that introduces this parameter - if let Some(impl_did) = tcx.impl_of_method(did) { - let generics: &'tcx ty::Generics = tcx.generics_of(impl_did); - - for this_param in &generics.params { - if this_param.name == param.name { - return radium::TyParamOrigin::SurroundingImpl; - } - } - } - - radium::TyParamOrigin::Direct - } - - /// Determine the number of args of a surrounding trait or impl. - pub fn determine_number_of_surrounding_params(did: DefId, tcx: ty::TyCtxt<'tcx>) -> usize { - // Check if there is a surrounding trait decl that introduces this parameter - if let Some(trait_did) = tcx.trait_of_item(did) { - let generics: &'tcx ty::Generics = tcx.generics_of(trait_did); - - return generics.params.len(); - } - // Check if there is a surrounding trait impl that introduces this parameter - if let Some(impl_did) = tcx.impl_of_method(did) { - let generics: &'tcx ty::Generics = tcx.generics_of(impl_did); - - return generics.params.len(); - } - - 0 - } - - /// Determine the origin of a trait obligation. - /// `surrounding_reqs` are the requirements of a surrounding impl or decl. - fn determine_origin_of_trait_requirement( - did: DefId, - tcx: ty::TyCtxt<'tcx>, - surrounding_reqs: &Option<Vec<ty::TraitRef<'tcx>>>, - req: ty::TraitRef<'tcx>, - ) -> radium::TyParamOrigin { - if let Some(surrounding_reqs) = surrounding_reqs { - let in_trait_decl = tcx.trait_of_item(did); - - if surrounding_reqs.contains(&req) { - if in_trait_decl.is_some() { - return radium::TyParamOrigin::SurroundingTrait; - } - return radium::TyParamOrigin::SurroundingImpl; - } - } - radium::TyParamOrigin::Direct - } - - /// Create from generics, optionally annotating the type parameters with their origin. - pub fn new_from_generics( - x: ty::GenericArgsRef<'tcx>, - with_origin: Option<(ty::TyCtxt<'tcx>, DefId)>, - ) -> Self { - let mut scope = Vec::new(); - let mut region_count = 0; - - let mut ty_names = HashMap::new(); - let mut lft_names = HashMap::new(); - - for p in x { - if let Some(r) = p.as_region() { - if let Some(name) = r.get_name() { - lft_names.insert(name.as_str().to_owned(), scope.len()); - scope.push(Param::Region(strip_coq_ident(name.as_str()))); - } else { - let name = format!("ulft_{region_count}"); - region_count += 1; - scope.push(Param::Region(name)); - } - } else if let Some(ty) = p.as_type() { - if let ty::TyKind::Param(x) = ty.kind() { - ty_names.insert(x.name.as_str().to_owned(), scope.len()); - let name = strip_coq_ident(x.name.as_str()); - - let lit = if let Some((tcx, of_did)) = with_origin { - let origin = Self::determine_origin_of_param(of_did, tcx, *x); - radium::LiteralTyParam::new_with_origin(&name, &name, origin) - } else { - radium::LiteralTyParam::new(&name, &name) - }; - scope.push(Param::Ty(lit)); - } else { - unreachable!("Should not convert a non-parametric GenericArgsRef to a ParamScope"); - } - } else if p.as_const().is_some() { - scope.push(Param::Const); - } - } - Self { - scope, - lft_names, - ty_names, - trait_scope: TraitScope::default(), - } - } - - /// Lookup a type parameter by its De Bruijn index. - #[must_use] - pub fn lookup_ty_param_idx(&self, idx: usize) -> Option<&radium::LiteralTyParam> { - let ty = self.scope.get(idx)?; - ty.as_type() - } - - /// Lookup a region parameter by its De Bruijn index. - #[must_use] - pub fn lookup_region_idx(&self, idx: usize) -> Option<&radium::Lft> { - let lft = self.scope.get(idx)?; - lft.as_region() - } - - /// Get all type parameters in scope. - #[must_use] - pub fn tyvars(&self) -> Vec<radium::LiteralTyParam> { - let mut tyvars = Vec::new(); - for x in &self.scope { - if let Param::Ty(ty) = x { - tyvars.push(ty.to_owned()); - } - } - tyvars - } - - /// Create a scope of typarams masked by a set of parameters. - /// The input must be sorted. - #[must_use] - pub fn mask_typarams(&self, used_params: &[ty::ParamTy]) -> Vec<radium::LiteralTyParam> { - let mut res = Vec::new(); - for x in used_params { - let ty = self.lookup_ty_param_idx(x.index as usize).unwrap(); - res.push(ty.to_owned()); - } - res - } - - /// Add a `ParamEnv` of a given `DefId` to the scope to process trait obligations. - pub fn add_param_env( - &mut self, - did: DefId, - env: &Environment<'tcx>, - type_translator: &TypeTranslator<'def, 'tcx>, - trait_registry: &TraitRegistry<'tcx, 'def>, - ) -> Result<(), TranslationError<'tcx>> { - trace!("Enter add_param_env for did = {did:?}"); - let param_env: ty::ParamEnv<'tcx> = env.tcx().param_env(did); - - // TODO - // What happens when we encounter the Self requirement when registering a trait? - // Is it okay to skip that? - - // TODO: add scope for referring to associated types in specs - - let requirements = Self::get_trait_requirements_with_origin(env, did); - - // pre-register all the requirements, in order to resolve dependencies - for (trait_ref, _, _) in &requirements { - let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap()); - let dummy_trait_use = trait_registry.make_empty_trait_use(*trait_ref); - self.trait_scope.used_traits.insert(key.clone(), dummy_trait_use); - } - - for (trait_ref, origin, is_used_in_self_trait) in &requirements { - // lookup the trait in the trait registry - if let Some(trait_spec) = trait_registry.lookup_trait(trait_ref.def_id) { - let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap()); - let entry = &self.trait_scope.used_traits[&key]; - - let mut deps = HashSet::new(); - // the scope to translate the arguments in - let mut state = TranslationStateInner::TranslateAdt(AdtTranslationState::new( - &mut deps, &*self, ¶m_env, - )); - - trait_registry.fill_trait_use( - entry, - &mut state, - trait_ref.to_owned(), - trait_spec, - *is_used_in_self_trait, - // trait associated types are fully generic for now, we make a second pass - // below - HashMap::new(), - *origin, - )?; - - self.trait_scope.ordered_assumptions.push(key); - } else { - return Err(trait_registry::Error::UnregisteredTrait(trait_ref.def_id).into()); - } - } - - // make a second pass to specify constraints on associated types - // We do this in a second pass so that we can refer to the other associated types - for (trait_ref, origin, _) in requirements { - let assoc_constraints = trait_registry::get_trait_assoc_constraints(env, param_env, trait_ref); - - let translated_constraints: HashMap<_, _> = assoc_constraints - .into_iter() - .map(|(name, ty)| { - let translated_ty = type_translator.translate_type_in_scope(self, ty).unwrap(); - (name, translated_ty) - }) - .collect(); - - // lookup the trait use - let key = (trait_ref.def_id, generate_args_inst_key(env.tcx(), trait_ref.args).unwrap()); - let entry = &self.trait_scope.used_traits[&key]; - - { - let mut trait_use_ref = entry.trait_use.borrow_mut(); - let trait_use = trait_use_ref.as_mut().unwrap(); - // and add the constraints - for (name, constr) in translated_constraints { - trait_use.specialize_assoc_type(name, constr); - } - } - - // finalize the entry by adding dependencies on other trait parameters - let mut deps = HashSet::new(); - let mut state = - TranslationStateInner::TranslateAdt(AdtTranslationState::new(&mut deps, &*self, ¶m_env)); - trait_registry.finalize_trait_use(entry, &mut state, trait_ref)?; - } - - trace!("Leave add_param_env for did = {did:?}"); - Ok(()) - } -} - -impl<'tcx, 'def> From<ty::GenericArgsRef<'tcx>> for ParamScope<'tcx, 'def> { - fn from(x: ty::GenericArgsRef<'tcx>) -> Self { - Self::new_from_generics(x, None) - } -} - -impl<'a, 'tcx, 'def> From<&'a [ty::GenericParamDef]> for ParamScope<'tcx, 'def> { - fn from(x: &[ty::GenericParamDef]) -> Self { - let mut scope = Vec::new(); - - let mut ty_names = HashMap::new(); - let mut lft_names = HashMap::new(); - - for p in x { - let name = strip_coq_ident(p.name.as_str()); - match p.kind { - ty::GenericParamDefKind::Const { .. } => { - scope.push(Param::Const); - }, - ty::GenericParamDefKind::Type { .. } => { - let lit = radium::LiteralTyParam::new(&name, &name); - ty_names.insert(p.name.as_str().to_owned(), scope.len()); - scope.push(Param::Ty(lit)); - }, - ty::GenericParamDefKind::Lifetime => { - let name = format!("ulft_{name}"); - lft_names.insert(p.name.as_str().to_owned(), scope.len()); - scope.push(Param::Region(name)); - }, - } - } - Self { - scope, - lft_names, - ty_names, - trait_scope: TraitScope::default(), - } - } -} - -/// A scope for translated trait requirements from `where` clauses. -#[derive(Clone, Debug, Default)] -pub struct TraitScope<'tcx, 'def> { - used_traits: HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>>, - ordered_assumptions: Vec<(DefId, GenericsKey<'tcx>)>, -} - -impl<'tcx, 'def> TraitScope<'tcx, 'def> { - /// Lookup the trait use for a specific trait with given parameters. - /// (here, args includes the self parameter as the first element) - pub fn lookup_trait_use( - &self, - tcx: ty::TyCtxt<'tcx>, - trait_did: DefId, - args: &[ty::GenericArg<'tcx>], - ) -> TraitResult<'tcx, &GenericTraitUse<'def>> { - if !tcx.is_trait(trait_did) { - return Err(trait_registry::Error::NotATrait(trait_did)); - } - - let key = (trait_did, generate_args_inst_key(tcx, args).unwrap()); - trace!("looking up trait use {key:?} in {:?}", self.used_traits); - if let Some(trait_ref) = self.used_traits.get(&key) { - Ok(trait_ref) - } else { - Err(trait_registry::Error::UnknownLocalInstance(trait_did, tcx.mk_args(args))) - } - } - - /// Get trait uses in the current scope. - pub const fn get_trait_uses(&self) -> &HashMap<(DefId, GenericsKey<'tcx>), GenericTraitUse<'def>> { - &self.used_traits - } - - /// Within a trait declaration, get the Self trait use. - pub fn get_self_trait_use(&self) -> Option<&GenericTraitUse<'def>> { - for trait_use in self.used_traits.values() { - // check if this is the Self trait use - { - let spec_use_ref = trait_use.trait_use.borrow(); - let spec_use = spec_use_ref.as_ref().unwrap(); - if !spec_use.is_used_in_self_trait { - continue; - } - } - return Some(trait_use); - } - None - } -} +use crate::traits::{self, registry}; +use crate::types::scope; +use crate::types::tyvars::*; +use crate::{attrs, base, search}; /// A scope tracking the type translation state when translating the body of a function. /// This also includes the state needed for tracking trait constraints, as type translation for /// associated types in the current scope depends on that. #[derive(Debug)] -pub struct TypeTranslationScope<'tcx, 'def> { +pub struct FunctionState<'tcx, 'def> { /// defid of the current function pub(crate) did: DefId, /// generic mapping: map DeBruijn indices to type parameters - pub(crate) generic_scope: ParamScope<'tcx, 'def>, + pub(crate) generic_scope: scope::Params<'tcx, 'def>, /// mapping for regions pub(crate) lifetime_scope: EarlyLateRegionMap, @@ -634,14 +48,14 @@ pub struct TypeTranslationScope<'tcx, 'def> { pub(crate) tuple_uses: HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>, /// Shim uses for the current function - pub(crate) shim_uses: HashMap<AdtUseKey, radium::LiteralTypeUse<'def>>, + pub(crate) shim_uses: HashMap<scope::AdtUseKey, radium::LiteralTypeUse<'def>>, /// optional Polonius Info for the current function #[debug(ignore)] polonius_info: Option<&'def PoloniusInfo<'def, 'tcx>>, } -impl<'tcx, 'def> ParamLookup for TypeTranslationScope<'tcx, 'def> { +impl<'tcx, 'def> ParamLookup for FunctionState<'tcx, 'def> { fn lookup_ty_param(&self, path: &[&str]) -> Option<&radium::LiteralTyParam> { if path.len() == 1 { self.generic_scope.lookup_ty_param(path) } else { None } } @@ -656,14 +70,14 @@ impl<'tcx, 'def> ParamLookup for TypeTranslationScope<'tcx, 'def> { } } -impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> { +impl<'tcx, 'def> FunctionState<'tcx, 'def> { /// Create a new empty scope for a function. pub fn empty(did: DefId) -> Self { Self { did, tuple_uses: HashMap::new(), shim_uses: HashMap::new(), - generic_scope: ParamScope::default(), + generic_scope: scope::Params::default(), polonius_info: None, lifetime_scope: EarlyLateRegionMap::default(), } @@ -679,7 +93,7 @@ impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> { ) -> Self { info!("Entering procedure with ty_params {:?} and lifetimes {:?}", ty_params, lifetimes); - let generics = ParamScope::new_from_generics(ty_params, Some((tcx, did))); + let generics = scope::Params::new_from_generics(ty_params, Some((tcx, did))); Self { did, @@ -699,12 +113,12 @@ impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> { ty_params: ty::GenericArgsRef<'tcx>, lifetimes: EarlyLateRegionMap, param_env: ty::ParamEnv<'tcx>, - type_translator: &TypeTranslator<'def, 'tcx>, - trait_registry: &TraitRegistry<'tcx, 'def>, + type_translator: &TX<'def, 'tcx>, + trait_registry: ®istry::TR<'tcx, 'def>, info: Option<&'def PoloniusInfo<'def, 'tcx>>, ) -> Result<Self, TranslationError<'tcx>> { info!("Entering procedure with ty_params {:?} and lifetimes {:?}", ty_params, lifetimes); - let mut generics = ParamScope::new_from_generics(ty_params, Some((env.tcx(), did))); + let mut generics = scope::Params::new_from_generics(ty_params, Some((env.tcx(), did))); generics.add_param_env(did, env, type_translator, trait_registry)?; @@ -721,18 +135,18 @@ impl<'tcx, 'def> TypeTranslationScope<'tcx, 'def> { /// Type translation state when translating an ADT. #[derive(Debug)] -pub struct AdtTranslationState<'a, 'tcx, 'def> { +pub struct AdtState<'a, 'tcx, 'def> { /// track dependencies on other ADTs deps: &'a mut HashSet<DefId>, /// scope to track parameters - scope: &'a ParamScope<'tcx, 'def>, + scope: &'a scope::Params<'tcx, 'def>, /// the current paramenv param_env: &'a ty::ParamEnv<'tcx>, } -impl<'a, 'tcx, 'def> AdtTranslationState<'a, 'tcx, 'def> { +impl<'a, 'tcx, 'def> AdtState<'a, 'tcx, 'def> { pub fn new( deps: &'a mut HashSet<DefId>, - scope: &'a ParamScope<'tcx, 'def>, + scope: &'a scope::Params<'tcx, 'def>, param_env: &'a ty::ParamEnv<'tcx>, ) -> Self { Self { @@ -746,32 +160,33 @@ impl<'a, 'tcx, 'def> AdtTranslationState<'a, 'tcx, 'def> { /// Translation state for translating the interface of a called function. /// Lifetimes are completely erased since we only need these types for syntactic types. #[derive(Constructor, Debug)] -pub struct CalleeTranslationState<'a, 'tcx, 'def> { +pub struct CalleeState<'a, 'tcx, 'def> { /// the env of the caller param_env: &'a ty::ParamEnv<'tcx>, /// the generic scope of the caller - param_scope: &'a ParamScope<'tcx, 'def>, + param_scope: &'a scope::Params<'tcx, 'def>, } -/// The `TypeTranslator` has two modes: +/// The type translator `TX` has three modes: /// - translation of a type within the generic scope of a function -/// - translation of a type as part of an ADT definition, where we always translate parameters as -/// variables, but need to track dependencies on other ADTs. +/// - translation of a type as part of an ADT definition, where we always translate parameters as variables, +/// but need to track dependencies on other ADTs. +/// - translation of a type when translating the signature of a callee. Regions are always erased. #[derive(Debug)] -pub enum TranslationStateInner<'b, 'def: 'b, 'tcx: 'def> { +pub enum STInner<'b, 'def: 'b, 'tcx: 'def> { /// This type is used in a function - InFunction(&'b mut TypeTranslationScope<'tcx, 'def>), + InFunction(&'b mut FunctionState<'tcx, 'def>), /// We are generating the definition of an ADT and this type is used in there - TranslateAdt(AdtTranslationState<'b, 'tcx, 'def>), + TranslateAdt(AdtState<'b, 'tcx, 'def>), /// We are translating in an empty dummy scope. /// All regions are erased. - CalleeTranslation(CalleeTranslationState<'b, 'tcx, 'def>), + CalleeTranslation(CalleeState<'b, 'tcx, 'def>), } -pub type TranslationState<'a, 'b, 'def, 'tcx> = &'a mut TranslationStateInner<'b, 'def, 'tcx>; -pub type InFunctionState<'a, 'def, 'tcx> = &'a mut TypeTranslationScope<'tcx, 'def>; -pub type TranslateAdtState<'a, 'tcx, 'def> = AdtTranslationState<'a, 'tcx, 'def>; +pub type ST<'a, 'b, 'def, 'tcx> = &'a mut STInner<'b, 'def, 'tcx>; +pub type InFunctionState<'a, 'def, 'tcx> = &'a mut FunctionState<'tcx, 'def>; +pub type TranslateAdtState<'a, 'tcx, 'def> = AdtState<'a, 'tcx, 'def>; -impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> { +impl<'a, 'def, 'tcx> STInner<'a, 'def, 'tcx> { const fn in_function(&self) -> bool { matches!(*self, Self::InFunction(_)) } @@ -807,19 +222,19 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> { tcx: ty::TyCtxt<'tcx>, trait_did: DefId, args: &[ty::GenericArg<'tcx>], - ) -> Result<&GenericTraitUse<'def>, TranslationError<'tcx>> { + ) -> Result<®istry::GenericTraitUse<'def>, TranslationError<'tcx>> { trace!("Enter lookup_trait_use for trait_did = {trait_did:?}, args = {args:?}, self = {self:?}"); let res = match &self { Self::InFunction(state) => { - let res = state.generic_scope.trait_scope.lookup_trait_use(tcx, trait_did, args)?; + let res = state.generic_scope.trait_scope().lookup_trait_use(tcx, trait_did, args)?; res }, Self::TranslateAdt(state) => { - let res = state.scope.trait_scope.lookup_trait_use(tcx, trait_did, args)?; + let res = state.scope.trait_scope().lookup_trait_use(tcx, trait_did, args)?; res }, Self::CalleeTranslation(state) => { - let res = state.param_scope.trait_scope.lookup_trait_use(tcx, trait_did, args)?; + let res = state.param_scope.trait_scope().lookup_trait_use(tcx, trait_did, args)?; res }, }; @@ -830,14 +245,14 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> { /// Lookup a type parameter in the current state fn lookup_ty_param(&self, param_ty: ty::ParamTy) -> Result<radium::Type<'def>, TranslationError<'tcx>> { match self { - TranslationStateInner::InFunction(scope) => { + STInner::InFunction(scope) => { let ty = scope.generic_scope.lookup_ty_param_idx(param_ty.index as usize).ok_or_else(|| { TranslationError::UnknownVar(format!("unknown generic parameter {:?}", param_ty)) })?; Ok(radium::Type::LiteralParam(ty.clone())) }, - TranslationStateInner::TranslateAdt(scope) => { + STInner::TranslateAdt(scope) => { let ty = scope.scope.lookup_ty_param_idx(param_ty.index as usize).ok_or_else(|| { TranslationError::UnknownVar(format!("unknown generic parameter {:?}", param_ty)) })?; @@ -858,7 +273,7 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> { region: &ty::EarlyBoundRegion, ) -> Result<radium::Lft, TranslationError<'tcx>> { match self { - TranslationStateInner::InFunction(scope) => { + STInner::InFunction(scope) => { info!("Looking up lifetime {region:?} in scope {:?}", scope.lifetime_scope); let lft = scope .lifetime_scope @@ -866,22 +281,22 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> { .ok_or(TranslationError::UnknownEarlyRegion(*region))?; Ok(lft.to_owned()) }, - TranslationStateInner::TranslateAdt(scope) => { + STInner::TranslateAdt(scope) => { // TODO: ? if region.has_name() { let name = region.name.as_str(); - return Ok(format!("ulft_{}", strip_coq_ident(name))); + return Ok(format!("ulft_{}", base::strip_coq_ident(name))); } return Err(TranslationError::UnknownEarlyRegion(*region)); }, - TranslationStateInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()), + STInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()), } } /// Lookup a late-bound region. fn lookup_late_region(&self, region: usize) -> Result<radium::Lft, TranslationError<'tcx>> { match self { - TranslationStateInner::InFunction(scope) => { + STInner::InFunction(scope) => { info!("Looking up lifetime {region:?} in scope {:?}", scope.lifetime_scope); let lft = scope .lifetime_scope @@ -889,22 +304,22 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> { .ok_or(TranslationError::UnknownLateRegion(region))?; Ok(lft.to_owned()) }, - TranslationStateInner::TranslateAdt(scope) => { + STInner::TranslateAdt(scope) => { info!("Translating region: ReLateBound {region:?} as None (outside of function)"); return Err(TranslationError::UnknownLateRegionOutsideFunction(region)); }, - TranslationStateInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()), + STInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()), } } /// Try to translate a Polonius region variable to a Caesium lifetime. - fn lookup_polonius_var(&self, v: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> { + pub fn lookup_polonius_var(&self, v: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> { match self { - TranslationStateInner::InFunction(scope) => { + STInner::InFunction(scope) => { if let Some(info) = scope.polonius_info { // If there is Polonius Info available, use that for translation let x = info.mk_atomic_region(v); - let r = format_atomic_region_direct(&x, Some(&**scope)); + let r = format_atomic_region_direct(&x, Some(&scope.lifetime_scope)); info!("Translating region: ReVar {:?} as {:?}", v, r); Ok(r) } else { @@ -917,16 +332,16 @@ impl<'a, 'def, 'tcx> TranslationStateInner<'a, 'def, 'tcx> { Ok(r.to_owned()) } }, - TranslationStateInner::TranslateAdt(scope) => { + STInner::TranslateAdt(scope) => { info!("Translating region: ReVar {:?} as None (outside of function)", v); return Err(TranslationError::PoloniusRegionOutsideFunction(v)); }, - TranslationStateInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()), + STInner::CalleeTranslation(_) => Ok("DUMMY".to_owned()), } } } -pub struct TypeTranslator<'def, 'tcx> { +pub struct TX<'def, 'tcx> { env: &'def Environment<'tcx>, /// arena for keeping ownership of structs @@ -976,14 +391,14 @@ pub struct TypeTranslator<'def, 'tcx> { adt_shims: RefCell<HashMap<DefId, radium::LiteralTypeRef<'def>>>, } -impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { +impl<'def, 'tcx: 'def> TX<'def, 'tcx> { pub fn new( env: &'def Environment<'tcx>, struct_arena: &'def Arena<OnceCell<radium::AbstractStruct<'def>>>, enum_arena: &'def Arena<OnceCell<radium::AbstractEnum<'def>>>, shim_arena: &'def Arena<radium::LiteralType>, ) -> Self { - TypeTranslator { + TX { env, adt_deps: RefCell::new(HashMap::new()), adt_shims: RefCell::new(HashMap::new()), @@ -1001,6 +416,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { self.shim_arena.alloc(lit) } + pub const fn env(&self) -> &'def Environment<'tcx> { + self.env + } + /// Register a shim for an ADT. pub fn register_adt_shim(&self, did: DefId, lit: &radium::LiteralType) -> Result<(), String> { let lit_ref = self.intern_literal(lit.clone()); @@ -1118,19 +537,19 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { } pub fn translate_region_in_scope( - scope: &ParamScope<'tcx, 'def>, + scope: &scope::Params<'tcx, 'def>, region: ty::Region<'tcx>, ) -> Result<radium::Lft, TranslationError<'tcx>> { let mut deps = HashSet::new(); let env = ty::ParamEnv::empty(); - let mut state = AdtTranslationState::new(&mut deps, scope, &env); - let mut state = TranslationStateInner::TranslateAdt(state); + let mut state = AdtState::new(&mut deps, scope, &env); + let mut state = STInner::TranslateAdt(state); Self::translate_region(&mut state, region) } /// Try to translate a region to a Caesium lifetime. pub fn translate_region<'a, 'b>( - translation_state: TranslationState<'a, 'b, 'def, 'tcx>, + translation_state: ST<'a, 'b, 'def, 'tcx>, region: ty::Region<'tcx>, ) -> Result<radium::Lft, TranslationError<'tcx>> { match *region { @@ -1229,7 +648,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { &self, ty: Ty<'tcx>, variant: Option<target::abi::VariantIdx>, - adt_deps: TranslationState<'a, 'b, 'def, 'tcx>, + adt_deps: ST<'a, 'b, 'def, 'tcx>, ) -> Result<radium::Type<'def>, TranslationError<'tcx>> { match ty.kind() { TyKind::Adt(adt, args) => { @@ -1285,7 +704,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { &self, adt_def: ty::AdtDef<'tcx>, args: F, - state: TranslationState<'a, 'b, 'def, 'tcx>, + state: ST<'a, 'b, 'def, 'tcx>, ) -> Result<radium::AbstractEnumUse<'def>, TranslationError<'tcx>> where F: IntoIterator<Item = ty::GenericArg<'tcx>>, @@ -1295,10 +714,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let (enum_ref, lit_ref) = self.lookup_enum(adt_def.did())?; let params = self.translate_generic_args(args, &mut *state)?; - let key = AdtUseKey::new(adt_def.did(), ¶ms); + let key = scope::AdtUseKey::new(adt_def.did(), ¶ms); // track this enum use for the current function - if let TranslationStateInner::InFunction(state) = state { + if let STInner::InFunction(state) = state { let lit_uses = &mut state.shim_uses; lit_uses @@ -1327,7 +746,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { fn translate_generic_args<'a, 'b, F>( &self, args: F, - state: TranslationState<'a, 'b, 'def, 'tcx>, + state: ST<'a, 'b, 'def, 'tcx>, ) -> Result<Vec<radium::Type<'def>>, TranslationError<'tcx>> where F: IntoIterator<Item = ty::GenericArg<'tcx>>, @@ -1355,7 +774,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { &self, variant_id: DefId, args: F, - state: TranslationState<'a, 'b, 'def, 'tcx>, + state: ST<'a, 'b, 'def, 'tcx>, ) -> Result<radium::AbstractStructUse<'def>, TranslationError<'tcx>> where F: IntoIterator<Item = ty::GenericArg<'tcx>>, @@ -1369,9 +788,9 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let (struct_ref, lit_ref) = self.lookup_adt_variant(variant_id)?; let params = self.translate_generic_args(args, &mut *state)?; - let key = AdtUseKey::new(variant_id, ¶ms); + let key = scope::AdtUseKey::new(variant_id, ¶ms); - if let TranslationStateInner::InFunction(scope) = state { + if let STInner::InFunction(scope) = state { let lit_uses = &mut scope.shim_uses; lit_uses @@ -1390,7 +809,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { adt_id: DefId, variant_idx: target::abi::VariantIdx, args: F, - state: TranslationState<'a, 'b, 'def, 'tcx>, + state: ST<'a, 'b, 'def, 'tcx>, ) -> Result<radium::AbstractStructUse<'def>, TranslationError<'tcx>> where F: IntoIterator<Item = ty::GenericArg<'tcx>>, @@ -1421,7 +840,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { pub fn generate_tuple_use<'a, 'b, F>( &self, tys: F, - state: TranslationState<'a, 'b, 'def, 'tcx>, + state: ST<'a, 'b, 'def, 'tcx>, ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>> where F: IntoIterator<Item = Ty<'tcx>>, @@ -1436,7 +855,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let key: Vec<_> = params.iter().map(radium::SynType::from).collect(); let struct_use = radium::LiteralTypeUse::new(lit, params); - if let TranslationStateInner::InFunction(ref mut scope) = *state { + if let STInner::InFunction(ref mut scope) = *state { let tuple_uses = &mut scope.tuple_uses; tuple_uses.entry(key).or_insert_with(|| struct_use.clone()); @@ -1514,7 +933,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let generics: &'tcx ty::Generics = self.env.tcx().generics_of(adt.did()); let mut deps = HashSet::new(); - let scope = ParamScope::from(generics.params.as_slice()); + let scope = scope::Params::from(generics.params.as_slice()); let param_env = self.env.tcx().param_env(adt.did()); // first thing: figure out the generics we are using here. @@ -1535,20 +954,16 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let struct_def_init = self.struct_arena.alloc(OnceCell::new()); let tcx = self.env.tcx(); - let struct_name = strip_coq_ident(&ty.ident(tcx).to_string()); + let struct_name = base::strip_coq_ident(&ty.ident(tcx).to_string()); self.variant_registry .borrow_mut() .insert(ty.def_id, (struct_name, struct_def_init, ty, false, None)); let translate_adt = || { - let struct_name = strip_coq_ident(&ty.ident(tcx).to_string()); - let (variant_def, invariant_def) = self.make_adt_variant( - &struct_name, - ty, - adt, - AdtTranslationState::new(&mut deps, &scope, ¶m_env), - )?; + let struct_name = base::strip_coq_ident(&ty.ident(tcx).to_string()); + let (variant_def, invariant_def) = + self.make_adt_variant(&struct_name, ty, adt, AdtState::new(&mut deps, &scope, ¶m_env))?; let mut struct_def = radium::AbstractStruct::new(variant_def, ty_param_defs); if let Some(invariant_def) = invariant_def { @@ -1606,10 +1021,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let expect_refinement; let mut invariant_spec; - if utils::has_tool_attr(outer_attrs, "refined_by") { - let outer_attrs = utils::filter_tool_attrs(outer_attrs); + if attrs::has_tool_attr(outer_attrs, "refined_by") { + let outer_attrs = attrs::filter_for_tool(outer_attrs); let mut spec_parser = struct_spec_parser::VerboseInvariantSpecParser::new(adt_deps.scope); - let ty_name = strip_coq_ident(format!("{}_inv_t", struct_name).as_str()); + let ty_name = base::strip_coq_ident(format!("{}_inv_t", struct_name).as_str()); let res = spec_parser .parse_invariant_spec(&ty_name, &outer_attrs) .map_err(TranslationError::FatalError)?; @@ -1628,16 +1043,12 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let f_name = f.ident(tcx).to_string(); let attrs = self.env.get_attributes(f.did); - let attrs = utils::filter_tool_attrs(attrs); + let attrs = attrs::filter_for_tool(attrs); let f_ty = self.env.tcx().type_of(f.did).instantiate_identity(); let mut ty = self.translate_type_in_state( f_ty, - &mut TranslationStateInner::TranslateAdt(AdtTranslationState::new( - adt_deps.deps, - adt_deps.scope, - adt_deps.param_env, - )), + &mut STInner::TranslateAdt(AdtState::new(adt_deps.deps, adt_deps.scope, adt_deps.param_env)), )?; let mut parser = struct_spec_parser::VerboseStructFieldSpecParser::new( @@ -1765,7 +1176,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { } fn does_did_match(&self, did: DefId, path: &[&str]) -> bool { - let lookup_did = utils::try_resolve_did(self.env.tcx(), path); + let lookup_did = search::try_resolve_did(self.env.tcx(), path); if let Some(lookup_did) = lookup_did { if lookup_did == did { return true; @@ -1867,7 +1278,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let tcx = self.env.tcx(); let generics: &'tcx ty::Generics = self.env.tcx().generics_of(def.did()); - let scope = ParamScope::from(generics.params.as_slice()); + let scope = scope::Params::from(generics.params.as_slice()); let mut deps = HashSet::new(); let param_env = tcx.param_env(def.did()); @@ -1875,7 +1286,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let enum_def_init = self.enum_arena.alloc(OnceCell::new()); // TODO: currently a hack, I don't know how to query the name properly - let enum_name = strip_coq_ident(format!("{:?}", def).as_str()); + let enum_name = base::strip_coq_ident(format!("{:?}", def).as_str()); // first thing: figure out the generics we are using here. // we need to search all of the variant types for that. @@ -1904,7 +1315,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { // now generate the variant let struct_def_init = self.struct_arena.alloc(OnceCell::new()); - let struct_name = strip_coq_ident(format!("{}_{}", enum_name, v.ident(tcx)).as_str()); + let struct_name = base::strip_coq_ident(format!("{}_{}", enum_name, v.ident(tcx)).as_str()); self.variant_registry .borrow_mut() .insert(v.def_id, (struct_name.clone(), &*struct_def_init, v, true, None)); @@ -1913,7 +1324,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { struct_name.as_str(), v, def, - AdtTranslationState::new(&mut deps, &scope, ¶m_env), + AdtState::new(&mut deps, &scope, ¶m_env), )?; let mut struct_def = radium::AbstractStruct::new(variant_def, ty_param_defs.clone()); @@ -1923,7 +1334,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { // also remember the attributes for additional processing let outer_attrs = self.env.get_attributes(v.def_id); - let outer_attrs = utils::filter_tool_attrs(outer_attrs); + let outer_attrs = attrs::filter_for_tool(outer_attrs); variant_attrs.push(outer_attrs); // finalize the definition @@ -1940,7 +1351,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { // get the type of the discriminant let it = def.repr().discr_type(); let it_is_signed = it.is_signed(); - let translated_it = TypeTranslator::translate_integer_type(it); + let translated_it = TX::translate_integer_type(it); // build the discriminant map let discrs = self.build_discriminant_map(def, it_is_signed)?; @@ -1957,7 +1368,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { enum_spec = spec; } else if self.env.has_tool_attribute(def.did(), "refined_by") { let attributes = self.env.get_attributes(def.did()); - let attributes = utils::filter_tool_attrs(attributes); + let attributes = attrs::filter_for_tool(attributes); let mut parser = VerboseEnumSpecParser::new(&scope); @@ -1987,7 +1398,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { // now build the enum itself for v in def.variants() { let (variant_ref, _) = self.lookup_adt_variant(v.def_id)?; - let variant_name = strip_coq_ident(&v.ident(tcx).to_string()); + let variant_name = base::strip_coq_ident(&v.ident(tcx).to_string()); let discriminant = discrs[&variant_name]; enum_builder.add_variant(&variant_name, variant_ref, discriminant); } @@ -2022,7 +1433,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { &self, adt: ty::AdtDef<'tcx>, substs: ty::GenericArgsRef<'tcx>, - state: TranslationState<'a, 'b, 'def, 'tcx>, + state: ST<'a, 'b, 'def, 'tcx>, ) -> Result<radium::Type<'def>, TranslationError<'tcx>> { let Some(shim) = self.lookup_adt_shim(adt.did()) else { return Err(TranslationError::UnknownError(format!( @@ -2033,10 +1444,10 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let params = self.translate_generic_args(substs.iter(), &mut *state)?; - let key = AdtUseKey::new(adt.did(), ¶ms); + let key = scope::AdtUseKey::new(adt.did(), ¶ms); let shim_use = radium::LiteralTypeUse::new(shim, params); - if let TranslationStateInner::InFunction(scope) = state { + if let STInner::InFunction(scope) = state { // track this shim use for the current function scope.shim_uses.entry(key).or_insert_with(|| shim_use.clone()); } @@ -2049,7 +1460,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { pub fn translate_type_in_state<'a, 'b>( &self, ty: Ty<'tcx>, - state: TranslationState<'a, 'b, 'def, 'tcx>, + state: ST<'a, 'b, 'def, 'tcx>, ) -> Result<radium::Type<'def>, TranslationError<'tcx>> { match ty.kind() { TyKind::Bool => Ok(radium::Type::Bool), @@ -2088,7 +1499,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let translated_ty = self.translate_type_in_state(*ty, &mut *state)?; - let lft = TypeTranslator::translate_region(&mut *state, *region)?; + let lft = TX::translate_region(&mut *state, *region)?; match mutability { ast::ast::Mutability::Mut => Ok(radium::Type::MutRef(Box::new(translated_ty), lft)), @@ -2117,7 +1528,7 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { return Ok(radium::Type::Unit); } - if let TranslationStateInner::TranslateAdt(state) = state { + if let STInner::TranslateAdt(state) = state { state.deps.insert(adt.did()); } @@ -2196,8 +1607,8 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { let type_name = self .env .get_assoc_item_name(alias_ty.def_id) - .ok_or(trait_registry::Error::NotAnAssocType(alias_ty.def_id))?; - let assoc_type = trait_use.make_assoc_type_use(&strip_coq_ident(&type_name)); + .ok_or(traits::Error::NotAnAssocType(alias_ty.def_id))?; + let assoc_type = trait_use.make_assoc_type_use(&base::strip_coq_ident(&type_name)); info!("Resolved projection to {assoc_type:?}"); @@ -2360,13 +1771,13 @@ impl<'def, 'tcx: 'def> TypeTranslator<'def, 'tcx> { } /// Public functions used by the `BodyTranslator`. -impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { +impl<'def, 'tcx> TX<'def, 'tcx> { /// Translate a MIR type to the Caesium syntactic type we need when storing an element of the type, /// substituting all generics. pub fn translate_type_to_syn_type( &self, ty: Ty<'tcx>, - scope: TranslationState<'_, '_, 'def, 'tcx>, + scope: ST<'_, '_, 'def, 'tcx>, ) -> Result<radium::SynType, TranslationError<'tcx>> { self.translate_type_in_state(ty, scope).map(radium::SynType::from) } @@ -2377,19 +1788,19 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { ty: Ty<'tcx>, scope: InFunctionState<'_, 'def, 'tcx>, ) -> Result<radium::Type<'def>, TranslationError<'tcx>> { - self.translate_type_in_state(ty, &mut TranslationStateInner::InFunction(&mut *scope)) + self.translate_type_in_state(ty, &mut STInner::InFunction(&mut *scope)) } /// Translate type in an empty scope. pub fn translate_type_in_scope( &self, - scope: &ParamScope<'tcx, 'def>, + scope: &scope::Params<'tcx, 'def>, ty: Ty<'tcx>, ) -> Result<radium::Type<'def>, TranslationError<'tcx>> { let mut deps = HashSet::new(); let param_env = ty::ParamEnv::empty(); - let mut state = AdtTranslationState::new(&mut deps, scope, ¶m_env); - self.translate_type_in_state(ty, &mut TranslationStateInner::TranslateAdt(state)) + let mut state = AdtState::new(&mut deps, scope, ¶m_env); + self.translate_type_in_state(ty, &mut STInner::TranslateAdt(state)) } /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently @@ -2423,14 +1834,12 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { }) } }, - TyKind::Tuple(args) => { - self.generate_tuple_use(*args, &mut TranslationStateInner::InFunction(scope)).map(Some) - }, + TyKind::Tuple(args) => self.generate_tuple_use(*args, &mut STInner::InFunction(scope)).map(Some), TyKind::Closure(_, args) => { // use the upvar tuple let closure_args = args.as_closure(); let upvars = closure_args.upvar_tys(); - self.generate_tuple_use(upvars, &mut TranslationStateInner::InFunction(scope)).map(Some) + self.generate_tuple_use(upvars, &mut STInner::InFunction(scope)).map(Some) }, _ => Err(TranslationError::UnknownError("not a structlike".to_owned())), } @@ -2451,9 +1860,8 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { self.register_adt(adt_def)?; let enum_ref: radium::LiteralTypeRef<'def> = self.lookup_enum_literal(adt_def.did())?; - let params = - self.translate_generic_args(args, &mut TranslationStateInner::InFunction(&mut *state))?; - let key = AdtUseKey::new(adt_def.did(), ¶ms); + let params = self.translate_generic_args(args, &mut STInner::InFunction(&mut *state))?; + let key = scope::AdtUseKey::new(adt_def.did(), ¶ms); let enum_use = radium::LiteralTypeUse::new(enum_ref, params); // track this enum use for the current function @@ -2483,9 +1891,8 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { return Ok(None); } - let params = - self.translate_generic_args(args, &mut TranslationStateInner::InFunction(&mut *scope))?; - let key = AdtUseKey::new(variant_id, ¶ms); + let params = self.translate_generic_args(args, &mut STInner::InFunction(&mut *scope))?; + let key = scope::AdtUseKey::new(variant_id, ¶ms); let struct_ref: radium::LiteralTypeRef<'def> = self.lookup_adt_variant_literal(variant_id)?; let struct_use = radium::LiteralTypeUse::new(struct_ref, params); @@ -2508,9 +1915,9 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { { info!("generating enum variant use for {:?}", variant_id); - let x: TranslationState<'_, '_, 'def, 'tcx> = &mut TranslationStateInner::InFunction(&mut *scope); + let x: ST<'_, '_, 'def, 'tcx> = &mut STInner::InFunction(&mut *scope); let params = self.translate_generic_args(args, x)?; - let _key = AdtUseKey::new(variant_id, ¶ms); + let _key = scope::AdtUseKey::new(variant_id, ¶ms); let struct_ref: radium::LiteralTypeRef<'def> = self.lookup_adt_variant_literal(variant_id)?; let struct_use = radium::LiteralTypeUse::new(struct_ref, params); @@ -2528,7 +1935,7 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { /// Make a tuple use. /// Hack: This does not take the translation state but rather a direct reference to the tuple /// use map so that we can this function when parsing closure specifications. - fn make_tuple_use( + pub fn make_tuple_use( &self, translated_tys: Vec<radium::Type<'def>>, uses: Option<&mut HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>>, @@ -2548,300 +1955,3 @@ impl<'def, 'tcx> TypeTranslator<'def, 'tcx> { radium::Type::Literal(struct_use) } } - -/// Type translator bundling the function scope -#[allow(clippy::module_name_repetitions)] -pub struct LocalTypeTranslator<'def, 'tcx> { - pub translator: &'def TypeTranslator<'def, 'tcx>, - pub scope: RefCell<TypeTranslationScope<'tcx, 'def>>, -} - -impl<'def, 'tcx> LocalTypeTranslator<'def, 'tcx> { - pub const fn new( - translator: &'def TypeTranslator<'def, 'tcx>, - scope: TypeTranslationScope<'tcx, 'def>, - ) -> Self { - Self { - translator, - scope: RefCell::new(scope), - } - } - - /// Get the `DefId` of the current function. - pub fn get_proc_did(&self) -> DefId { - let scope = self.scope.borrow(); - scope.did - } - - /// Translate a MIR type to the Radium syntactic type we need when storing an element of the type, - /// substituting all generics. - pub fn translate_type_to_syn_type( - &self, - ty: Ty<'tcx>, - ) -> Result<radium::SynType, TranslationError<'tcx>> { - let mut scope = self.scope.borrow_mut(); - let mut state = TranslationStateInner::InFunction(&mut scope); - self.translator.translate_type_to_syn_type(ty, &mut state) - } - - /// Translate a region in the scope of the current function. - pub fn translate_region(&self, region: ty::Region<'tcx>) -> Result<radium::Lft, TranslationError<'tcx>> { - let mut scope = self.scope.borrow_mut(); - let mut scope = TranslationStateInner::InFunction(&mut scope); - TypeTranslator::translate_region(&mut scope, region) - } - - /// Translate a Polonius region variable in the scope of the current function. - pub fn translate_region_var(&self, region: facts::Region) -> Result<radium::Lft, TranslationError<'tcx>> { - let mut scope = self.scope.borrow_mut(); - let mut scope = TranslationStateInner::InFunction(&mut scope); - scope.lookup_polonius_var(region) - } - - /// Translate type. - pub fn translate_type(&self, ty: Ty<'tcx>) -> Result<radium::Type<'def>, TranslationError<'tcx>> { - let mut scope = self.scope.borrow_mut(); - self.translator.translate_type(ty, &mut scope) - } - - /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently - /// registering a new ADT. - pub fn generate_structlike_use( - &self, - ty: Ty<'tcx>, - variant: Option<target::abi::VariantIdx>, - ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>> { - let mut scope = self.scope.borrow_mut(); - self.translator.generate_structlike_use(ty, variant, &mut scope) - } - - /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently - /// registering a new ADT. - pub fn generate_enum_use<F>( - &self, - adt_def: ty::AdtDef<'tcx>, - args: F, - ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>> - where - F: IntoIterator<Item = ty::GenericArg<'tcx>>, - { - let mut scope = self.scope.borrow_mut(); - self.translator.generate_enum_use(adt_def, args, &mut scope) - } - - /// Generate a struct use. - /// Returns None if this should be unit. - /// Assumes that the current state of the ADT registry is consistent, i.e. we are not currently - /// registering a new ADT. - pub fn generate_struct_use<F>( - &self, - variant_id: DefId, - args: F, - ) -> Result<Option<radium::LiteralTypeUse<'def>>, TranslationError<'tcx>> - where - F: IntoIterator<Item = ty::GenericArg<'tcx>>, - { - let mut scope = self.scope.borrow_mut(); - self.translator.generate_struct_use(variant_id, args, &mut scope) - } - - /// Generate a struct use. - /// Returns None if this should be unit. - pub fn generate_enum_variant_use<F>( - &self, - variant_id: DefId, - args: F, - ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>> - where - F: IntoIterator<Item = ty::GenericArg<'tcx>>, - { - let mut scope = self.scope.borrow_mut(); - self.translator.generate_enum_variant_use(variant_id, args, &mut scope) - } - - /// Make a tuple use. - /// Hack: This does not take the translation state but rather a direct reference to the tuple - /// use map so that we can this function when parsing closure specifications. - pub fn make_tuple_use( - &self, - translated_tys: Vec<radium::Type<'def>>, - uses: Option<&mut HashMap<Vec<radium::SynType>, radium::LiteralTypeUse<'def>>>, - ) -> radium::Type<'def> { - self.translator.make_tuple_use(translated_tys, uses) - } - - pub fn generate_tuple_use<F>( - &self, - tys: F, - ) -> Result<radium::LiteralTypeUse<'def>, TranslationError<'tcx>> - where - F: IntoIterator<Item = Ty<'tcx>>, - { - let mut scope = self.scope.borrow_mut(); - self.translator - .generate_tuple_use(tys, &mut TranslationStateInner::InFunction(&mut scope)) - } - - /// Format the Coq representation of an atomic region. - pub fn format_atomic_region(&self, r: &polonius_info::AtomicRegion) -> String { - let scope = self.scope.borrow(); - format_atomic_region_direct(r, Some(&*scope)) - } - - /// Normalize a type in the given function environment. - pub fn normalize<T>(&self, ty: T) -> Result<T, TranslationError<'tcx>> - where - T: ty::TypeFoldable<ty::TyCtxt<'tcx>>, - { - let scope = self.scope.borrow(); - normalize_in_function(scope.did, self.translator.env.tcx(), ty) - } - - pub fn get_trait_of_method(env: &Environment<'tcx>, method_did: DefId) -> Option<DefId> { - if let Some(impl_did) = env.tcx().impl_of_method(method_did) { - env.tcx().trait_id_of_impl(impl_did) - } else { - // else expect it to be an abstract method of a trait decl - env.tcx().trait_of_item(method_did) - } - } - - /// Split the params of a trait method into params of the trait and params of the method - /// itself. - pub fn split_trait_method_args( - env: &Environment<'tcx>, - trait_did: DefId, - ty_params: ty::GenericArgsRef<'tcx>, - ) -> (ty::GenericArgsRef<'tcx>, ty::GenericArgsRef<'tcx>) { - // split args - let trait_generics: &'tcx ty::Generics = env.tcx().generics_of(trait_did); - let trait_generic_count = trait_generics.params.len(); - - let trait_args = &ty_params.as_slice()[..trait_generic_count]; - let method_args = &ty_params.as_slice()[trait_generic_count..]; - - (env.tcx().mk_args(trait_args), env.tcx().mk_args(method_args)) - } - - /// Lookup a trait parameter. - pub fn lookup_trait_param( - &self, - env: &Environment<'tcx>, - trait_did: DefId, - args: ty::GenericArgsRef<'tcx>, - ) -> TraitResult<'tcx, GenericTraitUse<'def>> { - let scope = self.scope.borrow(); - scope.generic_scope.trait_scope.lookup_trait_use(env.tcx(), trait_did, args).cloned() - } - - /// Register a procedure use of a trait method. - /// The given `ty_params` need to include the args to both the trait and the method. - /// Returns: - /// - the parameter name for the method loc - /// - the spec term for the method spec - /// - the arguments of the method - pub fn register_use_trait_procedure( - &self, - env: &Environment<'tcx>, - trait_method_did: DefId, - ty_params: ty::GenericArgsRef<'tcx>, - ) -> Result<(String, String, ty::GenericArgsRef<'tcx>), TranslationError<'tcx>> { - let trait_did = env - .tcx() - .trait_of_item(trait_method_did) - .ok_or(trait_registry::Error::NotATrait(trait_method_did))?; - - // split args - let (trait_args, method_args) = Self::split_trait_method_args(env, trait_did, ty_params); - - let trait_spec_use = { - let scope = self.scope.borrow(); - let entry = scope.generic_scope.trait_scope.lookup_trait_use( - env.tcx(), - trait_did, - trait_args.as_slice(), - )?; - let trait_use_ref = entry.trait_use.borrow(); - trait_use_ref.as_ref().unwrap().clone() - }; - - // get name of the trait - let trait_name = trait_spec_use.trait_ref.name.clone(); - - // get name of the method - let method_name = env.get_assoc_item_name(trait_method_did).unwrap(); - let mangled_method_name = - mangle_name_with_args(&strip_coq_ident(&method_name), method_args.as_slice()); - - let method_loc_name = trait_spec_use.make_loc_name(&mangled_method_name); - - // get spec. the spec takes the generics of the method as arguments - let method_spec_term = trait_spec_use.make_method_spec_term(&method_name); - - Ok((method_loc_name, method_spec_term, method_args)) - } -} - -/// Normalize a type in the given function environment. -pub fn normalize_in_function<'tcx, T>( - function_did: DefId, - tcx: ty::TyCtxt<'tcx>, - ty: T, -) -> Result<T, TranslationError<'tcx>> -where - T: ty::TypeFoldable<ty::TyCtxt<'tcx>>, -{ - let param_env = tcx.param_env(function_did); - info!("Normalizing type {ty:?} in env {param_env:?}"); - normalize_type(tcx, param_env, ty) - .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e))) -} - -pub fn normalize_erasing_regions_in_function<'tcx, T>( - function_did: DefId, - tcx: ty::TyCtxt<'tcx>, - ty: T, -) -> Result<T, TranslationError<'tcx>> -where - T: ty::TypeFoldable<ty::TyCtxt<'tcx>>, -{ - let param_env = tcx.param_env(function_did); - info!("Normalizing type {ty:?} in env {param_env:?}"); - tcx.try_normalize_erasing_regions(param_env, ty) - .map_err(|e| TranslationError::TraitResolution(format!("normalization error: {:?}", e))) -} - -pub fn normalize_projection_in_function<'tcx>( - function_did: DefId, - tcx: ty::TyCtxt<'tcx>, - ty: ty::AliasTy<'tcx>, -) -> Result<ty::Ty<'tcx>, TranslationError<'tcx>> { - let param_env = tcx.param_env(function_did); - info!("Normalizing type {ty:?} in env {param_env:?}"); - normalize_projection_type(tcx, param_env, ty) - .map_err(|e| TranslationError::TraitResolution(format!("could not normalize projection {ty:?}"))) -} - -/// Format the Coq representation of an atomic region. -pub fn format_atomic_region_direct( - r: &polonius_info::AtomicRegion, - scope: Option<&TypeTranslationScope<'_, '_>>, -) -> String { - match r { - polonius_info::AtomicRegion::Loan(_, r) => format!("llft{}", r.index()), - polonius_info::AtomicRegion::PlaceRegion(r) => format!("plft{}", r.index()), - polonius_info::AtomicRegion::Unknown(r) => format!("vlft{}", r.index()), - - polonius_info::AtomicRegion::Universal(_, r) => { - let Some(scope) = scope else { - return format!("ulft{}", r.index()); - }; - - let Some(s) = scope.lifetime_scope.lookup_region(*r) else { - return format!("ulft{}", r.index()); - }; - - s.to_string() - }, - } -} diff --git a/rr_frontend/translation/src/tyvars.rs b/rr_frontend/translation/src/types/tyvars.rs similarity index 79% rename from rr_frontend/translation/src/tyvars.rs rename to rr_frontend/translation/src/types/tyvars.rs index 76952d000a29a89182c6ba50990b642e268391c3..7cb852619112f6dd9dee2bdd1a98ad9916eee4f4 100644 --- a/rr_frontend/translation/src/tyvars.rs +++ b/rr_frontend/translation/src/types/tyvars.rs @@ -4,14 +4,14 @@ // If a copy of the BSD-3-clause license was not distributed with this // file, You can obtain one at https://opensource.org/license/bsd-3-clause/. +//! Utility folders for handling type variables. + use std::collections::{HashMap, HashSet}; use rr_rustc_interface::middle::ty; use rr_rustc_interface::middle::ty::{Ty, TyCtxt, TyKind}; use ty::TypeSuperFoldable; -use crate::base::*; - /// A `TypeFolder` that gathers all the type variables. pub struct TyVarFolder<'tcx> { tcx: TyCtxt<'tcx>, @@ -141,42 +141,3 @@ impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for TyRegionEraseFolder<'tcx> { ty::Region::new_from_kind(self.interner(), ty::RegionKind::ReErased) } } - -/// A `TypeFolder` that finds all regions occurring in a type. -pub struct TyRegionCollectFolder<'tcx> { - tcx: TyCtxt<'tcx>, - regions: HashSet<Region>, -} -impl<'tcx> TyRegionCollectFolder<'tcx> { - pub fn new(tcx: TyCtxt<'tcx>) -> Self { - TyRegionCollectFolder { - tcx, - regions: HashSet::new(), - } - } - - pub fn get_regions(self) -> HashSet<Region> { - self.regions - } -} -impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for TyRegionCollectFolder<'tcx> { - fn interner(&self) -> TyCtxt<'tcx> { - self.tcx - } - - // TODO: handle the case that we pass below binders - fn fold_binder<T>(&mut self, t: ty::Binder<'tcx, T>) -> ty::Binder<'tcx, T> - where - T: ty::TypeFoldable<TyCtxt<'tcx>>, - { - t.super_fold_with(self) - } - - fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { - if let ty::RegionKind::ReVar(r) = r.kind() { - self.regions.insert(r); - } - - r - } -} diff --git a/rr_frontend/translation/src/utils.rs b/rr_frontend/translation/src/utils.rs index cb00fd77a826021a04ae7f66db3697b5a0aa2dae..2c8e7e7cd600bb223caa7cf68f05c0a30494211d 100644 --- a/rr_frontend/translation/src/utils.rs +++ b/rr_frontend/translation/src/utils.rs @@ -1,554 +1,17 @@ -// Except for the exceptions specified below, this code is © 2019, ETH Zurich +// © 2019, ETH Zurich // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -// -// The following functions have been adapted from Miri (https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48) under the MIT license; -// - `try_resolve_did` -//! Various helper functions for working with `mir::Place`. - -use std::mem; +//! Utility functions for manipulating places. use log::{info, trace}; -use rr_rustc_interface::ast::ast; use rr_rustc_interface::data_structures::fx::FxHashSet; -use rr_rustc_interface::hir::def_id::{DefId, CRATE_DEF_INDEX}; use rr_rustc_interface::middle::mir; use rr_rustc_interface::middle::ty::{self, TyCtxt}; use rr_rustc_interface::{hir, middle, span}; -use serde::{Deserialize, Serialize}; - -use crate::spec_parsers::get_export_as_attr; -use crate::type_translator::normalize_in_function; -use crate::{force_matches, Environment}; - -/// An item path that receives generic arguments. -#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] -pub struct PathWithArgs { - path: Vec<String>, - /// An encoding of the GenericArgs for this definition. - /// This is `Some(ty)` if: - /// - the argument represents a type (not a constant or region) - /// - and the arg is not the trivial identity arg (in case of ADTs) - args: Vec<Option<FlatType>>, -} - -impl PathWithArgs { - pub fn to_item<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<(DefId, Vec<Option<ty::GenericArg<'tcx>>>)> { - let did = try_resolve_did(tcx, self.path.as_slice())?; - let mut ty_args = Vec::new(); - - for arg in &self.args { - if let Some(arg) = arg { - let ty = arg.to_type(tcx)?; - ty_args.push(Some(ty::GenericArg::from(ty))); - } else { - ty_args.push(None); - } - } - - Some((did, ty_args)) - } - - /// `args` should be normalized already. - pub fn from_item<'tcx>( - env: &Environment<'tcx>, - did: DefId, - args: &[ty::GenericArg<'tcx>], - ) -> Option<Self> { - let path = get_export_path_for_did(env, did); - let mut flattened_args = Vec::new(); - // TODO: how to represent type variables in case the definition is open? - let mut index = 0; - info!("flattening args {:?}", args); - for arg in args { - if let Some(ty) = arg.as_type() { - // TODO not quite right yet (see above) - if !ty.is_param(index) { - let flattened_ty = convert_ty_to_flat_type(env, ty)?; - flattened_args.push(Some(flattened_ty)); - } - index += 1; - } else { - flattened_args.push(None); - } - } - Some(Self { - path, - args: flattened_args, - }) - } -} - -#[derive(Serialize, Deserialize)] -#[serde(remote = "ty::IntTy")] -pub enum IntTyDef { - Isize, - I8, - I16, - I32, - I64, - I128, -} -#[derive(Serialize, Deserialize)] -#[serde(remote = "ty::UintTy")] -pub enum UintTyDef { - Usize, - U8, - U16, - U32, - U64, - U128, -} - -#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] -/// A "flattened" representation of types that should be suitable serialized storage, and should be -/// stable enough to resolve to the same actual type across compilations. -/// Currently mostly geared to our trait resolution needs. -pub enum FlatType { - /// Path + generic args - /// empty args represents the identity substitution - Adt(PathWithArgs), - #[serde(with = "IntTyDef")] - Int(ty::IntTy), - #[serde(with = "UintTyDef")] - Uint(ty::UintTy), - Char, - Bool, - // TODO: more cases -} - -impl FlatType { - /// Try to convert a flat type to a type. - pub fn to_type<'tcx>(&self, tcx: ty::TyCtxt<'tcx>) -> Option<ty::Ty<'tcx>> { - match self { - Self::Adt(path_with_args) => { - let (did, flat_args) = path_with_args.to_item(tcx)?; - - let ty: ty::EarlyBinder<ty::Ty<'tcx>> = tcx.type_of(did); - let ty::TyKind::Adt(_, args) = ty.skip_binder().kind() else { - return None; - }; - - // build substitution - let mut substs = Vec::new(); - for (ty_arg, flat_arg) in args.iter().zip(flat_args.into_iter()) { - match ty_arg.unpack() { - ty::GenericArgKind::Type(_) => { - if let Some(flat_arg) = flat_arg { - substs.push(flat_arg); - } - }, - _ => { - substs.push(ty_arg); - }, - } - } - - // substitute - info!("substituting {:?} with {:?}", ty, substs); - let subst_ty = - if substs.is_empty() { ty.instantiate_identity() } else { ty.instantiate(tcx, &substs) }; - - Some(subst_ty) - }, - Self::Bool => Some(tcx.mk_ty_from_kind(ty::TyKind::Bool)), - Self::Char => Some(tcx.mk_ty_from_kind(ty::TyKind::Char)), - Self::Int(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Int(it.to_owned()))), - Self::Uint(it) => Some(tcx.mk_ty_from_kind(ty::TyKind::Uint(it.to_owned()))), - } - } -} - -/// Try to convert a type to a flat type. Assumes the type has been normalized already. -pub fn convert_ty_to_flat_type<'tcx>(env: &Environment<'tcx>, ty: ty::Ty<'tcx>) -> Option<FlatType> { - match ty.kind() { - ty::TyKind::Adt(def, args) => { - let did = def.did(); - // TODO: if this is downcast to a variant, this might not work - let path_with_args = PathWithArgs::from_item(env, did, args.as_slice())?; - Some(FlatType::Adt(path_with_args)) - }, - ty::TyKind::Bool => Some(FlatType::Bool), - ty::TyKind::Char => Some(FlatType::Char), - ty::TyKind::Int(it) => Some(FlatType::Int(it.to_owned())), - ty::TyKind::Uint(it) => Some(FlatType::Uint(it.to_owned())), - _ => None, - } -} - -pub fn get_cleaned_def_path(tcx: TyCtxt<'_>, did: DefId) -> Vec<String> { - let def_path = tcx.def_path_str(did); - // we clean this up a bit and segment it - let mut components = Vec::new(); - for i in def_path.split("::") { - if i.starts_with('<') && i.ends_with('>') { - // this is a generic specialization, skip - continue; - } - components.push(i.to_owned()); - } - info!("split def path {:?} to {:?}", def_path, components); - - components -} - -// Alternative implementation of `get_cleaned_def_path`, but this doesn't always yield a rooted -// path (but only a suffix of the full path) -fn extract_def_path(path: &hir::definitions::DefPath) -> Vec<String> { - let mut components = Vec::new(); - for p in &path.data { - if let Some(name) = p.data.get_opt_name() { - components.push(name.as_str().to_owned()); - } - } - components -} - -/// Get the path we should export an item at. -pub fn get_export_path_for_did(env: &Environment, did: DefId) -> Vec<String> { - let attrs = env.get_attributes(did); - - if has_tool_attr(attrs, "export_as") { - let filtered_attrs = filter_tool_attrs(attrs); - - return get_export_as_attr(filtered_attrs.as_slice()).unwrap(); - } - - // Check for an annotation on the surrounding impl - if let Some(impl_did) = env.tcx().impl_of_method(did) { - let attrs = env.get_attributes(impl_did); - - if has_tool_attr(attrs, "export_as") { - let filtered_attrs = filter_tool_attrs(attrs); - let mut path_prefix = get_export_as_attr(filtered_attrs.as_slice()).unwrap(); - - // push the last component of this path - //let def_path = env.tcx().def_path(did); - let mut this_path = get_cleaned_def_path(env.tcx(), did); - path_prefix.push(this_path.pop().unwrap()); - - return path_prefix; - } - } - - get_cleaned_def_path(env.tcx(), did) -} - -/// Gets an instance for a path. -/// Taken from Miri <https://github.com/rust-lang/miri/blob/31fb32e49f42df19b45baccb6aa80c3d726ed6d5/src/helpers.rs#L48>. -pub fn try_resolve_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> -where - T: AsRef<str>, -{ - tcx.crates(()) - .iter() - .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref()) - .and_then(|krate| { - let krate = DefId { - krate: *krate, - index: CRATE_DEF_INDEX, - }; - - let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate); - let mut path_it = path.iter().skip(1).peekable(); - - while let Some(segment) = path_it.next() { - for item in mem::take(&mut items) { - let item: &middle::metadata::ModChild = item; - if item.ident.name.as_str() == segment.as_ref() { - if path_it.peek().is_none() { - return Some(item.res.def_id()); - } - - items = tcx.module_children(item.res.def_id()); - break; - } - } - } - None - }) -} - -pub fn try_resolve_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> -where - T: AsRef<str>, -{ - if let Some(did) = try_resolve_did_direct(tcx, path) { - return Some(did); - } - - // if the first component is "std", try if we can replace it with "alloc" or "core" - if path[0].as_ref() == "std" { - let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect(); - components[0] = "core".to_owned(); - if let Some(did) = try_resolve_did_direct(tcx, &components) { - return Some(did); - } - // try "alloc" - components[0] = "alloc".to_owned(); - try_resolve_did_direct(tcx, &components) - } else { - None - } -} - -/// Determine whether the two argument lists match for the type positions (ignoring consts and regions). -/// The first argument is the authority determining which argument positions are types. -/// The second argument may contain `None` for non-type positions. -fn args_match_types<'tcx>( - reference: &[ty::GenericArg<'tcx>], - compare: &[Option<ty::GenericArg<'tcx>>], -) -> bool { - if reference.len() != compare.len() { - return false; - } - - for (arg1, arg2) in reference.iter().zip(compare.iter()) { - if let Some(ty1) = arg1.as_type() { - if let Some(arg2) = arg2 { - if let Some(ty2) = arg2.as_type() { - if ty1 != ty2 { - return false; - } - } else { - return false; - } - } else { - return false; - } - } - } - true -} - -// Useful queries: -//tcx.trait_id_of_impl -//tcx.associated_items -//tcx.impl_trait_parent -//tcx.implementations_of_trait -//tcx.trait_impls_of -//tcx.trait_impls_in_crate -/// Try to resolve the `DefId` of an implementation of a trait for a particular type. -/// Note that this does not, in general, find a unique solution, in case there are complex things -/// with different where clauses going on. -pub fn try_resolve_trait_impl_did<'tcx>( - tcx: TyCtxt<'tcx>, - trait_did: DefId, - trait_args: &[Option<ty::GenericArg<'tcx>>], - for_type: ty::Ty<'tcx>, -) -> Option<DefId> { - // get all impls of this trait - let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did); - - let simplified_type = - middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?; - let defs = impls.non_blanket_impls().get(&simplified_type)?; - info!("found implementations: {:?}", impls); - - let mut solution = None; - for did in defs { - let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity(); - let impl_self_ty = normalize_in_function(*did, tcx, impl_self_ty).unwrap(); - - // check if this is an implementation for the right type - // TODO: is this the right way to compare the types? - if impl_self_ty == for_type { - let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did); - - if let Some(impl_ref) = impl_ref { - let impl_ref = normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap(); - - let this_impl_args = impl_ref.args; - // filter by the generic instantiation for the trait - info!("found impl with args {:?}", this_impl_args); - // args has self at position 0 and generics of the trait at position 1.. - - // check if the generic argument types match up - if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) { - continue; - } - - info!("found impl {:?}", impl_ref); - if solution.is_some() { - println!( - "Warning: Ambiguous resolution for impl of trait {:?} on type {:?}; solution {:?} but found also {:?}", - trait_did, - for_type, - solution.unwrap(), - impl_ref.def_id, - ); - } else { - solution = Some(*did); - } - } - } - } - - solution -} - -/// Try to resolve the `DefId` of a method in an implementation of a trait for a particular type. -/// Note that this does not, in general, find a unique solution, in case there are complex things -/// with different where clauses going on. -pub fn try_resolve_trait_method_did<'tcx>( - tcx: TyCtxt<'tcx>, - trait_did: DefId, - trait_args: &[Option<ty::GenericArg<'tcx>>], - method_name: &str, - for_type: ty::Ty<'tcx>, -) -> Option<DefId> { - // get all impls of this trait - let impls: &ty::trait_def::TraitImpls = tcx.trait_impls_of(trait_did); - - let simplified_type = - middle::ty::fast_reject::simplify_type(tcx, for_type, ty::fast_reject::TreatParams::AsCandidateKey)?; - let defs = impls.non_blanket_impls().get(&simplified_type)?; - info!("found implementations: {:?}", impls); - - let mut solution = None; - for did in defs { - let impl_self_ty: ty::Ty<'tcx> = tcx.type_of(did).instantiate_identity(); - let impl_self_ty = normalize_in_function(*did, tcx, impl_self_ty).unwrap(); - - // check if this is an implementation for the right type - // TODO: is this the right way to compare the types? - if impl_self_ty == for_type { - let impl_ref: Option<ty::EarlyBinder<ty::TraitRef<'_>>> = tcx.impl_trait_ref(did); - - if let Some(impl_ref) = impl_ref { - let impl_ref = normalize_in_function(*did, tcx, impl_ref.skip_binder()).unwrap(); - - let this_impl_args = impl_ref.args; - // filter by the generic instantiation for the trait - info!("found impl with args {:?}", this_impl_args); - // args has self at position 0 and generics of the trait at position 1.. - - // check if the generic argument types match up - if !args_match_types(&this_impl_args.as_slice()[1..], trait_args) { - continue; - } - - let impl_assoc_items: &ty::AssocItems = tcx.associated_items(did); - // find the right item - if let Some(item) = impl_assoc_items.find_by_name_and_kind( - tcx, - span::symbol::Ident::from_str(method_name), - ty::AssocKind::Fn, - trait_did, - ) { - info!("found impl {:?} with item {:?}", impl_ref, item); - if solution.is_some() { - println!( - "Warning: Ambiguous resolution for method {method_name} of trait {:?} on type {:?}; solution {:?} but found also {:?}", - trait_did, - for_type, - solution.unwrap(), - item.def_id - ); - } else { - solution = Some(item.def_id); - } - } - } - } - } - - solution -} - -/// Try to get a defid of a method at the given path. -/// This does not handle trait methods. -/// This also does not handle overlapping method definitions/specialization well. -pub fn try_resolve_method_did_direct<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> -where - T: AsRef<str>, -{ - tcx.crates(()) - .iter() - .find(|&&krate| tcx.crate_name(krate).as_str() == path[0].as_ref()) - .and_then(|krate| { - let krate = DefId { - krate: *krate, - index: CRATE_DEF_INDEX, - }; - - let mut items: &[middle::metadata::ModChild] = tcx.module_children(krate); - let mut path_it = path.iter().skip(1).peekable(); - - while let Some(segment) = path_it.next() { - //info!("items to look at: {:?}", items); - for item in mem::take(&mut items) { - let item: &middle::metadata::ModChild = item; - - if item.ident.name.as_str() != segment.as_ref() { - continue; - } - - info!("taking path: {:?}", segment.as_ref()); - if path_it.peek().is_none() { - return Some(item.res.def_id()); - } - - // just the method remaining - if path_it.len() != 1 { - items = tcx.module_children(item.res.def_id()); - break; - } - - let did: DefId = item.res.def_id(); - let impls: &[DefId] = tcx.inherent_impls(did); - info!("trying to find method among impls {:?}", impls); - - let find = path_it.next().unwrap(); - for impl_did in impls { - //let ty = tcx.type_of(*impl_did); - //info!("type of impl: {:?}", ty); - let items: &ty::AssocItems = tcx.associated_items(impl_did); - //info!("items here: {:?}", items); - // TODO more robust error handling if there are multiple matches. - for item in items.in_definition_order() { - //info!("comparing: {:?} with {:?}", item.name.as_str(), find); - if item.name.as_str() == find.as_ref() { - return Some(item.def_id); - } - } - //info!("impl items: {:?}", items); - } - - //info!("inherent impls for {:?}: {:?}", did, impls); - return None; - } - } - - None - }) -} - -pub fn try_resolve_method_did<T>(tcx: TyCtxt<'_>, path: &[T]) -> Option<DefId> -where - T: AsRef<str>, -{ - if let Some(did) = try_resolve_method_did_direct(tcx, path) { - return Some(did); - } - - // if the first component is "std", try if we can replace it with "alloc" or "core" - if path[0].as_ref() == "std" { - let mut components: Vec<_> = path.iter().map(|x| x.as_ref().to_owned()).collect(); - components[0] = "core".to_owned(); - if let Some(did) = try_resolve_method_did_direct(tcx, &components) { - return Some(did); - } - // try "alloc" - components[0] = "alloc".to_owned(); - try_resolve_method_did_direct(tcx, &components) - } else { - None - } -} +use crate::force_matches; /// Check if the place `potential_prefix` is a prefix of `place`. For example: /// @@ -777,64 +240,3 @@ impl<'tcx> VecPlace<'tcx> { self.components.len() } } - -/// Check if `<tool>::<name>` is among the attributes, where `tool` is determined by the -/// `spec_hotword` config. -/// Any arguments of the attribute are ignored. -pub fn has_tool_attr(attrs: &[ast::Attribute], name: &str) -> bool { - get_tool_attr(attrs, name).is_some() -} - -/// Get the arguments for a tool attribute, if it exists. -pub fn get_tool_attr<'a>(attrs: &'a [ast::Attribute], name: &str) -> Option<&'a ast::AttrArgs> { - attrs.iter().find_map(|attr| match &attr.kind { - ast::AttrKind::Normal(na) => { - let segments = &na.item.path.segments; - let args = &na.item.args; - (segments.len() == 2 - && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str() - && segments[1].ident.as_str() == name) - .then_some(args) - }, - _ => None, - }) -} - -/// Check if `<tool>::name` is among the filtered attributes. -pub fn has_tool_attr_filtered(attrs: &[&ast::AttrItem], name: &str) -> bool { - attrs.iter().any(|item| { - let segments = &item.path.segments; - segments.len() == 2 - && segments[0].ident.as_str() == rrconfig::spec_hotword().as_str() - && segments[1].ident.as_str() == name - }) -} - -/// Check if any attribute starting with `<tool>` is among the attributes. -pub fn has_any_tool_attr(attrs: &[ast::Attribute]) -> bool { - attrs.iter().any(|attr| match &attr.kind { - ast::AttrKind::Normal(na) => { - let segments = &na.item.path.segments; - segments[0].ident.as_str() == rrconfig::spec_hotword().as_str() - }, - _ => false, - }) -} - -/// Get all tool attributes, i.e. attributes of the shape `<tool>::attr`, where `tool` is -/// determined by the `spec_hotword` config. -pub fn filter_tool_attrs(attrs: &[ast::Attribute]) -> Vec<&ast::AttrItem> { - attrs - .iter() - .filter_map(|attr| match &attr.kind { - ast::AttrKind::Normal(na) => { - let item = &na.item; - - let seg = item.path.segments.get(0)?; - - (seg.ident.name.as_str() == rrconfig::spec_hotword()).then_some(item) - }, - _ => None, - }) - .collect() -}