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>,
packed: bool,
literal: bool,
},
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>,
source: TypeId,
inbounds: bool,
},
Cast {
op: CastOp,
value: ValueId,
},
Binary {
op: BinaryOp,
lhs: ValueId,
rhs: ValueId,
flags: u64,
},
Compare {
predicate: u64,
lhs: ValueId,
rhs: ValueId,
},
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Linkage {
#[default]
External,
Appending,
Internal,
WeakAny,
WeakOdr,
LinkOnceAny,
LinkOnceOdr,
ExternWeak,
Common,
Private,
AvailableExternally,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct GlobalProperties {
pub linkage: Linkage,
pub visibility: u8,
pub dll_storage: u8,
pub unnamed_addr: bool,
pub section: Option<String>,
pub alignment: u64,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Value {
Function {
ty: TypeId,
declaration: bool,
attributes: Option<usize>,
properties: GlobalProperties,
calling_conv: u64,
gc: Option<String>,
},
Global {
ty: TypeId,
value_ty: TypeId,
init: Option<ValueId>,
constant: bool,
address_space: u32,
properties: GlobalProperties,
thread_local: u8,
externally_initialized: bool,
},
Constant(Constant),
Local,
}
#[derive(Clone, Debug, PartialEq)]
pub enum Metadata {
String(Vec<u8>),
Value {
ty: TypeId,
value: ValueId,
},
Node(Vec<Option<MetadataId>>),
Distinct(Vec<Option<MetadataId>>),
}
impl Metadata {
pub fn operands(&self) -> Option<&[Option<MetadataId>]> {
match self {
Metadata::Node(ops) | Metadata::Distinct(ops) => Some(ops),
Metadata::String(_) | Metadata::Value { .. } => None,
}
}
}
#[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,
AddrSpaceCast,
}
#[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>,
attributes: Option<usize>,
calling_conv: u64,
tail: TailCall,
},
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,
size: ValueId,
align: u64,
inalloca: bool,
},
Load {
pointer: ValueId,
align: u64,
volatile: bool,
atomic: Option<Atomic>,
},
Store {
pointer: ValueId,
value: ValueId,
align: u64,
volatile: bool,
atomic: Option<Atomic>,
},
Gep {
base: ValueId,
indices: Vec<ValueId>,
source: TypeId,
inbounds: bool,
},
AtomicRmw {
pointer: ValueId,
value: ValueId,
op: u64,
volatile: bool,
atomic: Atomic,
},
CmpXchg {
pointer: ValueId,
compare: ValueId,
value: ValueId,
volatile: bool,
weak: bool,
success: Atomic,
failure: Ordering,
},
Fence(Atomic),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Ordering {
NotAtomic,
Unordered,
Monotonic,
Acquire,
Release,
AcquireRelease,
SequentiallyConsistent,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Atomic {
pub ordering: Ordering,
pub single_thread: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TailCall {
#[default]
None,
Tail,
MustTail,
}
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::Alloca { size, .. } => vec![*size],
Op::Branch { .. } | Op::Unreachable | 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 flags: u64,
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>,
pub names: Vec<(ValueId, String)>,
pub block_names: Vec<(usize, String)>,
pub block_use_orders: Vec<(usize, Vec<u64>)>,
}
impl Function {
pub fn name(&self, value: ValueId) -> Option<&str> {
self.names
.iter()
.find(|(v, _)| *v == value)
.map(|(_, n)| n.as_str())
}
pub fn block_name(&self, block: usize) -> Option<&str> {
self.block_names
.iter()
.find(|(b, _)| *b == block)
.map(|(_, n)| n.as_str())
}
}
#[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 triple: Option<String>,
pub datalayout: Option<String>,
pub asm: Option<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 },
packed: op(r, 0)? != 0,
literal: r.code == 18,
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 => Constant::String(text(&r.operands, r.at)?),
9 => {
let mut bytes = text(&r.operands, r.at)?;
bytes.push(0);
Constant::String(bytes)
}
22 => Constant::Data(r.operands.clone()),
12 | 20 => {
let explicit = r.operands.len() % 2 == 1;
let ops = r.operands.get(usize::from(explicit)..).unwrap_or_default();
let (pairs, rest) = ops.as_chunks::<2>();
let ([base_ty, base], true) = (
pairs.first().unwrap_or(&[0, 0]),
rest.is_empty() && !pairs.is_empty(),
) else {
return Err(error(r.at, "constant getelementptr operands"));
};
let source = if explicit {
self.ty(op(r, 0)?, r.at)?
} else {
match self.type_at(self.ty(*base_ty, r.at)?, r.at)? {
Type::Pointer(pointee, _) => *pointee,
_ => {
return Err(error(
r.at,
"a constant getelementptr of a value that is not a pointer",
));
}
}
};
Constant::Gep {
base: id(*base)?,
indices: pairs
.iter()
.skip(1)
.map(|[_, index]| id(*index))
.collect::<Result<_>>()?,
source,
inbounds: r.code == 20,
}
}
10 => Constant::Binary {
op: binary(op(r, 0)?, r.at)?,
lhs: id(op(r, 1)?)?,
rhs: id(op(r, 2)?)?,
flags: r.operands.get(3).copied().unwrap_or(0),
},
17 => Constant::Compare {
lhs: id(op(r, 1)?)?,
rhs: id(op(r, 2)?)?,
predicate: op(r, 3)?,
},
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} ({} is not supported)",
constant_record(other)
),
));
}
};
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 => {
let ops = r
.operands
.iter()
.map(|&v| {
if v == 0 {
Ok(None)
} else {
md(v.saturating_sub(1), r.at).map(Some)
}
})
.collect::<Result<_>>()?;
self.metadata.push(if r.code == 3 {
Metadata::Node(ops)
} else {
Metadata::Distinct(ops)
});
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()));
}
7 | 12..=32 => {
return Err(error(
r.at,
format!(
"metadata record {other}: debug information is not supported",
other = r.code
),
));
}
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,
})
}
}
type Symbols = (Vec<(ValueId, String)>, Vec<(usize, String)>);
fn read_symbols(block: &Block) -> Result<Symbols> {
let (mut values, mut blocks) = (Vec::new(), Vec::new());
for r in block.records() {
let id = usize::try_from(op(r, 0)?).map_err(|_| error(r.at, "value id"))?;
let skip = if r.code == 3 { 2 } else { 1 };
let name = utf8(text(r.operands.get(skip..).unwrap_or_default(), r.at)?, r.at)?;
match r.code {
1 | 3 => values.push((id, name)),
2 => blocks.push((id, name)),
other => return Err(error(r.at, format!("symbol record {other}"))),
}
}
Ok((values, blocks))
}
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, space: u32) -> TypeId {
self.m.intern(Type::Pointer(t, space))
}
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 alignment(stored: u64, at: usize) -> Result<u64> {
match stored {
0 => Ok(0),
n if n <= 30 => Ok(1u64.wrapping_shl(u32::try_from(n).unwrap_or(0).wrapping_sub(1))),
_ => Err(error(at, "alignment")),
}
}
fn ordering(code: u64, at: usize) -> Result<Ordering> {
Ok(match code {
0 => Ordering::NotAtomic,
1 => Ordering::Unordered,
2 => Ordering::Monotonic,
3 => Ordering::Acquire,
4 => Ordering::Release,
5 => Ordering::AcquireRelease,
6 => Ordering::SequentiallyConsistent,
other => return Err(error(at, format!("atomic ordering {other}"))),
})
}
fn atomic(ordering_code: u64, scope: u64, at: usize) -> Result<Atomic> {
Ok(Atomic {
ordering: ordering(ordering_code, at)?,
single_thread: match scope {
0 => true,
1 => false,
other => return Err(error(at, format!("synchronisation scope {other}"))),
},
})
}
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,
12 => CastOp::AddrSpaceCast,
other => return Err(error(at, format!("cast opcode {other}"))),
})
}
#[expect(clippy::too_many_arguments, reason = "the field positions of the two records")]
fn global_properties(
r: &Record,
linkage: usize,
alignment_at: usize,
section: usize,
visibility: usize,
unnamed_addr: usize,
dll_storage: usize,
comdat: usize,
sections: &[String],
) -> Result<GlobalProperties> {
let raw = op(r, linkage)?;
let decoded = match raw {
0 | 5 | 6 | 15 => Linkage::External,
2 => Linkage::Appending,
3 => Linkage::Internal,
7 => Linkage::ExternWeak,
8 => Linkage::Common,
9 | 13 | 14 => Linkage::Private,
12 => Linkage::AvailableExternally,
1 | 16 => Linkage::WeakAny,
10 | 17 => Linkage::WeakOdr,
4 | 18 => Linkage::LinkOnceAny,
11 | 19 => Linkage::LinkOnceOdr,
_ => Linkage::External,
};
let field = |i: usize| r.operands.get(i).copied();
if field(comdat).map_or(matches!(raw, 1 | 4 | 10 | 11), |c| c != 0) {
return Err(error(r.at, "a global value in a comdat is not supported"));
}
let local = matches!(decoded, Linkage::Internal | Linkage::Private);
Ok(GlobalProperties {
linkage: decoded,
visibility: match field(visibility).unwrap_or(0) {
_ if local => 0,
v @ 0..=2 => u8::try_from(v).unwrap_or(0),
_ => 0,
},
dll_storage: match field(dll_storage) {
Some(v @ 0..=2) => u8::try_from(v).unwrap_or(0),
Some(_) => 0,
None => match raw {
5 => 1,
6 => 2,
_ => 0,
},
},
unnamed_addr: field(unnamed_addr).is_some_and(|v| v != 0),
section: match op(r, section)? {
0 => None,
n => Some(
usize::try_from(n.saturating_sub(1))
.ok()
.and_then(|n| sections.get(n))
.cloned()
.ok_or_else(|| error(r.at, "a global value names a section the module lacks"))?,
),
},
alignment: alignment(op(r, alignment_at)?, r.at)?,
})
}
fn constant_record(code: u64) -> &'static str {
match code {
5 => "an integer wider than 64 bits",
13 => "a constant select",
14 => "a constant extractelement",
15 => "a constant insertelement",
16 | 19 => "a constant shufflevector",
18 => "inline assembly (old form)",
21 => "a block address",
23 | 30 => "inline assembly",
_ => "an unknown constant",
}
}
fn refused(code: u64) -> &'static str {
match code {
4 | 30 => "getelementptr (old form)",
13 => "invoke",
23 => "va_arg",
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();
let (mut names, mut block_names, mut block_use_orders) = (Vec::new(), Vec::new(), 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 => {
let (values, blocks) = read_symbols(b)?;
names.extend(values);
block_names.extend(blocks);
}
METADATA_ATTACHMENT => read_attachments(
body.m.precise_kind,
b,
&mut instructions,
&mut function_attachments,
)?,
METADATA => {
if let Some(r) = b.records().next() {
return Err(error(
r.at,
"metadata of a function body (function-local metadata) is not supported",
));
}
}
USELIST => {
for r in b.records() {
let (last, order) = r
.operands
.split_last()
.filter(|(_, order)| order.len() >= 2)
.ok_or_else(|| error(r.at, "use-list order"))?;
match r.code {
1 => {}
2 => block_use_orders.push((
usize::try_from(*last).map_err(|_| error(r.at, "use-list block"))?,
order.to_vec(),
)),
other => return Err(error(r.at, format!("use-list record {other}"))),
}
}
}
other => return Err(error(b.at, format!("block {other} in a function"))),
}
continue;
}
crate::bitstream::Element::Record(r) => r,
};
let mut i = 0;
let mut flags = 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 => {
return Err(error(r.at, "debug locations are not supported"));
}
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)?;
flags = r.operands.get(i.saturating_add(1)).copied().unwrap_or(0);
(Op::Binary { op: o, lhs, rhs }, Some(t))
}
3 => {
let (value, _) = body.value_type(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)?;
flags = r.operands.get(i.saturating_add(1)).copied().unwrap_or(0);
let lanes = match body.m.type_at(t, r.at)? {
Type::Vector(n, _) => Some(*n),
_ => None,
};
let flag = body.m.intern(Type::Integer(1));
let bool_ty = match lanes {
Some(n) => body.m.intern(Type::Vector(n, flag)),
None => flag,
};
(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 => {
let attributes = usize::try_from(op(r, 0)?)
.map_err(|_| error(r.at, "call attribute list"))?
.checked_sub(1);
if attributes.is_some_and(|a| a >= body.m.attribute_lists.len()) {
return Err(error(r.at, "a call names an attribute list the module lacks"));
}
i = 1;
let cc = op(r, i)?;
i = i.saturating_add(1);
let tail = if cc & (1 << 14) != 0 {
TailCall::MustTail
} else if cc & 1 != 0 {
TailCall::Tail
} else {
TailCall::None
};
let calling_conv = (cc >> 1) & 0x1FFF;
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,
attributes,
calling_conv,
tail,
},
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"))
};
if op(r, 0)? >> 16 == 0x4B5 {
return Err(error(r.at, "switch with case ranges is not supported"));
}
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 stated = body.m.ty(op(r, 0)?, r.at)?;
let packed = op(r, 3)?;
let ty = if packed & (1 << 6) != 0 {
stated
} else {
match body.m.type_at(stated, r.at)? {
Type::Pointer(t, _) => *t,
_ => {
return Err(error(
r.at,
"an alloca of the old form whose type is not a pointer",
));
}
}
};
let size = usize::try_from(op(r, 2)?).map_err(|_| error(r.at, "alloca size"))?;
(
Op::Alloca {
ty,
size,
align: alignment(packed & !((1 << 5) | (1 << 6)), r.at)?,
inalloca: packed & (1 << 5) != 0,
},
Some(body.pointer_to(ty, 0)),
)
}
20 | 41 => {
let (pointer, pt) = body.value_type(r, &mut i)?;
let tail = if r.code == 20 { 2 } else { 4 };
let ty = if r.operands.len() == i.saturating_add(tail).saturating_add(1) {
let t = body.m.ty(op(r, i)?, r.at)?;
i = i.saturating_add(1);
t
} else {
match body.m.type_at(pt, r.at)? {
Type::Pointer(t, _) => *t,
_ => return Err(error(r.at, "a load from a value that is not a pointer")),
}
};
if r.operands.len() != i.saturating_add(tail) {
return Err(error(r.at, "load operands"));
}
let atomic = if r.code == 41 {
Some(atomic(
op(r, i.saturating_add(2))?,
op(r, i.saturating_add(3))?,
r.at,
)?)
} else {
None
};
(
Op::Load {
pointer,
align: alignment(op(r, i)?, r.at)?,
volatile: op(r, i.saturating_add(1))? != 0,
atomic,
},
Some(ty),
)
}
24 | 42 | 44 | 45 => {
let (pointer, _) = body.value_type(r, &mut i)?;
let value = if matches!(r.code, 44 | 45) {
body.value_type(r, &mut i)?.0
} else {
body.value(r, &mut i)?
};
let tail = if matches!(r.code, 24 | 44) { 2 } else { 4 };
if r.operands.len() != i.saturating_add(tail) {
return Err(error(r.at, "store operands"));
}
let atomic = if matches!(r.code, 42 | 45) {
Some(atomic(
op(r, i.saturating_add(2))?,
op(r, i.saturating_add(3))?,
r.at,
)?)
} else {
None
};
(
Op::Store {
pointer,
value,
align: alignment(op(r, i)?, r.at)?,
volatile: op(r, i.saturating_add(1))? != 0,
atomic,
},
None,
)
}
38 => {
let (pointer, pt) = body.value_type(r, &mut i)?;
let value = body.value(r, &mut i)?;
let ty = match body.m.type_at(pt, r.at)? {
Type::Pointer(t, _) => *t,
_ => return Err(error(r.at, "an atomicrmw on a value that is not a pointer")),
};
(
Op::AtomicRmw {
pointer,
value,
op: op(r, i)?,
volatile: op(r, i.saturating_add(1))? != 0,
atomic: atomic(op(r, i.saturating_add(2))?, op(r, i.saturating_add(3))?, r.at)?,
},
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)?;
if r.operands.len() != i.saturating_add(5) {
return Err(error(
r.at,
"cmpxchg without its failure ordering and weak flag (an old form)",
));
}
let success = atomic(op(r, i.saturating_add(1))?, op(r, i.saturating_add(2))?, r.at)?;
let failure = ordering(op(r, i.saturating_add(3))?, r.at)?;
let flag = body.m.intern(Type::Integer(1));
let pair = Type::Struct {
name: None,
elements: vec![ty, flag],
packed: false,
literal: true,
};
let result = body.m.intern(pair);
(
Op::CmpXchg {
pointer,
compare,
value,
volatile: op(r, i)? != 0,
weak: op(r, i.saturating_add(4))? != 0,
success,
failure,
},
Some(result),
)
}
36 => (Op::Fence(atomic(op(r, 0)?, op(r, 1)?, r.at)?), None),
43 => {
i = 2;
let inbounds = op(r, 0)? != 0;
let source = body.m.ty(op(r, 1)?, r.at)?;
let (base, base_ty) = body.value_type(r, &mut i)?;
let space = match body.m.type_at(base_ty, r.at)? {
Type::Pointer(_, space) => *space,
_ => return Err(error(r.at, "getelementptr of a value that is not a pointer")),
};
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,
source,
inbounds,
},
Some(body.pointer_to(t, space)),
)
}
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,
flags,
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,
names,
block_names,
block_use_orders,
})
}
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;
let (mut sections, mut gcs) = (Vec::new(), Vec::new());
for e in &module.elements {
match e {
crate::bitstream::Element::Record(r) => match r.code {
1 => version = Some(op(r, 0)?),
2 => m.triple = Some(utf8(text(&r.operands, r.at)?, r.at)?),
3 => m.datalayout = Some(utf8(text(&r.operands, r.at)?, r.at)?),
4 => m.asm = Some(utf8(text(&r.operands, r.at)?, r.at)?),
5 => sections.push(utf8(text(&r.operands, r.at)?, r.at)?),
11 => gcs.push(utf8(text(&r.operands, r.at)?, r.at)?),
6 | 13 | 16 => {} 7 => {
let t = m.ty(op(r, 0)?, r.at)?;
let flags = op(r, 1)?;
let (ty, value_ty, address_space) = if flags & 2 != 0 {
let space =
u32::try_from(flags >> 2).map_err(|_| error(r.at, "address space"))?;
(m.intern(Type::Pointer(t, space)), t, space)
} else {
match m.type_at(t, r.at)? {
Type::Pointer(inner, space) => (t, *inner, *space),
_ => 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"))?,
),
};
let properties = global_properties(r, 3, 4, 5, 6, 8, 10, 11, §ions)?;
m.push(
Value::Global {
ty,
value_ty,
init,
constant: flags & 1 != 0,
address_space,
properties,
thread_local: u8::try_from(r.operands.get(7).copied().unwrap_or(0))
.ok()
.filter(|&t| t <= 4)
.ok_or_else(|| error(r.at, "thread-local model"))?,
externally_initialized: r.operands.get(9).is_some_and(|&v| v != 0),
},
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));
for (field, what) in [
(10, "prologue data"),
(13, "prefix data"),
(14, "a personality function"),
] {
if r.operands.get(field).is_some_and(|&v| v != 0) {
return Err(error(r.at, format!("a function with {what} is not supported")));
}
}
let gc = match r.operands.get(8).copied().unwrap_or(0) {
0 => None,
n => Some(
usize::try_from(n.saturating_sub(1))
.ok()
.and_then(|n| gcs.get(n))
.cloned()
.ok_or_else(|| {
error(r.at, "a function names a garbage collector the module lacks")
})?,
),
};
let v = m.push(
Value::Function {
ty: t,
declaration,
attributes,
properties: global_properties(r, 3, 5, 6, 7, 9, 11, 12, §ions)?,
calling_conv: op(r, 1)?,
gc,
},
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 => {
let (values, blocks) = read_symbols(b)?;
if let Some((_, name)) = blocks.first() {
return Err(error(
b.at,
format!("a basic block name ({name}) in the module's symbol table"),
));
}
m.names.extend(values);
}
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)
}
}