Skip to content

Represent Option<bool> and Option<char> as 8bit and 32bit values #36237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 133 additions & 82 deletions src/librustc_trans/adt.rs
Original file line number Diff line number Diff line change
@@ -82,25 +82,37 @@ pub enum Repr<'tcx> {
/// General-case enums: for each case there is a struct, and they
/// all start with a field for the discriminant.
General(IntType, Vec<Struct<'tcx>>),
/// Two cases distinguished by a nullable pointer: the case with discriminant
/// `nndiscr` must have single field which is known to be nonnull due to its type.
/// The other case is known to be zero sized. Hence we represent the enum
/// as simply a nullable pointer: if not null it indicates the `nndiscr` variant,
/// otherwise it indicates the other case.
RawNullablePointer {
nndiscr: Disr,
nnty: Ty<'tcx>,
nullfields: Vec<Ty<'tcx>>
/// Two cases distinguished by a known-to-be-forbidden value.
///
/// Example: `Option<&T>` (a `&T` cannot be null)
/// Example: `Option<char>` (a `char` is large enough to hold 2^32 - 1,
/// but this value is forbidden by definition)
/// Example: `Result<&T, ()>` (a `&T` cannot be null)
///
/// One of the cases (the "unit case") must be known to be
/// zero-sized (e.g. `None`). The other case (the "payload case")
/// must be known to be a single field that cannot adopt a
/// specific value (in the above examples, 0 for `&T` or 2^32 - 1
/// for `char`).
///
/// We may safely represent the enum by its payload case and
/// differentiate between cases by checking for the forbidden
/// value.
RawForbiddenValue {
/// Unit case (e.g. `None` or `Either((), ())`)
unit_fields: Vec<Ty<'tcx>>,
/// Case holding a payload: the constructor
payload_discr: Disr,
/// Case holding a payload: the type
payload_ty: Ty<'tcx>,
/// A value that the payload can never hold.
forbidden_value: ValueRef,
},
/// Two cases distinguished by a nullable pointer: the case with discriminant
/// `nndiscr` is represented by the struct `nonnull`, where the `discrfield`th
/// field is known to be nonnull due to its type; if that field is null, then
/// it represents the other case, which is inhabited by at most one value
/// (and all other fields are undefined/unused).
///
/// For example, `std::option::Option` instantiated at a safe pointer type
/// is represented such that `None` is a null pointer and `Some` is the
/// identity function.
StructWrappedNullablePointer {
nonnull: Struct<'tcx>,
nndiscr: Disr,
@@ -217,34 +229,50 @@ fn represent_type_uncached<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
}

if cases.len() == 2 && hint == attr::ReprAny {
// Nullable pointer optimization
let mut discr = 0;
while discr < 2 {
// Two cases, so it might be possible to turn this
// into a `RawForbiddenValue` or a
// `StructWrappedNullablePointer`, if all conditions
// are met.
for discr in 0 .. 2 {
if cases[1 - discr].is_zerolen(cx, t) {
// One of the cases has zero length. We are on the right track.
let st = mk_struct(cx, &cases[discr].tys,
false, t);
match cases[discr].find_ptr(cx) {
Some(ref df) if df.len() == 1 && st.fields.len() == 1 => {
return RawNullablePointer {
nndiscr: Disr::from(discr),
nnty: st.fields[0],
nullfields: cases[1 - discr].tys.clone()

// For the moment, we can only apply these
// optimizations to safe pointers.
match cases[discr].find_forbidden_value(cx) {
Some((ref df, forbidden_value))
if df.len() == 1 && st.fields.len() == 1 => {
let payload_ty = st.fields[0];
return RawForbiddenValue {
payload_discr: Disr::from(discr),
payload_ty: payload_ty,
forbidden_value: forbidden_value,
unit_fields: cases[1 - discr].tys.clone()
};
}
Some(mut discrfield) => {
discrfield.push(0);
discrfield.reverse();
return StructWrappedNullablePointer {
nndiscr: Disr::from(discr),
nonnull: st,
discrfield: discrfield,
nullfields: cases[1 - discr].tys.clone()
};
Some((mut discrfield, forbidden)) => {
if is_null(forbidden) {
discrfield.push(0);
discrfield.reverse();
return StructWrappedNullablePointer {
nndiscr: Disr::from(discr),
nonnull: st,
discrfield: discrfield,
nullfields: cases[1 - discr].tys.clone()
};
}
}
None => {}
}
// No need to continue the loop. If both cases
// have zero length, we can apply neither
// `RawForbiddenValue` nor
// `StructWrappedNullablePointer`.
break;

}
discr += 1;
}
}

@@ -340,61 +368,73 @@ struct Case<'tcx> {
/// This represents the (GEP) indices to follow to get to the discriminant field
pub type DiscrField = Vec<usize>;

fn find_discr_field_candidate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
fn find_discr_field_candidate<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
tcx: TyCtxt<'a, 'tcx, 'tcx>,
ty: Ty<'tcx>,
mut path: DiscrField)
-> Option<DiscrField> {
-> Option<(DiscrField, ValueRef)> {
match ty.sty {
// Fat &T/&mut T/Box<T> i.e. T is [T], str, or Trait
// Fat &T/&mut T/Box<T> i.e. T is [T], str, or Trait; null is forbidden
ty::TyRef(_, ty::TypeAndMut { ty, .. }) | ty::TyBox(ty) if !type_is_sized(tcx, ty) => {
path.push(FAT_PTR_ADDR);
Some(path)
Some((path, C_null(type_of::sizing_type_of(cx, ty))))
},

// Regular thin pointer: &T/&mut T/Box<T>
ty::TyRef(..) | ty::TyBox(..) => Some(path),
// Regular thin pointer: &T/&mut T/Box<T>; null is forbidden
ty::TyRef(..) | ty::TyBox(..) => Some((path, C_null(type_of::sizing_type_of(cx, ty)))),

// Function pointer: `fn() -> i32`; null is forbidden
ty::TyFnPtr(_) => Some((path, C_null(type_of::sizing_type_of(cx, ty)))),

// Function pointer: `fn() -> i32`
ty::TyFnPtr(_) => Some(path),
// Is this a char or a bool? If so, std::uXX:MAX is forbidden.
ty::TyChar => Some((path,
C_integral(type_of::sizing_type_of(cx, ty),
std::u32::MAX as u64, false))),
ty::TyBool => Some((path,
C_integral(type_of::sizing_type_of(cx, ty),
std::u8::MAX as u64, false))),

// Is this the NonZero lang item wrapping a pointer or integer type?
// If so, null is forbidden.
ty::TyStruct(def, substs) if Some(def.did) == tcx.lang_items.non_zero() => {
let nonzero_fields = &def.struct_variant().fields;
assert_eq!(nonzero_fields.len(), 1);
let field_ty = monomorphize::field_ty(tcx, substs, &nonzero_fields[0]);
match field_ty.sty {
ty::TyRawPtr(ty::TypeAndMut { ty, .. }) if !type_is_sized(tcx, ty) => {
path.extend_from_slice(&[0, FAT_PTR_ADDR]);
Some(path)
Some((path, C_null(Type::i8p(cx))))
},
ty::TyRawPtr(..) | ty::TyInt(..) | ty::TyUint(..) => {
path.push(0);
Some(path)
Some((path, C_null(type_of::sizing_type_of(cx, field_ty))))
},
_ => None
}
},

// Perhaps one of the fields of this struct is non-zero
// Perhaps one of the fields of this struct has a forbidden value.
// let's recurse and find out
ty::TyStruct(def, substs) => {
for (j, field) in def.struct_variant().fields.iter().enumerate() {
let field_ty = monomorphize::field_ty(tcx, substs, field);
if let Some(mut fpath) = find_discr_field_candidate(tcx, field_ty, path.clone()) {
if let Some((mut fpath, forbidden)) =
find_discr_field_candidate(cx, field_ty, path.clone()) {
fpath.push(j);
return Some(fpath);
return Some((fpath, forbidden));
}
}
None
},

// Perhaps one of the upvars of this struct is non-zero
// Perhaps one of the upvars of this struct has a forbidden value.
// Let's recurse and find out!
ty::TyClosure(_, ref substs) => {
for (j, &ty) in substs.upvar_tys.iter().enumerate() {
if let Some(mut fpath) = find_discr_field_candidate(tcx, ty, path.clone()) {
if let Some((mut fpath, forbidden)) =
find_discr_field_candidate(cx, ty, path.clone()) {
fpath.push(j);
return Some(fpath);
return Some((fpath, forbidden));
}
}
None
@@ -403,26 +443,27 @@ fn find_discr_field_candidate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
// Can we use one of the fields in this tuple?
ty::TyTuple(ref tys) => {
for (j, &ty) in tys.iter().enumerate() {
if let Some(mut fpath) = find_discr_field_candidate(tcx, ty, path.clone()) {
if let Some((mut fpath, forbidden)) =
find_discr_field_candidate(cx, ty, path.clone()) {
fpath.push(j);
return Some(fpath);
return Some((fpath, forbidden));
}
}
None
},

// Is this a fixed-size array of something non-zero
// Is this a fixed-size array of something with a forbidden value
// with at least one element?
ty::TyArray(ety, d) if d > 0 => {
if let Some(mut vpath) = find_discr_field_candidate(tcx, ety, path) {
if let Some((mut vpath, forbidden)) = find_discr_field_candidate(cx, ety, path) {
vpath.push(0);
Some(vpath)
Some((vpath, forbidden))
} else {
None
}
},

// Anything else is not a pointer
// Anything else doesn't have a known-to-be-safe forbidden value.
_ => None
}
}
@@ -432,11 +473,22 @@ impl<'tcx> Case<'tcx> {
mk_struct(cx, &self.tys, false, scapegoat).size == 0
}

fn find_ptr<'a>(&self, cx: &CrateContext<'a, 'tcx>) -> Option<DiscrField> {
/// Find a forbidden value that may be used to discriminate in a
/// RawForbiddenValue or StructWrappedNullablePointer.
///
/// Example: In `Option<&T>`, since `&T` has a forbidden value 0,
/// this method will return the path to `&T`, with a value of 0.
///
/// Example: In `Option<(u64, char)>`, since `char` has a
/// forbidden value 2^32 - 1, this method will return the path to
/// the `char` field in the tuple, with a value of 2^32 - 1.
fn find_forbidden_value<'a>(&self, cx: &CrateContext<'a, 'tcx>) ->
Option<(DiscrField, ValueRef)> {
for (i, &ty) in self.tys.iter().enumerate() {
if let Some(mut path) = find_discr_field_candidate(cx.tcx(), ty, vec![]) {
if let Some((mut path, forbidden)) =
find_discr_field_candidate(cx, ty, vec![]) {
path.push(i);
return Some(path);
return Some((path, forbidden));
}
}
None
@@ -643,7 +695,7 @@ pub fn incomplete_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
pub fn finish_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
r: &Repr<'tcx>, llty: &mut Type) {
match *r {
CEnum(..) | General(..) | RawNullablePointer { .. } => { }
CEnum(..) | General(..) | RawForbiddenValue { .. } => { }
Univariant(ref st) | StructWrappedNullablePointer { nonnull: ref st, .. } =>
llty.set_struct_body(&struct_llfields(cx, st, false, false),
st.packed)
@@ -659,8 +711,8 @@ fn generic_type_of<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
r, name, sizing, dst);
match *r {
CEnum(ity, _, _) => ll_inttype(cx, ity),
RawNullablePointer { nnty, .. } =>
type_of::sizing_type_of(cx, nnty),
RawForbiddenValue { payload_ty, .. } =>
type_of::sizing_type_of(cx, payload_ty),
StructWrappedNullablePointer { nonnull: ref st, .. } => {
match name {
None => {
@@ -756,7 +808,7 @@ pub fn trans_switch<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
-> (BranchKind, Option<ValueRef>) {
match *r {
CEnum(..) | General(..) |
RawNullablePointer { .. } | StructWrappedNullablePointer { .. } => {
RawForbiddenValue{ .. } | StructWrappedNullablePointer { .. } => {
(BranchKind::Switch, Some(trans_get_discr(bcx, r, scrutinee, None, range_assert)))
}
Univariant(..) => {
@@ -771,7 +823,7 @@ pub fn is_discr_signed<'tcx>(r: &Repr<'tcx>) -> bool {
CEnum(ity, _, _) => ity.is_signed(),
General(ity, _) => ity.is_signed(),
Univariant(..) => false,
RawNullablePointer { .. } => false,
RawForbiddenValue { payload_ty, .. } => payload_ty.is_signed(),
StructWrappedNullablePointer { .. } => false,
}
}
@@ -792,10 +844,9 @@ pub fn trans_get_discr<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr<'tcx>,
range_assert)
}
Univariant(..) => C_u8(bcx.ccx(), 0),
RawNullablePointer { nndiscr, nnty, .. } => {
let cmp = if nndiscr == Disr(0) { IntEQ } else { IntNE };
let llptrty = type_of::sizing_type_of(bcx.ccx(), nnty);
ICmp(bcx, cmp, Load(bcx, scrutinee), C_null(llptrty), DebugLoc::None)
RawForbiddenValue { payload_discr, forbidden_value, .. } => {
let cmp = if payload_discr == Disr(0) { IntEQ } else { IntNE };
ICmp(bcx, cmp, Load(bcx, scrutinee), forbidden_value, DebugLoc::None)
}
StructWrappedNullablePointer { nndiscr, ref discrfield, .. } => {
struct_wrapped_nullable_bitdiscr(bcx, nndiscr, discrfield, scrutinee)
@@ -856,7 +907,7 @@ pub fn trans_case<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr, discr: Disr)
Univariant(..) => {
bug!("no cases for univariants or structs")
}
RawNullablePointer { .. } |
RawForbiddenValue { .. } |
StructWrappedNullablePointer { .. } => {
assert!(discr == Disr(0) || discr == Disr(1));
C_bool(bcx.ccx(), discr != Disr(0))
@@ -881,10 +932,9 @@ pub fn trans_set_discr<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, r: &Repr<'tcx>,
Univariant(_) => {
assert_eq!(discr, Disr(0));
}
RawNullablePointer { nndiscr, nnty, ..} => {
if discr != nndiscr {
let llptrty = type_of::sizing_type_of(bcx.ccx(), nnty);
Store(bcx, C_null(llptrty), val);
RawForbiddenValue { payload_discr, forbidden_value, ..} => {
if discr != payload_discr {
Store(bcx, forbidden_value, val);
}
}
StructWrappedNullablePointer { nndiscr, ref discrfield, .. } => {
@@ -936,7 +986,7 @@ pub fn trans_field_ptr_builder<'blk, 'tcx>(bcx: &BlockAndBuilder<'blk, 'tcx>,
General(_, ref cases) => {
struct_field_ptr(bcx, &cases[discr.0 as usize], val, ix + 1, true)
}
RawNullablePointer { nndiscr, ref nullfields, .. } |
RawForbiddenValue { payload_discr: nndiscr, unit_fields: ref nullfields, .. } |
StructWrappedNullablePointer { nndiscr, ref nullfields, .. } if discr != nndiscr => {
// The unit-like case might have a nonzero number of unit-like fields.
// (e.d., Result of Either with (), as one side.)
@@ -947,10 +997,11 @@ pub fn trans_field_ptr_builder<'blk, 'tcx>(bcx: &BlockAndBuilder<'blk, 'tcx>,
if bcx.is_unreachable() { return C_undef(ty.ptr_to()); }
bcx.pointercast(val.value, ty.ptr_to())
}
RawNullablePointer { nndiscr, nnty, .. } => {
RawForbiddenValue { payload_discr, payload_ty, .. } => {
// By definition, the payload of RawForbiddenValue has a single field.
assert_eq!(ix, 0);
assert_eq!(discr, nndiscr);
let ty = type_of::type_of(bcx.ccx(), nnty);
assert_eq!(discr, payload_discr);
let ty = type_of::type_of(bcx.ccx(), payload_ty);
if bcx.is_unreachable() { return C_undef(ty.ptr_to()); }
bcx.pointercast(val.value, ty.ptr_to())
}
@@ -1102,12 +1153,12 @@ pub fn trans_const<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, r: &Repr<'tcx>, discr
let contents = build_const_struct(ccx, st, vals);
C_struct(ccx, &contents[..], st.packed)
}
RawNullablePointer { nndiscr, nnty, .. } => {
if discr == nndiscr {
assert_eq!(vals.len(), 1);
RawForbiddenValue { payload_discr, forbidden_value, .. } => {
if discr == payload_discr {
assert_eq!(vals.len(), 1); // By definition, the payload has only a single field.
vals[0]
} else {
C_null(type_of::sizing_type_of(ccx, nnty))
forbidden_value
}
}
StructWrappedNullablePointer { ref nonnull, nndiscr, .. } => {
@@ -1203,14 +1254,14 @@ fn roundup(x: u64, a: u32) -> u64 { let a = a as u64; ((x + (a - 1)) / a) * a }
///
/// (Not to be confused with `common::const_get_elt`, which operates on
/// raw LLVM-level structs and arrays.)
pub fn const_get_field(r: &Repr, val: ValueRef, _discr: Disr,
pub fn const_get_field(r: &Repr, val: ValueRef, discr: Disr,
ix: usize) -> ValueRef {
match *r {
CEnum(..) => bug!("element access in C-like enum const"),
Univariant(..) => const_struct_field(val, ix),
General(..) => const_struct_field(val, ix + 1),
RawNullablePointer { .. } => {
assert_eq!(ix, 0);
RawForbiddenValue { .. } => {
assert_eq!(ix, 0); // By definition, the payload only has a single field.
val
},
StructWrappedNullablePointer{ .. } => const_struct_field(val, ix)
32 changes: 16 additions & 16 deletions src/librustc_trans/debuginfo/metadata.rs
Original file line number Diff line number Diff line change
@@ -1295,36 +1295,36 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> {
]
}
}
adt::RawNullablePointer { nndiscr: non_null_variant_index, nnty, .. } => {
adt::RawForbiddenValue { payload_discr: payload_variant_index, payload_ty, .. } => {
// As far as debuginfo is concerned, the pointer this enum
// represents is still wrapped in a struct. This is to make the
// DWARF representation of enums uniform.

// First create a description of the artificial wrapper struct:
let non_null_variant = &adt.variants[non_null_variant_index.0 as usize];
let non_null_variant_name = non_null_variant.name.as_str();
let payload_variant = &adt.variants[payload_variant_index.0 as usize];
let payload_variant_name = payload_variant.name.as_str();

// The llvm type and metadata of the pointer
let non_null_llvm_type = type_of::type_of(cx, nnty);
let non_null_type_metadata = type_metadata(cx, nnty, self.span);
let payload_llvm_type = type_of::type_of(cx, payload_ty);
let payload_type_metadata = type_metadata(cx, payload_ty, self.span);

// The type of the artificial struct wrapping the pointer
let artificial_struct_llvm_type = Type::struct_(cx,
&[non_null_llvm_type],
&[payload_llvm_type],
false);

// For the metadata of the wrapper struct, we need to create a
// MemberDescription of the struct's single field.
let sole_struct_member_description = MemberDescription {
name: match non_null_variant.kind {
name: match payload_variant.kind {
ty::VariantKind::Tuple => "__0".to_string(),
ty::VariantKind::Struct => {
non_null_variant.fields[0].name.to_string()
payload_variant.fields[0].name.to_string()
}
ty::VariantKind::Unit => bug!()
},
llvm_type: non_null_llvm_type,
type_metadata: non_null_type_metadata,
llvm_type: payload_llvm_type,
type_metadata: payload_type_metadata,
offset: FixedMemberOffset { bytes: 0 },
flags: FLAGS_NONE
};
@@ -1334,13 +1334,13 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> {
.get_unique_type_id_of_enum_variant(
cx,
self.enum_type,
&non_null_variant_name);
&payload_variant_name);

// Now we can create the metadata of the artificial struct
let artificial_struct_metadata =
composite_type_metadata(cx,
artificial_struct_llvm_type,
&non_null_variant_name,
&payload_variant_name,
unique_type_id,
&[sole_struct_member_description],
self.containing_scope,
@@ -1349,11 +1349,11 @@ impl<'tcx> EnumMemberDescriptionFactory<'tcx> {

// Encode the information about the null variant in the union
// member's name.
let null_variant_index = (1 - non_null_variant_index.0) as usize;
let null_variant_name = adt.variants[null_variant_index].name;
let unit_variant_index = (1 - payload_variant_index.0) as usize;
let unit_variant_name = adt.variants[unit_variant_index].name;
let union_member_name = format!("RUST$ENCODED$ENUM${}${}",
0,
null_variant_name);
unit_variant_name);

// Finally create the (singleton) list of descriptions of union
// members.
@@ -1607,7 +1607,7 @@ fn prepare_enum_metadata<'a, 'tcx>(cx: &CrateContext<'a, 'tcx>,
adt::CEnum(inttype, _, _) => {
return FinalMetadata(discriminant_type_metadata(inttype))
},
adt::RawNullablePointer { .. } |
adt::RawForbiddenValue { .. } |
adt::StructWrappedNullablePointer { .. } |
adt::Univariant(..) => None,
adt::General(inttype, _) => Some(discriminant_type_metadata(inttype)),
1 change: 1 addition & 0 deletions src/librustc_trans/disr.rs
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

/// Representation of single value in a C-style enum.
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub struct Disr(pub u64);

22 changes: 17 additions & 5 deletions src/test/run-pass/enum-null-pointer-opt.rs
Original file line number Diff line number Diff line change
@@ -38,21 +38,33 @@ fn main() {
assert_eq!(size_of::<&Trait>(), size_of::<Option<&Trait>>());
assert_eq!(size_of::<&mut Trait>(), size_of::<Option<&mut Trait>>());

// Chars
assert_eq!(size_of::<char>(), size_of::<Option<char>>());
assert_eq!(size_of::<char>(), size_of::<Result<char, ()>>());

// Bools
assert_eq!(size_of::<bool>(), size_of::<Option<bool>>());
assert_eq!(size_of::<bool>(), size_of::<Result<bool, ()>>());

// Pointers - Box<T>
assert_eq!(size_of::<Box<isize>>(), size_of::<Option<Box<isize>>>());

// The optimization can't apply to raw pointers
assert!(size_of::<Option<*const isize>>() != size_of::<*const isize>());
assert!(Some(0 as *const isize).is_some()); // Can't collapse None to null

struct Foo {
_a: Box<isize>
// The optimization can't apply to raw u32
assert!(size_of::<Option<u32>>() != size_of::<u32>());

struct Foo<T> {
_a: T
}
struct Bar(Box<isize>);
struct Bar<T>(T);

// Should apply through structs
assert_eq!(size_of::<Foo>(), size_of::<Option<Foo>>());
assert_eq!(size_of::<Bar>(), size_of::<Option<Bar>>());
assert_eq!(size_of::<Foo<Box<isize>>>(), size_of::<Option<Foo<Box<isize>>>>());
assert_eq!(size_of::<Bar<Box<isize>>>(), size_of::<Option<Bar<Box<isize>>>>());

// and tuples
assert_eq!(size_of::<(u8, Box<isize>)>(), size_of::<Option<(u8, Box<isize>)>>());
// and fixed-size arrays