use llvmkit_ir::attributes::{AttrIndex, AttrKind, Attribute, AttributeStorage};
use std::collections::HashMap;
use llvmkit_ir::{
Align, AnyTypeEnum, AtomicOrdering, CallingConv, DllStorageClass, FastMathFlags, IrError,
Linkage, MaybeAlign, Module, SelectionKind, StructType, SyncScope, ThreadLocalMode, Type,
UnnamedAddr, UseListOrderBBRecord, UseListOrderRecord, Visibility, derived_types::PointerType,
};
use llvmkit_support::{Span, Spanned};
use crate::ll_lexer::{LexError, Lexer};
use crate::ll_token::{IntLit, Keyword, NumBase, PrimitiveTy, Sign, Token};
use crate::numbered_values::NumberedValues;
use crate::parse_error::{DiagLoc, ParseError, ParseResult};
use crate::slot_mapping::{GlobalRef, SlotMapping};
pub fn describe(t: &Token<'_>) -> String {
match t {
Token::Eof => "<eof>".into(),
Token::DotDotDot => "'...'".into(),
Token::Equal => "'='".into(),
Token::Comma => "','".into(),
Token::Star => "'*'".into(),
Token::LSquare => "'['".into(),
Token::RSquare => "']'".into(),
Token::LBrace => "'{'".into(),
Token::RBrace => "'}'".into(),
Token::Less => "'<'".into(),
Token::Greater => "'>'".into(),
Token::LParen => "'('".into(),
Token::RParen => "')'".into(),
Token::Exclaim => "'!'".into(),
Token::Bar => "'|'".into(),
Token::Colon => "':'".into(),
Token::Hash => "'#'".into(),
Token::LabelStr(_) => "label".into(),
Token::GlobalVar(_) | Token::GlobalId(_) => "global identifier".into(),
Token::LocalVar(_) | Token::LocalVarId(_) => "local identifier".into(),
Token::ComdatVar(_) => "comdat identifier".into(),
Token::MetadataVar(_) => "metadata reference".into(),
Token::StringConstant(_) => "string constant".into(),
Token::AttrGrpId(_) => "attribute group id".into(),
Token::SummaryId(_) => "summary id".into(),
Token::IntegerLit(_) => "integer constant".into(),
Token::FloatLit(_) => "floating-point constant".into(),
Token::PrimitiveType(_) => "primitive type".into(),
Token::Instruction(_) => "instruction opcode".into(),
Token::Kw(k) => format!("keyword '{}'", keyword_text(*k)),
Token::DwarfTag(_) => "DWARF tag".into(),
Token::DwarfAttEncoding(_) => "DWARF attribute encoding".into(),
Token::DwarfVirtuality(_) => "DWARF virtuality".into(),
Token::DwarfLang(_) => "DWARF language".into(),
Token::DwarfSourceLangName(_) => "DWARF source language name".into(),
Token::DwarfCC(_) => "DWARF calling convention".into(),
Token::DwarfOp(_) => "DWARF operation".into(),
Token::DwarfMacinfo(_) => "DWARF macinfo".into(),
Token::DwarfEnumKind(_) => "DWARF enum kind".into(),
Token::DiFlag(_) => "DI flag".into(),
Token::DiSpFlag(_) => "DI subprogram flag".into(),
Token::ChecksumKind(_) => "checksum kind".into(),
Token::EmissionKind(_) => "emission kind".into(),
Token::NameTableKind(_) => "name-table kind".into(),
Token::FixedPointKind(_) => "fixed-point kind".into(),
Token::SpecializedMetadata(_) => "specialized metadata kind".into(),
Token::DbgRecordType(_) => "dbg record type".into(),
}
}
fn keyword_text(k: Keyword) -> &'static str {
match k {
Keyword::Target => "target",
Keyword::Triple => "triple",
Keyword::Datalayout => "datalayout",
Keyword::SourceFilename => "source_filename",
Keyword::Module => "module",
Keyword::Asm => "asm",
Keyword::Type => "type",
Keyword::Declare => "declare",
Keyword::Define => "define",
Keyword::Global => "global",
Keyword::Constant => "constant",
Keyword::External => "external",
Keyword::Internal => "internal",
Keyword::Private => "private",
Keyword::Common => "common",
Keyword::Addrspace => "addrspace",
Keyword::Opaque => "opaque",
Keyword::Zeroinitializer => "zeroinitializer",
Keyword::Null => "null",
Keyword::None => "none",
Keyword::Undef => "undef",
Keyword::Poison => "poison",
Keyword::True => "true",
Keyword::False => "false",
Keyword::X => "x",
_ => "<keyword>",
}
}
#[derive(Debug, Clone, Copy)]
struct TypeEntry<'ctx> {
ty: Type<'ctx>,
}
struct MetadataSlotEntry {
id: llvmkit_ir::metadata::MetadataId,
defined: bool,
first_ref: Span,
}
#[derive(Default)]
struct FunctionSuffix<'ctx> {
attr_groups: Vec<u32>,
section: Option<String>,
partition: Option<String>,
comdat: Option<Option<String>>,
align: MaybeAlign,
gc: Option<String>,
prefix_data: Option<llvmkit_ir::Constant<'ctx>>,
prologue_data: Option<llvmkit_ir::Constant<'ctx>>,
personality_fn: Option<llvmkit_ir::Constant<'ctx>>,
metadata: Vec<(
llvmkit_ir::metadata::MetadataAttachmentKind,
llvmkit_ir::metadata::MetadataId,
)>,
_marker: core::marker::PhantomData<&'ctx ()>,
}
pub struct Parser<'src, 'ctx> {
lex: Lexer<'src>,
current: Spanned<Token<'src>>,
module: &'ctx Module<'ctx>,
named_types: HashMap<String, TypeEntry<'ctx>>,
numbered_types: HashMap<u32, TypeEntry<'ctx>>,
next_unnamed_type_id: u32,
numbered_globals: NumberedValues<GlobalRef<'ctx>>,
numbered_attr_groups: NumberedValues<llvmkit_ir::attributes::AttributeStorage>,
metadata_slots: HashMap<u32, MetadataSlotEntry>,
deferred_global_initializers: Vec<DeferredGlobalInitializer<'ctx>>,
unresolved_intrinsic_uses: Vec<UnresolvedIntrinsicUse<'ctx>>,
}
#[derive(Debug, Default)]
pub struct ParsedModule<'ctx> {
pub slot_mapping: SlotMapping<'ctx>,
pub summary_index: Option<crate::module_summary::ModuleSummaryIndex>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum ForwardGlobalRef {
Name(String),
Id(u32),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
enum ForwardBlockRef {
Name(String),
Id(u32),
}
enum DeferredConstantKind {
BlockAddress {
function: ForwardGlobalRef,
block: ForwardBlockRef,
loc: Span,
},
DsoLocalEquivalent {
target: ForwardGlobalRef,
loc: Span,
},
NoCfi {
target: ForwardGlobalRef,
loc: Span,
},
}
struct DeferredGlobalInitializer<'ctx> {
global: llvmkit_ir::GlobalVariable<'ctx>,
value: DeferredConstantKind,
}
struct UnresolvedIntrinsicUse<'ctx> {
name: String,
loc: Span,
parsed_fn_ty: llvmkit_ir::FunctionType<'ctx>,
provisional: llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>,
direct_callee: bool,
}
enum ParsedDirectCallee {
Name { name: String, loc: Span },
Id { id: u32, loc: Span },
InlineAsm(ParsedInlineAsm),
}
struct ParsedInlineAsm {
asm: String,
constraints: String,
has_side_effects: bool,
is_align_stack: bool,
dialect: llvmkit_ir::AsmDialect,
can_unwind: bool,
}
enum ParsedCallee<'ctx> {
Function(llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>),
InlineAsm(llvmkit_ir::InlineAsm<'ctx>),
}
#[derive(Debug)]
enum ValId<'ctx> {
LocalId(u32),
GlobalId(u32),
LocalName(String),
GlobalName(String),
ApsInt { negative: bool, words: Box<[u64]> },
ApFloat(u128),
Null,
Undef,
Poison,
Zero,
Constant(llvmkit_ir::Constant<'ctx>),
Value(llvmkit_ir::Value<'ctx>),
ConstantSplat(llvmkit_ir::Constant<'ctx>),
}
fn is_supported_constant_expr_opcode(op: crate::ll_token::Opcode) -> bool {
matches!(
op,
crate::ll_token::Opcode::GetElementPtr
| crate::ll_token::Opcode::BitCast
| crate::ll_token::Opcode::AddrSpaceCast
| crate::ll_token::Opcode::IntToPtr
| crate::ll_token::Opcode::PtrToInt
| crate::ll_token::Opcode::PtrToAddr
| crate::ll_token::Opcode::Trunc
| crate::ll_token::Opcode::Add
| crate::ll_token::Opcode::Sub
| crate::ll_token::Opcode::Xor
| crate::ll_token::Opcode::ExtractElement
| crate::ll_token::Opcode::InsertElement
| crate::ll_token::Opcode::ShuffleVector
)
}
#[derive(Clone, Copy)]
struct ParsedAliasHeader {
linkage: Linkage,
visibility: Visibility,
dll_storage_class: DllStorageClass,
thread_local_mode: ThreadLocalMode,
unnamed_addr: UnnamedAddr,
}
fn map_lex_error(e: LexError) -> ParseError {
match e {
LexError::IntegerWidthOutOfRange { width, max, span } => {
ParseError::IntegerWidthOutOfRange {
width,
max,
loc: DiagLoc::span(span),
}
}
other => ParseError::Lex(other),
}
}
impl<'src, 'ctx> Parser<'src, 'ctx> {
pub fn new(src: &'src [u8], module: &'ctx Module<'ctx>) -> ParseResult<Self> {
let mut lex = Lexer::new(src);
let current = lex.next_token().map_err(map_lex_error)?;
Ok(Self {
lex,
current,
module,
named_types: HashMap::new(),
numbered_types: HashMap::new(),
next_unnamed_type_id: 0,
numbered_globals: NumberedValues::new(),
numbered_attr_groups: NumberedValues::new(),
metadata_slots: HashMap::new(),
deferred_global_initializers: Vec::new(),
unresolved_intrinsic_uses: Vec::new(),
})
}
pub fn with_slot_mapping(
src: &'src [u8],
module: &'ctx Module<'ctx>,
slots: &SlotMapping<'ctx>,
) -> ParseResult<Self> {
let mut parser = Self::new(src, module)?;
parser.numbered_globals = slots.global_values.clone();
parser.numbered_attr_groups = slots.attribute_groups.clone();
parser.named_types = slots
.named_types
.iter()
.map(|(name, ty)| (name.clone(), TypeEntry { ty: *ty }))
.collect();
parser.numbered_types = slots
.numbered_types
.iter()
.map(|(id, ty)| (*id, TypeEntry { ty: *ty }))
.collect();
parser.next_unnamed_type_id = slots
.numbered_types
.keys()
.next_back()
.map_or(0, |id| id.saturating_add(1));
parser.metadata_slots = slots
.metadata_nodes
.iter()
.map(|(slot, id)| {
(
slot,
MetadataSlotEntry {
id: *id,
defined: true,
first_ref: Span::new(0, 0),
},
)
})
.collect();
Ok(parser)
}
pub fn with_context(
src: &'src [u8],
module: &'ctx Module<'ctx>,
_context: &'ctx mut crate::asm_parser_context::AsmParserContext<'ctx>,
) -> ParseResult<Self> {
Self::new(src, module)
}
fn resolve_md_slot(&mut self, slot: u32, loc: Span) -> llvmkit_ir::metadata::MetadataId {
if let Some(entry) = self.metadata_slots.get(&slot) {
return entry.id;
}
let id = self.module.metadata_reserve();
self.metadata_slots.insert(
slot,
MetadataSlotEntry {
id,
defined: false,
first_ref: loc,
},
);
id
}
fn define_md_slot(
&mut self,
slot: u32,
content: llvmkit_ir::metadata::MetadataKind,
loc: Span,
) -> ParseResult<llvmkit_ir::metadata::MetadataId> {
if let Some(entry) = self.metadata_slots.get_mut(&slot) {
if entry.defined {
return Err(ParseError::Redefinition {
kind: crate::parse_error::SymbolKind::Metadata,
id: crate::parse_error::SymbolId::Numbered(slot),
loc: DiagLoc::span(loc),
});
}
self.module.metadata_set(entry.id, content);
entry.defined = true;
return Ok(entry.id);
}
let id = self.module.metadata_reserve();
self.module.metadata_set(id, content);
self.metadata_slots.insert(
slot,
MetadataSlotEntry {
id,
defined: true,
first_ref: loc,
},
);
Ok(id)
}
pub fn parse_module(mut self) -> ParseResult<ParsedModule<'ctx>> {
loop {
match self.current.value {
Token::Eof => break,
Token::Kw(Keyword::Target) => self.parse_target_definition()?,
Token::Kw(Keyword::SourceFilename) => self.parse_source_filename()?,
Token::Kw(Keyword::Module) => self.parse_module_asm()?,
Token::Kw(Keyword::Uselistorder) => self.parse_module_use_list_order()?,
Token::Kw(Keyword::UselistorderBb) => self.parse_use_list_order_bb()?,
Token::ComdatVar(_) => self.parse_comdat_definition()?,
Token::LocalVar(_) => self.parse_named_type_definition()?,
Token::LocalVarId(_) => self.parse_unnamed_type_definition()?,
Token::GlobalVar(_) | Token::GlobalId(_) => self.parse_global_or_function()?,
Token::Kw(Keyword::Declare) => self.parse_declare()?,
Token::Kw(Keyword::Define) => self.parse_define()?,
Token::Kw(Keyword::Attributes) => self.parse_unnamed_attr_group()?,
Token::Exclaim => self.parse_standalone_metadata()?,
Token::MetadataVar(_) => self.parse_named_metadata()?,
_ => return Err(self.token_error("top-level entity")),
}
}
for (slot, entry) in &self.metadata_slots {
if !entry.defined {
return Err(ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Metadata,
id: crate::parse_error::SymbolId::Numbered(*slot),
loc: DiagLoc::span(entry.first_ref),
});
}
}
self.resolve_deferred_global_initializers()?;
self.validate_intrinsic_uses()?;
Ok(ParsedModule {
slot_mapping: self.into_slot_mapping(),
summary_index: None,
})
}
fn resolve_deferred_global_initializers(&mut self) -> ParseResult<()> {
let deferred = std::mem::take(&mut self.deferred_global_initializers);
for item in deferred {
let constant = match item.value {
DeferredConstantKind::BlockAddress {
function,
block,
loc,
} => {
let f = self.resolve_forward_function(function, loc)?;
if f.basic_blocks().len() == 0 {
return Err(ParseError::Expected {
expected: "cannot take blockaddress inside a declaration".into(),
loc: DiagLoc::span(loc),
});
}
let bb = match block {
ForwardBlockRef::Name(name) => f
.basic_blocks()
.find(|bb| bb.name().as_deref() == Some(name.as_str()))
.ok_or_else(|| ParseError::Expected {
expected: "referenced value is not a basic block".into(),
loc: DiagLoc::span(loc),
})?,
ForwardBlockRef::Id(id) => {
let wanted = id.to_string();
f.basic_blocks()
.find(|bb| bb.name().as_deref() == Some(wanted.as_str()))
.ok_or_else(|| ParseError::Expected {
expected: "referenced value is not a basic block".into(),
loc: DiagLoc::span(loc),
})?
}
};
self.module
.block_address(f, bb)
.map_err(|e| self.builder_err("blockaddress", e))?
}
DeferredConstantKind::DsoLocalEquivalent { target, loc } => {
let global = self.resolve_forward_global_ref(
target,
loc,
"expected a function, alias to function, or ifunc in dso_local_equivalent",
)?;
self.module
.dso_local_equivalent_global(self.global_ref_to_constant(global))
.map_err(|_| ParseError::Expected {
expected: "expected a function, alias to function, or ifunc in dso_local_equivalent".into(),
loc: DiagLoc::span(loc),
})?
}
DeferredConstantKind::NoCfi { target, loc } => {
let global = self.resolve_forward_global_ref(
target,
loc,
"expected global value name in no_cfi",
)?;
self.module
.no_cfi_global(self.global_ref_to_constant(global))
.map_err(|_| ParseError::Expected {
expected: "expected global value name in no_cfi".into(),
loc: DiagLoc::span(loc),
})?
}
};
item.global
.set_initializer(Some(constant))
.map_err(|e| self.builder_err("deferred global initializer", e))?;
}
Ok(())
}
fn validate_intrinsic_uses(&self) -> ParseResult<()> {
for use_site in &self.unresolved_intrinsic_uses {
if !use_site.direct_callee {
return Err(ParseError::Expected {
expected: "intrinsic can only be used as callee".into(),
loc: DiagLoc::span(use_site.loc),
});
}
let id = llvmkit_ir::IntrinsicId::lookup(&use_site.name).ok_or_else(|| {
ParseError::Expected {
expected: "unknown intrinsic".into(),
loc: DiagLoc::span(use_site.loc),
}
})?;
let expected = id.function_type(self.module, &use_site.name).map_err(|_| {
ParseError::Expected {
expected: "intrinsic signature mismatch".into(),
loc: DiagLoc::span(use_site.loc),
}
})?;
if use_site.parsed_fn_ty != expected || use_site.provisional.signature() != expected {
return Err(ParseError::Expected {
expected: "intrinsic signature mismatch".into(),
loc: DiagLoc::span(use_site.loc),
});
}
}
Ok(())
}
fn resolve_forward_function(
&self,
target: ForwardGlobalRef,
loc: Span,
) -> ParseResult<llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>> {
match target {
ForwardGlobalRef::Name(name) => {
self.module
.function_by_name(&name)
.ok_or(ParseError::Expected {
expected: "expected function name in blockaddress".into(),
loc: DiagLoc::span(loc),
})
}
ForwardGlobalRef::Id(id) => self
.numbered_globals
.get(id)
.and_then(|g| match g {
GlobalRef::Function(f) => Some(*f),
_ => None,
})
.ok_or(ParseError::Expected {
expected: "expected function name in blockaddress".into(),
loc: DiagLoc::span(loc),
}),
}
}
fn resolve_forward_global_ref(
&self,
target: ForwardGlobalRef,
loc: Span,
expected: &'static str,
) -> ParseResult<GlobalRef<'ctx>> {
match target {
ForwardGlobalRef::Name(name) => {
self.resolve_global_name_as_ref(name)
.map_err(|_| ParseError::Expected {
expected: expected.into(),
loc: DiagLoc::span(loc),
})
}
ForwardGlobalRef::Id(id) => {
self.resolve_global_id_as_ref(id)
.map_err(|_| ParseError::Expected {
expected: expected.into(),
loc: DiagLoc::span(loc),
})
}
}
}
fn into_slot_mapping(self) -> SlotMapping<'ctx> {
let mut named_types = HashMap::with_capacity(self.named_types.len());
for (name, entry) in self.named_types {
named_types.insert(name, entry.ty);
}
let mut numbered_types = std::collections::BTreeMap::new();
for (id, entry) in self.numbered_types {
numbered_types.insert(id, entry.ty);
}
let mut metadata_nodes = NumberedValues::new();
let mut metadata_entries: Vec<_> = self
.metadata_slots
.into_iter()
.filter(|(_, entry)| entry.defined)
.collect();
metadata_entries.sort_by_key(|(slot, _)| *slot);
for (slot, entry) in metadata_entries {
let _ = metadata_nodes.add(slot, entry.id);
}
let mut attribute_groups = NumberedValues::new();
let mut attr_entries: Vec<_> = self.module.attribute_groups();
attr_entries.sort_by_key(|(slot, _)| *slot);
for (slot, storage) in attr_entries {
let _ = attribute_groups.add(slot, storage);
}
SlotMapping {
global_values: self.numbered_globals,
named_types,
numbered_types,
attribute_groups,
metadata_nodes,
}
}
#[inline]
fn peek(&self) -> &Token<'src> {
&self.current.value
}
#[inline]
fn loc(&self) -> Span {
self.current.span
}
fn bump(&mut self) -> ParseResult<Span> {
let prev = self.current.span;
self.current = self.lex.next_token().map_err(map_lex_error)?;
Ok(prev)
}
fn require_eof(&self) -> ParseResult<()> {
if matches!(self.peek(), Token::Eof) {
Ok(())
} else {
Err(ParseError::Expected {
expected: "end of string".into(),
loc: DiagLoc::span(self.loc()),
})
}
}
pub(crate) fn parse_type_at_beginning(mut self) -> ParseResult<(Type<'ctx>, usize)> {
let start = self.loc().start;
let ty = self.parse_type(true)?;
let consumed = self.loc().start.saturating_sub(start);
let consumed = usize::try_from(consumed).map_err(|_| ParseError::Expected {
expected: "type byte count fits in usize".into(),
loc: DiagLoc::span(self.loc()),
})?;
Ok((ty, consumed))
}
pub(crate) fn parse_standalone_type(mut self) -> ParseResult<Type<'ctx>> {
let ty = self.parse_type(true)?;
self.require_eof()?;
Ok(ty)
}
pub(crate) fn parse_standalone_constant_value(
mut self,
ty: Type<'ctx>,
) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
let scalar_start = matches!(
self.peek(),
Token::IntegerLit(_)
| Token::FloatLit(_)
| Token::GlobalVar(_)
| Token::GlobalId(_)
| Token::Kw(
Keyword::True
| Keyword::False
| Keyword::Null
| Keyword::Zeroinitializer
| Keyword::Undef
| Keyword::Poison
)
);
let value = match self.parse_global_value(ty) {
Ok(value) => value,
Err(ParseError::Lex(_)) if scalar_start => {
return Err(ParseError::Expected {
expected: "end of string".into(),
loc: DiagLoc::span(self.loc()),
});
}
Err(err) => return Err(err),
};
self.require_eof()?;
Ok(value)
}
fn eat_punct(&mut self, t: PunctKind) -> ParseResult<bool> {
if t.matches(self.peek()) {
self.bump()?;
Ok(true)
} else {
Ok(false)
}
}
fn expect_punct(&mut self, t: PunctKind, expected: &str) -> ParseResult<Span> {
if t.matches(self.peek()) {
self.bump()
} else {
Err(self.expected(expected))
}
}
fn eat_keyword(&mut self, k: Keyword) -> ParseResult<bool> {
if matches!(self.peek(), Token::Kw(got) if *got == k) {
self.bump()?;
Ok(true)
} else {
Ok(false)
}
}
fn expect_keyword(&mut self, k: Keyword, expected: &str) -> ParseResult<Span> {
if matches!(self.peek(), Token::Kw(got) if *got == k) {
self.bump()
} else {
Err(self.expected(expected))
}
}
fn expect_primitive(
&mut self,
p: crate::ll_token::PrimitiveTy,
expected: &str,
) -> ParseResult<Span> {
if matches!(self.peek(), Token::PrimitiveType(got) if *got == p) {
self.bump()
} else {
Err(self.expected(expected))
}
}
fn token_error(&self, expected: &str) -> ParseError {
self.expected(expected)
}
fn expected(&self, expected: &str) -> ParseError {
ParseError::Expected {
expected: expected.into(),
loc: DiagLoc::span(self.loc()),
}
}
fn parse_string_constant(&mut self, expected: &str) -> ParseResult<String> {
let s = match self.peek() {
Token::StringConstant(bytes) => {
let s = std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("UTF-8 string constant"))?
.to_owned();
Some(s)
}
_ => None,
};
match s {
Some(s) => {
self.bump()?;
Ok(s)
}
None => Err(self.expected(expected)),
}
}
fn parse_addr_space_paren(&mut self) -> ParseResult<u32> {
self.expect_punct(PunctKind::LParen, "'(' in addrspace")?;
let n = self.parse_uint32("address space (uint32)")?;
self.expect_punct(PunctKind::RParen, "')' in addrspace")?;
Ok(n)
}
fn parse_uint32(&mut self, expected: &str) -> ParseResult<u32> {
let n = match self.peek() {
Token::IntegerLit(IntLit {
sign: Sign::Pos,
base: NumBase::Dec,
digits,
}) => digits.parse::<u32>().ok(),
_ => None,
};
match n {
Some(n) => {
self.bump()?;
Ok(n)
}
None => Err(self.expected(expected)),
}
}
fn parse_uint64(&mut self, expected: &str) -> ParseResult<u64> {
let n = match self.peek() {
Token::IntegerLit(IntLit {
sign: Sign::Pos,
base: NumBase::Dec,
digits,
}) => digits.parse::<u64>().ok(),
_ => None,
};
match n {
Some(n) => {
self.bump()?;
Ok(n)
}
None => Err(self.expected(expected)),
}
}
fn parse_int_literal(&mut self) -> ParseResult<(bool, u64)> {
let (negative, digits) = match self.peek() {
Token::IntegerLit(IntLit {
sign,
base: NumBase::Dec,
digits,
}) => (matches!(sign, Sign::Neg), *digits),
_ => return Err(self.expected("integer literal")),
};
let value = digits
.parse::<u64>()
.map_err(|_| self.expected("integer literal in u64 range"))?;
self.bump()?;
Ok((negative, value))
}
fn parse_optional_fmf(&mut self) -> ParseResult<FastMathFlags> {
let mut flags = FastMathFlags::empty();
loop {
match self.peek() {
Token::Kw(Keyword::Nnan) => {
flags |= FastMathFlags::NO_NANS;
self.bump()?;
}
Token::Kw(Keyword::Ninf) => {
flags |= FastMathFlags::NO_INFS;
self.bump()?;
}
Token::Kw(Keyword::Nsz) => {
flags |= FastMathFlags::NO_SIGNED_ZEROS;
self.bump()?;
}
Token::Kw(Keyword::Arcp) => {
flags |= FastMathFlags::ALLOW_RECIPROCAL;
self.bump()?;
}
Token::Kw(Keyword::Contract) => {
flags |= FastMathFlags::ALLOW_CONTRACT;
self.bump()?;
}
Token::Kw(Keyword::Afn) => {
flags |= FastMathFlags::APPROX_FUNC;
self.bump()?;
}
Token::Kw(Keyword::Reassoc) => {
flags |= FastMathFlags::ALLOW_REASSOC;
self.bump()?;
}
Token::Kw(Keyword::Fast) => {
flags = FastMathFlags::fast();
self.bump()?;
}
_ => break,
}
}
Ok(flags)
}
fn parse_align_val(&mut self) -> ParseResult<Align> {
self.expect_keyword(Keyword::Align, "'align'")?;
let n = self.parse_uint64("alignment (bytes)")?;
Align::new(n).map_err(|_| ParseError::Expected {
expected: format!("alignment must be non-zero power of two, got {n}"),
loc: DiagLoc::span(self.loc()),
})
}
fn parse_optional_comma_align(&mut self) -> ParseResult<Option<Align>> {
if self.eat_punct(PunctKind::Comma)? {
if matches!(self.peek(), Token::Kw(Keyword::Align)) {
Ok(Some(self.parse_align_val()?))
} else {
Err(self.expected("'align' after ','"))
}
} else {
Ok(None)
}
}
fn parse_atomic_ordering(&mut self, expected: &str) -> ParseResult<AtomicOrdering> {
let ord = match self.peek() {
Token::Kw(Keyword::Unordered) => AtomicOrdering::Unordered,
Token::Kw(Keyword::Monotonic) => AtomicOrdering::Monotonic,
Token::Kw(Keyword::Acquire) => AtomicOrdering::Acquire,
Token::Kw(Keyword::Release) => AtomicOrdering::Release,
Token::Kw(Keyword::AcqRel) => AtomicOrdering::AcquireRelease,
Token::Kw(Keyword::SeqCst) => AtomicOrdering::SequentiallyConsistent,
_ => return Err(self.expected(expected)),
};
self.bump()?;
Ok(ord)
}
fn parse_optional_syncscope(&mut self) -> ParseResult<SyncScope> {
if !matches!(self.peek(), Token::Kw(Keyword::Syncscope)) {
return Ok(SyncScope::System);
}
self.bump()?; self.expect_punct(PunctKind::LParen, "'(' after syncscope")?;
let name = self.parse_string_constant("sync scope name")?;
self.expect_punct(PunctKind::RParen, "')' after sync scope")?;
Ok(match name.as_str() {
"system" => SyncScope::System,
"singlethread" => SyncScope::SingleThread,
_ => SyncScope::Named(name),
})
}
fn current_str_payload(&self) -> Option<String> {
match self.peek() {
Token::GlobalVar(s) | Token::LocalVar(s) => {
std::str::from_utf8(s.as_ref()).ok().map(str::to_owned)
}
_ => None,
}
}
fn parse_target_definition(&mut self) -> ParseResult<()> {
self.expect_keyword(Keyword::Target, "'target'")?;
match self.peek() {
Token::Kw(Keyword::Triple) => {
self.bump()?;
self.expect_punct(PunctKind::Equal, "'=' after target triple")?;
let s = self.parse_string_constant("target-triple string constant")?;
self.module.set_target_triple(Some(s));
Ok(())
}
Token::Kw(Keyword::Datalayout) => {
self.bump()?;
self.expect_punct(PunctKind::Equal, "'=' after target datalayout")?;
let loc = self.loc();
let s = self.parse_string_constant("target-datalayout string constant")?;
self.module.set_data_layout(s).map_err(|e| match e {
IrError::InvalidDataLayout { reason } => ParseError::Expected {
expected: format!("valid datalayout: {reason}"),
loc: DiagLoc::span(loc),
},
other => ParseError::Expected {
expected: format!("valid datalayout: {other}"),
loc: DiagLoc::span(loc),
},
})?;
Ok(())
}
_ => Err(self.expected("'triple' or 'datalayout' after 'target'")),
}
}
fn parse_source_filename(&mut self) -> ParseResult<()> {
self.expect_keyword(Keyword::SourceFilename, "'source_filename'")?;
self.expect_punct(PunctKind::Equal, "'=' after source_filename")?;
let source_filename = self.parse_string_constant("source-filename string constant")?;
self.module.set_source_filename(source_filename);
Ok(())
}
fn parse_comdat_definition(&mut self) -> ParseResult<()> {
let name = match self.peek() {
Token::ComdatVar(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 comdat name"))?
.to_owned(),
_ => return Err(self.expected("comdat variable")),
};
self.bump()?;
self.expect_punct(PunctKind::Equal, "'=' after comdat name")?;
self.expect_keyword(Keyword::Comdat, "'comdat'")?;
let kind = if self.eat_keyword(Keyword::Any)? {
SelectionKind::Any
} else if self.eat_keyword(Keyword::Exactmatch)? {
SelectionKind::ExactMatch
} else if self.eat_keyword(Keyword::Largest)? {
SelectionKind::Largest
} else if self.eat_keyword(Keyword::Nodeduplicate)? {
SelectionKind::NoDeduplicate
} else if self.eat_keyword(Keyword::Samesize)? {
SelectionKind::SameSize
} else {
return Err(self.expected("comdat selection kind"));
};
let comdat = self.module.get_or_insert_comdat(name);
comdat.set_selection_kind(kind);
Ok(())
}
fn parse_use_list_order_indexes(&mut self) -> ParseResult<Box<[u32]>> {
self.expect_punct(PunctKind::LBrace, "'{' before uselistorder indexes")?;
let mut indexes = Vec::new();
if !matches!(self.peek(), Token::RBrace) {
loop {
indexes.push(self.parse_uint32("uselistorder index")?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RBrace, "'}' after uselistorder indexes")?;
Ok(indexes.into_boxed_slice())
}
fn parse_use_list_order_directive(
&mut self,
pfs: Option<&PerFunctionState<'ctx>>,
) -> ParseResult<UseListOrderRecord> {
self.expect_keyword(Keyword::Uselistorder, "'uselistorder'")?;
let ty = self.parse_type(false)?;
let val_id = self.parse_val_id(pfs, Some(ty))?;
let value = self.convert_val_id_to_value(ty, val_id, pfs)?;
self.expect_punct(PunctKind::Comma, "',' before uselistorder indexes")?;
let indexes = self.parse_use_list_order_indexes()?;
Ok(UseListOrderRecord {
value: value.id(),
value_ty: ty.id(),
indexes,
})
}
fn parse_module_use_list_order(&mut self) -> ParseResult<()> {
let loc = self.loc();
let record = self.parse_use_list_order_directive(None)?;
self.module
.append_use_list_order(record)
.map_err(|e| match e {
IrError::InvalidOperation { message } => ParseError::Expected {
expected: message.into(),
loc: DiagLoc::span(loc),
},
other => self.builder_err("uselistorder", other),
})
}
fn parse_function_use_list_order(&mut self, state: &PerFunctionState<'ctx>) -> ParseResult<()> {
let loc = self.loc();
let record = self.parse_use_list_order_directive(Some(state))?;
state
.func
.append_use_list_order(record)
.map_err(|e| match e {
IrError::InvalidOperation { message } => ParseError::Expected {
expected: message.into(),
loc: DiagLoc::span(loc),
},
other => self.builder_err("uselistorder", other),
})
}
fn parse_use_list_order_bb(&mut self) -> ParseResult<()> {
let loc = self.loc();
self.expect_keyword(Keyword::UselistorderBb, "'uselistorder_bb'")?;
let function = match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("function name in uselistorder_bb"))?;
self.bump()?;
self.module
.function_by_name(&name)
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Named(name),
loc: DiagLoc::span(loc),
})?
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
self.resolve_global_id_as_function(id)?
}
_ => return Err(self.expected("function name in uselistorder_bb")),
};
self.expect_punct(PunctKind::Comma, "',' after uselistorder_bb function")?;
let block = match self.peek() {
Token::LocalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("basic block in uselistorder_bb"))?;
self.bump()?;
function
.basic_blocks()
.find(|bb| bb.name().as_deref() == Some(name.as_str()))
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Block,
id: crate::parse_error::SymbolId::Named(name),
loc: DiagLoc::span(loc),
})?
}
Token::LocalVarId(id) => {
let id = *id;
self.bump()?;
let mut next = 0u32;
for arg in function.params() {
if arg.name().is_none() {
next = next.saturating_add(1);
}
}
let mut found = None;
'blocks: for bb in function.basic_blocks() {
if bb.name().is_none() {
if next == id {
found = Some(bb);
break 'blocks;
}
next = next.saturating_add(1);
}
for inst in bb.instructions() {
if !inst.ty().is_void() && inst.name().is_none() {
next = next.saturating_add(1);
}
}
}
found.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Block,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(loc),
})?
}
_ => return Err(self.expected("basic block in uselistorder_bb")),
};
self.expect_punct(PunctKind::Comma, "',' before uselistorder_bb indexes")?;
let indexes = self.parse_use_list_order_indexes()?;
let record = UseListOrderBBRecord {
function: function.as_value().id(),
block: block.as_value().id(),
indexes,
};
self.module
.append_use_list_order_bb(record)
.map_err(|e| match e {
IrError::InvalidOperation { message } => ParseError::Expected {
expected: message.into(),
loc: DiagLoc::span(loc),
},
other => self.builder_err("uselistorder_bb", other),
})
}
fn parse_module_asm(&mut self) -> ParseResult<()> {
self.expect_keyword(Keyword::Module, "'module'")?;
self.expect_keyword(Keyword::Asm, "'asm' after 'module'")?;
let asm = self.parse_string_constant("module-asm string constant")?;
self.module.append_module_asm(asm);
Ok(())
}
fn parse_standalone_metadata(&mut self) -> ParseResult<()> {
let loc = self.bump()?; let slot = self.parse_uint32("metadata slot number after '!'")?;
self.expect_punct(PunctKind::Equal, "'=' after metadata id")?;
let distinct = self.eat_keyword(Keyword::Distinct)?;
match self.peek() {
Token::Exclaim => {
self.bump()?;
match self.peek() {
Token::LBrace => {
let content = self.parse_md_node_after_bang(distinct)?;
self.define_md_slot(slot, content, loc)?;
Ok(())
}
_ => Err(self.expected("metadata string or tuple")),
}
}
Token::MetadataVar(_) => {
let content = self.parse_md_node_after_bang(distinct)?;
self.define_md_slot(slot, content, loc)?;
Ok(())
}
_ => Err(self.expected("metadata string or tuple")),
}
}
fn parse_named_metadata(&mut self) -> ParseResult<()> {
use llvmkit_ir::metadata::MetadataRef;
let name = match self.peek() {
Token::MetadataVar(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 metadata name"))?
.to_owned(),
_ => return Err(self.expected("metadata name")),
};
self.bump()?;
self.expect_punct(PunctKind::Equal, "'=' after metadata name")?;
self.expect_exclaim("'!' before '{' in named metadata")?;
self.expect_punct(PunctKind::LBrace, "'{' in named metadata")?;
let nmd_idx = self.module.get_or_insert_named_metadata(&name);
if !matches!(self.peek(), Token::RBrace) {
loop {
self.expect_exclaim("'!' before metadata operand")?;
let loc = self.loc();
let slot = self.parse_uint32("metadata operand number")?;
let id = self.resolve_md_slot(slot, loc);
self.module
.named_metadata_add_operand(nmd_idx, MetadataRef(id));
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RBrace, "'}' closing named metadata")?;
Ok(())
}
fn parse_metadata_value_operand(&mut self) -> ParseResult<llvmkit_ir::Value<'ctx>> {
if matches!(self.peek(), Token::MetadataVar(_)) {
let kind = self.parse_md_node_after_bang(false)?;
let id = self.module.metadata_node(kind);
return Ok(self.module.metadata_as_value(id));
}
self.expect_exclaim("'!' in metadata operand")?;
let id = match self.peek() {
Token::StringConstant(_) => {
let s = self.parse_string_constant("metadata string")?;
self.module.metadata_string(s)
}
Token::LBrace => {
self.bump()?;
let mut operands = Vec::new();
if !matches!(self.peek(), Token::RBrace) {
loop {
operands.push(self.parse_md_tuple_operand()?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RBrace, "'}' closing metadata tuple")?;
self.module.metadata_tuple(operands)
}
Token::SpecializedMetadata(_) | Token::MetadataVar(_) => {
let kind = self.parse_md_node_after_bang(false)?;
self.module.metadata_node(kind)
}
_ => {
let loc = self.loc();
let slot = self.parse_uint32("metadata slot number after '!'")?;
self.resolve_md_slot(slot, loc)
}
};
Ok(self.module.metadata_as_value(id))
}
fn parse_metadata_attachment_operand(
&mut self,
) -> ParseResult<llvmkit_ir::metadata::MetadataId> {
match self.peek() {
Token::MetadataVar(_) => {
let kind = self.parse_md_node_after_bang(false)?;
Ok(self.module.metadata_node(kind))
}
Token::Exclaim => {
self.bump()?;
match self.peek() {
Token::LBrace | Token::SpecializedMetadata(_) | Token::MetadataVar(_) => {
let kind = self.parse_md_node_after_bang(false)?;
Ok(self.module.metadata_node(kind))
}
_ => {
let loc = self.loc();
let slot = self.parse_uint32("metadata attachment operand number")?;
Ok(self.resolve_md_slot(slot, loc))
}
}
}
_ => Err(self.expected("metadata attachment operand")),
}
}
fn parse_named_metadata_attachment(
&mut self,
) -> ParseResult<(
llvmkit_ir::metadata::MetadataAttachmentKind,
llvmkit_ir::metadata::MetadataId,
)> {
let name = match self.peek() {
Token::MetadataVar(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 metadata attachment name"))?
.to_owned(),
_ => return Err(self.expected("metadata attachment")),
};
self.bump()?;
let id = self.parse_metadata_attachment_operand()?;
Ok((
llvmkit_ir::metadata::MetadataAttachmentKind::from_name(&name),
id,
))
}
fn parse_md_tuple_operand(&mut self) -> ParseResult<llvmkit_ir::metadata::MetadataRef> {
use llvmkit_ir::metadata::MetadataRef;
if matches!(self.peek(), Token::Kw(Keyword::Null)) {
self.bump()?;
let id = self
.module
.metadata_node(llvmkit_ir::metadata::MetadataKind::Null);
return Ok(MetadataRef(id));
}
if matches!(self.peek(), Token::MetadataVar(_)) {
let content = self.parse_md_node_after_bang(false)?;
let id = self.module.metadata_node(content);
return Ok(MetadataRef(id));
}
self.expect_exclaim("'!' in metadata tuple operand")?;
match self.peek() {
Token::StringConstant(_) => {
let s = self.parse_string_constant("metadata string operand")?;
Ok(MetadataRef(self.module.metadata_string(s)))
}
Token::LBrace | Token::SpecializedMetadata(_) | Token::MetadataVar(_) => {
let content = self.parse_md_node_after_bang(false)?;
let id = self.module.metadata_node(content);
Ok(MetadataRef(id))
}
_ => {
let loc = self.loc();
let slot = self.parse_uint32("metadata operand number")?;
Ok(MetadataRef(self.resolve_md_slot(slot, loc)))
}
}
}
fn parse_md_node_after_bang(
&mut self,
distinct: bool,
) -> ParseResult<llvmkit_ir::metadata::MetadataKind> {
match self.peek() {
Token::LBrace => {
self.bump()?;
let mut operands = Vec::new();
if !matches!(self.peek(), Token::RBrace) {
loop {
operands.push(self.parse_md_tuple_operand()?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RBrace, "'}' closing metadata tuple")?;
Ok(llvmkit_ir::metadata::MetadataKind::Tuple { distinct, operands })
}
Token::SpecializedMetadata(name) => {
let kind = llvmkit_ir::metadata::SpecializedMetadataKind::from_name(name)
.ok_or_else(|| self.expected("specialized metadata kind"))?;
self.bump()?;
self.expect_punct(PunctKind::LParen, "'(' in specialized metadata")?;
let mut fields = Vec::new();
if !matches!(self.peek(), Token::RParen) {
loop {
let field_name = match self.peek() {
Token::LabelStr(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 metadata field name"))?
.to_owned(),
_ => return Err(self.expected("metadata field name")),
};
self.bump()?;
let value = self.parse_metadata_field_value()?;
fields.push(llvmkit_ir::metadata::MetadataField {
name: field_name,
value,
});
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' closing specialized metadata")?;
Ok(llvmkit_ir::metadata::MetadataKind::Specialized(
llvmkit_ir::metadata::SpecializedMetadataNode {
distinct,
kind,
fields,
},
))
}
Token::MetadataVar(bytes) => {
let name = std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("specialized metadata kind"))?;
let kind = llvmkit_ir::metadata::SpecializedMetadataKind::from_name(name)
.ok_or_else(|| self.expected("specialized metadata kind"))?;
self.bump()?;
self.expect_punct(PunctKind::LParen, "'(' in specialized metadata")?;
let mut fields = Vec::new();
if !matches!(self.peek(), Token::RParen) {
loop {
let field_name = match self.peek() {
Token::LabelStr(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 metadata field name"))?
.to_owned(),
_ => return Err(self.expected("metadata field name")),
};
self.bump()?;
let value = self.parse_metadata_field_value()?;
fields.push(llvmkit_ir::metadata::MetadataField {
name: field_name,
value,
});
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' closing specialized metadata")?;
Ok(llvmkit_ir::metadata::MetadataKind::Specialized(
llvmkit_ir::metadata::SpecializedMetadataNode {
distinct,
kind,
fields,
},
))
}
Token::StringConstant(_) => {
let s = self.parse_string_constant("metadata string")?;
Ok(llvmkit_ir::metadata::MetadataKind::String(s))
}
_ => Err(self.expected("metadata node")),
}
}
fn parse_metadata_field_value(
&mut self,
) -> ParseResult<llvmkit_ir::metadata::MetadataFieldValue> {
use llvmkit_ir::metadata::{MetadataFieldValue, MetadataRef};
match self.peek() {
Token::Kw(Keyword::Null) => {
self.bump()?;
Ok(MetadataFieldValue::Null)
}
Token::Kw(Keyword::True) => {
self.bump()?;
Ok(MetadataFieldValue::Bool(true))
}
Token::Kw(Keyword::False) => {
self.bump()?;
Ok(MetadataFieldValue::Bool(false))
}
Token::StringConstant(_) => Ok(MetadataFieldValue::String(
self.parse_string_constant("metadata field string")?,
)),
Token::IntegerLit(_) => {
let (neg, raw) = self.parse_int_literal()?;
let value = if neg { -(raw as i128) } else { raw as i128 };
Ok(MetadataFieldValue::Integer(value))
}
Token::MetadataVar(_) => {
let content = self.parse_md_node_after_bang(false)?;
Ok(MetadataFieldValue::Metadata(MetadataRef(
self.module.metadata_node(content),
)))
}
Token::Exclaim => {
self.bump()?;
match self.peek() {
Token::LBrace => {
self.bump()?;
let mut items = Vec::new();
if !matches!(self.peek(), Token::RBrace) {
loop {
items.push(self.parse_md_tuple_operand()?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RBrace, "'}' closing metadata list")?;
Ok(MetadataFieldValue::MetadataList(items))
}
Token::StringConstant(_) => {
let s = self.parse_string_constant("metadata string")?;
Ok(MetadataFieldValue::Metadata(MetadataRef(
self.module.metadata_string(s),
)))
}
Token::SpecializedMetadata(_) | Token::MetadataVar(_) => {
let content = self.parse_md_node_after_bang(false)?;
Ok(MetadataFieldValue::Metadata(MetadataRef(
self.module.metadata_node(content),
)))
}
_ => {
let loc = self.loc();
let slot = self.parse_uint32("metadata field metadata reference")?;
Ok(MetadataFieldValue::Metadata(MetadataRef(
self.resolve_md_slot(slot, loc),
)))
}
}
}
Token::DwarfTag(s)
| Token::DwarfAttEncoding(s)
| Token::DwarfVirtuality(s)
| Token::DwarfLang(s)
| Token::DwarfSourceLangName(s)
| Token::DwarfCC(s)
| Token::DwarfOp(s)
| Token::DwarfMacinfo(s)
| Token::DwarfEnumKind(s)
| Token::DiFlag(s)
| Token::DiSpFlag(s)
| Token::ChecksumKind(s)
| Token::EmissionKind(s)
| Token::NameTableKind(s)
| Token::FixedPointKind(s) => {
let value = (*s).to_owned();
self.bump()?;
Ok(MetadataFieldValue::Enum(value))
}
_ => Err(self.expected("metadata field value")),
}
}
fn expect_exclaim(&mut self, expected: &str) -> ParseResult<Span> {
if matches!(self.peek(), Token::Exclaim) {
self.bump()
} else {
Err(self.expected(expected))
}
}
fn parse_debug_metadata_operand(
&mut self,
state: &PerFunctionState<'ctx>,
) -> ParseResult<llvmkit_ir::metadata::DebugMetadataOperand> {
if matches!(
self.peek(),
Token::Exclaim | Token::SpecializedMetadata(_) | Token::MetadataVar(_)
) {
let id = self.parse_metadata_attachment_operand()?;
return Ok(llvmkit_ir::metadata::DebugMetadataOperand::Metadata(
llvmkit_ir::metadata::MetadataRef(id),
));
}
let ty = self.parse_type(false)?;
let value = self.parse_value(state, ty)?;
Ok(llvmkit_ir::metadata::DebugMetadataOperand::Value(
value.id(),
))
}
fn parse_debug_record(
&mut self,
state: &PerFunctionState<'ctx>,
) -> ParseResult<llvmkit_ir::metadata::DebugRecord> {
use llvmkit_ir::metadata::{DebugRecord, DebugVariableRecord, DebugVariableRecordKind};
let record_type = match self.peek() {
Token::DbgRecordType(name) => *name,
_ => return Err(self.expected("debug record type")),
};
self.bump()?;
self.expect_punct(PunctKind::LParen, "'(' in debug record")?;
if record_type == "label" {
let label = self.parse_metadata_attachment_operand()?;
self.expect_punct(PunctKind::Comma, "',' after debug label")?;
let debug_loc = self.parse_metadata_attachment_operand()?;
self.expect_punct(PunctKind::RParen, "')' closing debug record")?;
return Ok(DebugRecord::Label { label, debug_loc });
}
let kind = match record_type {
"declare" => DebugVariableRecordKind::Declare,
"value" => DebugVariableRecordKind::Value,
"assign" => DebugVariableRecordKind::Assign,
"declare_value" => DebugVariableRecordKind::DeclareValue,
_ => return Err(self.expected("known debug record type")),
};
let location = self.parse_debug_metadata_operand(state)?;
self.expect_punct(PunctKind::Comma, "',' after debug location operand")?;
let variable = self.parse_metadata_attachment_operand()?;
self.expect_punct(PunctKind::Comma, "',' after debug variable")?;
let expression = self.parse_metadata_attachment_operand()?;
self.expect_punct(PunctKind::Comma, "',' after debug expression")?;
let (assign_id, address_location, address_expression) =
if kind == DebugVariableRecordKind::Assign {
let assign_id = self.parse_metadata_attachment_operand()?;
self.expect_punct(PunctKind::Comma, "',' after DIAssignID")?;
let address_location = self.parse_debug_metadata_operand(state)?;
self.expect_punct(PunctKind::Comma, "',' after debug address location")?;
let address_expression = self.parse_metadata_attachment_operand()?;
self.expect_punct(PunctKind::Comma, "',' after debug address expression")?;
(
Some(assign_id),
Some(address_location),
Some(address_expression),
)
} else {
(None, None, None)
};
let debug_loc = self.parse_metadata_attachment_operand()?;
self.expect_punct(PunctKind::RParen, "')' closing debug record")?;
Ok(DebugRecord::Variable(DebugVariableRecord {
kind,
location,
variable,
expression,
assign_id,
address_location,
address_expression,
debug_loc,
}))
}
fn finish_trailing_metadata(
&mut self,
bb: llvmkit_ir::BasicBlock<'ctx, llvmkit_ir::Dyn, llvmkit_ir::Unsealed>,
pending_debug_records: &mut Vec<llvmkit_ir::metadata::DebugRecord>,
) -> ParseResult<()> {
self.skip_trailing_metadata(bb)?;
if !pending_debug_records.is_empty() {
let inst = bb
.instructions()
.last()
.ok_or_else(|| ParseError::Expected {
expected: "instruction after debug record".into(),
loc: DiagLoc::span(self.loc()),
})?;
for record in pending_debug_records.drain(..) {
inst.push_debug_record(record);
}
}
Ok(())
}
fn skip_trailing_metadata(
&mut self,
bb: llvmkit_ir::BasicBlock<'ctx, llvmkit_ir::Dyn, llvmkit_ir::Unsealed>,
) -> ParseResult<()> {
if matches!(self.peek(), Token::MetadataVar(_)) {
return Err(self.expected("',' before trailing metadata"));
}
while matches!(self.peek(), Token::Comma) {
self.bump()?;
let name = match self.peek() {
Token::MetadataVar(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 metadata attachment name"))?
.to_owned(),
_ => return Err(self.expected("metadata attachment")),
};
self.bump()?;
let id = self.parse_metadata_attachment_operand()?;
if let Some(inst) = bb.instructions().last() {
inst.set_metadata(
llvmkit_ir::metadata::MetadataAttachmentKind::from_name(&name),
id,
);
}
}
Ok(())
}
fn parse_named_type_definition(&mut self) -> ParseResult<()> {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("named type identifier"))?;
let name_loc = self.loc();
self.bump()?; self.expect_punct(PunctKind::Equal, "'=' after type name")?;
self.expect_keyword(Keyword::Type, "'type' after '='")?;
self.parse_struct_definition(Some(name), None, name_loc)
}
fn parse_unnamed_type_definition(&mut self) -> ParseResult<()> {
let id = match self.peek() {
Token::LocalVarId(n) => *n,
_ => return Err(self.expected("numbered type identifier")),
};
let loc = self.loc();
self.bump()?;
self.expect_punct(PunctKind::Equal, "'=' after type id")?;
self.expect_keyword(Keyword::Type, "'type' after '='")?;
self.parse_struct_definition(None, Some(id), loc)
}
fn parse_struct_definition(
&mut self,
name: Option<String>,
slot: Option<u32>,
decl_loc: Span,
) -> ParseResult<()> {
let handle: StructType<'ctx> = match (&name, slot) {
(Some(n), None) => self.module.named_struct(n),
(None, Some(id)) => {
if id != self.next_unnamed_type_id {
return Err(ParseError::Expected {
expected: format!(
"monotonic numbered type id %{}",
self.next_unnamed_type_id
),
loc: DiagLoc::span(decl_loc),
});
}
self.next_unnamed_type_id = self.next_unnamed_type_id.saturating_add(1);
self.module
.struct_type(core::iter::empty::<Type<'ctx>>(), false)
}
_ => unreachable!("parse_struct_definition called without a name xor slot"),
};
match self.peek() {
Token::Kw(Keyword::Opaque) => {
self.bump()?;
}
Token::LBrace | Token::Less => {
let (elements, packed) = self.parse_struct_body()?;
if name.is_some() {
self.module
.set_struct_body(handle, elements, packed)
.map_err(|e| ParseError::Expected {
expected: format!("valid struct body: {e}"),
loc: DiagLoc::span(decl_loc),
})?;
} else {
let lit = self.module.struct_type(elements, packed);
self.numbered_types.insert(
match slot {
Some(slot) => slot,
None => {
return Err(ParseError::Expected {
expected: "numbered type slot for anonymous literal body"
.into(),
loc: DiagLoc::span(decl_loc),
});
}
},
TypeEntry { ty: lit.as_type() },
);
return Ok(());
}
}
_ => return Err(self.expected("'opaque' or '{' after 'type'")),
}
if let Some(n) = name {
self.named_types.insert(
n,
TypeEntry {
ty: handle.as_type(),
},
);
}
Ok(())
}
fn parse_struct_body(&mut self) -> ParseResult<(Vec<Type<'ctx>>, bool)> {
let packed;
if self.eat_punct(PunctKind::Less)? {
packed = true;
self.expect_punct(PunctKind::LBrace, "'{' after '<' in packed struct")?;
} else {
packed = false;
self.expect_punct(PunctKind::LBrace, "'{' to start struct body")?;
}
let mut elems = Vec::new();
if !matches!(self.peek(), Token::RBrace) {
loop {
elems.push(self.parse_type(false)?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RBrace, "'}' to close struct body")?;
if packed {
self.expect_punct(PunctKind::Greater, "'>' to close packed struct")?;
}
Ok((elems, packed))
}
pub fn parse_type(&mut self, allow_void: bool) -> ParseResult<Type<'ctx>> {
let type_loc = self.loc();
let mut result: Type<'ctx> = match *self.peek() {
Token::PrimitiveType(p) => {
let ty = self.primitive_to_type(p, type_loc)?;
self.bump()?;
if matches!(p, PrimitiveTy::Ptr) {
let addr_space = if let Token::Kw(Keyword::Addrspace) = self.peek() {
self.bump()?;
self.parse_addr_space_paren()?
} else {
0
};
let ptr_ty: PointerType<'ctx> = self.module.ptr_type(addr_space);
if matches!(self.peek(), Token::Star) {
return Err(self.expected("ptr (no '*' suffix; use 'ptr')"));
}
ptr_ty.as_type()
} else {
ty
}
}
Token::LBrace => {
let (elems, packed) = self.parse_struct_body()?;
self.module.struct_type(elems, packed).as_type()
}
Token::Less => {
self.bump()?; if matches!(self.peek(), Token::LBrace) {
let (elems, _was_packed_redundant) = self.parse_struct_body_braces()?;
self.expect_punct(PunctKind::Greater, "'>' at end of packed struct")?;
self.module.struct_type(elems, true).as_type()
} else {
self.parse_array_or_vector_after_open(true)?
}
}
Token::LSquare => {
self.bump()?; self.parse_array_or_vector_after_open(false)?
}
Token::LocalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("local identifier payload"))?;
let loc = self.loc();
self.bump()?;
self.lookup_or_forward_named_type(&name, loc)
}
Token::LocalVarId(n) => {
let id = n;
let loc = self.loc();
self.bump()?;
self.lookup_or_forward_numbered_type(id, loc)
}
_ => {
return Err(ParseError::Expected {
expected: "type".into(),
loc: DiagLoc::span(type_loc),
});
}
};
loop {
match self.peek() {
Token::Kw(Keyword::Addrspace) => {
return Err(self.expected(
"ptr addrspace(N) (legacy 'T addrspace(N) *' form is unsupported)",
));
}
Token::Star => {
return Err(self.expected("opaque-pointer 'ptr' (typed 'T*' is unsupported)"));
}
Token::LParen => {
result = self.parse_function_type_after_return(result)?;
}
_ => {
if !allow_void && matches!(result.into_type_enum(), AnyTypeEnum::Void(_)) {
return Err(ParseError::Expected {
expected: "non-void type (void only allowed at function results)"
.into(),
loc: DiagLoc::span(type_loc),
});
}
return Ok(result);
}
}
}
}
fn parse_array_or_vector_after_open(&mut self, is_vector: bool) -> ParseResult<Type<'ctx>> {
let scalable = if is_vector && matches!(self.peek(), Token::Kw(Keyword::Vscale)) {
self.bump()?;
self.expect_keyword(Keyword::X, "'x' after 'vscale'")?;
true
} else {
false
};
let n = if is_vector {
u64::from(self.parse_uint32("vector element count")?)
} else {
self.parse_uint64("array element count")?
};
self.expect_keyword(Keyword::X, "'x' between count and element type")?;
let elem = self.parse_type(false)?;
if is_vector {
self.expect_punct(PunctKind::Greater, "'>' at end of vector type")?;
let n32 = u32::try_from(n).map_err(|_| ParseError::Expected {
expected: "vector element count fits in u32".into(),
loc: DiagLoc::span(self.loc()),
})?;
let v = self.module.vector_type(elem, n32, scalable);
Ok(v.as_type())
} else {
self.expect_punct(PunctKind::RSquare, "']' at end of array type")?;
let arr = self.module.array_type(elem, n);
Ok(arr.as_type())
}
}
fn parse_struct_body_braces(&mut self) -> ParseResult<(Vec<Type<'ctx>>, bool)> {
self.expect_punct(PunctKind::LBrace, "'{' after '<' in packed struct")?;
let mut elems = Vec::new();
if !matches!(self.peek(), Token::RBrace) {
loop {
elems.push(self.parse_type(false)?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RBrace, "'}' in packed struct body")?;
Ok((elems, true))
}
fn parse_function_type_after_return(&mut self, ret: Type<'ctx>) -> ParseResult<Type<'ctx>> {
self.expect_punct(PunctKind::LParen, "'(' in function type")?;
let mut params = Vec::new();
let mut var_args = false;
if !matches!(self.peek(), Token::RParen) {
loop {
if matches!(self.peek(), Token::DotDotDot) {
self.bump()?;
var_args = true;
break;
}
params.push(self.parse_type(false)?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' to close function type")?;
let fn_ty = self.module.fn_type(ret, params, var_args);
Ok(fn_ty.as_type())
}
fn primitive_to_type(&self, p: PrimitiveTy, loc: Span) -> ParseResult<Type<'ctx>> {
let m = self.module;
match p {
PrimitiveTy::Void => Ok(m.void_type().as_type()),
PrimitiveTy::Label => Ok(m.label_type().as_type()),
PrimitiveTy::Metadata => Ok(m.metadata_type().as_type()),
PrimitiveTy::Token => Ok(m.token_type().as_type()),
PrimitiveTy::X86Amx => Ok(m.x86_amx_type()),
PrimitiveTy::Half => Ok(m.half_type().as_type()),
PrimitiveTy::BFloat => Ok(m.bfloat_type().as_type()),
PrimitiveTy::Float => Ok(m.f32_type().as_type()),
PrimitiveTy::Double => Ok(m.f64_type().as_type()),
PrimitiveTy::X86Fp80 => Ok(m.x86_fp80_type().as_type()),
PrimitiveTy::Fp128 => Ok(m.fp128_type().as_type()),
PrimitiveTy::PpcFp128 => Ok(m.ppc_fp128_type().as_type()),
PrimitiveTy::Ptr => Ok(m.ptr_type(0).as_type()),
PrimitiveTy::Integer(n) => m
.custom_width_int_type(n.get())
.map(|t| t.as_type())
.map_err(|_| ParseError::IntegerWidthOutOfRange {
width: u64::from(n.get()),
max: (1u32 << 24) - 1,
loc: DiagLoc::span(loc),
}),
}
}
fn lookup_or_forward_named_type(&mut self, name: &str, _loc: Span) -> Type<'ctx> {
if let Some(entry) = self.named_types.get(name) {
return entry.ty;
}
let st = self.module.named_struct(name);
self.named_types
.insert(name.to_owned(), TypeEntry { ty: st.as_type() });
st.as_type()
}
fn lookup_or_forward_numbered_type(&mut self, id: u32, _loc: Span) -> Type<'ctx> {
if let Some(entry) = self.numbered_types.get(&id) {
return entry.ty;
}
let st = self
.module
.struct_type(core::iter::empty::<Type<'ctx>>(), false);
self.numbered_types
.insert(id, TypeEntry { ty: st.as_type() });
st.as_type()
}
fn parse_global_or_function(&mut self) -> ParseResult<()> {
let (name_id, decl_loc) = match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("global identifier"))?;
let loc = self.loc();
self.bump()?;
(NameOrId::Name(name), loc)
}
Token::GlobalId(n) => {
let id = *n;
let loc = self.loc();
self.bump()?;
(NameOrId::Id(id), loc)
}
_ => return Err(self.expected("global identifier")),
};
self.expect_punct(PunctKind::Equal, "'=' after global name")?;
let linkage = if self.eat_keyword(Keyword::External)? {
Linkage::External
} else if self.eat_keyword(Keyword::AvailableExternally)? {
Linkage::AvailableExternally
} else if self.eat_keyword(Keyword::Linkonce)? {
Linkage::LinkOnceAny
} else if self.eat_keyword(Keyword::LinkonceOdr)? {
Linkage::LinkOnceODR
} else if self.eat_keyword(Keyword::Weak)? {
Linkage::WeakAny
} else if self.eat_keyword(Keyword::WeakOdr)? {
Linkage::WeakODR
} else if self.eat_keyword(Keyword::Appending)? {
Linkage::Appending
} else if self.eat_keyword(Keyword::Internal)? {
Linkage::Internal
} else if self.eat_keyword(Keyword::Private)? {
Linkage::Private
} else if self.eat_keyword(Keyword::ExternWeak)? {
Linkage::ExternalWeak
} else if self.eat_keyword(Keyword::Common)? {
Linkage::Common
} else {
Linkage::External
};
let visibility = if self.eat_keyword(Keyword::Default)? {
Visibility::Default
} else if self.eat_keyword(Keyword::Hidden)? {
Visibility::Hidden
} else if self.eat_keyword(Keyword::Protected)? {
Visibility::Protected
} else {
Visibility::Default
};
let dll_storage_class = if self.eat_keyword(Keyword::Dllimport)? {
DllStorageClass::DllImport
} else if self.eat_keyword(Keyword::Dllexport)? {
DllStorageClass::DllExport
} else {
DllStorageClass::Default
};
let thread_local_mode = if self.eat_keyword(Keyword::ThreadLocal)? {
if self.eat_punct(PunctKind::LParen)? {
let mode = if self.eat_keyword(Keyword::Localdynamic)? {
ThreadLocalMode::LocalDynamic
} else if self.eat_keyword(Keyword::Initialexec)? {
ThreadLocalMode::InitialExec
} else if self.eat_keyword(Keyword::Localexec)? {
ThreadLocalMode::LocalExec
} else {
return Err(self.expected("thread-local model"));
};
self.expect_punct(PunctKind::RParen, "')' after thread-local model")?;
mode
} else {
ThreadLocalMode::GeneralDynamic
}
} else {
ThreadLocalMode::NotThreadLocal
};
let unnamed_addr = if self.eat_keyword(Keyword::UnnamedAddr)? {
UnnamedAddr::Global
} else if self.eat_keyword(Keyword::LocalUnnamedAddr)? {
UnnamedAddr::Local
} else {
UnnamedAddr::None
};
if matches!(
self.peek(),
Token::Kw(Keyword::Alias) | Token::Kw(Keyword::Ifunc)
) {
return self.parse_alias_or_ifunc(
name_id,
decl_loc,
ParsedAliasHeader {
linkage,
visibility,
dll_storage_class,
thread_local_mode,
unnamed_addr,
},
);
}
let address_space = if self.eat_keyword(Keyword::Addrspace)? {
self.parse_addr_space_paren()?
} else {
0
};
let externally_initialized = self.eat_keyword(Keyword::ExternallyInitialized)?;
let is_constant = if self.eat_keyword(Keyword::Global)? {
false
} else if self.eat_keyword(Keyword::Constant)? {
true
} else {
return Err(self.expected("'global' or 'constant' after linkage"));
};
let ty = self.parse_type(false)?;
let (initializer, deferred_initializer) = self.parse_global_initializer(ty)?;
let mut section = None;
let mut partition = None;
let mut align = MaybeAlign::NONE;
let mut comdat_name = None;
let mut metadata = Vec::new();
while self.eat_punct(PunctKind::Comma)? {
if self.eat_keyword(Keyword::Section)? {
section = Some(self.parse_string_constant("section name")?);
} else if self.eat_keyword(Keyword::Partition)? {
partition = Some(self.parse_string_constant("partition name")?);
} else if matches!(self.peek(), Token::Kw(Keyword::Align)) {
align = MaybeAlign::new(self.parse_align_val()?);
} else if self.eat_keyword(Keyword::Comdat)? {
let name = if self.eat_punct(PunctKind::LParen)? {
let name = match self.peek() {
Token::ComdatVar(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 comdat name"))?
.to_owned(),
_ => return Err(self.expected("comdat variable")),
};
self.bump()?;
self.expect_punct(PunctKind::RParen, "')' after comdat")?;
name
} else {
return Err(self.expected("explicit comdat($name)"));
};
comdat_name = Some(name);
} else if matches!(self.peek(), Token::MetadataVar(_)) {
metadata.push(self.parse_named_metadata_attachment()?);
} else {
return Err(self.expected("global attribute"));
}
}
let name_string = match &name_id {
NameOrId::Name(n) => n.clone(),
NameOrId::Id(id) => format!("{id}"),
};
let mut builder = self
.module
.global_builder(&name_string, ty)
.linkage(linkage)
.visibility(visibility)
.dll_storage_class(dll_storage_class)
.thread_local_mode(thread_local_mode)
.unnamed_addr(unnamed_addr)
.address_space(address_space)
.externally_initialized(externally_initialized)
.align(align)
.constant(is_constant);
if let Some(c) = initializer {
builder = builder.initializer(c);
}
if let Some(s) = section {
builder = builder.section(s);
}
if let Some(p) = partition {
builder = builder.partition(p);
}
if let Some(name) = comdat_name {
let comdat = self.module.get_or_insert_comdat(name);
builder = builder.comdat(comdat);
}
let g = builder.build().map_err(|e| ParseError::Expected {
expected: format!("valid global definition: {e}"),
loc: DiagLoc::span(decl_loc),
})?;
for (kind, id) in metadata {
g.set_metadata(kind, id);
}
if let Some(value) = deferred_initializer {
self.deferred_global_initializers
.push(DeferredGlobalInitializer { global: g, value });
}
if let NameOrId::Id(id) = name_id {
self.numbered_globals
.add(id, GlobalRef::Variable(g))
.map_err(|source| ParseError::InvalidSlotId {
source,
loc: DiagLoc::span(decl_loc),
})?;
}
Ok(())
}
fn parse_alias_or_ifunc(
&mut self,
name_id: NameOrId,
decl_loc: Span,
header: ParsedAliasHeader,
) -> ParseResult<()> {
let linkage = header.linkage;
let visibility = header.visibility;
let dll_storage_class = header.dll_storage_class;
let thread_local_mode = header.thread_local_mode;
let unnamed_addr = header.unnamed_addr;
let is_alias = if self.eat_keyword(Keyword::Alias)? {
true
} else if self.eat_keyword(Keyword::Ifunc)? {
false
} else {
return Err(self.expected("'alias' or 'ifunc'"));
};
if is_alias && !llvmkit_ir::global_alias::is_valid_alias_linkage(linkage) {
return Err(ParseError::Expected {
expected: "invalid linkage type for alias".into(),
loc: DiagLoc::span(decl_loc),
});
}
if !is_alias && !llvmkit_ir::global_ifunc::is_valid_ifunc_linkage(linkage) {
return Err(ParseError::Expected {
expected: "invalid linkage type for ifunc".into(),
loc: DiagLoc::span(decl_loc),
});
}
if matches!(linkage, Linkage::Internal | Linkage::Private)
&& visibility != Visibility::Default
{
return Err(ParseError::Expected {
expected: "symbol with local linkage must have default visibility".into(),
loc: DiagLoc::span(decl_loc),
});
}
if matches!(linkage, Linkage::Internal | Linkage::Private)
&& dll_storage_class != DllStorageClass::Default
{
return Err(ParseError::Expected {
expected: "symbol with local linkage cannot have a DLL storage class".into(),
loc: DiagLoc::span(decl_loc),
});
}
let value_type = self.parse_type(false)?;
self.expect_punct(
PunctKind::Comma,
"expected comma after alias or ifunc's type",
)?;
let target_ty = self.parse_type(false)?;
match target_ty.into_type_enum() {
AnyTypeEnum::Pointer(_) => {}
_ => {
return Err(ParseError::Expected {
expected: "An alias or ifunc must have pointer type".into(),
loc: DiagLoc::span(self.loc()),
});
}
}
let target = self
.parse_constant(target_ty)?
.ok_or_else(|| self.expected("alias or ifunc target constant"))?;
let mut partition = None;
while self.eat_punct(PunctKind::Comma)? {
if self.eat_keyword(Keyword::Partition)? {
partition = Some(self.parse_string_constant("partition name")?);
} else {
return Err(self.expected("unknown alias or ifunc property"));
}
}
let name_string = match &name_id {
NameOrId::Name(n) => n.clone(),
NameOrId::Id(id) => format!("{id}"),
};
if is_alias {
let mut builder = self
.module
.alias_builder(&name_string, value_type, target)
.linkage(linkage)
.visibility(visibility)
.dll_storage_class(dll_storage_class)
.thread_local_mode(thread_local_mode)
.unnamed_addr(unnamed_addr);
if let Some(p) = partition {
builder = builder.partition(p);
}
let a = builder.build().map_err(|e| ParseError::Expected {
expected: format!("valid alias definition: {e}"),
loc: DiagLoc::span(decl_loc),
})?;
if let NameOrId::Id(id) = name_id {
self.numbered_globals
.add(id, GlobalRef::Alias(a))
.map_err(|source| ParseError::InvalidSlotId {
source,
loc: DiagLoc::span(decl_loc),
})?;
}
} else {
let mut builder = self
.module
.ifunc_builder(&name_string, value_type, target)
.linkage(linkage)
.visibility(visibility);
if let Some(p) = partition {
builder = builder.partition(p);
}
let i = builder.build().map_err(|e| ParseError::Expected {
expected: format!("valid ifunc definition: {e}"),
loc: DiagLoc::span(decl_loc),
})?;
if let NameOrId::Id(id) = name_id {
self.numbered_globals
.add(id, GlobalRef::IFunc(i))
.map_err(|source| ParseError::InvalidSlotId {
source,
loc: DiagLoc::span(decl_loc),
})?;
}
}
Ok(())
}
fn parse_global_initializer(
&mut self,
ty: Type<'ctx>,
) -> ParseResult<(
Option<llvmkit_ir::Constant<'ctx>>,
Option<DeferredConstantKind>,
)> {
match self.peek() {
Token::Kw(Keyword::Blockaddress) => {
let loc = self.loc();
let value = self.parse_deferred_blockaddress(loc)?;
let placeholder = self.placeholder_constant(ty)?;
Ok((Some(placeholder), Some(value)))
}
Token::Kw(Keyword::DsoLocalEquivalent) => {
let loc = self.loc();
let value = self.parse_deferred_dso_local_equivalent(loc)?;
let placeholder = self.placeholder_constant(ty)?;
Ok((Some(placeholder), Some(value)))
}
Token::Kw(Keyword::NoCfi) => {
let loc = self.loc();
let value = self.parse_deferred_no_cfi(loc)?;
let placeholder = self.placeholder_constant(ty)?;
Ok((Some(placeholder), Some(value)))
}
_ => self.parse_constant(ty).map(|c| (c, None)),
}
}
fn placeholder_constant(&self, ty: Type<'ctx>) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
match ty.into_type_enum() {
AnyTypeEnum::Pointer(ptr_ty) => Ok(ptr_ty.const_null().as_constant()),
_ => Err(self.expected("pointer type for deferred constant")),
}
}
fn parse_forward_global_ref(
&mut self,
expected: &'static str,
) -> ParseResult<ForwardGlobalRef> {
match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected(expected))?;
self.bump()?;
Ok(ForwardGlobalRef::Name(name))
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
Ok(ForwardGlobalRef::Id(id))
}
_ => Err(self.expected(expected)),
}
}
fn parse_deferred_blockaddress(&mut self, loc: Span) -> ParseResult<DeferredConstantKind> {
self.expect_keyword(Keyword::Blockaddress, "'blockaddress'")?;
self.expect_punct(PunctKind::LParen, "'(' in blockaddress")?;
let function = self.parse_forward_global_ref("function name in blockaddress")?;
self.expect_punct(PunctKind::Comma, "',' in blockaddress")?;
let block = match self.peek() {
Token::LocalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("basic block name in blockaddress"))?;
self.bump()?;
ForwardBlockRef::Name(name)
}
Token::LocalVarId(id) => {
let id = *id;
self.bump()?;
ForwardBlockRef::Id(id)
}
_ => return Err(self.expected("basic block name in blockaddress")),
};
self.expect_punct(PunctKind::RParen, "')' in blockaddress")?;
Ok(DeferredConstantKind::BlockAddress {
function,
block,
loc,
})
}
fn parse_deferred_dso_local_equivalent(
&mut self,
loc: Span,
) -> ParseResult<DeferredConstantKind> {
self.expect_keyword(Keyword::DsoLocalEquivalent, "'dso_local_equivalent'")?;
let target = self.parse_forward_global_ref("global value name in dso_local_equivalent")?;
Ok(DeferredConstantKind::DsoLocalEquivalent { target, loc })
}
fn parse_deferred_no_cfi(&mut self, loc: Span) -> ParseResult<DeferredConstantKind> {
self.expect_keyword(Keyword::NoCfi, "'no_cfi'")?;
let target = self.parse_forward_global_ref("global value name in no_cfi")?;
Ok(DeferredConstantKind::NoCfi { target, loc })
}
fn parse_constant(
&mut self,
dst: Type<'ctx>,
) -> ParseResult<Option<llvmkit_ir::Constant<'ctx>>> {
self.parse_global_value(dst).map(Some)
}
fn unsupported_constant_value_form_at(&self, loc: Span) -> ParseError {
ParseError::Expected {
expected: "supported constant/value form".into(),
loc: DiagLoc::span(loc),
}
}
fn unsupported_constant_expr_at(&self, loc: Span, op: crate::ll_token::Opcode) -> ParseError {
let opcode = match op {
crate::ll_token::Opcode::ExtractValue => "extractvalue",
crate::ll_token::Opcode::InsertValue => "insertvalue",
crate::ll_token::Opcode::UDiv => "udiv",
crate::ll_token::Opcode::SDiv => "sdiv",
crate::ll_token::Opcode::URem => "urem",
crate::ll_token::Opcode::SRem => "srem",
crate::ll_token::Opcode::FAdd => "fadd",
crate::ll_token::Opcode::FSub => "fsub",
crate::ll_token::Opcode::FMul => "fmul",
crate::ll_token::Opcode::FDiv => "fdiv",
crate::ll_token::Opcode::FRem => "frem",
crate::ll_token::Opcode::And => "and",
crate::ll_token::Opcode::Or => "or",
crate::ll_token::Opcode::LShr => "lshr",
crate::ll_token::Opcode::AShr => "ashr",
crate::ll_token::Opcode::Shl => "shl",
crate::ll_token::Opcode::Mul => "mul",
crate::ll_token::Opcode::FNeg => "fneg",
crate::ll_token::Opcode::Select => "select",
crate::ll_token::Opcode::ZExt => "zext",
crate::ll_token::Opcode::SExt => "sext",
crate::ll_token::Opcode::FPTrunc => "fptrunc",
crate::ll_token::Opcode::FPExt => "fpext",
crate::ll_token::Opcode::UIToFP => "uitofp",
crate::ll_token::Opcode::SIToFP => "sitofp",
crate::ll_token::Opcode::FPToUI => "fptoui",
crate::ll_token::Opcode::FPToSI => "fptosi",
crate::ll_token::Opcode::ICmp => "icmp",
crate::ll_token::Opcode::FCmp => "fcmp",
_ => return self.unsupported_constant_value_form_at(loc),
};
ParseError::Expected {
expected: format!("{opcode} constexprs are no longer supported"),
loc: DiagLoc::span(loc),
}
}
fn parse_val_id(
&mut self,
pfs: Option<&PerFunctionState<'ctx>>,
expected_ty: Option<Type<'ctx>>,
) -> ParseResult<ValId<'ctx>> {
let loc = self.loc();
match self.peek() {
Token::Kw(Keyword::Asm) => {
return Err(self.unsupported_constant_value_form_at(loc));
}
Token::Instruction(op) if !is_supported_constant_expr_opcode(*op) => {
return Err(self.unsupported_constant_expr_at(loc, *op));
}
_ => {}
}
if let Some(ty) = expected_ty {
match ty.into_type_enum() {
AnyTypeEnum::Array(array_ty) if matches!(self.peek(), Token::LSquare) => {
self.expect_punct(PunctKind::LSquare, "'[' to open array constant")?;
let values = if matches!(self.peek(), Token::RSquare) {
Vec::new()
} else {
self.parse_global_value_vector()?
};
self.expect_punct(PunctKind::RSquare, "']' to close array constant")?;
let c = array_ty
.const_array(values)
.map_err(|e| ParseError::Expected {
expected: format!("valid array constant: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
return Ok(ValId::Constant(c.as_constant()));
}
AnyTypeEnum::Vector(vec_ty) if matches!(self.peek(), Token::Less) => {
self.expect_punct(PunctKind::Less, "'<' to open vector constant")?;
let values = if matches!(self.peek(), Token::Greater) {
Vec::new()
} else {
self.parse_global_value_vector()?
};
self.expect_punct(PunctKind::Greater, "'>' to close vector constant")?;
let c = vec_ty
.const_vector(values)
.map_err(|e| ParseError::Expected {
expected: format!("valid vector constant: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
return Ok(ValId::Constant(c.as_constant()));
}
AnyTypeEnum::Struct(struct_ty)
if matches!(self.peek(), Token::LBrace)
|| (struct_ty.is_packed() && matches!(self.peek(), Token::Less)) =>
{
if struct_ty.is_opaque() {
return Err(self.expected("non-opaque struct type for struct constant"));
}
if struct_ty.is_packed() {
self.expect_punct(PunctKind::Less, "'<' to open packed struct constant")?;
}
self.expect_punct(PunctKind::LBrace, "'{' to open struct constant")?;
let values = if matches!(self.peek(), Token::RBrace) {
Vec::new()
} else {
self.parse_global_value_vector()?
};
self.expect_punct(PunctKind::RBrace, "'}' to close struct constant")?;
if struct_ty.is_packed() {
self.expect_punct(
PunctKind::Greater,
"'>' to close packed struct constant",
)?;
}
let c = struct_ty
.const_struct(values)
.map_err(|e| ParseError::Expected {
expected: format!("valid struct constant: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
return Ok(ValId::Constant(c.as_constant()));
}
_ => {}
}
}
match self.peek() {
Token::LocalVar(_) => {
if pfs.is_none() {
return Err(self.expected("global constant value"));
}
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("local SSA name"))?;
self.bump()?;
Ok(ValId::LocalName(name))
}
Token::LocalVarId(id) => {
if pfs.is_none() {
return Err(self.expected("global constant value"));
}
let id = *id;
self.bump()?;
Ok(ValId::LocalId(id))
}
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("global variable name"))?;
self.bump()?;
Ok(ValId::GlobalName(name))
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
Ok(ValId::GlobalId(id))
}
Token::IntegerLit(_) => {
let (negative, value) = self.parse_int_literal()?;
Ok(ValId::ApsInt {
negative,
words: Box::from([value]),
})
}
Token::FloatLit(_) => {
let float_ty = match expected_ty.map(Type::into_type_enum) {
Some(AnyTypeEnum::Float(t)) => t,
_ => return Err(self.expected("float constant only valid for float type")),
};
let bits = self.parse_fp_literal(&float_ty)?;
Ok(ValId::ApFloat(bits))
}
Token::Kw(Keyword::True) => {
let ty = expected_ty.ok_or_else(|| self.expected("i1 type for boolean literal"))?;
if ty != self.module.i1_type().as_type() {
return Err(self.expected("i1 type for boolean literal"));
}
self.bump()?;
Ok(ValId::ApsInt {
negative: false,
words: Box::from([1]),
})
}
Token::Kw(Keyword::False) => {
let ty = expected_ty.ok_or_else(|| self.expected("i1 type for boolean literal"))?;
if ty != self.module.i1_type().as_type() {
return Err(self.expected("i1 type for boolean literal"));
}
self.bump()?;
Ok(ValId::ApsInt {
negative: false,
words: Box::from([0]),
})
}
Token::Kw(Keyword::Null) => {
self.bump()?;
Ok(ValId::Null)
}
Token::Kw(Keyword::Zeroinitializer) => {
self.bump()?;
Ok(ValId::Zero)
}
Token::Kw(Keyword::Undef) => {
self.bump()?;
Ok(ValId::Undef)
}
Token::Kw(Keyword::Poison) => {
self.bump()?;
Ok(ValId::Poison)
}
Token::Kw(Keyword::None) => {
let ty = expected_ty.ok_or_else(|| self.expected("type for none constant"))?;
self.bump()?;
match ty.into_type_enum() {
AnyTypeEnum::Token(_) => Ok(ValId::Constant(self.module.token_none())),
_ => Err(self.expected("invalid type for none constant")),
}
}
Token::Kw(Keyword::Blockaddress) => {
let ty =
expected_ty.ok_or_else(|| self.expected("pointer type for blockaddress"))?;
self.parse_blockaddress_constant(ty).map(ValId::Constant)
}
Token::Kw(Keyword::DsoLocalEquivalent) => self
.parse_dso_local_equivalent_constant()
.map(ValId::Constant),
Token::Kw(Keyword::NoCfi) => self.parse_no_cfi_constant().map(ValId::Constant),
Token::MetadataVar(_) => {
let ty = expected_ty.ok_or_else(|| self.expected("metadata operand type"))?;
if !ty.is_metadata() {
return Err(self.expected("`metadata` type for a metadata operand"));
}
Ok(ValId::Value(self.parse_metadata_value_operand()?))
}
Token::Exclaim => {
let ty = expected_ty.ok_or_else(|| self.expected("metadata operand type"))?;
if !ty.is_metadata() {
return Err(self.expected("`metadata` type for a metadata operand"));
}
Ok(ValId::Value(self.parse_metadata_value_operand()?))
}
Token::Kw(Keyword::Ptrauth) => self.parse_ptrauth_constant().map(ValId::Constant),
Token::Kw(Keyword::Splat) => {
self.expect_keyword(Keyword::Splat, "'splat'")?;
self.expect_punct(PunctKind::LParen, "'(' in splat constant")?;
let scalar = self.parse_global_type_and_value()?;
self.expect_punct(PunctKind::RParen, "')' in splat constant")?;
Ok(ValId::ConstantSplat(scalar))
}
Token::Instruction(op) if is_supported_constant_expr_opcode(*op) => {
let ty = expected_ty.ok_or_else(|| self.unsupported_constant_value_form_at(loc))?;
self.parse_constant_expr(ty).map(ValId::Constant)
}
_ => Err(self.expected("constant initializer")),
}
}
fn expand_splat_constant(
&self,
ty: Type<'ctx>,
scalar: llvmkit_ir::Constant<'ctx>,
) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
let AnyTypeEnum::Vector(vec_ty) = ty.into_type_enum() else {
return Err(self.expected("fixed vector type for splat constant"));
};
if vec_ty.is_scalable() || scalar.ty() != vec_ty.element() {
return Err(self.expected("fixed vector type for splat constant"));
}
let len = usize::try_from(vec_ty.min_len()).map_err(|_| ParseError::Expected {
expected: "fixed vector type for splat constant".into(),
loc: DiagLoc::span(self.loc()),
})?;
let elements = vec![scalar; len];
vec_ty
.const_vector(elements)
.map(|c| c.as_constant())
.map_err(|e| self.builder_err("splat constant", e))
}
fn convert_val_id_to_value(
&mut self,
ty: Type<'ctx>,
id: ValId<'ctx>,
pfs: Option<&PerFunctionState<'ctx>>,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
match id {
ValId::LocalName(name) => pfs
.ok_or_else(|| self.expected("local value in function context"))?
.local_named
.get(&name)
.copied()
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: SYMBOL_KIND_LOCAL,
id: crate::parse_error::SymbolId::Named(name),
loc: DiagLoc::span(self.loc()),
}),
ValId::LocalId(id) => pfs
.ok_or_else(|| self.expected("local value in function context"))?
.local_numbered
.get(&id)
.copied()
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: SYMBOL_KIND_LOCAL,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(self.loc()),
}),
ValId::GlobalName(name) => self.resolve_global_name_as_value(name),
ValId::GlobalId(id) => self.resolve_global_id_as_value(id),
ValId::ApsInt { negative, words } => {
let int_ty = match ty.into_type_enum() {
AnyTypeEnum::Int(t) => t,
_ => return Err(self.expected("integer constant only valid for int type")),
};
let value = single_apint_word(&words)
.ok_or_else(|| self.expected("single-word integer literal"))?;
let raw = if negative {
value.wrapping_neg()
} else {
value
};
let c = int_ty
.const_int_raw(raw, negative)
.map_err(|e| self.builder_err("integer constant", e))?;
Ok(c.as_value())
}
ValId::ApFloat(bits) => {
let float_ty = match ty.into_type_enum() {
AnyTypeEnum::Float(t) => t,
_ => return Err(self.expected("float constant only valid for float type")),
};
Ok(float_ty.const_from_bits(bits).as_value())
}
ValId::Null => {
let pty = match ty.into_type_enum() {
AnyTypeEnum::Pointer(t) => t,
_ => return Err(self.expected("'null' is only valid for pointer types")),
};
Ok(pty.const_null().as_value())
}
ValId::Zero => match ty.into_type_enum() {
AnyTypeEnum::Int(t) => Ok(t.const_zero().as_value()),
AnyTypeEnum::Pointer(t) => Ok(t.const_null().as_value()),
AnyTypeEnum::Float(t) => Ok(t.const_from_bits(0).as_value()),
_ => Err(self.expected("zeroinitializer for the modeled scalar types")),
},
ValId::Undef => Ok(ty.get_undef().as_value()),
ValId::Poison => Ok(ty.get_poison().as_value()),
ValId::Constant(c) => Ok(c.as_value()),
ValId::ConstantSplat(c) => self.expand_splat_constant(ty, c).map(|c| c.as_value()),
ValId::Value(v) => Ok(v),
}
}
fn convert_val_id_to_constant(
&mut self,
ty: Type<'ctx>,
id: ValId<'ctx>,
) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
match id {
ValId::GlobalName(name) => {
match ty.into_type_enum() {
AnyTypeEnum::Pointer(_) => {}
_ => return Err(self.expected("global reference for pointer constant")),
}
self.resolve_global_name_as_constant(name)
}
ValId::GlobalId(id) => {
match ty.into_type_enum() {
AnyTypeEnum::Pointer(_) => {}
_ => return Err(self.expected("global reference for pointer constant")),
}
self.resolve_global_id_as_constant(id)
}
ValId::ApsInt { negative, words } => {
let int_ty = match ty.into_type_enum() {
AnyTypeEnum::Int(t) => t,
_ => return Err(self.expected("integer constant for non-integer type")),
};
let value = single_apint_word(&words)
.ok_or_else(|| self.expected("single-word integer literal"))?;
let raw = if negative {
value.wrapping_neg()
} else {
value
};
let c = int_ty
.const_int_raw(raw, negative)
.map_err(|e| ParseError::Expected {
expected: format!("valid integer constant: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
Ok(c.as_constant())
}
ValId::ApFloat(bits) => {
let float_ty = match ty.into_type_enum() {
AnyTypeEnum::Float(t) => t,
_ => return Err(self.expected("float constant only valid for float type")),
};
Ok(float_ty.const_from_bits(bits).as_constant())
}
ValId::Null => {
let ptr_ty = match ty.into_type_enum() {
AnyTypeEnum::Pointer(t) => t,
_ => return Err(self.expected("'null' is only valid for pointer types")),
};
Ok(ptr_ty.const_null().as_constant())
}
ValId::Zero => match ty.into_type_enum() {
AnyTypeEnum::Int(t) => Ok(t.const_zero().as_constant()),
AnyTypeEnum::Pointer(t) => Ok(t.const_null().as_constant()),
AnyTypeEnum::Float(t) => Ok(t.const_from_bits(0).as_constant()),
AnyTypeEnum::TargetExt(_) => self.module.target_ext_none(ty).map_err(|e| match e {
IrError::InvalidOperation { message } => ParseError::Expected {
expected: message.into(),
loc: DiagLoc::span(self.loc()),
},
other => self.builder_err("target extension none", other),
}),
_ => Err(self.expected("zeroinitializer for the modeled scalar types")),
},
ValId::Undef => Ok(ty.get_undef().as_constant()),
ValId::Poison => Ok(ty.get_poison().as_constant()),
ValId::Constant(c) => Ok(c),
ValId::ConstantSplat(c) => self.expand_splat_constant(ty, c),
ValId::LocalId(_) | ValId::LocalName(_) | ValId::Value(_) => {
Err(self.expected("constant value"))
}
}
}
fn parse_global_value(&mut self, ty: Type<'ctx>) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
let id = self.parse_val_id(None, Some(ty))?;
self.convert_val_id_to_constant(ty, id)
}
fn parse_global_type_and_value(&mut self) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
let ty = self.parse_type(false)?;
self.parse_global_value(ty)
}
fn parse_global_value_vector(&mut self) -> ParseResult<Vec<llvmkit_ir::Constant<'ctx>>> {
let mut values = Vec::new();
loop {
values.push(self.parse_global_type_and_value()?);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
Ok(values)
}
fn resolve_global_name_as_value(&self, name: String) -> ParseResult<llvmkit_ir::Value<'ctx>> {
if name.starts_with("llvm.") {
return Err(ParseError::Expected {
expected: "intrinsic can only be used as callee".into(),
loc: DiagLoc::span(self.loc()),
});
}
if let Some(gv) = self.module.get_global(&name) {
Ok(gv.as_value())
} else if let Some(fv) = self.module.function_by_name(&name) {
Ok(fv.as_value())
} else if let Some(a) = self.module.get_alias(&name) {
Ok(a.as_value())
} else if let Some(i) = self.module.get_ifunc(&name) {
Ok(i.as_value())
} else {
Err(ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Named(name),
loc: DiagLoc::span(self.loc()),
})
}
}
fn resolve_global_id_as_value(&self, id: u32) -> ParseResult<llvmkit_ir::Value<'ctx>> {
self.numbered_globals
.get(id)
.copied()
.map(|r| match r {
GlobalRef::Function(f) => f.as_value(),
GlobalRef::Variable(g) => g.as_value(),
GlobalRef::Alias(a) => a.as_value(),
GlobalRef::IFunc(i) => i.as_value(),
})
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(self.loc()),
})
}
fn resolve_global_name_as_constant(
&self,
name: String,
) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
if name.starts_with("llvm.") {
return Err(ParseError::Expected {
expected: "intrinsic can only be used as callee".into(),
loc: DiagLoc::span(self.loc()),
});
}
if let Some(g) = self.module.get_global(&name) {
Ok(g.as_global_constant_ptr())
} else if let Some(f) = self.module.function_by_name(&name) {
Ok(f.as_global_constant_ptr())
} else if let Some(a) = self.module.get_alias(&name) {
Ok(a.as_global_constant_ptr())
} else if let Some(i) = self.module.get_ifunc(&name) {
Ok(i.as_global_constant_ptr())
} else {
Err(ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Named(name),
loc: DiagLoc::span(self.loc()),
})
}
}
fn resolve_global_id_as_constant(&self, id: u32) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
self.numbered_globals
.get(id)
.copied()
.map(|r| self.global_ref_to_constant(r))
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(self.loc()),
})
}
fn global_ref_to_constant(&self, r: GlobalRef<'ctx>) -> llvmkit_ir::Constant<'ctx> {
match r {
GlobalRef::Function(f) => f.as_global_constant_ptr(),
GlobalRef::Variable(g) => g.as_global_constant_ptr(),
GlobalRef::Alias(a) => a.as_global_constant_ptr(),
GlobalRef::IFunc(i) => i.as_global_constant_ptr(),
}
}
fn resolve_global_name_as_ref(&self, name: String) -> ParseResult<GlobalRef<'ctx>> {
if let Some(gv) = self.module.get_global(&name) {
Ok(GlobalRef::Variable(gv))
} else if let Some(fv) = self.module.function_by_name(&name) {
Ok(GlobalRef::Function(fv))
} else if let Some(a) = self.module.get_alias(&name) {
Ok(GlobalRef::Alias(a))
} else if let Some(i) = self.module.get_ifunc(&name) {
Ok(GlobalRef::IFunc(i))
} else {
Err(ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Named(name),
loc: DiagLoc::span(self.loc()),
})
}
}
fn resolve_global_id_as_ref(&self, id: u32) -> ParseResult<GlobalRef<'ctx>> {
self.numbered_globals
.get(id)
.copied()
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(self.loc()),
})
}
fn resolve_global_id_as_function(
&self,
id: u32,
) -> ParseResult<llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>> {
self.numbered_globals
.get(id)
.and_then(|r| match r {
GlobalRef::Function(f) => Some(*f),
_ => None,
})
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(self.loc()),
})
}
fn get_or_create_forward_function(
&self,
name: &str,
) -> ParseResult<llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>> {
if let Some(function) = self.module.function_by_name(name) {
return Ok(function);
}
let fn_ty =
self.module
.fn_type(self.module.void_type().as_type(), Vec::<Type>::new(), false);
self.module
.function_builder::<llvmkit_ir::Dyn>(name.to_owned(), fn_ty)
.build()
.map_err(|e| ParseError::Expected {
expected: format!("valid forward function declaration: {e}"),
loc: DiagLoc::span(self.loc()),
})
}
fn parse_function_ref_for_constant(
&mut self,
expected: &'static str,
) -> ParseResult<llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>> {
match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected(expected))?;
self.bump()?;
self.get_or_create_forward_function(&name)
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
self.resolve_global_id_as_function(id)
}
_ => Err(self.expected(expected)),
}
}
fn parse_blockaddress_constant(
&mut self,
expected_ty: Type<'ctx>,
) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
if !matches!(expected_ty.into_type_enum(), AnyTypeEnum::Pointer(_)) {
return Err(self.expected("pointer type for blockaddress"));
}
self.expect_keyword(Keyword::Blockaddress, "'blockaddress'")?;
self.expect_punct(PunctKind::LParen, "'(' in blockaddress")?;
let function = self.parse_function_ref_for_constant("function name in blockaddress")?;
self.expect_punct(PunctKind::Comma, "',' in blockaddress")?;
let label = match self.peek() {
Token::LocalVar(_) => self
.current_str_payload()
.ok_or_else(|| self.expected("basic block name in blockaddress"))?,
Token::LocalVarId(id) => id.to_string(),
_ => return Err(self.expected("basic block name in blockaddress")),
};
self.bump()?;
self.expect_punct(PunctKind::RParen, "')' in blockaddress")?;
let block = if let Some(block) = function
.basic_blocks()
.find(|bb| bb.name().as_deref() == Some(label.as_str()))
{
block
} else if function.basic_blocks().all(|bb| bb.is_empty()) {
function.append_basic_block(label)
} else {
return Err(self.expected("referenced value is not a basic block"));
};
self.module
.block_address(function, block)
.map_err(|e| self.builder_err("blockaddress", e))
}
fn parse_dso_local_equivalent_constant(&mut self) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
self.expect_keyword(Keyword::DsoLocalEquivalent, "'dso_local_equivalent'")?;
let global = match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("global value name in dso_local_equivalent"))?;
self.bump()?;
match self.resolve_global_name_as_ref(name.clone()) {
Ok(global) => Ok(global),
Err(ParseError::UndefinedSymbol { .. }) => self
.get_or_create_forward_function(&name)
.map(GlobalRef::Function),
Err(err) => Err(err),
}
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
self.resolve_global_id_as_ref(id)
}
_ => Err(self.expected("global value name in dso_local_equivalent")),
}?;
self.module
.dso_local_equivalent_global(self.global_ref_to_constant(global))
.map_err(|e| self.builder_err("dso_local_equivalent", e))
}
fn parse_no_cfi_constant(&mut self) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
self.expect_keyword(Keyword::NoCfi, "'no_cfi'")?;
let global = match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("global value name in no_cfi"))?;
self.bump()?;
match self.resolve_global_name_as_ref(name.clone()) {
Ok(global) => Ok(global),
Err(ParseError::UndefinedSymbol { .. }) => self
.get_or_create_forward_function(&name)
.map(GlobalRef::Function),
Err(err) => Err(err),
}
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
self.resolve_global_id_as_ref(id)
}
_ => Err(self.expected("global value name in no_cfi")),
}?;
self.module
.no_cfi_global(self.global_ref_to_constant(global))
.map_err(|e| self.builder_err("no_cfi", e))
}
fn parse_ptrauth_pointer_operand(&mut self) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
let ty = self.parse_type(false)?;
match ty.into_type_enum() {
AnyTypeEnum::Pointer(_) => {}
_ => return Err(self.expected("pointer operand in ptrauth")),
}
match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("global value name in ptrauth"))?;
self.bump()?;
self.resolve_global_name_as_constant(name)
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
self.resolve_global_id_as_constant(id)
}
_ => self.parse_global_value(ty),
}
}
fn parse_ptrauth_constant(&mut self) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
self.expect_keyword(Keyword::Ptrauth, "'ptrauth'")?;
self.expect_punct(PunctKind::LParen, "'(' in ptrauth")?;
let pointer = self.parse_ptrauth_pointer_operand()?;
self.expect_punct(PunctKind::Comma, "',' in ptrauth")?;
let key = self.parse_global_type_and_value()?;
let discriminator = if self.eat_punct(PunctKind::Comma)? {
self.parse_global_type_and_value()?
} else {
self.module.i64_type().const_zero().as_constant()
};
let addr_discriminator = if self.eat_punct(PunctKind::Comma)? {
self.parse_ptrauth_pointer_operand()?
} else {
self.module.ptr_type(0).const_null().as_constant()
};
let deactivation_symbol = if self.eat_punct(PunctKind::Comma)? {
self.parse_ptrauth_pointer_operand()?
} else {
self.module.ptr_type(0).const_null().as_constant()
};
self.expect_punct(PunctKind::RParen, "')' in ptrauth")?;
self.module
.ptr_auth(
pointer.as_value(),
key.as_value(),
discriminator.as_value(),
addr_discriminator.as_value(),
deactivation_symbol.as_value(),
)
.map_err(|e| self.builder_err("ptrauth", e))
}
fn parse_constant_expr(
&mut self,
result_ty: Type<'ctx>,
) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
use llvmkit_ir::{ConstantExprFlags, ConstantExprOpcode};
let op = match self.peek() {
Token::Instruction(op) => *op,
_ => return Err(self.expected("constant expression opcode")),
};
self.bump()?;
let mut flags = ConstantExprFlags::default();
let opcode = match op {
crate::ll_token::Opcode::GetElementPtr => {
if self.eat_keyword(Keyword::Inbounds)? {
flags.inbounds = true;
ConstantExprOpcode::InBoundsGetElementPtr
} else {
ConstantExprOpcode::GetElementPtr
}
}
crate::ll_token::Opcode::BitCast => ConstantExprOpcode::BitCast,
crate::ll_token::Opcode::AddrSpaceCast => ConstantExprOpcode::AddrSpaceCast,
crate::ll_token::Opcode::IntToPtr => ConstantExprOpcode::IntToPtr,
crate::ll_token::Opcode::PtrToAddr => ConstantExprOpcode::PtrToAddr,
crate::ll_token::Opcode::PtrToInt => ConstantExprOpcode::PtrToInt,
crate::ll_token::Opcode::Trunc => {
flags.nuw = self.eat_keyword(Keyword::Nuw)?;
flags.nsw = self.eat_keyword(Keyword::Nsw)?;
ConstantExprOpcode::Trunc
}
crate::ll_token::Opcode::Add => {
flags.nuw = self.eat_keyword(Keyword::Nuw)?;
flags.nsw = self.eat_keyword(Keyword::Nsw)?;
ConstantExprOpcode::Add
}
crate::ll_token::Opcode::Sub => {
flags.nuw = self.eat_keyword(Keyword::Nuw)?;
flags.nsw = self.eat_keyword(Keyword::Nsw)?;
ConstantExprOpcode::Sub
}
crate::ll_token::Opcode::Xor => ConstantExprOpcode::Xor,
crate::ll_token::Opcode::ExtractElement => ConstantExprOpcode::ExtractElement,
crate::ll_token::Opcode::InsertElement => ConstantExprOpcode::InsertElement,
crate::ll_token::Opcode::ShuffleVector => ConstantExprOpcode::ShuffleVector,
_ => return Err(self.unsupported_constant_value_form_at(self.loc())),
};
self.expect_punct(PunctKind::LParen, "'(' in constantexpr")?;
if matches!(
opcode,
ConstantExprOpcode::Trunc
| ConstantExprOpcode::IntToPtr
| ConstantExprOpcode::PtrToAddr
| ConstantExprOpcode::PtrToInt
| ConstantExprOpcode::BitCast
| ConstantExprOpcode::AddrSpaceCast
) {
let operand = self.parse_global_type_and_value()?;
self.expect_keyword(Keyword::To, "'to' in constantexpr cast")?;
let dst_ty = self.parse_type(false)?;
if dst_ty != result_ty {
return Err(
self.expected("constant expression destination type matches initializer type")
);
}
self.expect_punct(PunctKind::RParen, "')' in constantexpr")?;
return self.build_constant_expr(result_ty, None, opcode, vec![operand], flags);
}
let source_ty = if matches!(
opcode,
ConstantExprOpcode::GetElementPtr | ConstantExprOpcode::InBoundsGetElementPtr
) {
let ty = self.parse_type(false)?;
self.expect_punct(PunctKind::Comma, "',' after getelementptr source type")?;
Some(ty)
} else {
None
};
let operands = if matches!(self.peek(), Token::RParen) {
Vec::new()
} else {
self.parse_global_value_vector()?
};
self.expect_punct(PunctKind::RParen, "')' in constantexpr")?;
self.build_constant_expr(result_ty, source_ty, opcode, operands, flags)
}
fn build_constant_expr(
&self,
result_ty: Type<'ctx>,
source_ty: Option<Type<'ctx>>,
opcode: llvmkit_ir::ConstantExprOpcode,
operands: Vec<llvmkit_ir::Constant<'ctx>>,
flags: llvmkit_ir::ConstantExprFlags,
) -> ParseResult<llvmkit_ir::Constant<'ctx>> {
self.module
.constant_expr_with_options(
result_ty,
opcode,
operands.into_iter().map(|c| c.as_value()),
[],
[],
llvmkit_ir::ConstantExprOptions { source_ty, flags },
)
.map_err(|e| self.builder_err("constant expression", e))
}
fn parse_optional_function_linkage(&mut self, is_define: bool) -> ParseResult<Linkage> {
let loc = self.loc();
let linkage = if self.eat_keyword(Keyword::External)? {
Linkage::External
} else if self.eat_keyword(Keyword::AvailableExternally)? {
Linkage::AvailableExternally
} else if self.eat_keyword(Keyword::Linkonce)? {
Linkage::LinkOnceAny
} else if self.eat_keyword(Keyword::LinkonceOdr)? {
Linkage::LinkOnceODR
} else if self.eat_keyword(Keyword::Weak)? {
Linkage::WeakAny
} else if self.eat_keyword(Keyword::WeakOdr)? {
Linkage::WeakODR
} else if self.eat_keyword(Keyword::Appending)? {
Linkage::Appending
} else if self.eat_keyword(Keyword::Internal)? {
Linkage::Internal
} else if self.eat_keyword(Keyword::Private)? {
Linkage::Private
} else if self.eat_keyword(Keyword::ExternWeak)? {
Linkage::ExternalWeak
} else if self.eat_keyword(Keyword::Common)? {
Linkage::Common
} else {
Linkage::External
};
match linkage {
Linkage::Appending | Linkage::Common => Err(ParseError::Expected {
expected: "invalid function linkage type".into(),
loc: DiagLoc::span(loc),
}),
Linkage::ExternalWeak if is_define => Err(ParseError::Expected {
expected: "invalid linkage for function definition".into(),
loc: DiagLoc::span(loc),
}),
Linkage::Private
| Linkage::Internal
| Linkage::AvailableExternally
| Linkage::LinkOnceAny
| Linkage::LinkOnceODR
| Linkage::WeakAny
| Linkage::WeakODR
if !is_define =>
{
Err(ParseError::Expected {
expected: "invalid linkage for function declaration".into(),
loc: DiagLoc::span(loc),
})
}
_ => Ok(linkage),
}
}
fn parse_optional_visibility(&mut self) -> ParseResult<Visibility> {
if self.eat_keyword(Keyword::Default)? {
Ok(Visibility::Default)
} else if self.eat_keyword(Keyword::Hidden)? {
Ok(Visibility::Hidden)
} else if self.eat_keyword(Keyword::Protected)? {
Ok(Visibility::Protected)
} else {
Ok(Visibility::Default)
}
}
fn parse_optional_dll_storage_class(&mut self) -> ParseResult<DllStorageClass> {
if self.eat_keyword(Keyword::Dllimport)? {
Ok(DllStorageClass::DllImport)
} else if self.eat_keyword(Keyword::Dllexport)? {
Ok(DllStorageClass::DllExport)
} else {
Ok(DllStorageClass::Default)
}
}
fn parse_optional_dso_locality(&mut self) -> ParseResult<llvmkit_ir::DsoLocality> {
if self.eat_keyword(Keyword::DsoLocal)? {
Ok(llvmkit_ir::DsoLocality::Local)
} else if self.eat_keyword(Keyword::DsoPreemptable)? {
Ok(llvmkit_ir::DsoLocality::Preemptable)
} else {
Ok(llvmkit_ir::DsoLocality::Default)
}
}
fn parse_optional_function_unnamed_addr(&mut self) -> ParseResult<UnnamedAddr> {
if self.eat_keyword(Keyword::UnnamedAddr)? {
Ok(UnnamedAddr::Global)
} else if self.eat_keyword(Keyword::LocalUnnamedAddr)? {
Ok(UnnamedAddr::Local)
} else {
Ok(UnnamedAddr::None)
}
}
fn parse_unnamed_attr_group(&mut self) -> ParseResult<()> {
self.expect_keyword(Keyword::Attributes, "'attributes'")?;
let loc = self.loc();
let id = match self.peek() {
Token::AttrGrpId(id) => {
let id = *id;
self.bump()?;
id
}
_ => return Err(self.expected("attribute group id")),
};
self.expect_punct(PunctKind::Equal, "'=' after attribute group id")?;
self.expect_punct(PunctKind::LBrace, "'{' in attribute group")?;
let mut storage = AttributeStorage::new();
let groups =
self.parse_fn_attribute_value_pairs(&mut storage, AttrIndex::Function, false)?;
if !groups.is_empty() {
return Err(ParseError::Expected {
expected: "attribute".into(),
loc: DiagLoc::span(loc),
});
}
self.expect_punct(PunctKind::RBrace, "'}' closing attribute group")?;
if storage.is_empty() {
return Err(ParseError::Expected {
expected: "attribute group has no attributes".into(),
loc: DiagLoc::span(loc),
});
}
self.numbered_attr_groups
.add(id, storage.clone())
.map_err(|source| ParseError::InvalidSlotId {
source,
loc: DiagLoc::span(loc),
})?;
self.module.set_attribute_group(id, storage);
Ok(())
}
fn attr_kind_for_keyword(keyword: Keyword) -> Option<AttrKind> {
Some(match keyword {
Keyword::Zeroext => AttrKind::ZExt,
Keyword::Signext => AttrKind::SExt,
Keyword::Noundef => AttrKind::NoUndef,
Keyword::Nonnull => AttrKind::NonNull,
Keyword::Noalias => AttrKind::NoAlias,
Keyword::Nounwind => AttrKind::NoUnwind,
Keyword::Readnone => AttrKind::ReadNone,
Keyword::Readonly => AttrKind::ReadOnly,
Keyword::Alwaysinline => AttrKind::AlwaysInline,
Keyword::Noinline => AttrKind::NoInline,
Keyword::Writeonly => AttrKind::WriteOnly,
Keyword::Returned => AttrKind::Returned,
Keyword::Nocapture => AttrKind::NoCapture,
Keyword::Nofree => AttrKind::NoFree,
Keyword::Writable => AttrKind::Writable,
Keyword::Noreturn => AttrKind::NoReturn,
Keyword::Willreturn => AttrKind::WillReturn,
Keyword::Mustprogress => AttrKind::MustProgress,
Keyword::Nosync => AttrKind::NoSync,
Keyword::Optnone => AttrKind::OptimizeNone,
Keyword::Optsize => AttrKind::OptimizeForSize,
Keyword::Speculatable => AttrKind::Speculatable,
_ => return None,
})
}
fn is_attr_start(&self) -> bool {
match self.peek() {
Token::AttrGrpId(_) | Token::StringConstant(_) => true,
Token::Kw(Keyword::Align | Keyword::Alignstack) => true,
Token::Kw(keyword) => Self::attr_kind_for_keyword(*keyword).is_some(),
_ => false,
}
}
fn parse_optional_function_header_attrs(
&mut self,
attrs: &mut AttributeStorage,
) -> ParseResult<Vec<u32>> {
let mut groups = Vec::new();
while self.is_attr_start() {
groups.extend(self.parse_fn_attribute_value_pairs(attrs, AttrIndex::Function, true)?);
}
Ok(groups)
}
fn parse_optional_function_suffix(
&mut self,
attrs: &mut AttributeStorage,
) -> ParseResult<FunctionSuffix<'ctx>> {
let mut suffix = FunctionSuffix {
attr_groups: self.parse_optional_function_header_attrs(attrs)?,
..FunctionSuffix::default()
};
loop {
match self.peek() {
Token::Kw(Keyword::Section) => {
self.bump()?;
suffix.section = Some(self.parse_string_constant("section name")?);
}
Token::Kw(Keyword::Partition) => {
self.bump()?;
suffix.partition = Some(self.parse_string_constant("partition name")?);
}
Token::Kw(Keyword::Comdat) => {
self.bump()?;
suffix.comdat = if self.eat_punct(PunctKind::LParen)? {
let name = match self.peek() {
Token::ComdatVar(bytes) => std::str::from_utf8(bytes.as_ref())
.map_err(|_| self.expected("valid UTF-8 comdat name"))?
.to_owned(),
_ => return Err(self.expected("comdat variable")),
};
self.bump()?;
self.expect_punct(PunctKind::RParen, "')' after comdat")?;
Some(Some(name))
} else {
Some(None)
};
}
Token::Kw(Keyword::Align) => {
self.bump()?;
let bytes = self.parse_uint64("function alignment")?;
suffix.align = MaybeAlign::new(
Align::new(bytes).map_err(|e| self.builder_err("function align", e))?,
);
}
Token::Kw(Keyword::Gc) => {
self.bump()?;
suffix.gc = Some(self.parse_string_constant("gc name")?);
}
Token::Kw(Keyword::Prefix) => {
self.bump()?;
suffix.prefix_data = Some(self.parse_global_type_and_value()?);
}
Token::Kw(Keyword::Prologue) => {
self.bump()?;
suffix.prologue_data = Some(self.parse_global_type_and_value()?);
}
Token::Kw(Keyword::Personality) => {
self.bump()?;
suffix.personality_fn = Some(self.parse_global_type_and_value()?);
}
Token::MetadataVar(_) => {
suffix
.metadata
.push(self.parse_named_metadata_attachment()?);
}
Token::Comma => {
self.bump()?;
if matches!(self.peek(), Token::MetadataVar(_)) {
suffix
.metadata
.push(self.parse_named_metadata_attachment()?);
} else {
return Err(self.expected("metadata attachment"));
}
}
_ if self.is_attr_start() => {
suffix
.attr_groups
.extend(self.parse_optional_function_header_attrs(attrs)?);
}
_ => break,
}
}
Ok(suffix)
}
fn parse_fn_attribute_value_pairs(
&mut self,
out: &mut AttributeStorage,
index: AttrIndex,
allow_group_refs: bool,
) -> ParseResult<Vec<u32>> {
let mut groups = Vec::new();
loop {
match self.peek() {
Token::RBrace | Token::LBrace | Token::Comma | Token::Eof => break,
Token::AttrGrpId(id) if allow_group_refs => {
let id = *id;
self.bump()?;
groups.push(id);
}
Token::AttrGrpId(_) => return Err(self.expected("attribute")),
Token::StringConstant(_) => {
let key = self.parse_string_constant("attribute string key")?;
let value = if self.eat_punct(PunctKind::Equal)? {
self.parse_string_constant("attribute string value")?
} else {
String::new()
};
out.add(index, Attribute::string(key, value));
}
Token::Kw(Keyword::Align) => {
self.bump()?;
let value = self.parse_uint64("align value")?;
let attr = Attribute::int(AttrKind::Alignment, value)
.ok_or_else(|| self.expected("attribute"))?;
out.add(index, attr);
}
Token::Kw(Keyword::Alignstack) => {
self.bump()?;
let value = self.parse_uint64("alignstack value")?;
let attr = Attribute::int(AttrKind::StackAlignment, value)
.ok_or_else(|| self.expected("attribute"))?;
out.add(index, attr);
}
Token::Kw(keyword) => {
let Some(kind) = Self::attr_kind_for_keyword(*keyword) else {
break;
};
self.bump()?;
let attr =
Attribute::enum_attr(kind).ok_or_else(|| self.expected("attribute"))?;
out.add(index, attr);
}
_ => break,
}
}
Ok(groups)
}
fn parse_optional_param_attrs(&mut self) -> ParseResult<AttributeStorage> {
let mut storage = AttributeStorage::new();
let groups =
self.parse_fn_attribute_value_pairs(&mut storage, AttrIndex::Param(0), false)?;
if !groups.is_empty() {
return Err(self.expected("attribute"));
}
Ok(storage)
}
fn parse_optional_return_attrs(&mut self) -> ParseResult<AttributeStorage> {
let mut storage = AttributeStorage::new();
let groups = self.parse_fn_attribute_value_pairs(&mut storage, AttrIndex::Return, false)?;
if !groups.is_empty() {
return Err(self.expected("attribute"));
}
Ok(storage)
}
fn parse_optional_fn_attrs(&mut self) -> ParseResult<(AttributeStorage, Vec<u32>)> {
let mut storage = AttributeStorage::new();
let groups =
self.parse_fn_attribute_value_pairs(&mut storage, AttrIndex::Function, true)?;
Ok((storage, groups))
}
fn operand_bundle_tag_from_name(name: String) -> llvmkit_ir::instr_types::OperandBundleTag {
match name.as_str() {
"deopt" => llvmkit_ir::instr_types::OperandBundleTag::Deopt,
"funclet" => llvmkit_ir::instr_types::OperandBundleTag::Funclet,
"gc-transition" => llvmkit_ir::instr_types::OperandBundleTag::GcTransition,
"cfguardtarget" => llvmkit_ir::instr_types::OperandBundleTag::CfGuardTarget,
"preallocated" => llvmkit_ir::instr_types::OperandBundleTag::Preallocated,
"gc-live" => llvmkit_ir::instr_types::OperandBundleTag::GcLive,
"clang.arc.attachedcall" => {
llvmkit_ir::instr_types::OperandBundleTag::ClangArcAttachedCall
}
"ptrauth" => llvmkit_ir::instr_types::OperandBundleTag::PtrAuth,
"kcfi" => llvmkit_ir::instr_types::OperandBundleTag::Kcfi,
"convergencectrl" => llvmkit_ir::instr_types::OperandBundleTag::ConvergenceCtrl,
"align" => llvmkit_ir::instr_types::OperandBundleTag::Align,
"deactivation" => llvmkit_ir::instr_types::OperandBundleTag::DeactivationSymbol,
_ => llvmkit_ir::instr_types::OperandBundleTag::Custom(name),
}
}
fn parse_optional_operand_bundles(
&mut self,
state: &PerFunctionState<'ctx>,
) -> ParseResult<Box<[llvmkit_ir::instr_types::OperandBundleData]>> {
if !self.eat_punct(PunctKind::LSquare)? {
return Ok(Box::new([]));
}
let mut bundles = Vec::new();
if !matches!(self.peek(), Token::RSquare) {
loop {
let tag = self.parse_string_constant("operand bundle tag")?;
self.expect_punct(PunctKind::LParen, "'(' in operand bundle")?;
let mut inputs = Vec::new();
if !matches!(self.peek(), Token::RParen) {
loop {
let ty = self.parse_type(false)?;
let value = self.parse_value(state, ty)?;
inputs.push(core::cell::Cell::new(value.id()));
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' in operand bundle")?;
bundles.push(llvmkit_ir::instr_types::OperandBundleData {
tag: Self::operand_bundle_tag_from_name(tag),
inputs: inputs.into_boxed_slice(),
});
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RSquare, "']' to close operand bundles")?;
Ok(bundles.into_boxed_slice())
}
fn parse_declare(&mut self) -> ParseResult<()> {
self.expect_keyword(Keyword::Declare, "'declare'")?;
let linkage = self.parse_optional_function_linkage(false)?;
let visibility = self.parse_optional_visibility()?;
let dll_storage_class = self.parse_optional_dll_storage_class()?;
let dso_locality = self.parse_optional_dso_locality()?;
let calling_conv = self.parse_optional_calling_conv()?;
let mut attrs = AttributeStorage::new();
self.parse_fn_attribute_value_pairs(&mut attrs, AttrIndex::Return, false)?;
let ret_ty = self.parse_type(true)?;
let (name_id, name) = match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("function name"))?;
(NameOrId::Name(name.clone()), name)
}
Token::GlobalId(n) => {
let id = *n;
(NameOrId::Id(id), format!("{id}"))
}
_ => return Err(self.expected("function name after return type")),
};
let decl_loc = self.loc();
self.bump()?;
self.expect_punct(PunctKind::LParen, "'(' in function declaration")?;
let mut params = Vec::new();
let mut param_names: Vec<Option<String>> = Vec::new();
let mut var_args = false;
if !matches!(self.peek(), Token::RParen) {
loop {
if matches!(self.peek(), Token::DotDotDot) {
self.bump()?;
var_args = true;
break;
}
let p_ty = self.parse_type(false)?;
let slot = u32::try_from(params.len()).map_err(|_| ParseError::Expected {
expected: "parameter slot fits in u32".into(),
loc: DiagLoc::span(decl_loc),
})?;
self.parse_fn_attribute_value_pairs(&mut attrs, AttrIndex::Param(slot), false)?;
let p_name = if matches!(self.peek(), Token::LocalVar(_)) {
let n = self
.current_str_payload()
.ok_or_else(|| self.expected("parameter name"))?;
self.bump()?;
Some(n)
} else if matches!(self.peek(), Token::LocalVarId(_)) {
self.bump()?;
None
} else {
None
};
param_names.push(p_name);
params.push(p_ty);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' to close function declaration")?;
let unnamed_addr = self.parse_optional_function_unnamed_addr()?;
let address_space = if self.eat_keyword(Keyword::Addrspace)? {
self.parse_addr_space_paren()?
} else {
0
};
let suffix = self.parse_optional_function_suffix(&mut attrs)?;
let fn_ty = self.module.fn_type(ret_ty, params, var_args);
let f = if let Some(existing) = self.module.function_by_name(&name) {
if existing.signature() != fn_ty || existing.basic_blocks().len() != 0 {
return Err(ParseError::Expected {
expected: "forward function declaration with matching signature".into(),
loc: DiagLoc::span(decl_loc),
});
}
existing.set_linkage(linkage);
existing.set_visibility(visibility);
existing.set_dll_storage_class(dll_storage_class);
existing.set_dso_locality(dso_locality);
existing.set_calling_conv(calling_conv);
existing.set_unnamed_addr(unnamed_addr);
existing.set_address_space(address_space);
existing.set_attributes(attrs);
existing
} else {
let mut fb = self
.module
.function_builder::<llvmkit_ir::Dyn>(name, fn_ty)
.linkage(linkage)
.visibility(visibility)
.dll_storage_class(dll_storage_class)
.dso_locality(dso_locality)
.calling_conv(calling_conv)
.unnamed_addr(unnamed_addr)
.address_space(address_space)
.attribute_storage(attrs);
for (slot, name) in param_names.into_iter().enumerate() {
if let Some(name) = name {
let slot = u32::try_from(slot).map_err(|_| ParseError::Expected {
expected: "parameter slot fits in u32".into(),
loc: DiagLoc::span(decl_loc),
})?;
fb = fb.param_name(slot, name);
}
}
fb.build().map_err(|e| ParseError::Expected {
expected: format!("valid function declaration: {e}"),
loc: DiagLoc::span(decl_loc),
})?
};
for group in suffix.attr_groups {
f.add_function_attr_group(group);
}
if let Some(section) = suffix.section {
f.set_section(Some(section));
}
if let Some(partition) = suffix.partition {
f.set_partition(Some(partition));
}
if let Some(comdat_name) = suffix.comdat {
let name = comdat_name.unwrap_or_else(|| f.name().to_owned());
let comdat = self.module.get_or_insert_comdat(name);
f.set_comdat(Some(comdat))
.map_err(|e| self.builder_err("function comdat", e))?;
}
f.set_align(suffix.align);
if let Some(gc) = suffix.gc {
f.set_gc(Some(gc));
}
if let Some(prefix_data) = suffix.prefix_data {
f.set_prefix_data(Some(prefix_data))
.map_err(|e| self.builder_err("function prefix", e))?;
}
if let Some(prologue_data) = suffix.prologue_data {
f.set_prologue_data(Some(prologue_data))
.map_err(|e| self.builder_err("function prologue", e))?;
}
if let Some(personality_fn) = suffix.personality_fn {
f.set_personality_fn(Some(personality_fn))
.map_err(|e| self.builder_err("function personality", e))?;
}
for (kind, id) in suffix.metadata {
f.set_metadata(kind, id);
}
if let NameOrId::Id(id) = name_id {
self.numbered_globals
.add(id, GlobalRef::Function(f))
.map_err(|source| ParseError::InvalidSlotId {
source,
loc: DiagLoc::span(decl_loc),
})?;
}
Ok(())
}
fn parse_define(&mut self) -> ParseResult<()> {
self.expect_keyword(Keyword::Define, "'define'")?;
let linkage = self.parse_optional_function_linkage(true)?;
let visibility = self.parse_optional_visibility()?;
let dll_storage_class = self.parse_optional_dll_storage_class()?;
let dso_locality = self.parse_optional_dso_locality()?;
let calling_conv = self.parse_optional_calling_conv()?;
let mut attrs = AttributeStorage::new();
self.parse_fn_attribute_value_pairs(&mut attrs, AttrIndex::Return, false)?;
let ret_ty = self.parse_type(true)?;
let name = match self.peek() {
Token::GlobalVar(_) => self
.current_str_payload()
.ok_or_else(|| self.expected("function name"))?,
Token::GlobalId(n) => format!("{n}"),
_ => return Err(self.expected("function name after return type")),
};
let decl_loc = self.loc();
self.bump()?;
self.expect_punct(PunctKind::LParen, "'(' in function header")?;
let mut param_types = Vec::new();
let mut param_names: Vec<Option<ParamName>> = Vec::new();
let mut var_args = false;
if !matches!(self.peek(), Token::RParen) {
loop {
if matches!(self.peek(), Token::DotDotDot) {
self.bump()?;
var_args = true;
break;
}
let p_ty = self.parse_type(false)?;
let slot = u32::try_from(param_types.len()).map_err(|_| ParseError::Expected {
expected: "parameter slot fits in u32".into(),
loc: DiagLoc::span(decl_loc),
})?;
self.parse_fn_attribute_value_pairs(&mut attrs, AttrIndex::Param(slot), false)?;
let p_name = match self.peek() {
Token::LocalVar(_) => {
let s = self
.current_str_payload()
.ok_or_else(|| self.expected("local identifier payload"))?;
self.bump()?;
Some(ParamName::Named(s))
}
Token::LocalVarId(id) => {
let id = *id;
self.bump()?;
Some(ParamName::Numbered(id))
}
_ => None,
};
param_types.push(p_ty);
param_names.push(p_name);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' to close function header")?;
let unnamed_addr = self.parse_optional_function_unnamed_addr()?;
let address_space = if self.eat_keyword(Keyword::Addrspace)? {
self.parse_addr_space_paren()?
} else {
0
};
let suffix = self.parse_optional_function_suffix(&mut attrs)?;
let fn_ty = self.module.fn_type(ret_ty, param_types, var_args);
let f = if let Some(existing) = self.module.function_by_name(&name) {
if existing.signature() != fn_ty || existing.basic_blocks().any(|bb| !bb.is_empty()) {
return Err(ParseError::Expected {
expected: "forward function definition with matching signature".into(),
loc: DiagLoc::span(decl_loc),
});
}
existing.set_linkage(linkage);
existing.set_visibility(visibility);
existing.set_dll_storage_class(dll_storage_class);
existing.set_dso_locality(dso_locality);
existing.set_calling_conv(calling_conv);
existing.set_unnamed_addr(unnamed_addr);
existing.set_address_space(address_space);
existing.set_attributes(attrs);
existing
} else {
let mut fb = self
.module
.function_builder::<llvmkit_ir::Dyn>(name.clone(), fn_ty)
.linkage(linkage)
.visibility(visibility)
.dll_storage_class(dll_storage_class)
.dso_locality(dso_locality)
.calling_conv(calling_conv)
.unnamed_addr(unnamed_addr)
.address_space(address_space)
.attribute_storage(attrs);
for (slot, p) in param_names.iter().enumerate() {
if let Some(ParamName::Named(n)) = p {
let slot_u32 = u32::try_from(slot).map_err(|_| ParseError::Expected {
expected: "parameter slot fits in u32".into(),
loc: DiagLoc::span(decl_loc),
})?;
fb = fb.param_name(slot_u32, n.clone());
}
}
fb.build().map_err(|e| ParseError::Expected {
expected: format!("valid function definition: {e}"),
loc: DiagLoc::span(decl_loc),
})?
};
for group in suffix.attr_groups {
f.add_function_attr_group(group);
}
if let Some(section) = suffix.section {
f.set_section(Some(section));
}
if let Some(partition) = suffix.partition {
f.set_partition(Some(partition));
}
if let Some(comdat_name) = suffix.comdat {
let name = comdat_name.unwrap_or_else(|| f.name().to_owned());
let comdat = self.module.get_or_insert_comdat(name);
f.set_comdat(Some(comdat))
.map_err(|e| self.builder_err("function comdat", e))?;
}
f.set_align(suffix.align);
if let Some(gc) = suffix.gc {
f.set_gc(Some(gc));
}
if let Some(prefix_data) = suffix.prefix_data {
f.set_prefix_data(Some(prefix_data))
.map_err(|e| self.builder_err("function prefix", e))?;
}
if let Some(prologue_data) = suffix.prologue_data {
f.set_prologue_data(Some(prologue_data))
.map_err(|e| self.builder_err("function prologue", e))?;
}
if let Some(personality_fn) = suffix.personality_fn {
f.set_personality_fn(Some(personality_fn))
.map_err(|e| self.builder_err("function personality", e))?;
}
for (kind, id) in suffix.metadata {
f.set_metadata(kind, id);
}
self.expect_punct(PunctKind::LBrace, "'{' to open function body")?;
let mut state = PerFunctionState::new(f);
for (slot, name) in param_names.into_iter().enumerate() {
let slot_u32 = u32::try_from(slot).map_err(|_| ParseError::Expected {
expected: "parameter slot fits in u32".into(),
loc: DiagLoc::span(decl_loc),
})?;
let arg = f.param(slot_u32).map_err(|e| ParseError::Expected {
expected: format!("function parameter slot {slot}: {e}"),
loc: DiagLoc::span(decl_loc),
})?;
let v = arg.as_value();
match name {
Some(ParamName::Named(n)) => {
state.local_named.insert(n, v);
}
Some(ParamName::Numbered(id)) => {
state.next_unnamed_value_id = state.next_unnamed_value_id.max(id + 1);
}
None => {
let id = state.next_unnamed_value_id;
state.local_numbered.insert(id, v);
state.next_unnamed_value_id = id.saturating_add(1);
}
}
}
self.parse_function_body(&mut state)?;
state.finish(self.module)?;
self.expect_punct(PunctKind::RBrace, "'}' to close function body")?;
Ok(())
}
fn parse_function_body(&mut self, state: &mut PerFunctionState<'ctx>) -> ParseResult<()> {
loop {
match self.peek() {
Token::RBrace => break,
Token::Kw(Keyword::Uselistorder) => {
self.parse_function_use_list_order(state)?;
}
Token::LabelStr(_) => {
let label = self
.current_label_str()
.ok_or_else(|| self.expected("basic-block label"))?;
self.bump()?;
self.parse_basic_block(state, BlockHeader::Named(label))?;
}
_ => {
self.parse_basic_block(state, BlockHeader::Implicit)?;
}
}
}
Ok(())
}
fn current_label_str(&self) -> Option<String> {
match self.peek() {
Token::LabelStr(bytes) => std::str::from_utf8(bytes.as_ref()).ok().map(str::to_owned),
_ => None,
}
}
fn parse_basic_block(
&mut self,
state: &mut PerFunctionState<'ctx>,
header: BlockHeader,
) -> ParseResult<()> {
let bb_name = match header {
BlockHeader::Named(n) => n,
BlockHeader::Implicit => "".to_owned(),
};
state.defined_blocks.insert(bb_name.clone());
let bb = state.ensure_block(self.module, &bb_name);
let builder = llvmkit_ir::IRBuilder::new(self.module).position_at_end(bb);
let mut builder = Some(builder);
let mut pending_debug_records = Vec::new();
loop {
while matches!(self.peek(), Token::Hash) {
self.bump()?;
pending_debug_records.push(self.parse_debug_record(state)?);
}
match self.peek() {
Token::Instruction(crate::ll_token::Opcode::Ret) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.parse_ret(state, b)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::Unreachable) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.bump()?;
let _ = b.build_unreachable();
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::Br) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.parse_br(state, b)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::Store) => {
let b_ref = borrow_live_builder(&builder, self.loc())?;
self.bump()?;
self.parse_store(state, b_ref)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
continue;
}
Token::Instruction(crate::ll_token::Opcode::Fence) => {
let b_ref = borrow_live_builder(&builder, self.loc())?;
self.bump()?;
self.parse_fence(b_ref)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
continue;
}
Token::Instruction(crate::ll_token::Opcode::Switch) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.parse_switch(state, b)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::IndirectBr) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.parse_indirectbr(state, b)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::Invoke) => {
let b = take_live_builder(&mut builder, self.loc())?;
let result_loc = self.loc();
let result_name = self.parse_lhs_before_invoke()?;
let v = self.parse_invoke(state, b, &result_name)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
if let Some(val) = v {
state.bind_local(&result_name, val, result_loc)?;
}
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::Resume) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.bump()?;
self.parse_resume(state, b)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::CleanupRet) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.bump()?;
self.parse_cleanupret(state, b)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::CatchRet) => {
let b = take_live_builder(&mut builder, self.loc())?;
self.bump()?;
self.parse_catchret(state, b)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::CatchSwitch) => {
let b = take_live_builder(&mut builder, self.loc())?;
let result_loc = self.loc();
let result_name = self.parse_lhs_assignment()?;
let v = self.parse_catchswitch(state, b, &result_name)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
state.bind_local(&result_name, v, result_loc)?;
return Ok(());
}
Token::Instruction(crate::ll_token::Opcode::CallBr) => {
let b = take_live_builder(&mut builder, self.loc())?;
let result_loc = self.loc();
let result_name = self.parse_lhs_assignment()?;
let v = self.parse_callbr(state, b, &result_name)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
state.bind_local(&result_name, v, result_loc)?;
return Ok(());
}
_ => {}
}
let result_name = self.parse_lhs_assignment()?;
let result_loc = self.loc();
if matches!(
self.peek(),
Token::Kw(Keyword::Tail | Keyword::Musttail | Keyword::Notail)
) {
let b_ref = borrow_live_builder(&builder, self.loc())?;
let value = self.parse_call(state, b_ref, &result_name)?;
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
state.bind_local(&result_name, value, result_loc)?;
continue;
}
let opcode = match self.peek() {
Token::Instruction(op) => *op,
_ => return Err(self.expected("instruction opcode")),
};
self.bump()?;
let b_ref = borrow_live_builder(&builder, self.loc())?;
let value = match opcode {
crate::ll_token::Opcode::Add => {
self.parse_int_binop(state, b_ref, IntBinOp::Add, &result_name)?
}
crate::ll_token::Opcode::Sub => {
self.parse_int_binop(state, b_ref, IntBinOp::Sub, &result_name)?
}
crate::ll_token::Opcode::Mul => {
self.parse_int_binop(state, b_ref, IntBinOp::Mul, &result_name)?
}
crate::ll_token::Opcode::UDiv => {
self.parse_int_binop(state, b_ref, IntBinOp::UDiv, &result_name)?
}
crate::ll_token::Opcode::SDiv => {
self.parse_int_binop(state, b_ref, IntBinOp::SDiv, &result_name)?
}
crate::ll_token::Opcode::URem => {
self.parse_int_binop(state, b_ref, IntBinOp::URem, &result_name)?
}
crate::ll_token::Opcode::SRem => {
self.parse_int_binop(state, b_ref, IntBinOp::SRem, &result_name)?
}
crate::ll_token::Opcode::Shl => {
self.parse_int_binop(state, b_ref, IntBinOp::Shl, &result_name)?
}
crate::ll_token::Opcode::LShr => {
self.parse_int_binop(state, b_ref, IntBinOp::LShr, &result_name)?
}
crate::ll_token::Opcode::AShr => {
self.parse_int_binop(state, b_ref, IntBinOp::AShr, &result_name)?
}
crate::ll_token::Opcode::And => {
self.parse_int_binop(state, b_ref, IntBinOp::And, &result_name)?
}
crate::ll_token::Opcode::Or => {
self.parse_int_binop(state, b_ref, IntBinOp::Or, &result_name)?
}
crate::ll_token::Opcode::Xor => {
self.parse_int_binop(state, b_ref, IntBinOp::Xor, &result_name)?
}
crate::ll_token::Opcode::ICmp => self.parse_icmp(state, b_ref, &result_name)?,
crate::ll_token::Opcode::Trunc => {
self.parse_int_cast(state, b_ref, IntCast::Trunc, &result_name)?
}
crate::ll_token::Opcode::ZExt => {
self.parse_int_cast(state, b_ref, IntCast::ZExt, &result_name)?
}
crate::ll_token::Opcode::SExt => {
self.parse_int_cast(state, b_ref, IntCast::SExt, &result_name)?
}
crate::ll_token::Opcode::PtrToInt => {
self.parse_ptr_to_int(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::IntToPtr => {
self.parse_int_to_ptr(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::FNeg => self.parse_fneg(state, b_ref, &result_name)?,
crate::ll_token::Opcode::FAdd => {
self.parse_fp_binop(state, b_ref, FpBinOp::Add, &result_name)?
}
crate::ll_token::Opcode::FSub => {
self.parse_fp_binop(state, b_ref, FpBinOp::Sub, &result_name)?
}
crate::ll_token::Opcode::FMul => {
self.parse_fp_binop(state, b_ref, FpBinOp::Mul, &result_name)?
}
crate::ll_token::Opcode::FDiv => {
self.parse_fp_binop(state, b_ref, FpBinOp::Div, &result_name)?
}
crate::ll_token::Opcode::FRem => {
self.parse_fp_binop(state, b_ref, FpBinOp::Rem, &result_name)?
}
crate::ll_token::Opcode::FCmp => self.parse_fcmp(state, b_ref, &result_name)?,
crate::ll_token::Opcode::Alloca => self.parse_alloca(b_ref, &result_name)?,
crate::ll_token::Opcode::Load => self.parse_load(state, b_ref, &result_name)?,
crate::ll_token::Opcode::GetElementPtr => {
self.parse_gep(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::Select => self.parse_select(state, b_ref, &result_name)?,
crate::ll_token::Opcode::FPToUI => {
self.parse_fp_to_int(state, b_ref, FpToInt::FpToUI, &result_name)?
}
crate::ll_token::Opcode::FPToSI => {
self.parse_fp_to_int(state, b_ref, FpToInt::FpToSI, &result_name)?
}
crate::ll_token::Opcode::UIToFP => {
self.parse_int_to_fp(state, b_ref, IntToFp::UIToFp, &result_name)?
}
crate::ll_token::Opcode::SIToFP => {
self.parse_int_to_fp(state, b_ref, IntToFp::SIToFp, &result_name)?
}
crate::ll_token::Opcode::AddrSpaceCast => {
self.parse_addrspace_cast(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::BitCast => {
self.parse_bitcast(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::FPTrunc => {
self.parse_fptrunc(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::FPExt => self.parse_fpext(state, b_ref, &result_name)?,
crate::ll_token::Opcode::PtrToAddr => {
self.parse_ptrtoaddr(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::ExtractElement => {
self.parse_extractelement(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::InsertElement => {
self.parse_insertelement(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::ShuffleVector => {
self.parse_shufflevector(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::ExtractValue => {
self.parse_extractvalue(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::InsertValue => {
self.parse_insertvalue(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::Phi => self.parse_phi(state, b_ref, &result_name)?,
crate::ll_token::Opcode::Call => self.parse_call(state, b_ref, &result_name)?,
crate::ll_token::Opcode::VAArg => self.parse_vaarg(state, b_ref, &result_name)?,
crate::ll_token::Opcode::Freeze => self.parse_freeze(state, b_ref, &result_name)?,
crate::ll_token::Opcode::AtomicCmpXchg => {
self.parse_cmpxchg(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::AtomicRMW => {
self.parse_atomicrmw(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::LandingPad => {
self.parse_landingpad(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::CleanupPad => {
self.parse_cleanuppad(state, b_ref, &result_name)?
}
crate::ll_token::Opcode::CatchPad => {
self.parse_catchpad(state, b_ref, &result_name)?
}
_ => {
return Err(ParseError::Expected {
expected: format!(
"instruction opcode supported by this parser (got {opcode:?})"
),
loc: DiagLoc::span(result_loc),
});
}
};
self.finish_trailing_metadata(bb, &mut pending_debug_records)?;
state.bind_local(&result_name, value, result_loc)?;
}
}
fn parse_lhs_assignment(&mut self) -> ParseResult<LocalLhs> {
match self.peek() {
Token::LocalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("local SSA name"))?;
self.bump()?;
self.expect_punct(PunctKind::Equal, "'=' after local SSA name")?;
Ok(LocalLhs::Named(name))
}
Token::LocalVarId(id) => {
let id = *id;
self.bump()?;
self.expect_punct(PunctKind::Equal, "'=' after local SSA id")?;
Ok(LocalLhs::Numbered(id))
}
_ => Ok(LocalLhs::None),
}
}
fn parse_ret(
&mut self,
state: &PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
self.bump()?; if let Token::PrimitiveType(crate::ll_token::PrimitiveTy::Void) = self.peek() {
self.bump()?;
let _ = b.build_ret_void().map_err(|e| ParseError::Expected {
expected: format!("valid ret void: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
return Ok(());
}
let ty = self.parse_type(false)?;
let v = self.parse_value(state, ty)?;
let _ = b.build_ret(v).map_err(|e| ParseError::Expected {
expected: format!("valid ret: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
Ok(())
}
fn parse_br(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
self.bump()?; if matches!(
self.peek(),
Token::PrimitiveType(crate::ll_token::PrimitiveTy::Label)
) {
self.bump()?;
let target = self.parse_block_ref(state)?;
let _ = b.build_br(target).map_err(|e| ParseError::Expected {
expected: format!("valid br: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
return Ok(());
}
let cond_ty = self.parse_type(false)?;
if !matches!(
cond_ty.into_type_enum(),
AnyTypeEnum::Int(t) if t.bit_width() == 1
) {
return Err(self.expected("'i1' condition for cond-br"));
}
let cond_v = self.parse_value(state, cond_ty)?;
self.expect_punct(PunctKind::Comma, "',' after br condition")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' for then-target",
)?;
let then_bb = self.parse_block_ref(state)?;
self.expect_punct(PunctKind::Comma, "',' between br targets")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' for else-target",
)?;
let else_bb = self.parse_block_ref(state)?;
let cond_iv: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = cond_v
.try_into()
.map_err(|_| self.expected("i1 condition"))?;
let cond_i1: llvmkit_ir::IntValue<'ctx, bool> = cond_iv
.try_into()
.map_err(|_| self.expected("i1 condition"))?;
let _ = b
.build_cond_br(cond_i1, then_bb, else_bb)
.map_err(|e| ParseError::Expected {
expected: format!("valid cond_br: {e}"),
loc: DiagLoc::span(self.loc()),
})?;
Ok(())
}
fn parse_int_binop(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
op: IntBinOp,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
use llvmkit_ir::instr_types::{
AShrFlags, AddFlags, LShrFlags, MulFlags, OrFlags, SDivFlags, ShlFlags, SubFlags,
UDivFlags,
};
let nuw = matches!(
op,
IntBinOp::Add | IntBinOp::Sub | IntBinOp::Mul | IntBinOp::Shl
) && self.eat_keyword(Keyword::Nuw)?;
let nsw = matches!(
op,
IntBinOp::Add | IntBinOp::Sub | IntBinOp::Mul | IntBinOp::Shl
) && self.eat_keyword(Keyword::Nsw)?;
let exact = matches!(
op,
IntBinOp::UDiv | IntBinOp::SDiv | IntBinOp::LShr | IntBinOp::AShr
) && self.eat_keyword(Keyword::Exact)?;
let disjoint_or = matches!(op, IntBinOp::Or) && self.eat_keyword(Keyword::Disjoint)?;
let ty = self.parse_type(false)?;
let lhs_v = self.parse_value(state, ty)?;
self.expect_punct(PunctKind::Comma, "',' between binop operands")?;
let rhs_v = self.parse_value_no_type(state, ty)?;
let lhs: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = lhs_v
.try_into()
.map_err(|_| self.expected("integer-typed lhs"))?;
let rhs: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = rhs_v
.try_into()
.map_err(|_| self.expected("integer-typed rhs"))?;
let name = result_name.as_str();
let v = match op {
IntBinOp::Add => {
let mut flags = AddFlags::new();
if nuw {
flags = flags.nuw();
}
if nsw {
flags = flags.nsw();
}
b.build_int_add_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("add", e))?
.as_value()
}
IntBinOp::Sub => {
let mut flags = SubFlags::new();
if nuw {
flags = flags.nuw();
}
if nsw {
flags = flags.nsw();
}
b.build_int_sub_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("sub", e))?
.as_value()
}
IntBinOp::Mul => {
let mut flags = MulFlags::new();
if nuw {
flags = flags.nuw();
}
if nsw {
flags = flags.nsw();
}
b.build_int_mul_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("mul", e))?
.as_value()
}
IntBinOp::Shl => {
let mut flags = ShlFlags::new();
if nuw {
flags = flags.nuw();
}
if nsw {
flags = flags.nsw();
}
b.build_int_shl_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("shl", e))?
.as_value()
}
IntBinOp::UDiv => {
let mut flags = UDivFlags::new();
if exact {
flags = flags.exact();
}
b.build_int_udiv_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("udiv", e))?
.as_value()
}
IntBinOp::SDiv => {
let mut flags = SDivFlags::new();
if exact {
flags = flags.exact();
}
b.build_int_sdiv_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("sdiv", e))?
.as_value()
}
IntBinOp::LShr => {
let mut flags = LShrFlags::new();
if exact {
flags = flags.exact();
}
b.build_int_lshr_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("lshr", e))?
.as_value()
}
IntBinOp::AShr => {
let mut flags = AShrFlags::new();
if exact {
flags = flags.exact();
}
b.build_int_ashr_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("ashr", e))?
.as_value()
}
IntBinOp::URem => b
.build_int_urem::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, name)
.map_err(|e| self.builder_err("urem", e))?
.as_value(),
IntBinOp::SRem => b
.build_int_srem::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, name)
.map_err(|e| self.builder_err("srem", e))?
.as_value(),
IntBinOp::And => b
.build_int_and::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, name)
.map_err(|e| self.builder_err("and", e))?
.as_value(),
IntBinOp::Or => {
let flags = if disjoint_or {
OrFlags::new().disjoint()
} else {
OrFlags::new()
};
b.build_int_or_with_flags::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, flags, name)
.map_err(|e| self.builder_err("or", e))?
.as_value()
}
IntBinOp::Xor => b
.build_int_xor::<llvmkit_ir::IntDyn, _, _>(lhs, rhs, name)
.map_err(|e| self.builder_err("xor", e))?
.as_value(),
};
Ok(v)
}
fn parse_icmp(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let samesign = self.eat_keyword(Keyword::Samesign)?;
let pred = match self.peek() {
Token::Kw(Keyword::Eq) => llvmkit_ir::IntPredicate::Eq,
Token::Kw(Keyword::Ne) => llvmkit_ir::IntPredicate::Ne,
Token::Kw(Keyword::Slt) => llvmkit_ir::IntPredicate::Slt,
Token::Kw(Keyword::Sle) => llvmkit_ir::IntPredicate::Sle,
Token::Kw(Keyword::Sgt) => llvmkit_ir::IntPredicate::Sgt,
Token::Kw(Keyword::Sge) => llvmkit_ir::IntPredicate::Sge,
Token::Kw(Keyword::Ult) => llvmkit_ir::IntPredicate::Ult,
Token::Kw(Keyword::Ule) => llvmkit_ir::IntPredicate::Ule,
Token::Kw(Keyword::Ugt) => llvmkit_ir::IntPredicate::Ugt,
Token::Kw(Keyword::Uge) => llvmkit_ir::IntPredicate::Uge,
_ => return Err(self.expected("integer compare predicate")),
};
self.bump()?;
let ty = self.parse_type(false)?;
let lhs_v = self.parse_value(state, ty)?;
self.expect_punct(PunctKind::Comma, "',' between icmp operands")?;
let rhs_v = self.parse_value_no_type(state, ty)?;
let lhs: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = lhs_v
.try_into()
.map_err(|_| self.expected("integer-typed lhs"))?;
let rhs: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = rhs_v
.try_into()
.map_err(|_| self.expected("integer-typed rhs"))?;
let name = result_name.as_str();
let flags = if samesign {
llvmkit_ir::instr_types::ICmpFlags::new().samesign()
} else {
llvmkit_ir::instr_types::ICmpFlags::new()
};
let r = b
.build_int_cmp_with_flags_dyn(flags, pred, lhs, rhs, name)
.map_err(|e| self.builder_err("icmp", e))?;
Ok(r.as_value())
}
fn parse_int_cast(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
op: IntCast,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let trunc_nuw = matches!(op, IntCast::Trunc) && self.eat_keyword(Keyword::Nuw)?;
let trunc_nsw = matches!(op, IntCast::Trunc) && self.eat_keyword(Keyword::Nsw)?;
let zext_nneg = matches!(op, IntCast::ZExt) && self.eat_keyword(Keyword::Nneg)?;
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(
Keyword::To,
"'to' between cast operand and destination type",
)?;
let dst_ty = self.parse_type(false)?;
let src_int: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = src_v
.try_into()
.map_err(|_| self.expected("integer-typed cast source"))?;
let dst_int = match dst_ty.into_type_enum() {
AnyTypeEnum::Int(t) => t,
_ => return Err(self.expected("integer destination type for trunc/zext/sext")),
};
let name = result_name.as_str();
let v = match op {
IntCast::Trunc => {
let flags = llvmkit_ir::instr_types::TruncFlags::new();
let flags = if trunc_nuw { flags.nuw() } else { flags };
let flags = if trunc_nsw { flags.nsw() } else { flags };
if trunc_nuw || trunc_nsw {
b.build_trunc_with_flags_dyn(src_int, dst_int, flags, name)
} else {
b.build_trunc_dyn(src_int, dst_int, name)
}
.map_err(|e| self.builder_err("trunc", e))?
.as_value()
}
IntCast::ZExt => if zext_nneg {
b.build_zext_with_flags_dyn(
src_int,
dst_int,
llvmkit_ir::instr_types::ZExtFlags::new().nneg(),
name,
)
} else {
b.build_zext_dyn(src_int, dst_int, name)
}
.map_err(|e| self.builder_err("zext", e))?
.as_value(),
IntCast::SExt => b
.build_sext_dyn(src_int, dst_int, name)
.map_err(|e| self.builder_err("sext", e))?
.as_value(),
};
Ok(v)
}
fn parse_ptr_to_int(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in ptrtoint")?;
let dst_ty = self.parse_type(false)?;
let src_ptr: llvmkit_ir::PointerValue<'ctx> = src_v
.try_into()
.map_err(|_| self.expected("ptr-typed ptrtoint source"))?;
let dst_int = match dst_ty.into_type_enum() {
AnyTypeEnum::Int(t) => t,
_ => return Err(self.expected("integer destination type for ptrtoint")),
};
let v = b
.build_ptr_to_int(src_ptr, dst_int, result_name.as_str())
.map_err(|e| self.builder_err("ptrtoint", e))?;
Ok(v.as_value())
}
fn parse_int_to_ptr(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in inttoptr")?;
let dst_ty = self.parse_type(false)?;
let src_int: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = src_v
.try_into()
.map_err(|_| self.expected("integer-typed inttoptr source"))?;
let dst_ptr = match dst_ty.into_type_enum() {
AnyTypeEnum::Pointer(t) => t,
_ => return Err(self.expected("pointer destination type for inttoptr")),
};
let v = b
.build_int_to_ptr(src_int, dst_ptr, result_name.as_str())
.map_err(|e| self.builder_err("inttoptr", e))?;
Ok(v.as_value())
}
fn parse_fneg(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let fmf = self.parse_optional_fmf()?;
let ty = self.parse_type(false)?;
let v = self.parse_value(state, ty)?;
let f: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = v
.try_into()
.map_err(|_| self.expected("float-typed fneg operand"))?;
let r = if fmf.is_empty() {
b.build_float_neg::<llvmkit_ir::FloatDyn, _>(f, result_name.as_str())
} else {
b.build_float_neg_with_flags::<llvmkit_ir::FloatDyn, _>(f, fmf, result_name.as_str())
}
.map_err(|e| self.builder_err("fneg", e))?;
Ok(r.as_value())
}
fn parse_fp_binop(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
op: FpBinOp,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let fmf = self.parse_optional_fmf()?;
let ty = self.parse_type(false)?;
let lhs_v = self.parse_value(state, ty)?;
self.expect_punct(PunctKind::Comma, "',' between FP binop operands")?;
let rhs_v = self.parse_value_no_type(state, ty)?;
let lhs: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = lhs_v
.try_into()
.map_err(|_| self.expected("float-typed lhs"))?;
let rhs: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = rhs_v
.try_into()
.map_err(|_| self.expected("float-typed rhs"))?;
let name = result_name.as_str();
let v = match op {
FpBinOp::Add => if fmf.is_empty() {
b.build_fp_add::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, name)
} else {
b.build_fp_add_fmf::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, fmf, name)
}
.map_err(|e| self.builder_err("fadd", e))?
.as_value(),
FpBinOp::Sub => if fmf.is_empty() {
b.build_fp_sub::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, name)
} else {
b.build_fp_sub_fmf::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, fmf, name)
}
.map_err(|e| self.builder_err("fsub", e))?
.as_value(),
FpBinOp::Mul => if fmf.is_empty() {
b.build_fp_mul::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, name)
} else {
b.build_fp_mul_fmf::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, fmf, name)
}
.map_err(|e| self.builder_err("fmul", e))?
.as_value(),
FpBinOp::Div => if fmf.is_empty() {
b.build_fp_div::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, name)
} else {
b.build_fp_div_fmf::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, fmf, name)
}
.map_err(|e| self.builder_err("fdiv", e))?
.as_value(),
FpBinOp::Rem => if fmf.is_empty() {
b.build_fp_rem::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, name)
} else {
b.build_fp_rem_fmf::<llvmkit_ir::FloatDyn, _, _>(lhs, rhs, fmf, name)
}
.map_err(|e| self.builder_err("frem", e))?
.as_value(),
};
Ok(v)
}
fn parse_fcmp(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let fmf = self.parse_optional_fmf()?;
use llvmkit_ir::FloatPredicate as P;
let pred = match self.peek() {
Token::Kw(Keyword::Oeq) => P::Oeq,
Token::Kw(Keyword::Ogt) => P::Ogt,
Token::Kw(Keyword::Oge) => P::Oge,
Token::Kw(Keyword::Olt) => P::Olt,
Token::Kw(Keyword::Ole) => P::Ole,
Token::Kw(Keyword::One) => P::One,
Token::Kw(Keyword::Ord) => P::Ord,
Token::Kw(Keyword::Uno) => P::Uno,
Token::Kw(Keyword::Ueq) => P::Ueq,
Token::Kw(Keyword::Ugt) => P::Ugt,
Token::Kw(Keyword::Uge) => P::Uge,
Token::Kw(Keyword::Ult) => P::Ult,
Token::Kw(Keyword::Ule) => P::Ule,
Token::Kw(Keyword::Une) => P::Une,
Token::Kw(Keyword::True) => P::True,
Token::Kw(Keyword::False) => P::False,
_ => return Err(self.expected("floating-point compare predicate")),
};
self.bump()?;
let ty = self.parse_type(false)?;
let lhs_v = self.parse_value(state, ty)?;
self.expect_punct(PunctKind::Comma, "',' between fcmp operands")?;
let rhs_v = self.parse_value_no_type(state, ty)?;
let lhs: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = lhs_v
.try_into()
.map_err(|_| self.expected("float-typed lhs"))?;
let rhs: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = rhs_v
.try_into()
.map_err(|_| self.expected("float-typed rhs"))?;
let name = result_name.as_str();
let r = if fmf.is_empty() {
b.build_fp_cmp::<llvmkit_ir::FloatDyn, _, _>(pred, lhs, rhs, name)
.map_err(|e| self.builder_err("fcmp", e))?
.as_value()
} else {
b.build_fp_cmp_fmf::<llvmkit_ir::FloatDyn, _, _>(pred, lhs, rhs, fmf, name)
.map_err(|e| self.builder_err("fcmp", e))?
.as_value()
};
Ok(r)
}
fn parse_alloca(
&mut self,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let ty = self.parse_type(false)?;
let align = self.parse_optional_comma_align()?;
let r = match align {
Some(a) => b
.build_alloca_with_align(ty, a, result_name.as_str())
.map_err(|e| self.builder_err("alloca", e))?,
None => b
.build_alloca(ty, result_name.as_str())
.map_err(|e| self.builder_err("alloca", e))?,
};
Ok(r.as_value())
}
fn parse_load(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let is_atomic = self.eat_keyword(Keyword::Atomic)?;
let volatile = self.eat_keyword(Keyword::Volatile)?;
let ty = self.parse_type(false)?;
self.expect_punct(PunctKind::Comma, "',' between load type and pointer")?;
let ptr_ty = self.parse_type(false)?;
let ptr_v = self.parse_value(state, ptr_ty)?;
let ptr: llvmkit_ir::PointerValue<'ctx> = ptr_v
.try_into()
.map_err(|_| self.expected("ptr-typed load operand"))?;
if is_atomic {
let sync_scope = self.parse_optional_syncscope()?;
let ordering = self.parse_atomic_ordering("atomic ordering")?;
self.expect_punct(PunctKind::Comma, "',' after atomic ordering")?;
let align = self.parse_align_val()?;
let config = llvmkit_ir::instr_types::AtomicLoadConfig {
ordering,
sync_scope,
align,
volatile,
};
let v = b
.build_load_atomic(ty, ptr, config, result_name.as_str())
.map_err(|e| self.builder_err("load", e))?;
Ok(v)
} else {
let align = self.parse_optional_comma_align()?;
let v = if volatile {
match align {
Some(a) => b.build_load_volatile_with_align(ty, ptr, a, result_name.as_str()),
None => b.build_load_volatile(ty, ptr, result_name.as_str()),
}
} else {
match align {
Some(a) => b.build_load_with_align(ty, ptr, a, result_name.as_str()),
None => b.build_load(ty, ptr, result_name.as_str()),
}
}
.map_err(|e| self.builder_err("load", e))?;
Ok(v)
}
}
fn parse_store(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
let is_atomic = self.eat_keyword(Keyword::Atomic)?;
let volatile = self.eat_keyword(Keyword::Volatile)?;
let val_ty = self.parse_type(false)?;
let val_v = self.parse_value(state, val_ty)?;
self.expect_punct(PunctKind::Comma, "',' between store value and pointer")?;
let ptr_ty = self.parse_type(false)?;
let ptr_v = self.parse_value(state, ptr_ty)?;
let ptr: llvmkit_ir::PointerValue<'ctx> = ptr_v
.try_into()
.map_err(|_| self.expected("ptr-typed store target"))?;
if is_atomic {
let sync_scope = self.parse_optional_syncscope()?;
let ordering = self.parse_atomic_ordering("atomic ordering")?;
self.expect_punct(PunctKind::Comma, "',' after atomic ordering")?;
let align = self.parse_align_val()?;
let config = llvmkit_ir::instr_types::AtomicStoreConfig {
ordering,
sync_scope,
align,
volatile,
};
b.build_store_atomic(val_v, ptr, config)
.map_err(|e| self.builder_err("store", e))?;
} else {
let align = self.parse_optional_comma_align()?;
match (volatile, align) {
(true, Some(a)) => b.build_store_volatile_with_align(val_v, ptr, a),
(true, None) => b.build_store_volatile(val_v, ptr),
(false, Some(a)) => b.build_store_with_align(val_v, ptr, a),
(false, None) => b.build_store(val_v, ptr),
}
.map_err(|e| self.builder_err("store", e))?;
}
Ok(())
}
fn parse_gep(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let inbounds = self.eat_keyword(Keyword::Inbounds)?;
let nuw = self.eat_keyword(Keyword::Nuw)?;
let nusw = self.eat_keyword(Keyword::Nusw)?;
let source_ty = self.parse_type(false)?;
self.expect_punct(PunctKind::Comma, "',' after GEP source type")?;
let ptr_ty = self.parse_type(false)?;
let ptr_v = self.parse_value(state, ptr_ty)?;
let ptr: llvmkit_ir::PointerValue<'ctx> = ptr_v
.try_into()
.map_err(|_| self.expected("ptr-typed GEP base"))?;
let mut indices: Vec<llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn>> = Vec::new();
while self.eat_punct(PunctKind::Comma)? {
let idx_ty = self.parse_type(false)?;
let idx_v = self.parse_value(state, idx_ty)?;
let idx: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = idx_v
.try_into()
.map_err(|_| self.expected("integer GEP index"))?;
indices.push(idx);
}
let name = result_name.as_str();
let flags = {
let mut f = if inbounds {
llvmkit_ir::GepNoWrapFlags::inbounds()
} else {
llvmkit_ir::GepNoWrapFlags::empty()
};
if nuw {
f |= llvmkit_ir::GepNoWrapFlags::NUW;
}
if nusw {
f |= llvmkit_ir::GepNoWrapFlags::NUSW;
}
f
};
let v = b
.build_gep_with_flags(source_ty, ptr, indices, flags, name)
.map_err(|e| self.builder_err("getelementptr", e))?;
Ok(v.as_value())
}
fn parse_select(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let cond_ty = self.parse_type(false)?;
let cond_v = self.parse_value(state, cond_ty)?;
let cond_iv: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = cond_v
.try_into()
.map_err(|_| self.expected("integer-typed select condition"))?;
let cond_i1: llvmkit_ir::IntValue<'ctx, bool> = cond_iv
.try_into()
.map_err(|_| self.expected("i1 select condition"))?;
self.expect_punct(PunctKind::Comma, "',' after select condition")?;
let true_ty = self.parse_type(false)?;
let true_v = self.parse_value(state, true_ty)?;
self.expect_punct(PunctKind::Comma, "',' between select arms")?;
let false_ty = self.parse_type(false)?;
let false_v = self.parse_value(state, false_ty)?;
if true_ty != false_ty {
return Err(self.expected("matching arm types in select"));
}
let name = result_name.as_str();
let v = match true_ty.into_type_enum() {
AnyTypeEnum::Int(_) => {
let t: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = true_v
.try_into()
.map_err(|_| self.expected("int-typed select arm"))?;
let f: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = false_v
.try_into()
.map_err(|_| self.expected("int-typed select arm"))?;
b.build_select(cond_i1, t, f, name)
.map_err(|e| self.builder_err("select", e))?
.as_value()
}
AnyTypeEnum::Float(_) => {
let t: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = true_v
.try_into()
.map_err(|_| self.expected("float-typed select arm"))?;
let f: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = false_v
.try_into()
.map_err(|_| self.expected("float-typed select arm"))?;
b.build_select(cond_i1, t, f, name)
.map_err(|e| self.builder_err("select", e))?
.as_value()
}
AnyTypeEnum::Pointer(_) => {
let t: llvmkit_ir::PointerValue<'ctx> = true_v
.try_into()
.map_err(|_| self.expected("ptr-typed select arm"))?;
let f: llvmkit_ir::PointerValue<'ctx> = false_v
.try_into()
.map_err(|_| self.expected("ptr-typed select arm"))?;
b.build_select(cond_i1, t, f, name)
.map_err(|e| self.builder_err("select", e))?
.as_value()
}
_ => {
return Err(
self.expected("select arm category supported by this session (int/fp/ptr)")
);
}
};
Ok(v)
}
fn parse_fp_to_int(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
op: FpToInt,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in fp->int cast")?;
let dst_ty = self.parse_type(false)?;
let src_fp: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = src_v
.try_into()
.map_err(|_| self.expected("float-typed source for fp->int cast"))?;
let dst_int = match dst_ty.into_type_enum() {
AnyTypeEnum::Int(t) => t,
_ => return Err(self.expected("integer destination for fp->int cast")),
};
let name = result_name.as_str();
let v = match op {
FpToInt::FpToSI => b
.build_fp_to_si(src_fp, dst_int, name)
.map_err(|e| self.builder_err("fptosi", e))?
.as_value(),
FpToInt::FpToUI => b
.build_fp_to_ui(src_fp, dst_int, name)
.map_err(|e| self.builder_err("fptoui", e))?
.as_value(),
};
Ok(v)
}
fn parse_int_to_fp(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
op: IntToFp,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let nneg = matches!(op, IntToFp::UIToFp) && self.eat_keyword(Keyword::Nneg)?;
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in int->fp cast")?;
let dst_ty = self.parse_type(false)?;
let src_int: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = src_v
.try_into()
.map_err(|_| self.expected("integer-typed source for int->fp cast"))?;
let dst_fp = match dst_ty.into_type_enum() {
AnyTypeEnum::Float(t) => t,
_ => return Err(self.expected("float destination for int->fp cast")),
};
let name = result_name.as_str();
let v = match op {
IntToFp::SIToFp => b
.build_si_to_fp(src_int, dst_fp, name)
.map_err(|e| self.builder_err("sitofp", e))?
.as_value(),
IntToFp::UIToFp => {
if nneg {
b.build_ui_to_fp_with_flags_dyn(
src_int,
dst_fp,
llvmkit_ir::instr_types::UIToFpFlags::new().nneg(),
name,
)
.map_err(|e| self.builder_err("uitofp", e))?
.as_value()
} else {
b.build_ui_to_fp(src_int, dst_fp, name)
.map_err(|e| self.builder_err("uitofp", e))?
.as_value()
}
}
};
Ok(v)
}
fn parse_addrspace_cast(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in addrspacecast")?;
let dst_ty = self.parse_type(false)?;
let src_ptr: llvmkit_ir::PointerValue<'ctx> = src_v
.try_into()
.map_err(|_| self.expected("ptr-typed source for addrspacecast"))?;
let dst_ptr = match dst_ty.into_type_enum() {
AnyTypeEnum::Pointer(t) => t,
_ => return Err(self.expected("ptr destination for addrspacecast")),
};
let v = b
.build_addrspace_cast(src_ptr, dst_ptr, result_name.as_str())
.map_err(|e| self.builder_err("addrspacecast", e))?;
Ok(v.as_value())
}
fn parse_bitcast(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in bitcast")?;
let dst_ty = self.parse_type(false)?;
let name = result_name.as_str();
let v = b
.build_bitcast_dyn(src_v, dst_ty, name)
.map_err(|e| self.builder_err("bitcast", e))?;
Ok(v)
}
fn parse_fptrunc(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in fptrunc")?;
let dst_ty = self.parse_type(false)?;
let sv: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = src_v
.try_into()
.map_err(|_| self.expected("float-typed source for fptrunc"))?;
let df = match dst_ty.into_type_enum() {
AnyTypeEnum::Float(t) => t,
_ => return Err(self.expected("float destination type for fptrunc")),
};
let v = b
.build_fp_trunc_dyn(sv, df, result_name.as_str())
.map_err(|e| self.builder_err("fptrunc", e))?;
Ok(v.as_value())
}
fn parse_fpext(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let src_ty = self.parse_type(false)?;
let src_v = self.parse_value(state, src_ty)?;
self.expect_keyword(Keyword::To, "'to' in fpext")?;
let dst_ty = self.parse_type(false)?;
let sv: llvmkit_ir::FloatValue<'ctx, llvmkit_ir::FloatDyn> = src_v
.try_into()
.map_err(|_| self.expected("float-typed source for fpext"))?;
let df = match dst_ty.into_type_enum() {
AnyTypeEnum::Float(t) => t,
_ => return Err(self.expected("float destination type for fpext")),
};
let v = b
.build_fp_ext_dyn(sv, df, result_name.as_str())
.map_err(|e| self.builder_err("fpext", e))?;
Ok(v.as_value())
}
fn parse_ptrtoaddr(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
self.parse_ptr_to_int(state, b, result_name)
}
fn parse_extractelement(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let vec_ty = self.parse_type(false)?;
let vec_v = self.parse_value(state, vec_ty)?;
self.expect_punct(PunctKind::Comma, "',' in extractelement")?;
let idx_ty = self.parse_type(false)?;
let idx_v = self.parse_value(state, idx_ty)?;
let idx: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = idx_v
.try_into()
.map_err(|_| self.expected("integer index for extractelement"))?;
let v = b
.build_extract_element(vec_v, idx, result_name.as_str())
.map_err(|e| self.builder_err("extractelement", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_insertelement(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let vec_ty = self.parse_type(false)?;
let vec_v = self.parse_value(state, vec_ty)?;
self.expect_punct(PunctKind::Comma, "',' after vector in insertelement")?;
let elt_ty = self.parse_type(false)?;
let elt_v = self.parse_value(state, elt_ty)?;
self.expect_punct(PunctKind::Comma, "',' after element in insertelement")?;
let idx_ty = self.parse_type(false)?;
let idx_v = self.parse_value(state, idx_ty)?;
let idx: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = idx_v
.try_into()
.map_err(|_| self.expected("integer index for insertelement"))?;
let v = b
.build_insert_element(vec_v, elt_v, idx, result_name.as_str())
.map_err(|e| self.builder_err("insertelement", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_shufflevector(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let v1_ty = self.parse_type(false)?;
let v1 = self.parse_value(state, v1_ty)?;
self.expect_punct(PunctKind::Comma, "',' after v1 in shufflevector")?;
let v2_ty = self.parse_type(false)?;
let v2 = self.parse_value(state, v2_ty)?;
self.expect_punct(PunctKind::Comma, "',' before mask in shufflevector")?;
let mask = self.parse_shuffle_mask()?;
let v = b
.build_shuffle_vector(v1, v2, &mask, result_name.as_str())
.map_err(|e| self.builder_err("shufflevector", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_shuffle_mask(&mut self) -> ParseResult<Vec<i32>> {
use llvmkit_ir::instr_types::POISON_MASK_ELEM;
if matches!(self.peek(), Token::Kw(Keyword::Poison)) {
self.bump()?;
return Ok(vec![POISON_MASK_ELEM]);
}
self.expect_punct(PunctKind::Less, "'<' to open shuffle mask")?;
if matches!(
self.peek(),
Token::IntegerLit(_) | Token::Kw(Keyword::Vscale)
) {
let _mask_ty = self.parse_array_or_vector_after_open(true)?;
self.expect_punct(PunctKind::Less, "'<' to open shuffle mask elements")?;
}
let mut mask = Vec::new();
loop {
let _ety = self.parse_type(false).map_err(|err| match err {
ParseError::Lex(LexError::UnknownToken { span }) => ParseError::Expected {
expected: "valid shufflevector mask element".into(),
loc: DiagLoc::span(span),
},
other => other,
})?;
let elem_loc = self.loc();
let (neg, val) = self.parse_int_literal().map_err(|_| ParseError::Expected {
expected: "valid shufflevector mask element".into(),
loc: DiagLoc::span(elem_loc),
})?;
let entry = if neg {
let magnitude = i32::try_from(val).map_err(|_| ParseError::Expected {
expected: "valid shufflevector mask element".into(),
loc: DiagLoc::span(elem_loc),
})?;
magnitude
.checked_neg()
.ok_or_else(|| ParseError::Expected {
expected: "valid shufflevector mask element".into(),
loc: DiagLoc::span(elem_loc),
})?
} else {
i32::try_from(val).map_err(|_| ParseError::Expected {
expected: "valid shufflevector mask element".into(),
loc: DiagLoc::span(elem_loc),
})?
};
mask.push(entry);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
self.expect_punct(PunctKind::Greater, "'>' to close shuffle mask")?;
Ok(mask)
}
fn parse_extractvalue(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let agg_ty = self.parse_type(false)?;
let agg_v = self.parse_value(state, agg_ty)?;
let mut indices = Vec::new();
while self.eat_punct(PunctKind::Comma)? {
let idx = self.parse_uint32("extractvalue index")?;
indices.push(idx);
}
let v = b
.build_extract_value(agg_v, indices, result_name.as_str())
.map_err(|e| self.builder_err("extractvalue", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_insertvalue(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let agg_ty = self.parse_type(false)?;
let agg_v = self.parse_value(state, agg_ty)?;
self.expect_punct(PunctKind::Comma, "',' after agg in insertvalue")?;
let elt_ty = self.parse_type(false)?;
let elt_v = self.parse_value(state, elt_ty)?;
let mut indices = Vec::new();
while self.eat_punct(PunctKind::Comma)? {
let idx = self.parse_uint32("insertvalue index")?;
indices.push(idx);
}
let v = b
.build_insert_value(agg_v, elt_v, indices, result_name.as_str())
.map_err(|e| self.builder_err("insertvalue", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_phi(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let _fmf = self.parse_optional_fmf()?;
let ty = self.parse_type(false)?;
let name = result_name.as_str();
let phi_val = match ty.into_type_enum() {
AnyTypeEnum::Int(int_ty) => {
let phi = b
.build_int_phi_dyn(int_ty, name)
.map_err(|e| self.builder_err("phi", e))?;
phi.as_instruction().as_value()
}
AnyTypeEnum::Float(fp_ty) => {
let phi = b
.build_fp_phi_dyn(fp_ty, name)
.map_err(|e| self.builder_err("phi", e))?;
phi.as_instruction().as_value()
}
AnyTypeEnum::Pointer(ptr_ty) => {
let phi = b
.build_pointer_phi_in_addrspace(ptr_ty, name)
.map_err(|e| self.builder_err("phi", e))?;
phi.as_instruction().as_value()
}
_ => return Err(self.expected("phi result type must be int, float, or pointer")),
};
let mut first = true;
loop {
if !first {
if !self.eat_punct(PunctKind::Comma)? {
break;
}
if !matches!(self.peek(), Token::LSquare) {
return Err(self.expected("'[' to start phi incoming pair after ','"));
}
}
first = false;
if !matches!(self.peek(), Token::LSquare) {
break;
}
self.bump()?; let val_loc = self.loc();
let val_ref = match self.peek() {
Token::LocalVar(_) => {
let n = self
.current_str_payload()
.ok_or_else(|| self.expected("local name in phi incoming value"))?;
self.bump()?;
if let Some(v) = state.local_named.get(&n).copied() {
PhiValRef::Resolved(v)
} else {
PhiValRef::Named(n)
}
}
Token::LocalVarId(id) => {
let id = *id;
self.bump()?;
if let Some(v) = state.local_numbered.get(&id).copied() {
PhiValRef::Resolved(v)
} else {
PhiValRef::Numbered(id)
}
}
Token::IntegerLit(_) => {
let (neg, raw) = self.parse_int_literal()?;
let int_ty = match ty.into_type_enum() {
AnyTypeEnum::Int(t) => t,
_ => return Err(self.expected("integer literal only valid for int phi")),
};
let bits = if neg { raw.wrapping_neg() } else { raw };
let c = int_ty
.const_int_raw(bits, neg)
.map_err(|e| self.builder_err("phi constant", e))?;
PhiValRef::Resolved(c.as_value())
}
Token::Kw(Keyword::Zeroinitializer) => {
self.bump()?;
let v = match ty.into_type_enum() {
AnyTypeEnum::Int(t) => t.const_zero().as_value(),
AnyTypeEnum::Pointer(t) => t.const_null().as_value(),
_ => return Err(self.expected("zeroinitializer for int/ptr phi")),
};
PhiValRef::Resolved(v)
}
Token::Kw(Keyword::Null) => {
self.bump()?;
let v = match ty.into_type_enum() {
AnyTypeEnum::Pointer(t) => t.const_null().as_value(),
_ => return Err(self.expected("null only valid for pointer phi")),
};
PhiValRef::Resolved(v)
}
Token::Kw(Keyword::Undef) | Token::Kw(Keyword::Poison) => {
self.bump()?;
let v = match ty.into_type_enum() {
AnyTypeEnum::Int(t) => t.const_zero().as_value(),
AnyTypeEnum::Pointer(t) => t.const_null().as_value(),
AnyTypeEnum::Float(t) => {
state.deferred_phi.push(DeferredPhiEdge {
phi_val,
val_ref: PhiValRef::Undef,
bb_name: String::new(), loc: val_loc,
});
self.expect_punct(PunctKind::Comma, "',' in phi incoming pair")?;
let bb_name = self.parse_phi_label()?;
self.expect_punct(PunctKind::RSquare, "']' in phi incoming pair")?;
if let Some(last) = state.deferred_phi.last_mut() {
last.bb_name = bb_name;
}
let _ = t;
continue;
}
_ => return Err(self.expected("undef for int/float/ptr phi")),
};
PhiValRef::Resolved(v)
}
_ => return Err(self.expected("value in phi incoming pair")),
};
self.expect_punct(PunctKind::Comma, "',' in phi incoming pair")?;
let bb_name = self.parse_phi_label()?;
self.expect_punct(PunctKind::RSquare, "']' to close phi incoming pair")?;
match val_ref {
PhiValRef::Resolved(v) => {
let bb = state.ensure_block(self.module, &bb_name);
let tmp_b = llvmkit_ir::IRBuilder::new(self.module);
tmp_b
.phi_add_incoming_from_value(phi_val, v, bb)
.map_err(|e| self.builder_err("phi.add_incoming", e))?;
}
other => {
state.deferred_phi.push(DeferredPhiEdge {
phi_val,
val_ref: other,
bb_name,
loc: val_loc,
});
}
}
}
Ok(phi_val)
}
fn parse_phi_label(&mut self) -> ParseResult<String> {
match self.peek() {
Token::LocalVar(_) => {
let n = self
.current_str_payload()
.ok_or_else(|| self.expected("block label in phi pair"))?;
self.bump()?;
Ok(n)
}
Token::LocalVarId(id) => {
let id = *id;
self.bump()?;
Ok(format!("{id}"))
}
_ => Err(self.expected("block label in phi incoming pair")),
}
}
fn parse_call(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let tail_kind = if self.eat_keyword(Keyword::Tail)? {
llvmkit_ir::instr_types::TailCallKind::Tail
} else if self.eat_keyword(Keyword::Musttail)? {
llvmkit_ir::instr_types::TailCallKind::MustTail
} else if self.eat_keyword(Keyword::Notail)? {
llvmkit_ir::instr_types::TailCallKind::NoTail
} else {
llvmkit_ir::instr_types::TailCallKind::None
};
if matches!(
self.peek(),
Token::Instruction(crate::ll_token::Opcode::Call)
) {
self.bump()?;
}
let calling_conv = self.parse_optional_calling_conv()?;
let return_attrs = self.parse_optional_return_attrs()?;
let ret_ty = self.parse_type(true)?;
let parsed_callee = self.parse_direct_callee_ref()?;
self.expect_punct(PunctKind::LParen, "'(' in call argument list")?;
let mut args: Vec<llvmkit_ir::Value<'ctx>> = Vec::new();
let mut arg_tys: Vec<Type<'ctx>> = Vec::new();
let mut arg_attrs = Vec::new();
let mut var_args = false;
if !matches!(self.peek(), Token::RParen) {
loop {
if matches!(self.peek(), Token::DotDotDot) {
self.bump()?;
var_args = true;
break;
}
let arg_ty = self.parse_type(false)?;
let one_arg_attrs = self.parse_optional_param_attrs()?;
let arg_v = self.parse_value(state, arg_ty)?;
arg_tys.push(arg_ty);
arg_attrs.push(one_arg_attrs);
args.push(arg_v);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' to close call argument list")?;
let (function_attrs, function_attr_groups) = self.parse_optional_fn_attrs()?;
let operand_bundles = self.parse_optional_operand_bundles(state)?;
let call_attrs = llvmkit_ir::instr_types::CallAttributeData {
return_attrs,
arg_attrs: arg_attrs.into_boxed_slice(),
function_attrs,
function_attr_groups: function_attr_groups.into_boxed_slice(),
operand_bundles,
fmf: FastMathFlags::empty(),
};
let parsed_fn_ty = self.module.fn_type(ret_ty, arg_tys, var_args);
let callee = self.resolve_direct_callee(parsed_callee, parsed_fn_ty)?;
let name = result_name.as_str();
let v = match callee {
ParsedCallee::Function(callee) => {
let mut builder = b
.call_builder(callee)
.calling_conv(calling_conv)
.call_attributes(call_attrs);
builder = match tail_kind {
llvmkit_ir::instr_types::TailCallKind::None => builder,
llvmkit_ir::instr_types::TailCallKind::Tail => builder.tail(),
llvmkit_ir::instr_types::TailCallKind::MustTail => builder.must_tail(),
llvmkit_ir::instr_types::TailCallKind::NoTail => builder.no_tail(),
};
for arg in args {
builder = builder.arg(arg);
}
builder
.name(name)
.build()
.map_err(|e| self.builder_err("call", e))?
.as_instruction()
.as_value()
}
ParsedCallee::InlineAsm(asm) => {
if asm.label_constraint_count() != 0 {
return Err(self.expected("inline asm call without label constraints"));
}
b.build_inline_asm_call::<llvmkit_ir::Dyn, _, _>(asm, args, name)
.map_err(|e| self.builder_err("call", e))?
.as_instruction()
.as_value()
}
};
Ok(v)
}
fn parse_optional_calling_conv(&mut self) -> ParseResult<CallingConv> {
let cc = match self.peek() {
Token::Kw(Keyword::Ccc) => Some(CallingConv::C),
Token::Kw(Keyword::Fastcc) => Some(CallingConv::FAST),
Token::Kw(Keyword::Coldcc) => Some(CallingConv::COLD),
Token::Kw(Keyword::Anyregcc) => Some(CallingConv::ANY_REG),
Token::Kw(Keyword::PreserveMostcc) => Some(CallingConv::PRESERVE_MOST),
Token::Kw(Keyword::PreserveAllcc) => Some(CallingConv::PRESERVE_ALL),
Token::Kw(Keyword::Ghccc) => Some(CallingConv::GHC),
Token::Kw(Keyword::Swiftcc) => Some(CallingConv::SWIFT),
Token::Kw(Keyword::Swifttailcc) => Some(CallingConv::SWIFT_TAIL),
Token::Kw(Keyword::X86Stdcallcc) => Some(CallingConv::X86_STD_CALL),
Token::Kw(Keyword::X86Fastcallcc) => Some(CallingConv::X86_FAST_CALL),
Token::Kw(Keyword::X86Thiscallcc) => Some(CallingConv::X86_THIS_CALL),
Token::Kw(Keyword::X86Vectorcallcc) => Some(CallingConv::X86_VECTOR_CALL),
Token::Kw(Keyword::X86Regcallcc) => Some(CallingConv::X86_REG_CALL),
Token::Kw(Keyword::IntelOclBicc) => Some(CallingConv::INTEL_OCL_BI),
Token::Kw(Keyword::Win64cc) => Some(CallingConv::WIN64),
Token::Kw(Keyword::X86_64Sysvcc) => Some(CallingConv::X86_64_SYS_V),
Token::Kw(Keyword::Hhvmcc) => Some(CallingConv::DUMMY_HHVM),
Token::Kw(Keyword::HhvmCcc) => Some(CallingConv::DUMMY_HHVM_C),
Token::Kw(Keyword::AmdgpuVs) => Some(CallingConv::AMDGPU_VS),
Token::Kw(Keyword::AmdgpuLs) => Some(CallingConv::AMDGPU_LS),
Token::Kw(Keyword::AmdgpuHs) => Some(CallingConv::AMDGPU_HS),
Token::Kw(Keyword::AmdgpuEs) => Some(CallingConv::AMDGPU_ES),
Token::Kw(Keyword::AmdgpuGs) => Some(CallingConv::AMDGPU_GS),
Token::Kw(Keyword::AmdgpuPs) => Some(CallingConv::AMDGPU_PS),
Token::Kw(Keyword::AmdgpuCs) => Some(CallingConv::AMDGPU_CS),
Token::Kw(Keyword::AmdgpuKernel) => Some(CallingConv::AMDGPU_KERNEL),
Token::Kw(Keyword::Tailcc) => Some(CallingConv::TAIL),
Token::Kw(Keyword::CfguardCheckcc) => Some(CallingConv::CF_GUARD_CHECK),
Token::Kw(Keyword::M68kRtdcc) => Some(CallingConv::M68K_RTD),
Token::Kw(Keyword::Cc) => {
self.bump()?;
let raw = self.parse_uint32("calling convention number")?;
return CallingConv::from_raw(raw)
.ok_or_else(|| self.expected("valid calling convention number"));
}
_ => None,
};
if let Some(cc) = cc {
self.bump()?;
Ok(cc)
} else {
Ok(CallingConv::C)
}
}
fn parse_direct_callee_ref(&mut self) -> ParseResult<ParsedDirectCallee> {
let loc = self.loc();
match self.peek() {
Token::GlobalVar(_) => {
let name = self
.current_str_payload()
.ok_or_else(|| self.expected("callee function name"))?;
self.bump()?;
Ok(ParsedDirectCallee::Name { name, loc })
}
Token::GlobalId(id) => {
let id = *id;
self.bump()?;
Ok(ParsedDirectCallee::Id { id, loc })
}
Token::Kw(Keyword::Asm) => {
self.bump()?;
let has_side_effects = self.eat_keyword(Keyword::Sideeffect)?;
let is_align_stack = self.eat_keyword(Keyword::Alignstack)?;
let dialect = if self.eat_keyword(Keyword::Inteldialect)? {
llvmkit_ir::AsmDialect::Intel
} else {
llvmkit_ir::AsmDialect::ATT
};
let can_unwind = self.eat_keyword(Keyword::Unwind)?;
let asm = self.parse_string_constant("inline asm string")?;
self.expect_punct(PunctKind::Comma, "',' after inline asm string")?;
let constraints = self.parse_string_constant("inline asm constraint string")?;
Ok(ParsedDirectCallee::InlineAsm(ParsedInlineAsm {
asm,
constraints,
has_side_effects,
is_align_stack,
dialect,
can_unwind,
}))
}
_ => Err(self.expected("function name after call")),
}
}
fn resolve_direct_callee(
&mut self,
parsed: ParsedDirectCallee,
parsed_fn_ty: llvmkit_ir::FunctionType<'ctx>,
) -> ParseResult<ParsedCallee<'ctx>> {
match parsed {
ParsedDirectCallee::Name { name, loc } => {
if let Some(f) = self.module.function_by_name(&name) {
if name.starts_with("llvm.") {
self.record_intrinsic_use(name, loc, parsed_fn_ty, f, true);
}
return Ok(ParsedCallee::Function(f));
}
if name.starts_with("llvm.") {
let f = self
.module
.function_builder::<llvmkit_ir::Dyn>(name.clone(), parsed_fn_ty)
.linkage(Linkage::External)
.build()
.map_err(|_| ParseError::Expected {
expected: "intrinsic signature mismatch".into(),
loc: DiagLoc::span(loc),
})?;
self.record_intrinsic_use(name, loc, parsed_fn_ty, f, true);
return Ok(ParsedCallee::Function(f));
}
Err(ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Named(name),
loc: DiagLoc::span(loc),
})
}
ParsedDirectCallee::Id { id, loc } => self
.numbered_globals
.get(id)
.and_then(|r| match r {
GlobalRef::Function(f) => Some(*f),
_ => None,
})
.map(ParsedCallee::Function)
.ok_or_else(|| ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Global,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(loc),
}),
ParsedDirectCallee::InlineAsm(data) => Ok(ParsedCallee::InlineAsm(
self.module.inline_asm(
parsed_fn_ty,
data.asm,
data.constraints,
llvmkit_ir::InlineAsmOptions::new()
.side_effects(data.has_side_effects)
.align_stack(data.is_align_stack)
.dialect(data.dialect)
.can_unwind(data.can_unwind),
),
)),
}
}
fn record_intrinsic_use(
&mut self,
name: String,
loc: Span,
parsed_fn_ty: llvmkit_ir::FunctionType<'ctx>,
provisional: llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>,
direct_callee: bool,
) {
self.unresolved_intrinsic_uses.push(UnresolvedIntrinsicUse {
name,
loc,
parsed_fn_ty,
provisional,
direct_callee,
});
}
fn parse_lhs_before_invoke(&mut self) -> ParseResult<LocalLhs> {
self.bump()?; self.parse_lhs_assignment()
}
fn parse_vaarg(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let list_ty = self.parse_type(false)?;
let list_v = self.parse_value(state, list_ty)?;
let list_ptr: llvmkit_ir::PointerValue<'ctx> = list_v
.try_into()
.map_err(|_| self.expected("ptr-typed va_arg list operand"))?;
self.expect_punct(PunctKind::Comma, "',' in va_arg")?;
let result_ty = self.parse_type(false)?;
let v = b
.build_va_arg(list_ptr, result_ty, result_name.as_str())
.map_err(|e| self.builder_err("va_arg", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_freeze(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let ty = self.parse_type(false)?;
let v = self.parse_value(state, ty)?;
let r = b
.build_freeze(v, result_name.as_str())
.map_err(|e| self.builder_err("freeze", e))?;
Ok(r.as_instruction().as_value())
}
fn parse_switch(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
self.bump()?; let cond_ty = self.parse_type(false)?;
let cond_v = self.parse_value(state, cond_ty)?;
self.expect_punct(PunctKind::Comma, "',' after switch condition")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' for switch default",
)?;
let default_bb = self.parse_block_ref(state)?;
let (_, mut sw) = b
.build_switch(cond_v, default_bb, "")
.map_err(|e| self.builder_err("switch", e))?;
self.expect_punct(PunctKind::LSquare, "'[' to open switch case list")?;
loop {
if matches!(self.peek(), Token::RSquare) {
self.bump()?;
break;
}
let case_ty = self.parse_type(false)?;
let case_v = self.parse_value(state, case_ty)?;
let case_int: llvmkit_ir::IntValue<'ctx, llvmkit_ir::IntDyn> = case_v
.try_into()
.map_err(|_| self.expected("integer switch case value"))?;
self.expect_punct(PunctKind::Comma, "',' between case value and label")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' for switch case destination",
)?;
let case_bb = self.parse_block_ref(state)?;
sw = sw
.add_case(case_int, case_bb)
.map_err(|e| self.builder_err("switch.add_case", e))?;
}
let _ = sw.finish();
Ok(())
}
fn parse_indirectbr(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
self.bump()?; let addr_ty = self.parse_type(false)?;
let addr_v = self.parse_value(state, addr_ty)?;
self.expect_punct(PunctKind::Comma, "',' after indirectbr address")?;
let (_, mut ibr) = b
.build_indirectbr(addr_v, "")
.map_err(|e| self.builder_err("indirectbr", e))?;
self.expect_punct(
PunctKind::LSquare,
"'[' to open indirectbr destination list",
)?;
loop {
if matches!(self.peek(), Token::RSquare) {
self.bump()?;
break;
}
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' in indirectbr destination",
)?;
let dest_bb = self.parse_block_ref(state)?;
ibr = ibr
.add_destination(dest_bb)
.map_err(|e| self.builder_err("indirectbr.add_destination", e))?;
let _ = self.eat_punct(PunctKind::Comma)?;
}
let _ = ibr.finish();
Ok(())
}
fn parse_fence(&mut self, b: &ParsedBlockBuilder<'ctx>) -> ParseResult<()> {
let sync_scope = self.parse_optional_syncscope()?;
let ordering = self.parse_atomic_ordering("fence ordering")?;
let _ = b
.build_fence(ordering, sync_scope, "")
.map_err(|e| self.builder_err("fence", e))?;
Ok(())
}
fn parse_cmpxchg(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let weak = self.eat_keyword(Keyword::Weak)?;
let volatile = self.eat_keyword(Keyword::Volatile)?;
let ptr_ty = self.parse_type(false)?;
let ptr_v = self.parse_value(state, ptr_ty)?;
let ptr: llvmkit_ir::PointerValue<'ctx> = ptr_v
.try_into()
.map_err(|_| self.expected("ptr operand for cmpxchg"))?;
self.expect_punct(PunctKind::Comma, "',' in cmpxchg")?;
let cmp_ty = self.parse_type(false)?;
let cmp_v = self.parse_value(state, cmp_ty)?;
self.expect_punct(PunctKind::Comma, "',' in cmpxchg after cmp")?;
let new_ty = self.parse_type(false)?;
let new_v = self.parse_value(state, new_ty)?;
let sync_scope = self.parse_optional_syncscope()?;
let success_ord = self.parse_atomic_ordering("cmpxchg success ordering")?;
let failure_ord = self.parse_atomic_ordering("cmpxchg failure ordering")?;
let align = self.parse_optional_comma_align()?;
let config = llvmkit_ir::instr_types::AtomicCmpXchgConfig {
success_ordering: success_ord,
failure_ordering: failure_ord,
sync_scope,
flags: {
let mut f = llvmkit_ir::instr_types::CmpXchgFlags::new();
if weak {
f = f.weak();
}
if volatile {
f = f.volatile();
}
f
},
align: match align {
Some(value) => llvmkit_ir::align::MaybeAlign::from(value),
None => llvmkit_ir::align::MaybeAlign::NONE,
},
};
let v = b
.build_atomic_cmpxchg(ptr, cmp_v, new_v, config, result_name.as_str())
.map_err(|e| self.builder_err("cmpxchg", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_atomicrmw(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let volatile = self.eat_keyword(Keyword::Volatile)?;
let op = self.parse_atomicrmw_op()?;
let ptr_ty = self.parse_type(false)?;
let ptr_v = self.parse_value(state, ptr_ty)?;
let ptr: llvmkit_ir::PointerValue<'ctx> = ptr_v
.try_into()
.map_err(|_| self.expected("ptr operand for atomicrmw"))?;
self.expect_punct(PunctKind::Comma, "',' in atomicrmw")?;
let val_ty = self.parse_type(false)?;
let val_v = self.parse_value(state, val_ty)?;
let sync_scope = self.parse_optional_syncscope()?;
let ordering = self.parse_atomic_ordering("atomicrmw ordering")?;
let align = self.parse_optional_comma_align()?;
let config = llvmkit_ir::instr_types::AtomicRMWConfig {
ordering,
sync_scope,
flags: {
let mut f = llvmkit_ir::instr_types::AtomicRMWFlags::new();
if volatile {
f = f.volatile();
}
f
},
align: match align {
Some(value) => llvmkit_ir::align::MaybeAlign::from(value),
None => llvmkit_ir::align::MaybeAlign::NONE,
},
};
let v = b
.build_atomicrmw(op, ptr, val_v, config, result_name.as_str())
.map_err(|e| self.builder_err("atomicrmw", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_atomicrmw_op(&mut self) -> ParseResult<llvmkit_ir::atomicrmw_binop::AtomicRMWBinOp> {
use llvmkit_ir::atomicrmw_binop::AtomicRMWBinOp as Op;
let op = match self.peek() {
Token::Kw(Keyword::Xchg) => Op::Xchg,
Token::Instruction(crate::ll_token::Opcode::Add) => Op::Add,
Token::Instruction(crate::ll_token::Opcode::Sub) => Op::Sub,
Token::Instruction(crate::ll_token::Opcode::And) => Op::And,
Token::Kw(Keyword::Nand) => Op::Nand,
Token::Instruction(crate::ll_token::Opcode::Or) => Op::Or,
Token::Instruction(crate::ll_token::Opcode::Xor) => Op::Xor,
Token::Kw(Keyword::Max) => Op::Max,
Token::Kw(Keyword::Min) => Op::Min,
Token::Kw(Keyword::Umax) => Op::UMax,
Token::Kw(Keyword::Umin) => Op::UMin,
Token::Instruction(crate::ll_token::Opcode::FAdd) => Op::FAdd,
Token::Instruction(crate::ll_token::Opcode::FSub) => Op::FSub,
Token::Kw(Keyword::Fmax) => Op::FMax,
Token::Kw(Keyword::Fmin) => Op::FMin,
Token::Kw(Keyword::Fmaximum) => Op::FMaximum,
Token::Kw(Keyword::Fminimum) => Op::FMinimum,
Token::Kw(Keyword::UincWrap) => Op::UIncWrap,
Token::Kw(Keyword::UdecWrap) => Op::UDecWrap,
Token::Kw(Keyword::UsubCond) => Op::USubCond,
Token::Kw(Keyword::UsubSat) => Op::USubSat,
_ => return Err(self.expected("atomicrmw operation keyword")),
};
self.bump()?;
Ok(op)
}
fn parse_landingpad(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let result_ty = self.parse_type(false)?;
let cleanup = self.eat_keyword(Keyword::Cleanup)?;
let mut lp = b
.build_landingpad(result_ty, cleanup, result_name.as_str())
.map_err(|e| self.builder_err("landingpad", e))?;
loop {
match self.peek() {
Token::Kw(Keyword::Catch) => {
self.bump()?;
let clause_ty = self.parse_type(false)?;
let clause_v = self.parse_value(state, clause_ty)?;
lp = lp
.add_catch_clause(clause_v)
.map_err(|e| self.builder_err("landingpad.catch", e))?;
}
Token::Kw(Keyword::Filter) => {
self.bump()?;
let filter_ty = self.parse_type(false)?;
let filter_v = self.parse_value(state, filter_ty)?;
lp = lp
.add_filter_clause(filter_v)
.map_err(|e| self.builder_err("landingpad.filter", e))?;
}
_ => break,
}
}
Ok(lp.finish().as_instruction().as_value())
}
fn parse_cleanuppad(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
self.expect_keyword(Keyword::Within, "'within' in cleanuppad")?;
let parent_pad = self.parse_optional_pad_token(state)?;
let args = self.parse_bracket_value_list(state)?;
let v = b
.build_cleanup_pad(parent_pad, args, result_name.as_str())
.map_err(|e| self.builder_err("cleanuppad", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_catchpad(
&mut self,
state: &PerFunctionState<'ctx>,
b: &ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
self.expect_keyword(Keyword::Within, "'within' in catchpad")?;
let parent_ty = self.parse_type(false)?;
let parent_v = self.parse_value(state, parent_ty)?;
let args = self.parse_bracket_value_list(state)?;
let v = b
.build_catch_pad(parent_v, args, result_name.as_str())
.map_err(|e| self.builder_err("catchpad", e))?;
Ok(v.as_instruction().as_value())
}
fn parse_resume(
&mut self,
state: &PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
let ty = self.parse_type(false)?;
let v = self.parse_value(state, ty)?;
let _ = b
.build_resume(v, "")
.map_err(|e| self.builder_err("resume", e))?;
Ok(())
}
fn parse_cleanupret(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
self.expect_keyword(Keyword::From, "'from' in cleanupret")?;
let pad_ty = self.parse_type(false)?;
let pad_v = self.parse_value(state, pad_ty)?;
let unwind_dest = if self.eat_keyword(Keyword::Unwind)? {
if self.eat_keyword(Keyword::To)? {
self.expect_keyword(Keyword::Caller, "'caller' in cleanupret unwind")?;
None
} else {
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' in cleanupret unwind destination",
)?;
Some(self.parse_block_ref(state)?)
}
} else {
None
};
let _ = b
.build_cleanup_ret(pad_v, unwind_dest, "")
.map_err(|e| self.builder_err("cleanupret", e))?;
Ok(())
}
fn parse_catchret(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
) -> ParseResult<()> {
self.expect_keyword(Keyword::From, "'from' in catchret")?;
let pad_ty = self.parse_type(false)?;
let pad_v = self.parse_value(state, pad_ty)?;
self.expect_keyword(Keyword::To, "'to' in catchret")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' in catchret destination",
)?;
let dest = self.parse_block_ref(state)?;
let _ = b
.build_catch_ret(pad_v, dest, "")
.map_err(|e| self.builder_err("catchret", e))?;
Ok(())
}
fn parse_catchswitch(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
self.bump()?; self.expect_keyword(Keyword::Within, "'within' in catchswitch")?;
let parent_pad = self.parse_optional_pad_token(state)?;
self.expect_punct(PunctKind::LSquare, "'[' in catchswitch handlers")?;
let mut handlers: Vec<llvmkit_ir::BasicBlock<'ctx, llvmkit_ir::Dyn, llvmkit_ir::Unsealed>> =
Vec::new();
loop {
if matches!(self.peek(), Token::RSquare) {
self.bump()?;
break;
}
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' in catchswitch handler",
)?;
let bb = self.parse_block_ref(state)?;
handlers.push(bb);
let _ = self.eat_punct(PunctKind::Comma)?;
}
self.expect_keyword(Keyword::Unwind, "'unwind' in catchswitch")?;
let unwind_dest = if self.eat_keyword(Keyword::To)? {
self.expect_keyword(Keyword::Caller, "'caller' after 'to' in catchswitch")?;
None
} else {
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' in catchswitch unwind destination",
)?;
Some(self.parse_block_ref(state)?)
};
let name = result_name.as_str();
let (_, mut cs) = b
.build_catch_switch(parent_pad, unwind_dest, name)
.map_err(|e| self.builder_err("catchswitch", e))?;
for h in handlers {
cs = cs
.add_handler(h)
.map_err(|e| self.builder_err("catchswitch.add_handler", e))?;
}
Ok(cs.finish().as_instruction().as_value())
}
fn parse_invoke(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<Option<llvmkit_ir::Value<'ctx>>> {
let calling_conv = self.parse_optional_calling_conv()?;
let return_attrs = self.parse_optional_return_attrs()?;
let ret_ty = self.parse_type(true)?;
let parsed_callee = self.parse_direct_callee_ref()?;
self.expect_punct(PunctKind::LParen, "'(' in invoke argument list")?;
let mut args: Vec<llvmkit_ir::Value<'ctx>> = Vec::new();
let mut arg_tys: Vec<Type<'ctx>> = Vec::new();
let mut arg_attrs = Vec::new();
let mut var_args = false;
if !matches!(self.peek(), Token::RParen) {
loop {
if matches!(self.peek(), Token::DotDotDot) {
self.bump()?;
var_args = true;
break;
}
let arg_ty = self.parse_type(false)?;
let one_arg_attrs = self.parse_optional_param_attrs()?;
let arg_v = self.parse_value(state, arg_ty)?;
arg_attrs.push(one_arg_attrs);
arg_tys.push(arg_ty);
args.push(arg_v);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' to close invoke argument list")?;
let (function_attrs, function_attr_groups) = self.parse_optional_fn_attrs()?;
let operand_bundles = self.parse_optional_operand_bundles(state)?;
let call_attrs = llvmkit_ir::instr_types::CallAttributeData {
return_attrs,
arg_attrs: arg_attrs.into_boxed_slice(),
function_attrs,
function_attr_groups: function_attr_groups.into_boxed_slice(),
operand_bundles,
fmf: FastMathFlags::empty(),
};
self.expect_keyword(Keyword::To, "'to' in invoke")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' for invoke normal destination",
)?;
let normal_bb = self.parse_block_ref(state)?;
self.expect_keyword(Keyword::Unwind, "'unwind' in invoke")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' for invoke unwind destination",
)?;
let unwind_bb = self.parse_block_ref(state)?;
let parsed_fn_ty = self.module.fn_type(ret_ty, arg_tys, var_args);
let callee = self.resolve_direct_callee(parsed_callee, parsed_fn_ty)?;
let name = result_name.as_str();
let (_, inst) = match callee {
ParsedCallee::Function(callee) => b
.build_invoke_with_config(
callee,
args,
normal_bb,
unwind_bb,
llvmkit_ir::CallSiteConfig::new(name)
.calling_conv(calling_conv)
.attrs(call_attrs),
)
.map_err(|e| self.builder_err("invoke", e))?,
ParsedCallee::InlineAsm(asm) => {
if asm.label_constraint_count() != 0 {
return Err(self.expected("inline asm call without label constraints"));
}
b.build_inline_asm_invoke_with_config::<llvmkit_ir::Dyn, _, _, _, _>(
asm,
args,
normal_bb,
unwind_bb,
llvmkit_ir::CallSiteConfig::new(name)
.calling_conv(calling_conv)
.attrs(call_attrs),
)
.map_err(|e| self.builder_err("invoke", e))?
}
};
let ret_is_void = matches!(ret_ty.into_type_enum(), AnyTypeEnum::Void(_));
if ret_is_void || matches!(result_name, LocalLhs::None) {
Ok(None)
} else {
Ok(Some(inst.as_instruction().as_value()))
}
}
fn parse_callbr(
&mut self,
state: &mut PerFunctionState<'ctx>,
b: ParsedBlockBuilder<'ctx>,
result_name: &LocalLhs,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
self.bump()?; let calling_conv = self.parse_optional_calling_conv()?;
let return_attrs = self.parse_optional_return_attrs()?;
let ret_ty = self.parse_type(true)?;
let parsed_callee = self.parse_direct_callee_ref()?;
self.expect_punct(PunctKind::LParen, "'(' in callbr argument list")?;
let mut args: Vec<llvmkit_ir::Value<'ctx>> = Vec::new();
let mut arg_tys: Vec<Type<'ctx>> = Vec::new();
let mut arg_attrs = Vec::new();
let mut var_args = false;
if !matches!(self.peek(), Token::RParen) {
loop {
if matches!(self.peek(), Token::DotDotDot) {
self.bump()?;
var_args = true;
break;
}
let arg_ty = self.parse_type(false)?;
let one_arg_attrs = self.parse_optional_param_attrs()?;
let arg_v = self.parse_value(state, arg_ty)?;
arg_attrs.push(one_arg_attrs);
arg_tys.push(arg_ty);
args.push(arg_v);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RParen, "')' to close callbr argument list")?;
let (function_attrs, function_attr_groups) = self.parse_optional_fn_attrs()?;
let operand_bundles = self.parse_optional_operand_bundles(state)?;
let call_attrs = llvmkit_ir::instr_types::CallAttributeData {
return_attrs,
arg_attrs: arg_attrs.into_boxed_slice(),
function_attrs,
function_attr_groups: function_attr_groups.into_boxed_slice(),
operand_bundles,
fmf: FastMathFlags::empty(),
};
self.expect_keyword(Keyword::To, "'to' in callbr")?;
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' for callbr fallthrough destination",
)?;
let fallthrough = self.parse_block_ref(state)?;
let mut indirect: Vec<llvmkit_ir::BasicBlock<'ctx, llvmkit_ir::Dyn, llvmkit_ir::Unsealed>> =
Vec::new();
if matches!(self.peek(), Token::Comma) || matches!(self.peek(), Token::LSquare) {
if matches!(self.peek(), Token::Comma) {
self.bump()?;
}
self.expect_punct(PunctKind::LSquare, "'[' in callbr indirect targets")?;
loop {
if matches!(self.peek(), Token::RSquare) {
self.bump()?;
break;
}
self.expect_primitive(
crate::ll_token::PrimitiveTy::Label,
"'label' in callbr indirect target",
)?;
let bb = self.parse_block_ref(state)?;
indirect.push(bb);
let _ = self.eat_punct(PunctKind::Comma)?;
}
}
let parsed_fn_ty = self.module.fn_type(ret_ty, arg_tys, var_args);
let callee = self.resolve_direct_callee(parsed_callee, parsed_fn_ty)?;
let name = result_name.as_str();
let (_, inst) = match callee {
ParsedCallee::Function(callee) => b
.build_callbr_with_config(
callee,
args,
fallthrough,
&indirect,
llvmkit_ir::CallSiteConfig::new(name)
.calling_conv(calling_conv)
.attrs(call_attrs),
)
.map_err(|e| self.builder_err("callbr", e))?,
ParsedCallee::InlineAsm(asm) => {
if asm.label_constraint_count() != indirect.len() {
return Err(self.expected(
"inline asm callbr label constraint count matches indirect labels",
));
}
b.build_inline_asm_callbr_with_config::<llvmkit_ir::Dyn, _, _, _>(
asm,
args,
fallthrough,
&indirect,
llvmkit_ir::CallSiteConfig::new(name)
.calling_conv(calling_conv)
.attrs(call_attrs),
)
.map_err(|e| self.builder_err("callbr", e))?
}
};
Ok(inst.as_instruction().as_value())
}
fn parse_optional_pad_token(
&mut self,
state: &PerFunctionState<'ctx>,
) -> ParseResult<Option<llvmkit_ir::Value<'ctx>>> {
if matches!(self.peek(), Token::Kw(Keyword::None)) {
self.bump()?;
return Ok(None);
}
let ty = self.parse_type(false)?;
let v = self.parse_value(state, ty)?;
Ok(Some(v))
}
fn parse_bracket_value_list(
&mut self,
state: &PerFunctionState<'ctx>,
) -> ParseResult<Vec<llvmkit_ir::Value<'ctx>>> {
self.expect_punct(PunctKind::LSquare, "'[' to open pad argument list")?;
let mut args = Vec::new();
if !matches!(self.peek(), Token::RSquare) {
loop {
let ty = self.parse_type(false)?;
let v = self.parse_value(state, ty)?;
args.push(v);
if !self.eat_punct(PunctKind::Comma)? {
break;
}
}
}
self.expect_punct(PunctKind::RSquare, "']' to close pad argument list")?;
Ok(args)
}
fn builder_err(&self, label: &str, e: IrError) -> ParseError {
ParseError::Expected {
expected: format!("valid {label}: {e}"),
loc: DiagLoc::span(self.loc()),
}
}
fn parse_block_ref(
&mut self,
state: &mut PerFunctionState<'ctx>,
) -> ParseResult<llvmkit_ir::BasicBlock<'ctx, llvmkit_ir::Dyn, llvmkit_ir::Unsealed>> {
let loc = self.loc();
let name = match self.peek() {
Token::LocalVar(_) => self
.current_str_payload()
.ok_or_else(|| self.expected("block label name"))?,
Token::LocalVarId(n) => format!("{n}"),
_ => return Err(self.expected("block label after 'label'")),
};
self.bump()?;
if !state.defined_blocks.contains(&name) {
state.block_refs.entry(name.clone()).or_insert(loc);
}
Ok(state.ensure_block(self.module, &name))
}
fn parse_value(
&mut self,
state: &PerFunctionState<'ctx>,
ty: Type<'ctx>,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
self.parse_value_no_type(state, ty)
}
fn parse_value_no_type(
&mut self,
state: &PerFunctionState<'ctx>,
ty: Type<'ctx>,
) -> ParseResult<llvmkit_ir::Value<'ctx>> {
let id = self.parse_val_id(Some(state), Some(ty))?;
self.convert_val_id_to_value(ty, id, Some(state))
}
fn parse_fp_literal(
&mut self,
_float_ty: &llvmkit_ir::FloatType<'ctx, llvmkit_ir::FloatDyn>,
) -> ParseResult<u128> {
use crate::ll_token::FpLit;
let bits: u128 = match self.peek() {
Token::FloatLit(fp) => match *fp {
FpLit::Decimal(s) => {
let val: f64 = s
.parse()
.map_err(|_| self.expected("valid decimal float literal"))?;
u128::from(val.to_bits())
}
FpLit::HexDouble(s) => u128::from(
u64::from_str_radix(s, 16)
.map_err(|_| self.expected("valid hex double literal"))?,
),
FpLit::HexHalf(s) => u128::from(
u16::from_str_radix(s, 16)
.map_err(|_| self.expected("valid hex half literal"))?,
),
FpLit::HexBFloat(s) => u128::from(
u16::from_str_radix(s, 16)
.map_err(|_| self.expected("valid hex bfloat literal"))?,
),
FpLit::HexX87(s) => u128::from_str_radix(s, 16)
.map_err(|_| self.expected("valid hex x87 literal"))?,
FpLit::HexQuad(s) => u128::from_str_radix(s, 16)
.map_err(|_| self.expected("valid hex quad literal"))?,
FpLit::HexPpc128(s) => u128::from_str_radix(s, 16)
.map_err(|_| self.expected("valid hex ppc128 literal"))?,
},
_ => return Err(self.expected("floating-point literal")),
};
self.bump()?;
Ok(bits)
}
}
fn single_apint_word(words: &[u64]) -> Option<u64> {
match words {
[word] => Some(*word),
_ => None,
}
}
#[derive(Clone, Debug)]
enum PhiValRef<'ctx> {
Resolved(llvmkit_ir::Value<'ctx>),
Named(String),
Numbered(u32),
Undef,
}
struct DeferredPhiEdge<'ctx> {
phi_val: llvmkit_ir::Value<'ctx>,
val_ref: PhiValRef<'ctx>,
bb_name: String,
loc: llvmkit_support::Span,
}
struct PerFunctionState<'ctx> {
func: llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>,
local_named: std::collections::HashMap<String, llvmkit_ir::Value<'ctx>>,
local_numbered: std::collections::HashMap<u32, llvmkit_ir::Value<'ctx>>,
next_unnamed_value_id: u32,
blocks: std::collections::HashMap<
String,
llvmkit_ir::BasicBlock<'ctx, llvmkit_ir::Dyn, llvmkit_ir::Unsealed>,
>,
block_refs: std::collections::HashMap<String, Span>,
defined_blocks: std::collections::HashSet<String>,
deferred_phi: Vec<DeferredPhiEdge<'ctx>>,
}
impl<'ctx> PerFunctionState<'ctx> {
fn new(func: llvmkit_ir::FunctionValue<'ctx, llvmkit_ir::Dyn>) -> Self {
let mut blocks = std::collections::HashMap::new();
for bb in func.basic_blocks() {
let name = bb.name().unwrap_or_default();
blocks.insert(name, bb);
}
Self {
func,
local_named: std::collections::HashMap::new(),
local_numbered: std::collections::HashMap::new(),
next_unnamed_value_id: 0,
blocks,
block_refs: std::collections::HashMap::new(),
defined_blocks: std::collections::HashSet::new(),
deferred_phi: Vec::new(),
}
}
fn ensure_block(
&mut self,
_module: &'ctx llvmkit_ir::Module<'ctx>,
name: &str,
) -> llvmkit_ir::BasicBlock<'ctx, llvmkit_ir::Dyn, llvmkit_ir::Unsealed> {
if let Some(bb) = self.blocks.get(name) {
return *bb;
}
let bb = self.func.append_basic_block(name);
self.blocks.insert(name.to_owned(), bb);
bb
}
fn bind_local(
&mut self,
lhs: &LocalLhs,
v: llvmkit_ir::Value<'ctx>,
loc: Span,
) -> ParseResult<()> {
match lhs {
LocalLhs::Named(n) => {
if self.local_named.insert(n.clone(), v).is_some() {
return Err(ParseError::Redefinition {
kind: SYMBOL_KIND_LOCAL,
id: crate::parse_error::SymbolId::Named(n.clone()),
loc: DiagLoc::span(loc),
});
}
}
LocalLhs::Numbered(id) => {
if self.local_numbered.contains_key(id) {
return Err(ParseError::InvalidSlotId {
source: crate::numbered_values::AddError::StaleId {
id: *id,
next: self.next_unnamed_value_id,
},
loc: DiagLoc::span(loc),
});
}
self.local_numbered.insert(*id, v);
self.next_unnamed_value_id = self.next_unnamed_value_id.max(id.saturating_add(1));
}
LocalLhs::None => {
let id = self.next_unnamed_value_id;
self.local_numbered.insert(id, v);
self.next_unnamed_value_id = id.saturating_add(1);
}
}
Ok(())
}
fn finish(
mut self,
module: &'ctx llvmkit_ir::Module<'ctx>,
) -> crate::parse_error::ParseResult<()> {
for (name, loc) in &self.block_refs {
if !self.defined_blocks.contains(name) {
return Err(ParseError::UndefinedSymbol {
kind: crate::parse_error::SymbolKind::Block,
id: crate::parse_error::SymbolId::Named(name.clone()),
loc: DiagLoc::span(*loc),
});
}
}
let edges = std::mem::take(&mut self.deferred_phi);
for edge in edges {
let val = match edge.val_ref {
PhiValRef::Resolved(v) => v,
PhiValRef::Named(ref n) => self.local_named.get(n).copied().ok_or_else(|| {
crate::parse_error::ParseError::UndefinedSymbol {
kind: SYMBOL_KIND_LOCAL,
id: crate::parse_error::SymbolId::Named(n.clone()),
loc: DiagLoc::span(edge.loc),
}
})?,
PhiValRef::Numbered(id) => {
self.local_numbered.get(&id).copied().ok_or_else(|| {
crate::parse_error::ParseError::UndefinedSymbol {
kind: SYMBOL_KIND_LOCAL,
id: crate::parse_error::SymbolId::Numbered(id),
loc: DiagLoc::span(edge.loc),
}
})?
}
PhiValRef::Undef => {
let ty = edge.phi_val.ty();
match llvmkit_ir::AnyTypeEnum::from(ty) {
llvmkit_ir::AnyTypeEnum::Int(t) => t.const_zero().as_value(),
llvmkit_ir::AnyTypeEnum::Float(_t) => continue,
llvmkit_ir::AnyTypeEnum::Pointer(t) => t.const_null().as_value(),
_ => continue,
}
}
};
let bb = self.ensure_block(module, &edge.bb_name);
let tmp_b = llvmkit_ir::IRBuilder::new(module);
tmp_b
.phi_add_incoming_from_value(edge.phi_val, val, bb)
.map_err(|e| crate::parse_error::ParseError::Expected {
expected: format!("valid phi add_incoming: {e}"),
loc: DiagLoc::span(edge.loc),
})?;
}
Ok(())
}
}
enum BlockHeader {
Named(String),
Implicit,
}
enum ParamName {
Named(String),
Numbered(u32),
}
enum LocalLhs {
Named(String),
Numbered(u32),
None,
}
impl LocalLhs {
fn as_str(&self) -> &str {
match self {
LocalLhs::Named(n) => n.as_str(),
_ => "",
}
}
}
enum IntBinOp {
Add,
Sub,
Mul,
UDiv,
SDiv,
URem,
SRem,
Shl,
LShr,
AShr,
And,
Or,
Xor,
}
enum IntCast {
Trunc,
ZExt,
SExt,
}
enum FpBinOp {
Add,
Sub,
Mul,
Div,
Rem,
}
enum FpToInt {
FpToSI,
FpToUI,
}
enum IntToFp {
SIToFp,
UIToFp,
}
type ParsedBlockBuilder<'ctx> = llvmkit_ir::IRBuilder<
'ctx,
llvmkit_ir::ConstantFolder,
llvmkit_ir::Positioned,
llvmkit_ir::Dyn,
>;
fn live_builder_error(loc: Span) -> ParseError {
ParseError::Expected {
expected: "live insertion builder before terminator".into(),
loc: DiagLoc::span(loc),
}
}
fn take_live_builder<'ctx>(
builder: &mut Option<ParsedBlockBuilder<'ctx>>,
loc: Span,
) -> ParseResult<ParsedBlockBuilder<'ctx>> {
builder.take().ok_or_else(|| live_builder_error(loc))
}
fn borrow_live_builder<'b, 'ctx>(
builder: &'b Option<ParsedBlockBuilder<'ctx>>,
loc: Span,
) -> ParseResult<&'b ParsedBlockBuilder<'ctx>> {
builder.as_ref().ok_or_else(|| live_builder_error(loc))
}
const SYMBOL_KIND_LOCAL: crate::parse_error::SymbolKind = crate::parse_error::SymbolKind::Local;
#[derive(Clone, Debug)]
enum NameOrId {
Name(String),
Id(u32),
}
#[derive(Clone, Copy, Debug)]
enum PunctKind {
Equal,
Comma,
LParen,
RParen,
LBrace,
RBrace,
LSquare,
RSquare,
Less,
Greater,
}
impl PunctKind {
fn matches(self, t: &Token<'_>) -> bool {
matches!(
(self, t),
(PunctKind::Equal, Token::Equal)
| (PunctKind::Comma, Token::Comma)
| (PunctKind::LParen, Token::LParen)
| (PunctKind::RParen, Token::RParen)
| (PunctKind::LBrace, Token::LBrace)
| (PunctKind::RBrace, Token::RBrace)
| (PunctKind::LSquare, Token::LSquare)
| (PunctKind::RSquare, Token::RSquare)
| (PunctKind::Less, Token::Less)
| (PunctKind::Greater, Token::Greater)
)
}
}
trait IntoTypeEnum<'ctx> {
fn into_type_enum(self) -> AnyTypeEnum<'ctx>;
}
impl<'ctx> IntoTypeEnum<'ctx> for Type<'ctx> {
fn into_type_enum(self) -> AnyTypeEnum<'ctx> {
AnyTypeEnum::from(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn parse(src: &str) -> ParseResult<()> {
let m = Module::new("parse_test");
let p = Parser::new(src.as_bytes(), &m)?;
let _ = p.parse_module()?;
Ok(())
}
#[test]
fn parses_target_datalayout() {
let src = "target datalayout = \"e-m:e-i64:64\"\n";
let m = Module::new("dl");
Parser::new(src.as_bytes(), &m)
.unwrap()
.parse_module()
.unwrap();
let dl = m.data_layout();
assert!(dl.is_little_endian());
}
#[test]
fn parses_target_triple() {
let src = "target triple = \"x86_64-pc-linux-gnu\"\n";
let m = Module::new("triple");
Parser::new(src.as_bytes(), &m)
.unwrap()
.parse_module()
.unwrap();
assert_eq!(m.target_triple().as_deref(), Some("x86_64-pc-linux-gnu"));
}
#[test]
fn parses_module_asm() {
let src = "module asm \"hello\"\nmodule asm \"world\"\n";
let m = Module::new("masm");
Parser::new(src.as_bytes(), &m)
.unwrap()
.parse_module()
.unwrap();
let asm = m.module_asm();
assert!(asm.contains("hello"));
assert!(asm.contains("world"));
}
#[test]
fn parses_named_struct_definition() {
parse("%foo = type { i32, i64 }\n").unwrap();
}
#[test]
fn parses_numbered_struct_definition() {
parse("%0 = type { i32, i32, i32, i32 }\n").unwrap();
}
#[test]
fn rejects_legacy_typed_pointer_suffix() {
let err = parse("%foo = type { i32* }\n").unwrap_err();
match err {
ParseError::Expected { expected, .. } => assert!(expected.contains("opaque-pointer")),
other => panic!("unexpected error variant: {other:?}"),
}
}
#[test]
fn parses_simple_global_int() {
parse("@x = global i32 42\n").unwrap();
}
#[test]
fn parses_simple_global_constant() {
parse("@y = constant i32 -7\n").unwrap();
}
#[test]
fn parses_function_declaration() {
parse("declare i32 @add(i32, i32)\n").unwrap();
}
#[test]
fn parses_variadic_declaration() {
parse("declare i32 @printf(ptr, ...)\n").unwrap();
}
#[test]
fn parses_source_filename_directive() {
parse("source_filename = \"a.c\"\n").unwrap();
}
}