Add reference types R32 and R64

-Add resumable_trap, safepoint, isnull, and null instructions
-Add Stackmap struct and StackmapSink trait

Co-authored-by: Mir Ahmed <mirahmed753@gmail.com>
Co-authored-by: Dan Gohman <sunfish@mozilla.com>
This commit is contained in:
Carmen Kwan
2019-07-23 16:28:54 -07:00
committed by Dan Gohman
parent b659262d2a
commit 19257f80c1
47 changed files with 1027 additions and 62 deletions

View File

@@ -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<ReferenceType>) -> BoundInstruction {
bind_ref(self.clone(), Some(reference_type.into()), Vec::new())
}
pub fn bind_vector(&self, lane_type: impl Into<LaneType>, 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<ReferenceType>) -> BoundInstruction {
bind_ref(self.inst, Some(reference_type.into()), self.value_types)
}
pub fn bind_vector(self, lane_type: impl Into<LaneType>, 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<ReferenceType>) -> BoundInstruction {
match self {
InstSpec::Inst(inst) => inst.bind_ref(reference_type),
InstSpec::Bound(inst) => inst.clone().bind_ref(reference_type),
}
}
}
impl Into<InstSpec> 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<ReferenceType>,
mut value_types: Vec<ValueTypeOrAny>,
) -> 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,

View File

@@ -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<LaneType> for ValueType {
}
}
/// Create a ValueType from a given reference type.
impl From<ReferenceType> for ValueType {
fn from(reference: ReferenceType) -> Self {
ValueType::Reference(reference)
}
}
/// Create a ValueType from a given special type.
impl From<SpecialType> 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<shared_types::Reference> 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<Self::Item> {
if let Some(r) = self.reference_iter.next() {
Some(ReferenceType::from(r))
} else {
None
}
}
}

View File

@@ -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<SpecialType>,
}
@@ -416,6 +421,7 @@ impl TypeSet {
ints: NumSet,
floats: NumSet,
bools: NumSet,
refs: NumSet,
bitvecs: NumSet,
specials: Vec<SpecialType>,
) -> 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<SpecialType> = 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<Interval>) -> 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())

View File

@@ -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(") {");

View File

@@ -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<TypeSet>, 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("},");
}

View File

@@ -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)?;

View File

@@ -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<InstSpec>, 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<InstSpec> + 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
}

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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(

View File

@@ -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<Self::Item> {
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);
}
}

View File

@@ -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) {}
}

View File

@@ -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<CS, EI>(func: &Function, emit_inst: EI, sink: &mut CS)
pub fn emit_function<CS, EI>(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);
}
}

View File

@@ -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<BitSet<u32>>,
}
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:
// <trap reg dump> + <general spill> + <frame> + <inbound args>
// 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<bool>) -> 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::<u32>(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<bool> = 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::<u32>(2164261024)],
Stackmap::from_vec(&vec).bitmap
);
vec.push(false);
vec.push(true);
let res = Stackmap::from_vec(&vec);
assert_eq!(
vec![BitSet::<u32>(2164261024), BitSet::<u32>(2)],
res.bitmap
);
assert!(res.get_bit(5));
assert!(res.get_bit(31));
assert!(res.get_bit(33));
assert!(!res.get_bit(1));
}
}

View File

@@ -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<u8>,
relocs: &mut dyn RelocSink,
traps: &mut dyn TrapSink,
stackmaps: &mut dyn StackmapSink,
) -> CodegenResult<CodeInfo> {
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
}

View File

@@ -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));
}
}

View File

@@ -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]

View File

@@ -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"));

View File

@@ -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)
}
}

View File

@@ -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"));

View File

@@ -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)
}
}

View File

@@ -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;

View File

@@ -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)
}
}

View File

@@ -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"));

View File

@@ -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<()> {

View File

@@ -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()

View File

@@ -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;

View File

@@ -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::<Vec<_>>();
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);
}
}

View File

@@ -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\

View File

@@ -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)?;

View File

@@ -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<FaerieCompiledFunction> {
let mut code: Vec<u8> = 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");
}
}

View File

@@ -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: }

View File

@@ -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: }

View File

@@ -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<Box<dyn subtest::
"simple-gvn" => 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)),
}
}

View File

@@ -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 {

View File

@@ -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,
) {
}
}

View File

@@ -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<Box<SubTest>> {
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<Function>, 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)
}
}

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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<StackmapRecord>,
}
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 });
}
}

View File

@@ -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)),

View File

@@ -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,
)?;
}
}

View File

@@ -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(())
}

View File

@@ -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();

View File

@@ -834,6 +834,12 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
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<FE: FuncEnvironment + ?Sized>(
| 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

View File

@@ -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`.
///

View File

@@ -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<FE: FuncEnvironment + ?Sized>(
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<FE: FuncEnvironment + ?Sized>(
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),
};