Skip to content

Commit

Permalink
Represent C-like enums with a plain LLVM integer, not a struct.
Browse files Browse the repository at this point in the history
This is needed so that the FFI works as expected on platforms that don't
flatten aggregates the way the AMD64 ABI does, especially for `#[repr(C)]`.

This moves more of `type_of` into `trans::adt`, because the type might
or might not be an LLVM struct.
  • Loading branch information
jld committed Nov 25, 2013
1 parent ca32743 commit 8624d5b
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 40 deletions.
66 changes: 46 additions & 20 deletions src/librustc/middle/trans/adt.rs
Expand Up @@ -366,22 +366,41 @@ pub fn ty_of_inttype(ity: IntType) -> ty::t {


/**
* Returns the fields of a struct for the given representation.
* All nominal types are LLVM structs, in order to be able to use
* forward-declared opaque types to prevent circularity in `type_of`.
* LLVM-level types are a little complicated.
*
* C-like enums need to be actual ints, not wrapped in a struct,
* because that changes the ABI on some platforms (see issue #10308).
*
* For nominal types, in some cases, we need to use LLVM named structs
* and fill in the actual contents in a second pass to prevent
* unbounded recursion; see also the comments in `trans::type_of`.
*/
pub fn fields_of(cx: &mut CrateContext, r: &Repr) -> ~[Type] {
generic_fields_of(cx, r, false)
pub fn type_of(cx: &mut CrateContext, r: &Repr) -> Type {
generic_type_of(cx, r, None, false)
}
pub fn sizing_type_of(cx: &mut CrateContext, r: &Repr) -> Type {
generic_type_of(cx, r, None, true)
}
/// Like `fields_of`, but for `type_of::sizing_type_of` (q.v.).
pub fn sizing_fields_of(cx: &mut CrateContext, r: &Repr) -> ~[Type] {
generic_fields_of(cx, r, true)
pub fn incomplete_type_of(cx: &mut CrateContext, r: &Repr, name: &str) -> Type {
generic_type_of(cx, r, Some(name), false)
}
pub fn finish_type_of(cx: &mut CrateContext, r: &Repr, llty: &mut Type) {
match *r {
CEnum(*) | General(*) => { }
Univariant(ref st, _) | NullablePointer{ nonnull: ref st, _ } =>
llty.set_struct_body(struct_llfields(cx, st, false), st.packed)
}
}
fn generic_fields_of(cx: &mut CrateContext, r: &Repr, sizing: bool) -> ~[Type] {

fn generic_type_of(cx: &mut CrateContext, r: &Repr, name: Option<&str>, sizing: bool) -> Type {
match *r {
CEnum(ity, _, _) => ~[ll_inttype(cx, ity)],
Univariant(ref st, _dtor) => struct_llfields(cx, st, sizing),
NullablePointer{ nonnull: ref st, _ } => struct_llfields(cx, st, sizing),
CEnum(ity, _, _) => ll_inttype(cx, ity),
Univariant(ref st, _) | NullablePointer{ nonnull: ref st, _ } => {
match name {
None => Type::struct_(struct_llfields(cx, st, sizing), st.packed),
Some(name) => { assert_eq!(sizing, false); Type::named_struct(name) }
}
}
General(ity, ref sts) => {
// We need a representation that has:
// * The alignment of the most-aligned field
Expand All @@ -394,8 +413,7 @@ fn generic_fields_of(cx: &mut CrateContext, r: &Repr, sizing: bool) -> ~[Type] {
// more of its own type, then use alignment-sized ints to get the rest
// of the size.
//
// Note: if/when we start exposing SIMD vector types (or f80, on some
// platforms that have it), this will need some adjustment.
// FIXME #10604: this breaks when vector types are present.
let size = sts.iter().map(|st| st.size).max().unwrap();
let most_aligned = sts.iter().max_by(|st| st.align).unwrap();
let align = most_aligned.align;
Expand All @@ -411,9 +429,17 @@ fn generic_fields_of(cx: &mut CrateContext, r: &Repr, sizing: bool) -> ~[Type] {
assert_eq!(machine::llalign_of_min(cx, pad_ty) as u64, align);
let align_units = (size + align - 1) / align;
assert_eq!(align % discr_size, 0);
~[discr_ty,
let fields = ~[discr_ty,
Type::array(&discr_ty, align / discr_size - 1),
Type::array(&pad_ty, align_units - 1)]
Type::array(&pad_ty, align_units - 1)];
match name {
None => Type::struct_(fields, false),
Some(name) => {
let mut llty = Type::named_struct(name);
llty.set_struct_body(fields, false);
llty
}
}
}
}
}
Expand Down Expand Up @@ -460,7 +486,8 @@ pub fn trans_get_discr(bcx: @mut Block, r: &Repr, scrutinee: ValueRef, cast_to:
signed = ity.is_signed();
}
General(ity, ref cases) => {
val = load_discr(bcx, ity, scrutinee, 0, (cases.len() - 1) as Disr);
let ptr = GEPi(bcx, scrutinee, [0, 0]);
val = load_discr(bcx, ity, ptr, 0, (cases.len() - 1) as Disr);
signed = ity.is_signed();
}
Univariant(*) => {
Expand All @@ -487,9 +514,8 @@ fn nullable_bitdiscr(bcx: @mut Block, nonnull: &Struct, nndiscr: Disr, ptrfield:
}

/// Helper for cases where the discriminant is simply loaded.
fn load_discr(bcx: @mut Block, ity: IntType, scrutinee: ValueRef, min: Disr, max: Disr)
fn load_discr(bcx: @mut Block, ity: IntType, ptr: ValueRef, min: Disr, max: Disr)
-> ValueRef {
let ptr = GEPi(bcx, scrutinee, [0, 0]);
let llty = ll_inttype(bcx.ccx(), ity);
assert_eq!(val_ty(ptr), llty.ptr_to());
let bits = machine::llbitsize_of_real(bcx.ccx(), llty);
Expand Down Expand Up @@ -546,7 +572,7 @@ pub fn trans_start_init(bcx: @mut Block, r: &Repr, val: ValueRef, discr: Disr) {
CEnum(ity, min, max) => {
assert_discr_in_range(ity, min, max, discr);
Store(bcx, C_integral(ll_inttype(bcx.ccx(), ity), discr as u64, true),
GEPi(bcx, val, [0, 0]))
val)
}
General(ity, _) => {
Store(bcx, C_integral(ll_inttype(bcx.ccx(), ity), discr as u64, true),
Expand Down
34 changes: 14 additions & 20 deletions src/librustc/middle/trans/type_of.rs
Expand Up @@ -147,18 +147,17 @@ pub fn sizing_type_of(cx: &mut CrateContext, t: ty::t) -> Type {

ty::ty_tup(*) | ty::ty_enum(*) => {
let repr = adt::represent_type(cx, t);
Type::struct_(adt::sizing_fields_of(cx, repr), false)
adt::sizing_type_of(cx, repr)
}

ty::ty_struct(did, _) => {
ty::ty_struct(*) => {
if ty::type_is_simd(cx.tcx, t) {
let et = ty::simd_type(cx.tcx, t);
let n = ty::simd_size(cx.tcx, t);
Type::vector(&type_of(cx, et), n as u64)
} else {
let repr = adt::represent_type(cx, t);
let packed = ty::lookup_packed(cx.tcx, did);
Type::struct_(adt::sizing_fields_of(cx, repr), packed)
adt::sizing_type_of(cx, repr)
}
}

Expand Down Expand Up @@ -217,8 +216,9 @@ pub fn type_of(cx: &mut CrateContext, t: ty::t) -> Type {
// fill it in *after* placing it into the type cache. This
// avoids creating more than one copy of the enum when one
// of the enum's variants refers to the enum itself.

Type::named_struct(llvm_type_name(cx, an_enum, did, substs.tps))
let repr = adt::represent_type(cx, t);
let name = llvm_type_name(cx, an_enum, did, substs.tps);
adt::incomplete_type_of(cx, repr, name)
}
ty::ty_estr(ty::vstore_box) => {
Type::box(cx, &Type::vec(cx.sess.targ_cfg.arch, &Type::i8())).ptr_to()
Expand Down Expand Up @@ -287,7 +287,7 @@ pub fn type_of(cx: &mut CrateContext, t: ty::t) -> Type {
ty::ty_type => cx.tydesc_type.ptr_to(),
ty::ty_tup(*) => {
let repr = adt::represent_type(cx, t);
Type::struct_(adt::fields_of(cx, repr), false)
adt::type_of(cx, repr)
}
ty::ty_opaque_closure_ptr(_) => Type::opaque_box(cx).ptr_to(),
ty::ty_struct(did, ref substs) => {
Expand All @@ -299,7 +299,9 @@ pub fn type_of(cx: &mut CrateContext, t: ty::t) -> Type {
// Only create the named struct, but don't fill it in. We fill it
// in *after* placing it into the type cache. This prevents
// infinite recursion with recursive struct types.
Type::named_struct(llvm_type_name(cx, a_struct, did, substs.tps))
let repr = adt::represent_type(cx, t);
let name = llvm_type_name(cx, a_struct, did, substs.tps);
adt::incomplete_type_of(cx, repr, name)
}
}
ty::ty_self(*) => cx.tcx.sess.unimpl("type_of: ty_self"),
Expand All @@ -316,19 +318,11 @@ pub fn type_of(cx: &mut CrateContext, t: ty::t) -> Type {

// If this was an enum or struct, fill in the type now.
match ty::get(t).sty {
ty::ty_enum(*) => {
let repr = adt::represent_type(cx, t);
llty.set_struct_body(adt::fields_of(cx, repr), false);
}

ty::ty_struct(did, _) => {
if !ty::type_is_simd(cx.tcx, t) {
let repr = adt::represent_type(cx, t);
let packed = ty::lookup_packed(cx.tcx, did);
llty.set_struct_body(adt::fields_of(cx, repr), packed);
ty::ty_enum(*) | ty::ty_struct(*) if !ty::type_is_simd(cx.tcx, t) => {
let repr = adt::represent_type(cx, t);
adt::finish_type_of(cx, repr, &mut llty);
}
}
_ => ()
_ => ()
}

return llty;
Expand Down
39 changes: 39 additions & 0 deletions src/test/run-pass/enum-clike-ffi-as-int.rs
@@ -0,0 +1,39 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

/*!
* C-like enums have to be represented as LLVM ints, not wrapped in a
* struct, because it's important for the FFI that they interoperate
* with C integers/enums, and the ABI can treat structs differently.
* For example, on i686-linux-gnu, a struct return value is passed by
* storing to a hidden out parameter, whereas an integer would be
* returned in a register.
*
* This test just checks that the ABIs for the enum and the plain
* integer are compatible, rather than actually calling C code.
* The unused parameter to `foo` is to increase the likelihood of
* crashing if something goes wrong here.
*/

#[repr(u32)]
enum Foo {
A = 0,
B = 23
}

#[inline(never)]
extern "C" fn foo(_x: uint) -> Foo { B }

pub fn main() {
unsafe {
let f: extern "C" fn(uint) -> u32 = std::cast::transmute(foo);
assert_eq!(f(0xDEADBEEF), B as u32);
}
}

4 comments on commit 8624d5b

@bors
Copy link
Contributor

@bors bors commented on 8624d5b Nov 25, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

saw approval from thestinger
at jld@8624d5b

@bors
Copy link
Contributor

@bors bors commented on 8624d5b Nov 25, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

merging jld/rust/enum-unstruct = 8624d5b into auto

@bors
Copy link
Contributor

@bors bors commented on 8624d5b Nov 25, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jld/rust/enum-unstruct = 8624d5b merged ok, testing candidate = 4d5f8df0

Please sign in to comment.