Skip to content

Commit 08f0bf4

Browse files
committed
Support constant trait objects
This adds support for compiling constant values that are trait objects (e.g., `const X: &dyn Trait = &0u32`). Like other references to unsized values, these require adding special cases to `make_allocation_body` and `try_render_ref_opty` in order to support: * `make_allocation_body` unpacks the trait object to get the underlying type and then renders the constant based on that type. * `try_render_ref_opty` constructs a special `"trait_object"` constant that references the allocation body's `DefId`, along with the `DefId`s for the trait object's principal trait (`trait_id`) and vtable (`vtable`). These `DefId`s are used on the `crucible-mir` side to construct a trait object value when translating the MIR JSON into Crucible. (Note that trait objects without a principal trait are not yet supported. See #239 for more information on this point.) Note that because `"trait_object"` is a new part of the schema, I needed to bump the schema version number to `9`. Fixes #237.
1 parent 74b939b commit 08f0bf4

File tree

8 files changed

+165
-33
lines changed

8 files changed

+165
-33
lines changed

SCHEMA_CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@ The following document describes the changes to the JSON schema that
33
as a changelog for the code in the `mir-json` tools themselves, which are
44
versioned separately.)
55

6+
## 9
7+
8+
Add `trait_object`, which represents constant trait objects such as `const X:
9+
&dyn Trait = &0u32`. These include three fields:
10+
11+
* `def_id`: the `DefId` for the constant reference backing the trait object.
12+
This is the same as in `"static_ref"` and `"slice"` constant values.
13+
14+
* `trait_id`: the `DefId` for the principal trait bound in the trait object.
15+
This plays a similar role as the `trait_id` field in `Dynamic` types.
16+
17+
* `vtable`: the `DefId` for the trait object's vtable. This plays a similar
18+
role as the `vtable` field in `UnsizeVtable` cast kinds.
19+
620
## 8
721

822
Add details to the `InlineType` case for `TyKind::Coroutine` and to the

doc/mir-json-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ type ConstVal =
286286
| { kind: "closure", upvars: ConstVal[] }
287287
| { kind: "coroutine_closure", upvars: ConstVal[] }
288288
| { kind: "fn_ptr", "def_id": DefId }
289+
| { kind: "trait_object", def_id: DefId, trait_id: DefId, vtable: DefId }
289290

290291

291292

src/analyz/mod.rs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,13 +1195,6 @@ fn emit_vtable<'tcx>(
11951195
emit_new_defs(ms, out)
11961196
}
11971197

1198-
fn vtable_name<'tcx>(
1199-
mir: &mut MirState<'_, 'tcx>,
1200-
trait_ref: ty::PolyTraitRef<'tcx>,
1201-
) -> String {
1202-
ext_def_id_str(mir.tcx, trait_ref.def_id(), "_vtbl", trait_ref)
1203-
}
1204-
12051198
fn build_vtable_items<'tcx>(
12061199
mir: &mut MirState<'_, 'tcx>,
12071200
trait_ref: ty::PolyTraitRef<'tcx>,

src/analyz/ty_json.rs

Lines changed: 126 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use rustc_index::{IndexVec, Idx};
77
use rustc_middle::mir;
88
use rustc_middle::ty::CoroutineArgsExt;
99
use rustc_const_eval::interpret::{
10-
self, InterpCx, InterpResult, MPlaceTy, OffsetMode, Projectable,
10+
self, InterpCx, InterpResult, MemPlaceMeta, MPlaceTy, OffsetMode,
11+
Projectable,
1112
};
1213
use rustc_middle::ty;
1314
use rustc_middle::ty::{AdtKind, DynKind, TyCtxt, TypeVisitableExt};
@@ -244,6 +245,13 @@ pub fn trait_inst_id_str<'tcx>(
244245
}
245246
}
246247

248+
pub fn vtable_name<'tcx>(
249+
mir: &mut MirState<'_, 'tcx>,
250+
trait_ref: ty::PolyTraitRef<'tcx>,
251+
) -> String {
252+
ext_def_id_str(mir.tcx, trait_ref.def_id(), "_vtbl", trait_ref)
253+
}
254+
247255
/// Get the mangled name of a monomorphic function. As a side effect, this marks the function as
248256
/// "used", so its body will be emitted too.
249257
pub fn get_fn_def_name<'tcx>(
@@ -1460,6 +1468,24 @@ fn make_allocation_body<'tcx>(
14601468
) -> serde_json::Value {
14611469
let tcx = mir.tcx;
14621470

1471+
fn do_default<'tcx>(
1472+
mir: &mut MirState<'_, 'tcx>,
1473+
icx: &mut interpret::InterpCx<'tcx, RenderConstMachine<'tcx>>,
1474+
rty: ty::Ty<'tcx>,
1475+
d: &MPlaceTy<'tcx>,
1476+
) -> serde_json::Value {
1477+
let rlayout = mir.tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(rty)).unwrap();
1478+
let mpty: MPlaceTy = d.offset_with_meta(Size::ZERO, OffsetMode::Inbounds, d.meta(), rlayout, icx).unwrap();
1479+
let rendered = try_render_opty(mir, icx, &mpty.into());
1480+
1481+
json!({
1482+
"kind": "constant",
1483+
"mutable": false,
1484+
"ty": rty.to_json(mir),
1485+
"rendered": rendered,
1486+
})
1487+
}
1488+
14631489
if !is_mut {
14641490
/// Common logic for emitting `"kind": "strbody"` constants, shared by the `str` and `CStr`
14651491
/// cases.
@@ -1490,10 +1516,15 @@ fn make_allocation_body<'tcx>(
14901516
}
14911517

