From f2ebabaf5fd748feab38023b45ee75b334f8b137 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Fri, 18 Aug 2017 09:08:41 -0700 Subject: [PATCH] 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. --- cranelift/docs/langref.rst | 2 +- .../filetests/isa/intel/legalize-memory.cton | 29 +++++++++ lib/cretonne/meta/base/legalize.py | 4 ++ lib/cretonne/src/ir/extfunc.rs | 12 +++- lib/cretonne/src/legalizer/boundary.rs | 30 +++++++--- lib/cretonne/src/legalizer/globalvar.rs | 59 +++++++++++++++++++ lib/cretonne/src/legalizer/mod.rs | 3 + 7 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 cranelift/filetests/isa/intel/legalize-memory.cton create mode 100644 lib/cretonne/src/legalizer/globalvar.rs diff --git a/cranelift/docs/langref.rst b/cranelift/docs/langref.rst index d1d353186a..218e1a5e48 100644 --- a/cranelift/docs/langref.rst +++ b/cranelift/docs/langref.rst @@ -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 diff --git a/cranelift/filetests/isa/intel/legalize-memory.cton b/cranelift/filetests/isa/intel/legalize-memory.cton new file mode 100644 index 0000000000..ade217c84d --- /dev/null +++ b/cranelift/filetests/isa/intel/legalize-memory.cton @@ -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 +} diff --git a/lib/cretonne/meta/base/legalize.py b/lib/cretonne/meta/base/legalize.py index c250d53ce2..8c98468518 100644 --- a/lib/cretonne/meta/base/legalize.py +++ b/lib/cretonne/meta/base/legalize.py @@ -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') diff --git a/lib/cretonne/src/ir/extfunc.rs b/lib/cretonne/src/ir/extfunc.rs index 16211a4d83..9aad88ef13 100644 --- a/lib/cretonne/src/ir/extfunc.rs +++ b/lib/cretonne/src/ir/extfunc.rs @@ -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()); diff --git a/lib/cretonne/src/legalizer/boundary.rs b/lib/cretonne/src/legalizer/boundary.rs index 17ff37c8f7..6977c9e7bc 100644 --- a/lib/cretonne/src/legalizer/boundary.rs +++ b/lib/cretonne/src/legalizer/boundary.rs @@ -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) diff --git a/lib/cretonne/src/legalizer/globalvar.rs b/lib/cretonne/src/legalizer/globalvar.rs new file mode 100644 index 0000000000..308c8df9e5 --- /dev/null +++ b/lib/cretonne/src/legalizer/globalvar.rs @@ -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); +} diff --git a/lib/cretonne/src/legalizer/mod.rs b/lib/cretonne/src/legalizer/mod.rs index f4db515c6b..9e96c61e5d 100644 --- a/lib/cretonne/src/legalizer/mod.rs +++ b/lib/cretonne/src/legalizer/mod.rs @@ -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`.