Add load and store instructions.
Define a MemFlags class, currently holding a notrap and aligned flag.
This commit is contained in:
@@ -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
|
general loads and stores when compiling code for a sandboxed environment, so
|
||||||
Cretonne also provides more restricted memory operations that are always safe.
|
Cretonne also provides more restricted memory operations that are always safe.
|
||||||
|
|
||||||
.. inst:: a = load p, Offset, Flags...
|
.. autoinst:: load
|
||||||
|
.. autoinst:: store
|
||||||
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.
|
|
||||||
|
|
||||||
Loads and stores are *misaligned* if the resultant address is not a multiple of
|
Loads and stores are *misaligned* if the resultant address is not a multiple of
|
||||||
the expected alignment. Depending on the target architecture, misaligned memory
|
the expected alignment. Depending on the target architecture, misaligned memory
|
||||||
accesses may trap, or they may work. Sometimes, operating systems catch
|
accesses may trap, or they may work. Sometimes, operating systems catch
|
||||||
alignment traps and emulate the misaligned memory access.
|
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
|
Local variables
|
||||||
---------------
|
---------------
|
||||||
|
|||||||
@@ -128,3 +128,30 @@ ebb0(v1: i32):
|
|||||||
; nextln: $v2 = heap_load.f32 $v1
|
; nextln: $v2 = heap_load.f32 $v1
|
||||||
; nextln: $v3 = heap_load.f32 $v1+12
|
; nextln: $v3 = heap_load.f32 $v1+12
|
||||||
; nextln: heap_store $v3, $v1
|
; 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
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import absolute_import
|
|||||||
from cdsl.formats import InstructionFormat
|
from cdsl.formats import InstructionFormat
|
||||||
from cdsl.operands import VALUE, VARIABLE_ARGS
|
from cdsl.operands import VALUE, VARIABLE_ARGS
|
||||||
from .immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32
|
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
|
from .entities import ebb, sig_ref, func_ref, jump_table, stack_slot
|
||||||
|
|
||||||
Nullary = InstructionFormat()
|
Nullary = InstructionFormat()
|
||||||
@@ -53,6 +53,9 @@ IndirectCall = InstructionFormat(
|
|||||||
sig_ref, VALUE, VARIABLE_ARGS,
|
sig_ref, VALUE, VARIABLE_ARGS,
|
||||||
multiple_results=True)
|
multiple_results=True)
|
||||||
|
|
||||||
|
Load = InstructionFormat(memflags, VALUE, offset32)
|
||||||
|
Store = InstructionFormat(memflags, VALUE, VALUE, offset32)
|
||||||
|
|
||||||
StackLoad = InstructionFormat(stack_slot, offset32)
|
StackLoad = InstructionFormat(stack_slot, offset32)
|
||||||
StackStore = InstructionFormat(VALUE, stack_slot, offset32)
|
StackStore = InstructionFormat(VALUE, stack_slot, offset32)
|
||||||
|
|
||||||
|
|||||||
@@ -90,3 +90,9 @@ floatcc = ImmediateKind(
|
|||||||
'ugt': 'UnorderedOrGreaterThan',
|
'ugt': 'UnorderedOrGreaterThan',
|
||||||
'uge': 'UnorderedOrGreaterThanOrEqual',
|
'uge': 'UnorderedOrGreaterThanOrEqual',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
#: Flags for memory operations like :inst:`load` and :inst:`store`.
|
||||||
|
memflags = ImmediateKind(
|
||||||
|
'memflags',
|
||||||
|
'Memory operation flags',
|
||||||
|
default_member='flags', rust_type='MemFlags')
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from cdsl.typevar import TypeVar
|
|||||||
from cdsl.instructions import Instruction, InstructionGroup
|
from cdsl.instructions import Instruction, InstructionGroup
|
||||||
from base.types import i8, f32, f64, b1
|
from base.types import i8, f32, f64, b1
|
||||||
from base.immediates import imm64, uimm8, ieee32, ieee64, offset32, uoffset32
|
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
|
from base import entities
|
||||||
import base.formats # noqa
|
import base.formats # noqa
|
||||||
|
|
||||||
@@ -211,6 +211,25 @@ x = Operand('x', Mem, doc='Value to be stored')
|
|||||||
a = Operand('a', Mem, doc='Value loaded')
|
a = Operand('a', Mem, doc='Value loaded')
|
||||||
p = Operand('p', iAddr)
|
p = Operand('p', iAddr)
|
||||||
addr = Operand('addr', 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 = Instruction(
|
||||||
'stack_load', r"""
|
'stack_load', r"""
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
use ir::types;
|
use ir::types;
|
||||||
use ir::{InstructionData, DataFlowGraph, Cursor};
|
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::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32};
|
||||||
use ir::condcodes::{IntCC, FloatCC};
|
use ir::condcodes::{IntCC, FloatCC};
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::fmt::{self, Display, Formatter};
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::ops::{Deref, DerefMut};
|
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::immediates::{Imm64, Uimm8, Ieee32, Ieee64, Offset32, Uoffset32};
|
||||||
use ir::condcodes::*;
|
use ir::condcodes::*;
|
||||||
use ir::types;
|
use ir::types;
|
||||||
@@ -252,6 +252,20 @@ pub enum InstructionData {
|
|||||||
args: [Value; 2],
|
args: [Value; 2],
|
||||||
offset: Uoffset32,
|
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
|
/// A variable list of `Value` operands used for function call arguments and passing arguments to
|
||||||
|
|||||||
92
lib/cretonne/src/ir/memflags.rs
Normal file
92
lib/cretonne/src/ir/memflags.rs
Normal file
@@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,11 +10,12 @@ pub mod jumptable;
|
|||||||
pub mod dfg;
|
pub mod dfg;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod function;
|
pub mod function;
|
||||||
mod funcname;
|
|
||||||
mod extfunc;
|
|
||||||
mod builder;
|
mod builder;
|
||||||
mod valueloc;
|
mod extfunc;
|
||||||
|
mod funcname;
|
||||||
|
mod memflags;
|
||||||
mod progpoint;
|
mod progpoint;
|
||||||
|
mod valueloc;
|
||||||
|
|
||||||
pub use ir::funcname::FunctionName;
|
pub use ir::funcname::FunctionName;
|
||||||
pub use ir::extfunc::{Signature, ArgumentType, ArgumentExtension, ExtFuncData};
|
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::function::Function;
|
||||||
pub use ir::builder::InstBuilder;
|
pub use ir::builder::InstBuilder;
|
||||||
pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint};
|
pub use ir::progpoint::{ProgramPoint, ProgramOrder, ExpandedProgramPoint};
|
||||||
|
pub use ir::memflags::MemFlags;
|
||||||
|
|||||||
@@ -271,7 +271,9 @@ impl<'a> Verifier<'a> {
|
|||||||
&IntCompareImm { .. } |
|
&IntCompareImm { .. } |
|
||||||
&FloatCompare { .. } |
|
&FloatCompare { .. } |
|
||||||
&HeapLoad { .. } |
|
&HeapLoad { .. } |
|
||||||
&HeapStore { .. } => {}
|
&HeapStore { .. } |
|
||||||
|
&Load { .. } |
|
||||||
|
&Store { .. } => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -322,6 +322,13 @@ pub fn write_operands(w: &mut Write, dfg: &DataFlowGraph, inst: Inst) -> Result
|
|||||||
} => write!(w, " {}, {}{}", arg, stack_slot, offset),
|
} => write!(w, " {}, {}{}", arg, stack_slot, offset),
|
||||||
HeapLoad { arg, offset, .. } => write!(w, " {}{}", arg, offset),
|
HeapLoad { arg, offset, .. } => write!(w, " {}{}", arg, offset),
|
||||||
HeapStore { args, offset, .. } => write!(w, " {}, {}{}", args[0], args[1], 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use std::{u16, u32};
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable,
|
use cretonne::ir::{Function, Ebb, Opcode, Value, Type, FunctionName, StackSlotData, JumpTable,
|
||||||
JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef,
|
JumpTableData, Signature, ArgumentType, ArgumentExtension, ExtFuncData, SigRef,
|
||||||
FuncRef, StackSlot, ValueLoc, ArgumentLoc};
|
FuncRef, StackSlot, ValueLoc, ArgumentLoc, MemFlags};
|
||||||
use cretonne::ir::types::VOID;
|
use cretonne::ir::types::VOID;
|
||||||
use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64};
|
use cretonne::ir::immediates::{Imm64, Offset32, Uoffset32, Ieee32, Ieee64};
|
||||||
use cretonne::ir::entities::AnyEntity;
|
use cretonne::ir::entities::AnyEntity;
|
||||||
@@ -228,7 +228,8 @@ impl<'a> Context<'a> {
|
|||||||
InstructionData::Unary { ref mut arg, .. } |
|
InstructionData::Unary { ref mut arg, .. } |
|
||||||
InstructionData::UnarySplit { ref mut arg, .. } |
|
InstructionData::UnarySplit { ref mut arg, .. } |
|
||||||
InstructionData::StackStore { 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)?;
|
self.map.rewrite_value(arg, loc)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +239,8 @@ impl<'a> Context<'a> {
|
|||||||
InstructionData::InsertLane { ref mut args, .. } |
|
InstructionData::InsertLane { ref mut args, .. } |
|
||||||
InstructionData::IntCompare { ref mut args, .. } |
|
InstructionData::IntCompare { ref mut args, .. } |
|
||||||
InstructionData::FloatCompare { 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)?;
|
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.
|
// Match and consume an identifier.
|
||||||
fn match_any_identifier(&mut self, err_msg: &str) -> Result<&'a str> {
|
fn match_any_identifier(&mut self, err_msg: &str) -> Result<&'a str> {
|
||||||
if let Some(Token::Identifier(text)) = self.token() {
|
if let Some(Token::Identifier(text)) = self.token() {
|
||||||
@@ -1735,6 +1750,32 @@ impl<'a> Parser<'a> {
|
|||||||
offset: offset,
|
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)
|
Ok(idata)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user