diff --git a/rr_frontend/translation/src/arg_folder.rs b/rr_frontend/translation/src/arg_folder.rs
index 9002a9a5a1144c6afd8737453e00a3bba0580ddc..214ce62ea5b39cd79525947be0f8420f1da7f58b 100644
--- a/rr_frontend/translation/src/arg_folder.rs
+++ b/rr_frontend/translation/src/arg_folder.rs
@@ -2,9 +2,19 @@ use rr_rustc_interface::middle::ty::visit::*;
 use rr_rustc_interface::middle::ty::{self, GenericArg, GenericArgKind, ParamConst, Ty, TyCtxt, TypeFolder};
 use rr_rustc_interface::type_ir::fold::{TypeFoldable, TypeSuperFoldable};
 
+/// 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>],
@@ -208,12 +218,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/body/mod.rs b/rr_frontend/translation/src/body/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6fa0f3599c473f701ffa1a788ca395c2dfbc2e9a
--- /dev/null
+++ b/rr_frontend/translation/src/body/mod.rs
@@ -0,0 +1,146 @@
+// © 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, 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::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::traits::{registry, resolution};
+use crate::{base, consts, procedures, regions, search, traits, types};
+
+pub mod signature;
+pub mod translator;
+
+/// 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(&param_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)
+}
+
+// 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>,
+}
+
+/// 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..f0c7542acec1142bcd6206e9856cb8d879813a09
--- /dev/null
+++ b/rr_frontend/translation/src/body/signature.rs
@@ -0,0 +1,927 @@
+// © 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::translator;
+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::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::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::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 = Self::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::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 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))),
+        }
+    }
+
+    /// 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 = translator::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!"),
+        }
+    }
+
+    /// 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 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 &region_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))
+    }
+
+    /// 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 scope = self.ty_translator.scope.borrow();
+                    let to_universal = || {
+                        let x = scope.lifetime_scope.lookup_region_with_kind(uk1, *r2)?;
+                        let y = scope.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() {
+                        self.inclusion_tracker.add_dynamic_inclusion(*r1, *r2, root_point);
+                        universal_constraints.push((x, y));
+                    };
+                }
+            }
+        }
+        universal_constraints
+    }
+
+    /// 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 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: &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/function_body.rs b/rr_frontend/translation/src/body/translator.rs
similarity index 70%
rename from rr_frontend/translation/src/function_body.rs
rename to rr_frontend/translation/src/body/translator.rs
index 2a1f4b3f6aa3340f9bfa9499c9e8922e3c82c6ab..b9ef08b0e182faa02a561868805ea13c1a9064d2 100644
--- a/rr_frontend/translation/src/function_body.rs
+++ b/rr_frontend/translation/src/body/translator.rs
@@ -35,7 +35,7 @@ use crate::spec_parsers::verbose_function_spec_parser::{
     ClosureMetaInfo, FunctionRequirements, FunctionSpecParser, VerboseFunctionSpecParser,
 };
 use crate::traits::{registry, resolution};
-use crate::{base, regions, search, traits, types};
+use crate::{base, consts, procedures, regions, search, traits, types};
 
 /// Get the syntypes of function arguments for a procedure call.
 pub fn get_arg_syntypes_for_procedure_call<'tcx, 'def>(
@@ -96,212 +96,6 @@ pub fn get_arg_syntypes_for_procedure_call<'tcx, 'def>(
     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)]
@@ -318,1168 +112,206 @@ struct CallRegions {
     pub classification: HashMap<Region, CallRegionKind>,
 }
 
-/// 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> {
+/// 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>,
-    /// 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: 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> 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 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) = 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 = 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: &ProcedureMeta,
-        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 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 = 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 = Self::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!();
-                    }
-                }
+    /// maps locals to variable names
+    variable_map: HashMap<Local, String>,
 
-                // 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);
-                }
-                */
+    /// 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>,
 
