diff --git a/docs/metaref.rst b/docs/metaref.rst index 4ac81096cc..8df75d80a2 100644 --- a/docs/metaref.rst +++ b/docs/metaref.rst @@ -25,6 +25,45 @@ The main driver for this source code generation process is the :file:`meta/build.py` script which is invoked as part of the build process if anything in the :file:`meta` directory has changed since the last build. + +Settings +======== + +Settings are used by the environment embedding Cretonne to control the details +of code generation. Each setting is defined in the meta language so a compact +and consistent Rust representation can be generated. Shared settings are defined +in the :mod:`cretonne.settings` module. Some settings are specific to a target +ISA, and defined in a `settings` module under the appropriate :file:`meta/isa/*` +directory. + +Settings can take boolean on/off values, small numbers, or explicitly enumerated +symbolic values. Each type is represented by a sub-class of :class:`Setting`: + +.. inheritance-diagram:: Setting BoolSetting NumSetting EnumSetting + :parts: 1 + +.. autoclass:: Setting +.. autoclass:: BoolSetting +.. autoclass:: NumSetting +.. autoclass:: EnumSetting + +All settings must belong to a *group*, represented by a :class:`SettingGroup` +object. + +.. autoclass:: SettingGroup + +Normally, a setting group corresponds to all settings defined in a module. Such +a module looks like this:: + + group = SettingGroup('example') + + foo = BoolSetting('use the foo') + bar = BoolSetting('enable bars', True) + opt = EnumSetting('optimization level', 'Debug', 'Release') + + group.close(globals()) + + Instruction descriptions ======================== diff --git a/meta/build.py b/meta/build.py index 5dadc112e2..6ca4425823 100644 --- a/meta/build.py +++ b/meta/build.py @@ -5,6 +5,7 @@ import argparse import isa import gen_instr +import gen_settings import gen_build_deps parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') @@ -16,4 +17,5 @@ out_dir = args.out_dir isas = isa.all_isas() gen_instr.generate(isas, out_dir) +gen_settings.generate(isas, out_dir) gen_build_deps.generate() diff --git a/meta/cretonne/__init__.py b/meta/cretonne/__init__.py index ee523cfec5..54f20cc678 100644 --- a/meta/cretonne/__init__.py +++ b/meta/cretonne/__init__.py @@ -18,6 +18,146 @@ def camel_case(s): return camel_re.sub(lambda m: m.group(2).upper(), s) +class Setting(object): + """ + A named setting variable that can be configured externally to Cretonne. + + Settings are normally not named when they are created. They get their name + from the `extract_names` method. + """ + + def __init__(self, doc): + self.name = None # Assigned later by `extract_names()`. + self.__doc__ = doc + # Offset of byte in settings vector containing this setting. + self.byte_offset = None + SettingGroup.append(self) + + @staticmethod + def extract_names(globs): + """ + Given a dict mapping name -> object as returned by `globals()`, find + all the Setting objects and set their name from the dict key. This is + used to name a bunch of global variables in a module. + """ + for name, obj in globs.iteritems(): + if isinstance(obj, Setting): + assert obj.name is None + obj.name = name + + +class BoolSetting(Setting): + """ + A named setting with a boolean on/off value. + + :param doc: Documentation string. + :param default: The default value of this setting. + """ + + def __init__(self, doc, default=False): + super(BoolSetting, self).__init__(doc) + self.default = default + + def default_byte(self): + """ + Get the default value of this setting, as a byte that can be bitwise + or'ed with the other booleans sharing the same byte. + """ + if self.default: + return 1 << self.bit_offset + else: + return 0 + + +class NumSetting(Setting): + """ + A named setting with an integral value in the range 0--255. + + :param doc: Documentation string. + :param default: The default value of this setting. + """ + + def __init__(self, doc, default=0): + super(NumSetting, self).__init__(doc) + assert default == int(default) + assert default >= 0 and default <= 255 + self.default = default + + def default_byte(self): + return self.default + + +class EnumSetting(Setting): + """ + A named setting with an enumerated set of possible values. + + The default value is always the first enumerator. + + :param doc: Documentation string. + :param args: Tuple of unique strings representing the possible values. + """ + + def __init__(self, doc, *args): + super(EnumSetting, self).__init__(doc) + assert len(args) > 0, "EnumSetting must have at least one value" + self.values = tuple(str(x) for x in args) + self.default = self.values[0] + + def default_byte(self): + return 0 + + +class SettingGroup(object): + """ + A group of settings. + + Whenever a :class:`Setting` object is created, it is added to the currently + open group. A setting group must be closed explicitly before another can be + opened. + + :param name: Short mnemonic name for setting group. + """ + + # The currently open setting group. + _current = None + + def __init__(self, name): + self.name = name + self.settings = [] + self.open() + + def open(self): + """ + Open this setting group such that future new settings are added to this + group. + """ + assert SettingGroup._current is None, ( + "Can't open {} since {} is already open" + .format(self, SettingGroup._current)) + SettingGroup._current = self + + def close(self, globs=None): + """ + Close this setting group. This function must be called before opening + another setting group. + + :param globs: Pass in `globals()` to run `extract_names` on all + settings defined in the module. + """ + assert SettingGroup._current is self, ( + "Can't close {}, the open setting group is {}" + .format(self, SettingGroup._current)) + SettingGroup._current = None + if globs: + Setting.extract_names(globs) + + @staticmethod + def append(setting): + assert SettingGroup._current, \ + "Open a setting group before defining settings." + SettingGroup._current.settings.append(setting) + + # Kinds of operands. # # Each instruction has an opcode and a number of operands. The opcode @@ -689,7 +829,7 @@ class BoundInstruction(object): assert len(typevars) <= 1 + len(inst.other_typevars) def __str__(self): - return '.'.join([self.inst.name,] + map(str, self.typevars)) + return '.'.join([self.inst.name, ] + map(str, self.typevars)) def bind(self, *args): """ @@ -711,7 +851,9 @@ class BoundInstruction(object): `(inst, typevars)` pair. """ if len(self.typevars) < 1 + len(self.inst.other_typevars): - unb = ', '.join(str(tv) for tv in self.inst.other_typevars[len(self.typevars) - 1:]) + unb = ', '.join( + str(tv) for tv in + self.inst.other_typevars[len(self.typevars) - 1:]) raise AssertionError("Unbound typevar {} in {}".format(unb, self)) assert len(self.typevars) == 1 + len(self.inst.other_typevars) return (self.inst, self.typevars) diff --git a/meta/cretonne/settings.py b/meta/cretonne/settings.py new file mode 100644 index 0000000000..f06c69bfef --- /dev/null +++ b/meta/cretonne/settings.py @@ -0,0 +1,13 @@ +""" +Cretonne shared settings. + +This module defines settings are are relevant for all code generators. +""" + +from . import SettingGroup, BoolSetting + +group = SettingGroup('shared') + +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 new file mode 100644 index 0000000000..accd739cf0 --- /dev/null +++ b/meta/gen_settings.py @@ -0,0 +1,104 @@ +""" +Generate sources with settings. +""" + +import srcgen +from cretonne import BoolSetting, NumSetting, settings + + +def layout_group(sgrp): + """ + Layout the settings in sgrp, assigning byte and bit offsets. + + Return the next unused byte offset. + """ + # Byte offset where booleans are allocated. + bool_byte = -1 + # Next available bit number in bool_byte. + bool_bit = 10 + # Next available whole byte. + next_byte = 0 + + for setting in sgrp.settings: + if isinstance(setting, BoolSetting): + # Allocate a bit from bool_byte. + if bool_bit > 7: + bool_byte = next_byte + next_byte += 1 + bool_bit = 0 + setting.byte_offset = bool_byte + setting.bit_offset = bool_bit + bool_bit += 1 + else: + # This is a numerical or enumerated setting. Allocate a single + # byte. + setting.byte_offset = next_byte + next_byte += 1 + + return next_byte + + +def gen_getter(setting, fmt): + """ + Emit a getter function for `setting`. + """ + fmt.doc_comment(setting.__doc__ + '.') + + if isinstance(setting, BoolSetting): + proto = 'pub fn {}(&self) -> bool'.format(setting.name) + with fmt.indented(proto + ' {', '}'): + fmt.line('(self.bytes[{}] & (1 << {})) != 0'.format( + setting.byte_offset, + setting.bit_offset)) + elif isinstance(setting, NumSetting): + proto = 'pub fn {}(&self) -> u8'.format(setting.name) + with fmt.indented(proto + ' {', '}'): + fmt.line('self.bytes[{}]'.format(setting.byte_offset)) + else: + raise AssertionError("Unknown setting kind") + + +def gen_getters(sgrp, fmt): + """ + Emit getter functions for all the settings in fmt. + """ + fmt.doc_comment("User-defined settings.") + with fmt.indented('impl Settings {', '}'): + for setting in sgrp.settings: + gen_getter(setting, fmt) + + +def gen_default(sgrp, byte_size, fmt): + """ + Emit a Default impl for Settings. + """ + v = [0] * byte_size + for setting in sgrp.settings: + v[setting.byte_offset] |= setting.default_byte() + + with fmt.indented('impl Default for Settings {', '}'): + fmt.doc_comment('Return a `Settings` object with default values.') + with fmt.indented('fn default() -> Settings {', '}'): + with fmt.indented('Settings {', '}'): + vs = ', '.join('{:#04x}'.format(x) for x in v) + fmt.line('bytes: [ {} ],'.format(vs)) + + +def gen_group(sgrp, fmt): + """ + Generate a Settings struct representing `sgrp`. + """ + byte_size = layout_group(sgrp) + + fmt.doc_comment('Settings group `{}`.'.format(sgrp.name)) + with fmt.indented('pub struct Settings {', '}'): + fmt.line('bytes: [u8; {}],'.format(byte_size)) + + gen_getters(sgrp, fmt) + gen_default(sgrp, byte_size, fmt) + + +def generate(isas, out_dir): + fmt = srcgen.Formatter() + gen_group(settings.group, fmt) + fmt.update_file('settings.rs', out_dir) diff --git a/src/libcretonne/lib.rs b/src/libcretonne/lib.rs index 9d696fab53..766c33abcf 100644 --- a/src/libcretonne/lib.rs +++ b/src/libcretonne/lib.rs @@ -13,5 +13,6 @@ pub mod write; pub mod cfg; pub mod dominator_tree; pub mod entity_map; +pub mod settings; #[cfg(test)]pub mod test_utils; diff --git a/src/libcretonne/settings.rs b/src/libcretonne/settings.rs new file mode 100644 index 0000000000..0b21bc2eb0 --- /dev/null +++ b/src/libcretonne/settings.rs @@ -0,0 +1,7 @@ +//! Shared settings module. +//! +//! This module defines data structures to access the settings defined in the meta language. + +// Include code generated by `meta/gen_settings.py`. This file contains a public `Settings` struct +// with an impl for all of the settings defined in `meta/cretonne/settings.py`. +include!(concat!(env!("OUT_DIR"), "/settings.rs"));