Scaffolding for defining settings.

Settings can be defined globally or per-ISA. They are available to code through
a generated Settings struct with accessor methods per setting.
This commit is contained in:
Jakob Stoklund Olesen
2016-08-05 13:38:43 -07:00
parent 6b69391289
commit cfeefde3fc
7 changed files with 310 additions and 2 deletions

View File

@@ -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 :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. 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 Instruction descriptions
======================== ========================

View File

@@ -5,6 +5,7 @@
import argparse import argparse
import isa import isa
import gen_instr import gen_instr
import gen_settings
import gen_build_deps import gen_build_deps
parser = argparse.ArgumentParser(description='Generate sources for Cretonne.') parser = argparse.ArgumentParser(description='Generate sources for Cretonne.')
@@ -16,4 +17,5 @@ out_dir = args.out_dir
isas = isa.all_isas() isas = isa.all_isas()
gen_instr.generate(isas, out_dir) gen_instr.generate(isas, out_dir)
gen_settings.generate(isas, out_dir)
gen_build_deps.generate() gen_build_deps.generate()

View File

@@ -18,6 +18,146 @@ def camel_case(s):
return camel_re.sub(lambda m: m.group(2).upper(), 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. # Kinds of operands.
# #
# Each instruction has an opcode and a number of operands. The opcode # 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) assert len(typevars) <= 1 + len(inst.other_typevars)
def __str__(self): 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): def bind(self, *args):
""" """
@@ -711,7 +851,9 @@ class BoundInstruction(object):
`(inst, typevars)` pair. `(inst, typevars)` pair.
""" """
if len(self.typevars) < 1 + len(self.inst.other_typevars): 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)) raise AssertionError("Unbound typevar {} in {}".format(unb, self))
assert len(self.typevars) == 1 + len(self.inst.other_typevars) assert len(self.typevars) == 1 + len(self.inst.other_typevars)
return (self.inst, self.typevars) return (self.inst, self.typevars)

13
meta/cretonne/settings.py Normal file
View File

@@ -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())

104
meta/gen_settings.py Normal file
View File

@@ -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)

View File

@@ -13,5 +13,6 @@ pub mod write;
pub mod cfg; pub mod cfg;
pub mod dominator_tree; pub mod dominator_tree;
pub mod entity_map; pub mod entity_map;
pub mod settings;
#[cfg(test)]pub mod test_utils; #[cfg(test)]pub mod test_utils;

View File

@@ -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"));