c2rust-refactor 0.15.0

C2Rust refactoring tool implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
//! This module contains an analysis to infer ownership information for pointers.  It analyzes code
//! using raw pointers and indicates, for each pointer, whether it appears to be owned, mutably
//! borrowed, or immutably borrowed.  It can also infer ownership-polymorphic function signatures,
//! which handles cases where the original C code used a single accessor for both mutable and
//! immutable access to a field.
//!
//! The analysis operates on constraint sets over "permission variables", which can be take on the
//! concrete permissions "READ", "WRITE", and "MOVE".  The analysis runs in two phases.  First, for
//! each function, it analyzes the function and produces a set of constraints relating variables in
//! the function's signature, variables appearing in static locations (such as struct field types).
//! Since interprocedural information is not available yet, this phase leaves holes where
//! constraints for callee functions can be plugged in.  The second phase fills in holes in
//! function summaries to produce complete summaries that are useful to analysis consumers.  It
//! runs interprocedurally to a fixed point, on each function plugging in the complete summaries of
//! its callees and simplifying to produce a complete summary for the current function.

use std::collections::HashMap;
use std::fmt;
use std::u32;

use arena::SyncDroplessArena;
use log::Level;
use rustc::hir;
use rustc::hir::def_id::{DefId, LOCAL_CRATE};
use rustc::hir::{Mutability, Node};
use rustc::ty::{TyCtxt, TyKind, TypeAndMut, TyS};
use rustc_index::vec::{Idx, IndexVec};
use syntax::ast::IntTy;
use syntax::source_map::Span;

use crate::analysis::labeled_ty::{LabeledTy, LabeledTyCtxt};
use crate::command::CommandState;
use crate::context::HirMap;
use crate::type_map;
use crate::RefactorCtxt;

mod annot;
pub mod constraint;
mod context;
mod inst;
mod inter;
mod intra;
mod mono;
/*
mod mono_filter;
*/
mod debug;

use self::annot::{handle_attrs, handle_marks};
use self::constraint::*;
use self::context::Ctxt;
use self::inst::find_instantiations;
use self::inter::InterCtxt;
use self::intra::IntraCtxt;
use self::mono::compute_all_mono_sigs;
/*
use self::mono_filter::filter_suspicious_monos;
*/
use self::debug::*;

/// A variable index.
///
/// There are multiple kinds of variables using the same index type, so the variable kind must be
/// known by other means to use this effectively.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub struct Var(pub u32);

impl Idx for Var {
    fn new(idx: usize) -> Var {
        assert!(idx as u32 as usize == idx);
        Var(idx as u32)
    }

    fn index(self) -> usize {
        self.0 as usize
    }
}

impl Var {
    fn next(self) -> Self {
        Var(self.0 + 1)
    }
}

/// A permission variable.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PermVar {
    /// "Static" variables appear in the types of non-function items.  This includes `static` items
    /// as well as `struct`s and other ADTs.  Constraints on static vars are inferred from their
    /// usage inside functions.
    Static(Var),

    /// "Signature" variables appear in the signatures of function items.  Constraints on sig vars
    /// are inferred from the body of the function in question.
    Sig(Var),

    /// "Instantiation" variables appear in the instantiations of function signatures inside other
    /// functions.  They are left intact during the initial summary generation, to be filled in
    /// during a later phase of the analysis.
    Inst(Var),

    /// "Local" variables appear in the types of temporaries.  Constraints on local vars are
    /// produced while analyzing a function, and are simplified away when the function's constraint
    /// generation is done.
    Local(Var),
}

/// A type where pointer type constructors are labeled with permission variables.
pub type LTy<'lty, 'tcx> = LabeledTy<'lty, 'tcx, Option<PermVar>>;
type LFnSig<'lty, 'tcx> = FnSig<'lty, 'tcx, Option<PermVar>>;

/// A generic labeled function signature.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct FnSig<'lty, 'tcx, L: 'lty> {
    pub inputs: &'lty [LabeledTy<'lty, 'tcx, L>],
    pub output: LabeledTy<'lty, 'tcx, L>,
    pub is_variadic: bool,
}