-                if let ty::TyKind::Tuple(args) = input_tuple_ty.kind() {
-                    inputs.extend(args.iter());
-                }
+    /// 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)>,
 
-                info!("inputs({}): {:?}, output: {:?}", inputs.len(), inputs, output);
+    /// 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>,
 
-                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),
-                    );
-                }
+    /// map of loop heads to their optional spec closure defid
+    loop_specs: HashMap<BasicBlock, Option<DefId>>,
 
-                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(),
-                };
+    /// relevant locals: (local, name, type)
+    fn_locals: Vec<(Local, String, radium::Type<'def>)>,
 
-                // 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()),
-                    };
-                }
+    /// 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>>,
 
-                // process attributes
-                t.process_closure_attrs(&trait_attrs, &inputs, output, meta)?;
-                Ok(t)
-            },
-            Err(err) => Err(TranslationError::UnknownError(format!("{:?}", err))),
-        }
-    }
+    /// the Caesium function buildder
+    translated_fn: radium::FunctionBuilder<'def>,
+}
 
-    /// Translate the body of a function.
+impl<'a, 'def: 'a, 'tcx: 'def> TX<'a, 'def, 'tcx> {
     pub fn new(
         env: &'def Environment<'tcx>,
-        meta: &ProcedureMeta,
-        proc: Procedure<'tcx>,
-        attrs: &'a [&'a ast::ast::AttrItem],
-        ty_translator: &'def types::TX<'def, 'tcx>,
+        procedure_registry: &'a procedures::Scope<'def>,
+        const_registry: &'a consts::Scope<'def>,
         trait_registry: &'def registry::TR<'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);
+        ty_translator: types::LocalTX<'def, 'tcx>,
 
-        // TODO can we avoid the leak
-        let proc: &'def Procedure = &*Box::leak(Box::new(proc));
+        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();
-        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 = 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 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);
+        let mut checked_op_analyzer = CheckedOpLocalAnalysis::new(env.tcx(), body);
         checked_op_analyzer.analyze();
-        let checked_op_locals = checked_op_analyzer.results();
+        let checked_op_temporaries = 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>, regions::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 = regions::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 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 &region_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))
-    }
-
-    /// 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: &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 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)
-    }
-
-    // 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)
-    }
+        let mut variable_map: HashMap<Local, String> = HashMap::new();
 
-    /// 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
-        }
-    }
+        let local_decls = &body.local_decls;
+        info!("Have {} local decls\n", local_decls.len());
 
-    /// 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;
+        info!("using debug info: {:?}", 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());
-                        }
-                    }
-                }
+        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 {
-                // is this case used when constant propagation happens during MIR construction?
+                ty = local_decl.ty;
             }
-        }
 
-        None
-    }
+            // 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;
+                }
+            }
 
-    fn dump_body(body: &Body) {
-        // TODO: print to file
-        //for dec in &body.local_decls {
-        //info!("Local decl: {:?}", dec);
-        //}
+            // type:
+            info!("Trying to translate type of local {local:?}: {:?}", ty);
+            let tr_ty = ty_translator.translate_type(ty)?;
+            let st = (&tr_ty).into();
 
-        let basic_blocks = &body.basic_blocks;
-        for (bb_idx, bb) in basic_blocks.iter_enumerated() {
-            Self::dump_basic_block(bb_idx, bb);
-        }
-    }
+            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));
 
-    /// 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;
+            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!("{}\t{:?}", i, bb.terminator());
+        info!("radium name map: {:?}", variable_map);
+        // create the function
+        let return_name = opt_return_name?;
+
+        // add lifetime parameters to the map
+        let initial_constraints = Self::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(),
+        })
     }
 
     /// 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,
+        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 info = &self.info;
         let input_facts = &info.borrowck_in_facts;
         let subset_base = &input_facts.subset_base;
 
@@ -1487,7 +319,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
             block: BasicBlock::from_u32(0),
             statement_index: 0,
         };
