cranelift: Add narrower and wider constraints to the instruction DSL (#6013)

* Add narrower and wider constraints to the instruction DSL

* Add docs to narrower/wider operands

* Update cranelift/codegen/meta/src/cdsl/instructions.rs

Co-authored-by: Jamey Sharp <jamey@minilop.net>

* Fix assertion message

* Simplify upper bounds for the wider constraint

* Remove additional unnecessary cases in the verifier

* Remove unused variables

* Remove changes to is_ctrl_typevar_candidate

These changes were only necessary when the type returned by an
instruction was a variable constrained by narrow or widen. As we have
switched to requiring that constraints must appear on argument types and
not return types, these changes were not longer necessary.

---------

Co-authored-by: Jamey Sharp <jamey@minilop.net>
This commit is contained in:
Trevor Elliott
2023-03-14 09:34:17 -07:00
committed by GitHub
parent 5c1b468648
commit f5ad74e546
6 changed files with 145 additions and 111 deletions

View File

@@ -202,6 +202,32 @@ impl TypeVar {
"can't halve a scalar type" "can't halve a scalar type"
); );
} }
DerivedFunc::Narrower => {
assert_eq!(
*ts.lanes.iter().max().unwrap(),
1,
"The `narrower` constraint does not apply to vectors"
);
assert!(
(!ts.ints.is_empty() || !ts.floats.is_empty())
&& ts.refs.is_empty()
&& ts.dynamic_lanes.is_empty(),
"The `narrower` constraint only applies to scalar ints or floats"
);
}
DerivedFunc::Wider => {
assert_eq!(
*ts.lanes.iter().max().unwrap(),
1,
"The `wider` constraint does not apply to vectors"
);
assert!(
(!ts.ints.is_empty() || !ts.floats.is_empty())
&& ts.refs.is_empty()
&& ts.dynamic_lanes.is_empty(),
"The `wider` constraint only applies to scalar ints or floats"
);
}
DerivedFunc::LaneOf | DerivedFunc::AsBool | DerivedFunc::DynamicToVector => { DerivedFunc::LaneOf | DerivedFunc::AsBool | DerivedFunc::DynamicToVector => {
/* no particular assertions */ /* no particular assertions */
} }
@@ -241,6 +267,16 @@ impl TypeVar {
pub fn dynamic_to_vector(&self) -> TypeVar { pub fn dynamic_to_vector(&self) -> TypeVar {
self.derived(DerivedFunc::DynamicToVector) self.derived(DerivedFunc::DynamicToVector)
} }
/// Make a new [TypeVar] that includes all types narrower than self.
pub fn narrower(&self) -> TypeVar {
self.derived(DerivedFunc::Narrower)
}
/// Make a new [TypeVar] that includes all types wider than self.
pub fn wider(&self) -> TypeVar {
self.derived(DerivedFunc::Wider)
}
} }
impl Into<TypeVar> for &TypeVar { impl Into<TypeVar> for &TypeVar {
@@ -302,6 +338,8 @@ pub(crate) enum DerivedFunc {
SplitLanes, SplitLanes,
MergeLanes, MergeLanes,
DynamicToVector, DynamicToVector,
Narrower,
Wider,
} }
impl DerivedFunc { impl DerivedFunc {
@@ -314,6 +352,8 @@ impl DerivedFunc {
DerivedFunc::SplitLanes => "split_lanes", DerivedFunc::SplitLanes => "split_lanes",
DerivedFunc::MergeLanes => "merge_lanes", DerivedFunc::MergeLanes => "merge_lanes",
DerivedFunc::DynamicToVector => "dynamic_to_vector", DerivedFunc::DynamicToVector => "dynamic_to_vector",
DerivedFunc::Narrower => "narrower",
DerivedFunc::Wider => "wider",
} }
} }
} }
@@ -391,6 +431,8 @@ impl TypeSet {
DerivedFunc::SplitLanes => self.half_width().double_vector(), DerivedFunc::SplitLanes => self.half_width().double_vector(),
DerivedFunc::MergeLanes => self.double_width().half_vector(), DerivedFunc::MergeLanes => self.double_width().half_vector(),
DerivedFunc::DynamicToVector => self.dynamic_to_vector(), DerivedFunc::DynamicToVector => self.dynamic_to_vector(),
DerivedFunc::Narrower => self.clone(),
DerivedFunc::Wider => self.clone(),
} }
} }

