Fix legalization of heap_addrs with 32-bit indices. (#480)

This makes several changes:
 - It adds an index_type to heap declarations, allowing heaps to specify the
   type for indexing. This also anticipates 64-bit heap support.

 - It adds a memory_type to deref global values, allowing deref globals to
   have types other than pointers. This is used to allow the bound variable
   in dynamic heaps to have type i32, to match the index type in heaps
   with i32 index type.

 - And, it fixes heap legalization to do the bounds check in the heap's
   index type.
This commit is contained in:
Dan Gohman
2018-08-28 13:37:33 -07:00
committed by GitHub
parent 00ddf3a7a6
commit eb439c9a68
13 changed files with 396 additions and 70 deletions

View File

@@ -1,7 +1,8 @@
//! Global values.
use ir::immediates::Offset32;
use ir::{ExternalName, GlobalValue};
use ir::{ExternalName, GlobalValue, Type};
use isa::TargetIsa;
use std::fmt;
/// Information about a global value declaration.
@@ -18,13 +19,16 @@ pub enum GlobalValueData {
///
/// The `base` global value is assumed to contain a pointer. This global value is computed
/// by loading from memory at that pointer value, and then adding an offset. The memory must
/// be accessible, and naturally aligned to hold a pointer value.
/// be accessible, and naturally aligned to hold a value of the type.
Deref {
/// The base pointer global value.
base: GlobalValue,
/// Byte offset to be added to the loaded value.
offset: Offset32,
/// Type of the loaded value.
memory_type: Type,
},
/// Value is identified by a symbolic name. Cranelift itself does not interpret this name;
@@ -48,13 +52,25 @@ impl GlobalValueData {
_ => panic!("only symbols have names"),
}
}
/// Return the type of this global.
pub fn global_type(&self, isa: &TargetIsa) -> Type {
match *self {
GlobalValueData::VMContext { .. } | GlobalValueData::Sym { .. } => isa.pointer_type(),
GlobalValueData::Deref { memory_type, .. } => memory_type,
}
}
}
impl fmt::Display for GlobalValueData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
GlobalValueData::VMContext { offset } => write!(f, "vmctx{}", offset),
GlobalValueData::Deref { base, offset } => write!(f, "deref({}){}", base, offset),
GlobalValueData::Deref {
base,
offset,
memory_type,
} => write!(f, "deref({}){}: {}", base, offset, memory_type),
GlobalValueData::Sym {
ref name,
colocated,

View File

@@ -1,7 +1,7 @@
//! Heaps.
use ir::immediates::Imm64;
use ir::GlobalValue;
use ir::{GlobalValue, Type};
use std::fmt;
/// Information about a heap declaration.
@@ -19,6 +19,9 @@ pub struct HeapData {
/// Heap style, with additional style-specific info.
pub style: HeapStyle,
/// The index type for the heap.
pub index_type: Type,
}
/// Style of heap including style-specific information.
@@ -50,6 +53,10 @@ impl fmt::Display for HeapData {
HeapStyle::Dynamic { bound_gv } => write!(f, ", bound {}", bound_gv)?,
HeapStyle::Static { bound } => write!(f, ", bound {}", bound)?,
}
write!(f, ", guard {}", self.guard_size)
write!(
f,
", guard {}, index_type {}",
self.guard_size, self.index_type
)
}
}

View File

