Custom legalization for global_addr.
The code to compute the address of a global variable depends on the kind of variable, so custom legalization is required. - Add a legalizer::globalvar module which exposes an expand_global_addr() function. This module is likely to grow as we add more types of global variables. - Add a ArgumentPurpose::VMContext enumerator. This is used to represent special 'vmctx' arguments that are used as base pointers for vmctx globals.
This commit is contained in:
@@ -373,7 +373,7 @@ calling convention:
|
||||
retlist : arglist
|
||||
arg : type [argext] [argspecial]
|
||||
argext : "uext" | "sext"
|
||||
argspecial: "sret" | "link" | "fp" | "csr"
|
||||
argspecial: "sret" | "link" | "fp" | "csr" | "vmctx"
|
||||
callconv : `string`
|
||||
|
||||
Arguments and return values have flags whose meaning is mostly target
|
||||
|
||||
29
cranelift/filetests/isa/intel/legalize-memory.cton
Normal file
29
cranelift/filetests/isa/intel/legalize-memory.cton
Normal file
@@ -0,0 +1,29 @@
|
||||
; Test the legalization of memory objects.
|
||||
test legalizer
|
||||
set is_64bit
|
||||
isa intel
|
||||
|
||||
; regex: V=v\d+
|
||||
|
||||
function %vmctx(i64 vmctx) -> i64 {
|
||||
gv1 = vmctx-16
|
||||
|
||||
ebb1(v1: i64):
|
||||
v2 = global_addr.i64 gv1
|
||||
; check: $v2 = iadd_imm $v1, -16
|
||||
return v2
|
||||
; check: return $v2
|
||||
}
|
||||
|
||||
function %deref(i64 vmctx) -> i64 {
|
||||
gv1 = vmctx-16
|
||||
gv2 = deref(gv1)+32
|
||||
|
||||
ebb1(v1: i64):
|
||||
v2 = global_addr.i64 gv2
|
||||
; check: $(a1=$V) = iadd_imm $v1, -16
|
||||
; check: $(p1=$V) = load.i64 $a1
|
||||
; check: $v2 = iadd_imm $p1, 32
|
||||
return v2
|
||||
; check: return $v2
|
||||
}
|
||||
@@ -8,6 +8,7 @@ instructions that are legal.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
from .immediates import intcc
|
||||
from . import instructions as insts
|
||||
from .instructions import iadd, iadd_cout, iadd_cin, iadd_carry, iadd_imm
|
||||
from .instructions import isub, isub_bin, isub_bout, isub_borrow
|
||||
from .instructions import band, bor, bxor, isplit, iconcat
|
||||
@@ -43,6 +44,9 @@ expand = XFormGroup('expand', """
|
||||
operating on the same types as the original instructions.
|
||||
""")
|
||||
|
||||
# Custom expansions for memory objects.
|
||||
expand.custom_legalize(insts.global_addr, 'expand_global_addr')
|
||||
|
||||
x = Var('x')
|
||||
y = Var('y')
|
||||
a = Var('a')
|
||||
|
||||
@@ -240,10 +240,16 @@ pub enum ArgumentPurpose {
|
||||
/// Some calling conventions have registers that must be saved by the callee. These registers
|
||||
/// are represented as `CalleeSaved` arguments and return values.
|
||||
CalleeSaved,
|
||||
|
||||
/// A VM context pointer.
|
||||
///
|
||||
/// This is a pointer to a context struct containing details about the current sandbox. It is
|
||||
/// used as a base pointer for `vmctx` global variables.
|
||||
VMContext,
|
||||
}
|
||||
|
||||
/// Text format names of the `ArgumentPurpose` variants.
|
||||
static PURPOSE_NAMES: [&str; 5] = ["normal", "sret", "link", "fp", "csr"];
|
||||
static PURPOSE_NAMES: [&str; 6] = ["normal", "sret", "link", "fp", "csr", "vmctx"];
|
||||
|
||||
impl fmt::Display for ArgumentPurpose {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
@@ -260,6 +266,7 @@ impl FromStr for ArgumentPurpose {
|
||||
"link" => Ok(ArgumentPurpose::Link),
|
||||
"fp" => Ok(ArgumentPurpose::FramePointer),
|
||||
"csr" => Ok(ArgumentPurpose::CalleeSaved),
|
||||
"vmctx" => Ok(ArgumentPurpose::VMContext),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
@@ -343,7 +350,8 @@ mod tests {
|
||||
ArgumentPurpose::StructReturn,
|
||||
ArgumentPurpose::Link,
|
||||
ArgumentPurpose::FramePointer,
|
||||
ArgumentPurpose::CalleeSaved];
|
||||
ArgumentPurpose::CalleeSaved,
|
||||
ArgumentPurpose::VMContext];
|
||||
for (&e, &n) in all_purpose.iter().zip(PURPOSE_NAMES.iter()) {
|
||||
assert_eq!(e.to_string(), n);
|
||||
assert_eq!(Ok(e), n.parse());
|
||||
|
||||
@@ -56,6 +56,7 @@ pub fn legalize_signatures(func: &mut Function, isa: &TargetIsa) {
|
||||
fn legalize_entry_arguments(func: &mut Function, entry: Ebb) {
|
||||
let mut has_sret = false;
|
||||
let mut has_link = false;
|
||||
let mut has_vmctx = false;
|
||||
|
||||
// Insert position for argument conversion code.
|
||||
// We want to insert instructions before the first instruction in the entry block.
|
||||
@@ -86,6 +87,10 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) {
|
||||
assert!(!has_sret, "Multiple sret arguments found");
|
||||
has_sret = true;
|
||||
}
|
||||
ArgumentPurpose::VMContext => {
|
||||
assert!(!has_vmctx, "Multiple vmctx arguments found");
|
||||
has_vmctx = true;
|
||||
}
|
||||
_ => panic!("Unexpected special-purpose arg {}", abi_types[abi_arg]),
|
||||
}
|
||||
abi_arg += 1;
|
||||
@@ -134,7 +139,12 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) {
|
||||
assert!(!has_sret, "Multiple sret arguments found");
|
||||
has_sret = true;
|
||||
}
|
||||
ArgumentPurpose::VMContext => {
|
||||
assert!(!has_vmctx, "Multiple vmctx arguments found");
|
||||
has_vmctx = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Just create entry block values to match here. We will use them in `handle_return_abi()`
|
||||
// below.
|
||||
func.dfg.append_ebb_arg(entry, arg.value_type);
|
||||
@@ -503,14 +513,15 @@ pub fn handle_return_abi(inst: Inst, func: &mut Function, cfg: &ControlFlowGraph
|
||||
return false;
|
||||
}
|
||||
|
||||
// Count the special-purpose return values (`link` and `sret`) that were appended to the
|
||||
// legalized signature.
|
||||
// Count the special-purpose return values (`link`, `sret`, and `vmctx`) that were appended to
|
||||
// the legalized signature.
|
||||
let special_args = sig.return_types
|
||||
.iter()
|
||||
.rev()
|
||||
.take_while(|&rt| {
|
||||
rt.purpose == ArgumentPurpose::Link ||
|
||||
rt.purpose == ArgumentPurpose::StructReturn
|
||||
rt.purpose == ArgumentPurpose::StructReturn ||
|
||||
rt.purpose == ArgumentPurpose::VMContext
|
||||
})
|
||||
.count();
|
||||
|
||||
@@ -522,8 +533,8 @@ pub fn handle_return_abi(inst: Inst, func: &mut Function, cfg: &ControlFlowGraph
|
||||
|_, abi_arg| sig.return_types[abi_arg]);
|
||||
assert_eq!(dfg.inst_variable_args(inst).len(), abi_args);
|
||||
|
||||
// Append special return arguments for any `sret` and `link` return values added to the
|
||||
// legalized signature. These values should simply be propagated from the entry block
|
||||
// Append special return arguments for any `sret`, `link`, and `vmctx` return values added to
|
||||
// the legalized signature. These values should simply be propagated from the entry block
|
||||
// arguments.
|
||||
if special_args > 0 {
|
||||
dbg!("Adding {} special-purpose arguments to {}",
|
||||
@@ -533,13 +544,14 @@ pub fn handle_return_abi(inst: Inst, func: &mut Function, cfg: &ControlFlowGraph
|
||||
for arg in &sig.return_types[abi_args..] {
|
||||
match arg.purpose {
|
||||
ArgumentPurpose::Link |
|
||||
ArgumentPurpose::StructReturn => {}
|
||||
ArgumentPurpose::StructReturn |
|
||||
ArgumentPurpose::VMContext => {}
|
||||
ArgumentPurpose::Normal => panic!("unexpected return value {}", arg),
|
||||
_ => panic!("Unsupported special purpose return value {}", arg),
|
||||
}
|
||||
// A `link` or `sret` return value can only appear in a signature that has a unique
|
||||
// matching argument. They are appended at the end, so search the signature from the
|
||||
// end.
|
||||
// A `link`/`sret`/`vmctx` return value can only appear in a signature that has a
|
||||
// unique matching argument. They are appended at the end, so search the signature from
|
||||
// the end.
|
||||
let idx = sig.argument_types
|
||||
.iter()
|
||||
.rposition(|t| t.purpose == arg.purpose)
|
||||
|
||||
59
lib/cretonne/src/legalizer/globalvar.rs
Normal file
59
lib/cretonne/src/legalizer/globalvar.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
//! Legalization of global variables.
|
||||
//!
|
||||
//! This module exports the `expand_global_addr` function which transforms a `global_addr`
|
||||
//! instruction into code that depends on the kind of global variable referenced.
|
||||
|
||||
use cursor::{Cursor, FuncCursor};
|
||||
use flowgraph::ControlFlowGraph;
|
||||
use ir::{self, InstBuilder};
|
||||
|
||||
/// Expand a `global_addr` instruction according to the definition of the global variable.
|
||||
pub fn expand_global_addr(inst: ir::Inst, func: &mut ir::Function, _cfg: &mut ControlFlowGraph) {
|
||||
// Unpack the instruction.
|
||||
let gv = match &func.dfg[inst] {
|
||||
&ir::InstructionData::UnaryGlobalVar { opcode, global_var } => {
|
||||
assert_eq!(opcode, ir::Opcode::GlobalAddr);
|
||||
global_var
|
||||
}
|
||||
_ => panic!("Wanted global_addr: {}", func.dfg.display_inst(inst, None)),
|
||||
};
|
||||
|
||||
match func.global_vars[gv] {
|
||||
ir::GlobalVarData::VmCtx { offset } => vmctx_addr(inst, func, offset.into()),
|
||||
ir::GlobalVarData::Deref { base, offset } => deref_addr(inst, func, base, offset.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand a `global_addr` instruction for a vmctx global.
|
||||
fn vmctx_addr(inst: ir::Inst, func: &mut ir::Function, offset: i64) {
|
||||
// Find the incoming `vmctx` function argument. Start searching from the back since the special
|
||||
// arguments are appended by signature legalization.
|
||||
//
|
||||
// This argument must exist; `vmctx` global variables can not be used in functions with calling
|
||||
// conventions that don't add a `vmctx` argument.
|
||||
let argidx = func.signature
|
||||
.argument_types
|
||||
.iter()
|
||||
.rposition(|abi| abi.purpose == ir::ArgumentPurpose::VMContext)
|
||||
.expect("Need vmctx argument for vmctx global");
|
||||
|
||||
// Get the value representing the `vmctx` argument.
|
||||
let vmctx = func.dfg.ebb_args(func.layout.entry_block().unwrap())[argidx];
|
||||
|
||||
// Simply replace the `global_addr` instruction with an `iadd_imm`, reusing the result value.
|
||||
func.dfg.replace(inst).iadd_imm(vmctx, offset);
|
||||
}
|
||||
|
||||
/// Expand a `global_addr` instruction for a deref global.
|
||||
fn deref_addr(inst: ir::Inst, func: &mut ir::Function, base: ir::GlobalVar, offset: i64) {
|
||||
// We need to load a pointer from the `base` global variable, so insert a new `global_addr`
|
||||
// instruction. This depends on the iterative legalization loop. Note that the IL verifier
|
||||
// detects any cycles in the `deref` globals.
|
||||
let ptr_ty = func.dfg.value_type(func.dfg.first_result(inst));
|
||||
let mut pos = FuncCursor::new(func).at_inst(inst);
|
||||
|
||||
let base_addr = pos.ins().global_addr(ptr_ty, base);
|
||||
// TODO: We could probably set both `notrap` and `aligned` on this load instruction.
|
||||
let base_ptr = pos.ins().load(ptr_ty, ir::MemFlags::new(), base_addr, 0);
|
||||
pos.func.dfg.replace(inst).iadd_imm(base_ptr, offset);
|
||||
}
|
||||
@@ -22,8 +22,11 @@ use isa::TargetIsa;
|
||||
use bitset::BitSet;
|
||||
|
||||
mod boundary;
|
||||
mod globalvar;
|
||||
mod split;
|
||||
|
||||
use self::globalvar::expand_global_addr;
|
||||
|
||||
/// Legalize `func` for `isa`.
|
||||
///
|
||||
/// - Transform any instructions that don't have a legal representation in `isa`.
|
||||
|
||||
Reference in New Issue
Block a user