14921518
match *rty.kind() {
1493-
// Special cases for &str, &CStr, and &[T]
1519+
// Special cases for references to unsized types. Currently, the
1520+
// following are supported:
1521+
//
1522+
// * String slices (&str and &CStr)
1523+
// * Array slices (&[T])
1524+
// * Trait objects (&dyn Trait)
14941525
//
1495-
// These and the ones in try_render_ref_opty below should be
1496-
// kept in sync.
1526+
// These special cases and the ones in try_render_ref_opty below
1527+
// should be kept in sync.
14971528
ty::TyKind::Str => {
14981529
let len = d.len(icx).unwrap();
14991530
return do_strbody(mir, icx, d, len);
@@ -1527,21 +1558,16 @@ fn make_allocation_body<'tcx>(
15271558
"rendered": rendered,
15281559
});
15291560
},
1561+
ty::TyKind::Dynamic(ref preds, _, _) => {
1562+
let unpacked_d = unpack_dyn_place(icx, d, preds).unwrap();
1563+
return do_default(mir, icx, unpacked_d.layout.ty, &unpacked_d);
1564+
},
15301565
_ => ()
15311566
}
15321567
}
15331568

15341569
// Default case
1535-
let rlayout = tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(rty)).unwrap();
1536-
let mpty: MPlaceTy = d.offset_with_meta(Size::ZERO, OffsetMode::Inbounds, d.meta(), rlayout, icx).unwrap();
1537-
let rendered = try_render_opty(mir, icx, &mpty.into());
1538-
1539-
return json!({
1540-
"kind": "constant",
1541-
"mutable": false,
1542-
"ty": rty.to_json(mir),
1543-
"rendered": rendered,
1544-
});
1570+
return do_default(mir, icx, rty, d);
15451571
}
15461572

15471573
fn try_render_ref_opty<'tcx>(
@@ -1601,24 +1627,60 @@ fn try_render_ref_opty<'tcx>(
16011627
assert!(d_offset == Size::ZERO, "cannot handle nonzero reference offsets");
16021628

16031629
if !is_mut {
1604-
// Special cases for &str, &CStr, and &[T]
1605-
//
1606-
// These and the ones in make_allocation_body above should be kept in sync.
1607-
let do_slice_special_case = match *rty.kind() {
1608-
ty::TyKind::Str | ty::TyKind::Slice(_) => true,
1609-
ty::TyKind::Adt(adt_def, _) if tcx.is_lang_item(adt_def.did(), LangItem::CStr) => true,
1610-
_ => false,
1611-
};
1612-
if do_slice_special_case {
1630+
fn do_slice<'tcx>(
1631+
icx: &mut interpret::InterpCx<'tcx, RenderConstMachine<'tcx>>,
1632+
d: &MPlaceTy<'tcx>,
1633+
def_id_json: serde_json::Value,
1634+
) -> serde_json::Value {
16131635
// `<MPlaceTy as Projectable>::len` asserts that the input must have `Slice` or
16141636
// `Str` type. However, the implementation it uses works fine on `CStr` too, so we
16151637
// copy-paste the code here.
16161638
let len = d.meta().unwrap_meta().to_target_usize(icx).unwrap();
1617-
return Some(json!({
1639+
json!({
16181640
"kind": "slice",
16191641
"def_id": def_id_json,
16201642
"len": len
1621-
}))
1643+
})
1644+
}
1645+
1646+
// Special cases for references to unsized types. Currently, the
1647+
// following are supported:
1648+
//
1649+
// * String slices (&str and &CStr)
1650+
// * Array slices (&[T])
1651+
// * Trait objects (&dyn Trait)
1652+
//
1653+
// These special cases and the ones in make_allocation_body above should be kept in sync.
1654+
match *rty.kind() {
1655+
ty::TyKind::Str | ty::TyKind::Slice(_) =>
1656+
return Some(do_slice(icx, &d, def_id_json)),
1657+
ty::TyKind::Adt(adt_def, _) if tcx.is_lang_item(adt_def.did(), LangItem::CStr) =>
1658+
return Some(do_slice(icx, &d, def_id_json)),
1659+
ty::TyKind::Dynamic(ref preds, _, _) => {
1660+
let self_ty = unpack_dyn_ty(icx, &d, preds).unwrap();
1661+
let vtable_desc = preds.principal().map(|pred| pred.with_self_ty(tcx, self_ty));
1662+
match vtable_desc {
1663+
Some(vtable_desc) => {
1664+
mir.used.vtables.insert(vtable_desc);
1665+
let ti = TraitInst::from_dynamic_predicates(tcx, preds);
1666+
return Some(json!({
1667+
"kind": "trait_object",
1668+
"def_id": def_id_json,
1669+
"trait_id": trait_inst_id_str(tcx, &ti),
1670+
"vtable": vtable_name(mir, vtable_desc),
1671+
}))
1672+
},
1673+
None =>
1674+
// If there is no principal trait bound, then all of
1675+
// the trait bounds are auto traits. We do not
1676+
// currently support computing vtables for these sorts
1677+
// of trait objects (see #239).
1678+
return Some(json!({
1679+
"kind": "unsupported",
1680+
}))
1681+
}
1682+
},
1683+
_ => (),
16221684
}
16231685
}
16241686

