use llvm_native_core::opcode::Opcode;
use llvm_native_core::types::{TypeId, TypeKind};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt;
use std::fmt::Write;
#[derive(Debug, Clone)]
pub struct AsmWriterConfig {
pub use_color: bool,
pub annotate_code: bool,
pub print_debug_info: bool,
pub print_metadata: bool,
pub print_module_summary: bool,
pub print_use_list_order: bool,
pub max_line_width: usize,
pub indent_size: usize,
}
impl Default for AsmWriterConfig {
fn default() -> Self {
AsmWriterConfig {
use_color: false,
annotate_code: false,
print_debug_info: true,
print_metadata: true,
print_module_summary: false,
print_use_list_order: false,
max_line_width: 120,
indent_size: 2,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SlotTracker {
pub local_slots: HashMap<String, u32>,
pub slot_names: HashMap<u32, String>,
next_slot: u32,
pub global_slots: HashMap<String, u32>,
pub metadata_slots: HashMap<u64, u32>,
}
impl SlotTracker {
pub fn new() -> Self {
SlotTracker::default()
}
pub fn create_local_slot(&mut self, name: &str) -> u32 {
if let Some(slot) = self.local_slots.get(name) {
return *slot;
}
let slot = self.next_slot;
self.next_slot += 1;
self.local_slots.insert(name.to_string(), slot);
self.slot_names.insert(slot, name.to_string());
slot
}
pub fn get_local_slot(&self, name: &str) -> Option<u32> {
self.local_slots.get(name).copied()
}
pub fn get_slot_name(&self, slot: u32) -> Option<&str> {
self.slot_names.get(&slot).map(|s| s.as_str())
}
}
pub struct TypePrinter;
impl TypePrinter {
pub fn print(ty: &TypeKind) -> String {
match ty {
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::X86MMX => "x86_mmx".to_string(),
TypeKind::X86AMX => "x86_amx".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 type]", len) }
TypeKind::Struct { is_packed, .. } => {
if *is_packed {
"<{ ... }>".to_string()
} else {
"{ ... }".to_string()
}
}
TypeKind::FixedVector {
len,
element_type_id: _,
} => {
format!("<{} x type>", len)
}
TypeKind::ScalableVector {
min_elems,
element_type_id: _,
} => {
format!("<vscale x {} x type>", min_elems)
}
TypeKind::Function { is_vararg, .. } => {
let va = if *is_vararg { ", ..." } else { "" };
format!("type (...{})", va)
}
}
}
}
pub struct AssemblyPrinter {
pub config: AsmWriterConfig,
pub output: String,
pub indent_level: usize,
pub slot_tracker: SlotTracker,
}
impl AssemblyPrinter {
pub fn new(config: AsmWriterConfig) -> Self {
AssemblyPrinter {
config,
output: String::new(),
indent_level: 0,
slot_tracker: SlotTracker::new(),
}
}
pub fn emit(&mut self, s: &str) {
self.output.push_str(s);
}
pub fn emitln(&mut self, s: &str) {
self.emit_indent();
self.output.push_str(s);
self.output.push('\n');
}
pub fn emit_indent(&mut self) {
for _ in 0..self.indent_level {
self.output.push_str(&" ".repeat(self.config.indent_size));
}
}
pub fn newline(&mut self) {
self.output.push('\n');
}
pub fn print_module_header(
&mut self,
source_filename: Option<&str>,
target_triple: Option<&str>,
data_layout: Option<&str>,
module_id: Option<&str>,
) {
if let Some(id) = module_id {
self.emitln(&format!("; ModuleID = '{}'", id));
}
if let Some(sf) = source_filename {
self.emitln(&format!("source_filename = \"{}\"", sf));
}
if let Some(dl) = data_layout {
self.emitln(&format!("target datalayout = \"{}\"", dl));
}
if let Some(triple) = target_triple {
self.emitln(&format!("target triple = \"{}\"", triple));
}
}
pub fn print_global(
&mut self,
name: &str,
linkage: &str,
ty: &str,
initializer: Option<&str>,
align: Option<u32>,
is_constant: bool,
) {
let const_str = if is_constant { "constant" } else { "global" };
self.emit_indent();
write!(self.output, "@{} = {} {} {}", name, linkage, const_str, ty).unwrap();
if let Some(init) = initializer {
write!(self.output, " {}", init).unwrap();
}
if let Some(a) = align {
write!(self.output, ", align {}", a).unwrap();
}
self.output.push('\n');
}
pub fn print_function_header(
&mut self,
linkage: &str,
visibility: &str,
ret_ty: &str,
name: &str,
params: &[(String, String)], is_vararg: bool,
attrs: &[String],
) {
self.emit_indent();
write!(
self.output,
"define {} {} {} @{}(",
linkage, visibility, ret_ty, name
)
.unwrap();
for (i, (pty, pname)) in params.iter().enumerate() {
if i > 0 {
self.output.push_str(", ");
}
write!(self.output, "{} %{}", pty, pname).unwrap();
}
if is_vararg {
if !params.is_empty() {
self.output.push_str(", ");
}
self.output.push_str("...");
}
self.output.push(')');
for attr in attrs {
write!(self.output, " #{}", attr).unwrap();
}
self.output.push_str(" {\n");
self.indent_level += 1;
}
pub fn print_function_footer(&mut self) {
self.indent_level -= 1;
self.emitln("}");
}
pub fn print_block_label(&mut self, label: &str) {
self.indent_level -= 1;
self.emitln(&format!("{}:", label));
self.indent_level += 1;
}
pub fn print_instruction(
&mut self,
result: Option<&str>,
opcode: &str,
ty: Option<&str>,
operands: &[String],
) {
self.emit_indent();
if let Some(r) = result {
write!(self.output, "%{} = ", r).unwrap();
}
write!(self.output, "{}", opcode).unwrap();
if let Some(t) = ty {
write!(self.output, " {}", t).unwrap();
}
for (i, op) in operands.iter().enumerate() {
if i == 0 {
write!(self.output, " {}", op).unwrap();
} else {
write!(self.output, ", {}", op).unwrap();
}
}
self.output.push('\n');
}
pub fn print_terminator(&mut self, opcode: &str, operands: &[String]) {
self.emit_indent();
write!(self.output, " {}", opcode).unwrap();
for op in operands {
write!(self.output, " {}", op).unwrap();
}
self.output.push('\n');
}
pub fn print_attr_group(&mut self, id: u64, attrs: &[String]) {
self.emit_indent();
write!(self.output, "attributes #{} = {{ ", id).unwrap();
for (i, a) in attrs.iter().enumerate() {
if i > 0 {
self.output.push_str(", ");
}
self.output.push_str(a);
}
self.output.push_str(" }\n");
}
pub fn print_metadata(&mut self, id: u64, is_distinct: bool, operands: &[String]) {
self.emit_indent();
if is_distinct {
write!(self.output, "!{} = distinct !{{", id).unwrap();
} else {
write!(self.output, "!{} = !{{", id).unwrap();
}
for (i, op) in operands.iter().enumerate() {
if i > 0 {
self.output.push_str(", ");
}
self.output.push_str(op);
}
self.output.push_str("}\n");
}
pub fn print_named_metadata(&mut self, name: &str, nodes: &[u64]) {
self.emit_indent();
write!(self.output, "!{} = !{{", name).unwrap();
for (i, n) in nodes.iter().enumerate() {
if i > 0 {
self.output.push_str(", ");
}
write!(self.output, "!{}", n).unwrap();
}
self.output.push_str("}\n");
}
pub fn print_debug_loc(&mut self, line: u32, col: u32, scope_id: u64, inlined_at: Option<u64>) {
write!(self.output, ", !dbg !{}", scope_id).unwrap();
if let Some(ia) = inlined_at {
write!(self.output, ", !inlined_at !{}", ia).unwrap();
}
}
pub fn print_metadata_attachments(&mut self, attachments: &[(String, u64)]) {
for (kind, node) in attachments {
write!(self.output, ", !{} !{}", kind, node).unwrap();
}
}
pub fn finish(self) -> String {
self.output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_tracker() {
let mut st = SlotTracker::new();
let s1 = st.create_local_slot("x");
let s2 = st.create_local_slot("y");
assert_eq!(st.get_local_slot("x"), Some(s1));
assert_ne!(s1, s2);
}
#[test]
fn test_type_printer_void() {
assert_eq!(TypePrinter::print(&TypeKind::Void), "void");
}
#[test]
fn test_type_printer_integer() {
assert_eq!(TypePrinter::print(&TypeKind::Integer { bits: 32 }), "i32");
assert_eq!(TypePrinter::print(&TypeKind::Integer { bits: 1 }), "i1");
}
#[test]
fn test_type_printer_pointer() {
assert_eq!(
TypePrinter::print(&TypeKind::Pointer { addr_space: 0 }),
"ptr"
);
assert_eq!(
TypePrinter::print(&TypeKind::Pointer { addr_space: 1 }),
"ptr addrspace(1)"
);
}
#[test]
fn test_assembly_printer_module_header() {
let config = AsmWriterConfig::default();
let mut printer = AssemblyPrinter::new(config);
printer.print_module_header(
Some("test.c"),
Some("x86_64-linux"),
Some("e-m:e-p270:32:32..."),
None,
);
let out = printer.finish();
assert!(out.contains("source_filename"));
assert!(out.contains("target triple"));
assert!(out.contains("target datalayout"));
}
#[test]
fn test_assembly_printer_function() {
let config = AsmWriterConfig::default();
let mut printer = AssemblyPrinter::new(config);
printer.print_function_header(
"",
"",
"i32",
"main",
&[
("i32".to_string(), "argc".to_string()),
("ptr".to_string(), "argv".to_string()),
],
false,
&[],
);
printer.print_function_footer();
let out = printer.finish();
assert!(out.contains("define"));
assert!(out.contains("@main"));
assert!(out.contains("{"));
assert!(out.contains("}"));
}
#[test]
fn test_print_attr_group() {
let config = AsmWriterConfig::default();
let mut printer = AssemblyPrinter::new(config);
printer.print_attr_group(0, &["noinline".to_string(), "nounwind".to_string()]);
let out = printer.finish();
assert!(out.contains("attributes #0"));
assert!(out.contains("noinline"));
}
#[test]
fn test_print_metadata() {
let config = AsmWriterConfig::default();
let mut printer = AssemblyPrinter::new(config);
printer.print_metadata(0, false, &["!\"test\"".to_string(), "i32 42".to_string()]);
let out = printer.finish();
assert!(out.contains("!0 = !{"));
}
}