diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs index 6e2f62f870695..280480bb27656 100644 --- a/src/compiletest/runtest.rs +++ b/src/compiletest/runtest.rs @@ -273,7 +273,7 @@ fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) { }; let config = &mut config; let cmds = props.debugger_cmds.connect("\n"); - let check_lines = props.check_lines.clone(); + let check_lines = &props.check_lines; // compile test file (it shoud have 'compile-flags:-g' in the header) let mut ProcRes = compile_test(config, props, testfile); @@ -305,11 +305,34 @@ fn run_debuginfo_test(config: &config, props: &TestProps, testfile: &Path) { let num_check_lines = check_lines.len(); if num_check_lines > 0 { + // Allow check lines to leave parts unspecified (e.g., uninitialized + // bits in the wrong case of an enum) with the notation "[...]". + let check_fragments: ~[~[&str]] = check_lines.map(|s| s.split_str_iter("[...]").collect()); // check if each line in props.check_lines appears in the // output (in order) let mut i = 0u; for line in ProcRes.stdout.line_iter() { - if check_lines[i].trim() == line.trim() { + let mut rest = line.trim(); + let mut first = true; + let mut failed = false; + for &frag in check_fragments[i].iter() { + let found = if first { + if rest.starts_with(frag) { Some(0) } else { None } + } else { + rest.find_str(frag) + }; + match found { + None => { + failed = true; + break; + } + Some(i) => { + rest = rest.slice_from(i + frag.len()); + } + } + first = false; + } + if !failed && rest.len() == 0 { i += 1u; } if i == num_check_lines { diff --git a/src/libextra/enum_set.rs b/src/libextra/enum_set.rs index 2b1246b0af007..3975e1a719750 100644 --- a/src/libextra/enum_set.rs +++ b/src/libextra/enum_set.rs @@ -136,7 +136,7 @@ mod test { use enum_set::*; - #[deriving(Eq)] + #[deriving(Eq)] #[repr(uint)] enum Foo { A, B, C } diff --git a/src/librustc/lib/llvm.rs b/src/librustc/lib/llvm.rs index 9e81ee5e64ccf..cb4656f39855f 100644 --- a/src/librustc/lib/llvm.rs +++ b/src/librustc/lib/llvm.rs @@ -145,6 +145,7 @@ pub static Vector: TypeKind = 13; pub static Metadata: TypeKind = 14; pub static X86_MMX: TypeKind = 15; +#[repr(C)] pub enum AtomicBinOp { Xchg = 0, Add = 1, @@ -159,6 +160,7 @@ pub enum AtomicBinOp { UMin = 10, } +#[repr(C)] pub enum AtomicOrdering { NotAtomic = 0, Unordered = 1, @@ -171,6 +173,7 @@ pub enum AtomicOrdering { } // Consts for the LLVMCodeGenFileType type (in include/llvm/c/TargetMachine.h) +#[repr(C)] pub enum FileType { AssemblyFile = 0, ObjectFile = 1 @@ -192,6 +195,7 @@ pub enum AsmDialect { } #[deriving(Eq)] +#[repr(C)] pub enum CodeGenOptLevel { CodeGenLevelNone = 0, CodeGenLevelLess = 1, @@ -199,6 +203,7 @@ pub enum CodeGenOptLevel { CodeGenLevelAggressive = 3, } +#[repr(C)] pub enum RelocMode { RelocDefault = 0, RelocStatic = 1, @@ -206,6 +211,7 @@ pub enum RelocMode { RelocDynamicNoPic = 3, } +#[repr(C)] pub enum CodeGenModel { CodeModelDefault = 0, CodeModelJITDefault = 1, diff --git a/src/librustc/metadata/common.rs b/src/librustc/metadata/common.rs index c3bc3e0fe25c1..3692d571a53a7 100644 --- a/src/librustc/metadata/common.rs +++ b/src/librustc/metadata/common.rs @@ -111,7 +111,7 @@ pub static tag_items_data_item_reexport_def_id: uint = 0x4e; pub static tag_items_data_item_reexport_name: uint = 0x4f; // used to encode crate_ctxt side tables -#[deriving(Eq)] +#[deriving(Eq)] #[repr(uint)] pub enum astencode_tag { // Reserves 0x50 -- 0x6f tag_ast = 0x50, @@ -143,7 +143,7 @@ impl astencode_tag { pub fn from_uint(value : uint) -> Option { let is_a_tag = first_astencode_tag <= value && value <= last_astencode_tag; if !is_a_tag { None } else { - Some(unsafe { cast::transmute(value as int) }) + Some(unsafe { cast::transmute(value) }) } } } diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs index fb52963f4fc9a..29ecae83f074f 100644 --- a/src/librustc/metadata/encoder.rs +++ b/src/librustc/metadata/encoder.rs @@ -941,6 +941,7 @@ fn encode_info_for_item(ecx: &EncodeContext, encode_family(ebml_w, 't'); encode_bounds_and_type(ebml_w, ecx, &lookup_item_type(tcx, def_id)); encode_name(ecx, ebml_w, item.ident); + encode_attributes(ebml_w, item.attrs); for v in (*enum_definition).variants.iter() { encode_variant_id(ebml_w, local_def(v.node.id)); } diff --git a/src/librustc/middle/lint.rs b/src/librustc/middle/lint.rs index 6f4d94e2a647a..08f9f62fb0a46 100644 --- a/src/librustc/middle/lint.rs +++ b/src/librustc/middle/lint.rs @@ -10,6 +10,7 @@ use driver::session; +use middle::trans::adt; // for `adt::is_ffi_safe` use middle::ty; use middle::pat_util; use metadata::csearch; @@ -900,6 +901,14 @@ fn check_item_ctypes(cx: &Context, it: &ast::item) { "found rust type `uint` in foreign module, while \ libc::c_uint or libc::c_ulong should be used"); } + ast::DefTy(def_id) => { + if !adt::is_ffi_safe(cx.tcx, def_id) { + cx.span_lint(ctypes, ty.span, + "found enum type without foreign-function-safe \ + representation annotation in foreign module"); + // NOTE this message could be more helpful + } + } _ => () } } diff --git a/src/librustc/middle/trans/adt.rs b/src/librustc/middle/trans/adt.rs index 5c255ad081811..1951c78334766 100644 --- a/src/librustc/middle/trans/adt.rs +++ b/src/librustc/middle/trans/adt.rs @@ -14,7 +14,8 @@ * This module determines how to represent enums, structs, and tuples * based on their monomorphized types; it is responsible both for * choosing a representation and translating basic operations on - * values of those types. + * values of those types. (Note: exporting the representations for + * debuggers is handled in debuginfo.rs, not here.) * * Note that the interface treats everything as a general case of an * enum, so structs/tuples/etc. have one pseudo-variant with @@ -29,8 +30,6 @@ * that might contain one and adjust GEP indices accordingly. See * issue #4578. * - * - Using smaller integer types for discriminants. - * * - Store nested enums' discriminants in the same word. Rather, if * some variants start with enums, and those enums representations * have unused alignment padding between discriminant and body, the @@ -56,16 +55,21 @@ use middle::trans::machine; use middle::trans::type_of; use middle::ty; use middle::ty::Disr; +use syntax::abi::{X86, X86_64, Arm, Mips}; use syntax::ast; +use syntax::attr; +use syntax::attr::IntType; use util::ppaux::ty_to_str; use middle::trans::type_::Type; +type Hint = attr::ReprAttr; + /// Representations. pub enum Repr { /// C-like enums; basically an int. - CEnum(Disr, Disr), // discriminant range + CEnum(IntType, Disr, Disr), // discriminant range (signedness based on the IntType) /** * Single-case variants, and structs/tuples/records. * @@ -78,7 +82,7 @@ pub enum Repr { * General-case enums: for each case there is a struct, and they * all start with a field for the discriminant. */ - General(~[Struct]), + General(IntType, ~[Struct]), /** * Two cases distinguished by a nullable pointer: the case with discriminant * `nndiscr` is represented by the struct `nonnull`, where the `ptrfield`th @@ -141,38 +145,25 @@ fn represent_type_uncached(cx: &mut CrateContext, t: ty::t) -> Repr { return Univariant(mk_struct(cx, ftys, packed), dtor) } ty::ty_enum(def_id, ref substs) => { - struct Case { discr: Disr, tys: ~[ty::t] }; - impl Case { - fn is_zerolen(&self, cx: &mut CrateContext) -> bool { - mk_struct(cx, self.tys, false).size == 0 - } - fn find_ptr(&self) -> Option { - self.tys.iter().position(|&ty| mono_data_classify(ty) == MonoNonNull) - } - } - - let cases = do ty::enum_variants(cx.tcx, def_id).map |vi| { - let arg_tys = do vi.args.map |&raw_ty| { - ty::subst(cx.tcx, substs, raw_ty) - }; - Case { discr: vi.disr_val, tys: arg_tys } - }; + let cases = get_cases(cx.tcx, def_id, substs); + let hint = ty::lookup_repr_hint(cx.tcx, def_id); if cases.len() == 0 { // Uninhabitable; represent as unit + assert_eq!(hint, attr::ReprAny); return Univariant(mk_struct(cx, [], false), false); } if cases.iter().all(|c| c.tys.len() == 0) { // All bodies empty -> intlike let discrs = cases.map(|c| c.discr); - return CEnum(*discrs.iter().min().unwrap(), *discrs.iter().max().unwrap()); - } - - if cases.len() == 1 { - // Equivalent to a struct/tuple/newtype. - assert_eq!(cases[0].discr, 0); - return Univariant(mk_struct(cx, cases[0].tys, false), false) + let bounds = IntBounds { + ulo: *discrs.iter().min().unwrap(), + uhi: *discrs.iter().max().unwrap(), + slo: discrs.iter().map(|n| *n as i64).min().unwrap(), + shi: discrs.iter().map(|n| *n as i64).max().unwrap() + }; + return mk_cenum(cx, hint, &bounds); } // Since there's at least one @@ -184,7 +175,14 @@ fn represent_type_uncached(cx: &mut CrateContext, t: ty::t) -> Repr { ty::item_path_str(cx.tcx, def_id))) } - if cases.len() == 2 { + if cases.len() == 1 { + // Equivalent to a struct/tuple/newtype. + assert_eq!(hint, attr::ReprAny); + return Univariant(mk_struct(cx, cases[0].tys, false), false) + } + + if cases.len() == 2 && hint == attr::ReprAny { + // Nullable pointer optimization let mut discr = 0; while discr < 2 { if cases[1 - discr].is_zerolen(cx) { @@ -207,13 +205,67 @@ fn represent_type_uncached(cx: &mut CrateContext, t: ty::t) -> Repr { } // The general case. - let discr = ~[ty::mk_uint()]; - return General(cases.map(|c| mk_struct(cx, discr + c.tys, false))) + assert!((cases.len() - 1) as i64 >= 0); + let bounds = IntBounds { ulo: 0, uhi: (cases.len() - 1) as u64, + slo: 0, shi: (cases.len() - 1) as i64 }; + let ity = range_to_inttype(cx, hint, &bounds); + let discr = ~[ty_of_inttype(ity)]; + return General(ity, cases.map(|c| mk_struct(cx, discr + c.tys, false))) } _ => cx.sess.bug("adt::represent_type called on non-ADT type") } } +/// Determine, without doing translation, whether an ADT must be FFI-safe. +/// For use in lint or similar, where being sound but slightly incomplete is acceptable. +pub fn is_ffi_safe(tcx: ty::ctxt, def_id: ast::DefId) -> bool { + match ty::get(ty::lookup_item_type(tcx, def_id).ty).sty { + ty::ty_enum(def_id, ref substs) => { + let cases = get_cases(tcx, def_id, substs); + // Univariant => like struct/tuple. + if cases.len() <= 2 { + return true; + } + let hint = ty::lookup_repr_hint(tcx, def_id); + // Appropriate representation explicitly selected? + if hint.is_ffi_safe() { + return true; + } + // Conservative approximation of nullable pointers, for Option<~T> etc. + if cases.len() == 2 && hint == attr::ReprAny && + (cases[0].tys.is_empty() && cases[1].find_ptr().is_some() || + cases[1].tys.is_empty() && cases[0].find_ptr().is_some()) { + return true; + } + false + } + // struct, tuple, etc. + // (is this right in the present of typedefs?) + _ => true + } +} + +// NOTE this should probably all be in ty +struct Case { discr: Disr, tys: ~[ty::t] } +impl Case { + fn is_zerolen(&self, cx: &mut CrateContext) -> bool { + mk_struct(cx, self.tys, false).size == 0 + } + fn find_ptr(&self) -> Option { + self.tys.iter().position(|&ty| mono_data_classify(ty) == MonoNonNull) + } +} + +fn get_cases(tcx: ty::ctxt, def_id: ast::DefId, substs: &ty::substs) -> ~[Case] { + do ty::enum_variants(tcx, def_id).map |vi| { + let arg_tys = do vi.args.map |&raw_ty| { + ty::subst(tcx, substs, raw_ty) + }; + Case { discr: vi.disr_val, tys: arg_tys } + } +} + + fn mk_struct(cx: &mut CrateContext, tys: &[ty::t], packed: bool) -> Struct { let lltys = tys.map(|&ty| type_of::sizing_type_of(cx, ty)); let llty_rec = Type::struct_(lltys, packed); @@ -225,6 +277,94 @@ fn mk_struct(cx: &mut CrateContext, tys: &[ty::t], packed: bool) -> Struct { } } +struct IntBounds { + slo: i64, + shi: i64, + ulo: u64, + uhi: u64 +} + +fn mk_cenum(cx: &mut CrateContext, hint: Hint, bounds: &IntBounds) -> Repr { + let it = range_to_inttype(cx, hint, bounds); + match it { + attr::SignedInt(_) => CEnum(it, bounds.slo as Disr, bounds.shi as Disr), + attr::UnsignedInt(_) => CEnum(it, bounds.ulo, bounds.uhi) + } +} + +fn range_to_inttype(cx: &mut CrateContext, hint: Hint, bounds: &IntBounds) -> IntType { + debug!("range_to_inttype: %? %?", hint, bounds); + // Lists of sizes to try. u64 is always allowed as a fallback. + static choose_shortest: &'static[IntType] = &[ + attr::UnsignedInt(ast::ty_u8), attr::SignedInt(ast::ty_i8), + attr::UnsignedInt(ast::ty_u16), attr::SignedInt(ast::ty_i16), + attr::UnsignedInt(ast::ty_u32), attr::SignedInt(ast::ty_i32)]; + static at_least_32: &'static[IntType] = &[ + attr::UnsignedInt(ast::ty_u32), attr::SignedInt(ast::ty_i32)]; + + let attempts; + match hint { + attr::ReprInt(span, ity) => { + if !bounds_usable(cx, ity, bounds) { + cx.sess.span_bug(span, "representation hint insufficient for discriminant range") + } + return ity; + } + attr::ReprExtern => { + attempts = match cx.sess.targ_cfg.arch { + X86 | X86_64 => at_least_32, + // WARNING: the ARM EABI has two variants; the one corresponding to `at_least_32` + // appears to be used on Linux and NetBSD, but some systems may use the variant + // corresponding to `choose_shortest`. However, we don't run on those yet...? + Arm => at_least_32, + Mips => at_least_32, + } + } + attr::ReprAny => { + attempts = choose_shortest; + } + } + let mut best = attr::UnsignedInt(ast::ty_u64); + for &ity in attempts.iter() { + if bounds_usable(cx, ity, bounds) { + best = ity; + break; + } + } + return best; +} + +pub fn ll_inttype(cx: &mut CrateContext, ity: IntType) -> Type { + match ity { + attr::SignedInt(t) => Type::int_from_ty(cx, t), + attr::UnsignedInt(t) => Type::uint_from_ty(cx, t) + } +} + +fn bounds_usable(cx: &mut CrateContext, ity: IntType, bounds: &IntBounds) -> bool { + debug!("bounds_usable: %? %?", ity, bounds); + match ity { + attr::SignedInt(_) => { + let lllo = C_integral(ll_inttype(cx, ity), bounds.slo as u64, true); + let llhi = C_integral(ll_inttype(cx, ity), bounds.shi as u64, true); + bounds.slo == const_to_int(lllo) as i64 && bounds.shi == const_to_int(llhi) as i64 + } + attr::UnsignedInt(_) => { + let lllo = C_integral(ll_inttype(cx, ity), bounds.ulo, false); + let llhi = C_integral(ll_inttype(cx, ity), bounds.uhi, false); + bounds.ulo == const_to_uint(lllo) as u64 && bounds.uhi == const_to_uint(llhi) as u64 + } + } +} + +fn ty_of_inttype(ity: IntType) -> ty::t { + match ity { + attr::SignedInt(t) => ty::mk_mach_int(t), + attr::UnsignedInt(t) => ty::mk_mach_uint(t) + } +} + + /** * Returns the fields of a struct for the given representation. * All nominal types are LLVM structs, in order to be able to use @@ -239,10 +379,10 @@ pub fn sizing_fields_of(cx: &mut CrateContext, r: &Repr) -> ~[Type] { } fn generic_fields_of(cx: &mut CrateContext, r: &Repr, sizing: bool) -> ~[Type] { match *r { - CEnum(*) => ~[Type::enum_discrim(cx)], + CEnum(ity, _, _) => ~[ll_inttype(cx, ity)], Univariant(ref st, _dtor) => struct_llfields(cx, st, sizing), NullablePointer{ nonnull: ref st, _ } => struct_llfields(cx, st, sizing), - General(ref sts) => { + General(_ity, ref sts) => { // To get "the" type of a general enum, we pick the case // with the largest alignment (so it will always align // correctly in containing structures) and pad it out. @@ -288,7 +428,7 @@ pub fn trans_switch(bcx: @mut Block, r: &Repr, scrutinee: ValueRef) -> (_match::branch_kind, Option) { match *r { CEnum(*) | General(*) => { - (_match::switch, Some(trans_get_discr(bcx, r, scrutinee))) + (_match::switch, Some(trans_get_discr(bcx, r, scrutinee, None))) } NullablePointer{ nonnull: ref nonnull, nndiscr, ptrfield, _ } => { (_match::switch, Some(nullable_bitdiscr(bcx, nonnull, nndiscr, ptrfield, scrutinee))) @@ -302,17 +442,32 @@ pub fn trans_switch(bcx: @mut Block, r: &Repr, scrutinee: ValueRef) /// Obtain the actual discriminant of a value. -pub fn trans_get_discr(bcx: @mut Block, r: &Repr, scrutinee: ValueRef) +pub fn trans_get_discr(bcx: @mut Block, r: &Repr, scrutinee: ValueRef, cast_to: Option) -> ValueRef { + let signed; + let val; match *r { - CEnum(min, max) => load_discr(bcx, scrutinee, min, max), - Univariant(*) => C_disr(bcx.ccx(), 0), - General(ref cases) => load_discr(bcx, scrutinee, 0, (cases.len() - 1) as Disr), + CEnum(ity, min, max) => { + val = load_discr(bcx, ity, scrutinee, min, max); + signed = ity.is_signed(); + } + General(ity, ref cases) => { + val = load_discr(bcx, ity, scrutinee, 0, (cases.len() - 1) as Disr); + signed = ity.is_signed(); + } + Univariant(*) => { + val = C_u8(0); + signed = false; + } NullablePointer{ nonnull: ref nonnull, nndiscr, ptrfield, _ } => { - ZExt(bcx, nullable_bitdiscr(bcx, nonnull, nndiscr, ptrfield, scrutinee), - Type::enum_discrim(bcx.ccx())) + val = nullable_bitdiscr(bcx, nonnull, nndiscr, ptrfield, scrutinee); + signed = false; } } + match cast_to { + None => val, + Some(llty) => if signed { SExt(bcx, val, llty) } else { ZExt(bcx, val, llty) } + } } fn nullable_bitdiscr(bcx: @mut Block, nonnull: &Struct, nndiscr: Disr, ptrfield: uint, @@ -324,10 +479,15 @@ 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, scrutinee: ValueRef, min: Disr, max: Disr) +fn load_discr(bcx: @mut Block, ity: IntType, scrutinee: ValueRef, min: Disr, max: Disr) -> ValueRef { let ptr = GEPi(bcx, scrutinee, [0, 0]); - if max + 1 == min { + let llty = ll_inttype(bcx.ccx(), ity); + assert_eq!(val_ty(ptr), llty.ptr_to()); + let bits = machine::llbitsize_of_real(bcx.ccx(), llty); + assert!(bits <= 64); + let mask = (-1u64 >> (64 - bits)) as Disr; + if (max + 1) & mask == min & mask { // i.e., if the range is everything. The lo==hi case would be // rejected by the LLVM verifier (it would mean either an // empty set, which is impossible, or the entire range of the @@ -350,15 +510,17 @@ fn load_discr(bcx: @mut Block, scrutinee: ValueRef, min: Disr, max: Disr) */ pub fn trans_case(bcx: @mut Block, r: &Repr, discr: Disr) -> _match::opt_result { match *r { - CEnum(*) => { - _match::single_result(rslt(bcx, C_disr(bcx.ccx(), discr))) + CEnum(ity, _, _) => { + _match::single_result(rslt(bcx, C_integral(ll_inttype(bcx.ccx(), ity), + discr as u64, true))) + } + General(ity, _) => { + _match::single_result(rslt(bcx, C_integral(ll_inttype(bcx.ccx(), ity), + discr as u64, true))) } Univariant(*) => { bcx.ccx().sess.bug("no cases for univariants or structs") } - General(*) => { - _match::single_result(rslt(bcx, C_disr(bcx.ccx(), discr))) - } NullablePointer{ _ } => { assert!(discr == 0 || discr == 1); _match::single_result(rslt(bcx, C_i1(discr != 0))) @@ -373,9 +535,14 @@ pub fn trans_case(bcx: @mut Block, r: &Repr, discr: Disr) -> _match::opt_result */ pub fn trans_start_init(bcx: @mut Block, r: &Repr, val: ValueRef, discr: Disr) { match *r { - CEnum(min, max) => { - assert!(min <= discr && discr <= max); - Store(bcx, C_disr(bcx.ccx(), discr), GEPi(bcx, val, [0, 0])) + 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])) + } + General(ity, _) => { + Store(bcx, C_integral(ll_inttype(bcx.ccx(), ity), discr as u64, true), + GEPi(bcx, val, [0, 0])) } Univariant(ref st, true) => { assert_eq!(discr, 0); @@ -385,9 +552,6 @@ pub fn trans_start_init(bcx: @mut Block, r: &Repr, val: ValueRef, discr: Disr) { Univariant(*) => { assert_eq!(discr, 0); } - General(*) => { - Store(bcx, C_disr(bcx.ccx(), discr), GEPi(bcx, val, [0, 0])) - } NullablePointer{ nonnull: ref nonnull, nndiscr, ptrfield, _ } => { if discr != nndiscr { let llptrptr = GEPi(bcx, val, [0, ptrfield]); @@ -398,6 +562,13 @@ pub fn trans_start_init(bcx: @mut Block, r: &Repr, val: ValueRef, discr: Disr) { } } +fn assert_discr_in_range(ity: IntType, min: Disr, max: Disr, discr: Disr) { + match ity { + attr::UnsignedInt(_) => assert!(min <= discr && discr <= max), + attr::SignedInt(_) => assert!(min as i64 <= discr as i64 && discr as i64 <= max as i64) + } +} + /** * The number of fields in a given case; for use when obtaining this * information from the type or definition is less convenient. @@ -409,7 +580,7 @@ pub fn num_args(r: &Repr, discr: Disr) -> uint { assert_eq!(discr, 0); st.fields.len() - (if dtor { 1 } else { 0 }) } - General(ref cases) => cases[discr].fields.len() - 1, + General(_, ref cases) => cases[discr].fields.len() - 1, NullablePointer{ nonnull: ref nonnull, nndiscr, nullfields: ref nullfields, _ } => { if discr == nndiscr { nonnull.fields.len() } else { nullfields.len() } } @@ -430,7 +601,7 @@ pub fn trans_field_ptr(bcx: @mut Block, r: &Repr, val: ValueRef, discr: Disr, assert_eq!(discr, 0); struct_field_ptr(bcx, st, val, ix, false) } - General(ref cases) => { + General(_, ref cases) => { struct_field_ptr(bcx, &cases[discr], val, ix + 1, true) } NullablePointer{ nonnull: ref nonnull, nullfields: ref nullfields, nndiscr, _ } => { @@ -498,23 +669,22 @@ pub fn trans_drop_flag_ptr(bcx: @mut Block, r: &Repr, val: ValueRef) -> ValueRef pub fn trans_const(ccx: &mut CrateContext, r: &Repr, discr: Disr, vals: &[ValueRef]) -> ValueRef { match *r { - CEnum(min, max) => { + CEnum(ity, min, max) => { assert_eq!(vals.len(), 0); - assert!(min <= discr && discr <= max); - C_disr(ccx, discr) - } - Univariant(ref st, _dro) => { - assert_eq!(discr, 0); - C_struct(build_const_struct(ccx, st, vals)) + assert_discr_in_range(ity, min, max, discr); + C_integral(ll_inttype(ccx, ity), discr as u64, true) } - General(ref cases) => { + General(ity, ref cases) => { let case = &cases[discr]; let max_sz = cases.iter().map(|x| x.size).max().unwrap(); - let discr_ty = C_disr(ccx, discr); - let contents = build_const_struct(ccx, case, - ~[discr_ty] + vals); + let lldiscr = C_integral(ll_inttype(ccx, ity), discr as u64, true); + let contents = build_const_struct(ccx, case, ~[lldiscr] + vals); C_struct(contents + &[padding(max_sz - case.size)]) } + Univariant(ref st, _dro) => { + assert!(discr == 0); + C_struct(build_const_struct(ccx, st, vals)) + } NullablePointer{ nonnull: ref nonnull, nndiscr, ptrfield, _ } => { if discr == nndiscr { C_struct(build_const_struct(ccx, nonnull, vals)) @@ -584,9 +754,19 @@ fn roundup(x: u64, a: u64) -> u64 { ((x + (a - 1)) / a) * a } pub fn const_get_discrim(ccx: &mut CrateContext, r: &Repr, val: ValueRef) -> Disr { match *r { - CEnum(*) => const_to_uint(val) as Disr, + CEnum(ity, _, _) => { + match ity { + attr::SignedInt(*) => const_to_int(val) as Disr, + attr::UnsignedInt(*) => const_to_uint(val) as Disr + } + } + General(ity, _) => { + match ity { + attr::SignedInt(*) => const_to_int(const_get_elt(ccx, val, [0])) as Disr, + attr::UnsignedInt(*) => const_to_uint(const_get_elt(ccx, val, [0])) as Disr + } + } Univariant(*) => 0, - General(*) => const_to_uint(const_get_elt(ccx, val, [0])) as Disr, NullablePointer{ nndiscr, ptrfield, _ } => { if is_null(const_struct_field(ccx, val, ptrfield)) { /* subtraction as uint is ok because nndiscr is either 0 or 1 */ @@ -645,7 +825,3 @@ pub fn is_newtypeish(r: &Repr) -> bool { _ => false } } - -fn C_disr(cx: &CrateContext, i: Disr) -> ValueRef { - return C_integral(cx.int_type, i, false); -} diff --git a/src/librustc/middle/trans/debuginfo.rs b/src/librustc/middle/trans/debuginfo.rs index 646f71ec28ad2..cf97f0bb3da7c 100644 --- a/src/librustc/middle/trans/debuginfo.rs +++ b/src/librustc/middle/trans/debuginfo.rs @@ -67,6 +67,7 @@ use std::hashmap::HashMap; use std::libc::{c_uint, c_ulonglong, c_longlong}; use std::ptr; use std::vec; +use syntax::attr; use syntax::codemap::Span; use syntax::{ast, codemap, ast_util, ast_map, opt_vec}; use syntax::parse::token::special_idents; @@ -1071,13 +1072,7 @@ fn enum_metadata(cx: &mut CrateContext, return composite_type_metadata(cx, Type::nil(), enum_name, [], [], [], span); } - // Prepare some data (llvm type, size, align, ...) about the discriminant. This data will be - // needed in all of the following cases. - let discriminant_llvm_type = Type::enum_discrim(cx); - let (discriminant_size, discriminant_align) = size_and_align_of(cx, discriminant_llvm_type); - - assert!(Type::enum_discrim(cx) == cx.int_type); - let discriminant_type_metadata = type_metadata(cx, ty::mk_int(), span); + let type_rep = adt::represent_type(cx, enum_type); let variants: &[@ty::VariantInfo] = *ty::enum_variants(cx.tcx, enum_def_id); @@ -1101,32 +1096,39 @@ fn enum_metadata(cx: &mut CrateContext, let loc = span_start(cx, span); let file_metadata = file_metadata(cx, loc.file.name); - let discriminant_type_metadata = do enum_name.with_c_str |enum_name| { - unsafe { - llvm::LLVMDIBuilderCreateEnumerationType( - DIB(cx), - file_metadata, - enum_name, - file_metadata, - loc.line as c_uint, - bytes_to_bits(discriminant_size), - bytes_to_bits(discriminant_align), - create_DIArray(DIB(cx), enumerators_metadata), - discriminant_type_metadata) + let discriminant_type_metadata = |inttype| { + let discriminant_llvm_type = adt::ll_inttype(cx, inttype); + let (discriminant_size, discriminant_align) = size_and_align_of(cx, discriminant_llvm_type); + let discriminant_type_metadata = type_metadata(cx, match inttype { + attr::SignedInt(t) => ty::mk_mach_int(t), + attr::UnsignedInt(t) => ty::mk_mach_uint(t) + }, span); + do enum_name.with_c_str |enum_name| { + unsafe { + llvm::LLVMDIBuilderCreateEnumerationType( + DIB(cx), + file_metadata, + enum_name, + file_metadata, + loc.line as c_uint, + bytes_to_bits(discriminant_size), + bytes_to_bits(discriminant_align), + create_DIArray(DIB(cx), enumerators_metadata), + discriminant_type_metadata) + } } }; - let type_rep = adt::represent_type(cx, enum_type); - match *type_rep { - adt::CEnum(*) => { - return discriminant_type_metadata; + adt::CEnum(inttype, _min, _max) => { + return discriminant_type_metadata(inttype); } adt::Univariant(ref struct_def, _) => { assert!(variants.len() == 1); return adt_struct_metadata(cx, struct_def, variants[0], None, span); } - adt::General(ref struct_defs) => { + adt::General(inttype, ref struct_defs) => { + let discriminant_type_metadata = discriminant_type_metadata(inttype); let variants_member_metadata: ~[DIDescriptor] = do struct_defs .iter() .enumerate() diff --git a/src/librustc/middle/trans/expr.rs b/src/librustc/middle/trans/expr.rs index ce6fb6d3e7755..7283a113cc1af 100644 --- a/src/librustc/middle/trans/expr.rs +++ b/src/librustc/middle/trans/expr.rs @@ -1691,7 +1691,7 @@ fn trans_imm_cast(bcx: @mut Block, expr: @ast::Expr, (cast_enum, cast_float) => { let bcx = bcx; let repr = adt::represent_type(ccx, t_in); - let lldiscrim_a = adt::trans_get_discr(bcx, repr, llexpr); + let lldiscrim_a = adt::trans_get_discr(bcx, repr, llexpr, Some(Type::i64())); match k_out { cast_integral => int_cast(bcx, ll_t_out, val_ty(lldiscrim_a), diff --git a/src/librustc/middle/trans/reflect.rs b/src/librustc/middle/trans/reflect.rs index fb46aefbafe3b..eb232aa0e9214 100644 --- a/src/librustc/middle/trans/reflect.rs +++ b/src/librustc/middle/trans/reflect.rs @@ -25,7 +25,7 @@ use middle::ty; use util::ppaux::ty_to_str; use std::libc::c_uint; -use std::option::None; +use std::option::{Some,None}; use std::vec; use syntax::ast::DefId; use syntax::ast; @@ -309,7 +309,7 @@ impl Reflector { }; let mut bcx = fcx.entry_bcx.unwrap(); let arg = BitCast(bcx, arg, llptrty); - let ret = adt::trans_get_discr(bcx, repr, arg); + let ret = adt::trans_get_discr(bcx, repr, arg, Some(ccx.int_type)); Store(bcx, ret, fcx.llretptr.unwrap()); match fcx.llreturn { Some(llreturn) => cleanup_and_Br(bcx, bcx, llreturn), diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 0958eeb7097a6..11c13247eee57 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -38,6 +38,7 @@ use syntax::ast::*; use syntax::ast_util::is_local; use syntax::ast_util; use syntax::attr; +use syntax::attr::AttrMetaMethods; use syntax::codemap::Span; use syntax::codemap; use syntax::parse::token; @@ -711,7 +712,7 @@ pub struct ParamBounds { pub type BuiltinBounds = EnumSet; -#[deriving(Clone, Eq, IterBytes, ToStr)] +#[deriving(Clone, Eq, IterBytes, ToStr)] #[repr(uint)] pub enum BuiltinBound { BoundStatic, BoundSend, @@ -4120,27 +4121,42 @@ pub fn lookup_trait_def(cx: ctxt, did: ast::DefId) -> @ty::TraitDef { } } -/// Determine whether an item is annotated with an attribute -pub fn has_attr(tcx: ctxt, did: DefId, attr: &str) -> bool { +/// Iterate over meta_items of a definition. +// (This should really be an iterator, but that would require csearch and +// decoder to use iterators instead of higher-order functions.) +pub fn each_attr(tcx: ctxt, did: DefId, f: &fn(@MetaItem) -> bool) -> bool { if is_local(did) { match tcx.items.find(&did.node) { - Some( - &ast_map::node_item(@ast::item { - attrs: ref attrs, - _ - }, _)) => attr::contains_name(*attrs, attr), + Some(&ast_map::node_item(@ast::item {attrs: ref attrs, _}, _)) => + attrs.iter().advance(|attr| f(attr.node.value)), _ => tcx.sess.bug(fmt!("has_attr: %? is not an item", did)) } } else { - let mut ret = false; + let mut cont = true; do csearch::get_item_attrs(tcx.cstore, did) |meta_items| { - ret = ret || attr::contains_name(meta_items, attr); + if cont { + cont = meta_items.iter().advance(|ptrptr| f(*ptrptr)); + } } - ret + return cont; } } +/// Determine whether an item is annotated with an attribute +pub fn has_attr(tcx: ctxt, did: DefId, attr: &str) -> bool { + let mut found = false; + each_attr(tcx, did, |item| { + if attr == item.name() { + found = true; + false + } else { + true + } + }); + return found; +} + /// Determine whether an item is annotated with `#[packed]` pub fn lookup_packed(tcx: ctxt, did: DefId) -> bool { has_attr(tcx, did, "packed") @@ -4151,6 +4167,16 @@ pub fn lookup_simd(tcx: ctxt, did: DefId) -> bool { has_attr(tcx, did, "simd") } +// Obtain the the representation annotation for a definition. +pub fn lookup_repr_hint(tcx: ctxt, did: DefId) -> attr::ReprAttr { + let mut acc = attr::ReprAny; + ty::each_attr(tcx, did, |meta| { + acc = attr::find_repr_attr(tcx.sess.diagnostic(), meta, acc); + true + }); + return acc; +} + // Look up a field ID, whether or not it's local // Takes a list of type substs in case the struct is generic pub fn lookup_field_type(tcx: ctxt, diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index 0e335cf3f145d..2d3a5977ae05c 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -122,6 +122,7 @@ use syntax::ast; use syntax::ast_map; use syntax::ast_util::local_def; use syntax::ast_util; +use syntax::attr; use syntax::codemap::Span; use syntax::codemap; use syntax::opt_vec::OptVec; @@ -3160,9 +3161,38 @@ pub fn check_enum_variants(ccx: @mut CrateCtxt, sp: Span, vs: &[ast::variant], id: ast::NodeId) { + + fn disr_in_range(ccx: @mut CrateCtxt, + ty: attr::IntType, + disr: ty::Disr) -> bool { + fn uint_in_range(ccx: @mut CrateCtxt, ty: ast::uint_ty, disr: ty::Disr) -> bool { + match ty { + ast::ty_u8 => disr as u8 as Disr == disr, + ast::ty_u16 => disr as u16 as Disr == disr, + ast::ty_u32 => disr as u32 as Disr == disr, + ast::ty_u64 => disr as u64 as Disr == disr, + ast::ty_u => uint_in_range(ccx, ccx.tcx.sess.targ_cfg.uint_type, disr) + } + } + fn int_in_range(ccx: @mut CrateCtxt, ty: ast::int_ty, disr: ty::Disr) -> bool { + match ty { + ast::ty_i8 => disr as i8 as Disr == disr, + ast::ty_i16 => disr as i16 as Disr == disr, + ast::ty_i32 => disr as i32 as Disr == disr, + ast::ty_i64 => disr as i64 as Disr == disr, + ast::ty_i => int_in_range(ccx, ccx.tcx.sess.targ_cfg.int_type, disr) + } + } + match ty { + attr::UnsignedInt(ty) => uint_in_range(ccx, ty, disr), + attr::SignedInt(ty) => int_in_range(ccx, ty, disr) + } + } + fn do_check(ccx: @mut CrateCtxt, vs: &[ast::variant], - id: ast::NodeId) + id: ast::NodeId, + hint: attr::ReprAttr) -> ~[@ty::VariantInfo] { let rty = ty::node_id_to_type(ccx.tcx, id); @@ -3204,9 +3234,20 @@ pub fn check_enum_variants(ccx: @mut CrateCtxt, None => () }; - // Check for duplicate discriminator values + // Check for duplicate discriminant values if disr_vals.contains(¤t_disr_val) { - ccx.tcx.sess.span_err(v.span, "discriminator value already exists"); + ccx.tcx.sess.span_err(v.span, "discriminant value already exists"); + } + // Check for unrepresentable discriminant values + match hint { + attr::ReprAny | attr::ReprExtern => (), + attr::ReprInt(sp, ity) => { + if !disr_in_range(ccx, ity, current_disr_val) { + ccx.tcx.sess.span_err(v.span, + "discriminant value outside specified type"); + ccx.tcx.sess.span_note(sp, "discriminant type specified here"); + } + } } disr_vals.push(current_disr_val); @@ -3220,8 +3261,13 @@ pub fn check_enum_variants(ccx: @mut CrateCtxt, } let rty = ty::node_id_to_type(ccx.tcx, id); + let hint = ty::lookup_repr_hint(ccx.tcx, ast::DefId { crate: ast::LOCAL_CRATE, node: id }); + if hint != attr::ReprAny && vs.len() <= 1 { + ccx.tcx.sess.span_err(sp, fmt!("unsupported representation for %svariant enum", + if vs.len() == 1 { "uni" } else { "zero-" })) + } - let variants = do_check(ccx, vs, id); + let variants = do_check(ccx, vs, id, hint); // cache so that ty::enum_variants won't repeat this work ccx.tcx.enum_var_cache.insert(local_def(id), @variants); diff --git a/src/libsyntax/attr.rs b/src/libsyntax/attr.rs index fd0887de7224b..28aec5ce605d3 100644 --- a/src/libsyntax/attr.rs +++ b/src/libsyntax/attr.rs @@ -14,7 +14,7 @@ use extra; use ast; use ast::{Attribute, Attribute_, MetaItem, MetaWord, MetaNameValue, MetaList}; -use codemap::{Spanned, spanned, dummy_spanned}; +use codemap::{Span, Spanned, spanned, dummy_spanned}; use codemap::BytePos; use diagnostic::span_handler; use parse::comments::{doc_comment_style, strip_doc_comment_decoration}; @@ -363,3 +363,117 @@ pub fn require_unique_names(diagnostic: @mut span_handler, } } } + + +/** + * Fold this over attributes to parse #[repr(...)] forms. + * + * Valid repr contents: any of the primitive integral type names (see + * `int_type_of_word`, below) to specify the discriminant type; and `C`, to use + * the same discriminant size that the corresponding C enum would. These are + * not allowed on univariant or zero-variant enums, which have no discriminant. + * + * If a discriminant type is so specified, then the discriminant will be + * present (before fields, if any) with that type; reprensentation + * optimizations which would remove it will not be done. + */ +pub fn find_repr_attr(diagnostic: @mut span_handler, attr: @ast::MetaItem, acc: ReprAttr) + -> ReprAttr { + let mut acc = acc; + match attr.node { + ast::MetaList(s, ref items) if "repr" == s => { + for item in items.iter() { + match item.node { + ast::MetaWord(word) => { + let word: &str = word; + let hint = match word { + // Can't use "extern" because it's not a lexical identifier. + "C" => ReprExtern, + _ => match int_type_of_word(word) { + Some(ity) => ReprInt(item.span, ity), + None => { + // Not a word we recognize + diagnostic.span_warn(item.span, + "unrecognized representation hint"); + ReprAny + } + } + }; + if hint != ReprAny { + if acc == ReprAny { + acc = hint; + } else if acc != hint { + diagnostic.span_warn(item.span, + "conflicting representation hint \ + ignored") + } + } + } + // Not a word: + _ => diagnostic.span_warn(item.span, "unrecognized representation hint") + } + } + } + // Not a "repr" hint: ignore. + _ => { } + } + return acc; +} + +fn int_type_of_word(s: &str) -> Option { + match s { + "i8" => Some(SignedInt(ast::ty_i8)), + "u8" => Some(UnsignedInt(ast::ty_u8)), + "i16" => Some(SignedInt(ast::ty_i16)), + "u16" => Some(UnsignedInt(ast::ty_u16)), + "i32" => Some(SignedInt(ast::ty_i32)), + "u32" => Some(UnsignedInt(ast::ty_u32)), + "i64" => Some(SignedInt(ast::ty_i64)), + "u64" => Some(UnsignedInt(ast::ty_u64)), + "int" => Some(SignedInt(ast::ty_i)), + "uint" => Some(UnsignedInt(ast::ty_u)), + _ => None + } +} + +#[deriving(Eq)] +pub enum ReprAttr { + ReprAny, + ReprInt(Span, IntType), + ReprExtern +} + +impl ReprAttr { + pub fn is_ffi_safe(&self) -> bool { + match *self { + ReprAny => false, + ReprInt(_sp, ity) => ity.is_ffi_safe(), + ReprExtern => true + } + } +} + +#[deriving(Eq)] +pub enum IntType { + SignedInt(ast::int_ty), + UnsignedInt(ast::uint_ty) +} + +impl IntType { + #[inline] + pub fn is_signed(self) -> bool { + match self { + SignedInt(*) => true, + UnsignedInt(*) => false + } + } + fn is_ffi_safe(self) -> bool { + match self { + SignedInt(ast::ty_i8) | UnsignedInt(ast::ty_u8) | + SignedInt(ast::ty_i16) | UnsignedInt(ast::ty_u16) | + SignedInt(ast::ty_i32) | UnsignedInt(ast::ty_u32) | + SignedInt(ast::ty_i64) | UnsignedInt(ast::ty_u64) => true, + _ => false + } + } +} diff --git a/src/test/compile-fail/tag-variant-disr-dup.rs b/src/test/compile-fail/tag-variant-disr-dup.rs index 216779fac7c46..d0608ec4c1927 100644 --- a/src/test/compile-fail/tag-variant-disr-dup.rs +++ b/src/test/compile-fail/tag-variant-disr-dup.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//error-pattern:discriminator value already exists +//error-pattern:discriminant value already exists // black and white have the same discriminator value ... @@ -20,4 +20,4 @@ enum color { white = 0x000000, } -fn main() { } \ No newline at end of file +fn main() { } diff --git a/src/test/debug-info/method-on-enum.rs b/src/test/debug-info/method-on-enum.rs index c68b4e7deedf4..45b82984847d4 100644 --- a/src/test/debug-info/method-on-enum.rs +++ b/src/test/debug-info/method-on-enum.rs @@ -15,7 +15,7 @@ // STACK BY REF // debugger:finish // debugger:print *self -// check:$1 = {{Variant2, x = 1799, y = 1799}, {Variant2, 117901063}} +// check:$1 = {{Variant2, [...]}, {Variant2, 117901063}} // debugger:print arg1 // check:$2 = -1 // debugger:print arg2 @@ -25,7 +25,7 @@ // STACK BY VAL // debugger:finish // d ebugger:print self -- ignored for now because of issue #8512 -// c heck:$X = {{Variant2, x = 1799, y = 1799}, {Variant2, 117901063}} +// c heck:$X = {{Variant2, [...]}, {Variant2, 117901063}} // debugger:print arg1 // check:$4 = -3 // debugger:print arg2 @@ -35,7 +35,7 @@ // OWNED BY REF // debugger:finish // debugger:print *self -// check:$6 = {{Variant1, x = 1799, y = 1799}, {Variant1, 117901063}} +// check:$6 = {{Variant1, x = 1799, y = 1799}, {Variant1, [...]}} // debugger:print arg1 // check:$7 = -5 // debugger:print arg2 @@ -45,7 +45,7 @@ // OWNED BY VAL // debugger:finish // d ebugger:print self -- ignored for now because of issue #8512 -// c heck:$X = {{Variant1, x = 1799, y = 1799}, {Variant1, 117901063}} +// c heck:$X = {{Variant1, x = 1799, y = 1799}, {Variant1, [...]}} // debugger:print arg1 // check:$9 = -7 // debugger:print arg2 @@ -55,7 +55,7 @@ // OWNED MOVED // debugger:finish // debugger:print *self -// check:$11 = {{Variant1, x = 1799, y = 1799}, {Variant1, 117901063}} +// check:$11 = {{Variant1, x = 1799, y = 1799}, {Variant1, [...]}} // debugger:print arg1 // check:$12 = -9 // debugger:print arg2 @@ -65,7 +65,7 @@ // MANAGED BY REF // debugger:finish // debugger:print *self -// check:$14 = {{Variant2, x = 1799, y = 1799}, {Variant2, 117901063}} +// check:$14 = {{Variant2, [...]}, {Variant2, 117901063}} // debugger:print arg1 // check:$15 = -11 // debugger:print arg2 @@ -75,7 +75,7 @@ // MANAGED BY VAL // debugger:finish // d ebugger:print self -- ignored for now because of issue #8512 -// c heck:$X = {{Variant2, x = 1799, y = 1799}, {Variant2, 117901063}} +// c heck:$X = {{Variant2, [...]}, {Variant2, 117901063}} // debugger:print arg1 // check:$17 = -13 // debugger:print arg2 @@ -85,7 +85,7 @@ // MANAGED SELF // debugger:finish // debugger:print self->val -// check:$19 = {{Variant2, x = 1799, y = 1799}, {Variant2, 117901063}} +// check:$19 = {{Variant2, [...]}, {Variant2, 117901063}} // debugger:print arg1 // check:$20 = -15 // debugger:print arg2 diff --git a/src/test/run-pass/issue-2718.rs b/src/test/run-pass/issue-2718.rs index edfae096407b6..048a79b29aae6 100644 --- a/src/test/run-pass/issue-2718.rs +++ b/src/test/run-pass/issue-2718.rs @@ -26,7 +26,7 @@ pub mod pipes { payload: Option } - #[deriving(Eq)] + #[deriving(Eq)] #[repr(int)] pub enum state { empty, full, diff --git a/src/test/run-pass/small-enum-range-edge.rs b/src/test/run-pass/small-enum-range-edge.rs new file mode 100644 index 0000000000000..a9fc13437abb4 --- /dev/null +++ b/src/test/run-pass/small-enum-range-edge.rs @@ -0,0 +1,32 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! + * Tests the range assertion wraparound case in trans::middle::adt::load_discr. + */ + +#[repr(u8)] +enum Eu { Lu = 0, Hu = 255 } +static CLu: Eu = Lu; +static CHu: Eu = Hu; + +#[repr(i8)] +enum Es { Ls = -128, Hs = 127 } +static CLs: Es = Ls; +static CHs: Es = Hs; + +pub fn main() { + assert_eq!((Hu as u8) + 1, Lu as u8); + assert_eq!((Hs as i8) + 1, Ls as i8); + assert_eq!(CLu as u8, Lu as u8); + assert_eq!(CHu as u8, Hu as u8); + assert_eq!(CLs as i8, Ls as i8); + assert_eq!(CHs as i8, Hs as i8); +}