use crate::{context::Context, space::SpaceRef};
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};
#[macro_export]
macro_rules! composite_id {
($name:ident, $local:ty) => {
#[derive(
Clone,
Copy,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Default,
::serde::Serialize,
::serde::Deserialize,
)]
pub struct $name {
pub func: $crate::value::FunctionId,
pub local: $local,
}
impl $name {
pub const fn new(func: $crate::value::FunctionId, local: $local) -> Self {
Self { func, local }
}
#[inline]
pub fn localize(self, func: $crate::value::FunctionId) -> $local {
debug_assert_eq!(
self.func, func,
concat!(stringify!($name), "::localize: foreign id"),
);
self.local
}
}
impl ::core::fmt::Debug for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
write!(
f,
concat!(stringify!($name), "({}:{})"),
self.func, self.local
)
}
}
impl ::core::fmt::Display for $name {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
write!(f, "{}:{}", self.func, self.local)
}
}
};
}
pub use block::cfg::LocalBlockId;
pub use block::{BasicBlock, BlockId, BlockMutRef, BlockRef};
pub use block_param::{BlockParam, BlockParamId, BlockParamMutRef, BlockParamRef, LocalParamId};
pub use bytes::{
Bytes, BytesDisplay, BytesId, BytesRef, StringEncoding, decode_string, escape_decoded,
render_bytes_literal,
};
pub use function::{
ArgMemKind, BodyArenaKindStats, BodyArenaStats, DerivedOutput, ExternArg, ExternArgmem,
ExternInterface, ExternSlot, Footprint, FunctionBody, FunctionEffects, FunctionId,
FunctionKind, FunctionMutRef, FunctionRef, InterfaceSlot, MemoryChannelState,
MemoryInterfaceMap, ParamAttrs, RamBase, RamField, RamLocations, RamObject, RamRegion,
RegisterChannelState, RegisterEffectSets, RegisterInterfaceMap, SlotBase, WrittenSpaces,
WrittenSpacesState,
};
pub use insn::LocalInsnId;
pub use insn::{Instruction, InstructionId, InstructionRef};
pub use literal::{LiteralId, LiteralRef};
pub use poison::{Poison, PoisonId, PoisonRef};
pub use temp::{
LocalTempId, LocalTempSpaceId, Temp, TempId, TempRef, TempSpace, TempSpaceId, TempSpaceRef,
};
pub use util::named::{Named, Renameable};
pub use varnode::{Varnode, VarnodeId, VarnodeRef, register::Register, register::RegisterId};
pub mod block;
pub mod block_param;
pub mod bytes;
pub mod function;
pub mod insn;
pub mod interner;
pub mod literal;
pub mod poison;
pub mod registry;
pub mod temp;
pub mod util;
pub mod varnode;
pub mod view;
pub mod view_mut;
pub use view::{BodyView, ModuleView, QCodeView};
pub use view_mut::QCodeMut;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum ValueId {
Literal(LiteralId),
Bytes(BytesId),
Instruction(InstructionId),
BasicBlock(BlockId),
BlockParam(BlockParamId),
Varnode(VarnodeId),
Temp(TempId),
Function(FunctionId),
Poison(PoisonId),
}
impl ValueId {
pub fn ty(&self) -> &'static str {
match self {
ValueId::Literal(_) => "Literal",
ValueId::Bytes(_) => "Bytes",
ValueId::Instruction(_) => "Instruction",
ValueId::BasicBlock(_) => "BasicBlock",
ValueId::BlockParam(_) => "BlockParam",
ValueId::Varnode(_) => "Varnode",
ValueId::Temp(_) => "Temp",
ValueId::Function(_) => "Function",
ValueId::Poison(_) => "Poison",
}
}
pub fn owning_function(self) -> Option<FunctionId> {
match self {
ValueId::Instruction(id) => Some(id.func),
ValueId::BlockParam(id) => Some(id.func),
ValueId::Temp(id) => Some(id.func),
_ => None,
}
}
pub fn name_scope_function(self) -> Option<FunctionId> {
match self {
ValueId::Instruction(id) => Some(id.func),
ValueId::BlockParam(id) => Some(id.func),
ValueId::BasicBlock(id) => Some(id.func),
ValueId::Temp(id) => Some(id.func),
_ => None,
}
}
pub fn as_literal(self) -> Option<LiteralId> {
if let ValueId::Literal(id) = self {
Some(id)
} else {
None
}
}
pub fn as_instruction(self) -> Option<InstructionId> {
if let ValueId::Instruction(id) = self {
Some(id)
} else {
None
}
}
pub fn as_block(self) -> Option<BlockId> {
if let ValueId::BasicBlock(id) = self {
Some(id)
} else {
None
}
}
pub fn as_block_param(self) -> Option<BlockParamId> {
if let ValueId::BlockParam(id) = self {
Some(id)
} else {
None
}
}
pub fn as_bytes(self) -> Option<BytesId> {
if let ValueId::Bytes(id) = self {
Some(id)
} else {
None
}
}
pub fn is_varnode(self) -> bool {
matches!(self, ValueId::Varnode(_))
}
pub fn as_varnode(self) -> Option<VarnodeId> {
if let ValueId::Varnode(id) = self {
Some(id)
} else {
None
}
}
pub fn as_temp(self) -> Option<TempId> {
if let ValueId::Temp(id) = self {
Some(id)
} else {
None
}
}
pub fn as_function(self) -> Option<FunctionId> {
if let ValueId::Function(id) = self {
Some(id)
} else {
None
}
}
pub fn as_poison(self) -> Option<PoisonId> {
if let ValueId::Poison(id) = self {
Some(id)
} else {
None
}
}
pub fn is_poison(self) -> bool {
matches!(self, ValueId::Poison(_))
}
}
impl From<LiteralId> for ValueId {
fn from(id: LiteralId) -> Self {
ValueId::Literal(id)
}
}
impl From<BytesId> for ValueId {
fn from(id: BytesId) -> Self {
ValueId::Bytes(id)
}
}
impl From<InstructionId> for ValueId {
fn from(id: InstructionId) -> Self {
ValueId::Instruction(id)
}
}
impl From<BlockId> for ValueId {
fn from(id: BlockId) -> Self {
ValueId::BasicBlock(id)
}
}
impl From<BlockParamId> for ValueId {
fn from(id: BlockParamId) -> Self {
ValueId::BlockParam(id)
}
}
impl From<VarnodeId> for ValueId {
fn from(id: VarnodeId) -> Self {
ValueId::Varnode(id)
}
}
impl From<TempId> for ValueId {
fn from(id: TempId) -> Self {
ValueId::Temp(id)
}
}
impl From<FunctionId> for ValueId {
fn from(id: FunctionId) -> Self {
ValueId::Function(id)
}
}
impl From<PoisonId> for ValueId {
fn from(id: PoisonId) -> Self {
ValueId::Poison(id)
}
}
impl ValueId {
pub fn order_key(&self) -> (u8, u32, u32) {
match *self {
ValueId::Literal(id) => (0, 0, u32::try_from(usize::from(id)).unwrap_or(u32::MAX)),
ValueId::Bytes(id) => (1, 0, u32::try_from(usize::from(id)).unwrap_or(u32::MAX)),
ValueId::Varnode(id) => (2, 0, u32::try_from(usize::from(id)).unwrap_or(u32::MAX)),
ValueId::Function(id) => (3, 0, u32::try_from(usize::from(id)).unwrap_or(u32::MAX)),
ValueId::Instruction(id) => {
(4, usize::from(id.func) as u32, usize::from(id.local) as u32)
}
ValueId::BasicBlock(id) => {
(5, usize::from(id.func) as u32, usize::from(id.local) as u32)
}
ValueId::BlockParam(id) => {
(6, usize::from(id.func) as u32, usize::from(id.local) as u32)
}
ValueId::Temp(id) => (7, usize::from(id.func) as u32, usize::from(id.local) as u32),
ValueId::Poison(id) => (8, 0, u32::try_from(usize::from(id)).unwrap_or(u32::MAX)),
}
}
}
impl Display for ValueId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match *self {
ValueId::Literal(id) => write!(f, "Literal({})", usize::from(id)),
ValueId::Bytes(id) => write!(f, "Bytes({})", usize::from(id)),
ValueId::Varnode(id) => write!(f, "Varnode({})", usize::from(id)),
ValueId::Function(id) => write!(f, "Function({})", usize::from(id)),
ValueId::Instruction(id) => write!(f, "Instruction({id})"),
ValueId::BasicBlock(id) => write!(f, "BasicBlock({id})"),
ValueId::BlockParam(id) => write!(f, "BlockParam({id})"),
ValueId::Temp(id) => write!(f, "Temp({id})"),
ValueId::Poison(id) => write!(f, "Poison({})", usize::from(id)),
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum LocalValueId {
Literal(LiteralId),
Bytes(BytesId),
Instruction(LocalInsnId),
BasicBlock(LocalBlockId),
BlockParam(LocalParamId),
Varnode(VarnodeId),
Temp(LocalTempId),
Function(FunctionId),
Poison(PoisonId),
}
impl LocalValueId {
pub fn qualify(self, func: FunctionId) -> ValueId {
match self {
LocalValueId::Literal(id) => ValueId::Literal(id),
LocalValueId::Bytes(id) => ValueId::Bytes(id),
LocalValueId::Varnode(id) => ValueId::Varnode(id),
LocalValueId::Function(id) => ValueId::Function(id),
LocalValueId::Poison(id) => ValueId::Poison(id),
LocalValueId::Instruction(local) => {
ValueId::Instruction(InstructionId::new(func, local))
}
LocalValueId::BasicBlock(local) => ValueId::BasicBlock(BlockId::new(func, local)),
LocalValueId::BlockParam(local) => ValueId::BlockParam(BlockParamId::new(func, local)),
LocalValueId::Temp(local) => ValueId::Temp(TempId::new(func, local)),
}
}
}
impl ValueId {
pub fn localize(self, func: FunctionId) -> LocalValueId {
match self {
ValueId::Literal(id) => LocalValueId::Literal(id),
ValueId::Bytes(id) => LocalValueId::Bytes(id),
ValueId::Varnode(id) => LocalValueId::Varnode(id),
ValueId::Function(id) => LocalValueId::Function(id),
ValueId::Poison(id) => LocalValueId::Poison(id),
ValueId::Instruction(id) => {
debug_assert_eq!(
id.func, func,
"localize: foreign instruction operand {id:?} in function {func} \
(strict IR locality, ruling 2)"
);
LocalValueId::Instruction(id.local)
}
ValueId::BasicBlock(id) => {
debug_assert_eq!(
id.func, func,
"localize: foreign block operand {id:?} in function {func} \
(strict IR locality, ruling 2)"
);
LocalValueId::BasicBlock(id.local)
}
ValueId::BlockParam(id) => {
debug_assert_eq!(
id.func, func,
"localize: foreign block-param operand {id:?} in function {func} \
(strict IR locality, ruling 2)"
);
LocalValueId::BlockParam(id.local)
}
ValueId::Temp(id) => LocalValueId::Temp(id.localize(func)),
}
}
pub fn strip_func(self) -> LocalValueId {
match self {
ValueId::Literal(id) => LocalValueId::Literal(id),
ValueId::Bytes(id) => LocalValueId::Bytes(id),
ValueId::Varnode(id) => LocalValueId::Varnode(id),
ValueId::Function(id) => LocalValueId::Function(id),
ValueId::Poison(id) => LocalValueId::Poison(id),
ValueId::Instruction(id) => LocalValueId::Instruction(id.local),
ValueId::BasicBlock(id) => LocalValueId::BasicBlock(id.local),
ValueId::BlockParam(id) => LocalValueId::BlockParam(id.local),
ValueId::Temp(id) => LocalValueId::Temp(id.local),
}
}
pub fn as_function_agnostic(self) -> Option<LocalValueId> {
match self {
ValueId::Literal(id) => Some(LocalValueId::Literal(id)),
ValueId::Bytes(id) => Some(LocalValueId::Bytes(id)),
ValueId::Varnode(id) => Some(LocalValueId::Varnode(id)),
ValueId::Function(id) => Some(LocalValueId::Function(id)),
ValueId::Poison(id) => Some(LocalValueId::Poison(id)),
ValueId::Instruction(_)
| ValueId::BasicBlock(_)
| ValueId::BlockParam(_)
| ValueId::Temp(_) => None,
}
}
}
pub trait Value<'str, 'ctx>: Display {
fn id(&self) -> ValueId;
fn size(&self) -> usize;
}
pub enum ValueRef<'str, 'ctx, R = ModuleView<'ctx, 'str>> {
Literal(LiteralRef<'str, 'ctx>),
Bytes(BytesRef<'str, 'ctx>),
Instruction(InstructionRef<'str, 'ctx, R>),
BasicBlock(BlockRef<'str, 'ctx, R>),
BlockParam(BlockParamRef<'str, 'ctx, R>),
Varnode(VarnodeRef<'str, 'ctx>),
Temp(TempRef<'str, 'ctx, R>),
Function(FunctionRef<'str, 'ctx, R>),
Poison(PoisonRef<'str, 'ctx>),
}
impl<R> Debug for ValueRef<'_, '_, R> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ValueRef::Literal(_) => f.write_str("Literal"),
ValueRef::Bytes(_) => f.write_str("Bytes"),
ValueRef::Instruction(_) => f.write_str("Instruction"),
ValueRef::BasicBlock(_) => f.write_str("BasicBlock"),
ValueRef::BlockParam(_) => f.write_str("BlockParam"),
ValueRef::Varnode(_) => f.write_str("Varnode"),
ValueRef::Temp(_) => f.write_str("Temp"),
ValueRef::Function(_) => f.write_str("Function"),
ValueRef::Poison(_) => f.write_str("Poison"),
}
}
}
impl<'str, 'ctx, R> From<LiteralRef<'str, 'ctx>> for ValueRef<'str, 'ctx, R> {
fn from(lit_ref: LiteralRef<'str, 'ctx>) -> Self {
ValueRef::Literal(lit_ref)
}
}
impl<'str, 'ctx, R> From<BytesRef<'str, 'ctx>> for ValueRef<'str, 'ctx, R> {
fn from(bytes_ref: BytesRef<'str, 'ctx>) -> Self {
ValueRef::Bytes(bytes_ref)
}
}
impl<'str, 'ctx, R> From<InstructionRef<'str, 'ctx, R>> for ValueRef<'str, 'ctx, R> {
fn from(insn_ref: InstructionRef<'str, 'ctx, R>) -> Self {
ValueRef::Instruction(insn_ref)
}
}
impl<'str, 'ctx, R> From<BlockRef<'str, 'ctx, R>> for ValueRef<'str, 'ctx, R> {
fn from(bb_ref: BlockRef<'str, 'ctx, R>) -> Self {
ValueRef::BasicBlock(bb_ref)
}
}
impl<'str, 'ctx, R> From<BlockParamRef<'str, 'ctx, R>> for ValueRef<'str, 'ctx, R> {
fn from(param_ref: BlockParamRef<'str, 'ctx, R>) -> Self {
ValueRef::BlockParam(param_ref)
}
}
impl<'str, 'ctx, R> From<VarnodeRef<'str, 'ctx>> for ValueRef<'str, 'ctx, R> {
fn from(var_ref: VarnodeRef<'str, 'ctx>) -> Self {
ValueRef::Varnode(var_ref)
}
}
impl<'str, 'ctx, R> From<TempRef<'str, 'ctx, R>> for ValueRef<'str, 'ctx, R> {
fn from(temp_ref: TempRef<'str, 'ctx, R>) -> Self {
ValueRef::Temp(temp_ref)
}
}
impl<'str, 'ctx, R> From<FunctionRef<'str, 'ctx, R>> for ValueRef<'str, 'ctx, R> {
fn from(fn_ref: FunctionRef<'str, 'ctx, R>) -> Self {
ValueRef::Function(fn_ref)
}
}
impl<'str, 'ctx, R> From<PoisonRef<'str, 'ctx>> for ValueRef<'str, 'ctx, R> {
fn from(poison_ref: PoisonRef<'str, 'ctx>) -> Self {
ValueRef::Poison(poison_ref)
}
}
impl<'str, 'ctx> ValueRef<'str, 'ctx> {
pub fn new(id: ValueId, ctx: &'ctx Context<'str>) -> Self {
ValueRef::from_view(ModuleView::new(ctx), id)
}
pub fn from_id(ctx: &'ctx Context<'str>, id: ValueId) -> Self {
Self::new(id, ctx)
}
}
impl<'str: 'ctx, 'ctx, R> ValueRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
pub fn from_view(view: R, id: ValueId) -> Self {
match id {
ValueId::Literal(id) => ValueRef::Literal(LiteralRef::from_id(view.shared(), id)),
ValueId::Bytes(id) => ValueRef::Bytes(BytesRef::from_id(view.shared(), id)),
ValueId::Varnode(id) => ValueRef::Varnode(Varnode::from_id(view.shared(), id)),
ValueId::Temp(id) => ValueRef::Temp(TempRef::new(view, id)),
ValueId::Instruction(id) => ValueRef::Instruction(InstructionRef::new(view, id)),
ValueId::BasicBlock(id) => ValueRef::BasicBlock(BlockRef::new(view, id)),
ValueId::BlockParam(id) => ValueRef::BlockParam(BlockParamRef::new(view, id)),
ValueId::Function(id) => ValueRef::Function(FunctionRef::new(view, id)),
ValueId::Poison(id) => ValueRef::Poison(PoisonRef::from_id(view.shared(), id)),
}
}
fn inner(&self) -> &dyn Value<'str, 'ctx> {
match self {
ValueRef::Literal(r) => r,
ValueRef::Bytes(r) => r,
ValueRef::Instruction(r) => r,
ValueRef::BasicBlock(r) => r,
ValueRef::BlockParam(r) => r,
ValueRef::Varnode(r) => r,
ValueRef::Temp(r) => r,
ValueRef::Function(r) => r,
ValueRef::Poison(r) => r,
}
}
pub fn space(&self) -> Option<SpaceRef<'ctx>> {
match self {
ValueRef::Varnode(v) => Some(v.space()),
ValueRef::Instruction(i) => i.space(),
ValueRef::Temp(_) => None,
ValueRef::Literal(_)
| ValueRef::Bytes(_)
| ValueRef::BasicBlock(_)
| ValueRef::BlockParam(_)
| ValueRef::Function(_)
| ValueRef::Poison(_) => None,
}
}
pub fn memory_space(&self) -> Option<crate::space::MemorySpaceId> {
match self {
ValueRef::Varnode(v) => Some(crate::space::MemorySpaceId::Shared(v.space().id)),
ValueRef::Instruction(i) => i.memory_space(),
ValueRef::Temp(t) => Some(t.memory_space()),
ValueRef::Literal(_)
| ValueRef::Bytes(_)
| ValueRef::BasicBlock(_)
| ValueRef::BlockParam(_)
| ValueRef::Function(_)
| ValueRef::Poison(_) => None,
}
}
}
impl<'str: 'ctx, 'ctx, R> Display for ValueRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let tokens = match self {
ValueRef::Literal(r) => insn::segment::value_tokens_shared(r.ctx, self.id()),
ValueRef::Bytes(r) => insn::segment::value_tokens_shared(r.ctx, self.id()),
ValueRef::Varnode(r) => insn::segment::value_tokens_shared(r.ctx, self.id()),
ValueRef::Poison(r) => insn::segment::value_tokens_shared(r.ctx, self.id()),
ValueRef::Temp(r) => insn::segment::value_tokens_view(r.view, self.id()),
ValueRef::Instruction(r) => insn::segment::value_tokens_view(r.view, self.id()),
ValueRef::BasicBlock(r) => insn::segment::value_tokens_view(r.view, self.id()),
ValueRef::BlockParam(r) => insn::segment::value_tokens_view(r.view, self.id()),
ValueRef::Function(r) => insn::segment::value_tokens_view(r.view, self.id()),
};
for token in tokens {
write!(f, "{}", token.text)?;
}
Ok(())
}
}
impl<'str: 'ctx, 'ctx, R> Value<'str, 'ctx> for ValueRef<'str, 'ctx, R>
where
R: QCodeView<'ctx, 'str>,
{
fn id(&self) -> ValueId {
self.inner().id()
}
fn size(&self) -> usize {
self.inner().size()
}
}
#[cfg(test)]
mod local_value_id_tests {
use super::*;
#[test]
fn qualify_localize_round_trips_every_variant() {
let func = FunctionId::from(7usize);
let other = FunctionId::from(3usize);
let arena: [ValueId; 4] = [
ValueId::Instruction(InstructionId::new(func, LocalInsnId::from(2usize))),
ValueId::BasicBlock(BlockId::new(func, LocalBlockId::from(5usize))),
ValueId::BlockParam(BlockParamId::new(func, LocalParamId::from(1usize))),
ValueId::Temp(TempId::new(func, LocalTempId::from(4usize))),
];
for id in arena {
assert_eq!(id.localize(func).qualify(func), id, "{id:?}");
}
let shared: [ValueId; 4] = [
ValueId::Literal(LiteralId::from(0usize)),
ValueId::Bytes(BytesId::from(0usize)),
ValueId::Varnode(VarnodeId::from(0usize)),
ValueId::Function(other),
];
for id in shared {
assert_eq!(id.localize(func).qualify(func), id, "{id:?}");
assert_eq!(id.localize(func).qualify(other), id, "{id:?}");
}
}
#[test]
#[should_panic(expected = "strict IR locality")]
#[cfg(debug_assertions)]
fn localize_rejects_foreign_arena_id() {
let func = FunctionId::from(7usize);
let foreign = FunctionId::from(9usize);
let id = ValueId::Instruction(InstructionId::new(foreign, LocalInsnId::from(0usize)));
let _ = id.localize(func);
}
#[test]
#[should_panic(expected = "TempId::localize: foreign id")]
#[cfg(debug_assertions)]
fn localize_rejects_foreign_temporary_id() {
let owner = FunctionId::from(7usize);
let foreign = FunctionId::from(9usize);
let id = ValueId::Temp(TempId::new(foreign, LocalTempId::from(0usize)));
let _ = id.localize(owner);
}
}