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:
Dan Gohman
2018-09-04 21:09:04 -07:00
committed by GitHub
parent 59b83912ba
commit ca9da7702e
30 changed files with 467 additions and 320 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -462,8 +462,9 @@ where
pub fn declare_data_in_func(&self, data: DataId, func: &mut ir::Function) -> ir::GlobalValue {
let decl = &self.contents.data_objects[data].decl;
let colocated = decl.linkage.is_final();
func.create_global_value(ir::GlobalValueData::Sym {
func.create_global_value(ir::GlobalValueData::Symbol {
name: ir::ExternalName::user(1, data.index() as u32),
offset: ir::immediates::Imm64::new(0),
colocated,
})
}

View File

@@ -164,8 +164,9 @@ impl<'a> Context<'a> {
fn add_gv(&mut self, gv: GlobalValue, data: GlobalValueData, loc: Location) -> ParseResult<()> {
self.map.def_gv(gv, loc)?;
while self.function.global_values.next_key().index() <= gv.index() {
self.function.create_global_value(GlobalValueData::Sym {
self.function.create_global_value(GlobalValueData::Symbol {
name: ExternalName::testcase(""),
offset: Imm64::new(0),
colocated: false,
});
}
@@ -590,14 +591,32 @@ impl<'a> Parser<'a> {
// present, it must contain a sign.
fn optional_offset32(&mut self) -> ParseResult<Offset32> {
if let Some(Token::Integer(text)) = self.token() {
self.consume();
// Lexer just gives us raw text that looks like an integer.
// Parse it as an `Offset32` to check for overflow and other issues.
text.parse().map_err(|e| self.error(e))
} else {
// An offset32 operand can be absent.
Ok(Offset32::new(0))
if text.starts_with('+') || text.starts_with('-') {
self.consume();
// Lexer just gives us raw text that looks like an integer.
// Parse it as an `Offset32` to check for overflow and other issues.
return text.parse().map_err(|e| self.error(e));
}
}
// An offset32 operand can be absent.
Ok(Offset32::new(0))
}
// Match and consume an optional offset32 immediate.
//
// Note that this will match an empty string as an empty offset, and that if an offset is
// present, it must contain a sign.
fn optional_offset_imm64(&mut self) -> ParseResult<Imm64> {
if let Some(Token::Integer(text)) = self.token() {
if text.starts_with('+') || text.starts_with('-') {
self.consume();
// Lexer just gives us raw text that looks like an integer.
// Parse it as an `Offset32` to check for overflow and other issues.
return text.parse().map_err(|e| self.error(e));
}
}
// If no explicit offset is present, the offset is 0.
Ok(Imm64::new(0))
}
// Match and consume an Ieee32 immediate.
@@ -1185,9 +1204,10 @@ impl<'a> Parser<'a> {
// Parse a global value decl.
//
// global-val-decl ::= * GlobalValue(gv) "=" global-val-desc
// global-val-desc ::= "vmctx" offset32
// | "deref" "(" GlobalValue(base) ")" offset32
// | globalsym ["colocated"] name
// global-val-desc ::= "vmctx"
// | "load" "." type "notrap" "aligned" GlobalValue(base) [offset]
// | "iadd_imm" "(" GlobalValue(base) ")" imm64
// | "symbol" ["colocated"] name + imm64
//
fn parse_global_value_decl(&mut self) -> ParseResult<(GlobalValue, GlobalValueData)> {
let gv = self.match_gv("expected global value number: gv«n»")?;
@@ -1195,27 +1215,55 @@ impl<'a> Parser<'a> {
self.match_token(Token::Equal, "expected '=' in global value declaration")?;
let data = match self.match_any_identifier("expected global value kind")? {
"vmctx" => {
let offset = self.optional_offset32()?;
GlobalValueData::VMContext { offset }
}
"deref" => {
self.match_token(Token::LPar, "expected '(' in 'deref' global value decl")?;
"vmctx" => GlobalValueData::VMContext,
"load" => {
self.match_token(
Token::Dot,
"expected '.' followed by type in load global value decl",
)?;
let global_type = self.match_type("expected load type")?;
let flags = self.optional_memflags();
let base = self.match_gv("expected global value: gv«n»")?;
self.match_token(Token::RPar, "expected ')' in 'deref' global value decl")?;
let offset = self.optional_offset32()?;
self.match_token(Token::Colon, "expected ':' in 'deref' global value decl")?;
let memory_type = self.match_type("expected deref type")?;
GlobalValueData::Deref {
let mut expected_flags = MemFlags::new();
expected_flags.set_notrap();
expected_flags.set_aligned();
if flags != expected_flags {
return err!(self.loc, "global-value load must be notrap and aligned");
}
GlobalValueData::Load {
base,
offset,
memory_type,
global_type,
}
}
"globalsym" => {
"iadd_imm" => {
self.match_token(
Token::Dot,
"expected '.' followed by type in iadd_imm global value decl",
)?;
let global_type = self.match_type("expected iadd type")?;
let base = self.match_gv("expected global value: gv«n»")?;
self.match_token(
Token::Comma,
"expected ',' followed by rhs in iadd_imm global value decl",
)?;
let offset = self.match_imm64("expected iadd_imm immediate")?;
GlobalValueData::IAddImm {
base,
offset,
global_type,
}
}
"symbol" => {
let colocated = self.optional(Token::Identifier("colocated"));
let name = self.parse_external_name()?;
GlobalValueData::Sym { name, colocated }
let offset = self.optional_offset_imm64()?;
GlobalValueData::Symbol {
name,
offset,
colocated,
}
}
other => return err!(self.loc, "Unknown global value kind '{}'", other),
};
@@ -2680,8 +2728,8 @@ mod tests {
fn duplicate_gv() {
let ParseError { location, message } = Parser::new(
"function %ebbs() system_v {
gv0 = vmctx+64
gv0 = vmctx+64",
gv0 = vmctx
gv0 = vmctx",
).parse_function(None)
.unwrap_err();

View File

@@ -2,7 +2,7 @@
//! wasm translation.
use cranelift_codegen::cursor::FuncCursor;
use cranelift_codegen::ir::immediates::Imm64;
use cranelift_codegen::ir::immediates::{Imm64, Offset32};
use cranelift_codegen::ir::types::*;
use cranelift_codegen::ir::{self, InstBuilder};
use cranelift_codegen::settings;
@@ -162,21 +162,26 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
fn make_global(&mut self, func: &mut ir::Function, index: GlobalIndex) -> GlobalVariable {
// Just create a dummy `vmctx` global.
let offset = ((index * 8) as i32 + 8).into();
let gv = func.create_global_value(ir::GlobalValueData::VMContext { offset });
let offset = ((index * 8) as i64 + 8).into();
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext {});
let iadd = func.create_global_value(ir::GlobalValueData::IAddImm {
base: vmctx,
offset,
global_type: self.pointer_type(),
});
GlobalVariable::Memory {
gv,
gv: iadd,
ty: self.mod_info.globals[index].entity.ty,
}
}
fn make_heap(&mut self, func: &mut ir::Function, _index: MemoryIndex) -> ir::Heap {
// Create a static heap whose base address is stored at `vmctx+0`.
let addr = func.create_global_value(ir::GlobalValueData::VMContext { offset: 0.into() });
let gv = func.create_global_value(ir::GlobalValueData::Deref {
let addr = func.create_global_value(ir::GlobalValueData::VMContext);
let gv = func.create_global_value(ir::GlobalValueData::Load {
base: addr,
offset: 0.into(),
memory_type: self.pointer_type(),
offset: Offset32::new(0),
global_type: self.pointer_type(),
});
func.create_heap(ir::HeapData {
@@ -192,19 +197,16 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ
fn make_table(&mut self, func: &mut ir::Function, _index: TableIndex) -> ir::Table {
// Create a table whose base address is stored at `vmctx+0`.
let base_gv_addr =
func.create_global_value(ir::GlobalValueData::VMContext { offset: 0.into() });
let base_gv = func.create_global_value(ir::GlobalValueData::Deref {
base: base_gv_addr,
offset: 0.into(),
memory_type: self.pointer_type(),
let vmctx = func.create_global_value(ir::GlobalValueData::VMContext);
let base_gv = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: Offset32::new(0),
global_type: self.pointer_type(),
});
let bound_gv_addr =
func.create_global_value(ir::GlobalValueData::VMContext { offset: 0.into() });
let bound_gv = func.create_global_value(ir::GlobalValueData::Deref {
base: bound_gv_addr,
offset: 0.into(),
memory_type: self.pointer_type(),
let bound_gv = func.create_global_value(ir::GlobalValueData::Load {
base: vmctx,
offset: Offset32::new(0),
global_type: self.pointer_type(),
});
func.create_table(ir::TableData {