/// One of the concrete permission values, READ, WRITE, or MOVE.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
pub enum ConcretePerm {
    Read,
    Write,
    Move,
}

impl<'lty, 'tcx, L: fmt::Debug> type_map::Signature<LabeledTy<'lty, 'tcx, L>>
    for FnSig<'lty, 'tcx, L>
{
    fn num_inputs(&self) -> usize {
        self.inputs.len()
    }

    fn input(&self, idx: usize) -> LabeledTy<'lty, 'tcx, L> {
        self.inputs[idx]
    }

    fn output(&self) -> LabeledTy<'lty, 'tcx, L> {
        self.output
    }
}

/// Check if a definition is a `fn` item of some sort.  Note that this does not return true on
/// closures.
fn is_fn(hir_map: &hir::map::Map, def_id: DefId) -> bool {
    let n = match hir_map.get_if_local(def_id) {
        None => return false,
        Some(n) => n,
    };

    match n {
        Node::Item(i) => match i.kind {
            hir::ItemKind::Fn(..) => true,
            _ => false,
        },
        Node::ForeignItem(i) => match i.kind {
            hir::ForeignItemKind::Fn(..) => true,
            _ => false,
        },
        Node::TraitItem(i) => match i.kind {
            hir::TraitItemKind::Method(..) => true,
            _ => false,
        },
        Node::ImplItem(i) => match i.kind {
            hir::ImplItemKind::Method(..) => true,
            _ => false,
        },
        _ => false,
    }
}

/// Run the intraprocedural step of polymorphic signature inference.  Results are written back into
/// the `Ctxt`.
fn analyze_intra<'a, 'tcx, 'lty>(
    cx: &mut Ctxt<'lty, 'tcx>,
    hir_map: &HirMap<'a, 'tcx>,
    tcx: TyCtxt<'tcx>,
) {
    for &def_id in tcx.mir_keys(LOCAL_CRATE).iter() {
        // We currently don't process `static` bodies, even though they do have MIR.
        if !is_fn(hir_map, def_id) {
            continue;
        }

        let mir = tcx.optimized_mir(def_id);

        let mut local_cx = IntraCtxt::new(cx, def_id, mir);
        local_cx.init();

        for (bbid, bb) in mir.basic_blocks().iter_enumerated() {
            local_cx.handle_basic_block(bbid, bb);
        }

        local_cx.finish();
    }
}

/// Add conservative assignments for extern functions that we can't
/// analyze. Results are written back into the first variant for each external
/// function in the `Ctxt`.
fn analyze_externs<'a, 'tcx, 'lty>(cx: &mut Ctxt<'lty, 'tcx>, hir_map: &HirMap<'a, 'tcx>) {
    for (def_id, func_summ) in cx.funcs_mut() {
        if func_summ.cset_provided {
            continue;
        }
        match hir_map.get_if_local(*def_id) {
            Some(Node::ForeignItem(i)) => match i.kind {
                // We only want to consider foreign functions
                hir::ForeignItemKind::Fn(..) => {}
                _ => continue,
            },
            _ => continue,
        }
        for &input in func_summ.sig.inputs {
            if let Some(p) = input.label {
                match input.ty.kind {
                    TyKind::Ref(_, _, Mutability::Mutable) => {
                        func_summ.sig_cset.add(Perm::Concrete(ConcretePerm::Move), Perm::var(p));
                    }
                    TyKind::RawPtr(TypeAndMut{mutbl: Mutability::Mutable, ..}) => {
                        func_summ.sig_cset.add(Perm::Concrete(ConcretePerm::Move), Perm::var(p));
                    }
                    _ => {}
                }
            }
        }
        func_summ.cset_provided = true;
    }
}

/// Run the interprocedural step of polymorphic signature inference.  Results are written back into
/// the `Ctxt`.
fn analyze_inter<'lty, 'tcx>(cx: &mut Ctxt<'lty, 'tcx>) {
    let mut inter_cx = InterCtxt::new(cx);
    inter_cx.process();
    inter_cx.finish();
}

