use crate::attributes::{AttributeKind, AttributeList};
use crate::module::Module;
use crate::opcode::Opcode;
use crate::types::{Type, TypeId, TypeKind};
use crate::value::{SubclassKind, Value, ValueRef};
use std::collections::HashMap;
pub fn write_assembly(module: &Module) -> String {
let mut out = String::new();
write_module_header(module, &mut out);
write_type_definitions(module, &mut out);
write_attr_groups(module, &mut out);
write_named_metadata(module, &mut out);
for func in &module.functions {
let f = func.borrow();
if f.is_function() && f.num_operands == 0 {
let ret_ty = function_return_type(func);
out.push_str(&format!(
"declare {} @{}()\n",
type_to_str_full(&ret_ty),
f.name
));
}
}
if module
.functions
.iter()
.any(|f| f.borrow().num_operands == 0)
{
out.push('\n');
}
for g in &module.globals {
write_global_variable(g, &mut out);
}
if !module.globals.is_empty() {
out.push('\n');
}
for func in &module.functions {
let f = func.borrow();
if f.is_function() && f.num_operands > 0 {
let ret_ty = function_return_type(func);
out.push_str(&format!(
"define {} @{}() {{\n",
type_to_str_full(&ret_ty),
f.name
));
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
out.push_str(&format!("{}:\n", bb.name));
for inst in &bb.operands {
let i = inst.borrow();
if i.is_instruction() {
write_instruction(&mut out, inst);
}
}
}
}
out.push_str("}\n\n");
}
}
write_use_list_order(module, &mut out);
out
}
fn write_module_header(module: &Module, out: &mut String) {
if !module.source_filename.is_empty() {
write_source_filename(&module.source_filename, out);
}
if let Some(ref triple) = module.target_triple {
write_target_triple(triple, out);
}
if let Some(ref dl) = module.data_layout {
write_data_layout(dl, out);
}
if !module.source_filename.is_empty()
|| module.target_triple.is_some()
|| module.data_layout.is_some()
{
out.push('\n');
}
}
fn write_source_filename(filename: &str, out: &mut String) {
out.push_str(&format!("source_filename = \"{}\"\n", filename));
}
fn write_target_triple(triple: &str, out: &mut String) {
out.push_str(&format!("target triple = \"{}\"\n", triple));
}
fn write_data_layout(layout: &str, out: &mut String) {
out.push_str(&format!("target datalayout = \"{}\"\n", layout));
}
fn write_type_definitions(_module: &Module, _out: &mut String) {
}
fn write_attr_groups(_module: &Module, _out: &mut String) {
}
fn write_named_metadata(_module: &Module, _out: &mut String) {
}
fn write_use_list_order(_module: &Module, _out: &mut String) {
}
fn write_global_variable(gv: &ValueRef, out: &mut String) {
let g = gv.borrow();
if g.subclass != SubclassKind::GlobalVariable {
return;
}
let linkage = if g.name.starts_with("internal") {
"internal "
} else {
""
};
out.push_str(&format!(
"@{} = {}global {} {}\n",
g.name,
linkage,
type_to_str_full(&g.ty),
value_to_str(gv)
));
}
fn write_function_declaration(func: &ValueRef, out: &mut String) {
let f = func.borrow();
if f.is_function() {
let ret_ty = function_return_type(func);
out.push_str(&format!(
"declare {} @{}()\n",
type_to_str_full(&ret_ty),
f.name
));
}
}
fn write_function_definition(func: &ValueRef, out: &mut String) {
let f = func.borrow();
if f.is_function() && f.num_operands > 0 {
let ret_ty = function_return_type(func);
out.push_str(&format!(
"define {} @{}() {{\n",
type_to_str_full(&ret_ty),
f.name
));
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
out.push_str(&format!("{}:\n", bb.name));
for inst in &bb.operands {
let i = inst.borrow();
if i.is_instruction() {
write_instruction(out, inst);
}
}
}
}
out.push_str("}\n\n");
}
}
fn function_return_type(func: &ValueRef) -> Type {
let f = func.borrow();
if let Some(ref rt) = f.return_type {
return rt.clone();
}
match &f.ty.kind {
TypeKind::Function { .. } => Type::void(),
TypeKind::Integer { bits } => Type::int(*bits),
_ => f.ty.clone(),
}
}
pub fn type_to_str(ty: &Type) -> String {
type_to_str_full(ty)
}
fn type_to_str_full(ty: &Type) -> String {
match &ty.kind {
TypeKind::Void => "void".into(),
TypeKind::Half => "half".into(),
TypeKind::BFloat => "bfloat".into(),
TypeKind::Float => "float".into(),
TypeKind::Double => "double".into(),
TypeKind::FP128 => "fp128".into(),
TypeKind::X86FP80 => "x86_fp80".into(),
TypeKind::PPCFP128 => "ppc_fp128".into(),
TypeKind::Label => "label".into(),
TypeKind::Metadata => "metadata".into(),
TypeKind::X86AMX => "x86_amx".into(),
TypeKind::X86MMX => "x86_mmx".into(),
TypeKind::Token => "token".into(),
TypeKind::Integer { bits } => format!("i{}", bits),
TypeKind::Pointer { addr_space } => {
if *addr_space == 0 {
"ptr".into()
} else {
format!("ptr addrspace({})", addr_space)
}
}
TypeKind::Array {
len,
element_type_id,
} => {
format!("[{} x <ty>]", len)
}
TypeKind::Struct {
name,
is_packed,
is_opaque,
element_type_ids,
} => {
if let Some(n) = name {
if *is_packed {
format!("<{{ %{} }}>", n)
} else {
format!("%{}", n)
}
} else if *is_opaque {
if *is_packed {
"<{ opaque }>".into()
} else {
"{ opaque }".into()
}
} else if element_type_ids.is_empty() {
if *is_packed {
"<{ }>".into()
} else {
"{ }".into()
}
} else {
let elts: Vec<String> = element_type_ids
.iter()
.map(|_| "<ty>".to_string())
.collect();
let body = elts.join(", ");
if *is_packed {
format!("<{{ {} }}>", body)
} else {
format!("{{ {} }}", body)
}
}
}
TypeKind::FixedVector {
len,
element_type_id,
} => {
format!("<{} x <ty>>", len)
}
TypeKind::ScalableVector {
min_elems,
element_type_id,
} => {
format!("<vscale x {} x <ty>>", min_elems)
}
TypeKind::Function {
return_type_id,
param_type_ids,
is_vararg,
} => {
let params: Vec<String> = param_type_ids.iter().map(|_| "<ty>".to_string()).collect();
let mut sig = params.join(", ");
if *is_vararg {
if !sig.is_empty() {
sig.push_str(", ...");
} else {
sig.push_str("...");
}
}
format!("<ty> ({})", sig)
}
}
}
fn write_instruction(out: &mut String, inst_val: &ValueRef) {
let i = inst_val.borrow();
let name_prefix = if i.name.is_empty() {
String::new()
} else {
format!("%{} = ", i.name)
};
if let Some(opcode) = i.get_opcode() {
match opcode {
crate::opcode::Opcode::Ret => {
if i.operands.is_empty() {
out.push_str(&format!(" {}ret void\n", name_prefix));
} else {
let val = value_to_str(&i.operands[0]);
let ty_str = type_to_str_full(&i.operands[0].borrow().ty);
out.push_str(&format!(" {}ret {} {}\n", name_prefix, ty_str, val));
}
}
crate::opcode::Opcode::Br => {
if i.operands.len() == 1 {
let label = i.operands[0].borrow().name.clone();
out.push_str(&format!(" {}br label %{}\n", name_prefix, label));
} else if i.operands.len() >= 3 {
let cond = value_to_str(&i.operands[0]);
let t_label = i.operands[1].borrow().name.clone();
let f_label = i.operands[2].borrow().name.clone();
out.push_str(&format!(
" {}br i1 {}, label %{}, label %{}\n",
name_prefix, cond, t_label, f_label
));
} else {
out.push_str(&format!(" {}br <unknown>\n", name_prefix));
}
}
crate::opcode::Opcode::Switch => {
if i.operands.len() >= 2 {
let val = value_to_str(&i.operands[0]);
let val_ty = type_to_str_full(&i.operands[0].borrow().ty);
let default_label = i.operands[1].borrow().name.clone();
let mut cases = String::new();
let mut idx = 2;
while idx + 1 < i.operands.len() {
let case_val = value_to_str(&i.operands[idx]);
let case_ty = type_to_str_full(&i.operands[idx].borrow().ty);
let case_label = i.operands[idx + 1].borrow().name.clone();
cases.push_str(&format!(
" {} {}, label %{}",
case_ty, case_val, case_label
));
idx += 2;
if idx + 1 < i.operands.len() {
cases.push_str(",\n");
}
}
out.push_str(&format!(
" {}switch {} {}, label %{} [\n{}\n ]\n",
name_prefix, val_ty, val, default_label, cases
));
}
}
crate::opcode::Opcode::Invoke => {
if i.operands.len() >= 1 {
let func_name = i.operands[0].borrow().name.clone();
let ret_ty = type_to_str_full(&i.ty);
let mut args = Vec::new();
for a in &i.operands[1..] {
let a_borrow = a.borrow();
if a_borrow.is_basic_block() {
args.push(format!("label %{}", a_borrow.name));
} else {
let aty = type_to_str_full(&a_borrow.ty);
let av = value_to_str(a);
args.push(format!("{} {}", aty, av));
}
}
out.push_str(&format!(
" {}invoke {} @{}({})\n",
name_prefix,
ret_ty,
func_name,
args.join(", ")
));
}
}
crate::opcode::Opcode::Resume => {
if !i.operands.is_empty() {
let val = value_to_str(&i.operands[0]);
let ty_str = type_to_str_full(&i.operands[0].borrow().ty);
out.push_str(&format!(" {}resume {} {}\n", name_prefix, ty_str, val));
} else {
out.push_str(&format!(" {}resume undef\n", name_prefix));
}
}
crate::opcode::Opcode::LandingPad => {
let ty_str = type_to_str_full(&i.ty);
out.push_str(&format!(" {}landingpad {}\n", name_prefix, ty_str));
}
crate::opcode::Opcode::CleanupRet => {
out.push_str(&format!(" {}cleanupret\n", name_prefix));
}
crate::opcode::Opcode::CatchRet => {
out.push_str(&format!(" {}catchret\n", name_prefix));
}
crate::opcode::Opcode::CatchSwitch => {
out.push_str(&format!(" {}catchswitch\n", name_prefix));
}
crate::opcode::Opcode::CallBr => {
out.push_str(&format!(" {}callbr\n", name_prefix));
}
crate::opcode::Opcode::IndirectBr => {
out.push_str(&format!(" {}indirectbr\n", name_prefix));
}
crate::opcode::Opcode::Add
| crate::opcode::Opcode::Sub
| crate::opcode::Opcode::Mul
| crate::opcode::Opcode::UDiv
| crate::opcode::Opcode::SDiv
| crate::opcode::Opcode::URem
| crate::opcode::Opcode::SRem
| crate::opcode::Opcode::FAdd
| crate::opcode::Opcode::FSub
| crate::opcode::Opcode::FMul
| crate::opcode::Opcode::FDiv
| crate::opcode::Opcode::FRem
| crate::opcode::Opcode::Shl
| crate::opcode::Opcode::LShr
| crate::opcode::Opcode::AShr
| crate::opcode::Opcode::And
| crate::opcode::Opcode::Or
| crate::opcode::Opcode::Xor => {
if i.operands.len() >= 2 {
let a = value_to_str(&i.operands[0]);
let b = value_to_str(&i.operands[1]);
let ty_str = type_to_str_full(&i.operands[0].borrow().ty);
out.push_str(&format!(
" {}{} {} {}, {}\n",
name_prefix,
opcode.as_mnemonic(),
ty_str,
a,
b
));
}
}
crate::opcode::Opcode::Alloca => {
let ty_str = type_to_str_full(&i.ty);
out.push_str(&format!(" {}alloca {}\n", name_prefix, ty_str));
}
crate::opcode::Opcode::Load => {
let ty_str = type_to_str_full(&i.ty);
let ptr = value_to_str(&i.operands[0]);
out.push_str(&format!(" {}load {}, ptr {}\n", name_prefix, ty_str, ptr));
}
crate::opcode::Opcode::Store => {
if i.operands.len() >= 2 {
let val = value_to_str(&i.operands[0]);
let val_ty = type_to_str_full(&i.operands[0].borrow().ty);
let ptr = value_to_str(&i.operands[1]);
out.push_str(&format!(
" {}store {} {}, ptr {}\n",
name_prefix, val_ty, val, ptr
));
}
}
crate::opcode::Opcode::GetElementPtr => {
if !i.operands.is_empty() {
let ptr = value_to_str(&i.operands[0]);
let ptr_ty = type_to_str_full(&i.operands[0].borrow().ty);
let mut idx_parts = Vec::new();
for idx in &i.operands[1..] {
let ity = type_to_str_full(&idx.borrow().ty);
let iv = value_to_str(idx);
idx_parts.push(format!("{} {}", ity, iv));
}
out.push_str(&format!(
" {}getelementptr inbounds {}, ptr {}, {}\n",
name_prefix,
ptr_ty,
ptr,
idx_parts.join(", ")
));
}
}
crate::opcode::Opcode::Fence => {
out.push_str(&format!(" {}fence seq_cst\n", name_prefix));
}
crate::opcode::Opcode::CmpXchg => {
if i.operands.len() >= 3 {
let ptr = value_to_str(&i.operands[0]);
let ptr_ty = type_to_str_full(&i.operands[0].borrow().ty);
let old = value_to_str(&i.operands[1]);
let old_ty = type_to_str_full(&i.operands[1].borrow().ty);
let new = value_to_str(&i.operands[2]);
let new_ty = type_to_str_full(&i.operands[2].borrow().ty);
out.push_str(&format!(
" {}cmpxchg {} {}, {} {}, {} {} acq_rel monotonic\n",
name_prefix, ptr_ty, ptr, old_ty, old, new_ty, new
));
}
}
crate::opcode::Opcode::AtomicRMW => {
if i.operands.len() >= 2 {
let ptr = value_to_str(&i.operands[0]);
let ptr_ty = type_to_str_full(&i.operands[0].borrow().ty);
let val = value_to_str(&i.operands[1]);
let val_ty = type_to_str_full(&i.operands[1].borrow().ty);
out.push_str(&format!(
" {}atomicrmw xchg {} {}, {} {} seq_cst\n",
name_prefix, ptr_ty, ptr, val_ty, val
));
}
}
crate::opcode::Opcode::Trunc
| crate::opcode::Opcode::ZExt
| crate::opcode::Opcode::SExt
| crate::opcode::Opcode::FPTrunc
| crate::opcode::Opcode::FPExt
| crate::opcode::Opcode::FPToUI
| crate::opcode::Opcode::FPToSI
| crate::opcode::Opcode::UIToFP
| crate::opcode::Opcode::SIToFP
| crate::opcode::Opcode::PtrToInt
| crate::opcode::Opcode::IntToPtr
| crate::opcode::Opcode::BitCast
| crate::opcode::Opcode::AddrSpaceCast => {
if !i.operands.is_empty() {
let val = value_to_str(&i.operands[0]);
let from_ty = type_to_str_full(&i.operands[0].borrow().ty);
let to_ty = type_to_str_full(&i.ty);
out.push_str(&format!(
" {}{} {} {} to {}\n",
name_prefix,
opcode.as_mnemonic(),
from_ty,
val,
to_ty
));
}
}
crate::opcode::Opcode::ICmp => {
let pred_str = if !i.name.is_empty() {
let name = &i.name;
if let Some(pred) = name.strip_prefix("icmp.") {
pred.to_string()
} else {
"eq".into()
}
} else {
"eq".into()
};
if i.operands.len() >= 2 {
let a = value_to_str(&i.operands[0]);
let b = value_to_str(&i.operands[1]);
let ty_str = type_to_str_full(&i.operands[0].borrow().ty);
out.push_str(&format!(
" {}icmp {} {} {}, {}\n",
name_prefix, pred_str, ty_str, a, b
));
}
}
crate::opcode::Opcode::FCmp => {
if i.operands.len() >= 2 {
let a = value_to_str(&i.operands[0]);
let b = value_to_str(&i.operands[1]);
let ty_str = type_to_str_full(&i.operands[0].borrow().ty);
out.push_str(&format!(
" {}fcmp oeq {} {}, {}\n",
name_prefix, ty_str, a, b
));
}
}
crate::opcode::Opcode::Call => {
if !i.operands.is_empty() {
let func_name = if !i.operands[0].borrow().name.is_empty() {
format!("@{}", i.operands[0].borrow().name)
} else {
"@unknown".into()
};
let args: Vec<String> = i.operands[1..]
.iter()
.map(|v| {
let ty = type_to_str_full(&v.borrow().ty);
let val = value_to_str(v);
format!("{} {}", ty, val)
})
.collect();
let ret_ty = type_to_str_full(&i.ty);
out.push_str(&format!(
" {}call {} {}({})\n",
name_prefix,
ret_ty,
func_name,
args.join(", ")
));
}
}
crate::opcode::Opcode::Phi => {
let ty_str = type_to_str_full(&i.ty);
let mut incoming = Vec::new();
for chunk in i.operands.chunks(2) {
if chunk.len() == 2 {
let val = value_to_str(&chunk[0]);
let label = chunk[1].borrow().name.clone();
incoming.push(format!("[ {}, %{} ]", val, label));
}
}
out.push_str(&format!(
" {}phi {} {}\n",
name_prefix,
ty_str,
incoming.join(", ")
));
}
crate::opcode::Opcode::Select => {
if i.operands.len() >= 3 {
let cond = value_to_str(&i.operands[0]);
let a = value_to_str(&i.operands[1]);
let a_ty = type_to_str_full(&i.operands[1].borrow().ty);
let b = value_to_str(&i.operands[2]);
let b_ty = type_to_str_full(&i.operands[2].borrow().ty);
out.push_str(&format!(
" {}select i1 {}, {} {}, {} {}\n",
name_prefix, cond, a_ty, a, b_ty, b
));
}
}
crate::opcode::Opcode::Freeze => {
if !i.operands.is_empty() {
let val = value_to_str(&i.operands[0]);
let ty_str = type_to_str_full(&i.operands[0].borrow().ty);
out.push_str(&format!(" {}freeze {} {}\n", name_prefix, ty_str, val));
}
}
crate::opcode::Opcode::VAArg => {
if i.operands.len() >= 2 {
let va = value_to_str(&i.operands[0]);
let ty_str = type_to_str_full(&i.ty);
out.push_str(&format!(" {}va_arg {} {}\n", name_prefix, ty_str, va));
}
}
crate::opcode::Opcode::ExtractElement => {
if i.operands.len() >= 2 {
let vec = value_to_str(&i.operands[0]);
let vec_ty = type_to_str_full(&i.operands[0].borrow().ty);
let idx = value_to_str(&i.operands[1]);
let idx_ty = type_to_str_full(&i.operands[1].borrow().ty);
out.push_str(&format!(
" {}extractelement {} {}, {} {}\n",
name_prefix, vec_ty, vec, idx_ty, idx
));
}
}
crate::opcode::Opcode::InsertElement => {
if i.operands.len() >= 3 {
let vec = value_to_str(&i.operands[0]);
let vec_ty = type_to_str_full(&i.operands[0].borrow().ty);
let elem = value_to_str(&i.operands[1]);
let elem_ty = type_to_str_full(&i.operands[1].borrow().ty);
let idx = value_to_str(&i.operands[2]);
let idx_ty = type_to_str_full(&i.operands[2].borrow().ty);
out.push_str(&format!(
" {}insertelement {} {}, {} {}, {} {}\n",
name_prefix, vec_ty, vec, elem_ty, elem, idx_ty, idx
));
}
}
crate::opcode::Opcode::ShuffleVector => {
if i.operands.len() >= 2 {
let a = value_to_str(&i.operands[0]);
let a_ty = type_to_str_full(&i.operands[0].borrow().ty);
let b = value_to_str(&i.operands[1]);
let b_ty = type_to_str_full(&i.operands[1].borrow().ty);
let result_ty = type_to_str_full(&i.ty);
out.push_str(&format!(
" {}shufflevector {} {}, {} {}, {} <i32 0, i32 1, i32 2, i32 3>\n",
name_prefix, a_ty, a, b_ty, b, result_ty
));
}
}
crate::opcode::Opcode::ExtractValue => {
if !i.operands.is_empty() {
let agg = value_to_str(&i.operands[0]);
let agg_ty = type_to_str_full(&i.operands[0].borrow().ty);
let mut indices = String::from("0");
if let Some(ev_pos) = i.name.find(".ev.") {
indices = i.name[ev_pos + 4..].to_string();
}
out.push_str(&format!(
" {}extractvalue {} {}, {}\n",
name_prefix, agg_ty, agg, indices
));
}
}
crate::opcode::Opcode::InsertValue => {
if i.operands.len() >= 2 {
let agg = value_to_str(&i.operands[0]);
let agg_ty = type_to_str_full(&i.operands[0].borrow().ty);
let elem = value_to_str(&i.operands[1]);
let elem_ty = type_to_str_full(&i.operands[1].borrow().ty);
out.push_str(&format!(
" {}insertvalue {} {}, {} {}, 0\n",
name_prefix, agg_ty, agg, elem_ty, elem
));
}
}
crate::opcode::Opcode::Unreachable => {
out.push_str(&format!(" {}unreachable\n", name_prefix));
}
_ => {
out.push_str(&format!(" {}{}\n", name_prefix, opcode.as_mnemonic()));
}
}
} else {
let void_ty = i.ty.is_void();
let num_ops = i.operands.len();
if void_ty && num_ops == 0 {
out.push_str(&format!(" {}ret void\n", name_prefix));
} else if void_ty && num_ops == 1 && i.operands[0].borrow().is_basic_block() {
let label = i.operands[0].borrow().name.clone();
out.push_str(&format!(" {}br label %{}\n", name_prefix, label));
} else if void_ty && num_ops == 1 {
let val = value_to_str(&i.operands[0]);
out.push_str(&format!(
" {}ret {} {}\n",
name_prefix,
type_to_str_full(&i.operands[0].borrow().ty),
val
));
} else if void_ty && num_ops == 3 && i.operands[1].borrow().is_basic_block() {
let cond = value_to_str(&i.operands[0]);
let t_label = i.operands[1].borrow().name.clone();
let f_label = i.operands[2].borrow().name.clone();
out.push_str(&format!(
" {}br i1 {}, label %{}, label %{}\n",
name_prefix, cond, t_label, f_label
));
} else {
out.push_str(&format!(" {}unknown\n", name_prefix));
}
}
}
fn value_to_str(val: &ValueRef) -> String {
let v = val.borrow();
match v.subclass {
SubclassKind::Constant => {
if v.name == "null" {
"null".into()
} else if v.name == "undef" {
"undef".into()
} else if v.name == "poison" {
"poison".into()
} else if v.name == "zeroinitializer" {
"zeroinitializer".into()
} else if v.ty.is_integer() && v.ty.integer_bit_width() == 1 {
if v.name == "1" {
"true".into()
} else {
"false".into()
}
} else {
v.name.clone()
}
}
SubclassKind::Argument | SubclassKind::BasicBlock => format!("%{}", v.name),
SubclassKind::Function => format!("@{}", v.name),
SubclassKind::GlobalVariable => {
if v.name.is_empty() {
format!("@gv{}", v.vid)
} else if v.name.starts_with('@') {
v.name.clone()
} else {
format!("@{}", v.name)
}
}
_ => {
if v.name.is_empty() {
format!("%v{}", v.vid)
} else {
format!("%{}", v.name)
}
}
}
}
#[allow(dead_code)]
fn write_fn_attributes(_func: &ValueRef, out: &mut String) {
out.push(' ');
}
#[allow(dead_code)]
fn write_param_attributes(_params: &[ValueRef], _out: &mut String) {
}
#[allow(dead_code)]
fn write_metadata(_module: &Module, _out: &mut String) {
}
#[allow(dead_code)]
fn write_comdat(_comdat_name: &str, _kind: &str, _out: &mut String) {
}
#[derive(Debug, Clone)]
pub struct AssemblyWriterConfig {
pub indent: usize,
pub use_color: bool,
pub show_annotations: bool,
pub show_metadata: bool,
pub show_debug_loc: bool,
pub max_line_width: usize,
pub show_value_ids: bool,
pub show_value_symtab: bool,
pub opaque_pointers: bool,
pub show_use_list_order: bool,
pub show_comdat: bool,
pub show_attribute_groups: bool,
}
impl Default for AssemblyWriterConfig {
fn default() -> Self {
Self {
indent: 2,
use_color: false,
show_annotations: false,
show_metadata: false,
show_debug_loc: false,
max_line_width: 80,
show_value_ids: false,
show_value_symtab: false,
opaque_pointers: true,
show_use_list_order: false,
show_comdat: true,
show_attribute_groups: true,
}
}
}
impl AssemblyWriterConfig {
pub fn pretty() -> Self {
Self {
indent: 2,
use_color: true,
show_annotations: true,
show_metadata: true,
show_debug_loc: true,
max_line_width: 100,
show_value_ids: false,
show_value_symtab: false,
opaque_pointers: true,
show_use_list_order: false,
show_comdat: true,
show_attribute_groups: true,
}
}
pub fn canonical() -> Self {
Self {
indent: 2,
use_color: false,
show_annotations: false,
show_metadata: true,
show_debug_loc: false,
max_line_width: 0,
show_value_ids: false,
show_value_symtab: false,
opaque_pointers: true,
show_use_list_order: true,
show_comdat: true,
show_attribute_groups: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SlotTracker {
module_slots: HashMap<u64, u32>,
local_slots: HashMap<u64, Vec<(u64, u32)>>,
next_module_slot: u32,
next_local_slots: HashMap<u64, u32>,
type_slots: HashMap<u64, u32>,
next_type_slot: u32,
metadata_slots: HashMap<u32, u32>,
next_metadata_slot: u32,
attr_slots: HashMap<u32, u32>,
next_attr_slot: u32,
}
impl SlotTracker {
pub fn new() -> Self {
Self {
module_slots: HashMap::new(),
local_slots: HashMap::new(),
next_module_slot: 0,
next_local_slots: HashMap::new(),
type_slots: HashMap::new(),
next_type_slot: 0,
metadata_slots: HashMap::new(),
next_metadata_slot: 0,
attr_slots: HashMap::new(),
next_attr_slot: 0,
}
}
pub fn assign_module_slot(&mut self, val: &ValueRef) -> u32 {
let vid = val.borrow().vid;
*self.module_slots.entry(vid).or_insert_with(|| {
let slot = self.next_module_slot;
self.next_module_slot += 1;
slot
})
}
pub fn assign_local_slot(&mut self, func_vid: u64, val: &ValueRef) -> u32 {
let vid = val.borrow().vid;
let slots = self.local_slots.entry(func_vid).or_default();
if let Some((_, slot)) = slots.iter().find(|(v, _)| *v == vid) {
return *slot;
}
let next = self.next_local_slots.entry(func_vid).or_insert(0);
let slot = *next;
*next += 1;
slots.push((vid, slot));
slot
}
pub fn get_module_slot(&self, val: &ValueRef) -> Option<u32> {
self.module_slots.get(&val.borrow().vid).copied()
}
pub fn get_local_slot(&self, func_vid: u64, val: &ValueRef) -> Option<u32> {
self.local_slots
.get(&func_vid)
.and_then(|slots| slots.iter().find(|(v, _)| *v == val.borrow().vid))
.map(|(_, slot)| *slot)
}
pub fn assign_type_slot(&mut self, type_id: &TypeId) -> u32 {
let id = type_id.as_u64();
*self.type_slots.entry(id).or_insert_with(|| {
let slot = self.next_type_slot;
self.next_type_slot += 1;
slot
})
}
pub fn get_type_slot(&self, type_id: &TypeId) -> Option<u32> {
self.type_slots.get(&type_id.as_u64()).copied()
}
pub fn assign_metadata_slot(&mut self, md_id: u32) -> u32 {
*self.metadata_slots.entry(md_id).or_insert_with(|| {
let slot = self.next_metadata_slot;
self.next_metadata_slot += 1;
slot
})
}
pub fn get_metadata_slot(&self, md_id: u32) -> Option<u32> {
self.metadata_slots.get(&md_id).copied()
}
pub fn assign_attr_slot(&mut self, group_id: u32) -> u32 {
*self.attr_slots.entry(group_id).or_insert_with(|| {
let slot = self.next_attr_slot;
self.next_attr_slot += 1;
slot
})
}
pub fn get_attr_slot(&self, group_id: u32) -> Option<u32> {
self.attr_slots.get(&group_id).copied()
}
pub fn clear(&mut self) {
self.module_slots.clear();
self.local_slots.clear();
self.next_module_slot = 0;
self.next_local_slots.clear();
self.type_slots.clear();
self.next_type_slot = 0;
self.metadata_slots.clear();
self.next_metadata_slot = 0;
self.attr_slots.clear();
self.next_attr_slot = 0;
}
pub fn enumerate_module(&mut self, module: &Module) {
for ty in &module.types {
self.assign_type_slot(&ty.id);
}
for gv in &module.globals {
self.assign_module_slot(gv);
}
for func in &module.functions {
self.assign_module_slot(func);
}
for alias in &module.aliases {
self.assign_module_slot(alias);
}
for ifunc in &module.ifuncs {
self.assign_module_slot(ifunc);
}
for (&gid, _) in &module.attr_groups {
self.assign_attr_slot(gid);
}
}
}
impl Default for SlotTracker {
fn default() -> Self {
Self::new()
}
}
pub fn print_type(ty: &Type) -> String {
print_type_inner(ty, false)
}
fn print_type_inner(ty: &Type, _in_struct: bool) -> String {
match &ty.kind {
TypeKind::Void => "void".to_string(),
TypeKind::Half => "half".to_string(),
TypeKind::BFloat => "bfloat".to_string(),
TypeKind::Float => "float".to_string(),
TypeKind::Double => "double".to_string(),
TypeKind::FP128 => "fp128".to_string(),
TypeKind::X86FP80 => "x86_fp80".to_string(),
TypeKind::PPCFP128 => "ppc_fp128".to_string(),
TypeKind::Label => "label".to_string(),
TypeKind::Metadata => "metadata".to_string(),
TypeKind::X86AMX => "x86_amx".to_string(),
TypeKind::X86MMX => "x86_mmx".to_string(),
TypeKind::Token => "token".to_string(),
TypeKind::Integer { bits } => format!("i{}", bits),
TypeKind::Pointer { addr_space } => {
if *addr_space == 0 {
"ptr".to_string()
} else {
format!("ptr addrspace({})", addr_space)
}
}
TypeKind::Array {
len,
element_type_id: _,
} => {
format!("[{} x <ty>]", len)
}
TypeKind::Struct {
name,
is_packed,
is_opaque,
element_type_ids,
} => {
if *is_opaque {
if let Some(n) = name {
return format!("%{}", n);
}
return "%opaque".to_string();
}
let body: Vec<String> = element_type_ids
.iter()
.map(|_| "<ty>".to_string())
.collect();
let packed = if *is_packed { "<" } else { "" };
let packed_end = if *is_packed { ">" } else { "" };
let body_str = if body.is_empty() {
"{}".to_string()
} else {
format!("{}{{{} {}}}", packed, " ", body.join(", "))
};
if let Some(n) = name {
format!("%{}{}", n, body_str)
} else {
body_str
}
}
TypeKind::FixedVector {
len,
element_type_id: _,
} => {
format!("<{} x <ty>>", len)
}
TypeKind::ScalableVector {
min_elems,
element_type_id: _,
} => {
format!("<vscale x {} x <ty>>", min_elems)
}
TypeKind::Function {
return_type_id: _,
param_type_ids,
is_vararg,
} => {
let params: Vec<String> = param_type_ids.iter().map(|_| "<ty>".to_string()).collect();
let vararg = if *is_vararg { ", ..." } else { "" };
format!("<ty> ({}{})", params.join(", "), vararg)
}
}
}
pub fn print_type_name(ty: &Type) -> String {
match &ty.kind {
TypeKind::Struct { name, .. } => {
if let Some(n) = name {
return format!("%{}", n);
}
}
_ => {}
}
print_type(ty)
}
pub fn print_type_definitions(module: &Module) -> String {
let mut out = String::new();
for ty in &module.types {
match &ty.kind {
TypeKind::Struct { name, .. } if name.is_some() => {
out.push_str(&format!(
"{} = type {}\n",
print_type_name(ty),
print_type(ty)
));
}
_ => {}
}
}
out
}
pub fn print_constant(val: &ValueRef) -> String {
let v = val.borrow();
match v.subclass {
SubclassKind::ConstantInt => {
let bits = v.ty.integer_bit_width();
if bits == 1 {
if v.name == "1" || v.name == "true" {
"true".to_string()
} else {
"false".to_string()
}
} else {
let ty_str = format!("i{}", bits);
format!("{} {}", ty_str, v.name)
}
}
SubclassKind::ConstantFP => {
let ty_str = print_type(&v.ty);
format!("{} {}", ty_str, v.name)
}
SubclassKind::ConstantAggregate => {
let ty_str = print_type(&v.ty);
let elements: Vec<String> = v
.operands
.iter()
.map(|op| {
let op_val = op.borrow();
let op_ty = print_type(&op_val.ty);
format!("{} {}", op_ty, print_constant(op))
})
.collect();
if elements.is_empty() {
format!("{} zeroinitializer", ty_str)
} else {
format!("{} [{}]", ty_str, elements.join(", "))
}
}
SubclassKind::ConstantData => {
let ty_str = print_type(&v.ty);
if v.name == "zeroinitializer" {
return format!("{} zeroinitializer", ty_str);
}
if v.name.is_empty() {
format!("{} c\"\"", ty_str)
} else {
format!("{} c\"{}\"", ty_str, v.name)
}
}
SubclassKind::Constant => {
if v.name == "null" {
format!("{} null", print_type(&v.ty))
} else if v.name == "undef" {
format!("{} undef", print_type(&v.ty))
} else if v.name == "poison" {
format!("{} poison", print_type(&v.ty))
} else if v.name == "zeroinitializer" {
format!("{} zeroinitializer", print_type(&v.ty))
} else if v.name == "true" || v.name == "false" {
v.name.clone()
} else if v.ty.is_integer() {
let bits = v.ty.integer_bit_width();
if bits == 1 {
v.name.clone()
} else {
format!("i{} {}", bits, v.name)
}
} else if v.ty.is_float() {
format!("{} {}", print_type(&v.ty), v.name)
} else {
v.name.clone()
}
}
SubclassKind::BlockAddress => {
let func_name = if !v.operands.is_empty() {
v.operands[0].borrow().name.clone()
} else {
"<unknown>".to_string()
};
let bb_name = if v.operands.len() >= 2 {
v.operands[1].borrow().name.clone()
} else {
"<unknown>".to_string()
};
format!("blockaddress(@{}, %{})", func_name, bb_name)
}
SubclassKind::GlobalVariable | SubclassKind::Function | SubclassKind::GlobalAlias => {
if v.name.starts_with('@') {
v.name.clone()
} else {
format!("@{}", v.name)
}
}
_ => {
if v.name.is_empty() {
format!("%v{}", v.vid)
} else if v.name.starts_with('%') || v.name.starts_with('@') {
v.name.clone()
} else {
format!("%{}", v.name)
}
}
}
}
pub fn print_constant_gep(_ty: &Type, ops: &[ValueRef], _inbounds: bool) -> String {
if ops.is_empty() {
return "getelementptr (<ty>, <bad>)".to_string();
}
let ptr = print_constant(&ops[0]);
let ptr_ty = print_type(&ops[0].borrow().ty);
let indices: Vec<String> = ops[1..]
.iter()
.map(|op| {
let op_ty = print_type(&op.borrow().ty);
let op_val = print_constant(op);
format!("{} {}", op_ty, op_val)
})
.collect();
format!("getelementptr ({}, {})", ptr_ty, ptr,)
}
pub fn print_constant_bitcast(_from_ty: &Type, _to_ty: &Type, _op: &ValueRef) -> String {
let op_str = print_constant(_op);
let from_str = print_type(_from_ty);
let to_str = print_type(_to_ty);
format!("bitcast ({} {} to {})", from_str, op_str, to_str)
}
pub fn print_undef_value(ty: &Type) -> String {
format!("{} undef", print_type(ty))
}
pub fn print_poison_value(ty: &Type) -> String {
format!("{} poison", print_type(ty))
}
pub fn print_instruction(inst_val: &ValueRef, config: &AssemblyWriterConfig) -> String {
let mut out = String::new();
let indent = " ".repeat(config.indent);
let i = inst_val.borrow();
let lhs = if i.name.is_empty() || i.ty.is_void() {
String::new()
} else {
format!("%{} = ", i.name)
};
if let Some(opcode) = i.get_opcode() {
let mnemonic = opcode.name();
match opcode {
Opcode::Ret => {
if i.operands.is_empty() {
out.push_str(&format!("{}ret void\n", lhs));
} else {
let val = print_value_with_type(&i.operands[0]);
out.push_str(&format!("{}ret {}\n", lhs, val));
}
}
Opcode::Br => {
if i.operands.len() == 1 {
let label = bb_name(&i.operands[0]);
out.push_str(&format!("{}br label {}\n", lhs, label));
} else if i.operands.len() >= 3 {
let cond = print_value_with_type(&i.operands[0]);
let t_label = bb_name(&i.operands[1]);
let f_label = bb_name(&i.operands[2]);
out.push_str(&format!(
"{}br {}, label {}, label {}\n",
lhs, cond, t_label, f_label
));
} else {
out.push_str(&format!("{}br <malformed>\n", lhs));
}
}
Opcode::Switch => {
if i.operands.len() >= 2 {
let val = print_value_with_type(&i.operands[0]);
let default_label = bb_name(&i.operands[1]);
let mut cases = String::new();
let mut idx = 2;
while idx + 1 < i.operands.len() {
let case_val = print_value_with_type(&i.operands[idx]);
let case_label = bb_name(&i.operands[idx + 1]);
cases.push_str(&format!(" {} {},\n", case_val, case_label));
idx += 2;
}
out.push_str(&format!(
"{}switch {} label {} [\n{} ]\n",
lhs, val, default_label, cases
));
}
}
Opcode::IndirectBr => {
if !i.operands.is_empty() {
let addr = print_value_with_type(&i.operands[0]);
let labels: Vec<String> = i.operands[1..].iter().map(|b| bb_name(b)).collect();
out.push_str(&format!(
"{}indirectbr {}, [{}]\n",
lhs,
addr,
labels.join(", ")
));
}
}
Opcode::Invoke => {
if i.operands.len() >= 3 {
let callee = print_function_ref(&i.operands[0]);
let args = print_call_args(&i.operands[1..i.operands.len() - 2]);
let normal = bb_name(&i.operands[i.operands.len() - 2]);
let unwind = bb_name(&i.operands[i.operands.len() - 1]);
let ret_ty = print_type(&i.ty);
out.push_str(&format!(
"{}invoke {} {}({})\n to label {} unwind label {}\n",
lhs, ret_ty, callee, args, normal, unwind
));
}
}
Opcode::CallBr => {
if !i.operands.is_empty() {
let callee = print_function_ref(&i.operands[0]);
let args = print_call_args(&i.operands[1..]);
let ret_ty = print_type(&i.ty);
out.push_str(&format!("{}callbr {} {}({})\n", lhs, ret_ty, callee, args));
}
}
Opcode::Resume => {
if !i.operands.is_empty() {
let val = print_value_with_type(&i.operands[0]);
out.push_str(&format!("{}resume {}\n", lhs, val));
}
}
Opcode::CatchSwitch => {
out.push_str(&format!("{}catchswitch within none [\n", lhs));
for succ in &i.successors {
out.push_str(&format!(" to label {}\n", bb_name(succ)));
}
out.push_str("]\n");
}
Opcode::CatchRet => {
if !i.operands.is_empty() && !i.successors.is_empty() {
let pad = print_value_with_type(&i.operands[0]);
let succ = bb_name(&i.successors[0]);
out.push_str(&format!("{}catchret from {} to label {}\n", lhs, pad, succ));
}
}
Opcode::CleanupRet => {
if !i.operands.is_empty() {
let pad = print_value_with_type(&i.operands[0]);
if i.successors.is_empty() {
out.push_str(&format!(
"{}cleanupret from {} unwind to caller\n",
lhs, pad
));
} else {
let succ = bb_name(&i.successors[0]);
out.push_str(&format!(
"{}cleanupret from {} unwind label {}\n",
lhs, pad, succ
));
}
}
}
Opcode::Unreachable => {
out.push_str(&format!("{}unreachable\n", lhs));
}
Opcode::Add
| Opcode::FAdd
| Opcode::Sub
| Opcode::FSub
| Opcode::Mul
| Opcode::FMul
| Opcode::UDiv
| Opcode::SDiv
| Opcode::FDiv
| Opcode::URem
| Opcode::SRem
| Opcode::FRem
| Opcode::Shl
| Opcode::LShr
| Opcode::AShr
| Opcode::And
| Opcode::Or
| Opcode::Xor => {
if i.operands.len() >= 2 {
let (a, b) = (
print_value_with_type(&i.operands[0]),
print_value_with_type(&i.operands[1]),
);
out.push_str(&format!("{} {} {}\n", lhs, mnemonic, join_op2(a, b)));
}
}
Opcode::Alloca => {
let alloc_ty = print_type(&i.ty);
let mut extras = String::new();
if i.operands.len() >= 1 && !is_constant_one(&i.operands[0]) {
extras.push_str(&format!(
", {} {}",
print_type(&i.operands[0].borrow().ty),
print_constant(&i.operands[0])
));
}
if i.subclass_data != 0 {
extras.push_str(&format!(", align {}", 1u32 << i.subclass_data));
}
out.push_str(&format!("{}alloca {}{}\n", lhs, alloc_ty, extras));
}
Opcode::Load => {
let load_ty = print_type(&i.ty);
let ptr = print_value_with_type(&i.operands[0]);
let mut extras = String::new();
if i.subclass_data != 0 {
extras.push_str(&format!(", align {}", 1u32 << i.subclass_data));
}
out.push_str(&format!("{}load {}, ptr {}{}\n", lhs, load_ty, ptr, extras));
}
Opcode::Store => {
if i.operands.len() >= 2 {
let val = print_value_with_type(&i.operands[0]);
let ptr = print_value_with_type(&i.operands[1]);
out.push_str(&format!("store {}, ptr {}\n", val, ptr));
}
}
Opcode::GetElementPtr => {
if !i.operands.is_empty() {
let ptr = print_value_with_type(&i.operands[0]);
let indices: Vec<String> =
i.operands[1..].iter().map(print_value_with_type).collect();
let inb = if i.subclass_data & 1 != 0 {
" inbounds"
} else {
""
};
out.push_str(&format!(
"{}getelementptr{}{}, {}\n",
lhs,
inb,
ptr,
indices.join(", ")
));
}
}
Opcode::Fence => {
let ordering = fence_ordering_str(i.subclass_data);
out.push_str(&format!("{}fence {}\n", lhs, ordering));
}
Opcode::CmpXchg => {
if i.operands.len() >= 3 {
let ptr = print_value_with_type(&i.operands[0]);
let cmp = print_value_with_type(&i.operands[1]);
let new = print_value_with_type(&i.operands[2]);
out.push_str(&format!(
"{}cmpxchg ptr {}, {}, {} acq_rel monotonic\n",
lhs, ptr, cmp, new
));
}
}
Opcode::AtomicRMW => {
if i.operands.len() >= 2 {
let ptr = print_value_with_type(&i.operands[0]);
let val = print_value_with_type(&i.operands[1]);
let binop = atomicrmw_binop_str(i.subclass_data);
out.push_str(&format!(
"{}atomicrmw {} ptr {}, {} seq_cst\n",
lhs, binop, ptr, val
));
}
}
Opcode::Trunc
| Opcode::ZExt
| Opcode::SExt
| Opcode::FPTrunc
| Opcode::FPExt
| Opcode::FPToUI
| Opcode::FPToSI
| Opcode::UIToFP
| Opcode::SIToFP
| Opcode::PtrToInt
| Opcode::IntToPtr
| Opcode::BitCast
| Opcode::AddrSpaceCast => {
if !i.operands.is_empty() {
let val = print_value_with_type(&i.operands[0]);
let to_ty = print_type(&i.ty);
out.push_str(&format!("{}{} {} to {}\n", lhs, mnemonic, val, to_ty));
}
}
Opcode::ICmp => {
if i.operands.len() >= 2 {
let pred = icmp_pred_str(i.subclass_data);
let a = print_value_with_type(&i.operands[0]);
let b = print_value_with_type(&i.operands[1]);
out.push_str(&format!("{}icmp {} {}\n", lhs, pred, join_op2(a, b)));
}
}
Opcode::FCmp => {
if i.operands.len() >= 2 {
let pred = fcmp_pred_str(i.subclass_data);
let a = print_value_with_type(&i.operands[0]);
let b = print_value_with_type(&i.operands[1]);
out.push_str(&format!("{}fcmp {} {}\n", lhs, pred, join_op2(a, b)));
}
}
Opcode::Phi => {
let ty_str = print_type(&i.ty);
let mut incoming = Vec::new();
for chunk in i.operands.chunks(2) {
if chunk.len() == 2 {
let val = print_value_with_type(&chunk[0]);
let label = bb_name(&chunk[1]);
incoming.push(format!("[ {}, {} ]", val, label));
}
}
out.push_str(&format!("{}phi {} {}\n", lhs, ty_str, incoming.join(", ")));
}
Opcode::Call => {
if !i.operands.is_empty() {
let callee = print_function_ref(&i.operands[0]);
let args = print_call_args(&i.operands[1..]);
let ret_ty = print_type(&i.ty);
out.push_str(&format!("{}call {} {}({})\n", lhs, ret_ty, callee, args));
}
}
Opcode::Select => {
if i.operands.len() >= 3 {
let cond = print_value_with_type(&i.operands[0]);
let a = print_value_with_type(&i.operands[1]);
let b = print_value_with_type(&i.operands[2]);
out.push_str(&format!("{}select {}, {}, {}\n", lhs, cond, a, b));
}
}
Opcode::Freeze => {
if !i.operands.is_empty() {
let val = print_value_with_type(&i.operands[0]);
out.push_str(&format!("{}freeze {}\n", lhs, val));
}
}
Opcode::VAArg => {
if !i.operands.is_empty() {
let va = print_value_with_type(&i.operands[0]);
let ty_str = print_type(&i.ty);
out.push_str(&format!("{}va_arg {}, {}\n", lhs, ty_str, va));
}
}
Opcode::ExtractElement => {
if i.operands.len() >= 2 {
let vec = print_value_with_type(&i.operands[0]);
let idx = print_value_with_type(&i.operands[1]);
out.push_str(&format!("{}extractelement {}, {}\n", lhs, vec, idx));
}
}
Opcode::InsertElement => {
if i.operands.len() >= 3 {
let vec = print_value_with_type(&i.operands[0]);
let elem = print_value_with_type(&i.operands[1]);
let idx = print_value_with_type(&i.operands[2]);
out.push_str(&format!(
"{}insertelement {}, {}, {}\n",
lhs, vec, elem, idx
));
}
}
Opcode::ShuffleVector => {
if i.operands.len() >= 2 {
let a = print_value_with_type(&i.operands[0]);
let b = print_value_with_type(&i.operands[1]);
let mask = if i.operands.len() >= 3 {
print_constant(&i.operands[2])
} else {
"<i32 0, i32 1, i32 2, i32 3>".to_string()
};
out.push_str(&format!("{}shufflevector {}, {}, {}\n", lhs, a, b, mask));
}
}
Opcode::ExtractValue => {
if !i.operands.is_empty() {
let agg = print_value_with_type(&i.operands[0]);
let indices: Vec<String> =
i.operands[1..].iter().map(|v| print_constant(v)).collect();
out.push_str(&format!(
"{}extractvalue {}, {}\n",
lhs,
agg,
indices.join(", ")
));
}
}
Opcode::InsertValue => {
if i.operands.len() >= 2 {
let agg = print_value_with_type(&i.operands[0]);
let elem = print_value_with_type(&i.operands[1]);
let indices: Vec<String> =
i.operands[2..].iter().map(|v| print_constant(v)).collect();
out.push_str(&format!(
"{}insertvalue {}, {}, {}\n",
lhs,
agg,
elem,
indices.join(", ")
));
}
}
Opcode::LandingPad => {
let ty_str = print_type(&i.ty);
let mut clauses = String::new();
for clause in &i.operands {
let c_str = print_constant(clause);
let c_ty = print_type(&clause.borrow().ty);
clauses.push_str(&format!(" catch {} {}\n", c_ty, c_str));
}
if clauses.is_empty() {
out.push_str(&format!("{}{}landingpad {}\n", indent, lhs, ty_str));
} else {
out.push_str(&format!(
"{}{}landingpad {}\n{}\n",
indent, lhs, ty_str, clauses
));
}
}
Opcode::CatchPad => {
if !i.operands.is_empty() {
let args: Vec<String> = i.operands.iter().map(print_value_with_type).collect();
out.push_str(&format!("{}catchpad [{}]\n", lhs, args.join(", ")));
}
}
Opcode::CleanupPad => {
out.push_str(&format!("{}cleanuppad within none []\n", lhs));
}
_ => {
out.push_str(&format!("{}{} <unimpl>\n", lhs, mnemonic));
}
}
} else {
let void_ty = i.ty.is_void();
let num_ops = i.operands.len();
if void_ty && num_ops == 0 {
out.push_str(&format!("{}ret void\n", lhs));
} else if void_ty && num_ops == 1 && i.operands[0].borrow().is_basic_block() {
out.push_str(&format!("{}br label {}\n", lhs, bb_name(&i.operands[0])));
} else {
out.push_str(&format!("{}; unknown instruction\n", lhs));
}
}
out
}
fn bb_name(bb: &ValueRef) -> String {
let b = bb.borrow();
if b.name.is_empty() {
format!("%v{}", b.vid)
} else {
format!("%{}", b.name)
}
}
fn print_value_with_type(val: &ValueRef) -> String {
let v = val.borrow();
match v.subclass {
SubclassKind::Constant
| SubclassKind::ConstantInt
| SubclassKind::ConstantFP
| SubclassKind::ConstantAggregate
| SubclassKind::ConstantData => print_constant(val),
_ => {
let ty_str = print_type(&v.ty);
let name = if v.name.is_empty() {
format!("%v{}", v.vid)
} else if v.name.starts_with('%') || v.name.starts_with('@') {
v.name.clone()
} else {
format!("%{}", v.name)
};
format!("{} {}", ty_str, name)
}
}
}
fn print_function_ref(val: &ValueRef) -> String {
let v = val.borrow();
if v.name.starts_with('@') {
v.name.clone()
} else if v.name.is_empty() {
format!("@f{}", v.vid)
} else {
format!("@{}", v.name)
}
}
fn print_call_args(args: &[ValueRef]) -> String {
args.iter()
.map(|a| {
let a_val = a.borrow();
if a_val.is_basic_block() {
format!("label {}", bb_name(a))
} else {
print_value_with_type(a)
}
})
.collect::<Vec<_>>()
.join(", ")
}
fn join_op2(a: String, b: String) -> String {
format!("{}, {}", a, b)
}
fn is_constant_one(val: &ValueRef) -> bool {
let v = val.borrow();
matches!(
v.subclass,
SubclassKind::Constant | SubclassKind::ConstantInt
) && v.name == "1"
}
fn fence_ordering_str(data: u32) -> &'static str {
match data {
0 => "acquire",
1 => "release",
2 => "acq_rel",
3 => "seq_cst",
4 => "syncscope(\"singlethread\") seq_cst",
_ => "seq_cst",
}
}
fn atomicrmw_binop_str(data: u32) -> &'static str {
match data {
0 => "xchg",
1 => "add",
2 => "sub",
3 => "and",
4 => "nand",
5 => "or",
6 => "xor",
7 => "max",
8 => "min",
9 => "umax",
10 => "umin",
11 => "fadd",
12 => "fsub",
13 => "fmax",
14 => "fmin",
_ => "xchg",
}
}
fn icmp_pred_str(data: u32) -> &'static str {
match data {
0 => "eq",
1 => "ne",
2 => "ugt",
3 => "uge",
4 => "ult",
5 => "ule",
6 => "sgt",
7 => "sge",
8 => "slt",
9 => "sle",
_ => "eq",
}
}
fn fcmp_pred_str(data: u32) -> &'static str {
match data {
0 => "false",
1 => "oeq",
2 => "ogt",
3 => "oge",
4 => "olt",
5 => "ole",
6 => "one",
7 => "ord",
8 => "ueq",
9 => "ugt",
10 => "uge",
11 => "ult",
12 => "ule",
13 => "une",
14 => "uno",
15 => "true",
_ => "oeq",
}
}
pub fn print_metadata(md_id: u32, _module: &Module) -> String {
format!("!{}", md_id)
}
pub fn print_md_node(node_id: u32, _operands: &[u32], _module: &Module) -> String {
let mut out = format!("!{} = !{{", node_id);
out.push_str("}");
out
}
pub fn print_named_metadata(name: &str, node_ids: &[u32], _module: &Module) -> String {
let nodes: Vec<String> = node_ids.iter().map(|id| format!("!{}", id)).collect();
format!("!{} = !{{{}}}", name, nodes.join(", "))
}
pub fn print_metadata_attachment(md_map: &HashMap<u32, u32>) -> String {
if md_map.is_empty() {
return String::new();
}
let mut out = String::new();
for (&kind, &node) in md_map {
out.push_str(&format!(", !{} !{}", kind, node));
}
out
}
pub fn print_attribute_group(group_id: u32, attrs: &AttributeList) -> String {
let mut out = format!("attributes #{} = {{", group_id);
let parts: Vec<String> = attrs.fn_attrs.iter().map(|a| print_attribute(a)).collect();
if parts.is_empty() {
out.push('}');
} else {
out.push_str(&format!(" {} }}", parts.join(" ")));
}
out
}
pub fn print_attribute(attr: &AttributeKind) -> String {
use crate::attributes::FramePointerKind;
match attr {
AttributeKind::NoInline => "noinline".to_string(),
AttributeKind::AlwaysInline => "alwaysinline".to_string(),
AttributeKind::OptimizeNone => "optnone".to_string(),
AttributeKind::OptimizeForSize => "optsize".to_string(),
AttributeKind::OptimizeForSpeed => "optforspeed".to_string(),
AttributeKind::ReadNone => "readnone".to_string(),
AttributeKind::ReadOnly => "readonly".to_string(),
AttributeKind::WriteOnly => "writeonly".to_string(),
AttributeKind::ArgMemOnly => "argmemonly".to_string(),
AttributeKind::InaccessibleMemOnly => "inaccessiblememonly".to_string(),
AttributeKind::InaccessibleMemOrArgMemOnly => "inaccessiblemem_or_argmemonly".to_string(),
AttributeKind::NoUnwind => "nounwind".to_string(),
AttributeKind::NoReturn => "noreturn".to_string(),
AttributeKind::WillReturn => "willreturn".to_string(),
AttributeKind::Ssp => "ssp".to_string(),
AttributeKind::SspReq => "sspreq".to_string(),
AttributeKind::SspStrong => "sspstrong".to_string(),
AttributeKind::SanitizeAddress => "sanitize_address".to_string(),
AttributeKind::SanitizeThread => "sanitize_thread".to_string(),
AttributeKind::SanitizeMemory => "sanitize_memory".to_string(),
AttributeKind::SanitizeHwAddress => "sanitize_hwaddress".to_string(),
AttributeKind::NoSanitize => "nosanitize".to_string(),
AttributeKind::SanitizeCoverage => "sanitize_coverage".to_string(),
AttributeKind::ShadowCallStack => "shadowcallstack".to_string(),
AttributeKind::SpeculativeLoadHardening => "speculative_load_hardening".to_string(),
AttributeKind::NoCfCheck => "nocf_check".to_string(),
AttributeKind::InlineHint => "inlinehint".to_string(),
AttributeKind::NoMerge => "nomerge".to_string(),
AttributeKind::ReturnsTwice => "returns_twice".to_string(),
AttributeKind::JumpTable => "jumptable".to_string(),
AttributeKind::MustProgress => "mustprogress".to_string(),
AttributeKind::Convergent => "convergent".to_string(),
AttributeKind::NoDivergenceSource => "noduplicatesource".to_string(),
AttributeKind::NoProfile => "noprofile".to_string(),
AttributeKind::SkipProfile => "skipprofile".to_string(),
AttributeKind::OptDebug => "optdebug".to_string(),
AttributeKind::OptForFuzzing => "optforfuzzing".to_string(),
AttributeKind::AllocSize(n, m) => {
if let Some(m_val) = m {
format!("allocsize({}, {})", n, m_val)
} else {
format!("allocsize({})", n)
}
}
AttributeKind::AllocPtr => "allocptr".to_string(),
AttributeKind::NoBuiltin => "nobuiltin".to_string(),
AttributeKind::NoCallback => "nocallback".to_string(),
AttributeKind::NonLazyBind => "nonlazybind".to_string(),
AttributeKind::AlignStack(n) => format!("alignstack({})", n),
AttributeKind::Naked => "naked".to_string(),
AttributeKind::NoImplicitFloat => "noimplicitfloat".to_string(),
AttributeKind::NoRedZone => "noredzone".to_string(),
AttributeKind::UWTable => "uwtable".to_string(),
AttributeKind::FramePointer(fp_kind) => {
let kind_str = match fp_kind {
FramePointerKind::All => "all",
FramePointerKind::NonLeaf => "non-leaf",
FramePointerKind::None => "none",
};
format!("frame-pointer={}", kind_str)
}
AttributeKind::SafeStack => "safestack".to_string(),
AttributeKind::SanitizeMemTag => "sanitize_memtag".to_string(),
AttributeKind::DisableSanitizerInstrumentation => {
"disable_sanitizer_instrumentation".to_string()
}
AttributeKind::Hotpatch => "hotpatch".to_string(),
AttributeKind::FnRetThunkExtern => "fn_ret_thunk_extern".to_string(),
AttributeKind::StrictFP => "strictfp".to_string(),
AttributeKind::NoFPClass(classes) => format!("nofpclass({})", classes),
AttributeKind::VScaleRange(min, max) => format!("vscale_range({}, {})", min, max),
AttributeKind::Speculatable => "speculatable".to_string(),
AttributeKind::NoDuplicate => "noduplicate".to_string(),
AttributeKind::NoRecurse => "norecurse".to_string(),
AttributeKind::NoSync => "nosync".to_string(),
AttributeKind::NoFree => "nofree".to_string(),
AttributeKind::Cold => "cold".to_string(),
AttributeKind::Hot => "hot".to_string(),
AttributeKind::Minsize => "minsize".to_string(),
AttributeKind::NullPointerIsValid => "null_pointer_is_valid".to_string(),
AttributeKind::UseSampleProfile => "use_sample_profile".to_string(),
AttributeKind::StringAttribute(key, val) => {
if val.is_empty() {
format!("\"{}\"", key)
} else {
format!("\"{}\"=\"{}\"", key, val)
}
}
}
}
pub fn print_attribute_groups(module: &Module) -> String {
let mut out = String::new();
let mut ids: Vec<u32> = module.attr_groups.keys().copied().collect();
ids.sort();
for id in ids {
if let Some(attrs) = module.attr_groups.get(&id) {
out.push_str(&print_attribute_group(id, attrs));
out.push('\n');
}
}
out
}
pub fn print_function_attributes(attr_group_id: u32) -> String {
format!(" #{} ", attr_group_id)
}
pub fn print_module(module: &Module, config: &AssemblyWriterConfig) -> String {
let mut out = String::new();
out.push_str(&format!("; ModuleID = '{}'\n", module.module_identifier));
if !module.source_filename.is_empty() {
out.push_str(&format!(
"source_filename = \"{}\"\n",
module.source_filename
));
}
if let Some(ref triple) = module.target_triple {
out.push_str(&format!("target triple = \"{}\"\n", triple));
}
if let Some(ref dl) = module.data_layout {
out.push_str(&format!("target datalayout = \"{}\"\n", dl));
}
if let Some(ref sdk) = module.sdk_version {
out.push_str(&format!("!llvm.sdk.version = !{{!{{}}\n"));
let _ = sdk;
}
if !module.inline_asm.is_empty() {
out.push_str(&format!(
"module asm \"{}\"\n",
module.inline_asm.replace('\n', "\\0A")
));
}
if !module.types.is_empty() {
out.push_str(&format!("{}", print_type_definitions(module)));
}
if config.show_comdat && !module.comdats.is_empty() {
for (name, comdat) in &module.comdats {
out.push_str(&format!("$comdat_{} = comdat {}\n", name, comdat.kind));
}
}
if config.show_attribute_groups && !module.attr_groups.is_empty() {
out.push_str(&print_attribute_groups(module));
if !module.attr_groups.is_empty() {
out.push('\n');
}
}
for gv in &module.globals {
print_global_variable(gv, config, &mut out);
}
for alias in &module.aliases {
print_alias_declaration(alias, config, &mut out);
}
for ifunc in &module.ifuncs {
print_ifunc_declaration(ifunc, config, &mut out);
}
for func in &module.functions {
let is_definition = {
let f = func.borrow();
f.is_function() && f.num_operands > 0
};
if is_definition {
print_function_definition_full(func, config, &mut out);
} else {
print_function_declaration_full(func, config, &mut out);
}
out.push('\n');
}
if config.show_use_list_order {
out.push_str("; uselistorder directives omitted\n");
}
if !module.named_metadata.is_empty() {
for (name, node_ids) in &module.named_metadata {
out.push_str(&print_named_metadata(name, node_ids, module));
out.push('\n');
}
}
if !module.flags.is_empty() {
let flag_ids: Vec<String> = module
.flags
.iter()
.map(|f| {
let val_str = match &f.value {
crate::module::MetadataValue::Int(v) => format!("i32 {}", v),
crate::module::MetadataValue::String(s) => format!("!\"{}\"", s),
crate::module::MetadataValue::Node(ids) => {
let inner: Vec<String> = ids.iter().map(|id| format!("!{}", id)).collect();
format!("!{{{}}}", inner.join(", "))
}
crate::module::MetadataValue::Null => "!{{}}".to_string(),
};
format!("!{{{}, !\"{}\", {}}}", f.behavior, f.key, val_str)
})
.collect();
if !flag_ids.is_empty() {
out.push_str(&format!(
"!llvm.module.flags = !{{{}}}\n",
flag_ids.join(", ")
));
}
}
out
}
fn print_global_variable(gv: &ValueRef, _config: &AssemblyWriterConfig, out: &mut String) {
let v = gv.borrow();
let name = if v.name.starts_with('@') {
v.name.clone()
} else {
format!("@{}", v.name)
};
let linkage = infer_linkage_str(&v);
let visibility = infer_visibility_str(&v);
let ty_str = print_type(&v.ty);
let init = if v.operands.is_empty() {
format!("{} undef", ty_str)
} else {
print_constant(&v.operands[0])
};
out.push_str(&format!(
"{}{}{} = {} {}\n",
name,
linkage,
visibility,
if linkage.is_empty() && visibility.is_empty() {
""
} else {
" "
},
ty_str
));
let _ = (name, linkage, visibility, init);
}
fn print_alias_declaration(alias: &ValueRef, _config: &AssemblyWriterConfig, out: &mut String) {
let v = alias.borrow();
out.push_str(&format!("@{} = alias {}\n", v.name, print_type(&v.ty)));
}
fn print_ifunc_declaration(ifunc: &ValueRef, _config: &AssemblyWriterConfig, out: &mut String) {
let v = ifunc.borrow();
out.push_str(&format!("@{} = ifunc {}\n", v.name, print_type(&v.ty)));
}
fn print_function_declaration_full(
func: &ValueRef,
config: &AssemblyWriterConfig,
out: &mut String,
) {
let f = func.borrow();
let name = if f.name.starts_with('@') {
f.name.clone()
} else {
format!("@{}", f.name)
};
let linkage = infer_linkage_str(&f);
let visibility = infer_visibility_str(&f);
let ret_ty = if let Some(ref rt) = f.return_type {
print_type(rt)
} else {
"void".to_string()
};
let param_strs: Vec<String> = f
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::Argument)
.map(|op| print_type(&op.borrow().ty))
.collect();
let vararg = if param_strs.is_empty() && f.subclass_data & 1 != 0 {
"...".to_string()
} else {
String::new()
};
let attr_str = if !f.metadata.is_empty() {
print_metadata_attachment(&f.metadata)
} else {
String::new()
};
out.push_str(&format!(
"declare{}{}{} {} @{}({}{}){}\n",
linkage,
visibility,
"",
ret_ty,
f.name,
param_strs.join(", "),
if vararg.is_empty() { "" } else { ", ..." },
attr_str
));
let _ = config;
}
fn print_function_definition_full(
func: &ValueRef,
config: &AssemblyWriterConfig,
out: &mut String,
) {
let f = func.borrow();
let name = if f.name.starts_with('@') {
f.name.clone()
} else {
format!("@{}", f.name)
};
let linkage = infer_linkage_str(&f);
let visibility = infer_visibility_str(&f);
let ret_ty = if let Some(ref rt) = f.return_type {
print_type(rt)
} else {
"void".to_string()
};
let param_strs: Vec<String> = f
.operands
.iter()
.filter(|op| op.borrow().subclass == SubclassKind::Argument)
.map(|op| {
let p = op.borrow();
format!("{} {}", print_type(&p.ty), print_value_with_type(op))
})
.collect();
out.push_str(&format!(
"define{}{} {} {}({}) {{\n",
linkage,
visibility,
ret_ty,
name,
param_strs.join(", ")
));
out.push_str(&format!(
" ; function body: {} operands\n",
f.operands.len()
));
out.push_str("}\n");
let _ = config;
}
fn infer_linkage_str(v: &Value) -> String {
if v.name.contains("internal") {
return " internal".to_string();
}
if v.name.contains("private") {
return " private".to_string();
}
String::new()
}
fn infer_visibility_str(v: &Value) -> String {
if v.name.contains("hidden") {
return " hidden".to_string();
}
if v.name.contains("protected") {
return " protected".to_string();
}
String::new()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::LLVMContext;
use crate::function;
use crate::instruction;
use crate::module::Module;
#[test]
fn test_write_empty_module() {
let mut ctx = LLVMContext::new();
let module = ctx.create_module("empty");
module.set_target_triple("x86_64-unknown-linux-gnu");
let text = write_assembly(module);
assert!(text.contains("target triple"));
assert!(text.contains("x86_64"));
}
#[test]
fn test_write_function_declaration() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let f = function::new_function("printf", i32_ty, &[]);
m.add_function(f);
let text = write_assembly(&m);
assert!(text.contains("declare"));
assert!(text.contains("@printf"));
}
#[test]
fn test_write_function_with_body() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("main", i32_ty, &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let zero = crate::constants::const_i32(0);
let ret = instruction::ret_val(zero);
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("define"));
assert!(text.contains("@main"));
assert!(text.contains("entry:"));
assert!(text.contains("ret"));
}
#[test]
fn test_type_to_str_primitives() {
assert_eq!(type_to_str(&Type::void()), "void");
assert_eq!(type_to_str(&Type::i32()), "i32");
assert_eq!(type_to_str(&Type::i1()), "i1");
assert_eq!(type_to_str(&Type::float()), "float");
assert_eq!(type_to_str(&Type::double()), "double");
assert_eq!(type_to_str(&Type::pointer(0)), "ptr");
}
#[test]
fn test_write_roundtrip_smoke() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
m.set_target_triple("x86_64-unknown-linux-gnu");
let f = function::new_function("foo", i32_ty, &[]);
m.add_function(f);
let text = write_assembly(&m);
let parsed = crate::asm_parser::parse_assembly(&text);
assert!(parsed.is_some(), "Writer output should be parseable");
let parsed_mod = parsed.unwrap();
assert!(!parsed_mod.functions.is_empty());
}
#[test]
fn test_write_function_with_body_roundtrip() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
m.set_target_triple("x86_64-unknown-linux-gnu");
let func = function::new_function("main", i32_ty, &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let zero = crate::constants::const_i32(0);
let ret = instruction::ret_val(zero);
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
let parsed = crate::asm_parser::parse_assembly(&text);
assert!(parsed.is_some());
}
#[test]
fn test_write_type_pointer_addrspace() {
assert_eq!(type_to_str(&Type::pointer(5)), "ptr addrspace(5)");
}
#[test]
fn test_write_type_fixed_vector() {
let elem_id = Type::i32().id;
let v = Type::fixed_vector_with(4, elem_id);
let s = type_to_str(&v);
assert!(s.contains("4 x"));
}
#[test]
fn test_write_type_scalable_vector() {
let elem_id = Type::float().id;
let v = Type::scalable_vector_with(4, elem_id);
let s = type_to_str(&v);
assert!(s.contains("vscale"));
assert!(s.contains("4 x"));
}
#[test]
fn test_write_type_array() {
let elem_id = Type::i32().id;
let a = Type::array_with(10, elem_id);
let s = type_to_str(&a);
assert!(s.contains("[10 x"));
}
#[test]
fn test_write_type_struct_named() {
let s = Type::struct_named_with("Foo".to_string(), false, vec![]);
let text = type_to_str(&s);
assert!(text.contains("%Foo"));
}
#[test]
fn test_write_type_struct_packed() {
let s = Type::struct_named_with("Bar".to_string(), true, vec![]);
let text = type_to_str(&s);
assert!(text.contains("<{"));
}
#[test]
fn test_write_type_function() {
let ret_id = Type::i32().id;
let params = vec![Type::i32().id, Type::float().id];
let ft = Type::function_type_with(ret_id, params, false);
let text = type_to_str(&ft);
assert!(text.contains("("));
}
#[test]
fn test_write_alloca_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", Type::void(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let alloca = instruction::alloca(i32_ty);
alloca.borrow_mut().name = "ptr".to_string();
let ret = instruction::ret_void();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(alloca);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("alloca"));
}
#[test]
fn test_write_load_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", Type::void(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let alloca = instruction::alloca(i32_ty.clone());
alloca.borrow_mut().name = "ptr".to_string();
let load = instruction::load(i32_ty.clone(), alloca.clone());
load.borrow_mut().name = "val".to_string();
let ret = instruction::ret_void();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(alloca);
entry.borrow_mut().push_operand(load);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("load"));
assert!(text.contains("ptr %ptr"));
}
#[test]
fn test_write_store_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", Type::void(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let alloca = instruction::alloca(i32_ty.clone());
alloca.borrow_mut().name = "ptr".to_string();
let val = crate::constants::const_i32(42);
let store = instruction::store(val, alloca.clone());
let ret = instruction::ret_void();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(alloca);
entry.borrow_mut().push_operand(store);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("store"));
}
#[test]
fn test_write_gep_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", Type::pointer(0), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let ptr = crate::value::valref(crate::value::Value::new(Type::pointer(0)).named("p"));
let idx = crate::constants::const_i32(1);
let gep = instruction::getelementptr(i32_ty, ptr, vec![idx]);
gep.borrow_mut().name = "gep".to_string();
let ret = instruction::ret_val(gep.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(gep);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("getelementptr"));
}
#[test]
fn test_write_call_instruction() {
let mut ctx = LLVMContext::new();
let void_ty = Type::void();
let mut m = Module::new("test");
let callee = function::new_function("helper", void_ty.clone(), &[]);
m.add_function(callee.clone());
let func = function::new_function("main", void_ty.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let call = instruction::call(void_ty.clone(), callee, vec![]);
call.borrow_mut().name = "tmp".to_string();
let ret = instruction::ret_void();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(call);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("call"));
assert!(text.contains("@helper"));
}
#[test]
fn test_write_phi_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", i32_ty.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let bb_a = crate::basic_block::new_basic_block("a");
let bb_b = crate::basic_block::new_basic_block("b");
let merge = crate::basic_block::new_basic_block("merge");
let val1 = crate::constants::const_i32(1);
let val2 = crate::constants::const_i32(2);
let phi = instruction::phi(i32_ty, vec![(val1, bb_a.clone()), (val2, bb_b.clone())]);
phi.borrow_mut().name = "x".to_string();
let ret = instruction::ret_val(phi.clone());
func.borrow_mut().push_operand(entry.clone());
func.borrow_mut().push_operand(bb_a);
func.borrow_mut().push_operand(bb_b);
func.borrow_mut().push_operand(merge.clone());
merge.borrow_mut().push_operand(phi);
merge.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("phi"));
}
#[test]
fn test_write_select_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", i32_ty, &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let cond = crate::constants::const_bool(true);
let a = crate::constants::const_i32(10);
let b = crate::constants::const_i32(20);
let sel = instruction::select(cond, a, b);
sel.borrow_mut().name = "x".to_string();
let ret = instruction::ret_val(sel.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(sel);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("select"));
}
#[test]
fn test_write_icmp_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", Type::i1(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let a = crate::constants::const_i32(1);
let b = crate::constants::const_i32(2);
let cmp = instruction::icmp(crate::opcode::ICmpPred::Eq, a, b);
cmp.borrow_mut().name = "cmp".to_string();
let ret = instruction::ret_val(cmp.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(cmp);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("icmp"));
}
#[test]
fn test_write_fcmp_instruction() {
let mut ctx = LLVMContext::new();
let float_ty = Type::float();
let mut m = Module::new("test");
let func = function::new_function("test", Type::i1(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let a = crate::constants::const_float(1.0);
let b = crate::constants::const_float(2.0);
let cmp = instruction::fcmp(crate::opcode::FCmpPred::Oeq, a, b);
cmp.borrow_mut().name = "cmp".to_string();
let ret = instruction::ret_val(cmp.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(cmp);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("fcmp"));
}
#[test]
fn test_write_extractelement_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", i32_ty.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let vec = crate::value::valref(
crate::value::Value::new(Type::fixed_vector_with(4, Type::i32().id)).named("v"),
);
let idx = crate::constants::const_i32(0);
let ee = crate::value::valref(
crate::value::Value::new(i32_ty)
.with_subclass(SubclassKind::Instruction)
.named("x"),
);
ee.borrow_mut()
.set_opcode(crate::opcode::Opcode::ExtractElement);
ee.borrow_mut().push_operand(vec);
ee.borrow_mut().push_operand(idx);
let ret = instruction::ret_val(ee.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(ee);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("extractelement"));
}
#[test]
fn test_write_insertelement_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let vec4 = Type::fixed_vector_with(4, Type::i32().id);
let func = function::new_function("test", vec4.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let vec = crate::value::valref(crate::value::Value::new(vec4.clone()).named("v"));
let elem = crate::constants::const_i32(42);
let idx = crate::constants::const_i32(0);
let ie = crate::value::valref(
crate::value::Value::new(vec4)
.with_subclass(SubclassKind::Instruction)
.named("x"),
);
ie.borrow_mut()
.set_opcode(crate::opcode::Opcode::InsertElement);
ie.borrow_mut().push_operand(vec);
ie.borrow_mut().push_operand(elem);
ie.borrow_mut().push_operand(idx);
let ret = instruction::ret_val(ie.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(ie);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("insertelement"));
}
#[test]
fn test_write_shufflevector_instruction() {
let mut ctx = LLVMContext::new();
let vec4 = Type::fixed_vector_with(4, Type::i32().id);
let mut m = Module::new("test");
let func = function::new_function("test", vec4.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let a = crate::value::valref(crate::value::Value::new(vec4.clone()).named("a"));
let b = crate::value::valref(crate::value::Value::new(vec4.clone()).named("b"));
let sv = crate::value::valref(
crate::value::Value::new(vec4)
.with_subclass(SubclassKind::Instruction)
.named("x"),
);
sv.borrow_mut()
.set_opcode(crate::opcode::Opcode::ShuffleVector);
sv.borrow_mut().push_operand(a);
sv.borrow_mut().push_operand(b);
let ret = instruction::ret_val(sv.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(sv);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("shufflevector"));
}
#[test]
fn test_write_extractvalue_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", i32_ty.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let agg = crate::value::valref(crate::value::Value::new(i32_ty).named("s"));
let ev = crate::value::valref(
crate::value::Value::new(Type::i32())
.with_subclass(SubclassKind::Instruction)
.named("x"),
);
ev.borrow_mut()
.set_opcode(crate::opcode::Opcode::ExtractValue);
ev.borrow_mut().push_operand(agg);
let ret = instruction::ret_val(ev.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(ev);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("extractvalue"));
}
#[test]
fn test_write_insertvalue_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", i32_ty.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let agg = crate::value::valref(crate::value::Value::new(i32_ty.clone()).named("s"));
let elem = crate::constants::const_i32(42);
let iv = crate::value::valref(
crate::value::Value::new(i32_ty)
.with_subclass(SubclassKind::Instruction)
.named("x"),
);
iv.borrow_mut()
.set_opcode(crate::opcode::Opcode::InsertValue);
iv.borrow_mut().push_operand(agg);
iv.borrow_mut().push_operand(elem);
let ret = instruction::ret_val(iv.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(iv);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("insertvalue"));
}
#[test]
fn test_write_atomicrmw_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", i32_ty.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let ptr = crate::value::valref(crate::value::Value::new(Type::pointer(0)).named("p"));
let val = crate::constants::const_i32(1);
let armw = crate::value::valref(
crate::value::Value::new(i32_ty)
.with_subclass(SubclassKind::Instruction)
.named("old"),
);
armw.borrow_mut()
.set_opcode(crate::opcode::Opcode::AtomicRMW);
armw.borrow_mut().push_operand(ptr);
armw.borrow_mut().push_operand(val);
let ret = instruction::ret_val(armw.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(armw);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("atomicrmw"));
}
#[test]
fn test_write_fence_instruction() {
let mut ctx = LLVMContext::new();
let mut m = Module::new("test");
let func = function::new_function("test", Type::void(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let fence = crate::value::valref(
crate::value::Value::new(Type::void()).with_subclass(SubclassKind::Instruction),
);
fence.borrow_mut().set_opcode(crate::opcode::Opcode::Fence);
let ret = instruction::ret_void();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(fence);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("fence"));
}
#[test]
fn test_write_cmpxchg_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", Type::void(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let ptr = crate::value::valref(crate::value::Value::new(Type::pointer(0)).named("p"));
let old = crate::constants::const_i32(0);
let new = crate::constants::const_i32(1);
let cx = crate::value::valref(
crate::value::Value::new(Type::void())
.with_subclass(SubclassKind::Instruction)
.named("x"),
);
cx.borrow_mut().set_opcode(crate::opcode::Opcode::CmpXchg);
cx.borrow_mut().push_operand(ptr);
cx.borrow_mut().push_operand(old);
cx.borrow_mut().push_operand(new);
let ret = instruction::ret_void();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(cx);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("cmpxchg"));
}
#[test]
fn test_write_switch_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let mut m = Module::new("test");
let func = function::new_function("test", Type::void(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let val = crate::constants::const_i32(0);
let default_bb = crate::basic_block::new_basic_block("default");
let case1 = crate::basic_block::new_basic_block("case1");
let sw = instruction::switch(
val,
default_bb,
vec![(crate::constants::const_i32(1), case1)],
);
sw.borrow_mut().name = "".to_string();
let ret = instruction::ret_void();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(sw);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("switch"));
}
#[test]
fn test_write_unreachable_instruction() {
let mut ctx = LLVMContext::new();
let mut m = Module::new("test");
let func = function::new_function("test", Type::void(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let unreach = instruction::unreachable();
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(unreach);
let text = write_assembly(&m);
assert!(text.contains("unreachable"));
}
#[test]
fn test_write_cast_instruction() {
let mut ctx = LLVMContext::new();
let i32_ty = ctx.i32();
let i64_ty = ctx.i64();
let mut m = Module::new("test");
let func = function::new_function("test", i64_ty.clone(), &[]);
m.add_function(func.clone());
let entry = crate::basic_block::new_basic_block("entry");
let val = crate::constants::const_i32(42);
let sext = instruction::sext(val, i64_ty);
sext.borrow_mut().name = "ext".to_string();
let ret = instruction::ret_val(sext.clone());
func.borrow_mut().push_operand(entry.clone());
entry.borrow_mut().push_operand(sext);
entry.borrow_mut().push_operand(ret);
let text = write_assembly(&m);
assert!(text.contains("sext"));
assert!(text.contains("to"));
}
#[test]
fn test_write_roundtrip_alloca_load_store() {
let input = r#"
define void @test() {
entry:
%ptr = alloca i32
store i32 42, ptr %ptr
ret void
}
"#;
let parsed = crate::asm_parser::parse_assembly(input).unwrap();
let output = write_assembly(&parsed);
assert!(output.contains("alloca"));
let re_parsed = crate::asm_parser::parse_assembly(&output);
assert!(re_parsed.is_some(), "Writer output should be re-parseable");
}
#[test]
fn test_write_roundtrip_call() {
let input = r#"
declare void @helper()
define void @main() {
entry:
call void @helper()
ret void
}
"#;
let parsed = crate::asm_parser::parse_assembly(input).unwrap();
let output = write_assembly(&parsed);
assert!(output.contains("call"));
assert!(output.contains("@helper"));
let re_parsed = crate::asm_parser::parse_assembly(&output);
assert!(re_parsed.is_some(), "Writer output should be re-parseable");
}
#[test]
fn test_write_value_constant() {
let zero = crate::constants::const_i32(0);
let s = value_to_str(&zero);
assert!(!s.is_empty());
}
#[test]
fn test_write_value_global() {
let gv = crate::value::valref(
crate::value::Value::new(Type::i32())
.with_subclass(SubclassKind::GlobalVariable)
.named("@my_global"),
);
let s = value_to_str(&gv);
assert!(s.starts_with('@'));
}
#[test]
fn test_module_header_writing() {
let mut m = Module::new("test");
m.source_filename = "test.c".into();
m.set_target_triple("x86_64-unknown-linux-gnu");
m.set_data_layout("e-m:e-p270:32:32");
let text = write_assembly(&m);
assert!(text.contains("source_filename"));
assert!(text.contains("test.c"));
assert!(text.contains("target triple"));
assert!(text.contains("target datalayout"));
}
#[test]
fn test_type_to_str_full_coverage() {
assert!(!type_to_str(&Type::half()).is_empty());
assert!(!type_to_str(&Type::bfloat()).is_empty());
assert!(!type_to_str(&Type::fp128()).is_empty());
assert!(!type_to_str(&Type::x86_fp80()).is_empty());
assert!(!type_to_str(&Type::ppc_fp128()).is_empty());
assert!(!type_to_str(&Type::label()).is_empty());
assert!(!type_to_str(&Type::metadata()).is_empty());
assert!(!type_to_str(&Type::token()).is_empty());
assert!(!type_to_str(&Type::x86_amx()).is_empty());
assert!(!type_to_str(&Type::x86_mmx()).is_empty());
}
}