-        let root_point = self.info.interner.get_point_index(&facts::Point {
+        let root_point = info.interner.get_point_index(&facts::Point {
             location: root_location,
             typ: facts::PointType::Start,
         });
@@ -1498,8 +330,8 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
 
         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 lft1 = info.mk_atomic_region(*r1);
+            let lft2 = info.mk_atomic_region(*r2);
 
             let polonius_info::AtomicRegion::Universal(polonius_info::UniversalRegionKind::Local, _) = lft1
             else {
@@ -1507,12 +339,12 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
             };
 
             // this is a constraint we care about here, add it
-            if self.inclusion_tracker.check_inclusion(*r1, *r2, root_point) {
+            if 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);
+            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(_)));
 
@@ -1521,9 +353,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
         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)> {
@@ -1534,77 +364,59 @@ impl<'a, 'def: 'a, 'tcx: 'def> FunctionTranslator<'a, 'def, 'tcx> {
         // 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, 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>,
-
-    /// registry of other procedures
-    procedure_registry: &'a ProcedureScope<'def>,
-    /// scope of used consts
-    const_registry: &'a ConstScope<'def>,
-    /// trait registry
-    trait_registry: &'def registry::TR<'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: types::LocalTX<'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
+        }
+    }
 
-    /// map of loop heads to their optional spec closure defid
-    loop_specs: HashMap<BasicBlock, Option<DefId>>,
+    /// 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;
 
-    /// relevant locals: (local, name, type)
-    fn_locals: Vec<(Local, String, radium::Type<'def>)>,
+        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?
+            }
+        }
 
-    /// 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>>,
-}
+        None
+    }
 
-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
@@ -1619,11 +431,11 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
         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 {
+            for (r1, r2) in &self.initial_constraints {
                 translated_bb = radium::Stmt::Annot {
                     a: radium::Annotation::CopyLftName(
-                        self.format_atomic_region(&r1),
-                        self.format_atomic_region(&r2),
+                        self.ty_translator.format_atomic_region(r1),
+                        self.ty_translator.format_atomic_region(r2),
                     ),
                     s: Box::new(translated_bb),
                     why: Some("initialization".to_owned()),
@@ -1672,65 +484,15 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
 
         // generate dependencies on statics
         for did in &self.collected_statics {
-            let s = &self.const_registry.statics[did];
-            self.translated_fn.require_static(s.clone());
+            let s = self.const_registry.get_static(*did)?;
+            self.translated_fn.require_static(s.to_owned());
         }
 
         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
-    }
-
+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:
@@ -1882,15 +644,9 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
         }
     }
 
-    /// 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)
+        self.ty_translator.format_atomic_region(&lft)
     }
 
     /// Parse the attributes on spec closure `did` as loop annotations and add it as an invariant
@@ -2725,7 +1481,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
             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)),
+                    a: radium::Annotation::EndLft(self.ty_translator.format_atomic_region(&lft)),
                     s: Box::new(cont_stmt),
                     why: Some("endlft".to_owned()),
                 };
@@ -2924,7 +1680,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
             stmt_annots.insert(
                 0,
                 radium::Annotation::StartLft(
-                    self.format_atomic_region(&lft),
+                    self.ty_translator.format_atomic_region(&lft),
                     outliving.iter().map(|r| self.format_region(*r)).collect(),
                 ),
             );
@@ -2937,7 +1693,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
             // However, we still need to track the region created for the reborrow in an
             // annotation.
 
-            let region = BodyTranslator::region_to_region_vid(*region);
+            let region = TX::region_to_region_vid(*region);
 
             // find inclusion ?r1 ⊑ region -- we will actually enforce region = r1
             let new_constrs: Vec<(facts::Region, facts::Region)> =