fn is_mut_t(ty: &TyS) -> bool {
    if let TyKind::RawPtr(mut_ty) = ty.kind {
        if mut_ty.mutbl == Mutability::Mutable {
            if let TyKind::Param(param_ty) = mut_ty.ty.kind {
                return param_ty.name.as_str() == "T";
            }
        }
    }

    false
}

/// This function adds permission constraints to builtin functions like ptr.offset()
/// so that ownership analysis can reason about them properly
// TODO: When we want to add more constraints to functions here, we should make this
// more generic
fn register_std_constraints<'a, 'tcx, 'lty>(
    ctxt: &mut Ctxt<'lty, 'tcx>,
    tctxt: TyCtxt<'tcx>,
) {
    for (def_id, func_summ) in ctxt.funcs_mut() {
        let fn_name_path = tctxt.def_path(*def_id).to_string_no_crate();

        // #[ownership_constraints(le(WRITE, _0), le(WRITE, _1), le(_0, _1))]
        // fn offset<T>(self: *mut T, _: isize) -> *mut T;
        if func_summ.sig.inputs.len() == 2 && fn_name_path == "::ptr[0]::{{impl}}[1]::offset[0]" {
            let param0_is_mut_t = is_mut_t(func_summ.sig.inputs[0].ty);
            let param1_is_isize = if let TyKind::Int(int_ty) = func_summ.sig.inputs[1].ty.kind {
                int_ty == IntTy::Isize
            } else {
                false
            };
            let ret_is_mut_t = is_mut_t(func_summ.sig.output.ty);
            if param0_is_mut_t && param1_is_isize && ret_is_mut_t {
                func_summ.cset_provided = true;
                func_summ.sig_cset.add(Perm::SigVar(Var(1)), Perm::SigVar(Var(0)));
            }
        }
    }
}

/// Run the analysis.
pub fn analyze<'lty, 'a: 'lty, 'tcx: 'a>(
    st: &CommandState,
    dcx: &RefactorCtxt<'a, 'tcx>,
    arena: &'lty SyncDroplessArena,
) -> AnalysisResult<'lty, 'tcx> {
    let mut cx = Ctxt::new(dcx.ty_ctxt(), arena);

    // Process the annotations and marks provided by the user.
    handle_attrs(&mut cx, st, dcx);
    handle_marks(&mut cx, st, dcx);

    // Compute polymorphic signatures / constraint sets for each function
    analyze_intra(&mut cx, &dcx.hir_map(), dcx.ty_ctxt());
    // Add constraints for extern functions
    analyze_externs(&mut cx, &dcx.hir_map());
    // Inject constraints for std functions
    register_std_constraints(&mut cx, dcx.ty_ctxt());
    analyze_inter(&mut cx);

    // Compute monomorphic signatures and select instantiations in each function
    compute_all_mono_sigs(&mut cx);
    find_instantiations(&mut cx);

    // Convert results to a more usable format.
    cx.into()
}

/// A type where pointers are labeled with variables.
pub type VTy<'lty, 'tcx> = LabeledTy<'lty, 'tcx, Option<Var>>;
/// A signature where pointers are labeled with variables.
pub type VFnSig<'lty, 'tcx> = FnSig<'lty, 'tcx, Option<Var>>;

/// A type where pointers are labeled with concrete permissions.
pub type PTy<'lty, 'tcx> = LabeledTy<'lty, 'tcx, Option<ConcretePerm>>;
/// A signature where pointers are labeled with concrete permissions.
pub type PFnSig<'lty, 'tcx> = FnSig<'lty, 'tcx, Option<ConcretePerm>>;

/// The collected results of running the analysis.
pub struct AnalysisResult<'lty, 'tcx> {
    /// The permission-labeled type of every non-fn item.  This includes statics, consts, and
    /// struct/enum fields.
    pub statics: HashMap<DefId, PTy<'lty, 'tcx>>,

    /// Results for to each (analysis-level) function.  Note that only the primary variant of each
    /// variant group will have its `DefId` present in this table - look up `variants[&id].func_id`
    /// first if you aren't sure whether a `fn` is a primary variant.
    pub funcs: HashMap<DefId, FunctionResult<'lty, 'tcx>>,

