diff --git a/docs/langref.rst b/docs/langref.rst index 6ded3a8bfd..12949ab691 100644 --- a/docs/langref.rst +++ b/docs/langref.rst @@ -428,46 +428,14 @@ accessing memory. However, it can be very complicated to verify the safety of general loads and stores when compiling code for a sandboxed environment, so Cretonne also provides more restricted memory operations that are always safe. -.. inst:: a = load p, Offset, Flags... - - Load from memory at ``p + Offset``. - - This is a polymorphic instruction that can load any value type which has a - memory representation. - - :arg iPtr p: Base address. - :arg Offset: Immediate signed offset. - :flag align(N): Expected alignment of ``p + Offset``. Power of two. - :flag aligntrap: Always trap if the memory access is misaligned. - :result T a: Loaded value. - -.. inst:: store x, p, Offset, Flags... - - Store ``x`` to memory at ``p + Offset``. - - This is a polymorphic instruction that can store any value type with a - memory representation. - - :arg T x: Value to store. - :arg iPtr p: Base address. - :arg Offset: Immediate signed offset. - :flag align(N): Expected alignment of ``p + Offset``. Power of two. - :flag aligntrap: Always trap if the memory access is misaligned. +.. autoinst:: load +.. autoinst:: store Loads and stores are *misaligned* if the resultant address is not a multiple of the expected alignment. Depending on the target architecture, misaligned memory accesses may trap, or they may work. Sometimes, operating systems catch alignment traps and emulate the misaligned memory access. -On target architectures like x86 that don't check alignment, Cretonne expands -the `aligntrap` flag into a conditional trap instruction:: - - v5 = load.i32 v1, 4, align(4), aligntrap - ; Becomes: - v10 = and_imm v1, 3 - trapnz v10 - v5 = load.i32 v1, 4 - Local variables --------------- diff --git a/filetests/parser/tiny.cton b/filetests/parser/tiny.cton index 92842cd140..868a7ba0b1 100644 --- a/filetests/parser/tiny.cton +++ b/filetests/parser/tiny.cton @@ -128,3 +128,30 @@ ebb0(v1: i32): ; nextln: $v2 = heap_load.f32 $v1 ; nextln: $v3 = heap_load.f32 $v1+12 ; nextln: heap_store $v3, $v1 + +; Memory access instructions. +function memory(i32) { +ebb0(v1: i32): + v2 = load.i64 v1 + v3 = load.i64 aligned v1 + v4 = load.i64 notrap v1 + v5 = load.i64 notrap aligned v1 + v6 = load.i64 aligned notrap v1 + v7 = load.i64 v1-12 + v8 = load.i64 notrap v1+0x1_0000 + store v2, v1 + store aligned v3, v1+12 + store notrap aligned v3, v1-12 +} +; sameln: function memory(i32) { +; nextln: ebb0($v1: i32): +; nextln: $v2 = load.i64 $v1 +; nextln: $v3 = load.i64 aligned $v1 +; nextln: $v4 = load.i64 notrap $v1 +; nextln: $v5 = load.i64 notrap aligned $v1 +; nextln: $v6 = load.i64 notrap aligned $v1 +; nextln: $v7 = load.i64 $v1-12 +; nextln: $v8 = load.i64 notrap $v1+0x0001_0000 +; nextln: store $v2, $v1 +; nextln: store aligned $v3, $v1+12 +; nextln: store notrap aligned $v3, $v1-12 diff --git a/lib/cretonne/meta/base/formats.py b/lib/cretonne/meta/base/formats.py index 767be3be7e..acb9ce0131 100644 --- a/lib/cretonne/meta/base/formats.py +++ b/lib/cretonne/meta/base/formats.py @@ -9,7 +9,7 @@ from __future__ import absolute_import from cdsl.formats import InstructionFormat from cdsl.operands import VALUE, VARIABLE_ARGS from .immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from .immediates import intcc, floatcc +from .immediates import intcc, floatcc, memflags from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot Nullary = InstructionFormat() @@ -53,6 +53,9 @@ IndirectCall = InstructionFormat( sig_ref, VALUE, VARIABLE_ARGS, multiple_results=True) +Load = InstructionFormat(memflags, VALUE, offset32) +Store = InstructionFormat(memflags, VALUE, VALUE, offset32) + StackLoad = InstructionFormat(stack_slot, offset32) StackStore = InstructionFormat(VALUE, stack_slot, offset32) diff --git a/lib/cretonne/meta/base/immediates.py b/lib/cretonne/meta/base/immediates.py index 7f1662f5c1..2f8803e245 100644 --- a/lib/cretonne/meta/base/immediates.py +++ b/lib/cretonne/meta/base/immediates.py @@ -90,3 +90,9 @@ floatcc = ImmediateKind( 'ugt': 'UnorderedOrGreaterThan', 'uge': 'UnorderedOrGreaterThanOrEqual', }) + +#: Flags for memory operations like :inst:`load` and :inst:`store`. +memflags = ImmediateKind( + 'memflags', + 'Memory operation flags', + default_member='flags', rust_type='MemFlags') diff --git a/lib/cretonne/meta/base/instructions.py b/lib/cretonne/meta/base/instructions.py index d3de015311..487dca1a89 100644 --- a/lib/cretonne/meta/base/instructions.py +++ b/lib/cretonne/meta/base/instructions.py @@ -10,7 +10,7 @@ from cdsl.typevar import TypeVar from cdsl.instructions import Instruction, InstructionGroup from base.types import i8, f32, f64, b1 from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32 -from base.immediates import intcc, floatcc +from base.immediates import intcc, floatcc, memflags from base import entities import base.formats # noqa @@ -211,6 +211,25 @@ x = Operand('x', Mem, doc='Value to be stored') a = Operand('a', Mem, doc='Value loaded') p = Operand('p', iAddr) addr = Operand('addr', iAddr) +Flags = Operand('Flags', memflags) + +load = Instruction( + 'load', r""" + Load from memory at ``p + Offset``. + + This is a polymorphic instruction that can load any value type which + has a memory representation. + """, + ins=(Flags, p, Offset), outs=a) + +store = Instruction( + 'store', r""" + Store ``x`` to memory at ``p + Offset``. + + This is a polymorphic instruction that can store any value type with a + memory representation. + """, + ins=(Flags, x, p, Offset)) stack_load = Instruction( 'stack_load', r""" diff --git a/lib/cretonne/src/ir/builder.rs b/lib/cretonne/src/ir/builder.rs index 8b95f20272..dcec1bbb91 100644 --- a/lib/cretonne/src/ir/builder.rs +++ b/lib/cretonne/src/ir/builder.rs @@ -5,7 +5,8 @@ use ir::types; use ir::{InstructionData, DataFlowGraph, Cursor}; -use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList}; +use ir::{Opcode, Type, Inst, Value, Ebb, JumpTable, SigRef, FuncRef, StackSlot, ValueList, + MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::{IntCC, FloatCC}; diff --git a/lib/cretonne/src/ir/instructions.rs b/lib/cretonne/src/ir/instructions.rs index 5e8d66a09c..f48cbcae6f 100644 --- a/lib/cretonne/src/ir/instructions.rs +++ b/lib/cretonne/src/ir/instructions.rs @@ -10,7 +10,7 @@ use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use std::ops::{Deref, DerefMut}; -use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot}; +use ir::{Value, Type, Ebb, JumpTable, SigRef, FuncRef, StackSlot, MemFlags}; use ir::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32}; use ir::condcodes::*; use ir::types; @@ -252,6 +252,20 @@ pub enum InstructionData { args: [Value; 2], offset: Uoffset32, }, + Load { + opcode: Opcode, + ty: Type, + flags: MemFlags, + arg: Value, + offset: Offset32, + }, + Store { + opcode: Opcode, + ty: Type, + flags: MemFlags, + args: [Value; 2], + offset: Offset32, + }, } /// A variable list of `Value` operands used for function call arguments and passing arguments to diff --git a/lib/cretonne/src/ir/memflags.rs b/lib/cretonne/src/ir/memflags.rs new file mode 100644 index 0000000000..8cef887da2 --- /dev/null +++ b/lib/cretonne/src/ir/memflags.rs @@ -0,0 +1,92 @@ +//! Memory operation flags. + +use std::fmt; + +enum FlagBit { + Notrap, + Aligned, +} + +const NAMES: [&'static str; 2] = ["notrap", "aligned"]; + +/// Flags for memory operations like load/store. +/// +/// Each of these flags introduce a limited form of undefined behavior. The flags each enable +/// certain optimizations that need to make additional assumptions. Generally, the semantics of a +/// program does not change when a flag is removed, but adding a flag will. +#[derive(Clone, Copy, Debug)] +pub struct MemFlags { + bits: u8, +} + +impl MemFlags { + /// Create a new empty set of flags. + pub fn new() -> MemFlags { + MemFlags { bits: 0 } + } + + /// Read a flag bit. + fn read(self, bit: FlagBit) -> bool { + self.bits & (1 << bit as usize) != 0 + } + + /// Set a flag bit. + fn set(&mut self, bit: FlagBit) { + self.bits |= 1 << bit as usize + } + + /// Set a flag bit by name. + /// + /// Returns true if the flag was found and set, false for an unknown flag name. + pub fn set_by_name(&mut self, name: &str) -> bool { + match NAMES.iter().position(|&s| s == name) { + Some(bit) => { + self.bits |= 1 << bit; + true + } + None => false, + } + } + + /// Test if the `notrap` flag is set. + /// + /// Normally, trapping is part of the semantics of a load/store operation. If the platform + /// would cause a trap when accessing the effective address, the Cretonne memory operation is + /// also required to trap. + /// + /// The `notrap` flag gives a Cretonne operation permission to not trap. This makes it possible + /// to delete an unused load or a dead store instruction. + pub fn notrap(self) -> bool { + self.read(FlagBit::Notrap) + } + + /// Set the `notrap` flag. + pub fn set_notrap(&mut self) { + self.set(FlagBit::Notrap) + } + + /// Test if the `aligned` flag is set. + /// + /// By default, Cretonne memory instructions work with any unaligned effective address. If the + /// `aligned` flag is set, the instruction is permitted to trap or return a wrong result if the + /// effective address is misaligned. + pub fn aligned(self) -> bool { + self.read(FlagBit::Aligned) + } + + /// Set the `aligned` flag. + pub fn set_aligned(&mut self) { + self.set(FlagBit::Aligned) + } +} + +impl fmt::Display for MemFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for (i, n) in NAMES.iter().enumerate() { + if self.bits & (1 << i) != 0 { + write!(f, " {}", n)?; + } + } + Ok(()) + } +} diff --git a/lib/cretonne/src/ir/mod.rs b/lib/cretonne/src/ir/mod.rs index 302978d81b..488b4dab32 100644 --- a/lib/cretonne/src/ir/mod.rs +++ b/lib/cretonne/src/ir/mod.rs @@ -10,11 +10,12 @@ pub mod jumptable; pub mod dfg; pub mod layout; pub mod function; -mod funcname; -mod extfunc; mod builder; -mod valueloc; +mod extfunc; +mod funcname; +mod memflags; mod progpoint; +mod valueloc; pub use ir::funcname::FunctionName; pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData}; @@ -29,3 +30,4 @@ pub use ir::layout::{Layout, Cursor}; pub use ir::function::Function; pub use ir::builder::InstBuilder; pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint}; +pub use ir::memflags::MemFlags; diff --git a/lib/cretonne/src/verifier.rs b/lib/cretonne/src/verifier.rs index b1b4429d1a..e6dbf4c86d 100644 --- a/lib/cretonne/src/verifier.rs +++ b/lib/cretonne/src/verifier.rs @@ -271,7 +271,9 @@ impl<'a> Verifier<'a> { &IntCompareImm { .. } | &FloatCompare { .. } | &HeapLoad { .. } | - &HeapStore { .. } => {} + &HeapStore { .. } | + &Load { .. } | + &Store { .. } => {} } Ok(()) diff --git a/lib/cretonne/src/write.rs b/lib/cretonne/src/write.rs index f4b7de5f75..bc9299d796 100644 --- a/lib/cretonne/src/write.rs +++ b/lib/cretonne/src/write.rs @@ -322,6 +322,13 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result } => write!(w, " {}, {}{}", arg, stack_slot, offset), HeapLoad { arg, offset, .. } => write!(w, " {}{}", arg, offset), HeapStore { args, offset, .. } => write!(w, " {}, {}{}", args[0], args[1], offset), + Load { flags, arg, offset, .. } => write!(w, "{} {}{}", flags, arg, offset), + Store { + flags, + args, + offset, + .. + } => write!(w, "{} {}, {}{}", flags, args[0], args[1], offset), } } diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index e813404130..a0e1f71719 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -11,7 +11,7 @@ use std::{u16, u32}; use std::mem; use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable, JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef, - FuncRef, StackSlot, ValueLoc, ArgumentLoc}; + FuncRef, StackSlot, ValueLoc, ArgumentLoc, MemFlags}; use cretonne::ir::types::VOID; use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64}; use cretonne::ir::entities::AnyEntity; @@ -228,7 +228,8 @@ impl<'a> Context<'a> { InstructionData::Unary { ref mut arg, .. } | InstructionData::UnarySplit { ref mut arg, .. } | InstructionData::StackStore { ref mut arg, .. } | - InstructionData::HeapLoad { ref mut arg, .. } => { + InstructionData::HeapLoad { ref mut arg, .. } | + InstructionData::Load { ref mut arg, .. } => { self.map.rewrite_value(arg, loc)?; } @@ -238,7 +239,8 @@ impl<'a> Context<'a> { InstructionData::InsertLane { ref mut args, .. } | InstructionData::IntCompare { ref mut args, .. } | InstructionData::FloatCompare { ref mut args, .. } | - InstructionData::HeapStore { ref mut args, .. } => { + InstructionData::HeapStore { ref mut args, .. } | + InstructionData::Store { ref mut args, .. } => { self.map.rewrite_values(args, loc)?; } @@ -576,6 +578,19 @@ impl<'a> Parser<'a> { } } + // Match and a consume a possibly empty sequence of memory operation flags. + fn optional_memflags(&mut self) -> MemFlags { + let mut flags = MemFlags::new(); + while let Some(Token::Identifier(text)) = self.token() { + if flags.set_by_name(text) { + self.consume(); + } else { + break; + } + } + flags + } + // Match and consume an identifier. fn match_any_identifier(&mut self, err_msg: &str) -> Result<&'a str> { if let Some(Token::Identifier(text)) = self.token() { @@ -1735,6 +1750,32 @@ impl<'a> Parser<'a> { offset: offset, } } + InstructionFormat::Load => { + let flags = self.optional_memflags(); + let addr = self.match_value("expected SSA value address")?; + let offset = self.optional_offset32()?; + InstructionData::Load { + opcode: opcode, + ty: VOID, + flags: flags, + arg: addr, + offset: offset, + } + } + InstructionFormat::Store => { + let flags = self.optional_memflags(); + let arg = self.match_value("expected SSA value operand")?; + self.match_token(Token::Comma, "expected ',' between operands")?; + let addr = self.match_value("expected SSA value address")?; + let offset = self.optional_offset32()?; + InstructionData::Store { + opcode: opcode, + ty: VOID, + flags: flags, + args: [arg, addr], + offset: offset, + } + } }; Ok(idata) }