use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ClangOptLevel {
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl ClangOptLevel {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_uppercase().as_str() {
"O0" | "0" => Some(Self::O0),
"O1" | "1" => Some(Self::O1),
"O2" | "2" => Some(Self::O2),
"O3" | "3" => Some(Self::O3),
"OS" => Some(Self::Os),
"OZ" => Some(Self::Oz),
_ => None,
}
}
pub fn to_flag(&self) -> String {
match self {
Self::O0 => "-O0".into(),
Self::O1 => "-O1".into(),
Self::O2 => "-O2".into(),
Self::O3 => "-O3".into(),
Self::Os => "-Os".into(),
Self::Oz => "-Oz".into(),
}
}
pub fn is_optimizing(&self) -> bool {
!matches!(self, Self::O0)
}
pub fn is_size_optimized(&self) -> bool {
matches!(self, Self::Os | Self::Oz)
}
pub fn is_aggressive(&self) -> bool {
matches!(self, Self::O3 | Self::Oz)
}
pub fn description(&self) -> &'static str {
match self {
Self::O0 => "No optimization, fast compile",
Self::O1 => "Basic optimizations (mem2reg, instcombine, simplifycfg, dce, licm)",
Self::O2 => "Moderate optimizations (+gvn, earlycse, loop-rotate, loop-unroll, slp-vectorize)",
Self::O3 => "Aggressive optimizations (+loop-vectorize, aggressive-instcombine, argpromotion)",
Self::Os => "Optimize for size (favor smaller code)",
Self::Oz => "Aggressive size optimization (minsize, no unrolling, no vectorization)",
}
}
pub fn inline_threshold(&self) -> u32 {
match self {
Self::O0 => 0,
Self::O1 => 75,
Self::O2 => 225,
Self::O3 => 275,
Self::Os => 75,
Self::Oz => 25,
}
}
}
impl fmt::Display for ClangOptLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::O0 => write!(f, "O0"),
Self::O1 => write!(f, "O1"),
Self::O2 => write!(f, "O2"),
Self::O3 => write!(f, "O3"),
Self::Os => write!(f, "Os"),
Self::Oz => write!(f, "Oz"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PassName {
PromoteMemoryToRegister,
InstructionCombining,
SimplifyCFG,
DeadCodeElimination,
GlobalValueNumbering,
EarlyCSE,
LoopInvariantCodeMotion,
LoopRotate,
LoopUnroll,
LoopUnswitch,
LoopIdiom,
LoopDeletion,
LoopSimplify,
LoopDistribute,
SLPVectorize,
LoopVectorize,
AggressiveInstCombine,
ArgumentPromotion,
IPSCCP,
CalledValuePropagation,
GlobalOptimizer,
GlobalDCE,
Inline,
AlwaysInliner,
CFGSimplification,
Reassociate,
SCCP,
JumpThreading,
CorrelatedValuePropagation,
TailCallElimination,
MemCpyOptimization,
DeadStoreElimination,
FunctionAttrs,
StripDeadPrototypes,
GlobalMerge,
MergeFunctions,
LTOGlobalDCE,
LTOInternalize,
LTOArgumentPromotion,
LTODeadArgumentElimination,
}
impl PassName {
pub fn name(&self) -> &'static str {
match self {
Self::PromoteMemoryToRegister => "mem2reg",
Self::InstructionCombining => "instcombine",
Self::SimplifyCFG => "simplifycfg",
Self::DeadCodeElimination => "dce",
Self::GlobalValueNumbering => "gvn",
Self::EarlyCSE => "early-cse",
Self::LoopInvariantCodeMotion => "licm",
Self::LoopRotate => "loop-rotate",
Self::LoopUnroll => "loop-unroll",
Self::LoopUnswitch => "loop-unswitch",
Self::LoopIdiom => "loop-idiom",
Self::LoopDeletion => "loop-deletion",
Self::LoopSimplify => "loop-simplify",
Self::LoopDistribute => "loop-distribute",
Self::SLPVectorize => "slp-vectorizer",
Self::LoopVectorize => "loop-vectorize",
Self::AggressiveInstCombine => "aggressive-instcombine",
Self::ArgumentPromotion => "argpromotion",
Self::IPSCCP => "ipsccp",
Self::CalledValuePropagation => "called-value-propagation",
Self::GlobalOptimizer => "globalopt",
Self::GlobalDCE => "globaldce",
Self::Inline => "inline",
Self::AlwaysInliner => "always-inline",
Self::CFGSimplification => "simplifycfg",
Self::Reassociate => "reassociate",
Self::SCCP => "sccp",
Self::JumpThreading => "jump-threading",
Self::CorrelatedValuePropagation => "correlated-propagation",
Self::TailCallElimination => "tailcallelim",
Self::MemCpyOptimization => "memcpyopt",
Self::DeadStoreElimination => "dse",
Self::FunctionAttrs => "functionattrs",
Self::StripDeadPrototypes => "strip-dead-prototypes",
Self::GlobalMerge => "global-merge",
Self::MergeFunctions => "mergefunc",
Self::LTOGlobalDCE => "globaldce",
Self::LTOInternalize => "internalize",
Self::LTOArgumentPromotion => "argpromotion",
Self::LTODeadArgumentElimination => "deadargelim",
}
}
}
#[derive(Debug, Clone)]
pub struct OptimizationPipelineConfig {
pub opt_level: ClangOptLevel,
pub fast_math: bool,
pub omit_frame_pointer: bool,
pub vectorize: bool,
pub slp_vectorize: bool,
pub inline_functions: bool,
pub inline_threshold: u32,
pub lto: bool,
pub thin_lto: bool,
pub strip_debug: bool,
pub pgo: bool,
pub custom_passes: Vec<PassName>,
pub excluded_passes: Vec<PassName>,
pub emit_remarks: bool,
pub remark_format: RemarkFormat,
pub target_cpu: Option<String>,
pub disable_optimizations: bool,
pub sanitize: bool,
}
impl Default for OptimizationPipelineConfig {
fn default() -> Self {
Self {
opt_level: ClangOptLevel::O2,
fast_math: false,
omit_frame_pointer: true,
vectorize: true,
slp_vectorize: true,
inline_functions: true,
inline_threshold: 0,
lto: false,
thin_lto: false,
strip_debug: false,
pgo: false,
custom_passes: Vec::new(),
excluded_passes: Vec::new(),
emit_remarks: false,
remark_format: RemarkFormat::YAML,
target_cpu: None,
disable_optimizations: false,
sanitize: false,
}
}
}
impl OptimizationPipelineConfig {
pub fn for_level(level: ClangOptLevel) -> Self {
let mut config = Self::default();
config.opt_level = level;
match level {
ClangOptLevel::O0 => {
config.vectorize = false;
config.slp_vectorize = false;
config.inline_functions = false;
}
ClangOptLevel::Os | ClangOptLevel::Oz => {
config.vectorize = false;
config.slp_vectorize = false;
config.inline_functions = true;
}
_ => {}
}
config
}
pub fn with_fast_math(mut self) -> Self {
self.fast_math = true;
self
}
pub fn with_frame_pointer(mut self) -> Self {
self.omit_frame_pointer = false;
self
}
pub fn with_vectorize(mut self, enabled: bool) -> Self {
self.vectorize = enabled;
self
}
pub fn with_lto(mut self, thin: bool) -> Self {
self.lto = true;
self.thin_lto = thin;
self
}
pub fn with_remarks(mut self, format: RemarkFormat) -> Self {
self.emit_remarks = true;
self.remark_format = format;
self
}
pub fn add_pass(mut self, pass: PassName) -> Self {
self.custom_passes.push(pass);
self
}
pub fn exclude_pass(mut self, pass: PassName) -> Self {
self.excluded_passes.push(pass);
self
}
}
#[derive(Debug, Clone)]
pub struct PipelinePass {
pub name: PassName,
pub enabled: bool,
pub params: BTreeMap<String, String>,
}
impl PipelinePass {
pub fn new(name: PassName) -> Self {
Self {
name,
enabled: true,
params: BTreeMap::new(),
}
}
pub fn disabled(name: PassName) -> Self {
Self {
name,
enabled: false,
params: BTreeMap::new(),
}
}
pub fn with_param(mut self, key: &str, value: &str) -> Self {
self.params.insert(key.to_string(), value.to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct PassPipeline {
pub opt_level: ClangOptLevel,
pub passes: Vec<PipelinePass>,
pub description: String,
}
impl PassPipeline {
pub fn new(opt_level: ClangOptLevel) -> Self {
Self {
opt_level,
passes: Vec::new(),
description: String::new(),
}
}
pub fn add_pass(&mut self, pass: PipelinePass) {
self.passes.push(pass);
}
pub fn enabled_pass_count(&self) -> usize {
self.passes.iter().filter(|p| p.enabled).count()
}
pub fn enabled_pass_names(&self) -> Vec<String> {
self.passes
.iter()
.filter(|p| p.enabled)
.map(|p| p.name.name().to_string())
.collect()
}
}
pub fn build_optimization_pipeline(level: ClangOptLevel) -> PassPipeline {
let mut pipeline = PassPipeline::new(level);
pipeline.description = level.description().to_string();
let passes = match level {
ClangOptLevel::O0 => o0_passes(),
ClangOptLevel::O1 => o1_passes(),
ClangOptLevel::O2 => o2_passes(),
ClangOptLevel::O3 => o3_passes(),
ClangOptLevel::Os => os_passes(),
ClangOptLevel::Oz => oz_passes(),
};
for pass in passes {
pipeline.add_pass(pass);
}
pipeline
}
fn o0_passes() -> Vec<PipelinePass> {
vec![
PipelinePass::new(PassName::AlwaysInliner),
PipelinePass::disabled(PassName::PromoteMemoryToRegister),
PipelinePass::disabled(PassName::InstructionCombining),
PipelinePass::disabled(PassName::SimplifyCFG),
]
}
fn o1_passes() -> Vec<PipelinePass> {
vec![
PipelinePass::new(PassName::PromoteMemoryToRegister),
PipelinePass::new(PassName::InstructionCombining),
PipelinePass::new(PassName::SimplifyCFG),
PipelinePass::new(PassName::DeadCodeElimination),
PipelinePass::new(PassName::LoopInvariantCodeMotion),
PipelinePass::new(PassName::SCCP),
PipelinePass::new(PassName::Reassociate),
PipelinePass::new(PassName::CFGSimplification),
PipelinePass::new(PassName::TailCallElimination),
PipelinePass::new(PassName::MemCpyOptimization),
PipelinePass::new(PassName::DeadStoreElimination),
PipelinePass::new(PassName::FunctionAttrs),
]
}
fn o2_passes() -> Vec<PipelinePass> {
vec![
PipelinePass::new(PassName::PromoteMemoryToRegister),
PipelinePass::new(PassName::InstructionCombining),
PipelinePass::new(PassName::SimplifyCFG),
PipelinePass::new(PassName::DeadCodeElimination),
PipelinePass::new(PassName::GlobalValueNumbering),
PipelinePass::new(PassName::EarlyCSE),
PipelinePass::new(PassName::LoopRotate),
PipelinePass::new(PassName::LoopInvariantCodeMotion),
PipelinePass::new(PassName::LoopUnroll).with_param("threshold", "150"),
PipelinePass::new(PassName::SLPVectorize),
PipelinePass::new(PassName::SCCP),
PipelinePass::new(PassName::Reassociate),
PipelinePass::new(PassName::JumpThreading),
PipelinePass::new(PassName::CorrelatedValuePropagation),
PipelinePass::new(PassName::CFGSimplification),
PipelinePass::new(PassName::TailCallElimination),
PipelinePass::new(PassName::MemCpyOptimization),
PipelinePass::new(PassName::DeadStoreElimination),
PipelinePass::new(PassName::FunctionAttrs),
PipelinePass::new(PassName::GlobalDCE),
]
}
fn o3_passes() -> Vec<PipelinePass> {
vec![
PipelinePass::new(PassName::PromoteMemoryToRegister),
PipelinePass::new(PassName::InstructionCombining),
PipelinePass::new(PassName::SimplifyCFG),
PipelinePass::new(PassName::DeadCodeElimination),
PipelinePass::new(PassName::GlobalValueNumbering),
PipelinePass::new(PassName::EarlyCSE),
PipelinePass::new(PassName::LoopRotate),
PipelinePass::new(PassName::LoopInvariantCodeMotion),
PipelinePass::new(PassName::LoopUnroll).with_param("threshold", "300"),
PipelinePass::new(PassName::LoopVectorize),
PipelinePass::new(PassName::SLPVectorize),
PipelinePass::new(PassName::AggressiveInstCombine),
PipelinePass::new(PassName::ArgumentPromotion),
PipelinePass::new(PassName::IPSCCP),
PipelinePass::new(PassName::CalledValuePropagation),
PipelinePass::new(PassName::GlobalOptimizer),
PipelinePass::new(PassName::SCCP),
PipelinePass::new(PassName::Reassociate),
PipelinePass::new(PassName::JumpThreading),
PipelinePass::new(PassName::CorrelatedValuePropagation),
PipelinePass::new(PassName::CFGSimplification),
PipelinePass::new(PassName::TailCallElimination),
PipelinePass::new(PassName::MemCpyOptimization),
PipelinePass::new(PassName::DeadStoreElimination),
PipelinePass::new(PassName::FunctionAttrs),
PipelinePass::new(PassName::GlobalDCE),
PipelinePass::new(PassName::MergeFunctions),
]
}
fn os_passes() -> Vec<PipelinePass> {
vec![
PipelinePass::new(PassName::PromoteMemoryToRegister),
PipelinePass::new(PassName::InstructionCombining),
PipelinePass::new(PassName::SimplifyCFG),
PipelinePass::new(PassName::DeadCodeElimination),
PipelinePass::new(PassName::GlobalValueNumbering),
PipelinePass::new(PassName::EarlyCSE),
PipelinePass::new(PassName::LoopRotate),
PipelinePass::new(PassName::LoopInvariantCodeMotion),
PipelinePass::new(PassName::SCCP),
PipelinePass::new(PassName::Reassociate),
PipelinePass::new(PassName::JumpThreading),
PipelinePass::new(PassName::CFGSimplification),
PipelinePass::new(PassName::TailCallElimination),
PipelinePass::new(PassName::MemCpyOptimization),
PipelinePass::new(PassName::DeadStoreElimination),
PipelinePass::new(PassName::FunctionAttrs),
PipelinePass::new(PassName::GlobalDCE),
PipelinePass::new(PassName::MergeFunctions),
]
}
fn oz_passes() -> Vec<PipelinePass> {
vec![
PipelinePass::new(PassName::PromoteMemoryToRegister),
PipelinePass::new(PassName::InstructionCombining),
PipelinePass::new(PassName::SimplifyCFG),
PipelinePass::new(PassName::DeadCodeElimination),
PipelinePass::new(PassName::GlobalValueNumbering),
PipelinePass::new(PassName::EarlyCSE),
PipelinePass::new(PassName::LoopRotate),
PipelinePass::new(PassName::LoopInvariantCodeMotion),
PipelinePass::new(PassName::SCCP),
PipelinePass::new(PassName::Reassociate),
PipelinePass::new(PassName::JumpThreading),
PipelinePass::new(PassName::CFGSimplification),
PipelinePass::new(PassName::TailCallElimination),
PipelinePass::new(PassName::MemCpyOptimization),
PipelinePass::new(PassName::DeadStoreElimination),
PipelinePass::new(PassName::FunctionAttrs),
PipelinePass::new(PassName::GlobalDCE),
PipelinePass::new(PassName::MergeFunctions),
]
}
pub fn build_lto_pipeline(thin: bool) -> PassPipeline {
let mut pipeline = PassPipeline::new(ClangOptLevel::O2);
pipeline.description = if thin {
"ThinLTO pipeline"
} else {
"Full LTO pipeline"
}
.to_string();
let passes = vec![
PipelinePass::new(PassName::LTOGlobalDCE),
PipelinePass::new(PassName::LTOInternalize),
PipelinePass::new(PassName::LTOArgumentPromotion),
PipelinePass::new(PassName::LTODeadArgumentElimination),
];
for pass in passes {
pipeline.add_pass(pass);
}
pipeline
}
#[derive(Debug, Clone)]
pub struct OptimizationFlags {
pub fast_math: bool,
pub no_omit_frame_pointer: bool,
pub vectorize: bool,
pub no_vectorize: bool,
pub slp_vectorize: bool,
pub no_slp_vectorize: bool,
pub inline_functions: bool,
pub no_inline_functions: bool,
pub lto: bool,
pub thin_lto: bool,
pub opt_disable: bool,
pub inline_threshold: Option<u32>,
pub strip_debug: bool,
pub sanitize_address: bool,
pub sanitize_memory: bool,
pub sanitize_undefined: bool,
pub sanitize_thread: bool,
}
impl Default for OptimizationFlags {
fn default() -> Self {
Self {
fast_math: false,
no_omit_frame_pointer: false,
vectorize: true,
no_vectorize: false,
slp_vectorize: true,
no_slp_vectorize: false,
inline_functions: true,
no_inline_functions: false,
lto: false,
thin_lto: false,
opt_disable: false,
inline_threshold: None,
strip_debug: false,
sanitize_address: false,
sanitize_memory: false,
sanitize_undefined: false,
sanitize_thread: false,
}
}
}
impl OptimizationFlags {
pub fn from_args(args: &[String]) -> Self {
let mut flags = Self::default();
for arg in args {
match arg.as_str() {
"-ffast-math" => flags.fast_math = true,
"-fno-omit-frame-pointer" => flags.no_omit_frame_pointer = true,
"-fomit-frame-pointer" => flags.no_omit_frame_pointer = false,
"-fvectorize" => {
flags.vectorize = true;
flags.no_vectorize = false;
}
"-fno-vectorize" => {
flags.no_vectorize = true;
flags.vectorize = false;
}
"-fslp-vectorize" => {
flags.slp_vectorize = true;
flags.no_slp_vectorize = false;
}
"-fno-slp-vectorize" => {
flags.no_slp_vectorize = true;
flags.slp_vectorize = false;
}
"-finline-functions" => flags.inline_functions = true,
"-fno-inline-functions" => {
flags.inline_functions = false;
flags.no_inline_functions = true;
}
"-flto" => {
flags.lto = true;
flags.thin_lto = false;
}
"-flto=thin" => {
flags.thin_lto = true;
flags.lto = true;
}
"-fno-lto" => {
flags.lto = false;
flags.thin_lto = false;
}
"-g0" | "-g0" => flags.strip_debug = true,
"-fsanitize=address" => flags.sanitize_address = true,
"-fsanitize=memory" => flags.sanitize_memory = true,
"-fsanitize=undefined" => flags.sanitize_undefined = true,
"-fsanitize=thread" => flags.sanitize_thread = true,
_ => {
if arg.starts_with("-mllvm -inline-threshold=") {
if let Some(val) = arg.split('=').nth(1) {
flags.inline_threshold = val.parse().ok();
}
}
}
}
}
flags
}
pub fn apply_to_config(&self, config: &mut OptimizationPipelineConfig) {
config.fast_math = self.fast_math;
config.omit_frame_pointer = !self.no_omit_frame_pointer;
if self.no_vectorize {
config.vectorize = false;
} else if self.vectorize {
config.vectorize = true;
}
if self.no_slp_vectorize {
config.slp_vectorize = false;
} else if self.slp_vectorize {
config.slp_vectorize = true;
}
if self.no_inline_functions {
config.inline_functions = false;
}
if self.lto {
config.lto = true;
config.thin_lto = self.thin_lto;
}
if let Some(threshold) = self.inline_threshold {
config.inline_threshold = threshold;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemarkFormat {
YAML,
JSON,
Bitstream,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RemarkKind {
Missed,
Analysis,
Passed,
Other,
}
impl RemarkKind {
pub fn from_flag(flag: &str) -> Option<Self> {
match flag {
"missed" => Some(Self::Missed),
"analysis" => Some(Self::Analysis),
"passed" => Some(Self::Passed),
"all" => Some(Self::Other),
_ => None,
}
}
pub fn to_flag_suffix(&self) -> &'static str {
match self {
Self::Missed => "missed",
Self::Analysis => "analysis",
Self::Passed => "passed",
Self::Other => "all",
}
}
}
#[derive(Debug, Clone)]
pub struct OptimizationRemark {
pub kind: RemarkKind,
pub pass_name: String,
pub function: String,
pub location: Option<(String, u32, u32)>,
pub message: String,
pub args: BTreeMap<String, String>,
}
impl OptimizationRemark {
pub fn new(kind: RemarkKind, pass_name: &str, function: &str, message: &str) -> Self {
Self {
kind,
pass_name: pass_name.to_string(),
function: function.to_string(),
location: None,
message: message.to_string(),
args: BTreeMap::new(),
}
}
pub fn with_location(mut self, file: &str, line: u32, column: u32) -> Self {
self.location = Some((file.to_string(), line, column));
self
}
pub fn with_arg(mut self, key: &str, value: &str) -> Self {
self.args.insert(key.to_string(), value.to_string());
self
}
pub fn to_yaml(&self) -> String {
let mut yaml = String::new();
yaml.push_str("--- !");
yaml.push_str(match self.kind {
RemarkKind::Missed => "Missed",
RemarkKind::Analysis => "Analysis",
RemarkKind::Passed => "Passed",
RemarkKind::Other => "Other",
});
yaml.push('\n');
yaml.push_str(&format!("Pass: {}\n", self.pass_name));
yaml.push_str(&format!("Name: {}\n", self.message));
yaml.push_str(&format!("Function: {}\n", self.function));
if let Some((ref file, line, col)) = self.location {
yaml.push_str(&format!("DebugLoc: {{ File: {}, Line: {}, Column: {} }}\n", file, line, col));
}
for (k, v) in &self.args {
yaml.push_str(&format!(" {}: {}\n", k, v));
}
yaml
}
pub fn summary(&self) -> String {
let kind_str = match self.kind {
RemarkKind::Missed => "missed",
RemarkKind::Analysis => "analysis",
RemarkKind::Passed => "passed",
RemarkKind::Other => "other",
};
if let Some((ref file, line, col)) = self.location {
format!("{}:{}:{}: {}: {} in {}", file, line, col, kind_str, self.message, self.function)
} else {
format!("{}: {} in {}", kind_str, self.message, self.function)
}
}
}
#[derive(Debug, Clone)]
pub struct OptimizationRemarkEmitter {
pub enabled: bool,
pub format: RemarkFormat,
pub remarks: Vec<OptimizationRemark>,
pub pass_filter: Vec<String>,
}
impl OptimizationRemarkEmitter {
pub fn new() -> Self {
Self {
enabled: false,
format: RemarkFormat::YAML,
remarks: Vec::new(),
pass_filter: Vec::new(),
}
}
pub fn enable(&mut self) {
self.enabled = true;
}
pub fn with_format(mut self, format: RemarkFormat) -> Self {
self.format = format;
self
}
pub fn filter_pass(mut self, pass: &str) -> Self {
self.pass_filter.push(pass.to_string());
self
}
pub fn emit(&mut self, remark: OptimizationRemark) {
if !self.enabled {
return;
}
if !self.pass_filter.is_empty()
&& !self.pass_filter.contains(&remark.pass_name)
{
return;
}
self.remarks.push(remark);
}
pub fn missed(
&mut self,
pass_name: &str,
function: &str,
message: &str,
) {
self.emit(OptimizationRemark::new(
RemarkKind::Missed,
pass_name,
function,
message,
));
}
pub fn analysis(
&mut self,
pass_name: &str,
function: &str,
message: &str,
) {
self.emit(OptimizationRemark::new(
RemarkKind::Analysis,
pass_name,
function,
message,
));
}
pub fn passed(
&mut self,
pass_name: &str,
function: &str,
message: &str,
) {
self.emit(OptimizationRemark::new(
RemarkKind::Passed,
pass_name,
function,
message,
));
}
pub fn remark_count(&self) -> usize {
self.remarks.len()
}
pub fn clear(&mut self) {
self.remarks.clear();
}
pub fn format_all(&self) -> String {
let mut output = String::new();
for remark in &self.remarks {
match self.format {
RemarkFormat::YAML => {
output.push_str(&remark.to_yaml());
output.push('\n');
}
RemarkFormat::JSON => {
output.push_str(&format!(
r#"{{"kind":"{}","pass":"{}","fn":"{}","msg":"{}"}}"#,
match remark.kind {
RemarkKind::Missed => "missed",
RemarkKind::Analysis => "analysis",
RemarkKind::Passed => "passed",
RemarkKind::Other => "other",
},
remark.pass_name,
remark.function,
remark.message
));
output.push('\n');
}
RemarkFormat::Bitstream => {
output.push_str(&format!(
"[bitstream] {}:{}",
remark.pass_name, remark.message
));
output.push('\n');
}
}
}
output
}
}
impl Default for OptimizationRemarkEmitter {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PassBuilder {
pub config: OptimizationPipelineConfig,
pub remarks: OptimizationRemarkEmitter,
}
impl PassBuilder {
pub fn new(config: OptimizationPipelineConfig) -> Self {
Self {
config,
remarks: OptimizationRemarkEmitter::new(),
}
}
pub fn from_args(args: &[String], opt_level: ClangOptLevel) -> Self {
let config = OptimizationPipelineConfig::for_level(opt_level);
let flags = OptimizationFlags::from_args(args);
let mut config = config;
flags.apply_to_config(&mut config);
Self::new(config)
}
pub fn build_pipeline(&self) -> PassPipeline {
if self.config.disable_optimizations {
return build_optimization_pipeline(ClangOptLevel::O0);
}
let mut pipeline = build_optimization_pipeline(self.config.opt_level);
if !self.config.vectorize {
pipeline.passes.retain(|p| {
!matches!(p.name, PassName::LoopVectorize | PassName::SLPVectorize)
});
}
if !self.config.slp_vectorize {
pipeline.passes.retain(|p| p.name != PassName::SLPVectorize);
}
if !self.config.inline_functions {
pipeline.passes.retain(|p| p.name != PassName::Inline);
}
for pass_name in &self.config.custom_passes {
if !self.config.excluded_passes.contains(pass_name) {
pipeline.add_pass(PipelinePass::new(*pass_name));
}
}
pipeline
.passes
.retain(|p| !self.config.excluded_passes.contains(&p.name));
pipeline
}
pub fn build_lto_pipeline(&self) -> Option<PassPipeline> {
if self.config.lto {
Some(build_lto_pipeline(self.config.thin_lto))
} else {
None
}
}
pub fn enable_remarks(&mut self, format: RemarkFormat) {
self.config.emit_remarks = true;
self.config.remark_format = format;
self.remarks = OptimizationRemarkEmitter::new().with_format(format);
self.remarks.enable();
}
pub fn get_inline_threshold(&self) -> u32 {
if self.config.inline_threshold > 0 {
self.config.inline_threshold
} else {
self.config.opt_level.inline_threshold()
}
}
pub fn has_pass(&self, pass: PassName) -> bool {
let pipeline = self.build_pipeline();
pipeline.passes.iter().any(|p| p.name == pass && p.enabled)
}
}
impl Default for PassBuilder {
fn default() -> Self {
Self::new(OptimizationPipelineConfig::default())
}
}
#[derive(Debug, Clone)]
pub struct OptimizationSummary {
pub opt_level: ClangOptLevel,
pub pass_count: usize,
pub enabled_passes: usize,
pub fast_math: bool,
pub vectorization: bool,
pub lto: bool,
pub thin_lto: bool,
pub omit_frame_pointer: bool,
pub inline_threshold: u32,
pub pass_names: Vec<String>,
}
impl OptimizationSummary {
pub fn from_builder(builder: &PassBuilder) -> Self {
let pipeline = builder.build_pipeline();
Self {
opt_level: builder.config.opt_level,
pass_count: pipeline.passes.len(),
enabled_passes: pipeline.enabled_pass_count(),
fast_math: builder.config.fast_math,
vectorization: builder.config.vectorize,
lto: builder.config.lto,
thin_lto: builder.config.thin_lto,
omit_frame_pointer: builder.config.omit_frame_pointer,
inline_threshold: builder.get_inline_threshold(),
pass_names: pipeline.enabled_pass_names(),
}
}
pub fn format(&self) -> String {
let mut s = String::new();
s.push_str(&format!("Optimization Level: {}\n", self.opt_level));
s.push_str(&format!(
"Passes: {} total, {} enabled\n",
self.pass_count, self.enabled_passes
));
s.push_str(&format!("Fast Math: {}\n", self.fast_math));
s.push_str(&format!("Vectorization: {}\n", self.vectorization));
s.push_str(&format!(
"LTO: {} ({})\n",
self.lto,
if self.thin_lto { "thin" } else { "full" }
));
s.push_str(&format!(
"Omit Frame Pointer: {}\n",
self.omit_frame_pointer
));
s.push_str(&format!("Inline Threshold: {}\n", self.inline_threshold));
if !self.pass_names.is_empty() {
s.push_str("Enabled passes:\n");
for name in &self.pass_names {
s.push_str(&format!(" - {}\n", name));
}
}
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opt_level_from_str() {
assert_eq!(ClangOptLevel::from_str("O0"), Some(ClangOptLevel::O0));
assert_eq!(ClangOptLevel::from_str("O2"), Some(ClangOptLevel::O2));
assert_eq!(ClangOptLevel::from_str("Os"), Some(ClangOptLevel::Os));
assert_eq!(ClangOptLevel::from_str("Oz"), Some(ClangOptLevel::Oz));
assert_eq!(ClangOptLevel::from_str("O5"), None);
}
#[test]
fn test_opt_level_to_flag() {
assert_eq!(ClangOptLevel::O0.to_flag(), "-O0");
assert_eq!(ClangOptLevel::O3.to_flag(), "-O3");
assert_eq!(ClangOptLevel::Os.to_flag(), "-Os");
}
#[test]
fn test_opt_level_is_optimizing() {
assert!(!ClangOptLevel::O0.is_optimizing());
assert!(ClangOptLevel::O2.is_optimizing());
}
#[test]
fn test_opt_level_is_size_optimized() {
assert!(!ClangOptLevel::O2.is_size_optimized());
assert!(ClangOptLevel::Os.is_size_optimized());
assert!(ClangOptLevel::Oz.is_size_optimized());
}
#[test]
fn test_opt_level_inline_threshold() {
assert_eq!(ClangOptLevel::O0.inline_threshold(), 0);
assert_eq!(ClangOptLevel::O1.inline_threshold(), 75);
assert_eq!(ClangOptLevel::O2.inline_threshold(), 225);
assert_eq!(ClangOptLevel::O3.inline_threshold(), 275);
assert_eq!(ClangOptLevel::Os.inline_threshold(), 75);
assert_eq!(ClangOptLevel::Oz.inline_threshold(), 25);
}
#[test]
fn test_build_o0_pipeline() {
let pipeline = build_optimization_pipeline(ClangOptLevel::O0);
assert_eq!(pipeline.opt_level, ClangOptLevel::O0);
assert!(!pipeline.passes.is_empty());
}
#[test]
fn test_build_o1_pipeline() {
let pipeline = build_optimization_pipeline(ClangOptLevel::O1);
assert!(pipeline.enabled_pass_count() > 0);
}
#[test]
fn test_build_o2_pipeline() {
let pipeline = build_optimization_pipeline(ClangOptLevel::O2);
let names = pipeline.enabled_pass_names();
assert!(names.contains(&"gvn".to_string()));
assert!(names.contains(&"loop-rotate".to_string()));
}
#[test]
fn test_build_o3_pipeline() {
let pipeline = build_optimization_pipeline(ClangOptLevel::O3);
let names = pipeline.enabled_pass_names();
assert!(names.contains(&"loop-vectorize".to_string()));
assert!(names.contains(&"argpromotion".to_string()));
}
#[test]
fn test_build_os_pipeline() {
let pipeline = build_optimization_pipeline(ClangOptLevel::Os);
let names = pipeline.enabled_pass_names();
assert!(!names.contains(&"loop-unroll".to_string()));
assert!(!names.contains(&"loop-vectorize".to_string()));
}
#[test]
fn test_build_oz_pipeline() {
let pipeline = build_optimization_pipeline(ClangOptLevel::Oz);
let names = pipeline.enabled_pass_names();
assert!(!names.contains(&"slp-vectorizer".to_string()));
}
#[test]
fn test_build_lto_pipeline() {
let pipeline = build_lto_pipeline(false);
assert!(!pipeline.passes.is_empty());
let names = pipeline.enabled_pass_names();
assert!(names.contains(&"internalize".to_string()));
}
#[test]
fn test_build_thin_lto_pipeline() {
let pipeline = build_lto_pipeline(true);
assert!(pipeline.description.contains("ThinLTO"));
}
#[test]
fn test_pipeline_config_default() {
let config = OptimizationPipelineConfig::default();
assert_eq!(config.opt_level, ClangOptLevel::O2);
assert!(config.vectorize);
}
#[test]
fn test_pipeline_config_for_level_o0() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O0);
assert!(!config.vectorize);
assert!(!config.inline_functions);
}
#[test]
fn test_pipeline_config_builder() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O3)
.with_fast_math()
.with_lto(true)
.with_remarks(RemarkFormat::YAML)
.add_pass(PassName::LoopUnswitch);
assert!(config.fast_math);
assert!(config.lto);
assert!(config.thin_lto);
assert!(config.emit_remarks);
assert_eq!(config.custom_passes.len(), 1);
}
#[test]
fn test_pipeline_pass_new() {
let pass = PipelinePass::new(PassName::PromoteMemoryToRegister);
assert!(pass.enabled);
assert_eq!(pass.name.name(), "mem2reg");
}
#[test]
fn test_pipeline_pass_disabled() {
let pass = PipelinePass::disabled(PassName::LoopUnroll);
assert!(!pass.enabled);
}
#[test]
fn test_pipeline_pass_with_param() {
let pass = PipelinePass::new(PassName::LoopUnroll).with_param("threshold", "150");
assert_eq!(pass.params.get("threshold").unwrap(), "150");
}
#[test]
fn test_pass_builder_new() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O2);
let builder = PassBuilder::new(config);
assert!(builder.has_pass(PassName::PromoteMemoryToRegister));
}
#[test]
fn test_pass_builder_build_pipeline() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O2);
let builder = PassBuilder::new(config);
let pipeline = builder.build_pipeline();
assert!(!pipeline.passes.is_empty());
}
#[test]
fn test_pass_builder_without_vectorize() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O2)
.with_vectorize(false);
let builder = PassBuilder::new(config);
assert!(!builder.has_pass(PassName::LoopVectorize));
}
#[test]
fn test_pass_builder_get_inline_threshold() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O3);
let builder = PassBuilder::new(config);
assert_eq!(builder.get_inline_threshold(), 275);
}
#[test]
fn test_pass_builder_custom_threshold() {
let mut config = OptimizationPipelineConfig::for_level(ClangOptLevel::O2);
config.inline_threshold = 500;
let builder = PassBuilder::new(config);
assert_eq!(builder.get_inline_threshold(), 500);
}
#[test]
fn test_optimization_flags_default() {
let flags = OptimizationFlags::default();
assert!(!flags.fast_math);
assert!(flags.vectorize);
}
#[test]
fn test_optimization_flags_from_args() {
let args = vec![
"-ffast-math".to_string(),
"-fno-vectorize".to_string(),
"-flto".to_string(),
"-fno-omit-frame-pointer".to_string(),
];
let flags = OptimizationFlags::from_args(&args);
assert!(flags.fast_math);
assert!(flags.no_vectorize);
assert!(flags.lto);
assert!(flags.no_omit_frame_pointer);
}
#[test]
fn test_optimization_flags_sanitizers() {
let args = vec![
"-fsanitize=address".to_string(),
"-fsanitize=undefined".to_string(),
];
let flags = OptimizationFlags::from_args(&args);
assert!(flags.sanitize_address);
assert!(flags.sanitize_undefined);
assert!(!flags.sanitize_memory);
}
#[test]
fn test_optimization_flags_apply_to_config() {
let flags = OptimizationFlags {
fast_math: true,
no_vectorize: true,
..Default::default()
};
let mut config = OptimizationPipelineConfig::for_level(ClangOptLevel::O2);
flags.apply_to_config(&mut config);
assert!(config.fast_math);
assert!(!config.vectorize);
}
#[test]
fn test_remark_new() {
let remark = OptimizationRemark::new(
RemarkKind::Missed,
"inline",
"foo",
"foo not inlined into bar",
);
assert_eq!(remark.pass_name, "inline");
assert_eq!(remark.function, "foo");
}
#[test]
fn test_remark_to_yaml() {
let remark = OptimizationRemark::new(
RemarkKind::Passed,
"gvn",
"main",
"Redundant load eliminated",
)
.with_location("test.c", 10, 5)
.with_arg("load", "%ptr");
let yaml = remark.to_yaml();
assert!(yaml.contains("Passed"));
assert!(yaml.contains("gvn"));
assert!(yaml.contains("test.c"));
}
#[test]
fn test_remark_summary() {
let remark = OptimizationRemark::new(
RemarkKind::Missed,
"inline",
"foo",
"not inlined",
)
.with_location("a.c", 5, 1);
let summary = remark.summary();
assert!(summary.contains("a.c:5:1"));
assert!(summary.contains("missed"));
}
#[test]
fn test_remark_kind_from_flag() {
assert_eq!(RemarkKind::from_flag("missed"), Some(RemarkKind::Missed));
assert_eq!(RemarkKind::from_flag("passed"), Some(RemarkKind::Passed));
assert_eq!(RemarkKind::from_flag("unknown"), None);
}
#[test]
fn test_remark_emitter_disabled() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.missed("inline", "f", "msg");
assert_eq!(emitter.remark_count(), 0);
}
#[test]
fn test_remark_emitter_enabled() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.missed("inline", "f", "msg");
emitter.passed("gvn", "f", "msg2");
assert_eq!(emitter.remark_count(), 2);
}
#[test]
fn test_remark_emitter_filter() {
let mut emitter = OptimizationRemarkEmitter::new()
.filter_pass("gvn");
emitter.enable();
emitter.missed("inline", "f", "msg");
emitter.passed("gvn", "f", "msg2");
assert_eq!(emitter.remark_count(), 1);
}
#[test]
fn test_remark_emitter_format_all() {
let mut emitter = OptimizationRemarkEmitter::new().with_format(RemarkFormat::YAML);
emitter.enable();
emitter.passed("mem2reg", "main", "Promoted alloca");
let output = emitter.format_all();
assert!(output.contains("Passed"));
assert!(output.contains("mem2reg"));
}
#[test]
fn test_remark_emitter_clear() {
let mut emitter = OptimizationRemarkEmitter::new();
emitter.enable();
emitter.missed("gvn", "f", "msg");
emitter.clear();
assert_eq!(emitter.remark_count(), 0);
}
#[test]
fn test_optimization_summary_from_builder() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O3);
let builder = PassBuilder::new(config);
let summary = OptimizationSummary::from_builder(&builder);
assert_eq!(summary.opt_level, ClangOptLevel::O3);
assert!(summary.pass_count > 0);
assert!(summary.vectorization);
}
#[test]
fn test_optimization_summary_format() {
let config = OptimizationPipelineConfig::for_level(ClangOptLevel::O2);
let builder = PassBuilder::new(config);
let summary = OptimizationSummary::from_builder(&builder);
let formatted = summary.format();
assert!(formatted.contains("O2"));
assert!(formatted.contains("Vectorization"));
}
#[test]
fn test_pass_name_name() {
assert_eq!(PassName::PromoteMemoryToRegister.name(), "mem2reg");
assert_eq!(PassName::LoopVectorize.name(), "loop-vectorize");
assert_eq!(PassName::AggressiveInstCombine.name(), "aggressive-instcombine");
}
#[test]
fn test_all_pass_names() {
let passes = [
PassName::PromoteMemoryToRegister,
PassName::InstructionCombining,
PassName::SimplifyCFG,
PassName::DeadCodeElimination,
PassName::GlobalValueNumbering,
PassName::EarlyCSE,
PassName::LoopInvariantCodeMotion,
PassName::LoopRotate,
PassName::LoopUnroll,
PassName::SLPVectorize,
PassName::LoopVectorize,
PassName::AggressiveInstCombine,
PassName::ArgumentPromotion,
PassName::GlobalOptimizer,
PassName::GlobalDCE,
PassName::Reassociate,
PassName::SCCP,
PassName::JumpThreading,
PassName::TailCallElimination,
PassName::FunctionAttrs,
PassName::MergeFunctions,
];
for pass in &passes {
assert!(!pass.name().is_empty());
}
}
#[test]
fn test_pipeline_pass_count() {
let pipeline = build_optimization_pipeline(ClangOptLevel::O2);
assert!(pipeline.passes.len() >= 15); }
#[test]
fn test_o0_has_disabled_passes() {
let pipeline = build_optimization_pipeline(ClangOptLevel::O0);
let disabled: Vec<_> = pipeline.passes.iter().filter(|p| !p.enabled).collect();
assert!(!disabled.is_empty());
}
}