    /// Results for to each variant `fn`.  Every `fn` that was analyzed should have an entry
    /// in this table.
    pub variants: HashMap<DefId, VariantResult>,

    /// Results for each monomorphization of each analysis-level function, indexed by function ID
    /// and monomorphization index.
    pub monos: HashMap<(DefId, usize), MonoResult>,

    /// Arena used to allocate all type wrappers
    arena: &'lty SyncDroplessArena,
}

/// Results specific to an analysis-level function.
#[derive(Debug)]
pub struct FunctionResult<'lty, 'tcx: 'lty> {
    /// Polymorphic function signature.  Each pointer is labeled with a `SigVar`.
    pub sig: VFnSig<'lty, 'tcx>,

    /// Mapping of local pat spans to VTys
    pub locals: HashMap<Span, VTy<'lty, 'tcx>>,

    /// Mapping of local vars to concrete permissions
    pub local_assign: IndexVec<Var, ConcretePerm>,

    pub num_sig_vars: u32,

    /// Constraint set relating `SigVar`s to each other and to concrete permission values.
    pub cset: ConstraintSet<'lty>,

    /// List of variant IDs, for multi-variant functions.  If the function has only a single
    /// variant, this field is `None` and the variant's ID is the same as the function's ID.
    pub variants: Option<Vec<DefId>>,

    /// Number of monomorphizations.  If `self.variants` is not `None`, `num_monos` is equal to the
    /// length of that `Vec`.
    pub num_monos: usize,
}

/// Results specific to a variant `fn`.
///
/// Each variant has a parent `FunctionResult`, identified by the `func_id` field.
#[derive(Debug)]
pub struct VariantResult {
    /// ID of the parent function.
    pub func_id: DefId,

    /// Index of this variant within the parent.  If the parent function has multiple variants,
    /// this is also the index of the monomorphization corresponding to this variant.
    pub index: usize,

    /// All references to other functions that appear in this `fn`.  Usually these are function
    /// calls, but they also occur when a function's address is taken.
    pub func_refs: Vec<FuncRef>,
}

/// A reference to a function.
#[derive(Debug)]
pub struct FuncRef {
    /// Function ID of the callee.  Note this refers to an analysis-level function, even if the
    /// `Expr` in the AST refers to a specific variant.
    pub def_id: DefId,

    /// The location of the reference to this function.  If available, this span will point to a
    /// `Path` expression for the function name, a `MethodCall` expression, or an expression that
    /// invokes an overloaded operator.
    pub span: Option<Span>,
}

/// Results specific to a function monomorphization.
///
/// Each monomorphization has a parent `FunctionResult` and a parent `VariantResult`.  If the
/// function's `variants` field is `None`, then the ID of the variant is the same as the function
/// ID.  Otherwise, the variant ID is found by indexing into `variants` with the index of this
/// monomorphization.
#[derive(Debug)]
pub struct MonoResult {
    /// Suffix to add to the function name when this monomorphization is split into its own `fn`.
    /// Usually something like `"mut"` or `"take"`.  If empty, the original name should be used.
    pub suffix: String,

    /// Assignment of concrete permission values to the signature variables of the function.  All
    /// monomorphizations use the `sig` that appears in their parent `FunctionResult`, but each has
    /// a different assignments to the `SigVar`s.
    ///
    /// The length of `assign` should be equal to the parent `FunctionResult`'s `num_sig_vars`.
    pub assign: IndexVec<Var, ConcretePerm>,

    /// Index of the chosen monomorphization for each function reference.  These entries correspond
    /// to those in the parent `VariantResult`'s `func_refs` field.
    pub callee_mono_idxs: Vec<usize>,
}

impl<'lty, 'tcx> AnalysisResult<'lty, 'tcx> {
    /// Get the function and variant results for a `fn` item-like.
    pub fn fn_results(&self, id: DefId) -> (&FunctionResult<'lty, 'tcx>, &VariantResult) {
        let vr = &self.variants[&id];
        let fr = &self.funcs[&vr.func_id];
        (fr, vr)
    }

    pub fn arena(&self) -> &'lty SyncDroplessArena {
        self.arena
    }
}

