use crate::codegen::*;
use crate::module::Module;
pub const DEFAULT_RV64_DATA_LAYOUT: &str = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128";
pub const DEFAULT_RV32_DATA_LAYOUT: &str = "e-m:e-p:32:32-i64:64-n32-S128";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OptimizationLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl OptimizationLevel {
pub fn is_optimizing(&self) -> bool {
!matches!(self, OptimizationLevel::O0)
}
pub fn is_aggressive(&self) -> bool {
matches!(self, OptimizationLevel::O3)
}
pub fn is_size_optimized(&self) -> bool {
matches!(self, OptimizationLevel::Os | OptimizationLevel::Oz)
}
pub fn as_str(&self) -> &'static str {
match self {
OptimizationLevel::O0 => "O0",
OptimizationLevel::O1 => "O1",
OptimizationLevel::O2 => "O2",
OptimizationLevel::O3 => "O3",
OptimizationLevel::Os => "Os",
OptimizationLevel::Oz => "Oz",
}
}
}
impl std::fmt::Display for OptimizationLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RelocModel {
Static,
PIC,
}
impl RelocModel {
pub fn as_str(&self) -> &'static str {
match self {
RelocModel::Static => "static",
RelocModel::PIC => "pic",
}
}
}
impl std::fmt::Display for RelocModel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CodeModel {
Small,
Medium,
Large,
}
impl CodeModel {
pub fn as_str(&self) -> &'static str {
match self {
CodeModel::Small => "small",
CodeModel::Medium => "medium",
CodeModel::Large => "large",
}
}
}
impl std::fmt::Display for CodeModel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
pub struct RiscVTargetMachine {
pub triple: String,
pub cpu: String,
pub features: String,
pub is_64bit: bool,
pub data_layout: String,
pub opt_level: OptimizationLevel,
pub reloc_model: RelocModel,
pub code_model: CodeModel,
}
impl RiscVTargetMachine {
pub fn new(triple: &str, cpu: &str, features: &str) -> Self {
let is_64bit = Self::detect_64bit_from_triple(triple);
let data_layout = if is_64bit {
DEFAULT_RV64_DATA_LAYOUT.to_string()
} else {
DEFAULT_RV32_DATA_LAYOUT.to_string()
};
RiscVTargetMachine {
triple: triple.to_string(),
cpu: cpu.to_string(),
features: features.to_string(),
is_64bit,
data_layout,
opt_level: OptimizationLevel::O2,
reloc_model: RelocModel::Static,
code_model: CodeModel::Small,
}
}
fn detect_64bit_from_triple(triple: &str) -> bool {
let lower = triple.to_lowercase();
lower.starts_with("riscv64")
}
pub fn with_opt_level(mut self, level: OptimizationLevel) -> Self {
self.opt_level = level;
self
}
pub fn with_reloc_model(mut self, model: RelocModel) -> Self {
self.reloc_model = model;
self
}
pub fn with_code_model(mut self, model: CodeModel) -> Self {
self.code_model = model;
self
}
pub fn get_data_layout(&self) -> &str {
&self.data_layout
}
pub fn get_target_triple(&self) -> &str {
&self.triple
}
pub fn get_cpu(&self) -> &str {
&self.cpu
}
pub fn get_features(&self) -> &str {
&self.features
}
pub fn is_64bit(&self) -> bool {
self.is_64bit
}
pub fn get_opt_level(&self) -> OptimizationLevel {
self.opt_level
}
pub fn get_reloc_model(&self) -> RelocModel {
self.reloc_model
}
pub fn get_code_model(&self) -> CodeModel {
self.code_model
}
pub fn is_pic(&self) -> bool {
matches!(self.reloc_model, RelocModel::PIC)
}
pub fn enable_compressed(&self) -> bool {
self.features.contains("+c") || self.features.contains("+zca")
}
pub fn add_analysis_passes(&self) -> Vec<String> {
let mut passes = Vec::new();
passes.push("target-ir-analysis".to_string());
passes.push("domtree".to_string());
if self.opt_level.is_optimizing() {
passes.push("loops".to_string());
passes.push("scalar-evolution".to_string());
passes.push("branch-prob".to_string());
passes.push("block-freq".to_string());
passes.push("target-lib".to_string());
passes.push("stack-safety".to_string());
}
if self.opt_level.is_aggressive() {
passes.push("alias-analysis".to_string());
passes.push("memory-ssa".to_string());
passes.push("demanded-bits".to_string());
passes.push("assumption-cache".to_string());
}
passes
}
pub fn add_ir_passes(&self) -> Vec<String> {
let mut passes = Vec::new();
if !self.opt_level.is_optimizing() {
passes.push("mem2reg".to_string()); passes.push("simplifycfg".to_string()); passes.push("lower-expect".to_string());
passes.push("inline".to_string()); passes.push("sroa".to_string());
return passes;
}
passes.push("inline".to_string());
passes.push("mem2reg".to_string());
passes.push("simplifycfg".to_string());
passes.push("early-cse".to_string());
passes.push("instcombine".to_string());
passes.push("reassociate".to_string());
passes.push("licm".to_string());
passes.push("gvn".to_string());
passes.push("sccp".to_string());
passes.push("sroa".to_string());
passes.push("deadargelim".to_string());
passes.push("correlated-propagation".to_string());
passes.push("aggressive-instcombine".to_string());
passes.push("loop-simplify".to_string());
passes.push("indvars".to_string());
if self.opt_level.is_size_optimized() {
passes.push("merge-functions".to_string());
passes.push("alignment-from-assumptions".to_string());
}
if matches!(
self.opt_level,
OptimizationLevel::O2 | OptimizationLevel::O3
) {
passes.push("jump-threading".to_string());
passes.push("slp-vectorizer".to_string());
passes.push("loop-unroll".to_string());
passes.push("tailcallelim".to_string());
passes.push("dse".to_string()); }
if self.opt_level.is_aggressive() {
passes.push("loop-vectorize".to_string());
passes.push("slp-vectorizer".to_string());
passes.push("global-merge".to_string());
passes.push("argpromotion".to_string());
passes.push("ipsccp".to_string());
}
passes
}
pub fn add_isel_passes(&self) -> Vec<String> {
let mut passes = Vec::new();
passes.push("expand-isel-pseudos".to_string());
if self.opt_level.is_optimizing() {
passes.push("dag-combine".to_string());
passes.push("legalize-dag".to_string());
}
passes.push("select".to_string());
passes.push("localstackalloc".to_string());
passes
}
pub fn add_machine_passes(&self) -> Vec<String> {
let mut passes = Vec::new();
passes.push("prolog-epilog-insertion".to_string());
passes.push("regalloc".to_string());
passes.push("peephole-opt".to_string());
if self.opt_level.is_optimizing() {
passes.push("machine-licm".to_string());
passes.push("machine-cse".to_string());
passes.push("machine-sink".to_string());
passes.push("machine-block-placement".to_string());
}
if self.opt_level.is_aggressive() {
passes.push("machine-combiner".to_string());
passes.push("postrapseudos".to_string());
}
if self.opt_level.is_size_optimized() {
passes.push("machine-outliner".to_string());
}
passes.push("expand-post-rapseudos".to_string());
passes.push("branch-folder".to_string());
passes
}
pub fn emit_assembly(&self, module: &Module) -> String {
let mut output = String::new();
output.push_str(&format!(
"\t.attribute\t4, {}\n",
if self.is_64bit { 5 } else { 4 }
));
output.push_str(&format!(
"\t.attribute\t5, \"rv{}i2p1_{}\"\n",
if self.is_64bit { 64 } else { 32 },
self.features
.replace(',', "_")
.replace('+', "")
.replace('-', "no"),
));
for func_ref in &module.functions {
let func = func_ref.borrow();
let mf_name = if func.name.is_empty() {
format!(".Lfunc{}", func.vid)
} else {
func.name.clone()
};
output.push_str("\t.text\n");
output.push_str(&format!("\t.globl\t{}\n", mf_name));
output.push_str(&format!("\t.type\t{}, @function\n", mf_name));
output.push_str(&format!("{}:\n", mf_name));
output.push_str(&format!("\t.size\t{}, .-{}\n\n", mf_name, mf_name));
}
output
}
pub fn emit_object(&self, _module: &Module) -> Vec<u8> {
Vec::new()
}
pub fn describe(&self) -> String {
format!(
"RiscVTargetMachine {{ triple: \"{}\", cpu: \"{}\", features: \"{}\", \
is_64bit: {}, opt: {}, reloc: {}, code: {} }}",
self.triple,
self.cpu,
self.features,
self.is_64bit,
self.opt_level,
self.reloc_model,
self.code_model,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_construct_rv64_target_machine() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "+m,+a,+f,+d,+c");
assert!(tm.is_64bit());
assert_eq!(tm.get_target_triple(), "riscv64-unknown-elf");
assert_eq!(tm.get_cpu(), "generic-rv64");
assert_eq!(tm.get_features(), "+m,+a,+f,+d,+c");
assert_eq!(tm.get_data_layout(), DEFAULT_RV64_DATA_LAYOUT);
}
#[test]
fn test_construct_rv32_target_machine() {
let tm = RiscVTargetMachine::new("riscv32-unknown-elf", "generic-rv32", "+m,+a,+c");
assert!(!tm.is_64bit());
assert_eq!(tm.get_target_triple(), "riscv32-unknown-elf");
assert_eq!(tm.get_data_layout(), DEFAULT_RV32_DATA_LAYOUT);
}
#[test]
fn test_construct_with_linux_triple() {
let tm = RiscVTargetMachine::new("riscv64-unknown-linux-gnu", "generic-rv64", "");
assert!(tm.is_64bit());
assert_eq!(tm.get_target_triple(), "riscv64-unknown-linux-gnu");
}
#[test]
fn test_detect_64bit_case_insensitive() {
let tm = RiscVTargetMachine::new("RISCV64-unknown-elf", "generic-rv64", "");
assert!(tm.is_64bit());
}
#[test]
fn test_construct_rv32_defaults_to_32bit() {
let tm = RiscVTargetMachine::new("riscv32-unknown-unknown", "generic-rv32", "");
assert!(!tm.is_64bit());
}
#[test]
fn test_with_opt_level() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::O3);
assert_eq!(tm.get_opt_level(), OptimizationLevel::O3);
assert!(tm.get_opt_level().is_aggressive());
}
#[test]
fn test_with_reloc_model() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_reloc_model(RelocModel::PIC);
assert!(tm.is_pic());
}
#[test]
fn test_with_code_model() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_code_model(CodeModel::Medium);
assert_eq!(tm.get_code_model(), CodeModel::Medium);
}
#[test]
fn test_data_layout_rv64() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "");
let dl = tm.get_data_layout();
assert!(dl.contains("p:64:64"), "RV64 should have 64-bit pointers");
assert!(dl.contains("S128"), "Stack aligned to 128 bits");
assert!(dl.starts_with("e-m:e"), "Little-endian ELF mangling");
}
#[test]
fn test_data_layout_rv32() {
let tm = RiscVTargetMachine::new("riscv32-unknown-elf", "generic-rv32", "");
let dl = tm.get_data_layout();
assert!(dl.contains("p:32:32"), "RV32 should have 32-bit pointers");
assert!(dl.starts_with("e-m:e"), "Little-endian ELF mangling");
}
#[test]
fn test_analysis_passes_o0() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::O0);
let passes = tm.add_analysis_passes();
assert!(passes.contains(&"target-ir-analysis".to_string()));
assert!(passes.contains(&"domtree".to_string()));
assert!(!passes.contains(&"alias-analysis".to_string()));
}
#[test]
fn test_analysis_passes_o3() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::O3);
let passes = tm.add_analysis_passes();
assert!(passes.contains(&"alias-analysis".to_string()));
assert!(passes.contains(&"memory-ssa".to_string()));
}
#[test]
fn test_ir_passes_o0_minimal() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::O0);
let passes = tm.add_ir_passes();
assert!(passes.contains(&"mem2reg".to_string()));
assert!(!passes.contains(&"gvn".to_string()));
assert!(!passes.contains(&"licm".to_string()));
}
#[test]
fn test_ir_passes_o2_has_gvn() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::O2);
let passes = tm.add_ir_passes();
assert!(passes.contains(&"gvn".to_string()));
assert!(passes.contains(&"licm".to_string()));
assert!(passes.contains(&"jump-threading".to_string()));
}
#[test]
fn test_ir_passes_o3_has_vectorize() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::O3);
let passes = tm.add_ir_passes();
assert!(passes.contains(&"loop-vectorize".to_string()));
}
#[test]
fn test_ir_passes_os_favors_size() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::Os);
let passes = tm.add_ir_passes();
assert!(passes.contains(&"merge-functions".to_string()));
}
#[test]
fn test_isel_passes() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "");
let passes = tm.add_isel_passes();
assert!(passes.iter().any(|p| p == "select"));
}
#[test]
fn test_machine_passes_o0() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::O0);
let passes = tm.add_machine_passes();
assert!(passes.contains(&"regalloc".to_string()));
assert!(!passes.contains(&"machine-outliner".to_string()));
}
#[test]
fn test_machine_passes_oz() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "")
.with_opt_level(OptimizationLevel::Oz);
let passes = tm.add_machine_passes();
assert!(passes.contains(&"machine-outliner".to_string()));
}
#[test]
fn test_describe() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "+m,+c");
let desc = tm.describe();
assert!(desc.contains("riscv64-unknown-elf"));
assert!(desc.contains("generic-rv64"));
assert!(desc.contains("+m,+c"));
}
#[test]
fn test_enable_compressed() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "+m,+a,+c");
assert!(tm.enable_compressed());
let tm_no_c = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "+m,+a");
assert!(!tm_no_c.enable_compressed());
}
#[test]
fn test_emit_assembly_returns_non_empty() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "");
let module = Module::new("test");
let asm = tm.emit_assembly(&module);
assert!(!asm.is_empty() || module.functions.is_empty());
}
#[test]
fn test_emit_object_returns_bytes() {
let tm = RiscVTargetMachine::new("riscv64-unknown-elf", "generic-rv64", "");
let module = Module::new("test");
let obj = tm.emit_object(&module);
assert!(obj.is_empty()); }
#[test]
fn test_optimization_level_display() {
assert_eq!(format!("{}", OptimizationLevel::O0), "O0");
assert_eq!(format!("{}", OptimizationLevel::O3), "O3");
assert_eq!(format!("{}", OptimizationLevel::Os), "Os");
assert_eq!(format!("{}", OptimizationLevel::Oz), "Oz");
}
#[test]
fn test_optimization_level_is_optimizing() {
assert!(!OptimizationLevel::O0.is_optimizing());
assert!(OptimizationLevel::O1.is_optimizing());
assert!(OptimizationLevel::O2.is_optimizing());
assert!(OptimizationLevel::O3.is_optimizing());
assert!(OptimizationLevel::Os.is_optimizing());
assert!(OptimizationLevel::Oz.is_optimizing());
}
#[test]
fn test_reloc_model_display() {
assert_eq!(format!("{}", RelocModel::Static), "static");
assert_eq!(format!("{}", RelocModel::PIC), "pic");
}
#[test]
fn test_code_model_display() {
assert_eq!(format!("{}", CodeModel::Small), "small");
assert_eq!(format!("{}", CodeModel::Medium), "medium");
assert_eq!(format!("{}", CodeModel::Large), "large");
}
}