#![doc(html_root_url = "https://docs.rs/rucc-session/0.10.23")]
mod fs;
pub mod runtime;
pub use crate::fs::{Dir, FileSystem, Found, IncludeForm, MemoryFileSystem, SearchPath, path_key};
use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use rucc_base::Interner;
use rucc_diag::{Diagnostic, Severity, SourceMap};
use rucc_target::{TargetInfo, Triple};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum OptLevel {
#[default]
O0,
O1,
O2,
O3,
Os,
Oz,
}
impl OptLevel {
pub const fn as_flag(self) -> &'static str {
match self {
OptLevel::O0 => "-O0",
OptLevel::O1 => "-O1",
OptLevel::O2 => "-O2",
OptLevel::O3 => "-O3",
OptLevel::Os => "-Os",
OptLevel::Oz => "-Oz",
}
}
pub const fn is_size(self) -> bool {
matches!(self, OptLevel::Os | OptLevel::Oz)
}
pub const fn runs_optimizer(self) -> bool {
!matches!(self, OptLevel::O0)
}
}
impl fmt::Display for OptLevel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_flag())
}
}
impl FromStr for OptLevel {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"0" => OptLevel::O0,
"" | "1" => OptLevel::O1,
"2" => OptLevel::O2,
"3" | "4" | "5" | "6" | "7" | "8" | "9" => OptLevel::O3,
"s" => OptLevel::Os,
"z" => OptLevel::Oz,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Safety {
#[default]
Off,
Detect,
Enforce,
Kernel,
}
impl Safety {
pub const fn as_str(self) -> &'static str {
match self {
Safety::Off => "off",
Safety::Detect => "detect",
Safety::Enforce => "enforce",
Safety::Kernel => "kernel",
}
}
pub const fn instruments(self) -> bool {
!matches!(self, Safety::Off)
}
}
impl fmt::Display for Safety {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Safety {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"off" => Safety::Off,
"detect" => Safety::Detect,
"enforce" => Safety::Enforce,
"kernel" => Safety::Kernel,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Padding {
#[default]
Ignored,
Tracked,
}
impl Padding {
pub const fn as_str(self) -> &'static str {
match self {
Padding::Ignored => "nopadding",
Padding::Tracked => "padding",
}
}
}
impl fmt::Display for Padding {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Padding {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"nopadding" => Padding::Ignored,
"padding" => Padding::Tracked,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Subobject {
#[default]
Off,
Members,
}
impl Subobject {
pub const fn as_str(self) -> &'static str {
match self {
Subobject::Off => "off",
Subobject::Members => "members",
}
}
pub const fn asks(self) -> bool {
matches!(self, Subobject::Members)
}
}
impl fmt::Display for Subobject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Races {
#[default]
Off,
Metadata,
Pointer,
}
impl Races {
pub const fn as_str(self) -> &'static str {
match self {
Races::Off => "off",
Races::Metadata => "metadata",
Races::Pointer => "pointer",
}
}
pub const fn records(self) -> bool {
!matches!(self, Races::Off)
}
pub const fn reads(self) -> bool {
matches!(self, Races::Pointer)
}
}
impl fmt::Display for Races {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Races {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"off" => Races::Off,
"metadata" => Races::Metadata,
"pointer" => Races::Pointer,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Promise {
#[default]
Off,
Blocks,
}
impl Promise {
pub const fn as_str(self) -> &'static str {
match self {
Promise::Off => "off",
Promise::Blocks => "blocks",
}
}
pub const fn checks(self) -> bool {
matches!(self, Promise::Blocks)
}
}
impl fmt::Display for Promise {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Visibility {
#[default]
Default,
Hidden,
Protected,
}
impl Visibility {
pub const fn as_str(self) -> &'static str {
match self {
Visibility::Default => "default",
Visibility::Hidden => "hidden",
Visibility::Protected => "protected",
}
}
}
impl fmt::Display for Visibility {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Visibility {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"default" => Visibility::Default,
"hidden" | "internal" => Visibility::Hidden,
"protected" => Visibility::Protected,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Compress {
#[default]
None,
Zlib,
ZlibGnu,
Zstd,
}
impl Compress {
pub const fn as_str(self) -> &'static str {
match self {
Compress::None => "none",
Compress::Zlib => "zlib",
Compress::ZlibGnu => "zlib-gnu",
Compress::Zstd => "zstd",
}
}
}
impl fmt::Display for Compress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Compress {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"none" => Compress::None,
"zlib" => Compress::Zlib,
"zlib-gnu" => Compress::ZlibGnu,
"zstd" => Compress::Zstd,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum LtoJobs {
#[default]
One,
Auto,
Jobserver,
Count(u32),
}
impl FromStr for LtoJobs {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"auto" => LtoJobs::Auto,
"jobserver" => LtoJobs::Jobserver,
_ => match s.parse::<u32>() {
Ok(1) => LtoJobs::One,
Ok(n) if n > 1 => LtoJobs::Count(n),
_ => return Err(()),
},
})
}
}
impl fmt::Display for LtoJobs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LtoJobs::One => f.write_str("1"),
LtoJobs::Auto => f.write_str("auto"),
LtoJobs::Jobserver => f.write_str("jobserver"),
LtoJobs::Count(n) => write!(f, "{n}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Partition {
#[default]
Balanced,
OneToOne,
One,
Max,
None,
}
impl Partition {
pub const fn as_str(self) -> &'static str {
match self {
Partition::Balanced => "balanced",
Partition::OneToOne => "1to1",
Partition::One => "one",
Partition::Max => "max",
Partition::None => "none",
}
}
}
impl fmt::Display for Partition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Partition {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"balanced" => Partition::Balanced,
"1to1" => Partition::OneToOne,
"one" => Partition::One,
"max" => Partition::Max,
"none" => Partition::None,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Lto {
pub requested: bool,
pub jobs: LtoJobs,
pub partition: Partition,
pub compression: Option<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Profile {
pub requested: bool,
pub path: Option<String>,
pub dir: Option<String>,
pub absolute: bool,
pub correction: bool,
pub partial_training: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Protector {
#[default]
None,
Buffers,
Strong,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Wrapping {
pub signed: bool,
pub pointer: bool,
pub trap: bool,
}
impl Wrapping {
pub const ALL: Self = Self { signed: true, pointer: true, trap: false };
pub const NONE: Self = Self { signed: false, pointer: false, trap: false };
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PrefixMap {
entries: Vec<(String, String)>,
}
impl PrefixMap {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn push(&mut self, old: impl Into<String>, new: impl Into<String>) {
self.entries.push((old.into(), new.into()));
}
#[must_use]
pub fn split(arg: &str) -> Option<(&str, &str)> {
arg.rsplit_once('=')
}
#[must_use]
pub fn apply<'a>(&self, path: &'a str) -> Cow<'a, str> {
for (old, new) in self.entries.iter().rev() {
if let Some(rest) = path.strip_prefix(old.as_str()) {
return Cow::Owned(format!("{new}{rest}"));
}
}
Cow::Borrowed(path)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PrefixMaps {
pub macros: PrefixMap,
pub debug: PrefixMap,
pub profile: PrefixMap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Contract {
#[default]
Off,
On,
Fast,
}
impl Contract {
pub const fn as_str(self) -> &'static str {
match self {
Contract::Off => "off",
Contract::On => "on",
Contract::Fast => "fast",
}
}
}
impl fmt::Display for Contract {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Contract {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"off" => Contract::Off,
"on" => Contract::On,
"fast" => Contract::Fast,
_ => return Err(()),
})
}
}
impl Protector {
pub const fn as_str(self) -> &'static str {
match self {
Protector::None => "-fno-stack-protector",
Protector::Buffers => "-fstack-protector",
Protector::Strong => "-fstack-protector-strong",
Protector::All => "-fstack-protector-all",
}
}
}
impl fmt::Display for Protector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Control {
#[default]
None,
Branch,
Return,
Full,
Check,
}
impl Control {
#[must_use]
pub const fn branch(self) -> bool {
matches!(self, Control::Branch | Control::Full)
}
#[must_use]
pub const fn ret(self) -> bool {
matches!(self, Control::Return | Control::Full)
}
#[must_use]
pub const fn any(self) -> bool {
self.branch() || self.ret()
}
pub const fn as_str(self) -> &'static str {
match self {
Control::None => "none",
Control::Branch => "branch",
Control::Return => "return",
Control::Full => "full",
Control::Check => "check",
}
}
}
impl fmt::Display for Control {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl FromStr for Control {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"none" => Control::None,
"branch" => Control::Branch,
"return" => Control::Return,
"full" => Control::Full,
"check" => Control::Check,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Hook {
#[default]
Platform,
Early,
Late,
}
impl Hook {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Hook::Platform => "platform",
Hook::Early => "fentry",
Hook::Late => "mcount",
}
}
#[must_use]
pub const fn early(self, fentry: bool) -> bool {
match self {
Hook::Platform => fentry,
Hook::Early => true,
Hook::Late => false,
}
}
}
impl fmt::Display for Hook {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Patchable {
pub total: u32,
pub before: u32,
}
impl Patchable {
#[must_use]
pub const fn any(self) -> bool {
self.total > 0
}
#[must_use]
pub const fn after(self) -> u32 {
self.total - self.before
}
}
impl FromStr for Patchable {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
let (total, before) = match s.split_once(',') {
Some((total, before)) => (total, before),
None => (s, "0"),
};
let total: u32 = total.parse().map_err(|_| ())?;
let before: u32 = before.parse().map_err(|_| ())?;
if before > total {
return Err(());
}
Ok(Patchable { total, before })
}
}
impl fmt::Display for Patchable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.before {
0 => write!(f, "{}", self.total),
before => write!(f, "{},{before}", self.total),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Pic {
#[default]
Executable,
Library,
}
impl Pic {
pub const fn as_str(self) -> &'static str {
match self {
Pic::Executable => "-fPIE",
Pic::Library => "-fPIC",
}
}
}
impl fmt::Display for Pic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum EmitKind {
#[default]
Executable,
Object,
Asm,
Preprocessed,
Tast,
Ir,
MirFinal,
SafetySummary,
TypeGranules,
}
impl EmitKind {
pub const fn as_str(self) -> &'static str {
match self {
EmitKind::Executable => "exe",
EmitKind::Object => "obj",
EmitKind::Asm => "asm",
EmitKind::Preprocessed => "preprocessed",
EmitKind::Tast => "tast",
EmitKind::Ir => "ir",
EmitKind::MirFinal => "mir-final",
EmitKind::SafetySummary => "safety-summary",
EmitKind::TypeGranules => "type-granules",
}
}
}
impl FromStr for EmitKind {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"exe" => EmitKind::Executable,
"obj" => EmitKind::Object,
"asm" => EmitKind::Asm,
"preprocessed" => EmitKind::Preprocessed,
"tast" => EmitKind::Tast,
"ir" => EmitKind::Ir,
"mir-final" => EmitKind::MirFinal,
"safety-summary" => EmitKind::SafetySummary,
"type-granules" => EmitKind::TypeGranules,
_ => return Err(()),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum Std {
C89,
C99,
C11,
C17,
#[default]
C23,
}
impl Std {
pub const fn stdc_version(self) -> Option<&'static str> {
match self {
Std::C89 => None,
Std::C99 => Some("199901L"),
Std::C11 => Some("201112L"),
Std::C17 => Some("201710L"),
Std::C23 => Some("202311L"),
}
}
pub const fn as_str(self) -> &'static str {
match self {
Std::C89 => "c89",
Std::C99 => "c99",
Std::C11 => "c11",
Std::C17 => "c17",
Std::C23 => "c23",
}
}
pub const fn has_c11(self) -> bool {
matches!(self, Std::C11 | Std::C17 | Std::C23)
}
#[must_use]
pub fn from_flag(name: &str) -> Option<(Std, bool)> {
let gnu = name.starts_with("gnu");
let std = match name {
"c89" | "c90" | "gnu89" | "gnu90" | "iso9899:1990" | "iso9899:199409" => Std::C89,
"c99" | "c9x" | "gnu99" | "gnu9x" | "iso9899:1999" | "iso9899:199x" => Std::C99,
"c11" | "c1x" | "gnu11" | "gnu1x" | "iso9899:2011" => Std::C11,
"c17" | "c18" | "gnu17" | "gnu18" | "iso9899:2017" | "iso9899:2018" => Std::C17,
"c23" | "c2x" | "gnu23" | "gnu2x" => Std::C23,
_ => return None,
};
Some((std, gnu))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GnucVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
impl Default for GnucVersion {
fn default() -> GnucVersion {
GnucVersion { major: 7, minor: 0, patch: 0 }
}
}
impl FromStr for GnucVersion {
type Err = String;
fn from_str(text: &str) -> Result<GnucVersion, String> {
let mut parts = text.split('.');
let mut next = |what: &str| -> Result<u32, String> {
match parts.next() {
None => Ok(0),
Some(field) => {
field.parse().map_err(|_| format!("`{text}` has a {what} that is not a number"))
}
}
};
let major = next("major")?;
let minor = next("minor")?;
let patch = next("patchlevel")?;
if parts.next().is_some() {
return Err(format!("`{text}` has more than three components"));
}
Ok(GnucVersion { major, minor, patch })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Dumps {
pub macros: bool,
}
impl Dumps {
const LETTERS: &'static str = "MDNIU";
#[must_use]
pub fn is_family(arg: &str) -> bool {
match arg.strip_prefix("-d") {
Some("") | None => false,
Some(letters) => letters.chars().all(|c| Dumps::LETTERS.contains(c)),
}
}
pub fn add(&mut self, letters: &str) {
for letter in letters.chars() {
if letter == 'M' {
self.macros = true;
}
}
}
#[must_use]
pub const fn any(self) -> bool {
self.macros
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Preinclude {
pub name: String,
pub macros_only: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Deps {
pub emit: bool,
pub instead_of_compiling: bool,
pub system_headers: bool,
pub file: Option<String>,
pub targets: Vec<String>,
pub phony: bool,
}
impl Default for Deps {
fn default() -> Deps {
Deps {
emit: false,
instead_of_compiling: false,
system_headers: true,
file: None,
targets: Vec::new(),
phony: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SaveTemps {
#[default]
No,
Object,
Cwd,
}
impl SaveTemps {
#[must_use]
pub const fn wanted(self) -> bool {
!matches!(self, SaveTemps::No)
}
}
impl FromStr for SaveTemps {
type Err = String;
fn from_str(s: &str) -> Result<SaveTemps, String> {
match s {
"obj" => Ok(SaveTemps::Object),
"cwd" => Ok(SaveTemps::Cwd),
_ => Err(format!("`{s}` is not a -save-temps option; accepted: cwd, obj")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct Options {
pub target: Triple,
pub opt_level: OptLevel,
pub safety: Safety,
pub padding: Padding,
pub subobject: Subobject,
pub promise: Promise,
pub races: Races,
pub emit: EmitKind,
pub debug_info: bool,
pub compress: Compress,
pub lto: Lto,
pub profile_data: Profile,
pub frame_pointer: bool,
pub red_zone: bool,
pub protector: Protector,
pub stack_clash: bool,
pub control: Control,
pub profile: bool,
pub hook: Hook,
pub patchable: Patchable,
pub wrapping: Wrapping,
pub char_signed: Option<bool>,
pub short_enums: bool,
pub strict_aliasing: bool,
pub fp_contract: Contract,
pub prefix_map: PrefixMaps,
pub warnings_are_errors: bool,
pub warnings: bool,
pub error_limit: u32,
pub std: Std,
pub gnu_extensions: bool,
pub pedantic: bool,
pub permissive: bool,
pub gnu89_inline: bool,
pub visibility: Visibility,
pub pic: Pic,
pub interposition: bool,
pub async_unwind_tables: bool,
pub unwind_tables: bool,
pub function_sections: bool,
pub data_sections: bool,
pub gnuc: GnucVersion,
pub hosted: bool,
pub builtins: bool,
pub no_builtin: Vec<String>,
pub glibc_minor: Option<u32>,
pub defines: Vec<String>,
pub undefines: Vec<String>,
pub search: SearchPath,
pub preincludes: Vec<Preinclude>,
pub line_markers: bool,
pub dumps: Dumps,
pub deps: Deps,
pub save_temps: SaveTemps,
pub time: bool,
pub passes: Vec<(String, bool)>,
pub pass_fuel: Vec<(String, u32)>,
pub pass_fuel_global: Option<u32>,
pub pass_gates: Vec<(bool, String)>,
pub dump_ir: Vec<String>,
pub opt_info: Vec<String>,
pub opt_info_file: Option<String>,
pub verify_each: bool,
pub rule_coverage: Option<String>,
pub register_pressure: Option<String>,
}
impl Options {
pub fn new(target: Triple) -> Self {
Self {
target,
opt_level: OptLevel::default(),
safety: Safety::default(),
padding: Padding::default(),
subobject: Subobject::default(),
promise: Promise::default(),
races: Races::default(),
emit: EmitKind::default(),
debug_info: false,
compress: Compress::None,
lto: Lto::default(),
profile_data: Profile::default(),
frame_pointer: false,
red_zone: true,
protector: Protector::default(),
stack_clash: false,
control: Control::default(),
profile: false,
hook: Hook::default(),
patchable: Patchable::default(),
wrapping: Wrapping::NONE,
char_signed: None,
short_enums: false,
strict_aliasing: true,
fp_contract: Contract::Off,
prefix_map: PrefixMaps::default(),
warnings_are_errors: false,
warnings: true,
error_limit: 20,
std: Std::default(),
gnu_extensions: true,
pedantic: false,
permissive: false,
gnu89_inline: false,
visibility: Visibility::default(),
pic: Pic::default(),
interposition: true,
async_unwind_tables: true,
unwind_tables: false,
function_sections: false,
data_sections: false,
gnuc: GnucVersion::default(),
hosted: true,
builtins: true,
no_builtin: Vec::new(),
glibc_minor: None,
defines: Vec::new(),
undefines: Vec::new(),
search: SearchPath::new(),
preincludes: Vec::new(),
line_markers: true,
dumps: Dumps::default(),
deps: Deps::default(),
save_temps: SaveTemps::default(),
time: false,
passes: Vec::new(),
pass_fuel: Vec::new(),
pass_fuel_global: None,
pass_gates: Vec::new(),
dump_ir: Vec::new(),
opt_info: Vec::new(),
opt_info_file: None,
verify_each: cfg!(debug_assertions),
rule_coverage: None,
register_pressure: None,
}
}
#[must_use]
pub const fn unwinds(&self) -> bool {
self.async_unwind_tables || self.unwind_tables
}
}
#[derive(Debug)]
pub struct Session {
pub opts: Options,
pub target: TargetInfo,
pub interner: Interner,
pub sources: SourceMap,
diagnostics: Vec<Diagnostic>,
error_count: u32,
warning_count: u32,
}
impl Session {
pub fn new(opts: Options) -> Self {
let mut target = TargetInfo::new(opts.target);
if let Some(signed) = opts.char_signed {
target.char_is_signed = signed;
}
Self {
opts,
target,
interner: Interner::with_capacity(1024),
sources: SourceMap::new(),
diagnostics: Vec::new(),
error_count: 0,
warning_count: 0,
}
}
pub fn emit(&mut self, mut diag: Diagnostic) {
if !self.opts.warnings && diag.severity == Severity::Warning {
return;
}
if self.opts.warnings_are_errors && diag.severity == Severity::Warning {
diag.severity = Severity::Error;
}
match diag.severity {
Severity::Error | Severity::Ice => self.error_count += 1,
Severity::Warning => self.warning_count += 1,
Severity::Note | Severity::Help => {}
}
self.diagnostics.push(diag);
}
pub fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub fn has_errors(&self) -> bool {
self.error_count > 0
}
pub fn error_count(&self) -> u32 {
self.error_count
}
pub fn warning_count(&self) -> u32 {
self.warning_count
}
pub fn error_limit_reached(&self) -> bool {
self.opts.error_limit != 0 && self.error_count >= self.opts.error_limit
}
}
#[cfg(test)]
mod tests {
use super::*;
fn session() -> Session {
Session::new(Options::new("x86_64-unknown-linux-gnu".parse().unwrap()))
}
#[test]
fn a_version_claim_reads_the_way_gcc_prints_one() {
let all = |v: &str| v.parse::<GnucVersion>().unwrap();
assert_eq!(all("15.1.0"), GnucVersion { major: 15, minor: 1, patch: 0 });
assert_eq!(all("15"), GnucVersion { major: 15, minor: 0, patch: 0 });
assert_eq!(all("4.2"), GnucVersion { major: 4, minor: 2, patch: 0 });
assert!("".parse::<GnucVersion>().is_err());
assert!("15.".parse::<GnucVersion>().is_err(), "a trailing dot is a typo, not a zero");
assert!("1.2.3.4".parse::<GnucVersion>().is_err());
}
#[test]
fn a_prefix_map_rewrites_the_front_of_a_path_and_nothing_else() {
let map = |pairs: &[(&str, &str)]| {
let mut map = PrefixMap::new();
for &(old, new) in pairs {
map.push(old, new);
}
map
};
assert!(PrefixMap::new().is_empty());
assert_eq!(PrefixMap::new().apply("sub/h.h"), "sub/h.h");
let one = map(&[("sub", "SUB")]);
assert_eq!(one.apply("sub/h.h"), "SUB/h.h");
assert_eq!(one.apply("a.c"), "a.c", "a path the mapping does not start");
assert_eq!(one.apply("x/sub/h.h"), "x/sub/h.h", "the middle of a path is not the front");
assert_eq!(map(&[("s", "B")]).apply("sub/h.h"), "Bub/h.h");
assert_eq!(map(&[("sub/", "SUB/")]).apply("sub/h.h"), "SUB/h.h");
assert_eq!(map(&[("sub", "")]).apply("sub/h.h"), "/h.h", "mapping to nothing");
assert_eq!(map(&[("", "PRE")]).apply("a.c"), "PREa.c", "an empty old is in front of all");
assert_eq!(map(&[("sub", "ONE"), ("sub", "TWO")]).apply("sub/h.h"), "TWO/h.h");
assert_eq!(map(&[("sub", "A"), ("s", "B")]).apply("sub/h.h"), "Bub/h.h");
assert_eq!(map(&[("s", "B"), ("sub", "A")]).apply("sub/h.h"), "A/h.h");
assert_eq!(map(&[("nope", "X"), ("sub", "A")]).apply("sub/h.h"), "A/h.h");
}
#[test]
fn the_argument_is_split_at_the_last_equals_sign() {
assert_eq!(PrefixMap::split("old=new"), Some(("old", "new")));
assert_eq!(PrefixMap::split("=new"), Some(("", "new")), "an empty old is allowed");
assert_eq!(PrefixMap::split("old="), Some(("old", "")), "and so is an empty new");
assert_eq!(PrefixMap::split("/home/a=b=/src"), Some(("/home/a=b", "/src")));
assert_eq!(PrefixMap::split("nope"), None);
}
#[test]
fn optimisation_levels_parse_the_way_gcc_spells_them() {
assert_eq!("".parse::<OptLevel>().unwrap(), OptLevel::O1);
assert_eq!("0".parse::<OptLevel>().unwrap(), OptLevel::O0);
assert_eq!("2".parse::<OptLevel>().unwrap(), OptLevel::O2);
assert_eq!("9".parse::<OptLevel>().unwrap(), OptLevel::O3);
assert_eq!("s".parse::<OptLevel>().unwrap(), OptLevel::Os);
assert!("q".parse::<OptLevel>().is_err());
}
#[test]
fn only_o0_skips_the_optimizer() {
assert!(!OptLevel::O0.runs_optimizer());
assert!(OptLevel::O1.runs_optimizer());
assert!(OptLevel::Oz.runs_optimizer());
}
#[test]
fn the_safety_tiers_round_trip_and_nothing_else_is_one() {
for tier in [Safety::Off, Safety::Detect, Safety::Enforce, Safety::Kernel] {
assert_eq!(tier.as_str().parse::<Safety>().unwrap(), tier);
}
assert!("on".parse::<Safety>().is_err());
assert!("".parse::<Safety>().is_err());
}
#[test]
fn room_for_a_patcher_is_written_the_way_it_was_asked_for() {
for (written, total, before) in
[("0", 0, 0), ("2", 2, 0), ("16", 16, 0), ("5,3", 5, 3), ("3,3", 3, 3)]
{
let room: Patchable = written.parse().unwrap();
assert_eq!(room, Patchable { total, before });
assert_eq!(room.to_string(), written);
assert_eq!(room.after(), total - before);
assert_eq!(room.any(), total > 0);
}
assert_eq!("2,0".parse::<Patchable>().unwrap().to_string(), "2");
}
#[test]
fn more_room_in_front_of_the_label_than_there_is_room_at_all_is_refused() {
assert!("1,2".parse::<Patchable>().is_err());
assert!("1,2,3".parse::<Patchable>().is_err());
assert!("a".parse::<Patchable>().is_err());
assert!("".parse::<Patchable>().is_err());
assert!("-1".parse::<Patchable>().is_err());
}
#[test]
fn the_two_places_the_intermediate_files_can_go_are_the_two_words_that_are_taken() {
assert_eq!("obj".parse::<SaveTemps>().unwrap(), SaveTemps::Object);
assert_eq!("cwd".parse::<SaveTemps>().unwrap(), SaveTemps::Cwd);
assert!("obj,cwd".parse::<SaveTemps>().is_err());
assert!("".parse::<SaveTemps>().is_err());
assert_eq!(SaveTemps::default(), SaveTemps::No);
assert!(!SaveTemps::No.wanted());
assert!(SaveTemps::Object.wanted());
assert!(SaveTemps::Cwd.wanted());
}
#[test]
fn a_build_that_did_not_ask_for_the_monitor_does_not_get_it() {
assert_eq!(Safety::default(), Safety::Off);
assert!(!Safety::Off.instruments());
assert!(Safety::Detect.instruments());
assert!(Safety::Enforce.instruments());
assert!(Safety::Kernel.instruments());
}
#[test]
fn emit_kinds_round_trip_through_their_names() {
for k in [
EmitKind::Executable,
EmitKind::Object,
EmitKind::Asm,
EmitKind::Preprocessed,
EmitKind::Tast,
EmitKind::Ir,
EmitKind::MirFinal,
] {
assert_eq!(k.as_str().parse::<EmitKind>().unwrap(), k);
}
}
#[test]
fn errors_are_counted_and_warnings_are_not() {
let mut s = session();
s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
assert_eq!(s.error_count(), 1);
assert_eq!(s.warning_count(), 1);
assert!(s.has_errors());
assert_eq!(s.diagnostics().len(), 2);
}
#[test]
fn werror_promotes_once_at_the_sink() {
let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
opts.warnings_are_errors = true;
let mut s = Session::new(opts);
s.emit(Diagnostic::warning("hmm", rucc_diag::Span::DUMMY));
assert_eq!(s.error_count(), 1);
assert_eq!(s.warning_count(), 0);
assert_eq!(s.diagnostics()[0].severity, Severity::Error);
}
#[test]
fn the_error_limit_can_be_switched_off() {
let mut opts = Options::new("x86_64-unknown-linux-gnu".parse().unwrap());
opts.error_limit = 0;
let mut s = Session::new(opts);
for _ in 0..100 {
s.emit(Diagnostic::error("no", rucc_diag::Span::DUMMY));
}
assert!(!s.error_limit_reached());
}
#[test]
fn the_session_carries_the_source_map_spans_are_resolved_against() {
let mut s = session();
let file = s.sources.add("a.c", b"int x;\n".to_vec()).unwrap();
let start = s.sources.file(file).start;
assert_eq!(s.sources.render_position(start + 4), "a.c:1:5");
}
#[test]
fn the_session_carries_the_resolved_target() {
let s = session();
assert_eq!(s.target.pointer_width, 64);
assert!(s.target.char_is_signed);
}
}