impl<'lty, 'tcx> From<Ctxt<'lty, 'tcx>> for AnalysisResult<'lty, 'tcx> {
    /// Extract the useful information from the `Ctxt`, and collect it into an `AnalysisResult`.
    fn from(cx: Ctxt<'lty, 'tcx>) -> AnalysisResult<'lty, 'tcx> {
        let mut statics = HashMap::new();
        let mut funcs = HashMap::new();
        let mut variants = HashMap::new();
        let mut monos = HashMap::new();

        // statics

        let perm_lcx = LabeledTyCtxt::new(cx.arena);
        for (&def_id, &lty) in cx.static_summ.iter() {
            let pty = perm_lcx.relabel(lty, &mut |p| {
                if let Some(PermVar::Static(v)) = *p {
                    Some(cx.static_assign[v])
                } else {
                    None
                }
            });
            statics.insert(def_id, pty);
        }

        // funcs

        let var_lcx = LabeledTyCtxt::new(cx.arena);
        for def_id in cx.func_ids() {
            let func = cx.get_func_summ(def_id);

            let sig = {
                let mut f = |p: &Option<_>| {
                    if let Some(PermVar::Sig(v)) = *p {
                        Some(v)
                    } else {
                        None
                    }
                };
                FnSig {
                    inputs: var_lcx.relabel_slice(func.sig.inputs, &mut f),
                    output: var_lcx.relabel(func.sig.output, &mut f),
                    is_variadic: func.sig.is_variadic,
                }
            };

            let variant_ids = if func.variant_ids.len() == 1 {
                None
            } else {
                Some(func.variant_ids.clone())
            };

            // LTy -> VTy
            let mut f = |p: &Option<PermVar>| -> Option<Var> {
                if let Some(PermVar::Local(v)) = *p {
                    Some(v)
                } else {
                    None
                }
            };

            let locals = func.locals
                .iter()
                .map(|(&span, lty)| (span, var_lcx.relabel(&lty, &mut f)))
                .collect();

            funcs.insert(
                def_id,
                FunctionResult {
                    sig,
                    locals,
                    num_sig_vars: func.num_sig_vars,
                    cset: func.sig_cset.clone(),
                    variants: variant_ids,
                    num_monos: func.num_monos,
                    local_assign: func.local_assign.clone(),
                },
            );

            // func variants

            for (idx, &var_id) in func.variant_ids.iter().enumerate() {
                let variant = cx.get_variant_summ(var_id);
                let func_refs = variant
                    .insts
                    .iter()
                    .map(|inst| FuncRef {
                        def_id: inst.callee,
                        span: inst.span,
                    })
                    .collect();

                variants.insert(
                    var_id,
                    VariantResult {
                        func_id: def_id,
                        index: idx,
                        func_refs,
                    },
                );
            }

            // func monos

            // Assign suffixes if not provided.
            let mut suffixes = Vec::new();
            if func.monos_provided {
                // Do nothing. If monos were provided, we'll use their provided names.
            } else if func.num_monos == 1 {
                // Use the original name.
                suffixes.push(String::new());
            } else {
                /// Default suffixes corresponding to the three concrete permissions.
                static SUFFIX_BASE: [&str; 3] = ["", "mut", "take"];
                // If more than one mono tries to use the same default suffix, we need to append a
                // number to disambiguate.
                let mut suffix_count = [0, 0, 0];

                let is_output = mono::infer_outputs(func);

                // Guess a suffix for each mono depending on its output types.  Automatic suffixes look
                // like "", "mut", "take", "2", "mut3", "take4", etc.
                for idx in 0..func.num_monos {
                    let mono = cx.get_mono_summ(def_id, idx);

                    let max_perm = is_output
                        .iter_enumerated()
                        .filter(|&(_, &out)| out)
                        .map(|(v, _)| mono.assign[v])
                        .max()
                        .unwrap_or(ConcretePerm::Read);

                    let idx = max_perm as usize;
                    suffix_count[idx] += 1;
                    let suffix = if suffix_count[idx] == 1 {
                        SUFFIX_BASE[idx].to_owned()
                    } else {
                        format!("{}{}", SUFFIX_BASE[idx], suffix_count[idx])
                    };
                    suffixes.push(suffix);
                }
            }

            for idx in 0..func.num_monos {
                let mono = cx.get_mono_summ(def_id, idx);

                let suffix = if func.monos_provided {
                    mono.suffix.clone()
                } else {
                    suffixes[idx].clone()
                };

                monos.insert(
                    (def_id, idx),
                    MonoResult {
                        suffix,
                        assign: mono.assign.clone(),
                        callee_mono_idxs: mono.callee_mono_idxs.clone(),
                    },
                );
            }
        }

        let Ctxt { arena, .. } = cx;

        AnalysisResult {
            statics,
            funcs,
            variants,
            monos,
            arena,
        }
    }
}