View File

@@ -3064,12 +3064,6 @@ pub(crate) fn define(
TypeSetBuilder::new().ints(Interval::All).build(), TypeSetBuilder::new().ints(Interval::All).build(),
); );
let IntTo = &TypeVar::new(
"IntTo",
"A smaller integer type",
TypeSetBuilder::new().ints(Interval::All).build(),
);
ig.push( ig.push(
Inst::new( Inst::new(
"ireduce", "ireduce",
@@ -3081,8 +3075,9 @@ pub(crate) fn define(
"#, "#,
&formats.unary, &formats.unary,
) )
.operands_in(vec![Operand::new("x", Int)]) .operands_in(vec![Operand::new("x", &Int.wider())
.operands_out(vec![Operand::new("a", IntTo)]), .with_doc("A scalar integer type, wider than the controlling type")])
.operands_out(vec![Operand::new("a", Int)]),
); );
let I16or32or64xN = &TypeVar::new( let I16or32or64xN = &TypeVar::new(
@@ -3272,13 +3267,6 @@ pub(crate) fn define(
.operands_out(vec![Operand::new("a", I16x8)]), .operands_out(vec![Operand::new("a", I16x8)]),
); );
{
let IntTo = &TypeVar::new(
"IntTo",
"A larger integer type with the same number of lanes",
TypeSetBuilder::new().ints(Interval::All).build(),
);
ig.push( ig.push(
Inst::new( Inst::new(
"uextend", "uextend",
@@ -3295,8 +3283,10 @@ pub(crate) fn define(
"#, "#,
&formats.unary, &formats.unary,
) )
.operands_in(vec![Operand::new("x", Int)]) .operands_in(vec![Operand::new("x", &Int.narrower()).with_doc(
.operands_out(vec![Operand::new("a", IntTo)]), "A scalar integer type, narrower than the controlling type",
)])
.operands_out(vec![Operand::new("a", Int)]),
); );
ig.push( ig.push(
@@ -3315,10 +3305,11 @@ pub(crate) fn define(
"#, "#,
&formats.unary, &formats.unary,
) )
.operands_in(vec![Operand::new("x", Int)]) .operands_in(vec![Operand::new("x", &Int.narrower()).with_doc(
.operands_out(vec![Operand::new("a", IntTo)]), "A scalar integer type, narrower than the controlling type",
)])
.operands_out(vec![Operand::new("a", Int)]),
); );
}
let FloatScalar = &TypeVar::new( let FloatScalar = &TypeVar::new(
"FloatScalar", "FloatScalar",
@@ -3326,12 +3317,6 @@ pub(crate) fn define(
TypeSetBuilder::new().floats(Interval::All).build(), TypeSetBuilder::new().floats(Interval::All).build(),
); );
let FloatScalarTo = &TypeVar::new(
"FloatScalarTo",
"A scalar only floating point number",
TypeSetBuilder::new().floats(Interval::All).build(),
);
ig.push( ig.push(
Inst::new( Inst::new(
"fpromote", "fpromote",
@@ -3349,8 +3334,10 @@ pub(crate) fn define(
"#, "#,
&formats.unary, &formats.unary,
) )
.operands_in(vec![Operand::new("x", FloatScalar)]) .operands_in(vec![Operand::new("x", &FloatScalar.narrower()).with_doc(
.operands_out(vec![Operand::new("a", FloatScalarTo)]), "A scalar only floating point number, narrower than the controlling type",
)])
.operands_out(vec![Operand::new("a", FloatScalar)]),
); );
ig.push( ig.push(
@@ -3370,8 +3357,10 @@ pub(crate) fn define(
"#, "#,
&formats.unary, &formats.unary,
) )
.operands_in(vec![Operand::new("x", FloatScalar)]) .operands_in(vec![Operand::new("x", &FloatScalar.wider()).with_doc(
.operands_out(vec![Operand::new("a", FloatScalarTo)]), "A scalar only floating point number, wider than the controlling type",
)])
.operands_out(vec![Operand::new("a", FloatScalar)]),
); );
let F64x2 = &TypeVar::new( let F64x2 = &TypeVar::new(

View File

@@ -11,7 +11,7 @@ use core::mem::size_of;
use core::ops::{Add, BitOr, Shl, Sub}; use core::ops::{Add, BitOr, Shl, Sub};
/// A small bitset built on a single primitive integer type /// A small bitset built on a single primitive integer type
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BitSet<T>(pub T); pub struct BitSet<T>(pub T);

View File

@@ -575,12 +575,9 @@ impl OpcodeConstraints {
/// `ctrl_type`. /// `ctrl_type`.
pub fn result_type(self, n: usize, ctrl_type: Type) -> Type { pub fn result_type(self, n: usize, ctrl_type: Type) -> Type {
debug_assert!(n < self.num_fixed_results(), "Invalid result index"); debug_assert!(n < self.num_fixed_results(), "Invalid result index");
if let ResolvedConstraint::Bound(t) = match OPERAND_CONSTRAINTS[self.constraint_offset() + n].resolve(ctrl_type) {
OPERAND_CONSTRAINTS[self.constraint_offset() + n].resolve(ctrl_type) ResolvedConstraint::Bound(t) => t,
{ ResolvedConstraint::Free(ts) => panic!("Result constraints can't be free: {:?}", ts),
t
} else {
panic!("Result constraints can't be free");
} }
} }
@@ -614,7 +611,7 @@ type BitSet8 = BitSet<u8>;
type BitSet16 = BitSet<u16>; type BitSet16 = BitSet<u16>;
/// A value type set describes the permitted set of types for a type variable. /// A value type set describes the permitted set of types for a type variable.
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ValueTypeSet { pub struct ValueTypeSet {
/// Allowed lane sizes /// Allowed lane sizes
pub lanes: BitSet16, pub lanes: BitSet16,
@@ -703,6 +700,12 @@ enum OperandConstraint {
/// This operands is `ctrlType.dynamic_to_vector()`. /// This operands is `ctrlType.dynamic_to_vector()`.
DynamicToVector, DynamicToVector,
/// This operand is `ctrlType.narrower()`.
Narrower,
/// This operand is `ctrlType.wider()`.
Wider,
} }
impl OperandConstraint { impl OperandConstraint {
@@ -766,6 +769,47 @@ impl OperandConstraint {
.dynamic_to_vector() .dynamic_to_vector()
.expect("invalid type for dynamic_to_vector"), .expect("invalid type for dynamic_to_vector"),
), ),
Narrower => {
let ctrl_type_bits = ctrl_type.log2_lane_bits();
let mut tys = ValueTypeSet::default();
// We're testing scalar values, only.
tys.lanes = BitSet::from_range(0, 1);
if ctrl_type.is_int() {
// The upper bound in from_range is exclusive, so add one here get the closed
// interval of [I8, ctrl_type].
tys.ints = BitSet::from_range(3, ctrl_type_bits as u8 + 1);
} else if ctrl_type.is_float() {
// The upper bound in from_range is exclusive, so add one here get the closed
// interval of [F32, ctrl_type].
tys.floats = BitSet::from_range(5, ctrl_type_bits as u8 + 1);
} else {
panic!("The Narrower constraint only operates on floats or ints");
}
ResolvedConstraint::Free(tys)
}
Wider => {
let ctrl_type_bits = ctrl_type.log2_lane_bits();
let mut tys = ValueTypeSet::default();
// We're testing scalar values, only.
tys.lanes = BitSet::from_range(0, 1);
if ctrl_type.is_int() {
// The upper bound should include all types wider than `ctrl_type`, so we use
// `2^8` as the upper bound to define the closed range `[ctrl_type, I128]`.
tys.ints = BitSet::from_range(ctrl_type_bits as u8, 8);
} else if ctrl_type.is_float() {
// The upper bound should include all float types wider than `ctrl_type`, so we
// use `2^7` as the upper bound to define the closed range `[ctrl_type, F64]`.
tys.floats = BitSet::from_range(ctrl_type_bits as u8, 7);
} else {
panic!("The Wider constraint only operates on floats or ints");
}
ResolvedConstraint::Free(tys)
}
} }
} }
} }

View File

@@ -1191,7 +1191,7 @@ impl<'a> Verifier<'a> {
let _ = self.typecheck_fixed_args(inst, ctrl_type, errors); let _ = self.typecheck_fixed_args(inst, ctrl_type, errors);
let _ = self.typecheck_variable_args(inst, errors); let _ = self.typecheck_variable_args(inst, errors);
let _ = self.typecheck_return(inst, errors); let _ = self.typecheck_return(inst, errors);
let _ = self.typecheck_special(inst, ctrl_type, errors); let _ = self.typecheck_special(inst, errors);
Ok(()) Ok(())
} }
@@ -1478,63 +1478,8 @@ impl<'a> Verifier<'a> {
// Check special-purpose type constraints that can't be expressed in the normal opcode // Check special-purpose type constraints that can't be expressed in the normal opcode
// constraints. // constraints.
fn typecheck_special( fn typecheck_special(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
&self,
inst: Inst,
ctrl_type: Type,
errors: &mut VerifierErrors,
) -> VerifierStepResult<()> {
match self.func.dfg.insts[inst] { match self.func.dfg.insts[inst] {
ir::InstructionData::Unary { opcode, arg } => {
let arg_type = self.func.dfg.value_type(arg);
match opcode {
Opcode::Uextend | Opcode::Sextend | Opcode::Fpromote => {
if arg_type.lane_count() != ctrl_type.lane_count() {
return errors.nonfatal((
inst,
self.context(inst),
format!(
"input {} and output {} must have same number of lanes",
arg_type, ctrl_type,
),
));
}
if arg_type.lane_bits() >= ctrl_type.lane_bits() {
return errors.nonfatal((
inst,
self.context(inst),
format!(
"input {} must be smaller than output {}",
arg_type, ctrl_type,
),
));
}
}
Opcode::Ireduce | Opcode::Fdemote => {
if arg_type.lane_count() != ctrl_type.lane_count() {
return errors.nonfatal((
inst,
self.context(inst),
format!(
"input {} and output {} must have same number of lanes",
arg_type, ctrl_type,
),
));
}
if arg_type.lane_bits() <= ctrl_type.lane_bits() {
return errors.nonfatal((
inst,
self.context(inst),
format!(
"input {} must be larger than output {}",
arg_type, ctrl_type,
),
));
}
}
_ => {}
}
}
ir::InstructionData::TableAddr { table, arg, .. } => { ir::InstructionData::TableAddr { table, arg, .. } => {
let index_type = self.func.dfg.value_type(arg); let index_type = self.func.dfg.value_type(arg);
let table_index_type = self.func.tables[table].index_type; let table_index_type = self.func.tables[table].index_type;

View File

@@ -113,14 +113,28 @@ block2(v3: f32, v4: i8):
function %bad_extend() { function %bad_extend() {
block0: block0:
v0 = iconst.i32 10 v0 = iconst.i32 10
v1 = uextend.i16 v0 ; error: input i32 must be smaller than output i16 v1 = uextend.i16 v0 ; error: arg 0 (v0) with type i32 failed to satisfy type set
return return
} }
function %bad_reduce() { function %bad_reduce() {
block0: block0:
v0 = iconst.i32 10 v0 = iconst.i32 10
v1 = ireduce.i64 v0 ; error: input i32 must be larger than output i64 v1 = ireduce.i64 v0 ; error: arg 0 (v0) with type i32 failed to satisfy type set
return
}
function %bad_fdemote() {
block0:
v0 = f32const 0xf.f
v1 = fdemote.f64 v0 ; error: arg 0 (v0) with type f32 failed to satisfy type set
return
}
function %bad_fpromote() {
block0:
v0 = f64const 0xf.f
v1 = fpromote.f32 v0 ; error: arg 0 (v0) with type f64 failed to satisfy type set
return return
} }