use std::time::{Duration, Instant};
use crate::basic_block;
use crate::clang::ast::*;
use crate::clang::codegen::ClangCodeGen;
use crate::clang::lexer::tokenize;
use crate::clang::parser::Parser;
use crate::clang::sema::Sema;
use crate::clang::token::Token;
use crate::clang::CLangStandard;
use crate::codegen::{MachineBasicBlock, MachineFunction, MachineInstr};
use crate::elf::elf_types::{ElfClass, ElfEndian, ElfMachine, ElfOsAbi, ET_REL};
use crate::elf::elf_writer::{ElfRelocation, ElfSection, ElfWriter};
use crate::function;
use crate::instruction;
use crate::mc_assembler::encode_x86_64;
use crate::mc_inst::{MCInst, MCOperand};
use crate::mc_streamer::x86_opcodes;
use crate::module::Module;
use crate::object_emitter::compile_to_object;
use crate::object_writer::{ObjectFormat, ObjectWriter, SectionFlags};
use crate::types::Type;
use crate::value::{valref, SubclassKind, Value, ValueRef};
const TARGET_ENCODE_REG_REG_OPS: f64 = 20_000_000.0;
const TARGET_ENCODE_IMM_OPS: f64 = 15_000_000.0;
const TARGET_LEX_THROUGHPUT_KBPS: f64 = 50_000.0;
const TARGET_AST_THROUGHPUT_NPS: f64 = 500_000.0;
const TARGET_IRGEN_THROUGHPUT_IPS: f64 = 200_000.0;
const TARGET_ELF_THROUGHPUT_BPS: f64 = 1_000_000.0;
const TARGET_E2E_SMALL_MAX_MS: f64 = 50.0;
const TARGET_E2E_MEDIUM_MAX_MS: f64 = 500.0;
const BENCH_ITERATIONS: u32 = 1000;
const WARMUP_ITERATIONS: u32 = 100;
const MEDIUM_PROGRAM_LINES: usize = 500;
#[derive(Debug, Clone)]
pub enum BenchmarkStatus {
Pass,
Warn {
measured: f64,
target: f64,
unit: String,
},
Fail {
measured: f64,
target: f64,
unit: String,
},
}
impl BenchmarkStatus {
pub fn is_pass(&self) -> bool {
matches!(self, BenchmarkStatus::Pass)
}
pub fn summary(&self, name: &str) -> String {
match self {
BenchmarkStatus::Pass => format!(" \u{2713} {}: PASS", name),
BenchmarkStatus::Warn {
measured,
target,
unit,
} => {
format!(
" \u{26A0} {}: WARN \u{2014} {:.2} {} (target \u{2265} {:.2} {})",
name, measured, unit, target, unit
)
}
BenchmarkStatus::Fail {
measured,
target,
unit,
} => {
format!(
" \u{2717} {}: FAIL \u{2014} {:.2} {} (target \u{2265} {:.2} {})",
name, measured, unit, target, unit
)
}
}
}
}
#[derive(Debug, Clone)]
pub struct BenchMeasurement {
pub name: &'static str,
pub value: f64,
pub unit: &'static str,
pub elapsed: Duration,
pub count: u64,
pub status: BenchmarkStatus,
}
impl BenchMeasurement {
pub fn new(
name: &'static str,
value: f64,
unit: &'static str,
elapsed: Duration,
count: u64,
status: BenchmarkStatus,
) -> Self {
Self {
name,
value,
unit,
elapsed,
count,
status,
}
}
pub fn report(&self) -> String {
format!(
"{:40} {:>12.4} {:<8} {:>8?} count={}",
self.name, self.value, self.unit, self.elapsed, self.count
)
}
}
#[derive(Debug)]
pub struct BenchmarkReport {
pub measurements: Vec<BenchMeasurement>,
pub timestamp: std::time::SystemTime,
}
impl Default for BenchmarkReport {
fn default() -> Self {
Self::new()
}
}
impl BenchmarkReport {
pub fn new() -> Self {
Self {
measurements: Vec::new(),
timestamp: std::time::SystemTime::now(),
}
}
pub fn push(&mut self, m: BenchMeasurement) {
self.measurements.push(m);
}
pub fn print_summary(&self) {
println!();
println!("==================================================");
println!(" llvm-native vs Upstream LLVM Performance Report");
println!("==================================================");
println!();
let passes = self
.measurements
.iter()
.filter(|m| m.status.is_pass())
.count();
let total = self.measurements.len();
println!(" Results: {}/{} benchmarks passing", passes, total);
println!();
for m in &self.measurements {
println!(" {}", m.status.summary(m.name));
}
println!();
println!(" \u{2500}\u{2500} Detailed Measurements \u{2500}\u{2500}");
for m in &self.measurements {
println!(" {}", m.report());
}
println!();
}
pub fn all_pass(&self) -> bool {
self.measurements.iter().all(|m| m.status.is_pass())
}
pub fn score(&self) -> usize {
self.measurements
.iter()
.filter(|m| m.status.is_pass())
.count()
}
}
fn check_throughput(measured: f64, target: f64) -> BenchmarkStatus {
if measured >= target {
BenchmarkStatus::Pass
} else if measured >= target * 0.8 {
BenchmarkStatus::Warn {
measured,
target,
unit: String::new(),
}
} else {
BenchmarkStatus::Fail {
measured,
target,
unit: String::new(),
}
}
}
fn check_latency(measured_ms: f64, max_ms: f64) -> BenchmarkStatus {
if measured_ms <= max_ms {
BenchmarkStatus::Pass
} else if measured_ms <= max_ms * 1.25 {
BenchmarkStatus::Warn {
measured: measured_ms,
target: max_ms,
unit: String::new(),
}
} else {
BenchmarkStatus::Fail {
measured: measured_ms,
target: max_ms,
unit: String::new(),
}
}
}
fn annotate_status(s: BenchmarkStatus, value: f64, unit: &'static str) -> BenchmarkStatus {
match s {
BenchmarkStatus::Warn {
measured, target, ..
} => BenchmarkStatus::Warn {
measured,
target,
unit: unit.to_string(),
},
BenchmarkStatus::Fail {
measured, target, ..
} => BenchmarkStatus::Fail {
measured,
target,
unit: unit.to_string(),
},
ok => ok,
}
}
fn bench_timed<F: FnMut()>(n: u32, warmup: u32, mut f: F) -> Duration {
for _ in 0..warmup {
f();
}
let start = Instant::now();
for _ in 0..n {
f();
}
start.elapsed()
}
fn build_reg_reg_insts(count: usize) -> Vec<MCInst> {
let mut insts = Vec::with_capacity(count);
for i in 0..count {
let src = (i % 16) as u32;
let dst = ((i + 1) % 16) as u32;
let mut inst = MCInst::new(x86_opcodes::MOV);
inst.operands.push(MCOperand::Reg(dst));
inst.operands.push(MCOperand::Reg(src));
insts.push(inst);
}
insts
}
fn build_reg_imm_insts(count: usize) -> Vec<MCInst> {
let mut insts = Vec::with_capacity(count);
for i in 0..count {
let dst = (i % 16) as u32;
let mut inst = MCInst::new(x86_opcodes::MOV);
inst.operands.push(MCOperand::Reg(dst));
inst.operands.push(MCOperand::Imm((i as i64) * 0x42));
insts.push(inst);
}
insts
}
fn build_alu_reg_reg_insts(count: usize) -> Vec<MCInst> {
let mut insts = Vec::with_capacity(count);
for i in 0..count {
let src = ((i * 3) % 16) as u32;
let dst = ((i * 7) % 16) as u32;
let mut inst = MCInst::new(x86_opcodes::ADD);
inst.operands.push(MCOperand::Reg(dst));
inst.operands.push(MCOperand::Reg(src));
insts.push(inst);
}
insts
}
fn build_alu_imm_insts(count: usize) -> Vec<MCInst> {
let mut insts = Vec::with_capacity(count);
for i in 0..count {
let dst = ((i * 5) % 16) as u32;
let mut inst = MCInst::new(x86_opcodes::SUB);
inst.operands.push(MCOperand::Reg(dst));
inst.operands.push(MCOperand::Imm((i as i64) & 0xFF));
insts.push(inst);
}
insts
}
fn build_push_insts(count: usize) -> Vec<MCInst> {
let mut insts = Vec::with_capacity(count);
for i in 0..count {
let mut inst = MCInst::new(x86_opcodes::PUSH);
inst.operands.push(MCOperand::Reg((i % 16) as u32));
insts.push(inst);
}
insts
}
fn build_branch_insts(count: usize) -> Vec<MCInst> {
let mut insts = Vec::with_capacity(count);
for i in 0..count {
let opcode = if i % 2 == 0 {
x86_opcodes::JE
} else {
x86_opcodes::JNE
};
let mut inst = MCInst::new(opcode);
inst.operands.push(MCOperand::Imm(((i as i64) * 4) as i64));
insts.push(inst);
}
insts
}
fn build_mixed_insts(count: usize) -> Vec<MCInst> {
let mut insts = Vec::with_capacity(count);
for i in 0..count {
let ra = (i % 16) as u32;
let rb = ((i + 3) % 16) as u32;
let imm = (i as i64) & 0x7F;
let (opcode, ops): (u32, Vec<MCOperand>) = match i % 8 {
0 => (
x86_opcodes::MOV,
vec![MCOperand::Reg(ra), MCOperand::Reg(rb)],
),
1 => (
x86_opcodes::MOV,
vec![MCOperand::Reg(ra), MCOperand::Imm(imm)],
),
2 => (
x86_opcodes::ADD,
vec![MCOperand::Reg(ra), MCOperand::Reg(rb)],
),
3 => (
x86_opcodes::SUB,
vec![MCOperand::Reg(ra), MCOperand::Imm(imm)],
),
4 => (x86_opcodes::PUSH, vec![MCOperand::Reg(ra)]),
5 => (x86_opcodes::POP, vec![MCOperand::Reg(ra)]),
6 => (
x86_opcodes::XOR,
vec![MCOperand::Reg(ra), MCOperand::Reg(rb)],
),
7 => (x86_opcodes::NOP, vec![]),
_ => unreachable!(),
};
let mut inst = MCInst::new(opcode);
inst.operands = ops;
insts.push(inst);
}
insts
}
fn bench_encode_batch(insts: &[MCInst], name: &'static str, target: f64) -> BenchMeasurement {
let count = insts.len() as u64;
let total = count * (BENCH_ITERATIONS as u64);
let elapsed = bench_timed(BENCH_ITERATIONS, WARMUP_ITERATIONS, || {
for inst in insts {
let _ = encode_x86_64(inst);
}
});
let ops_sec = total as f64 / elapsed.as_secs_f64();
let status = annotate_status(check_throughput(ops_sec, target), ops_sec, "opcodes/sec");
BenchMeasurement::new(name, ops_sec, "opcodes/sec", elapsed, total, status)
}
fn bench_x86_encoding(report: &mut BenchmarkReport) {
let count = 1000;
report.push(bench_encode_batch(
&build_reg_reg_insts(count),
"x86 encode reg-reg MOV",
TARGET_ENCODE_REG_REG_OPS,
));
report.push(bench_encode_batch(
&build_reg_imm_insts(count),
"x86 encode reg-imm MOV",
TARGET_ENCODE_IMM_OPS,
));
report.push(bench_encode_batch(
&build_alu_reg_reg_insts(count),
"x86 encode reg-reg ADD",
TARGET_ENCODE_REG_REG_OPS,
));
report.push(bench_encode_batch(
&build_alu_imm_insts(count),
"x86 encode reg-imm SUB",
TARGET_ENCODE_IMM_OPS,
));
report.push(bench_encode_batch(
&build_push_insts(count),
"x86 encode PUSH",
TARGET_ENCODE_IMM_OPS,
));
report.push(bench_encode_batch(
&build_branch_insts(count),
"x86 encode branches",
TARGET_ENCODE_IMM_OPS,
));
report.push(bench_encode_batch(
&build_mixed_insts(count),
"x86 encode mixed workload",
TARGET_ENCODE_REG_REG_OPS,
));
let nops: Vec<MCInst> = (0..count).map(|_| MCInst::new(x86_opcodes::NOP)).collect();
report.push(bench_encode_batch(
&nops,
"x86 encode NOP fast path",
TARGET_ENCODE_REG_REG_OPS * 2.0,
));
}
fn generate_c_source(target_kb: usize) -> String {
let mut src = String::with_capacity(target_kb * 1024 + 512);
src.push_str("#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n");
src.push_str(
"typedef unsigned long size_t;\ntypedef int bool;\n#define true 1\n#define false 0\n\n",
);
let target_bytes = target_kb * 1024;
let mut func_idx = 0usize;
while src.len() < target_bytes {
let func = format!(
r#"int compute_{idx}(int a, int b, int *out) {{
int result = a + b;
if (result > 100) {{
result = result * 2 - (a >> 1);
for (int i = 0; i < result; i++) {{
if (i & 1) {{ *out += i; }} else {{ *out -= i / 2; }}
}}
}} else {{
switch (result) {{
case 0: return -1;
case 1: return a - b;
case 2: return a * b;
case 3: return a / (b + 1);
default: return result & 0xFF;
}}
}}
return *out;
}}
"#,
idx = func_idx
);
src.push_str(&func);
func_idx += 1;
}
src.push_str(&format!(
"int main(void) {{ int result = 0; for (int i = 0; i < {}; i++) {{ result += compute_{}(i, i + 1, &result); }} return result; }}\n",
func_idx.min(100), func_idx.saturating_sub(1).max(0)));
src
}
fn lex_source(src: &str) -> (Vec<Token>, Duration) {
let start = Instant::now();
let t = tokenize(src, CLangStandard::C11);
(t, start.elapsed())
}
fn bench_c_lexing(report: &mut BenchmarkReport) {
let small = generate_c_source(4);
let small_kb = small.len() as f64 / 1024.0;
let (ts, es) = lex_source(&small);
let tp = small_kb / es.as_secs_f64();
let st = check_throughput(tp, TARGET_LEX_THROUGHPUT_KBPS);
report.push(BenchMeasurement::new(
"C lex throughput (4 KB input)",
tp,
"KB/sec",
es,
ts.len() as u64,
annotate_status(st, tp, "KB/sec"),
));
let med = generate_c_source(64);
let med_kb = med.len() as f64 / 1024.0;
let (tm, em) = lex_source(&med);
let tpm = med_kb / em.as_secs_f64();
let stm = check_throughput(tpm, TARGET_LEX_THROUGHPUT_KBPS);
report.push(BenchMeasurement::new(
"C lex throughput (64 KB input)",
tpm,
"KB/sec",
em,
tm.len() as u64,
annotate_status(stm, tpm, "KB/sec"),
));
let tpk = tm.len() as f64 / med_kb;
report.push(BenchMeasurement::new(
"C lex token density (64 KB)",
tpk,
"tokens/KB",
em,
tm.len() as u64,
BenchmarkStatus::Pass,
));
let total_kb = small_kb + med_kb;
let total_elapsed = es + em;
let total_tp = total_kb / total_elapsed.as_secs_f64();
let st_total = check_throughput(total_tp, TARGET_LEX_THROUGHPUT_KBPS);
report.push(BenchMeasurement::new(
"C lex throughput (aggregate)",
total_tp,
"KB/sec",
total_elapsed,
(ts.len() + tm.len()) as u64,
annotate_status(st_total, total_tp, "KB/sec"),
));
}
fn build_ast(source: &str) -> (TranslationUnit, Duration, Duration, Duration) {
let ls = Instant::now();
let tokens = tokenize(source, CLangStandard::C11);
let lt = ls.elapsed();
let ps = Instant::now();
let mut p = Parser::new(&tokens, CLangStandard::C11);
let tu = p.parse().expect("parse");
let pt = ps.elapsed();
let ss = Instant::now();
let mut sema = Sema::new(CLangStandard::C11);
let _ = sema.analyze(&tu);
let st = ss.elapsed();
(tu, lt, pt, st)
}
fn count_ast_nodes(tu: &TranslationUnit) -> usize {
let mut count = 0;
for decl in &tu.decls {
count += 1;
match decl {
Decl::Function(fd) => {
count += 1;
if let Some(ref body) = fd.body {
count += count_stmt(&Stmt::Compound(body.clone()));
}
for _ in &fd.params {
count += 1;
}
}
Decl::Variable(vd) => {
count += 1;
if let Some(ref init) = vd.init {
count += count_expr(init);
}
}
Decl::Typedef(_) | Decl::Struct(_) | Decl::Enum(_) | Decl::EnumVariant(_) => {
count += 1;
}
}
}
count
}
fn count_stmt(stmt: &Stmt) -> usize {
let mut c = 1;
match stmt {
Stmt::Compound(cs) => {
for s in &cs.stmts {
c += count_stmt(s);
}
}
Stmt::If { cond, then, els } => {
c += count_expr(cond);
c += count_stmt(then);
if let Some(e) = els {
c += count_stmt(e);
}
}
Stmt::While { cond, body } => {
c += count_expr(cond);
c += count_stmt(body);
}
Stmt::DoWhile { body, cond } => {
c += count_stmt(body);
c += count_expr(cond);
}
Stmt::For {
init,
cond,
incr,
body,
} => {
if let Some(i) = init {
c += count_stmt(i);
}
if let Some(cd) = cond {
c += count_expr(cd);
}
if let Some(inc) = incr {
c += count_expr(inc);
}
c += count_stmt(body);
}
Stmt::Switch { expr, body } => {
c += count_expr(expr);
c += count_stmt(body);
}
Stmt::Return(e) => {
if let Some(e) = e {
c += count_expr(e);
}
}
Stmt::Case { value, stmt } => {
c += count_expr(value);
c += count_stmt(stmt);
}
Stmt::Default { stmt } => {
c += count_stmt(stmt);
}
Stmt::Expr(e) => {
c += count_expr(e);
}
Stmt::Decl(vd) => {
c += 1;
if let Some(ref init) = vd.init {
c += count_expr(init);
}
}
Stmt::Label { stmt, .. } => {
c += count_stmt(stmt);
}
Stmt::Goto { .. } | Stmt::Break | Stmt::Continue | Stmt::Null => {}
}
c
}
fn count_expr(expr: &Expr) -> usize {
let mut c = 1;
match expr {
Expr::Binary(_, l, r) | Expr::Assign(_, l, r) => {
c += count_expr(l);
c += count_expr(r);
}
Expr::Unary(_, e)
| Expr::SizeOf(e)
| Expr::AlignOf(e)
| Expr::Cast(_, e)
| Expr::PostInc(e)
| Expr::PostDec(e)
| Expr::PreInc(e)
| Expr::PreDec(e) => {
c += count_expr(e);
}
Expr::Conditional(a, b, d) => {
c += count_expr(a);
c += count_expr(b);
c += count_expr(d);
}
Expr::Call { callee, args } => {
c += count_expr(callee);
for a in args {
c += count_expr(a);
}
}
Expr::Subscript { base, index } => {
c += count_expr(base);
c += count_expr(index);
}
Expr::Member { base, .. } => {
c += count_expr(base);
}
Expr::CompoundLiteral(_, init) => {
c += count_expr(init);
}
Expr::AggregateLiteral(vals) => {
for v in vals {
c += count_expr(v);
}
}
Expr::SizeOfType(_) | Expr::AlignOfType(_) => {}
Expr::IntLiteral(_)
| Expr::UIntLiteral(..)
| Expr::FloatLiteral(_)
| Expr::DoubleLiteral(_)
| Expr::CharLiteral(_)
| Expr::StringLiteral(_)
| Expr::Ident(_) => {}
}
c
}
fn bench_ast_construction(report: &mut BenchmarkReport) {
let src = generate_c_source(8);
let (tu, lt, pt, st) = build_ast(&src);
let nc = count_ast_nodes(&tu);
let total = lt + pt + st;
let nps = nc as f64 / total.as_secs_f64();
let status = annotate_status(
check_throughput(nps, TARGET_AST_THROUGHPUT_NPS),
nps,
"nodes/sec",
);
report.push(BenchMeasurement::new(
"AST construction throughput",
nps,
"nodes/sec",
total,
nc as u64,
status,
));
report.push(BenchMeasurement::new(
"AST construction - lex phase",
lt.as_secs_f64() * 1000.0,
"ms",
lt,
0,
BenchmarkStatus::Pass,
));
report.push(BenchMeasurement::new(
"AST construction - parse phase",
pt.as_secs_f64() * 1000.0,
"ms",
pt,
0,
BenchmarkStatus::Pass,
));
report.push(BenchMeasurement::new(
"AST construction - sema phase",
st.as_secs_f64() * 1000.0,
"ms",
st,
0,
BenchmarkStatus::Pass,
));
let large = generate_c_source(32);
let (tu2, _, pt2, _) = build_ast(&large);
let nc2 = count_ast_nodes(&tu2);
let nps2 = nc2 as f64 / pt2.as_secs_f64();
let s2 = annotate_status(
check_throughput(nps2, TARGET_AST_THROUGHPUT_NPS),
nps2,
"nodes/sec",
);
report.push(BenchMeasurement::new(
"AST construction (32 KB input)",
nps2,
"nodes/sec",
pt2,
nc2 as u64,
s2,
));
}
fn generate_ir_from_source(source: &str) -> (Module, Duration, Duration, Duration) {
let tokens = tokenize(source, CLangStandard::C11);
let lt = Duration::ZERO;
let ps = Instant::now();
let mut parser = Parser::new(&tokens, CLangStandard::C11);
let tu = parser.parse().expect("parse");
let pt = ps.elapsed();
let cs = Instant::now();
let mut codegen = ClangCodeGen::new("benchmark_module", "x86_64-unknown-linux-gnu");
let module = codegen.compile(&tu).expect("codegen");
let ct = cs.elapsed();
(module, lt, pt, ct)
}
fn count_ir_instructions(module: &Module) -> usize {
let mut count = 0;
for fv in &module.functions {
let f = fv.borrow();
if !f.is_function() {
continue;
}
for op in &f.operands {
let bb = op.borrow();
if bb.is_basic_block() {
for iv in &bb.operands {
if iv.borrow().is_instruction() {
count += 1;
}
}
}
}
}
count
}
fn bench_ir_generation(report: &mut BenchmarkReport) {
let src = generate_c_source(12);
let (module, _, _, ct) = generate_ir_from_source(&src);
let ic = count_ir_instructions(&module);
let ips = ic as f64 / ct.as_secs_f64();
let st = annotate_status(
check_throughput(ips, TARGET_IRGEN_THROUGHPUT_IPS),
ips,
"instrs/sec",
);
report.push(BenchMeasurement::new(
"IR generation from C source",
ips,
"instrs/sec",
ct,
ic as u64,
st,
));
let syn_elapsed = bench_timed(100, 20, || {
let mut m = Module::new("synth");
let i32_ty = Type::i32();
let gv = valref(
Value::new(i32_ty.clone())
.named("g")
.with_subclass(SubclassKind::GlobalVariable),
);
m.globals.push(gv.clone());
for f in 0..10 {
let fv = function::new_function(
&format!("f{}", f),
i32_ty.clone(),
&[i32_ty.clone(), i32_ty.clone()],
);
m.add_function_unchecked(fv.clone());
let eb = basic_block::new_basic_block(&format!("e{}", f));
fv.borrow_mut().push_operand(eb.clone());
let l1 = instruction::load(i32_ty.clone(), gv.clone());
eb.borrow_mut().push_operand(l1);
let l2 = instruction::load(i32_ty.clone(), gv.clone());
eb.borrow_mut().push_operand(l2);
}
let _ = m;
});
let syn_count = 10 * 2 * 100u64;
let syn_ips = syn_count as f64 / syn_elapsed.as_secs_f64();
let ss = annotate_status(
check_throughput(syn_ips, TARGET_IRGEN_THROUGHPUT_IPS * 2.0),
syn_ips,
"instrs/sec",
);
report.push(BenchMeasurement::new(
"IR construction (synthetic, 10 fns)",
syn_ips,
"instrs/sec",
syn_elapsed,
syn_count,
ss,
));
let b_elapsed = bench_timed(500, 50, || {
let ctx = crate::context::LLVMContext::new();
let mut b = crate::ir_builder::IRBuilder::new(&ctx);
let _ = b.create_alloca(Type::i32(), "x");
let _ = b.create_alloca(Type::i32(), "y");
});
report.push(BenchMeasurement::new(
"IRBuilder create_alloca throughput",
500.0 / b_elapsed.as_secs_f64(),
"ops/sec",
b_elapsed,
500,
BenchmarkStatus::Pass,
));
}
fn bench_elf_emission(report: &mut BenchmarkReport) {
let ow_elapsed = bench_timed(500, 50, || {
let mut ow = ObjectWriter::new(ObjectFormat::ELF, "x86_64-unknown-linux-gnu");
ow.add_section(".text", vec![0x90u8, 0xC3], SectionFlags::text());
ow.add_section(".data", vec![0x00u8; 64], SectionFlags::data());
let _bytes = ow.write();
});
report.push(BenchMeasurement::new(
"ObjectWriter ELF throughput",
500.0 / ow_elapsed.as_secs_f64(),
"ops/sec",
ow_elapsed,
500,
BenchmarkStatus::Pass,
));
let (_, w_elapsed) = {
let ws = Instant::now();
for _ in 0..200 {
let mut ew = ElfWriter::new(
ElfClass::Elf64,
ElfEndian::Little,
ElfOsAbi::Linux,
ElfMachine::X86_64,
ET_REL,
);
ew.add_section(".text", vec![0x90u8; 256], 1, 6, 16);
ew.add_relocation(0, ElfRelocation::rela(0x100, 1, 2, 0));
let _r = ew.write();
}
(0u64, ws.elapsed())
};
let total_b = 200u64 * 256;
let tp = total_b as f64 / w_elapsed.as_secs_f64();
let st = annotate_status(
check_throughput(tp, TARGET_ELF_THROUGHPUT_BPS),
tp,
"bytes/sec",
);
report.push(BenchMeasurement::new(
"ELF emission (ElfWriter, 200 iterations)",
tp,
"bytes/sec",
w_elapsed,
total_b,
st,
));
let i32_ty = Type::i32();
let fn_val = function::new_function("bench_obj", i32_ty.clone(), &[i32_ty.clone()]);
let bb = basic_block::new_basic_block("entry");
fn_val.borrow_mut().push_operand(bb.clone());
let obj_elapsed = bench_timed(100, 20, || {
let _obj = compile_to_object(&[&fn_val], "x86_64-unknown-linux-gnu");
});
let single = compile_to_object(&[&fn_val], "x86_64-unknown-linux-gnu");
let sz = single.len() as u64;
let otp = (sz * 100) as f64 / obj_elapsed.as_secs_f64();
let ost = annotate_status(
check_throughput(otp, TARGET_ELF_THROUGHPUT_BPS),
otp,
"bytes/sec",
);
report.push(BenchMeasurement::new(
"ELF emission via compile_to_object",
otp,
"bytes/sec",
obj_elapsed,
sz * 100,
ost,
));
}
fn run_e2e_pipeline(source: &str) -> (Module, Vec<u8>, Duration) {
let start = Instant::now();
let tokens = tokenize(source, CLangStandard::C11);
let mut parser = Parser::new(&tokens, CLangStandard::C11);
let tu = parser.parse().expect("e2e parse");
let mut sema = Sema::new(CLangStandard::C11);
let _ = sema.analyze(&tu);
let mut codegen = ClangCodeGen::new("e2e_bench", "x86_64-unknown-linux-gnu");
let module = codegen.compile(&tu).expect("e2e codegen");
let refs: Vec<ValueRef> = module.functions.iter().map(|v| v.clone()).collect();
let ptrs: Vec<&ValueRef> = refs.iter().collect();
let obj = compile_to_object(&ptrs, "x86_64-unknown-linux-gnu");
(module, obj, start.elapsed())
}
fn bench_e2e_pipeline(report: &mut BenchmarkReport) {
let src_s = generate_c_source(2);
let (_, obj_s, el_s) = run_e2e_pipeline(&src_s);
let ms_s = el_s.as_secs_f64() * 1000.0;
let st_s = check_latency(ms_s, TARGET_E2E_SMALL_MAX_MS);
report.push(BenchMeasurement::new(
"E2E latency (small ~50 lines)",
ms_s,
"ms",
el_s,
obj_s.len() as u64,
annotate_status(st_s, ms_s, "ms"),
));
let src_m = generate_c_source(16);
let (_, obj_m, el_m) = run_e2e_pipeline(&src_m);
let ms_m = el_m.as_secs_f64() * 1000.0;
let st_m = check_latency(ms_m, TARGET_E2E_MEDIUM_MAX_MS);
report.push(BenchMeasurement::new(
"E2E latency (medium ~500 lines)",
ms_m,
"ms",
el_m,
obj_m.len() as u64,
annotate_status(st_m, ms_m, "ms"),
));
report.push(BenchMeasurement::new(
"E2E small object size",
obj_s.len() as f64,
"bytes",
el_s,
obj_s.len() as u64,
BenchmarkStatus::Pass,
));
report.push(BenchMeasurement::new(
"E2E medium object size",
obj_m.len() as f64,
"bytes",
el_m,
obj_m.len() as u64,
BenchmarkStatus::Pass,
));
let src_b = generate_c_source(16);
let lt = {
let s = Instant::now();
let _ = tokenize(&src_b, CLangStandard::C11);
s.elapsed()
};
let pt = {
let toks = tokenize(&src_b, CLangStandard::C11);
let s = Instant::now();
let mut p = Parser::new(&toks, CLangStandard::C11);
let _ = p.parse().expect("parse");
s.elapsed()
};
let ct = {
let toks = tokenize(&src_b, CLangStandard::C11);
let mut p = Parser::new(&toks, CLangStandard::C11);
let tu = p.parse().expect("parse");
let mut sema = Sema::new(CLangStandard::C11);
let _ = sema.analyze(&tu);
let s = Instant::now();
let mut cg = ClangCodeGen::new("e2e_brk", "x86_64-unknown-linux-gnu");
let _ = cg.compile(&tu).expect("cg");
s.elapsed()
};
report.push(BenchMeasurement::new(
"E2E breakdown - lex",
lt.as_secs_f64() * 1000.0,
"ms",
lt,
0,
BenchmarkStatus::Pass,
));
report.push(BenchMeasurement::new(
"E2E breakdown - parse",
pt.as_secs_f64() * 1000.0,
"ms",
pt,
0,
BenchmarkStatus::Pass,
));
report.push(BenchMeasurement::new(
"E2E breakdown - codegen",
ct.as_secs_f64() * 1000.0,
"ms",
ct,
0,
BenchmarkStatus::Pass,
));
let (_, _, t_small) = run_e2e_pipeline(&generate_c_source(2));
let (_, _, t_big) = run_e2e_pipeline(&generate_c_source(32));
let ratio = t_big.as_secs_f64() / t_small.as_secs_f64();
let overhead = ratio / 16.0;
let lin = if overhead < 1.5 {
BenchmarkStatus::Pass
} else if overhead < 2.0 {
BenchmarkStatus::Warn {
measured: overhead,
target: 1.5,
unit: "overhead".to_string(),
}
} else {
BenchmarkStatus::Fail {
measured: overhead,
target: 1.5,
unit: "overhead".to_string(),
}
};
report.push(BenchMeasurement::new(
"Pipeline scaling linearity",
overhead,
"ratio",
t_big,
0,
lin,
));
}
fn build_machine_function(name: &str, num_bbs: usize, instrs_per_bb: usize) -> MachineFunction {
let mut mf = MachineFunction::new(name);
for bb_id in 0..num_bbs {
let mut mbb = MachineBasicBlock::new(bb_id);
for inst_id in 0..instrs_per_bb {
let opcode = match inst_id % 5 {
0 => 0x89,
1 => 0x01,
2 => 0x29,
3 => 0x31,
_ => 0x90,
};
let mut mi = MachineInstr::new(opcode);
mi.push_reg((inst_id as u32) % 16);
mi.push_reg(((inst_id + 1) as u32) % 16);
mbb.instructions.push(mi);
}
mf.blocks.push(mbb);
}
mf
}
fn bench_machine_codegen(report: &mut BenchmarkReport) {
let mf_elapsed = bench_timed(500, 50, || {
let _ = build_machine_function("bench", 10, 20);
});
let total = 500 * 10 * 20;
let tpt = total as f64 / mf_elapsed.as_secs_f64();
report.push(BenchMeasurement::new(
"MachineInstr construction throughput",
tpt,
"instrs/sec",
mf_elapsed,
total as u64,
BenchmarkStatus::Pass,
));
let mf = build_machine_function("enc", 5, 10);
let mut mcis = Vec::new();
for blk in &mf.blocks {
for mi in &blk.instructions {
let oc = match mi.opcode {
0x89 => x86_opcodes::MOV,
0x01 => x86_opcodes::ADD,
0x29 => x86_opcodes::SUB,
0x31 => x86_opcodes::XOR,
_ => x86_opcodes::NOP,
};
let mut mc = MCInst::new(oc);
for op in &mi.operands {
if let Some(r) = op.reg_val() {
mc.operands.push(MCOperand::Reg(r));
}
}
mcis.push(mc);
}
}
let enc_elapsed = bench_timed(1000, 100, || {
for mc in &mcis {
let _ = encode_x86_64(mc);
}
});
let enc_total = mcis.len() as u64 * 1000;
report.push(BenchMeasurement::new(
"MachineInstr -> MCInst -> encode",
enc_total as f64 / enc_elapsed.as_secs_f64(),
"instrs/sec",
enc_elapsed,
enc_total,
BenchmarkStatus::Pass,
));
let asm_elapsed = bench_timed(500, 50, || {
let _ = crate::asm_parser::parse_assembly(".text\nmain:\n ret\n");
});
report.push(BenchMeasurement::new(
"Assembly parsing throughput",
500.0 / asm_elapsed.as_secs_f64(),
"parses/sec",
asm_elapsed,
500,
BenchmarkStatus::Pass,
));
let module = {
let mut m = Module::new("verifier_bench");
let i32 = Type::i32();
let fv = function::new_function("vfn", i32.clone(), &[i32.clone()]);
m.add_function_unchecked(fv.clone());
let bb = basic_block::new_basic_block("entry");
fv.borrow_mut().push_operand(bb.clone());
m
};
let v_elapsed = bench_timed(100, 20, || {
let _ = crate::verifier::verify_module(&module);
});
report.push(BenchMeasurement::new(
"Module verifier throughput",
100.0 / v_elapsed.as_secs_f64(),
"verifies/sec",
v_elapsed,
100,
BenchmarkStatus::Pass,
));
}
pub fn run_all_benchmarks() -> BenchmarkReport {
let mut report = BenchmarkReport::new();
println!("=== llvm-native X86 vs Upstream Benchmarks ===");
println!();
println!("-- X86 Instruction Encoding --");
bench_x86_encoding(&mut report);
println!("-- C Source Lexing --");
bench_c_lexing(&mut report);
println!("-- AST Construction --");
bench_ast_construction(&mut report);
println!("-- LLVM IR Generation --");
bench_ir_generation(&mut report);
println!("-- ELF Object Emission --");
bench_elf_emission(&mut report);
println!("-- End-to-End Pipeline --");
bench_e2e_pipeline(&mut report);
println!("-- Machine Code Generation --");
bench_machine_codegen(&mut report);
report.print_summary();
report
}
pub fn run_encoding_benchmarks() -> BenchmarkReport {
let mut r = BenchmarkReport::new();
bench_x86_encoding(&mut r);
r.print_summary();
r
}
pub fn run_lexing_benchmarks() -> BenchmarkReport {
let mut r = BenchmarkReport::new();
bench_c_lexing(&mut r);
r.print_summary();
r
}
pub fn run_ast_benchmarks() -> BenchmarkReport {
let mut r = BenchmarkReport::new();
bench_ast_construction(&mut r);
r.print_summary();
r
}
pub fn run_irgen_benchmarks() -> BenchmarkReport {
let mut r = BenchmarkReport::new();
bench_ir_generation(&mut r);
r.print_summary();
r
}
pub fn run_elf_benchmarks() -> BenchmarkReport {
let mut r = BenchmarkReport::new();
bench_elf_emission(&mut r);
r.print_summary();
r
}
pub fn run_e2e_benchmarks() -> BenchmarkReport {
let mut r = BenchmarkReport::new();
bench_e2e_pipeline(&mut r);
r.print_summary();
r
}
pub fn run_mc_benchmarks() -> BenchmarkReport {
let mut r = BenchmarkReport::new();
bench_machine_codegen(&mut r);
r.print_summary();
r
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encoding_plausible() {
let inst = MCInst::new(x86_opcodes::MOV);
let r = encode_x86_64(&inst);
let _ = r;
}
#[test]
fn test_source_generation() {
let src = generate_c_source(1);
assert!(src.len() >= 512);
assert!(src.contains("main"));
}
#[test]
fn test_tokenization() {
let toks = tokenize("int x = 42;", CLangStandard::C11);
assert!(!toks.is_empty());
}
#[test]
fn test_ast_parsing() {
let toks = tokenize("int x = 42;", CLangStandard::C11);
let mut p = Parser::new(&toks, CLangStandard::C11);
let tu = p.parse().expect("parse");
assert!(count_ast_nodes(&tu) > 0);
}
#[test]
fn test_elf_emission_plausible() {
let mut ow = ObjectWriter::new(ObjectFormat::ELF, "x86_64-unknown-linux-gnu");
ow.add_section(".text", vec![0x90, 0xC3], SectionFlags::text());
let bytes = ow.write();
assert!(!bytes.is_empty());
assert!(bytes.starts_with(&[0x7F, b'E', b'L', b'F']));
}
#[test]
fn test_machine_function_build() {
let mf = build_machine_function("test", 3, 5);
assert_eq!(mf.blocks.len(), 3);
assert_eq!(mf.blocks[0].instructions.len(), 5);
}
#[test]
fn test_benchmark_report() {
let mut r = BenchmarkReport::new();
r.push(BenchMeasurement::new(
"test",
1000.0,
"ops/sec",
Duration::from_secs(1),
1000,
BenchmarkStatus::Pass,
));
assert_eq!(r.score(), 1);
assert!(r.all_pass());
}
#[test]
fn test_throughput_check() {
assert!(matches!(
check_throughput(100.0, 100.0),
BenchmarkStatus::Pass
));
assert!(matches!(
check_throughput(90.0, 100.0),
BenchmarkStatus::Warn { .. }
));
assert!(matches!(
check_throughput(50.0, 100.0),
BenchmarkStatus::Fail { .. }
));
}
#[test]
fn test_latency_check() {
assert!(matches!(check_latency(50.0, 50.0), BenchmarkStatus::Pass));
assert!(matches!(
check_latency(60.0, 50.0),
BenchmarkStatus::Warn { .. }
));
assert!(matches!(
check_latency(70.0, 50.0),
BenchmarkStatus::Fail { .. }
));
}
#[test]
fn test_e2e_pipeline_minimal() {
let src = "int main(void) { return 0; }";
let (_mod, obj, elapsed) = run_e2e_pipeline(src);
assert!(!obj.is_empty());
assert!(elapsed.as_secs_f64() > 0.0);
}
#[test]
fn test_codegen_pipeline() {
let src = "int add(int a, int b) { return a + b; }";
let (module, _, _, _) = generate_ir_from_source(src);
assert!(count_ir_instructions(&module) > 0);
}
#[test]
fn test_medium_program_size() {
let src = generate_c_source(16);
assert!(src.lines().count() >= MEDIUM_PROGRAM_LINES - 50);
}
}