From 4bb0e2014c79cc65b9c83577aab9292dcb09a663 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Thu, 13 Jul 2017 14:49:17 -0700 Subject: [PATCH] Add support for setting presets. Fixes #11. Presets are groups of settings and values applied at once. This is used as a shorthand in test files, so for example "isa intel nehalem" enables all of the CPUID bits that the Nehalem micro-architecture provides. --- lib/cretonne/meta/cdsl/settings.py | 110 +++++++++++++++++++++++- lib/cretonne/meta/gen_settings.py | 32 +++++-- lib/cretonne/meta/isa/intel/settings.py | 8 +- lib/cretonne/src/isa/intel/settings.rs | 24 ++++++ lib/cretonne/src/isa/mod.rs | 4 +- lib/cretonne/src/isa/riscv/mod.rs | 8 +- lib/cretonne/src/isa/riscv/settings.rs | 10 +-- lib/cretonne/src/settings.rs | 46 +++++++--- lib/reader/src/isaspec.rs | 2 +- lib/reader/src/parser.rs | 2 +- 10 files changed, 213 insertions(+), 33 deletions(-) diff --git a/lib/cretonne/meta/cdsl/settings.py b/lib/cretonne/meta/cdsl/settings.py index d9404060dd..aca3ac296f 100644 --- a/lib/cretonne/meta/cdsl/settings.py +++ b/lib/cretonne/meta/cdsl/settings.py @@ -4,7 +4,8 @@ from collections import OrderedDict from .predicates import Predicate try: - from typing import Set, List, Dict, Any, TYPE_CHECKING # noqa + from typing import Tuple, Set, List, Dict, Any, Union, TYPE_CHECKING # noqa + BoolOrPresetOrDict = Union['BoolSetting', 'Preset', Dict['Setting', Any]] if TYPE_CHECKING: from .predicates import PredLeaf, PredNode # noqa except ImportError: @@ -47,6 +48,17 @@ class Setting(object): # type: () -> int raise NotImplementedError("default_byte is an abstract method") + def byte_for_value(self, value): + # type: (Any) -> int + """Get the setting byte value that corresponds to `value`""" + raise NotImplementedError("byte_for_value is an abstract method") + + def byte_mask(self): + # type: () -> int + """Get a mask of bits in our byte that are relevant to this setting.""" + # Only BoolSetting has a different mask. + return 0xff + class BoolSetting(Setting): """ @@ -73,6 +85,17 @@ class BoolSetting(Setting): else: return 0 + def byte_for_value(self, value): + # type: (Any) -> int + if value: + return 1 << self.bit_offset + else: + return 0 + + def byte_mask(self): + # type: () -> int + return 1 << self.bit_offset + def predicate_leafs(self, leafs): # type: (Set[PredLeaf]) -> None leafs.add(self) @@ -107,6 +130,12 @@ class NumSetting(Setting): # type: () -> int return self.default + def byte_for_value(self, value): + # type: (Any) -> int + assert isinstance(value, int), "NumSetting must be set to an int" + assert value >= 0 and value <= 255 + return value + class EnumSetting(Setting): """ @@ -129,6 +158,10 @@ class EnumSetting(Setting): # type: () -> int return 0 + def byte_for_value(self, value): + # type: (Any) -> int + return self.values.index(value) + class SettingGroup(object): """ @@ -160,6 +193,7 @@ class SettingGroup(object): # - Added parent predicates that are replicated in this group. # Maps predicate -> number. self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa + self.presets = [] # type: List[Preset] # Fully qualified Rust module name. See gen_settings.py. self.qual_mod = None # type: str @@ -199,6 +233,10 @@ class SettingGroup(object): assert obj.name is None obj.name = name self.named_predicates.append(obj) + if isinstance(obj, Preset): + assert obj.name is None, obj.name + obj.name = name + self.layout() @staticmethod @@ -209,6 +247,14 @@ class SettingGroup(object): g.settings.append(setting) return g + @staticmethod + def append_preset(preset): + # type: (Preset) -> SettingGroup + g = SettingGroup._current + assert g, "Open a setting group before defining presets." + g.presets.append(preset) + return g + def number_predicate(self, pred): # type: (PredNode) -> int """ @@ -295,3 +341,65 @@ class SettingGroup(object): predcate bits rounded up to a whole number of bytes. """ return self.boolean_offset + (len(self.predicate_number) + 7) // 8 + + +class Preset(object): + """ + A collection of setting values that are applied at once. + + A `Preset` represents a shorthand notation for applying a number of + settings at once. Example: + + nehalem = Preset(has_sse41, has_cmov, has_avx=0) + + Enabling the `nehalem` setting is equivalent to enabling `has_sse41` and + `has_cmov` while disabling the `has_avx` setting. + """ + + def __init__(self, *args): + # type: (*BoolOrPresetOrDict) -> None + self.name = None # type: str # Assigned later by `SettingGroup`. + # Each tuple provides the value for a setting. + self.values = list() # type: List[Tuple[Setting, Any]] + + for arg in args: + if isinstance(arg, Preset): + # Any presets in args are immediately expanded. + self.values.extend(arg.values) + elif isinstance(arg, dict): + # A dictionary of key: value pairs. + self.values.extend(arg.items()) + else: + # A BoolSetting to enable. + assert isinstance(arg, BoolSetting) + self.values.append((arg, True)) + + self.group = SettingGroup.append_preset(self) + # Index into the generated DESCRIPTORS table. + self.descriptor_index = None # type: int + + def layout(self): + # type: () -> List[Tuple[int, int]] + """ + Compute a list of (mask, byte) pairs that incorporate all values in + this preset. + + The list will have an entry for each setting byte in the settings + group. + """ + l = [(0, 0)] * self.group.settings_size + + # Apply setting values in order. + for s, v in self.values: + ofs = s.byte_offset + s_mask = s.byte_mask() + s_val = s.byte_for_value(v) + assert (s_val & ~s_mask) == 0 + l_mask, l_val = l[ofs] + # Accumulated mask of modified bits. + l_mask |= s_mask + # Overwrite the relevant bits with the new value. + l_val = (l_val & ~s_mask) | s_val + l[ofs] = (l_mask, l_val) + + return l diff --git a/lib/cretonne/meta/gen_settings.py b/lib/cretonne/meta/gen_settings.py index ae16b337c0..c56cc4df41 100644 --- a/lib/cretonne/meta/gen_settings.py +++ b/lib/cretonne/meta/gen_settings.py @@ -10,10 +10,10 @@ from cdsl.settings import BoolSetting, NumSetting, EnumSetting from base import settings try: - from typing import Sequence, Set, Tuple, List, TYPE_CHECKING # noqa + from typing import Sequence, Set, Tuple, List, Union, TYPE_CHECKING # noqa if TYPE_CHECKING: from cdsl.isa import TargetISA # noqa - from cdsl.settings import Setting, SettingGroup # noqa + from cdsl.settings import Setting, Preset, SettingGroup # noqa from cdsl.predicates import Predicate, PredContext # noqa except ImportError: pass @@ -106,14 +106,14 @@ def gen_getters(sgrp, fmt): def gen_descriptors(sgrp, fmt): # type: (SettingGroup, srcgen.Formatter) -> None """ - Generate the DESCRIPTORS and ENUMERATORS tables. + Generate the DESCRIPTORS, ENUMERATORS, and PRESETS tables. """ enums = UniqueSeqTable() with fmt.indented( 'static DESCRIPTORS: [detail::Descriptor; {}] = [' - .format(len(sgrp.settings)), + .format(len(sgrp.settings) + len(sgrp.presets)), '];'): for idx, setting in enumerate(sgrp.settings): setting.descriptor_index = idx @@ -135,6 +135,13 @@ def gen_descriptors(sgrp, fmt): else: raise AssertionError("Unknown setting kind") + for idx, preset in enumerate(sgrp.presets): + preset.descriptor_index = len(sgrp.settings) + idx + with fmt.indented('detail::Descriptor {', '},'): + fmt.line('name: "{}",'.format(preset.name)) + fmt.line('offset: {},'.format(idx * sgrp.settings_size)) + fmt.line('detail: detail::Detail::Preset,') + with fmt.indented( 'static ENUMERATORS: [&str; {}] = [' .format(len(enums.table)), @@ -143,10 +150,13 @@ def gen_descriptors(sgrp, fmt): fmt.line('"{}",'.format(txt)) def hash_setting(s): - # type: (Setting) -> int + # type: (Union[Setting, Preset]) -> int return constant_hash.simple_hash(s.name) - hash_table = constant_hash.compute_quadratic(sgrp.settings, hash_setting) + hash_elems = [] # type: List[Union[Setting, Preset]] + hash_elems.extend(sgrp.settings) + hash_elems.extend(sgrp.presets) + hash_table = constant_hash.compute_quadratic(hash_elems, hash_setting) with fmt.indented( 'static HASH_TABLE: [u16; {}] = [' .format(len(hash_table)), @@ -157,6 +167,15 @@ def gen_descriptors(sgrp, fmt): else: fmt.line('{},'.format(h.descriptor_index)) + with fmt.indented( + 'static PRESETS: [(u8, u8); {}] = [' + .format(len(sgrp.presets) * sgrp.settings_size), + '];'): + for preset in sgrp.presets: + fmt.comment(preset.name) + for mask, value in preset.layout(): + fmt.format('(0b{:08b}, 0b{:08b}),', mask, value) + def gen_template(sgrp, fmt): # type: (SettingGroup, srcgen.Formatter) -> None @@ -175,6 +194,7 @@ def gen_template(sgrp, fmt): fmt.line('hash_table: &HASH_TABLE,') vs = ', '.join('{:#04x}'.format(x) for x in v) fmt.line('defaults: &[ {} ],'.format(vs)) + fmt.line('presets: &PRESETS,') fmt.doc_comment( 'Create a `settings::Builder` for the {} settings group.' diff --git a/lib/cretonne/meta/isa/intel/settings.py b/lib/cretonne/meta/isa/intel/settings.py index 9251f73c5d..a16303a3b5 100644 --- a/lib/cretonne/meta/isa/intel/settings.py +++ b/lib/cretonne/meta/isa/intel/settings.py @@ -2,7 +2,7 @@ Intel settings. """ from __future__ import absolute_import -from cdsl.settings import SettingGroup, BoolSetting +from cdsl.settings import SettingGroup, BoolSetting, Preset from cdsl.predicates import And import base.settings as shared from .defs import ISA @@ -38,4 +38,10 @@ use_popcnt = And(has_popcnt, has_sse42) use_bmi1 = And(has_bmi1) use_lzcnt = And(has_lzcnt) +# Presets corresponding to Intel CPUs. + +nehalem = Preset( + has_sse2, has_sse3, has_ssse3, has_sse41, has_sse42, has_popcnt) +haswell = Preset(nehalem, has_bmi1, has_lzcnt) + ISA.settings.close(globals()) diff --git a/lib/cretonne/src/isa/intel/settings.rs b/lib/cretonne/src/isa/intel/settings.rs index 341eb2dcc9..7ca4886588 100644 --- a/lib/cretonne/src/isa/intel/settings.rs +++ b/lib/cretonne/src/isa/intel/settings.rs @@ -7,3 +7,27 @@ use std::fmt; // `Flags` struct with an impl for all of the settings defined in // `lib/cretonne/meta/cretonne/settings.py`. include!(concat!(env!("OUT_DIR"), "/settings-intel.rs")); + +#[cfg(test)] +mod tests { + use super::{builder, Flags}; + use settings::{self, Configurable}; + + #[test] + fn presets() { + let shared = settings::Flags::new(&settings::builder()); + + // Nehalem has SSE4.1 but not BMI1. + let mut b1 = builder(); + b1.enable("nehalem").unwrap(); + let f1 = Flags::new(&shared, &b1); + assert_eq!(f1.has_sse41(), true); + assert_eq!(f1.has_bmi1(), false); + + let mut b2 = builder(); + b2.enable("haswell").unwrap(); + let f2 = Flags::new(&shared, &b2); + assert_eq!(f2.has_sse41(), true); + assert_eq!(f2.has_bmi1(), true); + } +} diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index 1ec80e790c..edd72b27af 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -108,8 +108,8 @@ impl settings::Configurable for Builder { self.setup.set(name, value) } - fn set_bool(&mut self, name: &str, value: bool) -> settings::Result<()> { - self.setup.set_bool(name, value) + fn enable(&mut self, name: &str) -> settings::Result<()> { + self.setup.enable(name) } } diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 5f9cd771a2..246ec13bd1 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -114,7 +114,7 @@ mod tests { #[test] fn test_64bitenc() { let mut shared_builder = settings::builder(); - shared_builder.set_bool("is_64bit", true).unwrap(); + shared_builder.enable("is_64bit").unwrap(); let shared_flags = settings::Flags::new(&shared_builder); let isa = isa::lookup("riscv").unwrap().finish(shared_flags); @@ -161,7 +161,7 @@ mod tests { #[test] fn test_32bitenc() { let mut shared_builder = settings::builder(); - shared_builder.set_bool("is_64bit", false).unwrap(); + shared_builder.set("is_64bit", "false").unwrap(); let shared_flags = settings::Flags::new(&shared_builder); let isa = isa::lookup("riscv").unwrap().finish(shared_flags); @@ -216,13 +216,13 @@ mod tests { #[test] fn test_rv32m() { let mut shared_builder = settings::builder(); - shared_builder.set_bool("is_64bit", false).unwrap(); + shared_builder.set("is_64bit", "false").unwrap(); let shared_flags = settings::Flags::new(&shared_builder); // Set the supports_m stting which in turn enables the use_m predicate that unlocks // encodings for imul. let mut isa_builder = isa::lookup("riscv").unwrap(); - isa_builder.set_bool("supports_m", true).unwrap(); + isa_builder.enable("supports_m").unwrap(); let isa = isa_builder.finish(shared_flags); diff --git a/lib/cretonne/src/isa/riscv/settings.rs b/lib/cretonne/src/isa/riscv/settings.rs index aa66d75fd9..69a47f8717 100644 --- a/lib/cretonne/src/isa/riscv/settings.rs +++ b/lib/cretonne/src/isa/riscv/settings.rs @@ -34,17 +34,17 @@ mod tests { fn predicates() { let shared = settings::Flags::new(&settings::builder()); let mut b = builder(); - b.set_bool("supports_f", true).unwrap(); - b.set_bool("supports_d", true).unwrap(); + b.enable("supports_f").unwrap(); + b.enable("supports_d").unwrap(); let f = Flags::new(&shared, &b); assert_eq!(f.full_float(), true); let mut sb = settings::builder(); - sb.set_bool("enable_simd", false).unwrap(); + sb.set("enable_simd", "false").unwrap(); let shared = settings::Flags::new(&sb); let mut b = builder(); - b.set_bool("supports_f", true).unwrap(); - b.set_bool("supports_d", true).unwrap(); + b.enable("supports_f").unwrap(); + b.enable("supports_d").unwrap(); let f = Flags::new(&shared, &b); assert_eq!(f.full_float(), false); } diff --git a/lib/cretonne/src/settings.rs b/lib/cretonne/src/settings.rs index 204588c7c1..19b9fafc21 100644 --- a/lib/cretonne/src/settings.rs +++ b/lib/cretonne/src/settings.rs @@ -35,10 +35,10 @@ pub trait Configurable { /// This can set any type of setting whether it is numeric, boolean, or enumerated. fn set(&mut self, name: &str, value: &str) -> Result<()>; - /// Set the value of a boolean setting by name. + /// Enable a boolean setting or apply a preset. /// - /// If the identified setting isn't a boolean, a `BadType` error is returned. - fn set_bool(&mut self, name: &str, value: bool) -> Result<()>; + /// If the identified setting isn't a boolean or a preset, a `BadType` error is returned. + fn enable(&mut self, name: &str) -> Result<()>; } /// Collect settings values based on a template. @@ -73,6 +73,13 @@ impl Builder { } } + /// 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) -> Result<(usize, detail::Detail)> { match probe(self.template, name, simple_hash(name)) { @@ -101,14 +108,19 @@ fn parse_enum_value(value: &str, choices: &[&str]) -> Result { } impl Configurable for Builder { - fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { + fn enable(&mut self, name: &str) -> Result<()> { use self::detail::Detail; let (offset, detail) = self.lookup(name)?; - if let Detail::Bool { bit } = detail { - self.set_bit(offset, bit, value); - Ok(()) - } else { - Err(Error::BadType) + match detail { + Detail::Bool { bit } => { + self.set_bit(offset, bit, true); + Ok(()) + } + Detail::Preset => { + self.apply_preset(&self.template.presets[offset..]); + Ok(()) + } + _ => Err(Error::BadType), } } @@ -128,6 +140,7 @@ impl Configurable for Builder { self.bytes[offset] = parse_enum_value(value, self.template.enums(last, enumerators))?; } + Detail::Preset => return Err(Error::BadName), } Ok(()) } @@ -169,6 +182,8 @@ pub mod detail { pub hash_table: &'static [u16], /// Default values. pub defaults: &'static [u8], + /// Pairs of (mask, value) for presets. + pub presets: &'static [(u8, u8)], } impl Template { @@ -197,6 +212,8 @@ pub mod detail { write!(f, "{}", byte) } } + // Presets aren't printed. They are reflected in the other settings. + Detail::Preset { .. } => Ok(()), } } } @@ -251,6 +268,11 @@ pub mod detail { /// 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, } } @@ -284,9 +306,9 @@ mod tests { #[test] fn modify_bool() { let mut b = builder(); - assert_eq!(b.set_bool("not_there", true), Err(BadName)); - assert_eq!(b.set_bool("enable_simd", true), Ok(())); - assert_eq!(b.set_bool("enable_simd", false), Ok(())); + assert_eq!(b.enable("not_there"), Err(BadName)); + 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); diff --git a/lib/reader/src/isaspec.rs b/lib/reader/src/isaspec.rs index 3715f4ed6b..51748e284a 100644 --- a/lib/reader/src/isaspec.rs +++ b/lib/reader/src/isaspec.rs @@ -41,7 +41,7 @@ pub fn parse_options<'a, I>(iter: I, config: &mut Configurable, loc: &Location) for opt in iter.map(TestOption::new) { match opt { TestOption::Flag(name) => { - match config.set_bool(name, true) { + match config.enable(name) { Ok(_) => {} Err(SetError::BadName) => return err!(loc, "unknown flag '{}'", opt), Err(_) => return err!(loc, "not a boolean flag: '{}'", opt), diff --git a/lib/reader/src/parser.rs b/lib/reader/src/parser.rs index 620d02ca2b..fb659a6b8e 100644 --- a/lib/reader/src/parser.rs +++ b/lib/reader/src/parser.rs @@ -609,7 +609,7 @@ impl<'a> Parser<'a> { // would slow down normal compilation, but when we're reading IL from a text file we're // either testing or debugging Cretonne, and verification makes sense. flag_builder - .set_bool("enable_verifier", true) + .enable("enable_verifier") .expect("Missing enable_verifier setting"); while let Some(Token::Identifier(command)) = self.token() {