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,17 +3267,10 @@ pub(crate) fn define(
.operands_out(vec![Operand::new("a", I16x8)]), .operands_out(vec![Operand::new("a", I16x8)]),
); );
{ ig.push(
let IntTo = &TypeVar::new( Inst::new(
"IntTo", "uextend",
"A larger integer type with the same number of lanes", r#"
TypeSetBuilder::new().ints(Interval::All).build(),
);
ig.push(
Inst::new(
"uextend",
r#"
Convert `x` to a larger integer type by zero-extending. Convert `x` to a larger integer type by zero-extending.
Each lane in `x` is converted to a larger integer type by adding Each lane in `x` is converted to a larger integer type by adding
@@ -3293,16 +3281,18 @@ pub(crate) fn define(
and each lane must not have fewer bits that the input lanes. If the and each lane must not have fewer bits that the input lanes. If the
input and output types are the same, this is a no-op. input and output types are the same, this is a no-op.
"#, "#,
&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(
Inst::new( Inst::new(
"sextend", "sextend",
r#" r#"
Convert `x` to a larger integer type by sign-extending. Convert `x` to a larger integer type by sign-extending.
Each lane in `x` is converted to a larger integer type by replicating Each lane in `x` is converted to a larger integer type by replicating
@@ -3313,12 +3303,13 @@ pub(crate) fn define(
and each lane must not have fewer bits that the input lanes. If the and each lane must not have fewer bits that the input lanes. If the
input and output types are the same, this is a no-op. input and output types are the same, this is a no-op.
"#, "#,
&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
} }