/// Print the analysis results to stderr, for debugging.
pub fn dump_results(dcx: &RefactorCtxt, results: &AnalysisResult) {
    debug!("\n === summary ===");

    let arena = SyncDroplessArena::default();
    let new_lcx = LabeledTyCtxt::new(&arena);
    let format_sig = |sig: VFnSig, assign: &IndexVec<Var, ConcretePerm>| {
        let mut func = |p: &Option<_>| p.as_ref().map(|&v| assign[v]);

        let inputs = new_lcx.relabel_slice(sig.inputs, &mut func);
        let output = new_lcx.relabel(sig.output, &mut func);
        format!("{:?} -> {:?}", pretty_slice(inputs), Pretty(output))
    };

    let path_str = |def_id| dcx.ty_ctxt().def_path(def_id).to_string_no_crate();

    let mut ids = results.statics.keys().cloned().collect::<Vec<_>>();
    ids.sort();
    for id in ids {
        let ty = results.statics[&id];
        debug!("static {} :: {:?}", path_str(id), Pretty(ty));
    }

    let mut ids = results.funcs.keys().cloned().collect::<Vec<_>>();
    ids.sort();
    for id in ids {
        let fr = &results.funcs[&id];

        debug!("func {}:", path_str(id));

        debug!("  sig constraints:");
        if log_enabled!(Level::Debug) {
            for &(a, b) in fr.cset.iter() {
                debug!("    {:?} <= {:?}", a, b);
            }
        }

        if let Some(ref var_ids) = fr.variants {
            for (i, &var_id) in var_ids.iter().enumerate() {
                debug!("  variant {}: {}", i, path_str(var_id));
                let vr = &results.variants[&var_id];

                for (j, func_ref) in vr.func_refs.iter().enumerate() {
                    let callee_fr = &results.funcs[&func_ref.def_id];
                    debug!(
                        "    call #{}: {:?} :: {:?}",
                        j,
                        path_str(func_ref.def_id),
                        callee_fr.sig
                    );
                    debug!("      (at {:?})", func_ref.span);
                }
            }
        } else {
            debug!("  single variant");
            let vr = &results.variants[&id];

            for (j, func_ref) in vr.func_refs.iter().enumerate() {
                let callee_fr = &results.funcs[&func_ref.def_id];
                debug!(
                    "    call #{}: {:?} :: {:?}",
                    j,
                    path_str(func_ref.def_id),
                    callee_fr.sig
                );
                debug!("      (at {:?})", func_ref.span);
            }
        }

        for i in 0..fr.num_monos {
            let mr = &results.monos[&(id, i)];

            let var_id = fr.variants.as_ref().map_or(id, |vars| vars[i]);
            let vr = &results.variants[&var_id];

            debug!(
                "  mono #{} ({:?}): {}",
                i,
                mr.suffix,
                format_sig(fr.sig, &mr.assign)
            );
            for (j, (func_ref, &mono_idx)) in vr
                .func_refs
                .iter()
                .zip(mr.callee_mono_idxs.iter())
                .enumerate()
            {
                let callee_fr = &results.funcs[&func_ref.def_id];
                debug!(
                    "    call #{}: {:?} #{} :: {}",
                    j,
                    path_str(func_ref.def_id),
                    mono_idx,
                    format_sig(
                        callee_fr.sig,
                        &results.monos[&(func_ref.def_id, mono_idx)].assign
                    )
                );
                debug!("      (at {:?})", func_ref.span);
            }
        }
    }
}