From 1ef72dd5ec5704de81c07adcc74c92aeeff48e29 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Tue, 9 Aug 2016 14:12:36 -0700 Subject: [PATCH] Add support for enumerated settings. The EnumSetting objects can take one of 256 named values. --- meta/cretonne/settings.py | 16 +++++++- meta/gen_settings.py | 44 +++++++++++++++++---- meta/srcgen.py | 6 ++- src/libcretonne/settings.rs | 77 ++++++++++++++++++++++++++++++++++--- 4 files changed, 127 insertions(+), 16 deletions(-) diff --git a/meta/cretonne/settings.py b/meta/cretonne/settings.py index f06c69bfef..2876dfd84f 100644 --- a/meta/cretonne/settings.py +++ b/meta/cretonne/settings.py @@ -4,10 +4,22 @@ Cretonne shared settings. This module defines settings are are relevant for all code generators. """ -from . import SettingGroup, BoolSetting +from . import SettingGroup, BoolSetting, EnumSetting group = SettingGroup('shared') -enable_simd = BoolSetting("Enable the use of SIMD instructions", default=True) +opt_level = EnumSetting( + """ + Optimization level: + + - default: Very profitable optimizations enabled, none slow. + - best: Enable all optimizations + - fastest: Optimize for compile time by disabling most optimizations. + """, + 'default', 'best', 'fastest') + +enable_simd = BoolSetting( + """Enable the use of SIMD instructions.""", + default=True) group.close(globals()) diff --git a/meta/gen_settings.py b/meta/gen_settings.py index ce1e031e97..d8b0e4c919 100644 --- a/meta/gen_settings.py +++ b/meta/gen_settings.py @@ -5,7 +5,7 @@ Generate sources with settings. import srcgen from unique_table import UniqueSeqTable import constant_hash -from cretonne import BoolSetting, NumSetting, settings +from cretonne import camel_case, BoolSetting, NumSetting, EnumSetting, settings def layout_group(sgrp): @@ -40,11 +40,25 @@ def layout_group(sgrp): return next_byte +def gen_enum_types(sgrp, fmt): + """ + Emit enum types for any enum settings. + """ + for setting in sgrp.settings: + if not isinstance(setting, EnumSetting): + continue + ty = camel_case(setting.name) + fmt.line('#[derive(Debug, PartialEq, Eq)]') + fmt.line( + 'pub enum {} {{ {} }}' + .format(ty, ", ".join(camel_case(v) for v in setting.values))) + + def gen_getter(setting, fmt): """ Emit a getter function for `setting`. """ - fmt.doc_comment(setting.__doc__ + '.') + fmt.doc_comment(setting.__doc__) if isinstance(setting, BoolSetting): proto = 'pub fn {}(&self) -> bool'.format(setting.name) @@ -56,6 +70,15 @@ def gen_getter(setting, fmt): proto = 'pub fn {}(&self) -> u8'.format(setting.name) with fmt.indented(proto + ' {', '}'): fmt.line('self.bytes[{}]'.format(setting.byte_offset)) + elif isinstance(setting, EnumSetting): + ty = camel_case(setting.name) + proto = 'pub fn {}(&self) -> {}'.format(setting.name, ty) + with fmt.indented(proto + ' {', '}'): + with fmt.indented( + 'match self.bytes[{}] {{'.format(setting.byte_offset), '}'): + for i, v in enumerate(setting.values): + fmt.line( '{} => {}::{},'.format(i, ty, camel_case(v))) + fmt.line( '_ => panic!("Invalid enum value")') else: raise AssertionError("Unknown setting kind") @@ -108,6 +131,11 @@ def gen_descriptors(sgrp, fmt): .format(setting.bit_offset)) elif isinstance(setting, NumSetting): fmt.line('detail: Detail::Num,') + elif isinstance(setting, EnumSetting): + offs = enums.add(setting.values) + fmt.line( + 'detail: Detail::Enum {{ last: {}, enumerators: {} }},' + .format(len(setting.values)-1, offs)) else: raise AssertionError("Unknown setting kind") @@ -147,8 +175,8 @@ def gen_stringwise(sgrp, fmt): with fmt.indented('impl Stringwise for Settings {', '}'): with fmt.indented( - 'fn lookup_mut(&mut self, name: &str)' + - '-> Result<(Detail, &mut u8)> {', + 'fn lookup(&self, name: &str)' + + '-> Result<(usize, Detail)> {', '}'): fmt.line('use simple_hash::simple_hash;') fmt.line('let tlen = HASH_TABLE.len();') @@ -162,9 +190,8 @@ def gen_stringwise(sgrp, fmt): fmt.line('return Err(Error::BadName)') with fmt.indented('if DESCRIPTORS[entry].name == name {', '}'): fmt.line( - 'return Ok((DESCRIPTORS[entry].detail, ' + - '&mut self.bytes[DESCRIPTORS[entry].offset ' + - 'as usize]))') + 'return Ok((DESCRIPTORS[entry].offset as usize, ' + + 'DESCRIPTORS[entry].detail))') fmt.line('step += 1;') fmt.line('assert!(step < tlen);') fmt.line('idx += step;') @@ -175,6 +202,8 @@ def gen_stringwise(sgrp, fmt): '}'): fmt.line('ENUMERATORS[enums as usize + value as usize]') + with fmt.indented('fn raw_bytes_mut(&mut self) -> &mut [u8] {', '}'): + fmt.line('&mut self.bytes') def gen_display(sgrp, fmt): """ @@ -204,6 +233,7 @@ def gen_group(sgrp, fmt): with fmt.indented('pub struct Settings {', '}'): fmt.line('bytes: [u8; {}],'.format(byte_size)) + gen_enum_types(sgrp, fmt) gen_getters(sgrp, fmt) gen_default(sgrp, byte_size, fmt) gen_descriptors(sgrp, fmt) diff --git a/meta/srcgen.py b/meta/srcgen.py index 1ebd745286..42f39c96de 100644 --- a/meta/srcgen.py +++ b/meta/srcgen.py @@ -8,6 +8,7 @@ source code. import sys import os +import re class Formatter(object): @@ -108,8 +109,9 @@ class Formatter(object): self.line('// ' + s) def doc_comment(self, s): - """Add a documentation comment line.""" - self.line('/// ' + s) + """Add a (multi-line) documentation comment.""" + s = re.sub('^', self.indent + '/// ', s, flags=re.M) + '\n' + self.lines.append(s) if __name__ == "__main__": import doctest diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs index 80948e3ed3..af25dd458a 100644 --- a/src/libcretonne/settings.rs +++ b/src/libcretonne/settings.rs @@ -59,16 +59,26 @@ pub enum Error { pub type Result = result::Result; +fn parse_bool_value(value: &str) -> Result { + match value { + "true" | "on" | "yes" | "1" => Ok(true), + "false" | "off" | "no" | "0" => Ok(false), + _ => Err(Error::BadValue), + } +} + /// Interface for working with a group of settings as strings. pub trait Stringwise { - /// Look up a setting by name, return the details of the setting along with a reference to the - /// byte holding the value of the setting. - fn lookup_mut(&mut self, name: &str) -> Result<(Detail, &mut u8)>; + /// Look up a setting by name, return the byte offset and details of the setting. + fn lookup(&self, name: &str) -> Result<(usize, Detail)>; /// Get an enumerator string from the `Detail::enumerators` value and an offset. fn enumerator(&self, enums: u16, value: u8) -> &'static str; - /// Format a setting value as a TOML string. This is mostly for use by the generateed `Display` + /// Get the underlying byte array used to store settings. + fn raw_bytes_mut(&mut self) -> &mut [u8]; + + /// Format a setting value as a TOML string. This is mostly for use by the generated `Display` /// implementation. fn format_toml_value(&self, detail: Detail, byte: u8, f: &mut fmt::Formatter) -> fmt::Result { match detail { @@ -86,9 +96,10 @@ pub trait Stringwise { /// Set a boolean setting by name. fn set_bool(&mut self, name: &str, value: bool) -> Result<()> { - let (detail, byte) = try!(self.lookup_mut(name)); + let (offset, detail) = try!(self.lookup(name)); if let Detail::Bool { bit } = detail { let mask = 1 << bit; + let byte = &mut self.raw_bytes_mut()[offset]; if value { *byte |= mask; } else { @@ -99,6 +110,43 @@ pub trait Stringwise { Err(Error::BadType) } } + + /// Set the string value of a named setting. + /// + /// For boolean settings, any of the values accepted by `parse_bool_value` above are accepted + /// (true/false, on/off, yes/no, 1/0). + /// + /// For enumerated settings, the value must match one of the allowed values exactly. + fn set(&mut self, name: &str, value: &str) -> Result<()> { + let (offset, detail) = try!(self.lookup(name)); + match detail { + Detail::Bool { bit } => { + let mask = 1 << bit; + let byte = &mut self.raw_bytes_mut()[offset]; + if try!(parse_bool_value(value)) { + *byte |= mask; + } else { + *byte &= !mask; + } + } + Detail::Num => { + self.raw_bytes_mut()[offset] = try!(value.parse().map_err(|_| Error::BadValue)); + } + Detail::Enum { last, enumerators } => { + // Linear search.. + for i in 0.. { + if value == self.enumerator(enumerators, i) { + self.raw_bytes_mut()[offset] = i; + break; + } + if i == last { + return Err(Error::BadValue); + } + } + } + } + Ok(()) + } } // Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct @@ -116,6 +164,7 @@ mod tests { let s = Settings::default(); assert_eq!(s.to_string(), "[shared]\n\ + opt_level = \"default\"\n\ enable_simd = true\n"); } @@ -131,4 +180,22 @@ mod tests { assert_eq!(s.set_bool("enable_simd", false), Ok(())); assert_eq!(s.enable_simd(), false); } + + #[test] + fn modify_string() { + let mut s = Settings::default(); + assert_eq!(s.enable_simd(), true); + assert_eq!(s.opt_level(), super::OptLevel::Default); + + assert_eq!(s.set("not_there", "true"), Err(BadName)); + assert_eq!(s.set("enable_simd", ""), Err(BadValue)); + assert_eq!(s.set("enable_simd", "best"), Err(BadValue)); + assert_eq!(s.set("opt_level", "true"), Err(BadValue)); + + assert_eq!(s.set("enable_simd", "no"), Ok(())); + assert_eq!(s.enable_simd(), false); + + assert_eq!(s.set("opt_level", "best"), Ok(())); + assert_eq!(s.opt_level(), super::OptLevel::Best); + } }