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.
This commit is contained in:
Jakob Stoklund Olesen
2017-07-13 14:49:17 -07:00
parent 89634fa645
commit 4bb0e2014c
10 changed files with 213 additions and 33 deletions

View File

@@ -4,7 +4,8 @@ from collections import OrderedDict
from .predicates import Predicate from .predicates import Predicate
try: 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: if TYPE_CHECKING:
from .predicates import PredLeaf, PredNode # noqa from .predicates import PredLeaf, PredNode # noqa
except ImportError: except ImportError:
@@ -47,6 +48,17 @@ class Setting(object):
# type: () -> int # type: () -> int
raise NotImplementedError("default_byte is an abstract method") 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): class BoolSetting(Setting):
""" """
@@ -73,6 +85,17 @@ class BoolSetting(Setting):
else: else:
return 0 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): def predicate_leafs(self, leafs):
# type: (Set[PredLeaf]) -> None # type: (Set[PredLeaf]) -> None
leafs.add(self) leafs.add(self)
@@ -107,6 +130,12 @@ class NumSetting(Setting):
# type: () -> int # type: () -> int
return self.default 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): class EnumSetting(Setting):
""" """
@@ -129,6 +158,10 @@ class EnumSetting(Setting):
# type: () -> int # type: () -> int
return 0 return 0
def byte_for_value(self, value):
# type: (Any) -> int
return self.values.index(value)
class SettingGroup(object): class SettingGroup(object):
""" """
@@ -160,6 +193,7 @@ class SettingGroup(object):
# - Added parent predicates that are replicated in this group. # - Added parent predicates that are replicated in this group.
# Maps predicate -> number. # Maps predicate -> number.
self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa self.predicate_number = OrderedDict() # type: OrderedDict[PredNode, int] # noqa
self.presets = [] # type: List[Preset]
# Fully qualified Rust module name. See gen_settings.py. # Fully qualified Rust module name. See gen_settings.py.
self.qual_mod = None # type: str self.qual_mod = None # type: str
@@ -199,6 +233,10 @@ class SettingGroup(object):
assert obj.name is None assert obj.name is None
obj.name = name obj.name = name
self.named_predicates.append(obj) self.named_predicates.append(obj)
if isinstance(obj, Preset):
assert obj.name is None, obj.name
obj.name = name
self.layout() self.layout()
@staticmethod @staticmethod
@@ -209,6 +247,14 @@ class SettingGroup(object):
g.settings.append(setting) g.settings.append(setting)
return g 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): def number_predicate(self, pred):
# type: (PredNode) -> int # type: (PredNode) -> int
""" """
@@ -295,3 +341,65 @@ class SettingGroup(object):
predcate bits rounded up to a whole number of bytes. predcate bits rounded up to a whole number of bytes.
""" """
return self.boolean_offset + (len(self.predicate_number) + 7) // 8 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

View File

