diff --git a/cranelift/codegen/meta/src/cdsl/instructions.rs b/cranelift/codegen/meta/src/cdsl/instructions.rs index 00a166fb64..43a879066c 100644 --- a/cranelift/codegen/meta/src/cdsl/instructions.rs +++ b/cranelift/codegen/meta/src/cdsl/instructions.rs @@ -12,7 +12,7 @@ use crate::cdsl::formats::{ }; use crate::cdsl::operands::Operand; use crate::cdsl::type_inference::Constraint; -use crate::cdsl::types::{LaneType, ValueType, VectorType}; +use crate::cdsl::types::{LaneType, ReferenceType, ValueType, VectorType}; use crate::cdsl::typevar::TypeVar; #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -177,6 +177,10 @@ impl Instruction { bind(self.clone(), Some(lane_type.into()), Vec::new()) } + pub fn bind_ref(&self, reference_type: impl Into) -> BoundInstruction { + bind_ref(self.clone(), Some(reference_type.into()), Vec::new()) + } + pub fn bind_vector(&self, lane_type: impl Into, num_lanes: u64) -> BoundInstruction { bind_vector(self.clone(), lane_type.into(), num_lanes, Vec::new()) } @@ -406,6 +410,10 @@ impl BoundInstruction { bind(self.inst, Some(lane_type.into()), self.value_types) } + pub fn bind_ref(self, reference_type: impl Into) -> BoundInstruction { + bind_ref(self.inst, Some(reference_type.into()), self.value_types) + } + pub fn bind_vector(self, lane_type: impl Into, num_lanes: u64) -> BoundInstruction { bind_vector(self.inst, lane_type.into(), num_lanes, self.value_types) } @@ -1043,6 +1051,13 @@ impl InstSpec { InstSpec::Bound(inst) => inst.clone().bind(lane_type), } } + + pub fn bind_ref(&self, reference_type: impl Into) -> BoundInstruction { + match self { + InstSpec::Inst(inst) => inst.bind_ref(reference_type), + InstSpec::Bound(inst) => inst.clone().bind_ref(reference_type), + } + } } impl Into for &Instruction { @@ -1077,6 +1092,26 @@ fn bind( BoundInstruction { inst, value_types } } +/// Helper bind for reference types reused by {Bound,}Instruction::bind_ref. +fn bind_ref( + inst: Instruction, + reference_type: Option, + mut value_types: Vec, +) -> BoundInstruction { + match reference_type { + Some(reference_type) => { + value_types.push(ValueTypeOrAny::ValueType(reference_type.into())); + } + None => { + value_types.push(ValueTypeOrAny::Any); + } + } + + verify_polymorphic_binding(&inst, &value_types); + + BoundInstruction { inst, value_types } +} + /// Helper bind for vector types reused by {Bound,}Instruction::bind. fn bind_vector( inst: Instruction, diff --git a/cranelift/codegen/meta/src/cdsl/types.rs b/cranelift/codegen/meta/src/cdsl/types.rs index 21bf0161c4..eba239d1d7 100644 --- a/cranelift/codegen/meta/src/cdsl/types.rs +++ b/cranelift/codegen/meta/src/cdsl/types.rs @@ -11,12 +11,14 @@ use crate::shared::types as shared_types; // // 0: Void // 0x01-0x6f: Special types -// 0x70-0x7f: Lane types +// 0x70-0x7d: Lane types +// 0x7e-0x7f: Reference types // 0x80-0xff: Vector types // // Vector types are encoded with the lane type in the low 4 bits and log2(lanes) // in the high 4 bits, giving a range of 2-256 lanes. static LANE_BASE: u8 = 0x70; +static REFERENCE_BASE: u8 = 0x7E; // Rust name prefix used for the `rust_name` method. static _RUST_NAME_PREFIX: &'static str = "ir::types::"; @@ -31,6 +33,7 @@ static _RUST_NAME_PREFIX: &'static str = "ir::types::"; pub enum ValueType { BV(BVType), Lane(LaneType), + Reference(ReferenceType), Special(SpecialType), Vector(VectorType), } @@ -46,11 +49,16 @@ impl ValueType { SpecialTypeIterator::new() } + pub fn all_reference_types() -> ReferenceTypeIterator { + ReferenceTypeIterator::new() + } + /// Return a string containing the documentation comment for this type. pub fn doc(&self) -> String { match *self { ValueType::BV(ref b) => b.doc(), ValueType::Lane(l) => l.doc(), + ValueType::Reference(r) => r.doc(), ValueType::Special(s) => s.doc(), ValueType::Vector(ref v) => v.doc(), } @@ -61,6 +69,7 @@ impl ValueType { match *self { ValueType::BV(ref b) => b.lane_bits(), ValueType::Lane(l) => l.lane_bits(), + ValueType::Reference(r) => r.lane_bits(), ValueType::Special(s) => s.lane_bits(), ValueType::Vector(ref v) => v.lane_bits(), } @@ -84,6 +93,7 @@ impl ValueType { match *self { ValueType::BV(_) => None, ValueType::Lane(l) => Some(l.number()), + ValueType::Reference(r) => Some(r.number()), ValueType::Special(s) => Some(s.number()), ValueType::Vector(ref v) => Some(v.number()), } @@ -112,6 +122,7 @@ impl fmt::Display for ValueType { match *self { ValueType::BV(ref b) => b.fmt(f), ValueType::Lane(l) => l.fmt(f), + ValueType::Reference(r) => r.fmt(f), ValueType::Special(s) => s.fmt(f), ValueType::Vector(ref v) => v.fmt(f), } @@ -132,6 +143,13 @@ impl From for ValueType { } } +/// Create a ValueType from a given reference type. +impl From for ValueType { + fn from(reference: ReferenceType) -> Self { + ValueType::Reference(reference) + } +} + /// Create a ValueType from a given special type. impl From for ValueType { fn from(spec: SpecialType) -> Self { @@ -515,3 +533,83 @@ impl Iterator for SpecialTypeIterator { } } } + +/// Reference type is scalar type, but not lane type. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct ReferenceType(pub shared_types::Reference); + +impl ReferenceType { + /// Return a string containing the documentation comment for this reference type. + pub fn doc(self) -> String { + format!("An opaque reference type with {} bits.", self.lane_bits()) + } + + /// Return the number of bits in a lane. + pub fn lane_bits(self) -> u64 { + match self.0 { + shared_types::Reference::R32 => 32, + shared_types::Reference::R64 => 64, + } + } + + /// Find the unique number associated with this reference type. + pub fn number(self) -> u8 { + REFERENCE_BASE + + match self { + ReferenceType(shared_types::Reference::R32) => 0, + ReferenceType(shared_types::Reference::R64) => 1, + } + } + + pub fn ref_from_bits(num_bits: u16) -> ReferenceType { + ReferenceType(match num_bits { + 32 => shared_types::Reference::R32, + 64 => shared_types::Reference::R64, + _ => unreachable!("unexpected number of bits for a reference type"), + }) + } +} + +impl fmt::Display for ReferenceType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "r{}", self.lane_bits()) + } +} + +impl fmt::Debug for ReferenceType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ReferenceType(bits={})", self.lane_bits()) + } +} + +/// Create a ReferenceType from a given reference variant. +impl From for ReferenceType { + fn from(r: shared_types::Reference) -> Self { + ReferenceType(r) + } +} + +/// An iterator for different reference types. +pub struct ReferenceTypeIterator { + reference_iter: shared_types::ReferenceIterator, +} + +impl ReferenceTypeIterator { + /// Create a new reference type iterator. + fn new() -> Self { + Self { + reference_iter: shared_types::ReferenceIterator::new(), + } + } +} + +impl Iterator for ReferenceTypeIterator { + type Item = ReferenceType; + fn next(&mut self) -> Option { + if let Some(r) = self.reference_iter.next() { + Some(ReferenceType::from(r)) + } else { + None + } + } +} diff --git a/cranelift/codegen/meta/src/cdsl/typevar.rs b/cranelift/codegen/meta/src/cdsl/typevar.rs index 22640b6b9c..9ae4c33fd8 100644 --- a/cranelift/codegen/meta/src/cdsl/typevar.rs +++ b/cranelift/codegen/meta/src/cdsl/typevar.rs @@ -6,7 +6,7 @@ use std::iter::FromIterator; use std::ops; use std::rc::Rc; -use crate::cdsl::types::{BVType, LaneType, SpecialType, ValueType}; +use crate::cdsl::types::{BVType, LaneType, ReferenceType, SpecialType, ValueType}; const MAX_LANES: u16 = 256; const MAX_BITS: u16 = 64; @@ -64,6 +64,10 @@ impl TypeVar { ValueType::Special(special_type) => { return TypeVar::new(name, doc, builder.specials(vec![special_type]).build()); } + ValueType::Reference(ReferenceType(reference_type)) => { + let bits = reference_type as RangeBound; + return TypeVar::new(name, doc, builder.refs(bits..bits).build()); + } ValueType::Lane(lane_type) => (lane_type, 1), ValueType::Vector(vec_type) => { (vec_type.lane_type(), vec_type.lane_count() as RangeBound) @@ -406,6 +410,7 @@ pub struct TypeSet { pub ints: NumSet, pub floats: NumSet, pub bools: NumSet, + pub refs: NumSet, pub bitvecs: NumSet, pub specials: Vec, } @@ -416,6 +421,7 @@ impl TypeSet { ints: NumSet, floats: NumSet, bools: NumSet, + refs: NumSet, bitvecs: NumSet, specials: Vec, ) -> Self { @@ -424,6 +430,7 @@ impl TypeSet { ints, floats, bools, + refs, bitvecs, specials, } @@ -432,7 +439,11 @@ impl TypeSet { /// Return the number of concrete types represented by this typeset. pub fn size(&self) -> usize { self.lanes.len() - * (self.ints.len() + self.floats.len() + self.bools.len() + self.bitvecs.len()) + * (self.ints.len() + + self.floats.len() + + self.bools.len() + + self.refs.len() + + self.bitvecs.len()) + self.specials.len() } @@ -462,6 +473,7 @@ impl TypeSet { let mut copy = self.clone(); copy.ints = NumSet::new(); copy.floats = NumSet::new(); + copy.refs = NumSet::new(); copy.bitvecs = NumSet::new(); if (&self.lanes - &num_set![1]).len() > 0 { copy.bools = &self.ints | &self.floats; @@ -544,6 +556,7 @@ impl TypeSet { copy.ints = NumSet::new(); copy.bools = NumSet::new(); copy.floats = NumSet::new(); + copy.refs = NumSet::new(); copy.bitvecs = self .lanes .iter() @@ -568,6 +581,9 @@ impl TypeSet { for &bits in &self.bools { ret.push(LaneType::bool_from_bits(bits).by(num_lanes)); } + for &bits in &self.refs { + ret.push(ReferenceType::ref_from_bits(bits).into()); + } for &bits in &self.bitvecs { assert_eq!(num_lanes, 1); ret.push(BVType::new(bits).into()); @@ -630,6 +646,7 @@ impl TypeSet { let mut ints = range_to_set(Some(8..MAX_BITS)); let mut floats = range_to_set(Some(32..64)); let mut bools = range_to_set(Some(1..MAX_BITS)); + let refs = range_to_set(Some(32..64)); for &l in &all_lanes { for &i in &all_ints { @@ -654,7 +671,7 @@ impl TypeSet { let bitvecs = NumSet::new(); let specials = Vec::new(); - TypeSet::new(lanes, ints, floats, bools, bitvecs, specials) + TypeSet::new(lanes, ints, floats, bools, refs, bitvecs, specials) } } } @@ -664,6 +681,7 @@ impl TypeSet { self.ints = &self.ints & &other.ints; self.floats = &self.floats & &other.floats; self.bools = &self.bools & &other.bools; + self.refs = &self.refs & &other.refs; self.bitvecs = &self.bitvecs & &other.bitvecs; let mut new_specials = Vec::new(); @@ -680,6 +698,7 @@ impl TypeSet { && self.ints.is_subset(&other.ints) && self.floats.is_subset(&other.floats) && self.bools.is_subset(&other.bools) + && self.refs.is_subset(&other.refs) && self.bitvecs.is_subset(&other.bitvecs) && { let specials: HashSet = HashSet::from_iter(self.specials.clone()); @@ -692,12 +711,14 @@ impl TypeSet { set_wider_or_equal(&self.ints, &other.ints) && set_wider_or_equal(&self.floats, &other.floats) && set_wider_or_equal(&self.bools, &other.bools) + && set_wider_or_equal(&self.refs, &other.refs) } pub fn is_narrower(&self, other: &TypeSet) -> bool { set_narrower(&self.ints, &other.ints) && set_narrower(&self.floats, &other.floats) && set_narrower(&self.bools, &other.bools) + && set_narrower(&self.refs, &other.refs) } } @@ -738,6 +759,12 @@ impl fmt::Debug for TypeSet { Vec::from_iter(self.bools.iter().map(|x| x.to_string())).join(", ") )); } + if !self.refs.is_empty() { + subsets.push(format!( + "refs={{{}}}", + Vec::from_iter(self.refs.iter().map(|x| x.to_string())).join(", ") + )); + } if !self.bitvecs.is_empty() { subsets.push(format!( "bitvecs={{{}}}", @@ -760,6 +787,7 @@ pub struct TypeSetBuilder { ints: Interval, floats: Interval, bools: Interval, + refs: Interval, bitvecs: Interval, includes_scalars: bool, simd_lanes: Interval, @@ -772,6 +800,7 @@ impl TypeSetBuilder { ints: Interval::None, floats: Interval::None, bools: Interval::None, + refs: Interval::None, bitvecs: Interval::None, includes_scalars: true, simd_lanes: Interval::None, @@ -794,6 +823,11 @@ impl TypeSetBuilder { self.bools = interval.into(); self } + pub fn refs(mut self, interval: impl Into) -> Self { + assert!(self.refs == Interval::None); + self.refs = interval.into(); + self + } pub fn includes_scalars(mut self, includes_scalars: bool) -> Self { self.includes_scalars = includes_scalars; self @@ -827,6 +861,7 @@ impl TypeSetBuilder { range_to_set(self.ints.to_range(8..MAX_BITS, None)), range_to_set(self.floats.to_range(32..64, None)), bools, + range_to_set(self.refs.to_range(32..64, None)), range_to_set(self.bitvecs.to_range(1..MAX_BITVEC, None)), self.specials, ) @@ -837,6 +872,7 @@ impl TypeSetBuilder { .ints(Interval::All) .floats(Interval::All) .bools(Interval::All) + .refs(Interval::All) .simd_lanes(Interval::All) .bitvecs(Interval::All) .specials(ValueType::all_special_types().collect()) diff --git a/cranelift/codegen/meta/src/gen_binemit.rs b/cranelift/codegen/meta/src/gen_binemit.rs index 71684ffb55..1bf95a83e0 100644 --- a/cranelift/codegen/meta/src/gen_binemit.rs +++ b/cranelift/codegen/meta/src/gen_binemit.rs @@ -18,10 +18,12 @@ fn gen_recipe(formats: &FormatRegistry, recipe: &EncodingRecipe, fmt: &mut Forma let inst_format = formats.get(recipe.format); let num_value_ops = inst_format.num_value_operands; - let want_args = recipe.operands_in.iter().any(|c| match c { - OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true, - OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false, - }); + // TODO: Set want_args to true for only MultiAry instructions instead of all formats with value list. + let want_args = inst_format.has_value_list + || recipe.operands_in.iter().any(|c| match c { + OperandConstraint::RegClass(_) | OperandConstraint::Stack(_) => true, + OperandConstraint::FixedReg(_) | OperandConstraint::TiedInput(_) => false, + }); assert!(!want_args || num_value_ops > 0 || inst_format.has_value_list); let want_outs = recipe.operands_out.iter().any(|c| match c { @@ -159,6 +161,7 @@ fn gen_isa(formats: &FormatRegistry, isa_name: &str, recipes: &Recipes, fmt: &mu fmt.line("inst: Inst,"); fmt.line("_divert: &mut RegDiversions,"); fmt.line("_sink: &mut CS,"); + fmt.line("_isa: &dyn TargetIsa,"); }); fmt.line(") {"); fmt.indent(|fmt| { @@ -176,6 +179,7 @@ fn gen_isa(formats: &FormatRegistry, isa_name: &str, recipes: &Recipes, fmt: &mu fmt.line("inst: Inst,"); fmt.line("divert: &mut RegDiversions,"); fmt.line("sink: &mut CS,"); + fmt.line("isa: &dyn TargetIsa,") }); fmt.line(") {"); diff --git a/cranelift/codegen/meta/src/gen_inst.rs b/cranelift/codegen/meta/src/gen_inst.rs index a3c2a9fccd..5dbb9dc095 100644 --- a/cranelift/codegen/meta/src/gen_inst.rs +++ b/cranelift/codegen/meta/src/gen_inst.rs @@ -650,6 +650,9 @@ fn typeset_to_string(ts: &TypeSet) -> String { if ts.specials.len() > 0 { result += &format!(", specials=[{}]", iterable_to_string(&ts.specials)); } + if ts.refs.len() > 0 { + result += &format!(", refs={}", iterable_to_string(&ts.refs)); + } result += ")"; result } @@ -677,6 +680,7 @@ pub fn gen_typesets_table(type_sets: &UniqueTable, fmt: &mut Formatter) gen_bitset(&ts.ints, "ints", 8, fmt); gen_bitset(&ts.floats, "floats", 8, fmt); gen_bitset(&ts.bools, "bools", 8, fmt); + gen_bitset(&ts.refs, "refs", 8, fmt); }); fmt.line("},"); } diff --git a/cranelift/codegen/meta/src/gen_types.rs b/cranelift/codegen/meta/src/gen_types.rs index 0a52eb371e..d4b4c60d65 100644 --- a/cranelift/codegen/meta/src/gen_types.rs +++ b/cranelift/codegen/meta/src/gen_types.rs @@ -54,6 +54,11 @@ fn emit_types(fmt: &mut srcgen::Formatter) -> Result<(), error::Error> { emit_type(&ty, fmt)?; } + // Emit all reference types. + for ty in cdsl_types::ValueType::all_reference_types().map(cdsl_types::ValueType::from) { + emit_type(&ty, fmt)?; + } + // Emit vector definitions for common SIMD sizes. for vec_size in &[64_u64, 128, 256, 512] { emit_vectors(*vec_size, fmt)?; diff --git a/cranelift/codegen/meta/src/isa/x86/encodings.rs b/cranelift/codegen/meta/src/isa/x86/encodings.rs index 7ddde02bec..eaa5614bd0 100644 --- a/cranelift/codegen/meta/src/isa/x86/encodings.rs +++ b/cranelift/codegen/meta/src/isa/x86/encodings.rs @@ -13,6 +13,7 @@ use crate::cdsl::types::ValueType; use crate::shared::types::Bool::{B1, B16, B32, B64, B8}; use crate::shared::types::Float::{F32, F64}; use crate::shared::types::Int::{I16, I32, I64, I8}; +use crate::shared::types::Reference::{R32, R64}; use crate::shared::Definitions as SharedDefinitions; use super::recipes::{RecipeGroup, Template}; @@ -175,6 +176,20 @@ impl PerCpuModeEncodings { }); } + /// Add encodings for `inst.r32` to X86_32. + /// Add encodings for `inst.r32` to X86_64 with and without REX. + /// Add encodings for `inst.r64` to X86_64 with a REX.W prefix. + fn enc_r32_r64(&mut self, inst: impl Into, template: Template) { + let inst: InstSpec = inst.into(); + self.enc32(inst.bind_ref(R32), template.nonrex()); + + // REX-less encoding must come after REX encoding so we don't use it by default. Otherwise + // reg-alloc would never use r8 and up. + self.enc64(inst.bind_ref(R32), template.rex()); + self.enc64(inst.bind_ref(R32), template.nonrex()); + self.enc64(inst.bind_ref(R64), template.rex().w()); + } + /// Add encodings for `inst` to X86_64 with and without a REX prefix. fn enc_x86_64(&mut self, inst: impl Into + Clone, template: Template) { // See above comment about the ordering of rex vs non-rex encodings. @@ -331,6 +346,7 @@ pub fn define( let ireduce = shared.by_name("ireduce"); let ishl = shared.by_name("ishl"); let ishl_imm = shared.by_name("ishl_imm"); + let is_null = shared.by_name("is_null"); let istore16 = shared.by_name("istore16"); let istore16_complex = shared.by_name("istore16_complex"); let istore32 = shared.by_name("istore32"); @@ -344,6 +360,7 @@ pub fn define( let load = shared.by_name("load"); let load_complex = shared.by_name("load_complex"); let nearest = shared.by_name("nearest"); + let null = shared.by_name("null"); let popcnt = shared.by_name("popcnt"); let raw_bitcast = shared.by_name("raw_bitcast"); let regfill = shared.by_name("regfill"); @@ -354,6 +371,7 @@ pub fn define( let rotl_imm = shared.by_name("rotl_imm"); let rotr = shared.by_name("rotr"); let rotr_imm = shared.by_name("rotr_imm"); + let safepoint = shared.by_name("safepoint"); let scalar_to_vector = shared.by_name("scalar_to_vector"); let selectif = shared.by_name("selectif"); let sextend = shared.by_name("sextend"); @@ -374,6 +392,7 @@ pub fn define( let trap = shared.by_name("trap"); let trapff = shared.by_name("trapff"); let trapif = shared.by_name("trapif"); + let resumable_trap = shared.by_name("resumable_trap"); let trueff = shared.by_name("trueff"); let trueif = shared.by_name("trueif"); let trunc = shared.by_name("trunc"); @@ -455,6 +474,7 @@ pub fn define( let rec_icscc_ib = r.template("icscc_ib"); let rec_icscc_id = r.template("icscc_id"); let rec_indirect_jmp = r.template("indirect_jmp"); + let rec_is_zero = r.template("is_zero"); let rec_jmpb = r.template("jmpb"); let rec_jmpd = r.template("jmpd"); let rec_jt_base = r.template("jt_base"); @@ -473,6 +493,7 @@ pub fn define( let rec_popq = r.template("popq"); let rec_pu_id = r.template("pu_id"); let rec_pu_id_bool = r.template("pu_id_bool"); + let rec_pu_id_ref = r.template("pu_id_ref"); let rec_pu_iq = r.template("pu_iq"); let rec_pushq = r.template("pushq"); let rec_ret = r.template("ret"); @@ -492,6 +513,7 @@ pub fn define( let rec_rmov = r.template("rmov"); let rec_rr = r.template("rr"); let rec_rrx = r.template("rrx"); + let rec_safepoint = r.recipe("safepoint"); let rec_setf_abcd = r.template("setf_abcd"); let rec_seti_abcd = r.template("seti_abcd"); let rec_spaddr4_id = r.template("spaddr4_id"); @@ -566,6 +588,7 @@ pub fn define( e.enc_i32_i64(x86_umulx, rec_mulx.opcodes(vec![0xf7]).rrr(4)); e.enc_i32_i64(copy, rec_umr.opcodes(vec![0x89])); + e.enc_r32_r64(copy, rec_umr.opcodes(vec![0x89])); e.enc_both(copy.bind(B1), rec_umr.opcodes(vec![0x89])); e.enc_both(copy.bind(I8), rec_umr.opcodes(vec![0x89])); e.enc_both(copy.bind(I16), rec_umr.opcodes(vec![0x89])); @@ -579,6 +602,12 @@ pub fn define( e.enc64(regmove.bind(I64), rec_rmov.opcodes(vec![0x89]).rex().w()); e.enc_both(regmove.bind(B1), rec_rmov.opcodes(vec![0x89])); e.enc_both(regmove.bind(I8), rec_rmov.opcodes(vec![0x89])); + e.enc32(regmove.bind_ref(R32), rec_rmov.opcodes(vec![0x89])); + e.enc64(regmove.bind_ref(R32), rec_rmov.opcodes(vec![0x89]).rex()); + e.enc64( + regmove.bind_ref(R64), + rec_rmov.opcodes(vec![0x89]).rex().w(), + ); e.enc_i32_i64(iadd_imm, rec_r_ib.opcodes(vec![0x83]).rrr(0)); e.enc_i32_i64(iadd_imm, rec_r_id.opcodes(vec![0x81]).rrr(0)); @@ -841,6 +870,8 @@ pub fn define( e.enc_i32_i64(spill, rec_spillSib32.opcodes(vec![0x89])); e.enc_i32_i64(regspill, rec_regspill32.opcodes(vec![0x89])); + e.enc_r32_r64(spill, rec_spillSib32.opcodes(vec![0x89])); + e.enc_r32_r64(regspill, rec_regspill32.opcodes(vec![0x89])); // Use a 32-bit write for spilling `b1`, `i8` and `i16` to avoid // constraining the permitted registers. @@ -865,6 +896,8 @@ pub fn define( e.enc_i32_i64(fill, rec_fillSib32.opcodes(vec![0x8b])); e.enc_i32_i64(regfill, rec_regfill32.opcodes(vec![0x8b])); + e.enc_r32_r64(fill, rec_fillSib32.opcodes(vec![0x8b])); + e.enc_r32_r64(regfill, rec_regfill32.opcodes(vec![0x8b])); // Load 32 bits from `b1`, `i8` and `i16` spill slots. See `spill.b1` above. @@ -1248,6 +1281,8 @@ pub fn define( // Trap as ud2 e.enc32(trap, rec_trap.opcodes(vec![0x0f, 0x0b])); e.enc64(trap, rec_trap.opcodes(vec![0x0f, 0x0b])); + e.enc32(resumable_trap, rec_trap.opcodes(vec![0x0f, 0x0b])); + e.enc64(resumable_trap, rec_trap.opcodes(vec![0x0f, 0x0b])); // Debug trap as int3 e.enc32_rec(debugtrap, rec_debugtrap, 0); @@ -1666,5 +1701,20 @@ pub fn define( } } + // Reference type instructions + + // Null references implemented as iconst 0. + e.enc32(null.bind_ref(R32), rec_pu_id_ref.opcodes(vec![0xb8])); + + e.enc64(null.bind_ref(R64), rec_pu_id_ref.rex().opcodes(vec![0xb8])); + e.enc64(null.bind_ref(R64), rec_pu_id_ref.opcodes(vec![0xb8])); + + // is_null, implemented by testing whether the value is 0. + e.enc_r32_r64(is_null, rec_is_zero.opcodes(vec![0x85])); + + // safepoint instruction calls sink, no actual encoding. + e.enc32_rec(safepoint, rec_safepoint, 0); + e.enc64_rec(safepoint, rec_safepoint, 0); + e } diff --git a/cranelift/codegen/meta/src/isa/x86/recipes.rs b/cranelift/codegen/meta/src/isa/x86/recipes.rs index 11063e39b9..b948c0c2e5 100644 --- a/cranelift/codegen/meta/src/isa/x86/recipes.rs +++ b/cranelift/codegen/meta/src/isa/x86/recipes.rs @@ -888,6 +888,20 @@ pub fn define<'shared>( ), ); + // XX+rd id nullary with 0 as 32-bit immediate. Note no recipe predicate. + recipes.add_template_recipe( + EncodingRecipeBuilder::new("pu_id_ref", f_nullary, 4) + .operands_out(vec![gpr]) + .emit( + r#" + // The destination register is encoded in the low bits of the opcode. + // No ModR/M. + {{PUT_OP}}(bits | (out_reg0 & 7), rex1(out_reg0), sink); + sink.put4(0); + "#, + ), + ); + // XX+rd iq unary with 64-bit immediate. recipes.add_template_recipe( EncodingRecipeBuilder::new("pu_iq", f_unary_imm, 8) @@ -2851,5 +2865,28 @@ pub fn define<'shared>( ), ); + recipes.add_template_recipe( + EncodingRecipeBuilder::new("is_zero", f_unary, 2 + 2) + .operands_in(vec![gpr]) + .operands_out(vec![abcd]) + .emit( + r#" + // Test instruction. + {{PUT_OP}}(bits, rex2(in_reg0, in_reg0), sink); + modrm_rr(in_reg0, in_reg0, sink); + // Check ZF = 1 flag to see if register holds 0. + sink.put1(0x0f); + sink.put1(0x94); + modrm_rr(out_reg0, 0, sink); + "#, + ), + ); + + recipes.add_recipe(EncodingRecipeBuilder::new("safepoint", f_multiary, 0).emit( + r#" + sink.add_stackmap(args, func, isa); + "#, + )); + recipes } diff --git a/cranelift/codegen/meta/src/shared/instructions.rs b/cranelift/codegen/meta/src/shared/instructions.rs index 36a1a89fb5..dfeeec4906 100644 --- a/cranelift/codegen/meta/src/shared/instructions.rs +++ b/cranelift/codegen/meta/src/shared/instructions.rs @@ -85,6 +85,12 @@ pub fn define( TypeSetBuilder::new().ints(32..64).build(), ); + let Ref = &TypeVar::new( + "Ref", + "A scalar reference type", + TypeSetBuilder::new().refs(Interval::All).build(), + ); + let Testable = &TypeVar::new( "Testable", "A scalar boolean or integer type", @@ -118,11 +124,12 @@ pub fn define( let Any = &TypeVar::new( "Any", - "Any integer, float, or boolean scalar or vector type", + "Any integer, float, boolean, or reference scalar or vector type", TypeSetBuilder::new() .ints(Interval::All) .floats(Interval::All) .bools(Interval::All) + .refs(Interval::All) .simd_lanes(Interval::All) .includes_scalars(true) .build(), @@ -394,6 +401,19 @@ pub fn define( .can_trap(true), ); + ig.push( + Inst::new( + "resumable_trap", + r#" + A resumable trap. + + This instruction allows non-conditional traps to be used as non-terminal instructions. + "#, + ) + .operands_in(vec![code]) + .can_trap(true), + ); + ig.push( Inst::new( "trapnz", @@ -1068,6 +1088,20 @@ pub fn define( .operands_out(vec![a]), ); + let a = &operand_doc("a", Ref, "A constant reference null value"); + + ig.push( + Inst::new( + "null", + r#" + Null constant value for reference types. + + Create a scalar reference SSA value with a constant null value. + "#, + ) + .operands_out(vec![a]), + ); + ig.push(Inst::new( "nop", r#" @@ -1315,6 +1349,24 @@ pub fn define( .other_side_effects(true), ); + let N = &operand_doc( + "args", + variable_args, + "Variable number of args for Stackmap", + ); + + ig.push( + Inst::new( + "safepoint", + r#" + This instruction will provide live reference values at a point in + the function. It can only be used by the compiler. + "#, + ) + .operands_in(vec![N]) + .other_side_effects(true), + ); + let x = &operand_doc("x", TxN, "Vector to split"); let lo = &operand_doc("lo", &TxN.half_vector(), "Low-numbered lanes of `x`"); let hi = &operand_doc("hi", &TxN.half_vector(), "High-numbered lanes of `x`"); @@ -2578,6 +2630,23 @@ pub fn define( .operands_out(vec![a]), ); + let a = &operand("a", b1); + let x = &operand("x", Ref); + + ig.push( + Inst::new( + "is_null", + r#" + Reference verification. + + The condition code determines if the reference type in question is + null or not. + "#, + ) + .operands_in(vec![x]) + .operands_out(vec![a]), + ); + let Cond = &operand("Cond", intcc); let f = &operand("f", iflags); let a = &operand("a", b1); diff --git a/cranelift/codegen/meta/src/shared/settings.rs b/cranelift/codegen/meta/src/shared/settings.rs index 14ace5de14..6561ee9856 100644 --- a/cranelift/codegen/meta/src/shared/settings.rs +++ b/cranelift/codegen/meta/src/shared/settings.rs @@ -92,6 +92,18 @@ pub fn define() -> SettingGroup { true, ); + settings.add_bool( + "enable_safepoints", + r#" + Enable safepoint instruction insertions. + + This will allow the emit_stackmaps() function to insert the safepoint + instruction on top of calls and interrupt traps in order to display the + live reference values at that point in the program. + "#, + false, + ); + // Settings specific to the `baldrdash` calling convention. settings.add_enum( diff --git a/cranelift/codegen/meta/src/shared/types.rs b/cranelift/codegen/meta/src/shared/types.rs index b327da67db..266c30b3d8 100644 --- a/cranelift/codegen/meta/src/shared/types.rs +++ b/cranelift/codegen/meta/src/shared/types.rs @@ -145,6 +145,38 @@ impl Iterator for FlagIterator { } } +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub enum Reference { + /// 32-bit reference. + R32 = 32, + /// 64-bit reference. + R64 = 64, +} + +/// This provides an iterator through all of the supported reference variants. +pub struct ReferenceIterator { + index: u8, +} + +impl ReferenceIterator { + pub fn new() -> Self { + Self { index: 0 } + } +} + +impl Iterator for ReferenceIterator { + type Item = Reference; + fn next(&mut self) -> Option { + let res = match self.index { + 0 => Some(Reference::R32), + 1 => Some(Reference::R64), + _ => return None, + }; + self.index += 1; + res + } +} + #[cfg(test)] mod iter_tests { use super::*; @@ -185,4 +217,12 @@ mod iter_tests { assert_eq!(flag_iter.next(), Some(Flag::FFlags)); assert_eq!(flag_iter.next(), None); } + + #[test] + fn reference_iter_works() { + let mut reference_iter = ReferenceIterator::new(); + assert_eq!(reference_iter.next(), Some(Reference::R32)); + assert_eq!(reference_iter.next(), Some(Reference::R64)); + assert_eq!(reference_iter.next(), None); + } } diff --git a/cranelift/codegen/src/binemit/memorysink.rs b/cranelift/codegen/src/binemit/memorysink.rs index b70b4a2c86..cde6cd9c9a 100644 --- a/cranelift/codegen/src/binemit/memorysink.rs +++ b/cranelift/codegen/src/binemit/memorysink.rs @@ -13,9 +13,11 @@ //! that a `MemoryCodeSink` will always write binary machine code to raw memory. It forwards any //! relocations to a `RelocSink` trait object. Relocations are less frequent than the //! `CodeSink::put*` methods, so the performance impact of the virtual callbacks is less severe. - use super::{Addend, CodeInfo, CodeOffset, CodeSink, Reloc}; -use crate::ir::{ExternalName, JumpTable, SourceLoc, TrapCode}; +use crate::binemit::stackmap::Stackmap; +use crate::ir::entities::Value; +use crate::ir::{ExternalName, Function, JumpTable, SourceLoc, TrapCode}; +use crate::isa::TargetIsa; use core::ptr::write_unaligned; /// A `CodeSink` that writes binary machine code directly into memory. @@ -36,6 +38,7 @@ pub struct MemoryCodeSink<'a> { offset: isize, relocs: &'a mut dyn RelocSink, traps: &'a mut dyn TrapSink, + stackmaps: &'a mut dyn StackmapSink, /// Information about the generated code and read-only data. pub info: CodeInfo, } @@ -49,6 +52,7 @@ impl<'a> MemoryCodeSink<'a> { data: *mut u8, relocs: &'a mut dyn RelocSink, traps: &'a mut dyn TrapSink, + stackmaps: &'a mut dyn StackmapSink, ) -> Self { Self { data, @@ -61,6 +65,7 @@ impl<'a> MemoryCodeSink<'a> { }, relocs, traps, + stackmaps, } } } @@ -149,6 +154,12 @@ impl<'a> CodeSink for MemoryCodeSink<'a> { self.info.rodata_size = self.offset() - (self.info.jumptables_size + self.info.code_size); self.info.total_size = self.offset(); } + + fn add_stackmap(&mut self, val_list: &[Value], func: &Function, isa: &dyn TargetIsa) { + let ofs = self.offset(); + let stackmap = Stackmap::from_values(&val_list, func, isa); + self.stackmaps.add_stackmap(ofs, stackmap); + } } /// A `TrapSink` implementation that does nothing, which is convenient when @@ -158,3 +169,16 @@ pub struct NullTrapSink {} impl TrapSink for NullTrapSink { fn trap(&mut self, _offset: CodeOffset, _srcloc: SourceLoc, _code: TrapCode) {} } + +/// A trait for emitting stackmaps. +pub trait StackmapSink { + /// Output a bitmap of the stack representing the live reference variables at this code offset. + fn add_stackmap(&mut self, _: CodeOffset, _: Stackmap); +} + +/// Placeholder StackmapSink that does nothing. +pub struct NullStackmapSink {} + +impl StackmapSink for NullStackmapSink { + fn add_stackmap(&mut self, _: CodeOffset, _: Stackmap) {} +} diff --git a/cranelift/codegen/src/binemit/mod.rs b/cranelift/codegen/src/binemit/mod.rs index b0408e97e7..d6b72553bb 100644 --- a/cranelift/codegen/src/binemit/mod.rs +++ b/cranelift/codegen/src/binemit/mod.rs @@ -6,13 +6,18 @@ mod memorysink; mod relaxation; mod shrink; +mod stackmap; -pub use self::memorysink::{MemoryCodeSink, NullTrapSink, RelocSink, TrapSink}; +pub use self::memorysink::{ + MemoryCodeSink, NullStackmapSink, NullTrapSink, RelocSink, StackmapSink, TrapSink, +}; pub use self::relaxation::relax_branches; pub use self::shrink::shrink_instructions; -pub use crate::regalloc::RegDiversions; - +pub use self::stackmap::Stackmap; +use crate::ir::entities::Value; use crate::ir::{ExternalName, Function, Inst, JumpTable, SourceLoc, TrapCode}; +use crate::isa::TargetIsa; +pub use crate::regalloc::RegDiversions; use core::fmt; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; @@ -141,6 +146,9 @@ pub trait CodeSink { /// Read-only data output is complete, we're done. fn end_codegen(&mut self); + + /// Add a stackmap at the current code offset. + fn add_stackmap(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa); } /// Report a bad encoding error. @@ -157,17 +165,17 @@ pub fn bad_encoding(func: &Function, inst: Inst) -> ! { /// /// This function is called from the `TargetIsa::emit_function()` implementations with the /// appropriate instruction emitter. -pub fn emit_function(func: &Function, emit_inst: EI, sink: &mut CS) +pub fn emit_function(func: &Function, emit_inst: EI, sink: &mut CS, isa: &dyn TargetIsa) where CS: CodeSink, - EI: Fn(&Function, Inst, &mut RegDiversions, &mut CS), + EI: Fn(&Function, Inst, &mut RegDiversions, &mut CS, &dyn TargetIsa), { let mut divert = RegDiversions::new(); for ebb in func.layout.ebbs() { divert.clear(); debug_assert_eq!(func.offsets[ebb], sink.offset()); for inst in func.layout.ebb_insts(ebb) { - emit_inst(func, inst, &mut divert, sink); + emit_inst(func, inst, &mut divert, sink, isa); } } diff --git a/cranelift/codegen/src/binemit/stackmap.rs b/cranelift/codegen/src/binemit/stackmap.rs new file mode 100644 index 0000000000..8d0d2a3204 --- /dev/null +++ b/cranelift/codegen/src/binemit/stackmap.rs @@ -0,0 +1,122 @@ +use crate::bitset::BitSet; +use crate::ir; +use crate::isa::TargetIsa; +use std::vec::Vec; + +/// Wrapper class for longer bit vectors that cannot be represented by a single BitSet. +#[derive(Clone, Debug)] +pub struct Stackmap { + bitmap: Vec>, +} + +impl Stackmap { + /// Create a stackmap based on where references are located on a function's stack. + pub fn from_values( + args: &[ir::entities::Value], + func: &ir::Function, + isa: &dyn TargetIsa, + ) -> Self { + let loc = &func.locations; + let mut live_ref_in_stack_slot = std::collections::HashSet::new(); + // References can be in registers, and live registers values are pushed onto the stack before calls and traps. + // TODO: Implement register maps. If a register containing a reference is spilled and reused after a safepoint, + // it could contain a stale reference value if the garbage collector relocated the value. + for val in args { + if let Some(value_loc) = loc.get(*val) { + match *value_loc { + ir::ValueLoc::Stack(stack_slot) => live_ref_in_stack_slot.insert(stack_slot), + _ => false, + }; + } + } + + // SpiderMonkey stackmap structure: + // + + + + // Bit vector goes from lower addresses to higher addresses. + + // TODO: Get trap register layout from Spidermonkey and prepend to bitvector below. + let stack = &func.stack_slots; + let frame_size = stack.frame_size.unwrap(); + let word_size = ir::stackslot::StackSize::from(isa.pointer_bytes()); + let num_words = (frame_size / word_size) as usize; + let mut vec = std::vec::Vec::with_capacity(num_words); + + vec.resize(num_words, false); + + // Frame (includes spills and inbound args). + for (ss, ssd) in stack.iter() { + if live_ref_in_stack_slot.contains(&ss) { + // Assumption: greater magnitude of offset imply higher address. + let index = (((ssd.offset.unwrap().abs() as u32) - ssd.size) / word_size) as usize; + vec[index] = true; + } + } + + Stackmap::from_vec(&vec) + } + + /// Create a vec of Bitsets from a vec of bools. + pub fn from_vec(vec: &Vec) -> Self { + let mut rem = vec.len(); + let num_word = ((rem as f32) / 32.0).ceil() as usize; + let mut bitmap = Vec::with_capacity(num_word); + + for i in 0..num_word { + let mut curr_word = 0; + let count = if rem > 32 { 32 } else { rem }; + for j in 0..count { + if vec[i * 32 + j] { + curr_word |= 1 << j; + } + } + bitmap.push(BitSet::(curr_word)); + rem -= count; + } + Self { bitmap } + } + + /// Returns a specified bit. + pub fn get_bit(&self, bit_index: usize) -> bool { + assert!(bit_index < 32 * self.bitmap.len()); + let word_index = bit_index / 32; + let word_offset = (bit_index % 32) as u8; + self.bitmap[word_index].contains(word_offset) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stackmaps() { + let vec: Vec = Vec::new(); + assert!(Stackmap::from_vec(&vec).bitmap.is_empty()); + + let mut vec: [bool; 32] = Default::default(); + let set_true_idx = [5, 7, 24, 31]; + + for idx in set_true_idx.iter() { + vec[*idx] = true; + } + + let mut vec = vec.to_vec(); + assert_eq!( + vec![BitSet::(2164261024)], + Stackmap::from_vec(&vec).bitmap + ); + + vec.push(false); + vec.push(true); + let res = Stackmap::from_vec(&vec); + assert_eq!( + vec![BitSet::(2164261024), BitSet::(2)], + res.bitmap + ); + + assert!(res.get_bit(5)); + assert!(res.get_bit(31)); + assert!(res.get_bit(33)); + assert!(!res.get_bit(1)); + } +} diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs index a80d3c41e4..cbf412aa20 100644 --- a/cranelift/codegen/src/context.rs +++ b/cranelift/codegen/src/context.rs @@ -10,7 +10,8 @@ //! single ISA instance. use crate::binemit::{ - relax_branches, shrink_instructions, CodeInfo, MemoryCodeSink, RelocSink, TrapSink, + relax_branches, shrink_instructions, CodeInfo, MemoryCodeSink, RelocSink, StackmapSink, + TrapSink, }; use crate::dce::do_dce; use crate::dominator_tree::DominatorTree; @@ -100,12 +101,14 @@ impl Context { mem: &mut Vec, relocs: &mut dyn RelocSink, traps: &mut dyn TrapSink, + stackmaps: &mut dyn StackmapSink, ) -> CodegenResult { let info = self.compile(isa)?; let old_len = mem.len(); mem.resize(old_len + info.total_size as usize, 0); - let new_info = - unsafe { self.emit_to_memory(isa, mem.as_mut_ptr().add(old_len), relocs, traps) }; + let new_info = unsafe { + self.emit_to_memory(isa, mem.as_mut_ptr().add(old_len), relocs, traps, stackmaps) + }; debug_assert!(new_info == info); Ok(info) } @@ -168,9 +171,10 @@ impl Context { mem: *mut u8, relocs: &mut dyn RelocSink, traps: &mut dyn TrapSink, + stackmaps: &mut dyn StackmapSink, ) -> CodeInfo { let _tt = timing::binemit(); - let mut sink = MemoryCodeSink::new(mem, relocs, traps); + let mut sink = MemoryCodeSink::new(mem, relocs, traps, stackmaps); isa.emit_function_to_memory(&self.func, &mut sink); sink.info } diff --git a/cranelift/codegen/src/ir/instructions.rs b/cranelift/codegen/src/ir/instructions.rs index a94b0f3a60..30e8d14689 100644 --- a/cranelift/codegen/src/ir/instructions.rs +++ b/cranelift/codegen/src/ir/instructions.rs @@ -444,6 +444,8 @@ pub struct ValueTypeSet { pub floats: BitSet8, /// Allowed bool widths pub bools: BitSet8, + /// Allowed ref widths + pub refs: BitSet8, } impl ValueTypeSet { @@ -458,6 +460,8 @@ impl ValueTypeSet { self.floats.contains(l2b) } else if scalar.is_bool() { self.bools.contains(l2b) + } else if scalar.is_ref() { + self.refs.contains(l2b) } else { false } @@ -652,6 +656,7 @@ mod tests { ints: BitSet8::from_range(4, 7), floats: BitSet8::from_range(0, 0), bools: BitSet8::from_range(3, 7), + refs: BitSet8::from_range(5, 7), }; assert!(!vts.contains(I8)); assert!(vts.contains(I32)); @@ -661,6 +666,8 @@ mod tests { assert!(!vts.contains(B1)); assert!(vts.contains(B8)); assert!(vts.contains(B64)); + assert!(vts.contains(R32)); + assert!(vts.contains(R64)); assert_eq!(vts.example().to_string(), "i32"); let vts = ValueTypeSet { @@ -668,6 +675,7 @@ mod tests { ints: BitSet8::from_range(0, 0), floats: BitSet8::from_range(5, 7), bools: BitSet8::from_range(3, 7), + refs: BitSet8::from_range(0, 0), }; assert_eq!(vts.example().to_string(), "f32"); @@ -676,6 +684,7 @@ mod tests { ints: BitSet8::from_range(0, 0), floats: BitSet8::from_range(5, 7), bools: BitSet8::from_range(3, 7), + refs: BitSet8::from_range(0, 0), }; assert_eq!(vts.example().to_string(), "f32x2"); @@ -684,6 +693,7 @@ mod tests { ints: BitSet8::from_range(0, 0), floats: BitSet8::from_range(0, 0), bools: BitSet8::from_range(3, 7), + refs: BitSet8::from_range(0, 0), }; assert!(!vts.contains(B32X2)); assert!(vts.contains(B32X4)); @@ -695,8 +705,11 @@ mod tests { ints: BitSet8::from_range(3, 7), floats: BitSet8::from_range(0, 0), bools: BitSet8::from_range(0, 0), + refs: BitSet8::from_range(0, 0), }; assert!(vts.contains(I32)); assert!(vts.contains(I32X4)); + assert!(!vts.contains(R32)); + assert!(!vts.contains(R64)); } } diff --git a/cranelift/codegen/src/ir/types.rs b/cranelift/codegen/src/ir/types.rs index fd28b5bd7c..4eb72f3fc0 100644 --- a/cranelift/codegen/src/ir/types.rs +++ b/cranelift/codegen/src/ir/types.rs @@ -61,8 +61,8 @@ impl Type { B1 => 0, B8 | I8 => 3, B16 | I16 => 4, - B32 | I32 | F32 => 5, - B64 | I64 | F64 => 6, + B32 | I32 | F32 | R32 => 5, + B64 | I64 | F64 | R64 => 6, _ => 0, } } @@ -73,8 +73,8 @@ impl Type { B1 => 1, B8 | I8 => 8, B16 | I16 => 16, - B32 | I32 | F32 => 32, - B64 | I64 | F64 => 64, + B32 | I32 | F32 | R32 => 32, + B64 | I64 | F64 | R64 => 64, _ => 0, } } @@ -99,7 +99,7 @@ impl Type { /// Get a type with the same number of lanes as this type, but with the lanes replaced by /// booleans of the same size. /// - /// Scalar types are treated as vectors with one lane, so they are converted to the multi-bit + /// Lane types are treated as vectors with one lane, so they are converted to the multi-bit /// boolean types. pub fn as_bool_pedantic(self) -> Self { // Replace the low 4 bits with the boolean version, preserve the high 4 bits. @@ -108,6 +108,7 @@ impl Type { B16 | I16 => B16, B32 | I32 | F32 => B32, B64 | I64 | F64 => B64, + R32 | R64 => panic!("Reference types should not convert to bool"), _ => B1, }) } @@ -210,6 +211,14 @@ impl Type { } } + /// Is this a ref type? + pub fn is_ref(self) -> bool { + match self { + R32 | R64 => true, + _ => false, + } + } + /// Get log_2 of the number of lanes in this SIMD vector type. /// /// All SIMD types have a lane count that is a power of two and no larger than 256, so this @@ -301,6 +310,8 @@ impl Display for Type { write!(f, "f{}", self.lane_bits()) } else if self.is_vector() { write!(f, "{}x{}", self.lane_type(), self.lane_count()) + } else if self.is_ref() { + write!(f, "r{}", self.lane_bits()) } else { f.write_str(match *self { IFLAGS => "iflags", @@ -322,6 +333,8 @@ impl Debug for Type { write!(f, "types::F{}", self.lane_bits()) } else if self.is_vector() { write!(f, "{:?}X{}", self.lane_type(), self.lane_count()) + } else if self.is_ref() { + write!(f, "types::R{}", self.lane_bits()) } else { match *self { INVALID => write!(f, "types::INVALID"), @@ -366,6 +379,8 @@ mod tests { assert_eq!(B1, B1.by(8).unwrap().lane_type()); assert_eq!(I32, I32X4.lane_type()); assert_eq!(F64, F64X2.lane_type()); + assert_eq!(R32, R32.lane_type()); + assert_eq!(R64, R64.lane_type()); assert_eq!(INVALID.lane_bits(), 0); assert_eq!(IFLAGS.lane_bits(), 0); @@ -381,6 +396,8 @@ mod tests { assert_eq!(I64.lane_bits(), 64); assert_eq!(F32.lane_bits(), 32); assert_eq!(F64.lane_bits(), 64); + assert_eq!(R32.lane_bits(), 32); + assert_eq!(R64.lane_bits(), 64); } #[test] @@ -450,6 +467,8 @@ mod tests { assert_eq!(I64.to_string(), "i64"); assert_eq!(F32.to_string(), "f32"); assert_eq!(F64.to_string(), "f64"); + assert_eq!(R32.to_string(), "r32"); + assert_eq!(R64.to_string(), "r64"); } #[test] diff --git a/cranelift/codegen/src/isa/arm32/binemit.rs b/cranelift/codegen/src/isa/arm32/binemit.rs index 2ca78b4d6f..d74ee0911a 100644 --- a/cranelift/codegen/src/isa/arm32/binemit.rs +++ b/cranelift/codegen/src/isa/arm32/binemit.rs @@ -2,6 +2,7 @@ use crate::binemit::{bad_encoding, CodeSink}; use crate::ir::{Function, Inst}; +use crate::isa::TargetIsa; use crate::regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-arm32.rs")); diff --git a/cranelift/codegen/src/isa/arm32/mod.rs b/cranelift/codegen/src/isa/arm32/mod.rs index d4e2f32255..fde3339700 100644 --- a/cranelift/codegen/src/isa/arm32/mod.rs +++ b/cranelift/codegen/src/isa/arm32/mod.rs @@ -121,11 +121,11 @@ impl TargetIsa for Isa { divert: &mut regalloc::RegDiversions, sink: &mut dyn CodeSink, ) { - binemit::emit_inst(func, inst, divert, sink) + binemit::emit_inst(func, inst, divert, sink, self) } fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { - emit_function(func, binemit::emit_inst, sink) + emit_function(func, binemit::emit_inst, sink, self) } } diff --git a/cranelift/codegen/src/isa/arm64/binemit.rs b/cranelift/codegen/src/isa/arm64/binemit.rs index 05df67e5bb..4401b6d6f5 100644 --- a/cranelift/codegen/src/isa/arm64/binemit.rs +++ b/cranelift/codegen/src/isa/arm64/binemit.rs @@ -2,6 +2,7 @@ use crate::binemit::{bad_encoding, CodeSink}; use crate::ir::{Function, Inst}; +use crate::isa::TargetIsa; use crate::regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-arm64.rs")); diff --git a/cranelift/codegen/src/isa/arm64/mod.rs b/cranelift/codegen/src/isa/arm64/mod.rs index c24be19f69..d787524a6a 100644 --- a/cranelift/codegen/src/isa/arm64/mod.rs +++ b/cranelift/codegen/src/isa/arm64/mod.rs @@ -108,11 +108,11 @@ impl TargetIsa for Isa { divert: &mut regalloc::RegDiversions, sink: &mut dyn CodeSink, ) { - binemit::emit_inst(func, inst, divert, sink) + binemit::emit_inst(func, inst, divert, sink, self) } fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { - emit_function(func, binemit::emit_inst, sink) + emit_function(func, binemit::emit_inst, sink, self) } } diff --git a/cranelift/codegen/src/isa/riscv/binemit.rs b/cranelift/codegen/src/isa/riscv/binemit.rs index 64f3c00298..a1d2b82e12 100644 --- a/cranelift/codegen/src/isa/riscv/binemit.rs +++ b/cranelift/codegen/src/isa/riscv/binemit.rs @@ -2,7 +2,7 @@ use crate::binemit::{bad_encoding, CodeSink, Reloc}; use crate::ir::{Function, Inst, InstructionData}; -use crate::isa::{RegUnit, StackBaseMask, StackRef}; +use crate::isa::{RegUnit, StackBaseMask, StackRef, TargetIsa}; use crate::predicates::is_signed_int; use crate::regalloc::RegDiversions; use core::u32; diff --git a/cranelift/codegen/src/isa/riscv/mod.rs b/cranelift/codegen/src/isa/riscv/mod.rs index 3096ff69aa..233e92a3bb 100644 --- a/cranelift/codegen/src/isa/riscv/mod.rs +++ b/cranelift/codegen/src/isa/riscv/mod.rs @@ -115,11 +115,11 @@ impl TargetIsa for Isa { divert: &mut regalloc::RegDiversions, sink: &mut dyn CodeSink, ) { - binemit::emit_inst(func, inst, divert, sink) + binemit::emit_inst(func, inst, divert, sink, self) } fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { - emit_function(func, binemit::emit_inst, sink) + emit_function(func, binemit::emit_inst, sink, self) } } diff --git a/cranelift/codegen/src/isa/x86/binemit.rs b/cranelift/codegen/src/isa/x86/binemit.rs index ef44766d0b..8aa51c07b6 100644 --- a/cranelift/codegen/src/isa/x86/binemit.rs +++ b/cranelift/codegen/src/isa/x86/binemit.rs @@ -5,7 +5,7 @@ use super::registers::RU; use crate::binemit::{bad_encoding, CodeSink, Reloc}; use crate::ir::condcodes::{CondCode, FloatCC, IntCC}; use crate::ir::{Ebb, Function, Inst, InstructionData, JumpTable, Opcode, TrapCode}; -use crate::isa::{RegUnit, StackBase, StackBaseMask, StackRef}; +use crate::isa::{RegUnit, StackBase, StackBaseMask, StackRef, TargetIsa}; use crate::regalloc::RegDiversions; include!(concat!(env!("OUT_DIR"), "/binemit-x86.rs")); diff --git a/cranelift/codegen/src/isa/x86/mod.rs b/cranelift/codegen/src/isa/x86/mod.rs index dfac5cacc3..9244791ea8 100644 --- a/cranelift/codegen/src/isa/x86/mod.rs +++ b/cranelift/codegen/src/isa/x86/mod.rs @@ -131,11 +131,11 @@ impl TargetIsa for Isa { divert: &mut regalloc::RegDiversions, sink: &mut dyn CodeSink, ) { - binemit::emit_inst(func, inst, divert, sink) + binemit::emit_inst(func, inst, divert, sink, self) } fn emit_function_to_memory(&self, func: &ir::Function, sink: &mut MemoryCodeSink) { - emit_function(func, binemit::emit_inst, sink) + emit_function(func, binemit::emit_inst, sink, self) } fn prologue_epilogue(&self, func: &mut ir::Function) -> CodegenResult<()> { diff --git a/cranelift/codegen/src/regalloc/context.rs b/cranelift/codegen/src/regalloc/context.rs index cb4b4245f4..fe2e702647 100644 --- a/cranelift/codegen/src/regalloc/context.rs +++ b/cranelift/codegen/src/regalloc/context.rs @@ -13,6 +13,7 @@ use crate::regalloc::coloring::Coloring; use crate::regalloc::live_value_tracker::LiveValueTracker; use crate::regalloc::liveness::Liveness; use crate::regalloc::reload::Reload; +use crate::regalloc::safepoint::emit_stackmaps; use crate::regalloc::spilling::Spilling; use crate::regalloc::virtregs::VirtRegs; use crate::result::CodegenResult; @@ -192,6 +193,20 @@ impl Context { self.coloring .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + // This function runs after register allocation has taken + // place, meaning values have locations assigned already. + if isa.flags().enable_safepoints() { + emit_stackmaps(func, domtree, &self.liveness, &mut self.tracker, isa); + } else { + // Make sure no references are used. + for val in func.dfg.values() { + let ty = func.dfg.value_type(val); + if ty.lane_type().is_ref() { + panic!("reference types were found but safepoints were not enabled."); + } + } + } + if isa.flags().enable_verifier() { let ok = verify_context(func, cfg, domtree, isa, &mut errors).is_ok() && verify_liveness(isa, func, cfg, &self.liveness, &mut errors).is_ok() diff --git a/cranelift/codegen/src/regalloc/mod.rs b/cranelift/codegen/src/regalloc/mod.rs index dbec691602..cfa3ca7323 100644 --- a/cranelift/codegen/src/regalloc/mod.rs +++ b/cranelift/codegen/src/regalloc/mod.rs @@ -15,9 +15,11 @@ mod context; mod diversion; mod pressure; mod reload; +mod safepoint; mod solver; mod spilling; pub use self::context::Context; pub use self::diversion::RegDiversions; pub use self::register_set::RegisterSet; +pub use self::safepoint::emit_stackmaps; diff --git a/cranelift/codegen/src/regalloc/safepoint.rs b/cranelift/codegen/src/regalloc/safepoint.rs new file mode 100644 index 0000000000..9b27b6227d --- /dev/null +++ b/cranelift/codegen/src/regalloc/safepoint.rs @@ -0,0 +1,72 @@ +use crate::cursor::{Cursor, FuncCursor}; +use crate::dominator_tree::DominatorTree; +use crate::ir::{Function, InstBuilder, InstructionData, Opcode, TrapCode}; +use crate::isa::TargetIsa; +use crate::regalloc::live_value_tracker::LiveValueTracker; +use crate::regalloc::liveness::Liveness; +use std::vec::Vec; + +fn insert_and_encode_safepoint<'f>( + pos: &mut FuncCursor<'f>, + tracker: &LiveValueTracker, + isa: &dyn TargetIsa, +) { + // Iterate through all live values, collect only the references. + let live_ref_values = tracker + .live() + .iter() + .filter(|live_value| pos.func.dfg.value_type(live_value.value).is_ref()) + .map(|live_val| live_val.value) + .collect::>(); + + if !live_ref_values.is_empty() { + pos.ins().safepoint(&live_ref_values); + // Move cursor to the new safepoint instruction to encode it. + if let Some(inst) = pos.prev_inst() { + let ok = pos.func.update_encoding(inst, isa).is_ok(); + debug_assert!(ok); + } + // Restore cursor position. + pos.next_inst(); + } +} + +// The emit_stackmaps() function analyzes each instruction to retrieve the liveness of +// the defs and operands by traversing a function's ebbs in layout order. +pub fn emit_stackmaps( + func: &mut Function, + domtree: &DominatorTree, + liveness: &Liveness, + tracker: &mut LiveValueTracker, + isa: &dyn TargetIsa, +) { + let mut curr = func.layout.entry_block(); + + while let Some(ebb) = curr { + tracker.ebb_top(ebb, &func.dfg, liveness, &func.layout, domtree); + tracker.drop_dead_params(); + let mut pos = FuncCursor::new(func); + + // From the top of the ebb, step through the instructions. + pos.goto_top(ebb); + + while let Some(inst) = pos.next_inst() { + if let InstructionData::Trap { + code: TrapCode::Interrupt, + .. + } = &pos.func.dfg[inst] + { + insert_and_encode_safepoint(&mut pos, tracker, isa); + } else if pos.func.dfg[inst].opcode().is_call() { + insert_and_encode_safepoint(&mut pos, tracker, isa); + } else if pos.func.dfg[inst].opcode() == Opcode::Safepoint { + panic!("safepoint instruction can only be used by the compiler!"); + } + + // Process the instruction and get rid of dead values. + tracker.process_inst(inst, &pos.func.dfg, liveness); + tracker.drop_dead(inst); + } + curr = func.layout.next_ebb(ebb); + } +} diff --git a/cranelift/codegen/src/settings.rs b/cranelift/codegen/src/settings.rs index 75462869bc..f9bc9f6d1a 100644 --- a/cranelift/codegen/src/settings.rs +++ b/cranelift/codegen/src/settings.rs @@ -390,6 +390,7 @@ mod tests { enable_nan_canonicalization = false\n\ enable_simd = false\n\ enable_atomics = true\n\ + enable_safepoints = false\n\ allones_funcaddrs = false\n\ probestack_enabled = true\n\ probestack_func_adjusts_sp = false\n\ diff --git a/cranelift/codegen/src/verifier/mod.rs b/cranelift/codegen/src/verifier/mod.rs index a09a696fff..39055b1232 100644 --- a/cranelift/codegen/src/verifier/mod.rs +++ b/cranelift/codegen/src/verifier/mod.rs @@ -1672,9 +1672,12 @@ impl<'a> Verifier<'a> { // Instructions with side effects are not allowed to be ghost instructions. let opcode = self.func.dfg[inst].opcode(); - // The `fallthrough` and `fallthrough_return` instructions are marked as terminators and - // branches, but they are not required to have an encoding. - if opcode == Opcode::Fallthrough || opcode == Opcode::FallthroughReturn { + // The `fallthrough`, `fallthrough_return`, and `safepoint` instructions are not required + // to have an encoding. + if opcode == Opcode::Fallthrough + || opcode == Opcode::FallthroughReturn + || opcode == Opcode::Safepoint + { return Ok(()); } @@ -1739,6 +1742,24 @@ impl<'a> Verifier<'a> { } } + fn verify_safepoint_unused( + &self, + inst: Inst, + errors: &mut VerifierErrors, + ) -> VerifierStepResult<()> { + if let Some(isa) = self.isa { + if !isa.flags().enable_safepoints() && self.func.dfg[inst].opcode() == Opcode::Safepoint + { + return fatal!( + errors, + inst, + "safepoint instruction cannot be used when it is not enabled." + ); + } + } + Ok(()) + } + pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> { self.verify_global_values(errors)?; self.verify_heaps(errors)?; @@ -1750,6 +1771,7 @@ impl<'a> Verifier<'a> { for inst in self.func.layout.ebb_insts(ebb) { self.ebb_integrity(ebb, inst, errors)?; self.instruction_integrity(inst, errors)?; + self.verify_safepoint_unused(inst, errors)?; self.typecheck(inst, errors)?; self.verify_encoding(inst, errors)?; self.immediate_constraints(inst, errors)?; diff --git a/cranelift/faerie/src/backend.rs b/cranelift/faerie/src/backend.rs index 57dc21376a..bed56122f0 100644 --- a/cranelift/faerie/src/backend.rs +++ b/cranelift/faerie/src/backend.rs @@ -2,7 +2,9 @@ use crate::container; use crate::traps::{FaerieTrapManifest, FaerieTrapSink}; -use cranelift_codegen::binemit::{Addend, CodeOffset, NullTrapSink, Reloc, RelocSink}; +use cranelift_codegen::binemit::{ + Addend, CodeOffset, NullStackmapSink, NullTrapSink, Reloc, RelocSink, Stackmap, StackmapSink, +}; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::{self, binemit, ir}; use cranelift_module::{ @@ -141,6 +143,8 @@ impl Backend for FaerieBackend { total_size: u32, ) -> ModuleResult { let mut code: Vec = vec![0; total_size as usize]; + // TODO: Replace this with FaerieStackmapSink once it is implemented. + let mut stackmap_sink = NullStackmapSink {}; // Non-lexical lifetimes would obviate the braces here. { @@ -160,6 +164,7 @@ impl Backend for FaerieBackend { code.as_mut_ptr(), &mut reloc_sink, &mut trap_sink, + &mut stackmap_sink, ) }; trap_manifest.add_sink(trap_sink); @@ -171,6 +176,7 @@ impl Backend for FaerieBackend { code.as_mut_ptr(), &mut reloc_sink, &mut trap_sink, + &mut stackmap_sink, ) }; } @@ -425,3 +431,16 @@ impl<'a> RelocSink for FaerieRelocSink<'a> { } } } + +#[allow(dead_code)] +struct FaerieStackmapSink<'a> { + artifact: &'a mut faerie::Artifact, + namespace: &'a ModuleNamespace<'a, FaerieBackend>, +} + +/// Faerie is currently not used in SpiderMonkey. Methods are unimplemented. +impl<'a> StackmapSink for FaerieStackmapSink<'a> { + fn add_stackmap(&mut self, _: CodeOffset, _: Stackmap) { + unimplemented!("faerie support for stackmaps"); + } +} diff --git a/cranelift/filetests/filetests/safepoint/basic.clif b/cranelift/filetests/filetests/safepoint/basic.clif new file mode 100644 index 0000000000..b47a5bf393 --- /dev/null +++ b/cranelift/filetests/filetests/safepoint/basic.clif @@ -0,0 +1,55 @@ +test safepoint + +set enable_safepoints=true +target x86_64 + +function %test(i32, r64, r64) -> r64 { + ebb0(v0: i32, v1:r64, v2:r64): + jump ebb1(v0) + ebb1(v3: i32): + v4 = irsub_imm v3, 1 + jump ebb2(v4) + ebb2(v5: i32): + resumable_trap interrupt + brz v5, ebb1(v5) + v6 = null.r64 + v7 = is_null v6 + brnz v7, ebb2(v0) + brnz v0, ebb3 + jump ebb4 + ebb3: + return v1 + ebb4: + return v2 +} + +; sameln: function %test(i32 [%rdi], r64 [%rsi], r64 [%rdx]) -> r64 [%rax] fast { +; nextln: ebb0(v0: i32 [%rdi], v1: r64 [%rsi], v2: r64 [%rdx]): +; nextln: v10 = copy v0 +; nextln: jump ebb1(v10) +; nextln: +; nextln: ebb1(v3: i32 [%rax]): +; nextln: v8 = iconst.i32 1 +; nextln: v4 = isub v8, v3 +; nextln: jump ebb2(v4) +; nextln: +; nextln: ebb2(v5: i32 [%rcx]): +; nextln: safepoint v1, v2 +; nextln: resumable_trap interrupt +; nextln: regmove v5, %rcx -> %rax +; nextln: brz v5, ebb1(v5) +; nextln: v6 = null.r64 +; nextln: v7 = is_null v6 +; nextln: v9 = copy.i32 v0 +; nextln: brnz v7, ebb2(v9) +; nextln: brnz.i32 v0, ebb3 +; nextln: jump ebb4 +; nextln: +; nextln: ebb3: +; nextln: regmove.r64 v1, %rsi -> %rax +; nextln: return v1 +; nextln: +; nextln: ebb4: +; nextln: regmove.r64 v2, %rdx -> %rax +; nextln: return v2 +; nextln: } diff --git a/cranelift/filetests/filetests/safepoint/call.clif b/cranelift/filetests/filetests/safepoint/call.clif new file mode 100644 index 0000000000..4b9de997e0 --- /dev/null +++ b/cranelift/filetests/filetests/safepoint/call.clif @@ -0,0 +1,52 @@ +test safepoint + +set enable_safepoints=true +target x86_64 + +function %direct() -> r64 { + fn0 = %none() + fn1 = %one() -> r64 + fn2 = %two() -> i32, r64 + +ebb0: + call fn0() + v1 = call fn1() + v2, v3 = call fn2() + brz v2, ebb1 + return v1 +ebb1: + v4 = call fn1() + return v3 +} + +; sameln: function %direct() -> r64 [%rax] fast { +; nextln: ss0 = spill_slot 8 +; nextln: ss1 = spill_slot 8 +; nextln: sig0 = () fast +; nextln: sig1 = () -> r64 [%rax] fast +; nextln: sig2 = () -> i32 [%rax], r64 [%rdx] fast +; nextln: fn0 = %none sig0 +; nextln: fn1 = %one sig1 +; nextln: fn2 = %two sig2 +; nextln: +; nextln: ebb0: +; nextln: v5 = func_addr.i64 fn0 +; nextln: call_indirect sig0, v5() +; nextln: v6 = func_addr.i64 fn1 +; nextln: v9 = call_indirect sig1, v6() +; nextln: v1 = spill v9 +; nextln: v7 = func_addr.i64 fn2 +; nextln: safepoint v1 +; nextln: v2, v10 = call_indirect sig2, v7() +; nextln: v3 = spill v10 +; nextln: brz v2, ebb1 +; nextln: v11 = fill v1 +; nextln: return v11 +; nextln: +; nextln: ebb1: +; nextln: v8 = func_addr.i64 fn1 +; nextln: safepoint v3 +; nextln: v4 = call_indirect sig1, v8() +; nextln: v12 = fill.r64 v3 +; nextln: return v12 +; nextln: } diff --git a/cranelift/filetests/src/lib.rs b/cranelift/filetests/src/lib.rs index fbd069bcb5..6099ce3726 100644 --- a/cranelift/filetests/src/lib.rs +++ b/cranelift/filetests/src/lib.rs @@ -46,6 +46,7 @@ mod test_postopt; mod test_preopt; mod test_print_cfg; mod test_regalloc; +mod test_safepoint; mod test_shrink; mod test_simple_gvn; mod test_simple_preopt; @@ -127,6 +128,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult test_simple_gvn::subtest(parsed), "verifier" => test_verifier::subtest(parsed), "preopt" => test_preopt::subtest(parsed), + "safepoint" => test_safepoint::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/cranelift/filetests/src/test_binemit.rs b/cranelift/filetests/src/test_binemit.rs index 3925acb8eb..f134806663 100644 --- a/cranelift/filetests/src/test_binemit.rs +++ b/cranelift/filetests/src/test_binemit.rs @@ -11,6 +11,7 @@ use cranelift_codegen::dominator_tree::DominatorTree; use cranelift_codegen::flowgraph::ControlFlowGraph; use cranelift_codegen::ir; use cranelift_codegen::ir::entities::AnyEntity; +use cranelift_codegen::isa; use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::settings::OptLevel; use cranelift_reader::TestCommand; @@ -100,9 +101,15 @@ impl binemit::CodeSink for TextSink { fn begin_jumptables(&mut self) { self.code_size = self.offset } - fn begin_rodata(&mut self) {} fn end_codegen(&mut self) {} + fn add_stackmap( + &mut self, + _: &[ir::entities::Value], + _: &ir::Function, + _: &dyn isa::TargetIsa, + ) { + } } impl SubTest for TestBinEmit { diff --git a/cranelift/filetests/src/test_compile.rs b/cranelift/filetests/src/test_compile.rs index e9150a73ee..706aa00331 100644 --- a/cranelift/filetests/src/test_compile.rs +++ b/cranelift/filetests/src/test_compile.rs @@ -6,6 +6,7 @@ use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use cranelift_codegen; use cranelift_codegen::binemit::{self, CodeInfo}; use cranelift_codegen::ir; +use cranelift_codegen::isa; use cranelift_codegen::print_errors::pretty_error; use cranelift_reader::TestCommand; use log::info; @@ -53,8 +54,9 @@ impl SubTest for TestCompile { let mut sink = SizeSink { offset: 0 }; binemit::emit_function( &comp_ctx.func, - |func, inst, div, sink| isa.emit_inst(func, inst, div, sink), + |func, inst, div, sink, isa| isa.emit_inst(func, inst, div, sink), &mut sink, + isa, ); if sink.offset != total_size { @@ -109,4 +111,11 @@ impl binemit::CodeSink for SizeSink { fn begin_jumptables(&mut self) {} fn begin_rodata(&mut self) {} fn end_codegen(&mut self) {} + fn add_stackmap( + &mut self, + _: &[ir::entities::Value], + _: &ir::Function, + _: &dyn isa::TargetIsa, + ) { + } } diff --git a/cranelift/filetests/src/test_safepoint.rs b/cranelift/filetests/src/test_safepoint.rs new file mode 100644 index 0000000000..cec7368ae8 --- /dev/null +++ b/cranelift/filetests/src/test_safepoint.rs @@ -0,0 +1,39 @@ +use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; +use cranelift_codegen::ir::Function; +use cranelift_codegen::print_errors::pretty_error; +use cranelift_reader::TestCommand; +use std::borrow::Cow; + +struct TestSafepoint; + +pub fn subtest(parsed: &TestCommand) -> SubtestResult> { + assert_eq!(parsed.command, "safepoint"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestSafepoint)) + } +} + +impl SubTest for TestSafepoint { + fn name(&self) -> &'static str { + "safepoint" + } + + fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { + let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); + + let isa = context.isa.expect("register allocator needs an ISA"); + comp_ctx.compute_cfg(); + comp_ctx + .legalize(isa) + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; + comp_ctx.compute_domtree(); + comp_ctx + .regalloc(isa) + .map_err(|e| pretty_error(&comp_ctx.func, context.isa, e))?; + + let text = comp_ctx.func.display(context.isa).to_string(); + run_filecheck(&text, context) + } +} diff --git a/cranelift/frontend/src/ssa.rs b/cranelift/frontend/src/ssa.rs index 0d48c314af..aa735f7747 100644 --- a/cranelift/frontend/src/ssa.rs +++ b/cranelift/frontend/src/ssa.rs @@ -221,6 +221,8 @@ fn emit_zero(ty: Type, mut cur: FuncCursor) -> Value { cur.ins().f32const(Ieee32::with_bits(0)) } else if ty == F64 { cur.ins().f64const(Ieee64::with_bits(0)) + } else if ty.is_ref() { + cur.ins().null(ty) } else if ty.is_vector() { let scalar_ty = ty.lane_type(); if scalar_ty.is_int() { diff --git a/cranelift/reader/src/lexer.rs b/cranelift/reader/src/lexer.rs index 8673b2b922..76327a7a8d 100644 --- a/cranelift/reader/src/lexer.rs +++ b/cranelift/reader/src/lexer.rs @@ -370,6 +370,8 @@ impl<'a> Lexer<'a> { "b16" => types::B16, "b32" => types::B32, "b64" => types::B64, + "r32" => types::R32, + "r64" => types::R64, _ => return None, }; if is_vector { diff --git a/cranelift/simplejit/src/backend.rs b/cranelift/simplejit/src/backend.rs index 16612d0a39..0a97df8f46 100644 --- a/cranelift/simplejit/src/backend.rs +++ b/cranelift/simplejit/src/backend.rs @@ -1,7 +1,9 @@ //! Defines `SimpleJITBackend`. use crate::memory::Memory; -use cranelift_codegen::binemit::{Addend, CodeOffset, NullTrapSink, Reloc, RelocSink}; +use cranelift_codegen::binemit::{ + Addend, CodeOffset, NullTrapSink, Reloc, RelocSink, Stackmap, StackmapSink, +}; use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::{self, ir, settings}; use cranelift_module::{ @@ -128,6 +130,13 @@ struct RelocRecord { addend: Addend, } +struct StackmapRecord { + #[allow(dead_code)] + offset: CodeOffset, + #[allow(dead_code)] + stackmap: Stackmap, +} + pub struct SimpleJITCompiledFunction { code: *mut u8, size: usize, @@ -254,7 +263,16 @@ impl<'simple_jit_backend> Backend for SimpleJITBackend { // Ignore traps for now. For now, frontends should just avoid generating code // that traps. let mut trap_sink = NullTrapSink {}; - unsafe { ctx.emit_to_memory(&*self.isa, ptr, &mut reloc_sink, &mut trap_sink) }; + let mut stackmap_sink = SimpleJITStackmapSink::new(); + unsafe { + ctx.emit_to_memory( + &*self.isa, + ptr, + &mut reloc_sink, + &mut trap_sink, + &mut stackmap_sink, + ) + }; Ok(Self::CompiledFunction { code: ptr, @@ -549,3 +567,21 @@ impl RelocSink for SimpleJITRelocSink { } } } + +struct SimpleJITStackmapSink { + pub stackmaps: Vec, +} + +impl SimpleJITStackmapSink { + pub fn new() -> Self { + Self { + stackmaps: Vec::new(), + } + } +} + +impl StackmapSink for SimpleJITStackmapSink { + fn add_stackmap(&mut self, offset: CodeOffset, stackmap: Stackmap) { + self.stackmaps.push(StackmapRecord { offset, stackmap }); + } +} diff --git a/cranelift/src/bugpoint.rs b/cranelift/src/bugpoint.rs index fcac964aa3..643ed414bc 100644 --- a/cranelift/src/bugpoint.rs +++ b/cranelift/src/bugpoint.rs @@ -1,6 +1,6 @@ //! CLI tool to reduce Cranelift IR files crashing during compilation. -use crate::disasm::{PrintRelocs, PrintTraps}; +use crate::disasm::{PrintRelocs, PrintStackmaps, PrintTraps}; use crate::utils::{parse_sets_and_triple, read_to_string}; use cranelift_codegen::ir::{ Ebb, FuncRef, Function, GlobalValueData, Inst, InstBuilder, InstructionData, StackSlots, @@ -730,11 +730,16 @@ impl<'a> CrashCheckContext<'a> { let res = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let mut relocs = PrintRelocs::new(false); let mut traps = PrintTraps::new(false); + let mut stackmaps = PrintStackmaps::new(false); let mut mem = vec![]; - let _ = self - .context - .compile_and_emit(self.isa, &mut mem, &mut relocs, &mut traps); + let _ = self.context.compile_and_emit( + self.isa, + &mut mem, + &mut relocs, + &mut traps, + &mut stackmaps, + ); })) { Ok(()) => CheckResult::Succeed, Err(err) => CheckResult::Crash(get_panic_string(err)), diff --git a/cranelift/src/compile.rs b/cranelift/src/compile.rs index 4c663323b1..934a955480 100644 --- a/cranelift/src/compile.rs +++ b/cranelift/src/compile.rs @@ -1,6 +1,6 @@ //! CLI tool to read Cranelift IR files and compile them into native code. -use crate::disasm::{print_all, PrintRelocs, PrintTraps}; +use crate::disasm::{print_all, PrintRelocs, PrintStackmaps, PrintTraps}; use crate::utils::{parse_sets_and_triple, read_to_string}; use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::settings::FlagsOrIsa; @@ -62,11 +62,12 @@ fn handle_module( let mut relocs = PrintRelocs::new(flag_print); let mut traps = PrintTraps::new(flag_print); + let mut stackmaps = PrintStackmaps::new(flag_print); let mut mem = vec![]; // Compile and encode the result to machine code. let code_info = context - .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps) + .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stackmaps) .map_err(|err| pretty_error(&context.func, Some(isa), err))?; if flag_print { @@ -81,6 +82,7 @@ fn handle_module( code_info.jumptables_size + code_info.rodata_size, &relocs, &traps, + &stackmaps, )?; } } diff --git a/cranelift/src/disasm.rs b/cranelift/src/disasm.rs index d7ba0dca6b..a24371206a 100644 --- a/cranelift/src/disasm.rs +++ b/cranelift/src/disasm.rs @@ -80,6 +80,28 @@ impl binemit::TrapSink for PrintTraps { } } +pub struct PrintStackmaps { + pub flag_print: bool, + pub text: String, +} + +impl PrintStackmaps { + pub fn new(flag_print: bool) -> PrintStackmaps { + Self { + flag_print, + text: String::new(), + } + } +} + +impl binemit::StackmapSink for PrintStackmaps { + fn add_stackmap(&mut self, offset: binemit::CodeOffset, _: binemit::Stackmap) { + if self.flag_print { + write!(&mut self.text, "add_stackmap at {}\n", offset).unwrap(); + } + } +} + cfg_if! { if #[cfg(feature = "disas")] { use capstone::prelude::*; @@ -170,11 +192,12 @@ pub fn print_all( rodata_size: u32, relocs: &PrintRelocs, traps: &PrintTraps, + stackmaps: &PrintStackmaps, ) -> Result<(), String> { print_bytes(&mem); print_disassembly(isa, &mem[0..code_size as usize])?; print_readonly_data(&mem[code_size as usize..(code_size + rodata_size) as usize]); - println!("\n{}\n{}", &relocs.text, &traps.text); + println!("\n{}\n{}\n{}", &relocs.text, &traps.text, &stackmaps.text); Ok(()) } diff --git a/cranelift/src/wasm.rs b/cranelift/src/wasm.rs index dadb13389f..f58c5ae863 100644 --- a/cranelift/src/wasm.rs +++ b/cranelift/src/wasm.rs @@ -7,7 +7,7 @@ allow(clippy::too_many_arguments, clippy::cyclomatic_complexity) )] -use crate::disasm::{print_all, PrintRelocs, PrintTraps}; +use crate::disasm::{print_all, PrintRelocs, PrintTraps, PrintStackmaps}; use crate::utils::{parse_sets_and_triple, read_to_end}; use cranelift_codegen::print_errors::{pretty_error, pretty_verifier_error}; use cranelift_codegen::settings::FlagsOrIsa; @@ -171,13 +171,14 @@ fn handle_module( let mut mem = vec![]; let mut relocs = PrintRelocs::new(flag_print); let mut traps = PrintTraps::new(flag_print); + let mut stackmaps = PrintStackmaps::new(flag_print); if flag_check_translation { if let Err(errors) = context.verify(fisa) { return Err(pretty_verifier_error(&context.func, fisa.isa, None, errors)); } } else { let code_info = context - .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps) + .compile_and_emit(isa, &mut mem, &mut relocs, &mut traps, &mut stackmaps) .map_err(|err| pretty_error(&context.func, fisa.isa, err))?; if flag_print_size { @@ -223,7 +224,7 @@ fn handle_module( } if let Some((code_size, rodata_size)) = saved_sizes { - print_all(isa, &mem, code_size, rodata_size, &relocs, &traps)?; + print_all(isa, &mem, code_size, rodata_size, &relocs, &traps, &stackmaps)?; } context.clear(); diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index 05c5de4f55..f3accb825b 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -834,6 +834,12 @@ pub fn translate_operator( Operator::F32Le | Operator::F64Le => { translate_fcmp(FloatCC::LessThanOrEqual, builder, state) } + Operator::RefNull => state.push1(builder.ins().null(environ.reference_type())), + Operator::RefIsNull => { + let arg = state.pop1(); + let val = builder.ins().is_null(arg); + state.push1(val); + } Operator::Wake { .. } | Operator::I32Wait { .. } | Operator::I64Wait { .. } @@ -902,9 +908,6 @@ pub fn translate_operator( | Operator::I64AtomicRmw32UCmpxchg { .. } => { wasm_unsupported!("proposed thread operator {:?}", op); } - Operator::RefNull | Operator::RefIsNull { .. } => { - wasm_unsupported!("proposed reference-type operator {:?}", op); - } Operator::MemoryInit { .. } | Operator::DataDrop { .. } | Operator::MemoryCopy diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index c94131fa9f..59208ea562 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -131,6 +131,17 @@ pub trait FuncEnvironment { ReturnMode::NormalReturns } + /// Get the Cranelift reference type to use for native references. + /// + /// This returns `R64` for 64-bit architectures and `R32` for 32-bit architectures. + fn reference_type(&self) -> ir::Type { + match self.pointer_type() { + ir::types::I32 => ir::types::R32, + ir::types::I64 => ir::types::R64, + _ => panic!("unsupported pointer type"), + } + } + /// Set up the necessary preamble definitions in `func` to access the global variable /// identified by `index`. /// diff --git a/cranelift/wasm/src/func_translator.rs b/cranelift/wasm/src/func_translator.rs index 7f62f5cfe0..b49cffd474 100644 --- a/cranelift/wasm/src/func_translator.rs +++ b/cranelift/wasm/src/func_translator.rs @@ -104,7 +104,7 @@ impl FuncTranslator { builder.append_ebb_params_for_function_returns(exit_block); self.state.initialize(&builder.func.signature, exit_block); - parse_local_decls(&mut reader, &mut builder, num_params)?; + parse_local_decls(&mut reader, &mut builder, num_params, environ)?; parse_function_body(reader, &mut builder, &mut self.state, environ)?; builder.finalize(); @@ -143,10 +143,11 @@ fn declare_wasm_parameters(builder: &mut FunctionBuilder, entry_block: Ebb) -> u /// Parse the local variable declarations that precede the function body. /// /// Declare local variables, starting from `num_params`. -fn parse_local_decls( +fn parse_local_decls( reader: &mut BinaryReader, builder: &mut FunctionBuilder, num_params: usize, + environ: &mut FE, ) -> WasmResult<()> { let mut next_local = num_params; let local_count = reader.read_local_count()?; @@ -155,7 +156,7 @@ fn parse_local_decls( for _ in 0..local_count { builder.set_srcloc(cur_srcloc(reader)); let (count, ty) = reader.read_local_decl(&mut locals_total)?; - declare_locals(builder, count, ty, &mut next_local)?; + declare_locals(builder, count, ty, &mut next_local, environ)?; } Ok(()) @@ -164,11 +165,12 @@ fn parse_local_decls( /// Declare `count` local variables of the same type, starting from `next_local`. /// /// Fail of too many locals are declared in the function, or if the type is not valid for a local. -fn declare_locals( +fn declare_locals( builder: &mut FunctionBuilder, count: u32, wasm_type: wasmparser::Type, next_local: &mut usize, + environ: &mut FE, ) -> WasmResult<()> { // All locals are initialized to 0. use wasmparser::Type::*; @@ -177,6 +179,7 @@ fn declare_locals( I64 => builder.ins().iconst(ir::types::I64, 0), F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)), F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)), + AnyRef => builder.ins().null(environ.reference_type()), ty => wasm_unsupported!("unsupported local type {:?}", ty), };