@@ -13,7 +13,7 @@ pub fn expand_global_value(
inst: ir::Inst,
func: &mut ir::Function,
_cfg: &mut ControlFlowGraph,
_isa: &TargetIsa,
isa: &TargetIsa,
) {
// Unpack the instruction.
let gv = match func.dfg[inst] {
@@ -29,8 +29,12 @@ pub fn expand_global_value(
match func.global_values[gv] {
ir::GlobalValueData::VMContext { offset } => vmctx_addr(inst, func, offset.into()),
ir::GlobalValueData::Deref { base, offset } => deref_addr(inst, func, base, offset.into()),
ir::GlobalValueData::Sym { .. } => globalsym(inst, func, gv),
ir::GlobalValueData::Deref {
base,
offset,
memory_type,
} => deref_addr(inst, func, base, offset.into(), memory_type, isa),
ir::GlobalValueData::Sym { .. } => globalsym(inst, func, gv, isa),
}
}
@@ -46,11 +50,18 @@ fn vmctx_addr(inst: ir::Inst, func: &mut ir::Function, offset: i64) {
}
/// Expand a `global_value` instruction for a deref global.
fn deref_addr(inst: ir::Inst, func: &mut ir::Function, base: ir::GlobalValue, offset: i64) {
fn deref_addr(
inst: ir::Inst,
func: &mut ir::Function,
base: ir::GlobalValue,
offset: i64,
memory_type: ir::Type,
isa: &TargetIsa,
) {
// We need to load a pointer from the `base` global value, so insert a new `global_value`
// instruction. This depends on the iterative legalization loop. Note that the IR verifier
// detects any cycles in the `deref` globals.
let ptr_ty = func.dfg.value_type(func.dfg.first_result(inst));
let ptr_ty = isa.pointer_type();
let mut pos = FuncCursor::new(func).at_inst(inst);
pos.use_srcloc(inst);
@@ -59,12 +70,12 @@ fn deref_addr(inst: ir::Inst, func: &mut ir::Function, base: ir::GlobalValue, of
// Deref globals are required to be accessible and aligned.
mflags.set_notrap();
mflags.set_aligned();
let base_ptr = pos.ins().load(ptr_ty, mflags, base_addr, 0);
pos.func.dfg.replace(inst).iadd_imm(base_ptr, offset);
let loaded = pos.ins().load(memory_type, mflags, base_addr, 0);
pos.func.dfg.replace(inst).iadd_imm(loaded, offset);
}
/// Expand a `global_value` instruction for a symbolic name global.
fn globalsym(inst: ir::Inst, func: &mut ir::Function, gv: ir::GlobalValue) {
let ptr_ty = func.dfg.value_type(func.dfg.first_result(inst));
fn globalsym(inst: ir::Inst, func: &mut ir::Function, gv: ir::GlobalValue, isa: &TargetIsa) {
let ptr_ty = isa.pointer_type();
func.dfg.replace(inst).globalsym_addr(ptr_ty, gv);
}

View File

@@ -57,7 +57,7 @@ fn dynamic_addr(
pos.use_srcloc(inst);
// Start with the bounds check. Trap if `offset + access_size > bound`.
let bound = pos.ins().global_value(addr_ty, bound_gv);
let bound = pos.ins().global_value(offset_ty, bound_gv);
let oob;
if access_size == 1 {
// `offset > bound - 1` is the same as `offset >= bound`.

View File

@@ -348,14 +348,33 @@ impl<'a> Verifier<'a> {
cur = base;
}
if let ir::GlobalValueData::VMContext { .. } = self.func.global_values[cur] {
if self
.func
.special_param(ir::ArgumentPurpose::VMContext)
.is_none()
{
report!(errors, cur, "undeclared vmctx reference {}", cur);
match self.func.global_values[gv] {
ir::GlobalValueData::VMContext { .. } => {
if self
.func
.special_param(ir::ArgumentPurpose::VMContext)
.is_none()
{
report!(errors, gv, "undeclared vmctx reference {}", gv);
}
}
ir::GlobalValueData::Deref { base, .. } => {
if let Some(isa) = self.isa {
let base_type = self.func.global_values[base].global_type(isa);
let pointer_type = isa.pointer_type();
if base_type != pointer_type {
report!(
errors,
gv,
"deref base {} has type {}, which is not the pointer type {}",
base,
base_type,
pointer_type
);
}
}
}
_ => {}
}
}
@@ -363,6 +382,57 @@ impl<'a> Verifier<'a> {
Ok(())
}
fn verify_heaps(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
if let Some(isa) = self.isa {
for (heap, heap_data) in &self.func.heaps {
let base = heap_data.base;
if !self.func.global_values.is_valid(base) {
return nonfatal!(errors, heap, "invalid base global value {}", base);
}
let pointer_type = isa.pointer_type();
let base_type = self.func.global_values[base].global_type(isa);
if base_type != pointer_type {
report!(
errors,
heap,
"heap base has type {}, which is not the pointer type {}",
base_type,
pointer_type
);
}
match heap_data.style {
ir::HeapStyle::Dynamic { bound_gv, .. } => {
if !self.func.global_values.is_valid(bound_gv) {
return nonfatal!(
errors,
heap,
"invalid bound global value {}",
bound_gv
);
}
let index_type = heap_data.index_type;
let bound_type = self.func.global_values[bound_gv].global_type(isa);
if index_type != bound_type {
report!(
errors,
heap,
"heap index type {} differs from the type of its bound, {}",
index_type,
bound_type
);
}
}
_ => {}
}
}
}
Ok(())
}
fn ebb_integrity(
&self,
ebb: Ebb,
@@ -1255,51 +1325,82 @@ impl<'a> Verifier<'a> {
ctrl_type: Type,
errors: &mut VerifierErrors,
) -> VerifierStepResult<()> {
if let ir::InstructionData::Unary { opcode, arg } = self.func.dfg[inst] {
let arg_type = self.func.dfg.value_type(arg);
match opcode {
Opcode::Bextend | Opcode::Uextend | Opcode::Sextend | Opcode::Fpromote => {
if arg_type.lane_count() != ctrl_type.lane_count() {
return nonfatal!(
errors,
inst,
"input {} and output {} must have same number of lanes",
arg_type,
ctrl_type
);
match self.func.dfg[inst] {
ir::InstructionData::Unary { opcode, arg } => {
let arg_type = self.func.dfg.value_type(arg);
match opcode {
Opcode::Bextend | Opcode::Uextend | Opcode::Sextend | Opcode::Fpromote => {
if arg_type.lane_count() != ctrl_type.lane_count() {
return nonfatal!(
errors,
inst,
"input {} and output {} must have same number of lanes",
arg_type,
ctrl_type
);
}
if arg_type.lane_bits() >= ctrl_type.lane_bits() {
return nonfatal!(
errors,
inst,
"input {} must be smaller than output {}",
arg_type,
ctrl_type
);
}
}
if arg_type.lane_bits() >= ctrl_type.lane_bits() {
return nonfatal!(
errors,
inst,
"input {} must be smaller than output {}",
arg_type,
ctrl_type
);
Opcode::Breduce | Opcode::Ireduce | Opcode::Fdemote => {
if arg_type.lane_count() != ctrl_type.lane_count() {
return nonfatal!(
errors,
inst,
"input {} and output {} must have same number of lanes",
arg_type,
ctrl_type
);
}
if arg_type.lane_bits() <= ctrl_type.lane_bits() {
return nonfatal!(
errors,
inst,
"input {} must be larger than output {}",
arg_type,
ctrl_type
);
}
}
_ => {}
}
Opcode::Breduce | Opcode::Ireduce | Opcode::Fdemote => {
if arg_type.lane_count() != ctrl_type.lane_count() {
return nonfatal!(
errors,
inst,
"input {} and output {} must have same number of lanes",
arg_type,
ctrl_type
);
}
if arg_type.lane_bits() <= ctrl_type.lane_bits() {
return nonfatal!(
errors,
inst,
"input {} must be larger than output {}",
arg_type,
ctrl_type
);
}
}
_ => {}
}
ir::InstructionData::HeapAddr { heap, arg, .. } => {
let index_type = self.func.dfg.value_type(arg);
let heap_index_type = self.func.heaps[heap].index_type;
if index_type != heap_index_type {
return nonfatal!(
errors,
inst,
"index type {} differs from heap index type {}",
index_type,
heap_index_type
);
}
}
ir::InstructionData::UnaryGlobalValue { global_value, .. } => {
if let Some(isa) = self.isa {
let inst_type = self.func.dfg.value_type(self.func.dfg.first_result(inst));
let global_type = self.func.global_values[global_value].global_type(isa);
if inst_type != global_type {
return nonfatal!(
errors,
inst,
"global_value instruction with type {} references global value with type {}",
inst_type,
global_type
);
}
}
}
_ => {}
}
Ok(())
}
@@ -1509,6 +1610,7 @@ impl<'a> Verifier<'a> {
pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
self.verify_global_values(errors)?;
self.verify_heaps(errors)?;
self.typecheck_entry_block_params(errors)?;
for ebb in self.func.layout.ebbs() {