@@ -3092,8 +1848,8 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                         self.inclusion_tracker.add_static_inclusion(*s2, *s1, midpoint);
 
                         let annot = radium::Annotation::CopyLftName(
-                            self.format_atomic_region(&lft1),
-                            self.format_atomic_region(&lft2),
+                            self.ty_translator.format_atomic_region(&lft1),
+                            self.ty_translator.format_atomic_region(&lft2),
                         );
                         annots.push(annot);
                     }
@@ -3356,7 +2112,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                 // 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 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))
@@ -3431,12 +2187,12 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
 
             Rvalue::Ref(region, bk, pl) => {
                 let translated_pl = self.translate_place(pl)?;
-                let translated_bk = BodyTranslator::translate_borrow_kind(*bk)?;
+                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.format_atomic_region(&atomic_region);
+                    let lft = self.ty_translator.format_atomic_region(&atomic_region);
                     Ok(radium::Expr::Borrow {
                         lft,
                         bk: translated_bk,
@@ -3445,7 +2201,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                     })
                 } else {
                     info!("Didn't find loan at {:?}: {:?}; region {:?}", loc, rval, region);
-                    let region = BodyTranslator::region_to_region_vid(*region);
+                    let region = TX::region_to_region_vid(*region);
                     let lft = self.format_region(region);
 
                     Ok(radium::Expr::Borrow {
@@ -3459,7 +2215,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
 
             Rvalue::AddressOf(mt, pl) => {
                 let translated_pl = self.translate_place(pl)?;
-                let translated_mt = BodyTranslator::translate_mutability(*mt);
+                let translated_mt = TX::translate_mutability(*mt);
 
                 Ok(radium::Expr::AddressOf {
                     mt: translated_mt,
@@ -3500,7 +2256,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
 
                 let translated_e1 = self.translate_operand(e1, true)?;
                 let translated_e2 = self.translate_operand(e2, true)?;
-                let translated_op = BodyTranslator::translate_checked_binop(*op)?;
+                let translated_op = TX::translate_checked_binop(*op)?;
 
                 Ok(radium::Expr::BinOp {
                     o: translated_op,
@@ -3515,7 +2271,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                 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)?;
+                let translated_op = TX::translate_unop(*op, e1_ty)?;
 
                 Ok(radium::Expr::UnOp {
                     o: translated_op,
@@ -4068,13 +2824,7 @@ impl<'a, 'def: 'a, 'tcx: 'def> BodyTranslator<'a, 'def, 'tcx> {
                                 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
-                                )));
-                            };
-
+                            let s = self.const_registry.get_static(did)?;
                             self.collected_statics.insert(did);
                             Ok(radium::Expr::Literal(radium::Literal::Loc(s.loc_name.clone())))
                         },
diff --git a/rr_frontend/translation/src/consts.rs b/rr_frontend/translation/src/consts.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5ad5056ade7fbe6faa6ebe7e63b10b7301fead64
--- /dev/null
+++ b/rr_frontend/translation/src/consts.rs
@@ -0,0 +1,47 @@
+// © 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/lib.rs b/rr_frontend/translation/src/lib.rs
index 00cefaa7f92e22bdf19365706fca750e77ba5ca9..a418aae1965c8bf97e731d3f08e1c9b146a5c615 100644
--- a/rr_frontend/translation/src/lib.rs
+++ b/rr_frontend/translation/src/lib.rs
@@ -11,12 +11,14 @@
 mod arg_folder;
 mod attrs;
 mod base;
+mod body;
 mod checked_op_analysis;
+mod consts;
 mod data;
 pub mod environment;
 mod force_matches_macro;
-mod function_body;
 mod inclusion_tracker;
+mod procedures;
 mod regions;
 mod search;
 mod shims;
@@ -39,8 +41,9 @@ 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};
@@ -80,8 +83,8 @@ 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>,
+    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],
@@ -113,7 +116,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
+        {
             return None;
         }
 
@@ -149,7 +155,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;
         }
@@ -924,11 +933,8 @@ fn register_shims<'tcx>(vcx: &mut VerificationCtxt<'tcx, '_>) -> Result<(), base
             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)?;
             },
             _ => {
@@ -1031,11 +1037,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)?;
             }
