Files
wasmtime/cranelift/codegen/src/settings.rs
Chris Fallin 2d5db92a9e Rework/simplify unwind infrastructure and implement Windows unwind.
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.
2021-03-11 20:03:52 -08:00

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);
}
}