Our previous implementation of unwind infrastructure was somewhat complex and brittle: it parsed generated instructions in order to reverse-engineer unwind info from prologues. It also relied on some fragile linkage to communicate instruction-layout information that VCode was not designed to provide. A much simpler, more reliable, and easier-to-reason-about approach is to embed unwind directives as pseudo-instructions in the prologue as we generate it. That way, we can say what we mean and just emit it directly. The usual reasoning that leads to the reverse-engineering approach is that metadata is hard to keep in sync across optimization passes; but here, (i) prologues are generated at the very end of the pipeline, and (ii) if we ever do a post-prologue-gen optimization, we can treat unwind directives as black boxes with unknown side-effects, just as we do for some other pseudo-instructions today. It turns out that it was easier to just build this for both x64 and aarch64 (since they share a factored-out ABI implementation), and wire up the platform-specific unwind-info generation for Windows and SystemV. Now we have simpler unwind on all platforms and we can delete the old unwind infra as soon as we remove the old backend. There were a few consequences to supporting Fastcall unwind in particular that led to a refactor of the common ABI. Windows only supports naming clobbered-register save locations within 240 bytes of the frame-pointer register, whatever one chooses that to be (RSP or RBP). We had previously saved clobbers below the fixed frame (and below nominal-SP). The 240-byte range has to include the old RBP too, so we're forced to place clobbers at the top of the frame, just below saved RBP/RIP. This is fine; we always keep a frame pointer anyway because we use it to refer to stack args. It does mean that offsets of fixed-frame slots (spillslots, stackslots) from RBP are no longer known before we do regalloc, so if we ever want to index these off of RBP rather than nominal-SP because we add support for `alloca` (dynamic frame growth), then we'll need a "nominal-BP" mode that is resolved after regalloc and clobber-save code is generated. I added a comment to this effect in `abi_impl.rs`. The above refactor touched both x64 and aarch64 because of shared code. This had a further effect in that the old aarch64 prologue generation subtracted from `sp` once to allocate space, then used stores to `[sp, offset]` to save clobbers. Unfortunately the offset only has 7-bit range, so if there are enough clobbered registers (and there can be -- aarch64 has 384 bytes of registers; at least one unit test hits this) the stores/loads will be out-of-range. I really don't want to synthesize large-offset sequences here; better to go back to the simpler pre-index/post-index `stp r1, r2, [sp, #-16]` form that works just like a "push". It's likely not much worse microarchitecturally (dependence chain on SP, but oh well) and it actually saves an instruction if there's no other frame to allocate. As a further advantage, it's much simpler to understand; simpler is usually better. This PR adds the new backend on Windows to CI as well.
452 lines
14 KiB
Rust
452 lines
14 KiB
Rust
//! Shared settings module.
|
|
//!
|
|
//! This module defines data structures to access the settings defined in the meta language.
|
|
//!
|
|
//! Each settings group is translated to a `Flags` struct either in this module or in its
|
|
//! ISA-specific `settings` module. The struct provides individual getter methods for all of the
|
|
//! settings as well as computed predicate flags.
|
|
//!
|
|
//! The `Flags` struct is immutable once it has been created. A `Builder` instance is used to
|
|
//! create it.
|
|
//!
|
|
//! # Example
|
|
//! ```
|
|
//! use cranelift_codegen::settings::{self, Configurable};
|
|
//!
|
|
//! let mut b = settings::builder();
|
|
//! b.set("opt_level", "speed_and_size");
|
|
//!
|
|
//! let f = settings::Flags::new(b);
|
|
//! assert_eq!(f.opt_level(), settings::OptLevel::SpeedAndSize);
|
|
//! ```
|
|
|
|
use crate::constant_hash::{probe, simple_hash};
|
|
use crate::isa::TargetIsa;
|
|
use alloc::boxed::Box;
|
|
use alloc::string::{String, ToString};
|
|
use core::fmt;
|
|
use core::str;
|
|
use thiserror::Error;
|
|
|
|
/// A string-based configurator for settings groups.
|
|
///
|
|
/// The `Configurable` protocol allows settings to be modified by name before a finished `Flags`
|
|
/// struct is created.
|
|
pub trait Configurable {
|
|
/// Set the string value of any setting by name.
|
|
///
|
|
/// This can set any type of setting whether it is numeric, boolean, or enumerated.
|
|
fn set(&mut self, name: &str, value: &str) -> SetResult<()>;
|
|
|
|
/// Enable a boolean setting or apply a preset.
|
|
///
|
|
/// If the identified setting isn't a boolean or a preset, a `BadType` error is returned.
|
|
fn enable(&mut self, name: &str) -> SetResult<()>;
|
|
}
|
|
|
|
/// Collect settings values based on a template.
|
|
#[derive(Clone, Hash)]
|
|
pub struct Builder {
|
|
template: &'static detail::Template,
|
|
bytes: Box<[u8]>,
|
|
}
|
|
|
|
impl Builder {
|
|
/// Create a new builder with defaults and names from the given template.
|
|
pub fn new(tmpl: &'static detail::Template) -> Self {
|
|
Self {
|
|
template: tmpl,
|
|
bytes: tmpl.defaults.into(),
|
|
}
|
|
}
|
|
|
|
/// Extract contents of builder once everything is configured.
|
|
pub fn state_for(self, name: &str) -> Box<[u8]> {
|
|
assert_eq!(name, self.template.name);
|
|
self.bytes
|
|
}
|
|
|
|
/// Set the value of a single bit.
|
|
fn set_bit(&mut self, offset: usize, bit: u8, value: bool) {
|
|
let byte = &mut self.bytes[offset];
|
|
let mask = 1 << bit;
|
|
if value {
|
|
*byte |= mask;
|
|
} else {
|
|
*byte &= !mask;
|
|
}
|
|
}
|
|
|
|
/// Apply a preset. The argument is a slice of (mask, value) bytes.
|
|
fn apply_preset(&mut self, values: &[(u8, u8)]) {
|
|
for (byte, &(mask, value)) in self.bytes.iter_mut().zip(values) {
|
|
*byte = (*byte & !mask) | value;
|
|
}
|
|
}
|
|
|
|
/// Look up a descriptor by name.
|
|
fn lookup(&self, name: &str) -> SetResult<(usize, detail::Detail)> {
|
|
match probe(self.template, name, simple_hash(name)) {
|
|
Err(_) => Err(SetError::BadName(name.to_string())),
|
|
Ok(entry) => {
|
|
let d = &self.template.descriptors[self.template.hash_table[entry] as usize];
|
|
Ok((d.offset as usize, d.detail))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_bool_value(value: &str) -> SetResult<bool> {
|
|
match value {
|
|
"true" | "on" | "yes" | "1" => Ok(true),
|
|
"false" | "off" | "no" | "0" => Ok(false),
|
|
_ => Err(SetError::BadValue("bool".to_string())),
|
|
}
|
|
}
|
|
|
|
fn parse_enum_value(value: &str, choices: &[&str]) -> SetResult<u8> {
|
|
match choices.iter().position(|&tag| tag == value) {
|
|
Some(idx) => Ok(idx as u8),
|
|
None => {
|
|
// TODO: Use `join` instead of this code, once
|
|
// https://github.com/rust-lang/rust/issues/27747 is resolved.
|
|
let mut all_choices = String::new();
|
|
let mut first = true;
|
|
for choice in choices {
|
|
if first {
|
|
first = false
|
|
} else {
|
|
all_choices += ", ";
|
|
}
|
|
all_choices += choice;
|
|
}
|
|
Err(SetError::BadValue(format!("any among {}", all_choices)))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Configurable for Builder {
|
|
fn enable(&mut self, name: &str) -> SetResult<()> {
|
|
use self::detail::Detail;
|
|
let (offset, detail) = self.lookup(name)?;
|
|
match detail {
|
|
Detail::Bool { bit } => {
|
|
self.set_bit(offset, bit, true);
|
|
Ok(())
|
|
}
|
|
Detail::Preset => {
|
|
self.apply_preset(&self.template.presets[offset..]);
|
|
Ok(())
|
|
}
|
|
_ => Err(SetError::BadType),
|
|
}
|
|
}
|
|
|
|
fn set(&mut self, name: &str, value: &str) -> SetResult<()> {
|
|
use self::detail::Detail;
|
|
let (offset, detail) = self.lookup(name)?;
|
|
match detail {
|
|
Detail::Bool { bit } => {
|
|
self.set_bit(offset, bit, parse_bool_value(value)?);
|
|
}
|
|
Detail::Num => {
|
|
self.bytes[offset] = value
|
|
.parse()
|
|
.map_err(|_| SetError::BadValue("number".to_string()))?;
|
|
}
|
|
Detail::Enum { last, enumerators } => {
|
|
self.bytes[offset] =
|
|
parse_enum_value(value, self.template.enums(last, enumerators))?;
|
|
}
|
|
Detail::Preset => return Err(SetError::BadName(name.to_string())),
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// An error produced when changing a setting.
|
|
#[derive(Error, Debug, PartialEq, Eq)]
|
|
pub enum SetError {
|
|
/// No setting by this name exists.
|
|
#[error("No existing setting named '{0}'")]
|
|
BadName(String),
|
|
|
|
/// Type mismatch for setting (e.g., setting an enum setting as a bool).
|
|
#[error("Trying to set a setting with the wrong type")]
|
|
BadType,
|
|
|
|
/// This is not a valid value for this setting.
|
|
#[error("Unexpected value for a setting, expected {0}")]
|
|
BadValue(String),
|
|
}
|
|
|
|
/// A result returned when changing a setting.
|
|
pub type SetResult<T> = Result<T, SetError>;
|
|
|
|
/// A reference to just the boolean predicates of a settings object.
|
|
///
|
|
/// The settings objects themselves are generated and appear in the `isa/*/settings.rs` modules.
|
|
/// Each settings object provides a `predicate_view()` method that makes it possible to query
|
|
/// ISA predicates by number.
|
|
#[derive(Clone, Copy, Hash)]
|
|
pub struct PredicateView<'a>(&'a [u8]);
|
|
|
|
impl<'a> PredicateView<'a> {
|
|
/// Create a new view of a precomputed predicate vector.
|
|
///
|
|
/// See the `predicate_view()` method on the various `Flags` types defined for each ISA.
|
|
pub fn new(bits: &'a [u8]) -> Self {
|
|
PredicateView(bits)
|
|
}
|
|
|
|
/// Check a numbered predicate.
|
|
pub fn test(self, p: usize) -> bool {
|
|
self.0[p / 8] & (1 << (p % 8)) != 0
|
|
}
|
|
}
|
|
|
|
/// Implementation details for generated code.
|
|
///
|
|
/// This module holds definitions that need to be public so the can be instantiated by generated
|
|
/// code in other modules.
|
|
pub mod detail {
|
|
use crate::constant_hash;
|
|
use core::fmt;
|
|
use core::hash::Hash;
|
|
|
|
/// An instruction group template.
|
|
#[derive(Hash)]
|
|
pub struct Template {
|
|
/// Name of the instruction group.
|
|
pub name: &'static str,
|
|
/// List of setting descriptors.
|
|
pub descriptors: &'static [Descriptor],
|
|
/// Union of all enumerators.
|
|
pub enumerators: &'static [&'static str],
|
|
/// Hash table of settings.
|
|
pub hash_table: &'static [u16],
|
|
/// Default values.
|
|
pub defaults: &'static [u8],
|
|
/// Pairs of (mask, value) for presets.
|
|
pub presets: &'static [(u8, u8)],
|
|
}
|
|
|
|
impl Template {
|
|
/// Get enumerators corresponding to a `Details::Enum`.
|
|
pub fn enums(&self, last: u8, enumerators: u16) -> &[&'static str] {
|
|
let from = enumerators as usize;
|
|
let len = usize::from(last) + 1;
|
|
&self.enumerators[from..from + len]
|
|
}
|
|
|
|
/// Format a setting value as a TOML string. This is mostly for use by the generated
|
|
/// `Display` implementation.
|
|
pub fn format_toml_value(
|
|
&self,
|
|
detail: Detail,
|
|
byte: u8,
|
|
f: &mut fmt::Formatter,
|
|
) -> fmt::Result {
|
|
match detail {
|
|
Detail::Bool { bit } => write!(f, "{}", (byte & (1 << bit)) != 0),
|
|
Detail::Num => write!(f, "{}", byte),
|
|
Detail::Enum { last, enumerators } => {
|
|
if byte <= last {
|
|
let tags = self.enums(last, enumerators);
|
|
write!(f, "\"{}\"", tags[usize::from(byte)])
|
|
} else {
|
|
write!(f, "{}", byte)
|
|
}
|
|
}
|
|
// Presets aren't printed. They are reflected in the other settings.
|
|
Detail::Preset { .. } => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The template contains a hash table for by-name lookup.
|
|
impl<'a> constant_hash::Table<&'a str> for Template {
|
|
fn len(&self) -> usize {
|
|
self.hash_table.len()
|
|
}
|
|
|
|
fn key(&self, idx: usize) -> Option<&'a str> {
|
|
let e = self.hash_table[idx] as usize;
|
|
if e < self.descriptors.len() {
|
|
Some(self.descriptors[e].name)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A setting descriptor holds the information needed to generically set and print a setting.
|
|
///
|
|
/// Each settings group will be represented as a constant DESCRIPTORS array.
|
|
#[derive(Hash)]
|
|
pub struct Descriptor {
|
|
/// Lower snake-case name of setting as defined in meta.
|
|
pub name: &'static str,
|
|
|
|
/// Offset of byte containing this setting.
|
|
pub offset: u32,
|
|
|
|
/// Additional details, depending on the kind of setting.
|
|
pub detail: Detail,
|
|
}
|
|
|
|
/// The different kind of settings along with descriptor bits that depend on the kind.
|
|
#[derive(Clone, Copy, Hash)]
|
|
pub enum Detail {
|
|
/// A boolean setting only uses one bit, numbered from LSB.
|
|
Bool {
|
|
/// 0-7.
|
|
bit: u8,
|
|
},
|
|
|
|
/// A numerical setting uses the whole byte.
|
|
Num,
|
|
|
|
/// An Enum setting uses a range of enumerators.
|
|
Enum {
|
|
/// Numerical value of last enumerator, allowing for 1-256 enumerators.
|
|
last: u8,
|
|
|
|
/// First enumerator in the ENUMERATORS table.
|
|
enumerators: u16,
|
|
},
|
|
|
|
/// A preset is not an individual setting, it is a collection of settings applied at once.
|
|
///
|
|
/// The `Descriptor::offset` field refers to the `PRESETS` table.
|
|
Preset,
|
|
}
|
|
|
|
impl Detail {
|
|
/// Check if a detail is a Detail::Preset. Useful because the Descriptor
|
|
/// offset field has a different meaning when the detail is a preset.
|
|
pub fn is_preset(self) -> bool {
|
|
match self {
|
|
Self::Preset => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Include code generated by `meta/gen_settings.rs`. This file contains a public `Flags` struct
|
|
// with an implementation for all of the settings defined in
|
|
// `cranelift-codegen/meta/src/shared/settings.rs`.
|
|
include!(concat!(env!("OUT_DIR"), "/settings.rs"));
|
|
|
|
/// Wrapper containing flags and optionally a `TargetIsa` trait object.
|
|
///
|
|
/// A few passes need to access the flags but only optionally a target ISA. The `FlagsOrIsa`
|
|
/// wrapper can be used to pass either, and extract the flags so they are always accessible.
|
|
#[derive(Clone, Copy)]
|
|
pub struct FlagsOrIsa<'a> {
|
|
/// Flags are always present.
|
|
pub flags: &'a Flags,
|
|
|
|
/// The ISA may not be present.
|
|
pub isa: Option<&'a dyn TargetIsa>,
|
|
}
|
|
|
|
impl<'a> From<&'a Flags> for FlagsOrIsa<'a> {
|
|
fn from(flags: &'a Flags) -> FlagsOrIsa {
|
|
FlagsOrIsa { flags, isa: None }
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a dyn TargetIsa> for FlagsOrIsa<'a> {
|
|
fn from(isa: &'a dyn TargetIsa) -> FlagsOrIsa {
|
|
FlagsOrIsa {
|
|
flags: isa.flags(),
|
|
isa: Some(isa),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Configurable;
|
|
use super::SetError::*;
|
|
use super::{builder, Flags};
|
|
use alloc::string::ToString;
|
|
|
|
#[test]
|
|
fn display_default() {
|
|
let b = builder();
|
|
let f = Flags::new(b);
|
|
assert_eq!(
|
|
f.to_string(),
|
|
r#"[shared]
|
|
regalloc = "backtracking"
|
|
opt_level = "none"
|
|
tls_model = "none"
|
|
libcall_call_conv = "isa_default"
|
|
baldrdash_prologue_words = 0
|
|
probestack_size_log2 = 12
|
|
enable_verifier = true
|
|
is_pic = false
|
|
use_colocated_libcalls = false
|
|
avoid_div_traps = false
|
|
enable_float = true
|
|
enable_nan_canonicalization = false
|
|
enable_pinned_reg = false
|
|
use_pinned_reg_as_heap_base = false
|
|
enable_simd = false
|
|
enable_atomics = true
|
|
enable_safepoints = false
|
|
enable_llvm_abi_extensions = false
|
|
unwind_info = true
|
|
emit_all_ones_funcaddrs = false
|
|
enable_probestack = true
|
|
probestack_func_adjusts_sp = false
|
|
enable_jump_tables = true
|
|
enable_heap_access_spectre_mitigation = true
|
|
"#
|
|
);
|
|
assert_eq!(f.opt_level(), super::OptLevel::None);
|
|
assert_eq!(f.enable_simd(), false);
|
|
assert_eq!(f.baldrdash_prologue_words(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn modify_bool() {
|
|
let mut b = builder();
|
|
assert_eq!(b.enable("not_there"), Err(BadName("not_there".to_string())));
|
|
assert_eq!(b.enable("enable_simd"), Ok(()));
|
|
assert_eq!(b.set("enable_simd", "false"), Ok(()));
|
|
|
|
let f = Flags::new(b);
|
|
assert_eq!(f.enable_simd(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn modify_string() {
|
|
let mut b = builder();
|
|
assert_eq!(
|
|
b.set("not_there", "true"),
|
|
Err(BadName("not_there".to_string()))
|
|
);
|
|
assert_eq!(b.set("enable_simd", ""), Err(BadValue("bool".to_string())));
|
|
assert_eq!(
|
|
b.set("enable_simd", "best"),
|
|
Err(BadValue("bool".to_string()))
|
|
);
|
|
assert_eq!(
|
|
b.set("opt_level", "true"),
|
|
Err(BadValue(
|
|
"any among none, speed, speed_and_size".to_string()
|
|
))
|
|
);
|
|
assert_eq!(b.set("opt_level", "speed"), Ok(()));
|
|
assert_eq!(b.set("enable_simd", "0"), Ok(()));
|
|
|
|
let f = Flags::new(b);
|
|
assert_eq!(f.enable_simd(), false);
|
|
assert_eq!(f.opt_level(), super::OptLevel::Speed);
|
|
}
|
|
}
|