Reorganize the global value kinds. (#490)
* Reorganize the global value kinds. This: - renames "deref" global values to "load" and gives it a offset that works like the "load" instructions' does - adds an explicit "iadd_imm" global value kind, which replaces the builtin iadd in "vmctx" and "deref" global values. - also renames "globalsym" to "symbol"
This commit is contained in:
@@ -506,9 +506,9 @@ global_value = Instruction(
|
||||
|
||||
# A specialized form of global_value instructions that only handles
|
||||
# symbolic names.
|
||||
globalsym_addr = Instruction(
|
||||
'globalsym_addr', r"""
|
||||
Compute the address of global GV, which is a symbolic name.
|
||||
symbol_value = Instruction(
|
||||
'symbol_value', r"""
|
||||
Compute the value of global GV, which is a symbolic address.
|
||||
""",
|
||||
ins=GV, outs=addr)
|
||||
|
||||
|
||||
@@ -428,18 +428,18 @@ X86_64.enc(base.func_addr.i64, *r.got_fnaddr8.rex(0x8b, w=1),
|
||||
#
|
||||
|
||||
# Non-PIC
|
||||
X86_32.enc(base.globalsym_addr.i32, *r.gvaddr4(0xb8),
|
||||
X86_32.enc(base.symbol_value.i32, *r.gvaddr4(0xb8),
|
||||
isap=Not(is_pic))
|
||||
X86_64.enc(base.globalsym_addr.i64, *r.gvaddr8.rex(0xb8, w=1),
|
||||
X86_64.enc(base.symbol_value.i64, *r.gvaddr8.rex(0xb8, w=1),
|
||||
isap=Not(is_pic))
|
||||
|
||||
# PIC, colocated
|
||||
X86_64.enc(base.globalsym_addr.i64, *r.pcrel_gvaddr8.rex(0x8d, w=1),
|
||||
X86_64.enc(base.symbol_value.i64, *r.pcrel_gvaddr8.rex(0x8d, w=1),
|
||||
isap=is_pic,
|
||||
instp=IsColocatedData())
|
||||
|
||||
# PIC, non-colocated
|
||||
X86_64.enc(base.globalsym_addr.i64, *r.got_gvaddr8.rex(0x8b, w=1),
|
||||
X86_64.enc(base.symbol_value.i64, *r.got_gvaddr8.rex(0x8b, w=1),
|
||||
isap=is_pic)
|
||||
|
||||
#
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Global values.
|
||||
|
||||
use ir::immediates::Offset32;
|
||||
use ir::immediates::{Imm64, Offset32};
|
||||
use ir::{ExternalName, GlobalValue, Type};
|
||||
use isa::TargetIsa;
|
||||
use std::fmt;
|
||||
@@ -8,35 +8,47 @@ use std::fmt;
|
||||
/// Information about a global value declaration.
|
||||
#[derive(Clone)]
|
||||
pub enum GlobalValueData {
|
||||
/// Value is the address of a field in the VM context struct, a constant offset from the VM
|
||||
/// context pointer.
|
||||
VMContext {
|
||||
/// Offset from the `vmctx` pointer.
|
||||
offset: Offset32,
|
||||
},
|
||||
/// Value is the address of the VM context struct.
|
||||
VMContext,
|
||||
|
||||
/// Value is pointed to by another global value.
|
||||
///
|
||||
/// 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 value of the type.
|
||||
Deref {
|
||||
/// by loading from memory at that pointer value. The memory must be accessible, and
|
||||
/// naturally aligned to hold a value of the type.
|
||||
Load {
|
||||
/// The base pointer global value.
|
||||
base: GlobalValue,
|
||||
|
||||
/// Byte offset to be added to the loaded value.
|
||||
/// Offset added to the base pointer before doing the load.
|
||||
offset: Offset32,
|
||||
|
||||
/// Type of the loaded value.
|
||||
memory_type: Type,
|
||||
global_type: Type,
|
||||
},
|
||||
|
||||
/// Value is identified by a symbolic name. Cranelift itself does not interpret this name;
|
||||
/// Value is an offset from another global value.
|
||||
IAddImm {
|
||||
/// The base pointer global value.
|
||||
base: GlobalValue,
|
||||
|
||||
/// Byte offset to be added to the value.
|
||||
offset: Imm64,
|
||||
|
||||
/// Type of the iadd.
|
||||
global_type: Type,
|
||||
},
|
||||
|
||||
/// Value is a symbolic address. Cranelift itself does not interpret this name;
|
||||
/// it's used by embedders to link with other data structures.
|
||||
Sym {
|
||||
Symbol {
|
||||
/// The symbolic name.
|
||||
name: ExternalName,
|
||||
|
||||
/// Offset from the symbol. This can be used instead of IAddImm to represent folding an
|
||||
/// offset into a symbol.
|
||||
offset: Imm64,
|
||||
|
||||
/// Will this symbol be defined nearby, such that it will always be a certain distance
|
||||
/// away, after linking? If so, references to it can avoid going through a GOT. Note that
|
||||
/// symbols meant to be preemptible cannot be colocated.
|
||||
@@ -45,10 +57,10 @@ pub enum GlobalValueData {
|
||||
}
|
||||
|
||||
impl GlobalValueData {
|
||||
/// Assume that `self` is an `GlobalValueData::Sym` and return its name.
|
||||
/// Assume that `self` is an `GlobalValueData::Symbol` and return its name.
|
||||
pub fn symbol_name(&self) -> &ExternalName {
|
||||
match *self {
|
||||
GlobalValueData::Sym { ref name, .. } => name,
|
||||
GlobalValueData::Symbol { ref name, .. } => name,
|
||||
_ => panic!("only symbols have names"),
|
||||
}
|
||||
}
|
||||
@@ -56,8 +68,11 @@ impl GlobalValueData {
|
||||
/// 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,
|
||||
GlobalValueData::VMContext { .. } | GlobalValueData::Symbol { .. } => {
|
||||
isa.pointer_type()
|
||||
}
|
||||
GlobalValueData::IAddImm { global_type, .. }
|
||||
| GlobalValueData::Load { global_type, .. } => global_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,20 +80,34 @@ impl GlobalValueData {
|
||||
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 {
|
||||
GlobalValueData::VMContext => write!(f, "vmctx"),
|
||||
GlobalValueData::Load {
|
||||
base,
|
||||
offset,
|
||||
memory_type,
|
||||
} => write!(f, "deref({}){}: {}", base, offset, memory_type),
|
||||
GlobalValueData::Sym {
|
||||
global_type,
|
||||
} => write!(f, "load.{} notrap aligned {}{}", global_type, base, offset),
|
||||
GlobalValueData::IAddImm {
|
||||
global_type,
|
||||
base,
|
||||
offset,
|
||||
} => write!(f, "iadd_imm.{} {}, {}", global_type, base, offset),
|
||||
GlobalValueData::Symbol {
|
||||
ref name,
|
||||
offset,
|
||||
colocated,
|
||||
} => {
|
||||
if colocated {
|
||||
write!(f, "colocated ")?;
|
||||
}
|
||||
write!(f, "globalsym {}", name)
|
||||
write!(f, "symbol {}", name)?;
|
||||
let offset_val: i64 = offset.into();
|
||||
if offset_val > 0 {
|
||||
write!(f, "+")?;
|
||||
}
|
||||
if offset_val != 0 {
|
||||
write!(f, "{}", offset)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,16 @@ impl Offset32 {
|
||||
pub fn new(x: i32) -> Self {
|
||||
Offset32(x)
|
||||
}
|
||||
|
||||
/// Create a new `Offset32` representing the signed numver `x` if possible.
|
||||
pub fn try_from_i64(x: i64) -> Option<Self> {
|
||||
let casted = x as i32;
|
||||
if casted as i64 == x {
|
||||
Some(Self::new(casted))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<i32> for Offset32 {
|
||||
|
||||
@@ -28,54 +28,99 @@ pub fn expand_global_value(
|
||||
};
|
||||
|
||||
match func.global_values[gv] {
|
||||
ir::GlobalValueData::VMContext { offset } => vmctx_addr(inst, func, offset.into()),
|
||||
ir::GlobalValueData::Deref {
|
||||
ir::GlobalValueData::VMContext => vmctx_addr(inst, func),
|
||||
ir::GlobalValueData::IAddImm {
|
||||
base,
|
||||
offset,
|
||||
memory_type,
|
||||
} => deref_addr(inst, func, base, offset.into(), memory_type, isa),
|
||||
ir::GlobalValueData::Sym { .. } => globalsym(inst, func, gv, isa),
|
||||
global_type,
|
||||
} => iadd_imm_addr(inst, func, base, offset.into(), global_type),
|
||||
ir::GlobalValueData::Load {
|
||||
base,
|
||||
offset,
|
||||
global_type,
|
||||
} => load_addr(inst, func, base, offset, global_type, isa),
|
||||
ir::GlobalValueData::Symbol { .. } => symbol(inst, func, gv, isa),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand a `global_value` instruction for a vmctx global.
|
||||
fn vmctx_addr(inst: ir::Inst, func: &mut ir::Function, offset: i64) {
|
||||
fn vmctx_addr(inst: ir::Inst, func: &mut ir::Function) {
|
||||
// Get the value representing the `vmctx` argument.
|
||||
let vmctx = func
|
||||
.special_param(ir::ArgumentPurpose::VMContext)
|
||||
.expect("Missing vmctx parameter");
|
||||
|
||||
// Simply replace the `global_value` instruction with an `iadd_imm`, reusing the result value.
|
||||
func.dfg.replace(inst).iadd_imm(vmctx, offset);
|
||||
// Replace the `global_value` instruction's value with an alias to the vmctx arg.
|
||||
let result = func.dfg.first_result(inst);
|
||||
func.dfg.clear_results(inst);
|
||||
func.dfg.change_to_alias(result, vmctx);
|
||||
func.layout.remove_inst(inst);
|
||||
}
|
||||
|
||||
/// Expand a `global_value` instruction for a deref global.
|
||||
fn deref_addr(
|
||||
/// Expand a `global_value` instruction for an iadd_imm global.
|
||||
fn iadd_imm_addr(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
base: ir::GlobalValue,
|
||||
offset: i64,
|
||||
memory_type: ir::Type,
|
||||
global_type: ir::Type,
|
||||
) {
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
|
||||
// Get the value for the lhs. For tidiness, expand VMContext here so that we avoid
|
||||
// `vmctx_addr` which creates an otherwise unneeded value alias.
|
||||
let lhs = if let ir::GlobalValueData::VMContext = pos.func.global_values[base] {
|
||||
pos.func
|
||||
.special_param(ir::ArgumentPurpose::VMContext)
|
||||
.expect("Missing vmctx parameter")
|
||||
} else {
|
||||
pos.ins().global_value(global_type, base)
|
||||
};
|
||||
|
||||
// Simply replace the `global_value` instruction with an `iadd_imm`, reusing the result value.
|
||||
pos.func.dfg.replace(inst).iadd_imm(lhs, offset);
|
||||
}
|
||||
|
||||
/// Expand a `global_value` instruction for a load global.
|
||||
fn load_addr(
|
||||
inst: ir::Inst,
|
||||
func: &mut ir::Function,
|
||||
base: ir::GlobalValue,
|
||||
offset: ir::immediates::Offset32,
|
||||
global_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.
|
||||
// detects any cycles in the `load` globals.
|
||||
let ptr_ty = isa.pointer_type();
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
pos.use_srcloc(inst);
|
||||
|
||||
let base_addr = pos.ins().global_value(ptr_ty, base);
|
||||
// Get the value for the base. For tidiness, expand VMContext here so that we avoid
|
||||
// `vmctx_addr` which creates an otherwise unneeded value alias.
|
||||
let base_addr = if let ir::GlobalValueData::VMContext = pos.func.global_values[base] {
|
||||
pos.func
|
||||
.special_param(ir::ArgumentPurpose::VMContext)
|
||||
.expect("Missing vmctx parameter")
|
||||
} else {
|
||||
pos.ins().global_value(ptr_ty, base)
|
||||
};
|
||||
|
||||
// Global-value loads are always notrap and aligned.
|
||||
let mut mflags = ir::MemFlags::new();
|
||||
// Deref globals are required to be accessible and aligned.
|
||||
mflags.set_notrap();
|
||||
mflags.set_aligned();
|
||||
let loaded = pos.ins().load(memory_type, mflags, base_addr, 0);
|
||||
pos.func.dfg.replace(inst).iadd_imm(loaded, offset);
|
||||
|
||||
// Perform the load.
|
||||
pos.func
|
||||
.dfg
|
||||
.replace(inst)
|
||||
.load(global_type, mflags, base_addr, offset);
|
||||
}
|
||||
|
||||
/// Expand a `global_value` instruction for a symbolic name global.
|
||||
fn globalsym(inst: ir::Inst, func: &mut ir::Function, gv: ir::GlobalValue, isa: &TargetIsa) {
|
||||
fn symbol(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);
|
||||
func.dfg.replace(inst).symbol_value(ptr_ty, gv);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ pub fn is_colocated_func(func_ref: ir::FuncRef, func: &ir::Function) -> bool {
|
||||
#[allow(dead_code)]
|
||||
pub fn is_colocated_data(global_value: ir::GlobalValue, func: &ir::Function) -> bool {
|
||||
match func.global_values[global_value] {
|
||||
ir::GlobalValueData::Sym { colocated, .. } => colocated,
|
||||
ir::GlobalValueData::Symbol { colocated, .. } => colocated,
|
||||
_ => panic!("is_colocated_data only makes sense for data with symbolic addresses"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
//!
|
||||
//! Global values
|
||||
//!
|
||||
//! - Detect cycles in deref(base) declarations.
|
||||
//! - Detect cycles in global values.
|
||||
//! - Detect use of 'vmctx' global value when no corresponding parameter is defined.
|
||||
//!
|
||||
//! TODO:
|
||||
@@ -336,16 +336,28 @@ impl<'a> Verifier<'a> {
|
||||
seen.insert(gv);
|
||||
|
||||
let mut cur = gv;
|
||||
while let ir::GlobalValueData::Deref { base, .. } = self.func.global_values[cur] {
|
||||
if seen.insert(base).is_some() {
|
||||
if !cycle_seen {
|
||||
report!(errors, gv, "deref cycle: {}", DisplayList(seen.as_slice()));
|
||||
cycle_seen = true; // ensures we don't report the cycle multiple times
|
||||
}
|
||||
continue 'gvs;
|
||||
}
|
||||
loop {
|
||||
match self.func.global_values[cur] {
|
||||
ir::GlobalValueData::Load { base, .. }
|
||||
| ir::GlobalValueData::IAddImm { base, .. } => {
|
||||
if seen.insert(base).is_some() {
|
||||
if !cycle_seen {
|
||||
report!(
|
||||
errors,
|
||||
gv,
|
||||
"global value cycle: {}",
|
||||
DisplayList(seen.as_slice())
|
||||
);
|
||||
// ensures we don't report the cycle multiple times
|
||||
cycle_seen = true;
|
||||
}
|
||||
continue 'gvs;
|
||||
}
|
||||
|
||||
cur = base;
|
||||
cur = base;
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
match self.func.global_values[gv] {
|
||||
@@ -358,7 +370,30 @@ impl<'a> Verifier<'a> {
|
||||
report!(errors, gv, "undeclared vmctx reference {}", gv);
|
||||
}
|
||||
}
|
||||
ir::GlobalValueData::Deref { base, .. } => {
|
||||
ir::GlobalValueData::IAddImm {
|
||||
base, global_type, ..
|
||||
} => {
|
||||
if !global_type.is_int() {
|
||||
report!(
|
||||
errors,
|
||||
gv,
|
||||
"iadd_imm global value with non-int type {}",
|
||||
global_type
|
||||
);
|
||||
} else if let Some(isa) = self.isa {
|
||||
let base_type = self.func.global_values[base].global_type(isa);
|
||||
if global_type != base_type {
|
||||
report!(
|
||||
errors,
|
||||
gv,
|
||||
"iadd_imm type {} differs from operand type {}",
|
||||
global_type,
|
||||
base_type
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
ir::GlobalValueData::Load { base, .. } => {
|
||||
if let Some(isa) = self.isa {
|
||||
let base_type = self.func.global_values[base].global_type(isa);
|
||||
let pointer_type = isa.pointer_type();
|
||||
@@ -366,7 +401,7 @@ impl<'a> Verifier<'a> {
|
||||
report!(
|
||||
errors,
|
||||
gv,
|
||||
"deref base {} has type {}, which is not the pointer type {}",
|
||||
"base {} has type {}, which is not the pointer type {}",
|
||||
base,
|
||||
base_type,
|
||||
pointer_type
|
||||
|
||||
Reference in New Issue
Block a user