@@ -1051,31 +1053,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 attrs::has_tool_attr_filtered(attrs.as_slice(), "shim") {
-        return function_body::ProcedureMode::Shim;
+        return procedures::Mode::Shim;
     }
 
     if attrs::has_tool_attr_filtered(attrs.as_slice(), "trust_me") {
-        return function_body::ProcedureMode::TrustMe;
+        return procedures::Mode::TrustMe;
     }
 
     if attrs::has_tool_attr_filtered(attrs.as_slice(), "only_spec") {
-        return function_body::ProcedureMode::OnlySpec;
+        return procedures::Mode::OnlySpec;
     }
 
     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.
@@ -1085,7 +1084,7 @@ 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 = attrs::filter_for_tool(attrs);
@@ -1097,29 +1096,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 = 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)?;
     }
@@ -1150,7 +1145,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,
@@ -1160,7 +1155,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,
@@ -1175,7 +1170,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);
@@ -1533,7 +1528,7 @@ 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::SR::empty(&shim_string_arena);
 
@@ -1574,7 +1569,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/regions/mod.rs b/rr_frontend/translation/src/regions/mod.rs
index 072c6744b5fe96fda7e3a0032d2e4c641bda43aa..e177b1f92c6b66d560cc1e6282c14832b5cc36a1 100644
--- a/rr_frontend/translation/src/regions/mod.rs
+++ b/rr_frontend/translation/src/regions/mod.rs
@@ -9,13 +9,15 @@
 use std::collections::{BTreeMap, HashMap, HashSet};
 
 use derive_more::{Constructor, Debug};
+use log::{info, warn};
 use rr_rustc_interface::hir::def_id::DefId;
 use rr_rustc_interface::middle::mir::tcx::PlaceTy;
 use rr_rustc_interface::middle::ty;
 use rr_rustc_interface::middle::ty::{Ty, TyCtxt, TyKind, TypeFoldable};
 use ty::TypeSuperFoldable;
 
-use crate::base::*;
+use crate::arg_folder::ty_instantiate;
+use crate::base::{self, Region};
 use crate::environment::polonius_info::{self, PoloniusInfo};
 use crate::environment::Environment;
 
@@ -72,7 +74,7 @@ pub fn find_region_variables_of_place_type<'tcx>(
     collector.get_regions()
 }
 
-/// Data structure that maps early and late region indices to Polonius regions.
+/// 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
@@ -86,6 +88,26 @@ pub struct EarlyLateRegionMap {
     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(&region)
     }
@@ -125,3 +147,115 @@ pub fn format_atomic_region_direct(
         },
     }
 }
+
+/// 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)
+}
diff --git a/rr_frontend/translation/src/shims/flat.rs b/rr_frontend/translation/src/shims/flat.rs
index 95b6be3d23b8faa93c179e3a3e3fc901ba540ca5..cdee3783b035e51c37c0047453e189a2a5d47a03 100644
--- a/rr_frontend/translation/src/shims/flat.rs
+++ b/rr_frontend/translation/src/shims/flat.rs
@@ -7,9 +7,9 @@
 //! 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 rr_rustc_interface::hir;
 use serde::{Deserialize, Serialize};
 
 use crate::spec_parsers::get_export_as_attr;
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/registry.rs b/rr_frontend/translation/src/traits/registry.rs
index 6176103f367a2d4c3da473cc8273841afe7deccb..81b97f1bec8f8943cf5752cf41c48a6466ef777f 100644
--- a/rr_frontend/translation/src/traits/registry.rs
+++ b/rr_frontend/translation/src/traits/registry.rs
@@ -16,8 +16,8 @@ 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::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};
@@ -223,7 +223,7 @@ impl<'tcx, 'def> TR<'tcx, 'def> {
                 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,