Declare constants in the function preamble

This allows us to give names to constants in the constant pool and then use these names in the function body. The original behavior, specifiying the constant value as an instruction immediate, is still supported as a shortcut but some filetests had to change since the canonical way of printing the CLIF constants is now in the preamble.
This commit is contained in:
Andrew Brown
2020-03-20 14:08:03 -07:00
parent 7d88384c0f
commit 0672d1dc0f
14 changed files with 255 additions and 74 deletions

View File

@@ -58,6 +58,11 @@ impl ConstantData {
self.0.len() self.0.len()
} }
/// Check if the constant contains any bytes.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
/// Convert the data to a vector. /// Convert the data to a vector.
pub fn into_vec(self) -> Vec<u8> { pub fn into_vec(self) -> Vec<u8> {
self.0 self.0
@@ -92,23 +97,19 @@ impl ConstantData {
impl fmt::Display for ConstantData { impl fmt::Display for ConstantData {
/// Print the constant data in hexadecimal format, e.g. 0x000102030405060708090a0b0c0d0e0f. /// Print the constant data in hexadecimal format, e.g. 0x000102030405060708090a0b0c0d0e0f.
/// This function will flip the stored order of bytes--little-endian--to the more readable /// This function will flip the stored order of bytes--little-endian--to the more readable
/// big-endian ordering. Any zero bytes in high-order bytes will be discarded in the formatted /// big-endian ordering.
/// string.
/// ///
/// ``` /// ```
/// use cranelift_codegen::ir::ConstantData; /// use cranelift_codegen::ir::ConstantData;
/// let data = ConstantData::from([3, 2, 1, 0, 0].as_ref()); // note the little-endian order /// let data = ConstantData::from([3, 2, 1, 0, 0].as_ref()); // note the little-endian order
/// assert_eq!(data.to_string(), "0x010203"); /// assert_eq!(data.to_string(), "0x0000010203");
/// ``` /// ```
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if !self.is_empty() {
write!(f, "0x")?; write!(f, "0x")?;
let mut bytes_written = 0; for b in self.0.iter().rev() {
for b in self.0.iter().rev().skip_while(|&&b| b == 0) {
write!(f, "{:02x}", b)?; write!(f, "{:02x}", b)?;
bytes_written += 1;
} }
if bytes_written < 1 {
write!(f, "00")?;
} }
Ok(()) Ok(())
} }
@@ -224,10 +225,7 @@ impl ConstantPool {
*self.values_to_handles.get(&constant_value).unwrap() *self.values_to_handles.get(&constant_value).unwrap()
} else { } else {
let constant_handle = Constant::new(self.len()); let constant_handle = Constant::new(self.len());
self.values_to_handles self.set(constant_handle, constant_value);
.insert(constant_value.clone(), constant_handle);
self.handles_to_values
.insert(constant_handle, ConstantPoolEntry::new(constant_value));
constant_handle constant_handle
} }
} }
@@ -238,6 +236,25 @@ impl ConstantPool {
&self.handles_to_values.get(&constant_handle).unwrap().data &self.handles_to_values.get(&constant_handle).unwrap().data
} }
/// Link a constant handle to its value. This does not de-duplicate data but does avoid
/// replacing any existing constant values. use `set` to tie a specific `const42` to its value;
/// use `insert` to add a value and return the next available `const` entity.
pub fn set(&mut self, constant_handle: Constant, constant_value: ConstantData) {
let replaced = self.handles_to_values.insert(
constant_handle,
ConstantPoolEntry::new(constant_value.clone()),
);
assert!(
replaced.is_none(),
"attempted to overwrite an existing constant {:?}: {:?} => {:?}",
constant_handle,
&constant_value,
replaced.unwrap().data
);
self.values_to_handles
.insert(constant_value, constant_handle);
}
/// Assign an offset to a given constant, where the offset is the number of bytes from the /// Assign an offset to a given constant, where the offset is the number of bytes from the
/// beginning of the function to the beginning of the constant data inside the pool. /// beginning of the function to the beginning of the constant data inside the pool.
pub fn set_offset(&mut self, constant_handle: Constant, constant_offset: ConstantOffset) { pub fn set_offset(&mut self, constant_handle: Constant, constant_offset: ConstantOffset) {
@@ -344,6 +361,24 @@ mod tests {
assert_eq!(sut.get(handle), &data.into()); assert_eq!(sut.get(handle), &data.into());
} }
#[test]
fn set() {
let mut sut = ConstantPool::new();
let handle = Constant::with_number(42).unwrap();
let data = vec![1, 2, 3];
sut.set(handle, data.clone().into());
assert_eq!(sut.get(handle), &data.into());
}
#[test]
#[should_panic]
fn disallow_overwriting_constant() {
let mut sut = ConstantPool::new();
let handle = Constant::with_number(42).unwrap();
sut.set(handle, vec![].into());
sut.set(handle, vec![1].into());
}
#[test] #[test]
#[should_panic] #[should_panic]
fn get_nonexistent_constant() { fn get_nonexistent_constant() {
@@ -374,7 +409,7 @@ mod tests {
assert_eq!(ConstantData::from([42].as_ref()).to_string(), "0x2a"); assert_eq!(ConstantData::from([42].as_ref()).to_string(), "0x2a");
assert_eq!( assert_eq!(
ConstantData::from([3, 2, 1, 0].as_ref()).to_string(), ConstantData::from([3, 2, 1, 0].as_ref()).to_string(),
"0x010203" "0x00010203"
); );
assert_eq!( assert_eq!(
ConstantData::from(3735928559u32.to_le_bytes().as_ref()).to_string(), ConstantData::from(3735928559u32.to_le_bytes().as_ref()).to_string(),
@@ -435,12 +470,12 @@ mod tests {
} }
parse_ok("0x00", "0x00"); parse_ok("0x00", "0x00");
parse_ok("0x00000042", "0x42"); parse_ok("0x00000042", "0x00000042");
parse_ok( parse_ok(
"0x0102030405060708090a0b0c0d0e0f00", "0x0102030405060708090a0b0c0d0e0f00",
"0x0102030405060708090a0b0c0d0e0f00", "0x0102030405060708090a0b0c0d0e0f00",
); );
parse_ok("0x_0000_0043_21", "0x4321"); parse_ok("0x_0000_0043_21", "0x0000004321");
parse_err("", "Expected a hexadecimal string, e.g. 0x1234"); parse_err("", "Expected a hexadecimal string, e.g. 0x1234");
parse_err("0x", "Expected a hexadecimal string, e.g. 0x1234"); parse_err("0x", "Expected a hexadecimal string, e.g. 0x1234");

View File

@@ -382,6 +382,8 @@ pub enum AnyEntity {
GlobalValue(GlobalValue), GlobalValue(GlobalValue),
/// A jump table. /// A jump table.
JumpTable(JumpTable), JumpTable(JumpTable),
/// A constant.
Constant(Constant),
/// An external function. /// An external function.
FuncRef(FuncRef), FuncRef(FuncRef),
/// A function call signature. /// A function call signature.
@@ -402,6 +404,7 @@ impl fmt::Display for AnyEntity {
Self::StackSlot(r) => r.fmt(f), Self::StackSlot(r) => r.fmt(f),
Self::GlobalValue(r) => r.fmt(f), Self::GlobalValue(r) => r.fmt(f),
Self::JumpTable(r) => r.fmt(f), Self::JumpTable(r) => r.fmt(f),
Self::Constant(r) => r.fmt(f),
Self::FuncRef(r) => r.fmt(f), Self::FuncRef(r) => r.fmt(f),
Self::SigRef(r) => r.fmt(f), Self::SigRef(r) => r.fmt(f),
Self::Heap(r) => r.fmt(f), Self::Heap(r) => r.fmt(f),
@@ -452,6 +455,12 @@ impl From<JumpTable> for AnyEntity {
} }
} }
impl From<Constant> for AnyEntity {
fn from(r: Constant) -> Self {
Self::Constant(r)
}
}
impl From<FuncRef> for AnyEntity { impl From<FuncRef> for AnyEntity {
fn from(r: FuncRef) -> Self { fn from(r: FuncRef) -> Self {
Self::FuncRef(r) Self::FuncRef(r)

View File

@@ -102,6 +102,11 @@ pub trait FuncWriter {
self.write_entity_definition(w, func, jt.into(), jt_data)?; self.write_entity_definition(w, func, jt.into(), jt_data)?;
} }
for (&cref, cval) in func.dfg.constants.iter() {
any = true;
self.write_entity_definition(w, func, cref.into(), cval)?;
}
Ok(any) Ok(any)
} }
@@ -494,6 +499,9 @@ pub fn write_operands(
UnaryIeee64 { imm, .. } => write!(w, " {}", imm), UnaryIeee64 { imm, .. } => write!(w, " {}", imm),
UnaryBool { imm, .. } => write!(w, " {}", imm), UnaryBool { imm, .. } => write!(w, " {}", imm),
UnaryGlobalValue { global_value, .. } => write!(w, " {}", global_value), UnaryGlobalValue { global_value, .. } => write!(w, " {}", global_value),
UnaryConst {
constant_handle, ..
} => write!(w, " {}", constant_handle),
Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]), Binary { args, .. } => write!(w, " {}, {}", args[0], args[1]),
BinaryImm { arg, imm, .. } => write!(w, " {}, {}", arg, imm), BinaryImm { arg, imm, .. } => write!(w, " {}, {}", arg, imm),
Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]), Ternary { args, .. } => write!(w, " {}, {}, {}", args[0], args[1], args[2]),
@@ -507,12 +515,6 @@ pub fn write_operands(
NullAry { .. } => write!(w, " "), NullAry { .. } => write!(w, " "),
InsertLane { lane, args, .. } => write!(w, " {}, {}, {}", args[0], lane, args[1]), InsertLane { lane, args, .. } => write!(w, " {}, {}, {}", args[0], lane, args[1]),
ExtractLane { lane, arg, .. } => write!(w, " {}, {}", arg, lane), ExtractLane { lane, arg, .. } => write!(w, " {}, {}", arg, lane),
UnaryConst {
constant_handle, ..
} => {
let constant_data = dfg.constants.get(constant_handle);
write!(w, " {}", constant_data)
}
Shuffle { mask, args, .. } => { Shuffle { mask, args, .. } => {
let data = dfg.immediates.get(mask).expect( let data = dfg.immediates.get(mask).expect(
"Expected the shuffle mask to already be inserted into the immediates table", "Expected the shuffle mask to already be inserted into the immediates table",

View File

@@ -3,10 +3,12 @@ set enable_simd
target x86_64 skylake target x86_64 skylake
function %ineg_i32x4() -> b1 { function %ineg_i32x4() -> b1 {
; check: const0 = 0x00000001000000010000000100000001
; nextln: const1 = 0x00000000000000000000000000000000
block0: block0:
v0 = vconst.i32x4 [1 1 1 1] v0 = vconst.i32x4 [1 1 1 1]
v2 = ineg v0 v2 = ineg v0
; check: v5 = vconst.i32x4 0x00 ; check: v5 = vconst.i32x4 const1
; nextln: v2 = isub v5, v0 ; nextln: v2 = isub v5, v0
v3 = extractlane v2, 0 v3 = extractlane v2, 0
@@ -16,37 +18,39 @@ block0:
} }
function %ineg_legalized() { function %ineg_legalized() {
; check: const0 = 0x00000000000000000000000000000000
block0: block0:
v0 = vconst.i8x16 0x00 v0 = vconst.i8x16 0x00
v1 = ineg v0 v1 = ineg v0
; check: v6 = vconst.i8x16 0x00 ; check: v6 = vconst.i8x16 const0
; nextln: v1 = isub v6, v0 ; nextln: v1 = isub v6, v0
v2 = raw_bitcast.i16x8 v0 v2 = raw_bitcast.i16x8 v0
v3 = ineg v2 v3 = ineg v2
; check: v7 = vconst.i16x8 0x00 ; check: v7 = vconst.i16x8 const0
; nextln: v3 = isub v7, v2 ; nextln: v3 = isub v7, v2
v4 = raw_bitcast.i64x2 v0 v4 = raw_bitcast.i64x2 v0
v5 = ineg v4 v5 = ineg v4
; check: v8 = vconst.i64x2 0x00 ; check: v8 = vconst.i64x2 const0
; nextln: v5 = isub v8, v4 ; nextln: v5 = isub v8, v4
return return
} }
function %fneg_legalized() { function %fneg_legalized() {
; check: const2 = 0xffffffffffffffffffffffffffffffff
block0: block0:
v0 = vconst.f32x4 [0x1.0 0x2.0 0x3.0 0x4.0] v0 = vconst.f32x4 [0x1.0 0x2.0 0x3.0 0x4.0]
v1 = fneg v0 v1 = fneg v0
; check: v4 = vconst.i32x4 0xffffffffffffffffffffffffffffffff ; check: v4 = vconst.i32x4 const2
; nextln: v5 = ishl_imm v4, 31 ; nextln: v5 = ishl_imm v4, 31
; nextln: v6 = raw_bitcast.f32x4 v5 ; nextln: v6 = raw_bitcast.f32x4 v5
; nextln: v1 = bxor v0, v6 ; nextln: v1 = bxor v0, v6
v2 = vconst.f64x2 [0x1.0 0x2.0] v2 = vconst.f64x2 [0x1.0 0x2.0]
v3 = fneg v2 v3 = fneg v2
; check: v7 = vconst.i64x2 0xffffffffffffffffffffffffffffffff ; check: v7 = vconst.i64x2 const2
; nextln: v8 = ishl_imm v7, 63 ; nextln: v8 = ishl_imm v7, 63
; nextln: v9 = raw_bitcast.f64x2 v8 ; nextln: v9 = raw_bitcast.f64x2 v8
; nextln: v3 = bxor v2, v9 ; nextln: v3 = bxor v2, v9
@@ -55,10 +59,11 @@ block0:
} }
function %fabs_legalized() { function %fabs_legalized() {
; check: const1 = 0xffffffffffffffffffffffffffffffff
block0: block0:
v0 = vconst.f64x2 [0x1.0 -0x2.0] v0 = vconst.f64x2 [0x1.0 -0x2.0]
v1 = fabs v0 v1 = fabs v0
; check: v2 = vconst.i64x2 0xffffffffffffffffffffffffffffffff ; check: v2 = vconst.i64x2 const1
; nextln: v3 = ushr_imm v2, 1 ; nextln: v3 = ushr_imm v2, 1
; nextln: v4 = raw_bitcast.f64x2 v3 ; nextln: v4 = raw_bitcast.f64x2 v3
; nextln: v1 = band v0, v4 ; nextln: v1 = band v0, v4

View File

@@ -3,20 +3,22 @@ set enable_simd
target x86_64 skylake target x86_64 skylake
function %icmp_ne_32x4(i32x4, i32x4) -> b32x4 { function %icmp_ne_32x4(i32x4, i32x4) -> b32x4 {
; check: const0 = 0xffffffffffffffffffffffffffffffff
block0(v0: i32x4, v1: i32x4): block0(v0: i32x4, v1: i32x4):
v2 = icmp ne v0, v1 v2 = icmp ne v0, v1
; check: v3 = icmp eq v0, v1 ; check: v3 = icmp eq v0, v1
; nextln: v4 = vconst.b32x4 0xffffffffffffffffffffffffffffffff ; nextln: v4 = vconst.b32x4 const0
; nextln: v2 = bxor v4, v3 ; nextln: v2 = bxor v4, v3
return v2 return v2
} }
function %icmp_ugt_i32x4(i32x4, i32x4) -> b32x4 { function %icmp_ugt_i32x4(i32x4, i32x4) -> b32x4 {
; check: const0 = 0xffffffffffffffffffffffffffffffff
block0(v0: i32x4, v1: i32x4): block0(v0: i32x4, v1: i32x4):
v2 = icmp ugt v0, v1 v2 = icmp ugt v0, v1
; check: v3 = x86_pmaxu v0, v1 ; check: v3 = x86_pmaxu v0, v1
; nextln: v4 = icmp eq v3, v1 ; nextln: v4 = icmp eq v3, v1
; nextln: v5 = vconst.b32x4 0xffffffffffffffffffffffffffffffff ; nextln: v5 = vconst.b32x4 const0
; nextln: v2 = bxor v5, v4 ; nextln: v2 = bxor v5, v4
return v2 return v2
} }

View File

@@ -5,27 +5,30 @@ target x86_64 skylake
;; shuffle ;; shuffle
function %shuffle_different_ssa_values() -> i8x16 { function %shuffle_different_ssa_values() -> i8x16 {
; check: const2 = 0x80000000000000000000000000000000
; nextln: const3 = 0x01808080808080808080808080808080
block0: block0:
v0 = vconst.i8x16 0x00 v0 = vconst.i8x16 0x00
v1 = vconst.i8x16 0x01 v1 = vconst.i8x16 0x01
v2 = shuffle v0, v1, 0x11000000000000000000000000000000 ; pick the second lane of v1, the rest use the first lane of v0 v2 = shuffle v0, v1, 0x11000000000000000000000000000000 ; pick the second lane of v1, the rest use the first lane of v0
return v2 return v2
} }
; check: v1 = vconst.i8x16 0x01 ; check: v1 = vconst.i8x16 const1
; nextln: v3 = vconst.i8x16 0x80000000000000000000000000000000 ; nextln: v3 = vconst.i8x16 const2
; nextln: v4 = x86_pshufb v0, v3 ; nextln: v4 = x86_pshufb v0, v3
; nextln: v5 = vconst.i8x16 0x01808080808080808080808080808080 ; nextln: v5 = vconst.i8x16 const3
; nextln: v6 = x86_pshufb v1, v5 ; nextln: v6 = x86_pshufb v1, v5
; nextln: v2 = bor v4, v6 ; nextln: v2 = bor v4, v6
function %shuffle_same_ssa_value() -> i8x16 { function %shuffle_same_ssa_value() -> i8x16 {
; check: const1 = 0x03000000000000000000000000000000
block0: block0:
v1 = vconst.i8x16 0x01 v1 = vconst.i8x16 0x01
v2 = shuffle v1, v1, 0x13000000000000000000000000000000 ; pick the fourth lane of v1 and the rest from the first lane of v1 v2 = shuffle v1, v1, 0x13000000000000000000000000000000 ; pick the fourth lane of v1 and the rest from the first lane of v1
return v2 return v2
} }
; check: v1 = vconst.i8x16 0x01 ; check: v1 = vconst.i8x16 const0
; nextln: v3 = vconst.i8x16 0x03000000000000000000000000000000 ; nextln: v3 = vconst.i8x16 const1
; nextln: v2 = x86_pshufb v1, v3 ; nextln: v2 = x86_pshufb v1, v3
;; splat ;; splat
@@ -71,6 +74,7 @@ block0:
; nextln: return v1 ; nextln: return v1
function %splat_i8() -> i8x16 { function %splat_i8() -> i8x16 {
; check: const0 = 0x00000000000000000000000000000000
block0: block0:
v0 = iconst.i8 42 v0 = iconst.i8 42
v1 = splat.i8x16 v0 v1 = splat.i8x16 v0
@@ -80,16 +84,17 @@ block0:
; nextln: v2 = iconst.i32 42 ; nextln: v2 = iconst.i32 42
; nextln: v0 = ireduce.i8 v2 ; nextln: v0 = ireduce.i8 v2
; nextln: v3 = scalar_to_vector.i8x16 v0 ; nextln: v3 = scalar_to_vector.i8x16 v0
; nextln: v4 = vconst.i8x16 0x00 ; nextln: v4 = vconst.i8x16 const0
; nextln: v1 = x86_pshufb v3, v4 ; nextln: v1 = x86_pshufb v3, v4
; nextln: return v1 ; nextln: return v1
function %swizzle() -> i8x16 { function %swizzle() -> i8x16 {
; check: const1 = 0x70707070707070707070707070707070
block0: block0:
v0 = vconst.i8x16 [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] v0 = vconst.i8x16 [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
v1 = vconst.i8x16 [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15] v1 = vconst.i8x16 [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
v2 = swizzle.i8x16 v0, v1 v2 = swizzle.i8x16 v0, v1
; check: v3 = vconst.i8x16 0x70707070707070707070707070707070 ; check: v3 = vconst.i8x16 const1
; nextln: v4 = uadd_sat v1, v3 ; nextln: v4 = uadd_sat v1, v3
; nextln: v2 = x86_pshufb v0, v4 ; nextln: v2 = x86_pshufb v0, v4
return v2 return v2

View File

@@ -3,9 +3,10 @@ set enable_simd
target x86_64 skylake target x86_64 skylake
function %bnot_b32x4(b32x4) -> b32x4 { function %bnot_b32x4(b32x4) -> b32x4 {
; check: const0 = 0xffffffffffffffffffffffffffffffff
block0(v0: b32x4): block0(v0: b32x4):
v1 = bnot v0 v1 = bnot v0
; check: v2 = vconst.b32x4 0xffffffffffffffffffffffffffffffff ; check: v2 = vconst.b32x4 const0
; nextln: v1 = bxor v2, v0 ; nextln: v1 = bxor v2, v0
return v1 return v1
} }
@@ -19,9 +20,10 @@ block0(v0: b32x4):
} }
function %vall_true_i64x2(i64x2) -> b1 { function %vall_true_i64x2(i64x2) -> b1 {
; check: const0 = 0x00000000000000000000000000000000
block0(v0: i64x2): block0(v0: i64x2):
v1 = vall_true v0 v1 = vall_true v0
; check: v2 = vconst.i64x2 0x00 ; check: v2 = vconst.i64x2 const0
; nextln: v3 = icmp eq v0, v2 ; nextln: v3 = icmp eq v0, v2
; nextln: v4 = x86_ptest v3, v3 ; nextln: v4 = x86_ptest v3, v3
; nextln: v1 = trueif eq v4 ; nextln: v1 = trueif eq v4

View File

@@ -9,3 +9,13 @@ block0:
[-, %xmm3] v1 = vconst.b8x16 0x02 ; bin: 0f 10 1d 00000011 PCRelRodata4(31) [-, %xmm3] v1 = vconst.b8x16 0x02 ; bin: 0f 10 1d 00000011 PCRelRodata4(31)
return return
} }
function %vconst_with_preamble() {
const42 = i32x4 [1 0 0 0]
const43 = i32x4 [2 0 0 0]
block0:
[-, %xmm2] v0 = vconst.i32x4 const42 ; bin: 0f 10 15 00000008 PCRelRodata4(15)
[-, %xmm3] v1 = vconst.i32x4 const43 ; bin: 0f 10 1d 00000011 PCRelRodata4(31)
return
}

View File

@@ -9,8 +9,8 @@ block0:
v0 = vconst.i32x4 0x1234 v0 = vconst.i32x4 0x1234
return v0 return v0
} }
; check: const0 = 0x00000000000000000000000000001234
; check: block0: ; check: block0:
; nextln: v0 = vconst.i32x4 0x1234 ; nextln: v0 = vconst.i32x4 const0
; nextln: return v0 ; nextln: return v0
; nextln: } ; nextln: }

View File

@@ -37,3 +37,13 @@ block11:
} }
; sameln: [1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10] ; sameln: [1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10]
function %vconst_preamble() -> b16x8 {
const42 = i32x4 [0 1 2 3]
const43 = i32x4 [4 5 6 7]
block0:
v0 = vconst.b16x8 const42
return v0
}
; sameln: [0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0]

View File

@@ -1262,13 +1262,15 @@ block0:
assert_eq!( assert_eq!(
func.display(None).to_string(), func.display(None).to_string(),
"function %sample() -> i8x16, b8x16, f32x4 system_v { "function %sample() -> i8x16, b8x16, f32x4 system_v {
const0 = 0x00000000000000000000000000000000
block0: block0:
v5 = f32const 0.0 v5 = f32const 0.0
v6 = splat.f32x4 v5 v6 = splat.f32x4 v5
v2 -> v6 v2 -> v6
v4 = vconst.b8x16 0x00 v4 = vconst.b8x16 const0
v1 -> v4 v1 -> v4
v3 = vconst.i8x16 0x00 v3 = vconst.i8x16 const0
v0 -> v3 v0 -> v3
return v0, v1, v2 return v0, v1, v2
} }

View File

@@ -39,6 +39,7 @@ pub enum Token<'a> {
Heap(u32), // heap2 Heap(u32), // heap2
Table(u32), // table2 Table(u32), // table2
JumpTable(u32), // jt2 JumpTable(u32), // jt2
Constant(u32), // const2
FuncRef(u32), // fn2 FuncRef(u32), // fn2
SigRef(u32), // sig2 SigRef(u32), // sig2
UserRef(u32), // u345 UserRef(u32), // u345
@@ -345,6 +346,7 @@ impl<'a> Lexer<'a> {
"heap" => Some(Token::Heap(number)), "heap" => Some(Token::Heap(number)),
"table" => Some(Token::Table(number)), "table" => Some(Token::Table(number)),
"jt" => Some(Token::JumpTable(number)), "jt" => Some(Token::JumpTable(number)),
"const" => Some(Token::Constant(number)),
"fn" => Some(Token::FuncRef(number)), "fn" => Some(Token::FuncRef(number)),
"sig" => Some(Token::SigRef(number)), "sig" => Some(Token::SigRef(number)),
"u" => Some(Token::UserRef(number)), "u" => Some(Token::UserRef(number)),
@@ -656,7 +658,7 @@ mod tests {
#[test] #[test]
fn lex_names() { fn lex_names() {
let mut lex = Lexer::new("%0 %x3 %function %123_abc %ss0 %v3 %block11 %_"); let mut lex = Lexer::new("%0 %x3 %function %123_abc %ss0 %v3 %block11 %const42 %_");
assert_eq!(lex.next(), token(Token::Name("0"), 1)); assert_eq!(lex.next(), token(Token::Name("0"), 1));
assert_eq!(lex.next(), token(Token::Name("x3"), 1)); assert_eq!(lex.next(), token(Token::Name("x3"), 1));
@@ -665,6 +667,7 @@ mod tests {
assert_eq!(lex.next(), token(Token::Name("ss0"), 1)); assert_eq!(lex.next(), token(Token::Name("ss0"), 1));
assert_eq!(lex.next(), token(Token::Name("v3"), 1)); assert_eq!(lex.next(), token(Token::Name("v3"), 1));
assert_eq!(lex.next(), token(Token::Name("block11"), 1)); assert_eq!(lex.next(), token(Token::Name("block11"), 1));
assert_eq!(lex.next(), token(Token::Name("const42"), 1));
assert_eq!(lex.next(), token(Token::Name("_"), 1)); assert_eq!(lex.next(), token(Token::Name("_"), 1));
} }

View File

@@ -15,10 +15,10 @@ use cranelift_codegen::ir::instructions::{InstructionData, InstructionFormat, Va
use cranelift_codegen::ir::types::INVALID; use cranelift_codegen::ir::types::INVALID;
use cranelift_codegen::ir::types::*; use cranelift_codegen::ir::types::*;
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
AbiParam, ArgumentExtension, ArgumentLoc, Block, ConstantData, ExtFuncData, ExternalName, AbiParam, ArgumentExtension, ArgumentLoc, Block, Constant, ConstantData, ExtFuncData,
FuncRef, Function, GlobalValue, GlobalValueData, Heap, HeapData, HeapStyle, JumpTable, ExternalName, FuncRef, Function, GlobalValue, GlobalValueData, Heap, HeapData, HeapStyle,
JumpTableData, MemFlags, Opcode, SigRef, Signature, StackSlot, StackSlotData, StackSlotKind, JumpTable, JumpTableData, MemFlags, Opcode, SigRef, Signature, StackSlot, StackSlotData,
Table, TableData, Type, Value, ValueLoc, StackSlotKind, Table, TableData, Type, Value, ValueLoc,
}; };
use cranelift_codegen::isa::{self, CallConv, Encoding, RegUnit, TargetIsa}; use cranelift_codegen::isa::{self, CallConv, Encoding, RegUnit, TargetIsa};
use cranelift_codegen::packed_option::ReservedValue; use cranelift_codegen::packed_option::ReservedValue;
@@ -346,6 +346,27 @@ impl<'a> Context<'a> {
} }
} }
// Allocate a new constant.
fn add_constant(
&mut self,
constant: Constant,
data: ConstantData,
loc: Location,
) -> ParseResult<()> {
self.map.def_constant(constant, loc)?;
self.function.dfg.constants.set(constant, data);
Ok(())
}
// Resolve a reference to a constant.
fn check_constant(&self, c: Constant, loc: Location) -> ParseResult<()> {
if !self.map.contains_constant(c) {
err!(loc, "undefined constant {}", c)
} else {
Ok(())
}
}
// Allocate a new block. // Allocate a new block.
fn add_block(&mut self, block: Block, loc: Location) -> ParseResult<Block> { fn add_block(&mut self, block: Block, loc: Location) -> ParseResult<Block> {
self.map.def_block(block, loc)?; self.map.def_block(block, loc)?;
@@ -566,6 +587,17 @@ impl<'a> Parser<'a> {
err!(self.loc, "expected jump table number: jt«n»") err!(self.loc, "expected jump table number: jt«n»")
} }
// Match and consume a constant reference.
fn match_constant(&mut self) -> ParseResult<Constant> {
if let Some(Token::Constant(c)) = self.token() {
self.consume();
if let Some(c) = Constant::with_number(c) {
return Ok(c);
}
}
err!(self.loc, "expected constant number: const«n»")
}
// Match and consume a block reference. // Match and consume a block reference.
fn match_block(&mut self, err_msg: &str) -> ParseResult<Block> { fn match_block(&mut self, err_msg: &str) -> ParseResult<Block> {
if let Some(Token::Block(block)) = self.token() { if let Some(Token::Block(block)) = self.token() {
@@ -621,8 +653,20 @@ impl<'a> Parser<'a> {
} }
} }
// Match and consume either a hexadecimal Uimm128 immediate (e.g. 0x000102...) or its literal list form (e.g. [0 1 2...]) // Match and consume a sequence of immediate bytes (uimm8); e.g. [0x42 0x99 0x32]
fn match_constant_data(&mut self, controlling_type: Type) -> ParseResult<ConstantData> { fn match_constant_data(&mut self) -> ParseResult<ConstantData> {
self.match_token(Token::LBracket, "expected an opening left bracket")?;
let mut data = ConstantData::default();
while !self.optional(Token::RBracket) {
data = data.append(self.match_uimm8("expected a sequence of bytes (uimm8)")?);
}
Ok(data)
}
// Match and consume either a hexadecimal Uimm128 immediate (e.g. 0x000102...) or its literal
// list form (e.g. [0 1 2...]). For convenience, since uimm128 values are stored in the
// `ConstantPool`, this returns `ConstantData`.
fn match_uimm128(&mut self, controlling_type: Type) -> ParseResult<ConstantData> {
let expected_size = controlling_type.bytes() as usize; let expected_size = controlling_type.bytes() as usize;
let constant_data = if self.optional(Token::LBracket) { let constant_data = if self.optional(Token::LBracket) {
// parse using a list of values, e.g. vconst.i32x4 [0 1 2 3] // parse using a list of values, e.g. vconst.i32x4 [0 1 2 3]
@@ -1454,6 +1498,11 @@ impl<'a> Parser<'a> {
self.parse_jump_table_decl() self.parse_jump_table_decl()
.and_then(|(jt, dat)| ctx.add_jt(jt, dat, self.loc)) .and_then(|(jt, dat)| ctx.add_jt(jt, dat, self.loc))
} }
Some(Token::Constant(..)) => {
self.start_gathering_comments();
self.parse_constant_decl()
.and_then(|(c, v)| ctx.add_constant(c, v, self.loc))
}
// More to come.. // More to come..
_ => return Ok(()), _ => return Ok(()),
}?; }?;
@@ -1838,6 +1887,26 @@ impl<'a> Parser<'a> {
Ok((jt, data)) Ok((jt, data))
} }
// Parse a constant decl.
//
// constant-decl ::= * Constant(c) "=" ty? "[" literal {"," literal} "]"
fn parse_constant_decl(&mut self) -> ParseResult<(Constant, ConstantData)> {
let name = self.match_constant()?;
self.match_token(Token::Equal, "expected '=' in constant decl")?;
let data = if let Some(Token::Type(_)) = self.token() {
let ty = self.match_type("expected type of constant")?;
self.match_uimm128(ty)
} else {
self.match_constant_data()
}?;
// Collect any trailing comments.
self.token();
self.claim_gathered_comments(name);
Ok((name, data))
}
// Parse a function body, add contents to `ctx`. // Parse a function body, add contents to `ctx`.
// //
// function-body ::= * { extended-basic-block } // function-body ::= * { extended-basic-block }
@@ -2537,7 +2606,7 @@ impl<'a> Parser<'a> {
F32 => DataValue::from(f32::from_bits(self.match_ieee32("expected an f32")?.bits())), F32 => DataValue::from(f32::from_bits(self.match_ieee32("expected an f32")?.bits())),
F64 => DataValue::from(f64::from_bits(self.match_ieee64("expected an f64")?.bits())), F64 => DataValue::from(f64::from_bits(self.match_ieee64("expected an f64")?.bits())),
_ if ty.is_vector() => { _ if ty.is_vector() => {
let as_vec = self.match_constant_data(ty)?.into_vec(); let as_vec = self.match_uimm128(ty)?.into_vec();
if as_vec.len() == 16 { if as_vec.len() == 16 {
let mut as_array = [0; 16]; let mut as_array = [0; 16];
as_array.copy_from_slice(&as_vec[..16]); as_array.copy_from_slice(&as_vec[..16]);
@@ -2583,6 +2652,28 @@ impl<'a> Parser<'a> {
opcode, opcode,
imm: self.match_bool("expected immediate boolean operand")?, imm: self.match_bool("expected immediate boolean operand")?,
}, },
InstructionFormat::UnaryConst => {
let constant_handle = if let Some(Token::Constant(_)) = self.token() {
// If handed a `const?`, use that.
let c = self.match_constant()?;
ctx.check_constant(c, self.loc)?;
c
} else if let Some(controlling_type) = explicit_control_type {
// If an explicit control type is present, we expect a sized value and insert
// it in the constant pool.
let uimm128 = self.match_uimm128(controlling_type)?;
ctx.function.dfg.constants.insert(uimm128)
} else {
return err!(
self.loc,
"Expected either a const entity or a typed value, e.g. inst.i32x4 [...]"
);
};
InstructionData::UnaryConst {
opcode,
constant_handle,
}
}
InstructionFormat::UnaryGlobalValue => { InstructionFormat::UnaryGlobalValue => {
let gv = self.match_gv("expected global value")?; let gv = self.match_gv("expected global value")?;
ctx.check_gv(gv, self.loc)?; ctx.check_gv(gv, self.loc)?;
@@ -2753,29 +2844,12 @@ impl<'a> Parser<'a> {
let lane = self.match_uimm8("expected lane number")?; let lane = self.match_uimm8("expected lane number")?;
InstructionData::ExtractLane { opcode, lane, arg } InstructionData::ExtractLane { opcode, lane, arg }
} }
InstructionFormat::UnaryConst => match explicit_control_type {
None => {
return err!(
self.loc,
"Expected {:?} to have a controlling type variable, e.g. inst.i32x4",
opcode
)
}
Some(controlling_type) => {
let uimm128 = self.match_constant_data(controlling_type)?;
let constant_handle = ctx.function.dfg.constants.insert(uimm128);
InstructionData::UnaryConst {
opcode,
constant_handle,
}
}
},
InstructionFormat::Shuffle => { InstructionFormat::Shuffle => {
let a = self.match_value("expected SSA value first operand")?; let a = self.match_value("expected SSA value first operand")?;
self.match_token(Token::Comma, "expected ',' between operands")?; self.match_token(Token::Comma, "expected ',' between operands")?;
let b = self.match_value("expected SSA value second operand")?; let b = self.match_value("expected SSA value second operand")?;
self.match_token(Token::Comma, "expected ',' between operands")?; self.match_token(Token::Comma, "expected ',' between operands")?;
let uimm128 = self.match_constant_data(I8X16)?; let uimm128 = self.match_uimm128(I8X16)?;
let mask = ctx.function.dfg.immediates.push(uimm128); let mask = ctx.function.dfg.immediates.push(uimm128);
InstructionData::Shuffle { InstructionData::Shuffle {
opcode, opcode,
@@ -3692,6 +3766,18 @@ mod tests {
) )
} }
#[test]
fn parse_unbounded_constants() {
// Unlike match_uimm128, match_constant_data can parse byte sequences of any size:
assert_eq!(
Parser::new("[0 1]").match_constant_data().unwrap(),
vec![0, 1].into()
);
// Only parse byte literals:
assert!(Parser::new("[256]").match_constant_data().is_err());
}
#[test] #[test]
fn parse_run_commands() { fn parse_run_commands() {
// Helper for creating signatures. // Helper for creating signatures.
@@ -3760,7 +3846,7 @@ mod tests {
assert_eq!(parse("false", B64).to_string(), "false"); assert_eq!(parse("false", B64).to_string(), "false");
assert_eq!( assert_eq!(
parse("[0 1 2 3]", I32X4).to_string(), parse("[0 1 2 3]", I32X4).to_string(),
"0x03000000020000000100000000" "0x00000003000000020000000100000000"
); );
} }
} }

View File

@@ -10,7 +10,7 @@ use crate::error::{Location, ParseResult};
use crate::lexer::split_entity_name; use crate::lexer::split_entity_name;
use cranelift_codegen::ir::entities::AnyEntity; use cranelift_codegen::ir::entities::AnyEntity;
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
Block, FuncRef, GlobalValue, Heap, JumpTable, SigRef, StackSlot, Table, Value, Block, Constant, FuncRef, GlobalValue, Heap, JumpTable, SigRef, StackSlot, Table, Value,
}; };
use std::collections::HashMap; use std::collections::HashMap;
@@ -68,6 +68,11 @@ impl SourceMap {
self.locations.contains_key(&jt.into()) self.locations.contains_key(&jt.into())
} }
/// Look up a constant entity.
pub fn contains_constant(&self, c: Constant) -> bool {
self.locations.contains_key(&c.into())
}
/// Look up an entity by source name. /// Look up an entity by source name.
/// Returns the entity reference corresponding to `name`, if it exists. /// Returns the entity reference corresponding to `name`, if it exists.
pub fn lookup_str(&self, name: &str) -> Option<AnyEntity> { pub fn lookup_str(&self, name: &str) -> Option<AnyEntity> {
@@ -198,6 +203,11 @@ impl SourceMap {
self.def_entity(entity.into(), loc) self.def_entity(entity.into(), loc)
} }
/// Define the jump table `entity`.
pub fn def_constant(&mut self, entity: Constant, loc: Location) -> ParseResult<()> {
self.def_entity(entity.into(), loc)
}
/// Define an entity. This can be used for instructions whose numbers never /// Define an entity. This can be used for instructions whose numbers never
/// appear in source, or implicitly defined signatures. /// appear in source, or implicitly defined signatures.
pub fn def_entity(&mut self, entity: AnyEntity, loc: Location) -> ParseResult<()> { pub fn def_entity(&mut self, entity: AnyEntity, loc: Location) -> ParseResult<()> {