@@ -10,10 +10,10 @@ from cdsl.settings import BoolSetting, NumSetting, EnumSetting
from base import settings from base import settings
try: 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: if TYPE_CHECKING:
from cdsl.isa import TargetISA # noqa 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 from cdsl.predicates import Predicate, PredContext # noqa
except ImportError: except ImportError:
pass pass
@@ -106,14 +106,14 @@ def gen_getters(sgrp, fmt):
def gen_descriptors(sgrp, fmt): def gen_descriptors(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None # type: (SettingGroup, srcgen.Formatter) -> None
""" """
Generate the DESCRIPTORS and ENUMERATORS tables. Generate the DESCRIPTORS, ENUMERATORS, and PRESETS tables.
""" """
enums = UniqueSeqTable() enums = UniqueSeqTable()
with fmt.indented( with fmt.indented(
'static DESCRIPTORS: [detail::Descriptor; {}] = [' 'static DESCRIPTORS: [detail::Descriptor; {}] = ['
.format(len(sgrp.settings)), .format(len(sgrp.settings) + len(sgrp.presets)),
'];'): '];'):
for idx, setting in enumerate(sgrp.settings): for idx, setting in enumerate(sgrp.settings):
setting.descriptor_index = idx setting.descriptor_index = idx
@@ -135,6 +135,13 @@ def gen_descriptors(sgrp, fmt):
else: else:
raise AssertionError("Unknown setting kind") 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( with fmt.indented(
'static ENUMERATORS: [&str; {}] = [' 'static ENUMERATORS: [&str; {}] = ['
.format(len(enums.table)), .format(len(enums.table)),
@@ -143,10 +150,13 @@ def gen_descriptors(sgrp, fmt):
fmt.line('"{}",'.format(txt)) fmt.line('"{}",'.format(txt))
def hash_setting(s): def hash_setting(s):
# type: (Setting) -> int # type: (Union[Setting, Preset]) -> int
return constant_hash.simple_hash(s.name) 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( with fmt.indented(
'static HASH_TABLE: [u16; {}] = [' 'static HASH_TABLE: [u16; {}] = ['
.format(len(hash_table)), .format(len(hash_table)),
@@ -157,6 +167,15 @@ def gen_descriptors(sgrp, fmt):
else: else:
fmt.line('{},'.format(h.descriptor_index)) 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): def gen_template(sgrp, fmt):
# type: (SettingGroup, srcgen.Formatter) -> None # type: (SettingGroup, srcgen.Formatter) -> None
@@ -175,6 +194,7 @@ def gen_template(sgrp, fmt):
fmt.line('hash_table: &HASH_TABLE,') fmt.line('hash_table: &HASH_TABLE,')
vs = ', '.join('{:#04x}'.format(x) for x in v) vs = ', '.join('{:#04x}'.format(x) for x in v)
fmt.line('defaults: &[ {} ],'.format(vs)) fmt.line('defaults: &[ {} ],'.format(vs))
fmt.line('presets: &PRESETS,')
fmt.doc_comment( fmt.doc_comment(
'Create a `settings::Builder` for the {} settings group.' 'Create a `settings::Builder` for the {} settings group.'

View File

@@ -2,7 +2,7 @@
Intel settings. Intel settings.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from cdsl.settings import SettingGroup, BoolSetting from cdsl.settings import SettingGroup, BoolSetting, Preset
from cdsl.predicates import And from cdsl.predicates import And
import base.settings as shared import base.settings as shared
from .defs import ISA from .defs import ISA
@@ -38,4 +38,10 @@ use_popcnt = And(has_popcnt, has_sse42)
use_bmi1 = And(has_bmi1) use_bmi1 = And(has_bmi1)
use_lzcnt = And(has_lzcnt) 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()) ISA.settings.close(globals())

View File

@@ -7,3 +7,27 @@ use std::fmt;
// `Flags` struct with an impl for all of the settings defined in // `Flags` struct with an impl for all of the settings defined in
// `lib/cretonne/meta/cretonne/settings.py`. // `lib/cretonne/meta/cretonne/settings.py`.
include!(concat!(env!("OUT_DIR"), "/settings-intel.rs")); 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);
}
}

View File

@@ -108,8 +108,8 @@ impl settings::Configurable for Builder {
self.setup.set(name, value) self.setup.set(name, value)
} }
fn set_bool(&mut self, name: &str, value: bool) -> settings::Result<()> { fn enable(&mut self, name: &str) -> settings::Result<()> {
self.setup.set_bool(name, value) self.setup.enable(name)
} }
} }

View File

