215 lines
9.0 KiB
Rust
215 lines
9.0 KiB
Rust
//! Handling of enum discriminants
|
|
//!
|
|
//! Adapted from <https://github.com/rust-lang/rust/blob/31c0645b9d2539f47eecb096142474b29dc542f7/compiler/rustc_codegen_ssa/src/mir/place.rs>
|
|
//! (<https://github.com/rust-lang/rust/pull/104535>)
|
|
|
|
use rustc_target::abi::{Int, TagEncoding, Variants};
|
|
|
|
use crate::prelude::*;
|
|
|
|
pub(crate) fn codegen_set_discriminant<'tcx>(
|
|
fx: &mut FunctionCx<'_, '_, 'tcx>,
|
|
place: CPlace<'tcx>,
|
|
variant_index: VariantIdx,
|
|
) {
|
|
let layout = place.layout();
|
|
if layout.for_variant(fx, variant_index).abi.is_uninhabited() {
|
|
return;
|
|
}
|
|
match layout.variants {
|
|
Variants::Single { index } => {
|
|
assert_eq!(index, variant_index);
|
|
}
|
|
Variants::Multiple {
|
|
tag: _,
|
|
tag_field,
|
|
tag_encoding: TagEncoding::Direct,
|
|
variants: _,
|
|
} => {
|
|
let ptr = place.place_field(fx, FieldIdx::new(tag_field));
|
|
let to = layout.ty.discriminant_for_variant(fx.tcx, variant_index).unwrap().val;
|
|
let to = match ptr.layout().ty.kind() {
|
|
ty::Uint(UintTy::U128) | ty::Int(IntTy::I128) => {
|
|
let lsb = fx.bcx.ins().iconst(types::I64, to as u64 as i64);
|
|
let msb = fx.bcx.ins().iconst(types::I64, (to >> 64) as u64 as i64);
|
|
fx.bcx.ins().iconcat(lsb, msb)
|
|
}
|
|
ty::Uint(_) | ty::Int(_) => {
|
|
let clif_ty = fx.clif_type(ptr.layout().ty).unwrap();
|
|
let raw_val = ptr.layout().size.truncate(to);
|
|
fx.bcx.ins().iconst(clif_ty, raw_val as i64)
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
let discr = CValue::by_val(to, ptr.layout());
|
|
ptr.write_cvalue(fx, discr);
|
|
}
|
|
Variants::Multiple {
|
|
tag: _,
|
|
tag_field,
|
|
tag_encoding: TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start },
|
|
variants: _,
|
|
} => {
|
|
if variant_index != untagged_variant {
|
|
let niche = place.place_field(fx, FieldIdx::new(tag_field));
|
|
let niche_type = fx.clif_type(niche.layout().ty).unwrap();
|
|
let niche_value = variant_index.as_u32() - niche_variants.start().as_u32();
|
|
let niche_value = (niche_value as u128).wrapping_add(niche_start);
|
|
let niche_value = match niche_type {
|
|
types::I128 => {
|
|
let lsb = fx.bcx.ins().iconst(types::I64, niche_value as u64 as i64);
|
|
let msb =
|
|
fx.bcx.ins().iconst(types::I64, (niche_value >> 64) as u64 as i64);
|
|
fx.bcx.ins().iconcat(lsb, msb)
|
|
}
|
|
ty => fx.bcx.ins().iconst(ty, niche_value as i64),
|
|
};
|
|
let niche_llval = CValue::by_val(niche_value, niche.layout());
|
|
niche.write_cvalue(fx, niche_llval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn codegen_get_discriminant<'tcx>(
|
|
fx: &mut FunctionCx<'_, '_, 'tcx>,
|
|
dest: CPlace<'tcx>,
|
|
value: CValue<'tcx>,
|
|
dest_layout: TyAndLayout<'tcx>,
|
|
) {
|
|
let layout = value.layout();
|
|
|
|
if layout.abi.is_uninhabited() {
|
|
return;
|
|
}
|
|
|
|
let (tag_scalar, tag_field, tag_encoding) = match &layout.variants {
|
|
Variants::Single { index } => {
|
|
let discr_val = layout
|
|
.ty
|
|
.discriminant_for_variant(fx.tcx, *index)
|
|
.map_or(u128::from(index.as_u32()), |discr| discr.val);
|
|
|
|
let val = match dest_layout.ty.kind() {
|
|
ty::Uint(UintTy::U128) | ty::Int(IntTy::I128) => {
|
|
let lsb = fx.bcx.ins().iconst(types::I64, discr_val as u64 as i64);
|
|
let msb = fx.bcx.ins().iconst(types::I64, (discr_val >> 64) as u64 as i64);
|
|
fx.bcx.ins().iconcat(lsb, msb)
|
|
}
|
|
ty::Uint(_) | ty::Int(_) => {
|
|
let clif_ty = fx.clif_type(dest_layout.ty).unwrap();
|
|
let raw_val = dest_layout.size.truncate(discr_val);
|
|
fx.bcx.ins().iconst(clif_ty, raw_val as i64)
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
let res = CValue::by_val(val, dest_layout);
|
|
dest.write_cvalue(fx, res);
|
|
return;
|
|
}
|
|
Variants::Multiple { tag, tag_field, tag_encoding, variants: _ } => {
|
|
(tag, *tag_field, tag_encoding)
|
|
}
|
|
};
|
|
|
|
let cast_to = fx.clif_type(dest_layout.ty).unwrap();
|
|
|
|
// Read the tag/niche-encoded discriminant from memory.
|
|
let tag = value.value_field(fx, FieldIdx::new(tag_field));
|
|
let tag = tag.load_scalar(fx);
|
|
|
|
// Decode the discriminant (specifically if it's niche-encoded).
|
|
match *tag_encoding {
|
|
TagEncoding::Direct => {
|
|
let signed = match tag_scalar.primitive() {
|
|
Int(_, signed) => signed,
|
|
_ => false,
|
|
};
|
|
let val = clif_intcast(fx, tag, cast_to, signed);
|
|
let res = CValue::by_val(val, dest_layout);
|
|
dest.write_cvalue(fx, res);
|
|
}
|
|
TagEncoding::Niche { untagged_variant, ref niche_variants, niche_start } => {
|
|
let relative_max = niche_variants.end().as_u32() - niche_variants.start().as_u32();
|
|
|
|
// We have a subrange `niche_start..=niche_end` inside `range`.
|
|
// If the value of the tag is inside this subrange, it's a
|
|
// "niche value", an increment of the discriminant. Otherwise it
|
|
// indicates the untagged variant.
|
|
// A general algorithm to extract the discriminant from the tag
|
|
// is:
|
|
// relative_tag = tag - niche_start
|
|
// is_niche = relative_tag <= (ule) relative_max
|
|
// discr = if is_niche {
|
|
// cast(relative_tag) + niche_variants.start()
|
|
// } else {
|
|
// untagged_variant
|
|
// }
|
|
// However, we will likely be able to emit simpler code.
|
|
|
|
let (is_niche, tagged_discr, delta) = if relative_max == 0 {
|
|
// Best case scenario: only one tagged variant. This will
|
|
// likely become just a comparison and a jump.
|
|
// The algorithm is:
|
|
// is_niche = tag == niche_start
|
|
// discr = if is_niche {
|
|
// niche_start
|
|
// } else {
|
|
// untagged_variant
|
|
// }
|
|
let is_niche = codegen_icmp_imm(fx, IntCC::Equal, tag, niche_start as i128);
|
|
let tagged_discr =
|
|
fx.bcx.ins().iconst(cast_to, niche_variants.start().as_u32() as i64);
|
|
(is_niche, tagged_discr, 0)
|
|
} else {
|
|
// The special cases don't apply, so we'll have to go with
|
|
// the general algorithm.
|
|
let niche_start = match fx.bcx.func.dfg.value_type(tag) {
|
|
types::I128 => {
|
|
let lsb = fx.bcx.ins().iconst(types::I64, niche_start as u64 as i64);
|
|
let msb =
|
|
fx.bcx.ins().iconst(types::I64, (niche_start >> 64) as u64 as i64);
|
|
fx.bcx.ins().iconcat(lsb, msb)
|
|
}
|
|
ty => fx.bcx.ins().iconst(ty, niche_start as i64),
|
|
};
|
|
let relative_discr = fx.bcx.ins().isub(tag, niche_start);
|
|
let cast_tag = clif_intcast(fx, relative_discr, cast_to, false);
|
|
let is_niche = crate::common::codegen_icmp_imm(
|
|
fx,
|
|
IntCC::UnsignedLessThanOrEqual,
|
|
relative_discr,
|
|
i128::from(relative_max),
|
|
);
|
|
(is_niche, cast_tag, niche_variants.start().as_u32() as u128)
|
|
};
|
|
|
|
let tagged_discr = if delta == 0 {
|
|
tagged_discr
|
|
} else {
|
|
let delta = match cast_to {
|
|
types::I128 => {
|
|
let lsb = fx.bcx.ins().iconst(types::I64, delta as u64 as i64);
|
|
let msb = fx.bcx.ins().iconst(types::I64, (delta >> 64) as u64 as i64);
|
|
fx.bcx.ins().iconcat(lsb, msb)
|
|
}
|
|
ty => fx.bcx.ins().iconst(ty, delta as i64),
|
|
};
|
|
fx.bcx.ins().iadd(tagged_discr, delta)
|
|
};
|
|
|
|
let untagged_variant = if cast_to == types::I128 {
|
|
let zero = fx.bcx.ins().iconst(types::I64, 0);
|
|
let untagged_variant =
|
|
fx.bcx.ins().iconst(types::I64, i64::from(untagged_variant.as_u32()));
|
|
fx.bcx.ins().iconcat(untagged_variant, zero)
|
|
} else {
|
|
fx.bcx.ins().iconst(cast_to, i64::from(untagged_variant.as_u32()))
|
|
};
|
|
let discr = fx.bcx.ins().select(is_niche, tagged_discr, untagged_variant);
|
|
let res = CValue::by_val(discr, dest_layout);
|
|
dest.write_cvalue(fx, res);
|
|
}
|
|
}
|
|
}
|