use crate::bitstream::{Block, Record};
use crate::{Result, error};
const MODULE: u64 = 8;
const PARAMATTR: u64 = 9;
const PARAMATTR_GROUP: u64 = 10;
const CONSTANTS: u64 = 11;
const FUNCTION: u64 = 12;
const VALUE_SYMTAB: u64 = 14;
const METADATA: u64 = 15;
const METADATA_ATTACHMENT: u64 = 16;
const TYPES: u64 = 17;
const USELIST: u64 = 18;
const BLOCKINFO: u64 = 0;
const MAX_VALUES: usize = 1 << 22;
const MAX_TYPES: usize = 1 << 16;
pub type TypeId = usize;
pub type ValueId = usize;
pub type MetadataId = usize;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Type {
Void,
Label,
Metadata,
Half,
Float,
Double,
Integer(u32),
Pointer(TypeId, u32),
Array(u64, TypeId),
Vector(u64, TypeId),
Struct {
name: Option<String>,
elements: Vec<TypeId>,
},
Opaque(Option<String>),
Function {
vararg: bool,
ret: TypeId,
params: Vec<TypeId>,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum Constant {
Null,
Undef,
Integer(i64),
Float(u64),
Aggregate(Vec<ValueId>),
Data(Vec<u64>),
String(Vec<u8>),
Gep {
base: ValueId,
indices: Vec<ValueId>,
},
Cast {
op: CastOp,
value: ValueId,
},
Binary {
op: BinaryOp,
lhs: ValueId,
rhs: ValueId,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Function {
ty: TypeId,
declaration: bool,
attributes: Option<usize>,
},
Global {
ty: TypeId,
value_ty: TypeId,
init: Option<ValueId>,
constant: bool,
address_space: u32,
},
Constant(Constant),
Local,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Metadata {
String(Vec<u8>),
Value {
ty: TypeId,
value: ValueId,
},
Node(Vec<Option<MetadataId>>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BinaryOp {
Add,
Sub,
Mul,
UDiv,
SDiv,
URem,
SRem,
Shl,
LShr,
AShr,
And,
Or,
Xor,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CastOp {
Trunc,
ZExt,
SExt,
FpToUi,
FpToSi,
UiToFp,
SiToFp,
FpTrunc,
FpExt,
PtrToInt,
IntToPtr,
BitCast,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Op {
Binary {
op: BinaryOp,
lhs: ValueId,
rhs: ValueId,
},
Cast {
op: CastOp,
value: ValueId,
},
Compare {
predicate: u64,
lhs: ValueId,
rhs: ValueId,
},
Select {
condition: ValueId,
then: ValueId,
otherwise: ValueId,
},
ExtractValue {
aggregate: ValueId,
indices: Vec<u64>,
},
InsertValue {
aggregate: ValueId,
value: ValueId,
indices: Vec<u64>,
},
ExtractElement {
vector: ValueId,
index: ValueId,
},
InsertElement {
vector: ValueId,
value: ValueId,
index: ValueId,
},
ShuffleVector {
first: ValueId,
second: ValueId,
mask: ValueId,
},
Call {
function: ValueId,
args: Vec<ValueId>,
},
Phi {
incoming: Vec<(ValueId, usize)>,
},
Return(Option<ValueId>),
Branch {
target: usize,
},
CondBranch {
condition: ValueId,
then: usize,
otherwise: usize,
},
Switch {
value: ValueId,
default: usize,
cases: Vec<(ValueId, usize)>,
},
Unreachable,
Alloca {
ty: TypeId,
},
Load {
pointer: ValueId,
},
Store {
pointer: ValueId,
value: ValueId,
},
Gep {
base: ValueId,
indices: Vec<ValueId>,
},
AtomicRmw {
pointer: ValueId,
value: ValueId,
op: u64,
},
CmpXchg {
pointer: ValueId,
compare: ValueId,
value: ValueId,
},
Fence,
}
impl Op {
pub fn operands(&self) -> Vec<ValueId> {
match self {
Op::Binary { lhs, rhs, .. } | Op::Compare { lhs, rhs, .. } => vec![*lhs, *rhs],
Op::Cast { value, .. } => vec![*value],
Op::Select {
condition,
then,
otherwise,
} => vec![*condition, *then, *otherwise],
Op::ExtractValue { aggregate, .. } => vec![*aggregate],
Op::InsertValue { aggregate, value, .. } => vec![*aggregate, *value],
Op::ExtractElement { vector, index } => vec![*vector, *index],
Op::InsertElement { vector, value, index } => vec![*vector, *value, *index],
Op::ShuffleVector { first, second, mask } => vec![*first, *second, *mask],
Op::Call { args, .. } => args.clone(),
Op::Phi { incoming } => incoming.iter().map(|(v, _)| *v).collect(),
Op::Return(value) => value.iter().copied().collect(),
Op::CondBranch { condition, .. } => vec![*condition],
Op::Switch { value, cases, .. } => std::iter::once(*value)
.chain(cases.iter().map(|(c, _)| *c))
.collect(),
Op::Load { pointer } => vec![*pointer],
Op::Store { pointer, value } | Op::AtomicRmw { pointer, value, .. } => vec![*pointer, *value],
Op::Gep { base, indices } => std::iter::once(*base).chain(indices.iter().copied()).collect(),
Op::CmpXchg {
pointer,
compare,
value,
} => vec![*pointer, *compare, *value],
Op::Branch { .. } | Op::Unreachable | Op::Alloca { .. } | Op::Fence => Vec::new(),
}
}
pub fn blocks(&self) -> Vec<usize> {
match self {
Op::Branch { target } => vec![*target],
Op::CondBranch { then, otherwise, .. } => vec![*then, *otherwise],
Op::Switch { default, cases, .. } => std::iter::once(*default)
.chain(cases.iter().map(|(_, b)| *b))
.collect(),
Op::Phi { incoming } => incoming.iter().map(|(_, b)| *b).collect(),
_ => Vec::new(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Instruction {
pub op: Op,
pub result: Option<(ValueId, TypeId)>,
pub block: usize,
pub at: usize,
pub precise: bool,
pub attachments: Vec<Attachment>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Attachment {
pub kind: u64,
pub node: MetadataId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Attribute {
Enum(u64),
Integer(u64, u64),
String(String, String),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AttributeGroup {
pub id: u64,
pub slot: u64,
pub attributes: Vec<Attribute>,
}
pub const FUNCTION_SLOT: u64 = 0xFFFF_FFFF;
pub fn attribute_name(kind: u64) -> Option<&'static str> {
Some(match kind {
9 => "noalias",
11 => "nocapture",
12 => "noduplicate",
17 => "noreturn",
18 => "nounwind",
20 => "readnone",
21 => "readonly",
39 => "nonnull",
_ => return None,
})
}
#[derive(Clone, Debug, PartialEq)]
pub struct Function {
pub value: ValueId,
pub blocks: usize,
pub instructions: Vec<Instruction>,
pub first: ValueId,
pub locals: Vec<Value>,
pub local_types: Vec<TypeId>,
pub attachments: Vec<Attachment>,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Module {
pub types: Vec<Type>,
pub values: Vec<Value>,
pub value_types: Vec<TypeId>,
pub names: Vec<(ValueId, String)>,
pub metadata: Vec<Metadata>,
pub metadata_at: Vec<usize>,
pub named_metadata: Vec<(String, Vec<MetadataId>)>,
pub named_metadata_at: Vec<usize>,
pub at: usize,
pub functions: Vec<Function>,
pub precise_kind: Option<u64>,
pub kinds: Vec<(u64, String)>,
pub attribute_groups: Vec<AttributeGroup>,
pub attribute_lists: Vec<Vec<u64>>,
}
fn op(r: &Record, i: usize) -> Result<u64> {
r.operands
.get(i)
.copied()
.ok_or_else(|| error(r.at, format!("record {} lacks operand {i}", r.code)))
}
fn text(ops: &[u64], at: usize) -> Result<Vec<u8>> {
ops.iter()
.map(|&c| u8::try_from(c).map_err(|_| error(at, "character above 255")))
.collect()
}
fn c_string(r: &Record, from: usize) -> Result<(String, usize)> {
let rest = r.operands.get(from..).unwrap_or_default();
let n = rest
.iter()
.position(|&c| c == 0)
.ok_or_else(|| error(r.at, "an attribute string without its terminator"))?;
let bytes = text(rest.get(..n).unwrap_or_default(), r.at)?;
Ok((
String::from_utf8_lossy(&bytes).into_owned(),
from.saturating_add(n).saturating_add(1),
))
}
fn utf8(bytes: Vec<u8>, at: usize) -> Result<String> {
String::from_utf8(bytes).map_err(|_| error(at, "name is not UTF-8"))
}
fn signed_vbr(v: u64) -> i64 {
if v & 1 == 0 {
i64::try_from(v >> 1).unwrap_or(i64::MAX)
} else if v == 1 {
i64::MIN
} else {
i64::try_from(v >> 1).map_or(i64::MIN, i64::wrapping_neg)
}
}
impl Module {
fn ty(&self, id: u64, at: usize) -> Result<TypeId> {
let id = usize::try_from(id).map_err(|_| error(at, "type id"))?;
if id < self.types.len() {
Ok(id)
} else {
Err(error(at, format!("type {id} undefined")))
}
}
fn type_at(&self, id: TypeId, at: usize) -> Result<&Type> {
self.types
.get(id)
.ok_or_else(|| error(at, format!("type {id} of {} types", self.types.len())))
}
fn intern(&mut self, ty: Type) -> TypeId {
match self.types.iter().position(|x| *x == ty) {
Some(p) => p,
None => {
self.types.push(ty);
self.types.len().saturating_sub(1)
}
}
}
pub fn name(&self, value: ValueId) -> Option<&str> {
self.names
.iter()
.find(|(v, _)| *v == value)
.map(|(_, n)| n.as_str())
}
fn push(&mut self, value: Value, ty: TypeId, at: usize) -> Result<ValueId> {
if self.values.len() >= MAX_VALUES {
return Err(error(at, "too many values"));
}
self.values.push(value);
self.value_types.push(ty);
Ok(self.values.len().saturating_sub(1))
}
fn read_types(&mut self, block: &Block) -> Result<()> {
let mut struct_name: Option<String> = None;
for r in block.records() {
if self.types.len() >= MAX_TYPES {
return Err(error(r.at, "too many types"));
}
let t = match r.code {
1 => continue, 2 => Type::Void,
3 => Type::Float,
4 => Type::Double,
5 => Type::Label,
6 => Type::Opaque(struct_name.take()),
7 => Type::Integer(
u32::try_from(op(r, 0)?)
.ok()
.filter(|w| (1..=64).contains(w))
.ok_or_else(|| error(r.at, "integer width"))?,
),
8 => Type::Pointer(
self.ty(op(r, 0)?, r.at)?,
u32::try_from(op(r, 1)?).map_err(|_| error(r.at, "pointer address space"))?,
),
10 => Type::Half,
11 => Type::Array(op(r, 0)?, self.ty(op(r, 1)?, r.at)?),
12 => Type::Vector(op(r, 0)?, self.ty(op(r, 1)?, r.at)?),
16 => Type::Metadata,
18 | 20 => Type::Struct {
name: if r.code == 20 { struct_name.take() } else { None },
elements: r
.operands
.get(1..)
.unwrap_or_default()
.iter()
.map(|&t| self.ty(t, r.at))
.collect::<Result<_>>()?,
},
19 => {
struct_name = Some(utf8(text(&r.operands, r.at)?, r.at)?);
continue;
}
21 => Type::Function {
vararg: op(r, 0)? != 0,
ret: self.ty(op(r, 1)?, r.at)?,
params: r
.operands
.get(2..)
.unwrap_or_default()
.iter()
.map(|&t| self.ty(t, r.at))
.collect::<Result<_>>()?,
},
other => return Err(error(r.at, format!("type record {other}"))),
};
self.types.push(t);
}
Ok(())
}
fn read_constants(&mut self, block: &Block) -> Result<()> {
let mut ty: Option<TypeId> = None;
for r in block.records() {
let id = |v: u64| usize::try_from(v).map_err(|_| error(r.at, "value id"));
if r.code == 1 {
ty = Some(self.ty(op(r, 0)?, r.at)?);
continue;
}
let t = ty.ok_or_else(|| error(r.at, "constant before SETTYPE"))?;
let c = match r.code {
2 => Constant::Null,
3 => Constant::Undef,
4 => Constant::Integer(signed_vbr(op(r, 0)?)),
6 => Constant::Float(op(r, 0)?),
7 => Constant::Aggregate(
r.operands
.iter()
.map(|&v| usize::try_from(v).map_err(|_| error(r.at, "value id")))
.collect::<Result<_>>()?,
),
8 | 9 => Constant::String(text(&r.operands, r.at)?),
22 => Constant::Data(r.operands.clone()),
12 | 20 => {
let ops = r
.operands
.get(usize::from(r.operands.len() % 2 == 1)..)
.unwrap_or_default();
let (pairs, rest) = ops.as_chunks::<2>();
let ([_, base], true) = (
pairs.first().unwrap_or(&[0, 0]),
rest.is_empty() && !pairs.is_empty(),
) else {
return Err(error(r.at, "constant getelementptr operands"));
};
Constant::Gep {
base: id(*base)?,
indices: pairs
.iter()
.skip(1)
.map(|[_, index]| id(*index))
.collect::<Result<_>>()?,
}
}
10 => Constant::Binary {
op: binary(op(r, 0)?, r.at)?,
lhs: id(op(r, 1)?)?,
rhs: id(op(r, 2)?)?,
},
11 => Constant::Cast {
op: cast(op(r, 0)?, r.at)?,
value: id(op(r, 2)?)?,
},
other => {
return Err(error(
r.at,
format!("constant record {other} (constant expressions are not supported)"),
));
}
};
self.push(Value::Constant(c), t, r.at)?;
}
Ok(())
}
fn read_metadata(&mut self, block: &Block) -> Result<()> {
let mut name: Option<String> = None;
let md = |v: u64, at: usize| usize::try_from(v).map_err(|_| error(at, "metadata id"));
for r in block.records() {
match r.code {
1 => {
self.metadata.push(Metadata::String(text(&r.operands, r.at)?));
self.metadata_at.push(r.at);
}
2 => {
self.metadata.push(Metadata::Value {
ty: self.ty(op(r, 0)?, r.at)?,
value: md(op(r, 1)?, r.at)?,
});
self.metadata_at.push(r.at);
}
3 | 5 => {
self.metadata.push(Metadata::Node(
r.operands
.iter()
.map(|&v| {
if v == 0 {
Ok(None)
} else {
md(v.saturating_sub(1), r.at).map(Some)
}
})
.collect::<Result<_>>()?,
));
self.metadata_at.push(r.at);
}
4 => name = Some(utf8(text(&r.operands, r.at)?, r.at)?),
10 => {
let n = name
.take()
.ok_or_else(|| error(r.at, "named node without a name"))?;
let nodes = r.operands.iter().map(|&v| md(v, r.at)).collect::<Result<_>>()?;
self.named_metadata.push((n, nodes));
self.named_metadata_at.push(r.at);
}
6 => {
let id = op(r, 0)?;
let bytes = text(r.operands.get(1..).unwrap_or_default(), r.at)?;
if bytes == b"dx.precise" {
self.precise_kind = Some(id);
}
self.kinds
.push((id, String::from_utf8_lossy(&bytes).into_owned()));
}
other => return Err(error(r.at, format!("metadata record {other}"))),
}
}
Ok(())
}
fn read_attribute_groups(&mut self, block: &Block) -> Result<()> {
for r in block.records().filter(|r| r.code == 3) {
let mut attributes = Vec::new();
let mut i = 2;
while i < r.operands.len() {
match op(r, i)? {
0 => {
attributes.push(Attribute::Enum(op(r, i.saturating_add(1))?));
i = i.saturating_add(2);
}
1 => {
attributes.push(Attribute::Integer(
op(r, i.saturating_add(1))?,
op(r, i.saturating_add(2))?,
));
i = i.saturating_add(3);
}
form @ (3 | 4) => {
let (key, next) = c_string(r, i.saturating_add(1))?;
let (value, next) = if form == 4 {
c_string(r, next)?
} else {
(String::new(), next)
};
attributes.push(Attribute::String(key, value));
i = next;
}
other => return Err(error(r.at, format!("attribute encoding {other}"))),
}
}
self.attribute_groups.push(AttributeGroup {
id: op(r, 0)?,
slot: op(r, 1)?,
attributes,
});
}
Ok(())
}
fn read_attribute_lists(&mut self, block: &Block) {
for r in block.records().filter(|r| r.code == 2) {
self.attribute_lists.push(r.operands.clone());
}
}
pub fn kind_name(&self, kind: u64) -> Option<&str> {
self.kinds
.iter()
.find(|(k, _)| *k == kind)
.map(|(_, n)| n.as_str())
}
pub fn attachment(&self, attachments: &[Attachment], name: &str) -> Option<MetadataId> {
attachments
.iter()
.find(|a| self.kind_name(a.kind) == Some(name))
.map(|a| a.node)
}
pub fn function_attributes(&self, function: ValueId) -> Vec<&Attribute> {
let Some(Value::Function {
attributes: Some(list),
..
}) = self.values.get(function)
else {
return Vec::new();
};
let Some(groups) = self.attribute_lists.get(*list) else {
return Vec::new();
};
groups
.iter()
.filter_map(|id| {
self.attribute_groups
.iter()
.find(|g| g.id == *id && g.slot == FUNCTION_SLOT)
})
.flat_map(|g| g.attributes.iter())
.collect()
}
pub fn string_attribute(&self, function: ValueId, key: &str) -> Option<&str> {
self.function_attributes(function)
.into_iter()
.find_map(|a| match a {
Attribute::String(k, v) if k == key => Some(v.as_str()),
_ => None,
})
}
fn read_symbols(&mut self, block: &Block, base: usize) -> Result<()> {
for r in block.records() {
match r.code {
1 | 3 => {
let value = usize::try_from(op(r, 0)?).map_err(|_| error(r.at, "value id"))?;
let skip = if r.code == 3 { 2 } else { 1 };
let n = utf8(text(r.operands.get(skip..).unwrap_or_default(), r.at)?, r.at)?;
let id = base.checked_add(value).ok_or_else(|| error(r.at, "value id"))?;
self.names.push((id, n));
}
2 => {} other => return Err(error(r.at, format!("symbol record {other}"))),
}
}
Ok(())
}
}
struct Body<'m> {
m: &'m mut Module,
block: usize,
blocks: usize,
}
impl Body<'_> {
fn next(&self) -> usize {
self.m.values.len()
}
fn value_type(&self, r: &Record, i: &mut usize) -> Result<(ValueId, TypeId)> {
let id = self.absolute(op(r, *i)?, r.at)?;
*i = i.saturating_add(1);
if id < self.next() {
let t = self
.m
.value_types
.get(id)
.copied()
.ok_or_else(|| error(r.at, format!("value {id} has no type")))?;
Ok((id, t))
} else {
let t = self.m.ty(op(r, *i)?, r.at)?;
*i = i.saturating_add(1);
Ok((id, t))
}
}
fn value(&self, r: &Record, i: &mut usize) -> Result<ValueId> {
let id = self.absolute(op(r, *i)?, r.at)?;
*i = i.saturating_add(1);
Ok(id)
}
fn absolute(&self, relative: u64, at: usize) -> Result<ValueId> {
let relative = u32::try_from(relative)
.map_err(|_| error(at, format!("relative value id {relative} wider than 32 bits")))?;
let next = u32::try_from(self.next()).map_err(|_| error(at, "too many values"))?;
Ok(next.wrapping_sub(relative) as usize)
}
fn pointer_to(&mut self, t: TypeId) -> TypeId {
self.m.intern(Type::Pointer(t, 0))
}
fn constant_index(&self, v: ValueId) -> Option<usize> {
match self.m.values.get(v) {
Some(Value::Constant(Constant::Integer(i))) => usize::try_from(*i).ok(),
Some(Value::Constant(Constant::Null)) => Some(0),
_ => None,
}
}
fn result(&mut self, ty: TypeId, at: usize) -> Result<Option<(ValueId, TypeId)>> {
if *self.m.type_at(ty, at)? == Type::Void {
return Ok(None);
}
Ok(Some((self.m.push(Value::Local, ty, at)?, ty)))
}
}
fn binary(code: u64, at: usize) -> Result<BinaryOp> {
Ok(match code {
0 => BinaryOp::Add,
1 => BinaryOp::Sub,
2 => BinaryOp::Mul,
3 => BinaryOp::UDiv,
4 => BinaryOp::SDiv,
5 => BinaryOp::URem,
6 => BinaryOp::SRem,
7 => BinaryOp::Shl,
8 => BinaryOp::LShr,
9 => BinaryOp::AShr,
10 => BinaryOp::And,
11 => BinaryOp::Or,
12 => BinaryOp::Xor,
other => return Err(error(at, format!("binary opcode {other}"))),
})
}
fn cast(code: u64, at: usize) -> Result<CastOp> {
Ok(match code {
0 => CastOp::Trunc,
1 => CastOp::ZExt,
2 => CastOp::SExt,
3 => CastOp::FpToUi,
4 => CastOp::FpToSi,
5 => CastOp::UiToFp,
6 => CastOp::SiToFp,
7 => CastOp::FpTrunc,
8 => CastOp::FpExt,
9 => CastOp::PtrToInt,
10 => CastOp::IntToPtr,
11 => CastOp::BitCast,
other => return Err(error(at, format!("cast opcode {other}"))),
})
}
fn refused(code: u64) -> &'static str {
match code {
4 | 30 => "getelementptr (old form)",
13 => "invoke",
41 => "load (atomic)",
23 => "va_arg",
24 | 42 | 45 => "store (atomic)",
31 => "indirectbr",
37 => "cmpxchg (old form)",
59 => "atomicrmw (new form)",
39 => "resume",
40 | 47 => "landingpad",
_ => "unknown instruction record",
}
}
fn read_attachments(
precise: Option<u64>,
block: &Block,
instructions: &mut [Instruction],
function: &mut Vec<Attachment>,
) -> Result<()> {
for r in block.records().filter(|r| r.code == 11) {
let per_instruction = r.operands.len() % 2 == 1;
let mut attachments = Vec::new();
let (pairs, _) = r
.operands
.get(usize::from(per_instruction)..)
.unwrap_or_default()
.as_chunks::<2>();
for &[kind, node] in pairs {
attachments.push(Attachment {
kind,
node: usize::try_from(node).map_err(|_| error(r.at, "attachment node"))?,
});
}
if !per_instruction {
function.extend(attachments);
continue;
}
let at = usize::try_from(op(r, 0)?).map_err(|_| error(r.at, "attachment instruction"))?;
let instruction = instructions.get_mut(at).ok_or_else(|| {
error(
r.at,
format!("an attachment names instruction {at} of a shorter body"),
)
})?;
if precise.is_some_and(|kind| attachments.iter().any(|a| a.kind == kind)) {
instruction.precise = true;
}
instruction.attachments.extend(attachments);
}
Ok(())
}
impl Module {
fn read_function(&mut self, block: &Block, value: ValueId) -> Result<Function> {
let fty = match self.value_types.get(value).and_then(|&t| self.types.get(t)) {
Some(Type::Pointer(t, _)) => *t,
_ => return Err(error(block.at, "function value is not a function pointer")),
};
let params = match self.type_at(fty, block.at)? {
Type::Function { params, .. } => params.clone(),
_ => return Err(error(block.at, "function type")),
};
let base = self.values.len();
for p in params {
self.push(Value::Local, p, block.at)?;
}
let mut body = Body {
m: self,
block: 0,
blocks: 0,
};
let mut instructions = Vec::new();
let mut function_attachments = Vec::new();
for e in &block.elements {
let r = match e {
crate::bitstream::Element::Block(b) => {
match b.id {
CONSTANTS => body.m.read_constants(b)?,
VALUE_SYMTAB => body.m.read_symbols(b, 0)?,
METADATA_ATTACHMENT => read_attachments(
body.m.precise_kind,
b,
&mut instructions,
&mut function_attachments,
)?,
METADATA | USELIST => {}
other => return Err(error(b.at, format!("block {other} in a function"))),
}
continue;
}
crate::bitstream::Element::Record(r) => r,
};
let mut i = 0;
let (op, ty): (Op, Option<TypeId>) = match r.code {
1 => {
body.blocks = usize::try_from(op(r, 0)?)
.ok()
.filter(|&n| n > 0 && n <= 1 << 16)
.ok_or_else(|| error(r.at, "basic block count"))?;
continue;
}
33 | 35 => continue, 2 => {
let (lhs, t) = body.value_type(r, &mut i)?;
let rhs = body.value(r, &mut i)?;
let o = binary(op(r, i)?, r.at)?;
(Op::Binary { op: o, lhs, rhs }, Some(t))
}
3 => {
let value = body.value(r, &mut i)?;
let to = body.m.ty(op(r, i)?, r.at)?;
let o = cast(op(r, i.saturating_add(1))?, r.at)?;
(Op::Cast { op: o, value }, Some(to))
}
9 | 28 => {
let (lhs, t) = body.value_type(r, &mut i)?;
let rhs = body.value(r, &mut i)?;
let predicate = op(r, i)?;
let bool_ty = match body.m.type_at(t, r.at)? {
Type::Vector(..) => {
return Err(error(r.at, "vector comparison"));
}
_ => body
.m
.types
.iter()
.position(|x| *x == Type::Integer(1))
.ok_or_else(|| error(r.at, "no i1 type for a comparison"))?,
};
(Op::Compare { predicate, lhs, rhs }, Some(bool_ty))
}
29 => {
let (then, t) = body.value_type(r, &mut i)?;
let otherwise = body.value(r, &mut i)?;
let condition = body.value(r, &mut i)?;
(
Op::Select {
condition,
then,
otherwise,
},
Some(t),
)
}
26 => {
let (aggregate, mut t) = body.value_type(r, &mut i)?;
let indices = r.operands.get(i..).unwrap_or_default().to_vec();
for &k in &indices {
t = match body.m.type_at(t, r.at)? {
Type::Struct { elements, .. } => *usize::try_from(k)
.ok()
.and_then(|k| elements.get(k))
.ok_or_else(|| error(r.at, "extractvalue index"))?,
Type::Array(n, e) if k < *n => *e,
_ => return Err(error(r.at, "extractvalue index")),
};
}
(Op::ExtractValue { aggregate, indices }, Some(t))
}
6 => {
let (vector, t) = body.value_type(r, &mut i)?;
let (index, _) = body.value_type(r, &mut i)?;
let Type::Vector(_, element) = *body.m.type_at(t, r.at)? else {
return Err(error(r.at, "extractelement of something that is not a vector"));
};
(Op::ExtractElement { vector, index }, Some(element))
}
7 => {
let (vector, t) = body.value_type(r, &mut i)?;
let value = body.value(r, &mut i)?;
let (index, _) = body.value_type(r, &mut i)?;
(Op::InsertElement { vector, value, index }, Some(t))
}
8 => {
let (first, t) = body.value_type(r, &mut i)?;
let second = body.value(r, &mut i)?;
let (mask, mask_ty) = body.value_type(r, &mut i)?;
let (Type::Vector(_, element), Type::Vector(lanes, _)) = (
body.m.type_at(t, r.at)?.clone(),
body.m.type_at(mask_ty, r.at)?.clone(),
) else {
return Err(error(r.at, "shufflevector of something that is not a vector"));
};
let result = body
.m
.types
.iter()
.position(|x| *x == Type::Vector(lanes, element))
.ok_or_else(|| error(r.at, "no type for the shuffled vector"))?;
(Op::ShuffleVector { first, second, mask }, Some(result))
}
27 => {
let (aggregate, t) = body.value_type(r, &mut i)?;
let value = body.value(r, &mut i)?;
let indices = r.operands.get(i..).unwrap_or_default().to_vec();
(
Op::InsertValue {
aggregate,
value,
indices,
},
Some(t),
)
}
34 => {
i = 1; let cc = op(r, i)?;
i = i.saturating_add(1);
if cc & (1 << 17) != 0 {
i = i.saturating_add(1); }
let explicit = if cc & (1 << 15) != 0 {
let t = body.m.ty(op(r, i)?, r.at)?;
i = i.saturating_add(1);
Some(t)
} else {
None
};
let (function, callee_ty) = body.value_type(r, &mut i)?;
let fty = match (explicit, body.m.type_at(callee_ty, r.at)?) {
(Some(t), _) => t,
(None, Type::Pointer(t, _)) => *t,
_ => return Err(error(r.at, "callee is not a function pointer")),
};
let (ret, params, vararg) = match body.m.type_at(fty, r.at)? {
Type::Function { ret, params, vararg } => (*ret, params.clone(), *vararg),
_ => return Err(error(r.at, "call through a non-function type")),
};
if vararg {
return Err(error(r.at, "vararg call"));
}
let mut args = Vec::with_capacity(params.len());
for p in ¶ms {
let a = body.value(r, &mut i)?;
if a < body.next() && body.m.value_types.get(a) != Some(p) {
return Err(error(r.at, "call argument type"));
}
args.push(a);
}
if i != r.operands.len() {
return Err(error(r.at, "call operands left over"));
}
(Op::Call { function, args }, Some(ret))
}
16 => {
let t = body.m.ty(op(r, 0)?, r.at)?;
let pairs = r.operands.get(1..).unwrap_or_default();
if pairs.is_empty() || pairs.len() % 2 != 0 {
return Err(error(r.at, "phi operands"));
}
let next = i64::try_from(body.next()).map_err(|_| error(r.at, "too many values"))?;
let mut incoming = Vec::with_capacity(pairs.len() / 2);
for pair in pairs.as_chunks::<2>().0 {
let id = next
.checked_sub(signed_vbr(pair[0]))
.and_then(|v| usize::try_from(v).ok())
.ok_or_else(|| error(r.at, "phi value"))?;
let block = usize::try_from(pair[1])
.ok()
.filter(|&b| b < body.blocks)
.ok_or_else(|| error(r.at, "phi block"))?;
incoming.push((id, block));
}
(Op::Phi { incoming }, Some(t))
}
10 => {
if r.operands.is_empty() {
(Op::Return(None), None)
} else {
(Op::Return(Some(body.value(r, &mut i)?)), None)
}
}
11 => {
let target = |v: u64| -> Result<usize> {
usize::try_from(v)
.ok()
.filter(|&b| b < body.blocks)
.ok_or_else(|| error(r.at, "branch target"))
};
if r.operands.len() == 1 {
(
Op::Branch {
target: target(op(r, 0)?)?,
},
None,
)
} else {
let then = target(op(r, 0)?)?;
let otherwise = target(op(r, 1)?)?;
i = 2;
let condition = body.value(r, &mut i)?;
(
Op::CondBranch {
condition,
then,
otherwise,
},
None,
)
}
}
12 => {
let block = |v: u64| -> Result<usize> {
usize::try_from(v)
.ok()
.filter(|&b| b < body.blocks)
.ok_or_else(|| error(r.at, "switch target"))
};
i = 1;
let value = body.value(r, &mut i)?;
let default = block(op(r, i)?)?;
let pairs = r.operands.get(i.saturating_add(1)..).unwrap_or_default();
if pairs.len() % 2 != 0 {
return Err(error(r.at, "switch cases"));
}
let cases = pairs
.as_chunks::<2>()
.0
.iter()
.map(|p| {
Ok((
usize::try_from(p[0]).map_err(|_| error(r.at, "case value"))?,
block(p[1])?,
))
})
.collect::<Result<Vec<_>>>()?;
(
Op::Switch {
value,
default,
cases,
},
None,
)
}
15 => (Op::Unreachable, None),
19 => {
let ty = body.m.ty(op(r, 0)?, r.at)?;
(Op::Alloca { ty }, Some(body.pointer_to(ty)))
}
20 => {
let (pointer, pt) = body.value_type(r, &mut i)?;
let _ = pt;
let ty = body.m.ty(op(r, i)?, r.at)?;
(Op::Load { pointer }, Some(ty))
}
44 => {
let (pointer, _) = body.value_type(r, &mut i)?;
let (value, _) = body.value_type(r, &mut i)?;
(Op::Store { pointer, value }, None)
}
38 => {
let (pointer, _) = body.value_type(r, &mut i)?;
let value = body.value(r, &mut i)?;
let op = op(r, i)?;
let ty = body.m.value_types.get(value).copied();
let ty = match ty {
Some(t) if value < body.next() => t,
_ => return Err(error(r.at, "an atomicrmw of a value defined later")),
};
(Op::AtomicRmw { pointer, value, op }, Some(ty))
}
46 => {
let (pointer, _) = body.value_type(r, &mut i)?;
let (compare, ty) = body.value_type(r, &mut i)?;
let value = body.value(r, &mut i)?;
let flag = body
.m
.types
.iter()
.position(|t| *t == Type::Integer(1))
.ok_or_else(|| error(r.at, "a cmpxchg without a boolean type"))?;
let pair = Type::Struct {
name: None,
elements: vec![ty, flag],
};
let result = body.m.intern(pair);
(
Op::CmpXchg {
pointer,
compare,
value,
},
Some(result),
)
}
36 => (Op::Fence, None),
43 => {
i = 2;
let source = body.m.ty(op(r, 1)?, r.at)?;
let (base, _) = body.value_type(r, &mut i)?;
let mut indices = Vec::new();
while i < r.operands.len() {
indices.push(body.value_type(r, &mut i)?.0);
}
let mut t = source;
for &index in indices.iter().skip(1) {
t = match body.m.type_at(t, r.at)?.clone() {
Type::Array(_, e) | Type::Vector(_, e) => e,
Type::Struct { elements, .. } => {
let k = match body.constant_index(index) {
Some(k) => k,
None => {
return Err(error(r.at, "a structure index that is not constant"));
}
};
*elements
.get(k)
.ok_or_else(|| error(r.at, "getelementptr structure index"))?
}
_ => return Err(error(r.at, "getelementptr into a scalar")),
};
}
(Op::Gep { base, indices }, Some(body.pointer_to(t)))
}
other => {
return Err(error(
r.at,
format!("instruction {} (record {other}) is not supported", refused(other)),
));
}
};
let result = match ty {
Some(t) => body.result(t, r.at)?,
None => None,
};
let terminator = matches!(
op,
Op::Return(_)
| Op::Branch { .. }
| Op::CondBranch { .. }
| Op::Switch { .. }
| Op::Unreachable
);
instructions.push(Instruction {
op,
result,
block: body.block,
at: r.at,
precise: false,
attachments: Vec::new(),
});
if terminator {
body.block = body.block.saturating_add(1);
}
}
if body.block != body.blocks {
return Err(error(block.at, "basic blocks without terminators"));
}
let end = body.next();
let blocks = body.blocks;
for inst in &instructions {
if inst.block >= blocks {
return Err(error(inst.at, "an instruction after the last terminator"));
}
if let Some(v) = inst.op.operands().into_iter().find(|v| *v >= end) {
return Err(error(inst.at, format!("value {v} of {end} values")));
}
if let Some(b) = inst.op.blocks().into_iter().find(|b| *b >= blocks) {
return Err(error(inst.at, format!("basic block {b} of {blocks}")));
}
if let Op::Call { function, args } = &inst.op {
if *function >= end {
return Err(error(inst.at, format!("callee {function} of {end} values")));
}
if let Some(Value::Function { ty, .. }) = self.values.get(*function)
&& let Some(Type::Function {
vararg: false,
params,
..
}) = self.types.get(*ty)
&& params.len() != args.len()
{
return Err(error(
inst.at,
format!(
"a call of {} arguments to a function of {} parameters",
args.len(),
params.len()
),
));
}
}
}
let locals = self.values.split_off(base);
let local_types = self.value_types.split_off(base);
Ok(Function {
value,
blocks,
instructions,
first: base,
locals,
local_types,
attachments: function_attachments,
})
}
pub fn read(top: &[Block]) -> Result<Self> {
let module = match top {
[m] if m.id == MODULE => m,
_ => return Err(error(0, "bitcode is not one module block")),
};
let mut m = Module {
at: module.at,
..Module::default()
};
let mut bodies: Vec<ValueId> = Vec::new();
let mut version = None;
for e in &module.elements {
match e {
crate::bitstream::Element::Record(r) => match r.code {
1 => version = Some(op(r, 0)?),
2..=6 | 11 | 13 | 16 => {} 7 => {
let t = m.ty(op(r, 0)?, r.at)?;
let flags = op(r, 1)?;
let address_space =
u32::try_from(flags >> 2).map_err(|_| error(r.at, "address space"))?;
let (ty, value_ty) = if flags & 2 != 0 {
let pointer = m.intern(Type::Pointer(t, address_space));
(pointer, t)
} else {
match m.type_at(t, r.at)? {
Type::Pointer(inner, _) => (t, *inner),
_ => return Err(error(r.at, "a global whose type is not a pointer")),
}
};
let init = match op(r, 2)? {
0 => None,
n => Some(
usize::try_from(n.saturating_sub(1))
.map_err(|_| error(r.at, "initial value id"))?,
),
};
m.push(
Value::Global {
ty,
value_ty,
init,
constant: flags & 1 != 0,
address_space,
},
ty,
r.at,
)?;
}
8 => {
let t = m.ty(op(r, 0)?, r.at)?;
let pointer = m.intern(Type::Pointer(t, 0));
let declaration = op(r, 2)? != 0;
let attributes = r
.operands
.get(4)
.and_then(|&n| usize::try_from(n).ok())
.and_then(|n| n.checked_sub(1));
let v = m.push(
Value::Function {
ty: t,
declaration,
attributes,
},
pointer,
r.at,
)?;
if !declaration {
bodies.push(v);
}
}
other => return Err(error(r.at, format!("module record {other}"))),
},
crate::bitstream::Element::Block(b) => match b.id {
BLOCKINFO | USELIST => {}
PARAMATTR_GROUP => m.read_attribute_groups(b)?,
PARAMATTR => m.read_attribute_lists(b),
TYPES => m.read_types(b)?,
CONSTANTS => m.read_constants(b)?,
METADATA => m.read_metadata(b)?,
VALUE_SYMTAB => m.read_symbols(b, 0)?,
FUNCTION => {
if version != Some(1) {
return Err(error(b.at, "only relative value ids (module version 1)"));
}
if bodies.is_empty() {
return Err(error(b.at, "function body without a definition"));
}
let value = bodies.remove(0);
let f = m.read_function(b, value)?;
m.functions.push(f);
}
other => return Err(error(b.at, format!("module block {other}"))),
},
}
}
if !bodies.is_empty() {
return Err(error(module.at, "a defined function has no body"));
}
Ok(m)
}
}