@@ -114,7 +114,7 @@ mod tests {
#[test] #[test]
fn test_64bitenc() { fn test_64bitenc() {
let mut shared_builder = settings::builder(); 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 shared_flags = settings::Flags::new(&shared_builder);
let isa = isa::lookup("riscv").unwrap().finish(shared_flags); let isa = isa::lookup("riscv").unwrap().finish(shared_flags);
@@ -161,7 +161,7 @@ mod tests {
#[test] #[test]
fn test_32bitenc() { fn test_32bitenc() {
let mut shared_builder = settings::builder(); 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 shared_flags = settings::Flags::new(&shared_builder);
let isa = isa::lookup("riscv").unwrap().finish(shared_flags); let isa = isa::lookup("riscv").unwrap().finish(shared_flags);
@@ -216,13 +216,13 @@ mod tests {
#[test] #[test]
fn test_rv32m() { fn test_rv32m() {
let mut shared_builder = settings::builder(); 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 shared_flags = settings::Flags::new(&shared_builder);
// Set the supports_m stting which in turn enables the use_m predicate that unlocks // Set the supports_m stting which in turn enables the use_m predicate that unlocks
// encodings for imul. // encodings for imul.
let mut isa_builder = isa::lookup("riscv").unwrap(); 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); let isa = isa_builder.finish(shared_flags);

View File

@@ -34,17 +34,17 @@ mod tests {
fn predicates() { fn predicates() {
let shared = settings::Flags::new(&settings::builder()); let shared = settings::Flags::new(&settings::builder());
let mut b = builder(); let mut b = builder();
b.set_bool("supports_f", true).unwrap(); b.enable("supports_f").unwrap();
b.set_bool("supports_d", true).unwrap(); b.enable("supports_d").unwrap();
let f = Flags::new(&shared, &b); let f = Flags::new(&shared, &b);
assert_eq!(f.full_float(), true); assert_eq!(f.full_float(), true);
let mut sb = settings::builder(); 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 shared = settings::Flags::new(&sb);
let mut b = builder(); let mut b = builder();
b.set_bool("supports_f", true).unwrap(); b.enable("supports_f").unwrap();
b.set_bool("supports_d", true).unwrap(); b.enable("supports_d").unwrap();
let f = Flags::new(&shared, &b); let f = Flags::new(&shared, &b);
assert_eq!(f.full_float(), false); assert_eq!(f.full_float(), false);
} }

View File

@@ -35,10 +35,10 @@ pub trait Configurable {
/// This can set any type of setting whether it is numeric, boolean, or enumerated. /// This can set any type of setting whether it is numeric, boolean, or enumerated.
fn set(&mut self, name: &str, value: &str) -> Result<()>; 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. /// If the identified setting isn't a boolean or a preset, a `BadType` error is returned.
fn set_bool(&mut self, name: &str, value: bool) -> Result<()>; fn enable(&mut self, name: &str) -> Result<()>;
} }
/// Collect settings values based on a template. /// 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. /// Look up a descriptor by name.
fn lookup(&self, name: &str) -> Result<(usize, detail::Detail)> { fn lookup(&self, name: &str) -> Result<(usize, detail::Detail)> {
match probe(self.template, name, simple_hash(name)) { match probe(self.template, name, simple_hash(name)) {
@@ -101,14 +108,19 @@ fn parse_enum_value(value: &str, choices: &[&str]) -> Result<u8> {
} }
impl Configurable for Builder { 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; use self::detail::Detail;
let (offset, detail) = self.lookup(name)?; let (offset, detail) = self.lookup(name)?;
if let Detail::Bool { bit } = detail { match detail {
self.set_bit(offset, bit, value); Detail::Bool { bit } => {
self.set_bit(offset, bit, true);
Ok(()) Ok(())
} else { }
Err(Error::BadType) 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.bytes[offset] = parse_enum_value(value,
self.template.enums(last, enumerators))?; self.template.enums(last, enumerators))?;
} }
Detail::Preset => return Err(Error::BadName),
} }
Ok(()) Ok(())
} }
@@ -169,6 +182,8 @@ pub mod detail {
pub hash_table: &'static [u16], pub hash_table: &'static [u16],
/// Default values. /// Default values.
pub defaults: &'static [u8], pub defaults: &'static [u8],
/// Pairs of (mask, value) for presets.
pub presets: &'static [(u8, u8)],
} }
impl Template { impl Template {
@@ -197,6 +212,8 @@ pub mod detail {
write!(f, "{}", byte) 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. /// First enumerator in the ENUMERATORS table.
enumerators: u16, 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] #[test]
fn modify_bool() { fn modify_bool() {
let mut b = builder(); let mut b = builder();
assert_eq!(b.set_bool("not_there", true), Err(BadName)); assert_eq!(b.enable("not_there"), Err(BadName));
assert_eq!(b.set_bool("enable_simd", true), Ok(())); assert_eq!(b.enable("enable_simd"), Ok(()));
assert_eq!(b.set_bool("enable_simd", false), Ok(())); assert_eq!(b.set("enable_simd", "false"), Ok(()));
let f = Flags::new(&b); let f = Flags::new(&b);
assert_eq!(f.enable_simd(), false); assert_eq!(f.enable_simd(), false);

View File

@@ -41,7 +41,7 @@ pub fn parse_options<'a, I>(iter: I, config: &mut Configurable, loc: &Location)
for opt in iter.map(TestOption::new) { for opt in iter.map(TestOption::new) {
match opt { match opt {
TestOption::Flag(name) => { TestOption::Flag(name) => {
match config.set_bool(name, true) { match config.enable(name) {
Ok(_) => {} Ok(_) => {}
Err(SetError::BadName) => return err!(loc, "unknown flag '{}'", opt), Err(SetError::BadName) => return err!(loc, "unknown flag '{}'", opt),
Err(_) => return err!(loc, "not a boolean flag: '{}'", opt), Err(_) => return err!(loc, "not a boolean flag: '{}'", opt),

View File

@@ -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 // 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. // either testing or debugging Cretonne, and verification makes sense.
flag_builder flag_builder
.set_bool("enable_verifier", true) .enable("enable_verifier")
.expect("Missing enable_verifier setting"); .expect("Missing enable_verifier setting");
while let Some(Token::Identifier(command)) = self.token() { while let Some(Token::Identifier(command)) = self.token() {