@@ -1852,3 +1914,42 @@ pub fn eval_mir_constant<'tcx>(
18521914
.eval(tcx, ty::TypingEnv::fully_monomorphized(), constant.span)
18531915
.unwrap()
18541916
}
1917+
1918+
// Turn a place with a `dyn Trait` type into the actual dynamic type.
1919+
//
1920+
// This is based on the internals of
1921+
// `rustc_const_eval::interpret::InterpCx::unpack_dyn_trait`.
1922+
fn unpack_dyn_ty<'tcx>(
1923+
icx: &InterpCx<'tcx, RenderConstMachine<'tcx>>,
1924+
mplace: &MPlaceTy<'tcx>,
1925+
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
1926+
) -> InterpResult<'tcx, ty::Ty<'tcx>> {
1927+
assert!(
1928+
matches!(mplace.layout.ty.kind(), ty::Dynamic(_, _, ty::Dyn)),
1929+
"`unpack_dyn_ty` only makes sense on `dyn*` types"
1930+
);
1931+
let vtable = mplace.meta().unwrap_meta().to_pointer(icx)?;
1932+
icx.get_ptr_vtable_ty(vtable, Some(expected_trait))
1933+
}
1934+
1935+
// Turn a place with a `dyn Trait` type into a place with the actual dynamic
1936+
// type.
1937+
//
1938+
// This is based on `rustc_const_eval::interpret::InterpCx::unpack_dyn_trait`.
1939+
fn unpack_dyn_place<'tcx>(
1940+
icx: &InterpCx<'tcx, RenderConstMachine<'tcx>>,
1941+
mplace: &MPlaceTy<'tcx>,
1942+
expected_trait: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
1943+
) -> InterpResult<'tcx, MPlaceTy<'tcx>> {
1944+
let ty = unpack_dyn_ty(icx, mplace, expected_trait)?;
1945+
// This is a kind of transmute, from a place with unsized type and metadata to
1946+
// a place with sized type and no metadata.
1947+
let layout = icx.layout_of(ty)?;
1948+
mplace.offset_with_meta(
1949+
Size::ZERO,
1950+
OffsetMode::Wrapping,
1951+
MemPlaceMeta::None,
1952+
layout,
1953+
icx,
1954+
)
1955+
}

src/schema_ver.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
/// Each version of the schema is assumed to be backwards-incompatible with
77
/// previous versions of the schema. As such, any time this version number is
88
/// bumped, it should be treated as a major version bump.
9-
pub const SCHEMA_VER: u64 = 8;
9+
pub const SCHEMA_VER: u64 = 9;

tests/issues/test0237/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
A regression test for [issue
2+
#237](https://github.com/GaloisInc/mir-json/issues/237). This tests
3+
`mir-json`'s ability to compile constant trait objects.

tests/issues/test0237/test.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
pub trait Trait {
2+
fn method(&self) -> u32;
3+
}
4+
5+
impl Trait for u32 {
6+
fn method(&self) -> u32 { *self }
7+
}
8+
9+
const X: &dyn Trait = &0u32;
10+
11+
pub fn f() -> u32 {
12+
X.method()
13+
}

tests/issues/test0237/test.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
source "$(dirname "$0")/../../common.sh"
4+
5+
expect_no_panic \
6+
saw-rustc test.rs \
7+
--target "$(rustc --print host-tuple)"

0 commit comments

Comments
 (0)