Files
wasmtime/lib/cretonne/meta/gen_instr.py
Jakob Stoklund Olesen f8545574b5 Move ValueType into a new cdsl top-level module.
We want to separate the Python classes that make up the DSL used to
define the Cretonne language from the concrete definitions.

- cdsl.types defines the ValueType class hierarchy.
- base.types defines the concrete types.
2016-11-08 10:12:47 -08:00

633 lines
24 KiB
Python

"""
Generate sources with instruction info.
"""
from __future__ import absolute_import
import srcgen
import constant_hash
from unique_table import UniqueTable, UniqueSeqTable
import cdsl.types
import cretonne
def gen_formats(fmt):
# type: (srcgen.Formatter) -> None
"""Generate an instruction format enumeration"""
fmt.doc_comment('An instruction format')
fmt.doc_comment('')
fmt.doc_comment('Every opcode has a corresponding instruction format')
fmt.doc_comment('which is represented by both the `InstructionFormat`')
fmt.doc_comment('and the `InstructionData` enums.')
fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]')
with fmt.indented('pub enum InstructionFormat {', '}'):
for f in cretonne.InstructionFormat.all_formats:
fmt.doc_comment(str(f))
fmt.line(f.name + ',')
fmt.line()
# Emit a From<InstructionData> which also serves to verify that
# InstructionFormat and InstructionData are in sync.
with fmt.indented(
"impl<'a> From<&'a InstructionData> for InstructionFormat {", '}'):
with fmt.indented(
"fn from(inst: &'a InstructionData) -> InstructionFormat {",
'}'):
with fmt.indented('match *inst {', '}'):
for f in cretonne.InstructionFormat.all_formats:
fmt.line(('InstructionData::{} {{ .. }} => ' +
'InstructionFormat::{},')
.format(f.name, f.name))
fmt.line()
def gen_arguments_method(fmt, is_mut):
# type: (srcgen.Formatter, bool) -> None
method = 'arguments'
mut = ''
rslice = 'ref_slice'
if is_mut:
method += '_mut'
mut = 'mut '
rslice += '_mut'
with fmt.indented(
'pub fn {f}(&{m}self) -> [&{m}[Value]; 2] {{'
.format(f=method, m=mut), '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
n = 'InstructionData::' + f.name
has_varargs = cretonne.variable_args in f.kinds
# Formats with both fixed and variable arguments delegate to
# the data struct. We need to work around borrow checker quirks
# when extracting two mutable references.
if has_varargs and len(f.value_operands) > 0:
fmt.line(
'{} {{ ref {}data, .. }} => data.{}(),'
.format(n, mut, method))
continue
# Fixed args.
if len(f.value_operands) == 0:
arg = '&{}[]'.format(mut)
capture = ''
elif len(f.value_operands) == 1:
if f.boxed_storage:
capture = 'ref {}data, '.format(mut)
arg = '{}(&{}data.arg)'.format(rslice, mut)
else:
capture = 'ref {}arg, '.format(mut)
arg = '{}(arg)'.format(rslice)
else:
if f.boxed_storage:
capture = 'ref {}data, '.format(mut)
arg = '&{}data.args'.format(mut)
else:
capture = 'ref {}args, '.format(mut)
arg = 'args'
# Varargs.
if cretonne.variable_args in f.kinds:
varg = '&{}data.varargs'.format(mut)
capture = 'ref {}data, '.format(mut)
else:
varg = '&{}[]'.format(mut)
fmt.line(
'{} {{ {} .. }} => [{}, {}],'
.format(n, capture, arg, varg))
def gen_instruction_data_impl(fmt):
# type: (srcgen.Formatter) -> None
"""
Generate the boring parts of the InstructionData implementation.
These methods in `impl InstructionData` can be generated automatically from
the instruction formats:
- `pub fn opcode(&self) -> Opcode`
- `pub fn first_type(&self) -> Type`
- `pub fn second_result(&self) -> Option<Value>`
- `pub fn second_result_mut<'a>(&'a mut self) -> Option<&'a mut Value>`
- `pub fn arguments(&self) -> (&[Value], &[Value])`
"""
# The `opcode` and `first_type` methods simply read the `opcode` and `ty`
# members. This is really a workaround for Rust's enum types missing shared
# members.
with fmt.indented('impl InstructionData {', '}'):
fmt.doc_comment('Get the opcode of this instruction.')
with fmt.indented('pub fn opcode(&self) -> Opcode {', '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
fmt.line(
'InstructionData::{} {{ opcode, .. }} => opcode,'
.format(f.name))
fmt.doc_comment('Type of the first result, or `VOID`.')
with fmt.indented('pub fn first_type(&self) -> Type {', '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
fmt.line(
'InstructionData::{} {{ ty, .. }} => ty,'
.format(f.name))
fmt.doc_comment('Mutable reference to the type of the first result.')
with fmt.indented(
'pub fn first_type_mut(&mut self) -> &mut Type {', '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
fmt.line(
'InstructionData::{} {{ ref mut ty, .. }} => ty,'
.format(f.name))
# Generate shared and mutable accessors for `second_result` which only
# applies to instruction formats that can produce multiple results.
# Everything else returns `None`.
fmt.doc_comment('Second result value, if any.')
with fmt.indented(
'pub fn second_result(&self) -> Option<Value> {', '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
if f.multiple_results:
fmt.line(
'InstructionData::' + f.name +
' { second_result, .. }' +
' => Some(second_result),')
else:
# Single or no results.
fmt.line(
'InstructionData::{} {{ .. }} => None,'
.format(f.name))
fmt.doc_comment('Mutable reference to second result value, if any.')
with fmt.indented(
"pub fn second_result_mut<'a>(&'a mut self)" +
" -> Option<&'a mut Value> {", '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
if f.multiple_results:
fmt.line(
'InstructionData::' + f.name +
' { ref mut second_result, .. }' +
' => Some(second_result),')
else:
# Single or no results.
fmt.line(
'InstructionData::{} {{ .. }} => None,'
.format(f.name))
fmt.doc_comment('Get the controlling type variable operand.')
with fmt.indented(
'pub fn typevar_operand(&self) -> Option<Value> {', '}'):
with fmt.indented('match *self {', '}'):
for f in cretonne.InstructionFormat.all_formats:
n = 'InstructionData::' + f.name
if f.typevar_operand is None:
fmt.line(n + ' { .. } => None,')
elif len(f.value_operands) == 1:
# We have a single value operand called 'arg'.
if f.boxed_storage:
fmt.line(
n + ' { ref data, .. } => Some(data.arg),')
else:
fmt.line(n + ' { arg, .. } => Some(arg),')
else:
# We have multiple value operands and an array `args`.
# Which `args` index to use?
# Map from index into f.kinds into f.value_operands
# index.
i = f.value_operands.index(f.typevar_operand)
if f.boxed_storage:
fmt.line(
n +
' { ref data, .. } => ' +
('Some(data.args[{}]),'.format(i)))
else:
fmt.line(
n +
' {{ ref args, .. }} => Some(args[{}]),'
.format(i))
fmt.doc_comment(
"""
Get the value arguments to this instruction.
This is returned as two `Value` slices. The first one
represents the fixed arguments, the second any variable
arguments.
""")
gen_arguments_method(fmt, False)
fmt.doc_comment(
"""
Get mutable references to the value arguments to this
instruction.
""")
gen_arguments_method(fmt, True)
def collect_instr_groups(isas):
seen = set()
groups = []
for isa in isas:
for g in isa.instruction_groups:
if g not in seen:
groups.append(g)
seen.add(g)
return groups
def gen_opcodes(groups, fmt):
"""
Generate opcode enumerations.
Return a list of all instructions.
"""
fmt.doc_comment('An instruction opcode.')
fmt.doc_comment('')
fmt.doc_comment('All instructions from all supported ISAs are present.')
fmt.line('#[derive(Copy, Clone, PartialEq, Eq, Debug)]')
instrs = []
with fmt.indented('pub enum Opcode {', '}'):
fmt.doc_comment('An invalid opcode.')
fmt.line('NotAnOpcode,')
for g in groups:
for i in g.instructions:
instrs.append(i)
i.number = len(instrs)
fmt.doc_comment('`{}`. ({})'.format(i, i.format.name))
# Document polymorphism.
if i.is_polymorphic:
if i.use_typevar_operand:
fmt.doc_comment(
'Type inferred from {}.'
.format(i.ins[i.format.typevar_operand]))
# Enum variant itself.
fmt.line(i.camel_name + ',')
fmt.line()
# Generate a private opcode_format table.
with fmt.indented(
'const OPCODE_FORMAT: [InstructionFormat; {}] = ['
.format(len(instrs)),
'];'):
for i in instrs:
fmt.format(
'InstructionFormat::{}, // {}',
i.format.name, i.name)
fmt.line()
# Generate a private opcode_name function.
with fmt.indented('fn opcode_name(opc: Opcode) -> &\'static str {', '}'):
with fmt.indented('match opc {', '}'):
fmt.line('Opcode::NotAnOpcode => "<not an opcode>",')
for i in instrs:
fmt.format('Opcode::{} => "{}",', i.camel_name, i.name)
fmt.line()
# Generate an opcode hash table for looking up opcodes by name.
hash_table = constant_hash.compute_quadratic(
instrs,
lambda i: constant_hash.simple_hash(i.name))
with fmt.indented(
'const OPCODE_HASH_TABLE: [Opcode; {}] = ['
.format(len(hash_table)), '];'):
for i in hash_table:
if i is None:
fmt.line('Opcode::NotAnOpcode,')
else:
fmt.format('Opcode::{},', i.camel_name)
fmt.line()
return instrs
def get_constraint(op, ctrl_typevar, type_sets):
"""
Get the value type constraint for an SSA value operand, where
`ctrl_typevar` is the controlling type variable.
Each operand constraint is represented as a string, one of:
- `Concrete(vt)`, where `vt` is a value type name.
- `Free(idx)` where `idx` is an index into `type_sets`.
- `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints.
"""
assert op.kind is cretonne.value
t = op.typ
# A concrete value type.
if isinstance(t, cdsl.types.ValueType):
return 'Concrete({})'.format(t.rust_name())
if t.free_typevar() is not ctrl_typevar:
assert not t.is_derived
return 'Free({})'.format(type_sets.add(t.type_set))
if t.is_derived:
assert t.base is ctrl_typevar, "Not derived directly from ctrl_typevar"
return t.derived_func
assert t is ctrl_typevar
return 'Same'
def gen_type_constraints(fmt, instrs):
"""
Generate value type constraints for all instructions.
- Emit a compact constant table of ValueTypeSet objects.
- Emit a compact constant table of OperandConstraint objects.
- Emit an opcode-indexed table of instruction constraints.
"""
# Table of TypeSet instances.
type_sets = UniqueTable()
# Table of operand constraint sequences (as tuples). Each operand
# constraint is represented as a string, one of:
# - `Concrete(vt)`, where `vt` is a value type name.
# - `Free(idx)` where `idx` isan index into `type_sets`.
# - `Same`, `Lane`, `AsBool` for controlling typevar-derived constraints.
operand_seqs = UniqueSeqTable()
# Preload table with constraints for typical binops.
operand_seqs.add(['Same'] * 3)
# TypeSet indexes are encoded in 8 bits, with `0xff` reserved.
typeset_limit = 0xff
fmt.comment('Table of opcode constraints.')
with fmt.indented(
'const OPCODE_CONSTRAINTS : [OpcodeConstraints; {}] = ['
.format(len(instrs)), '];'):
for i in instrs:
# Collect constraints for the value results, not including
# `variable_args` results which are always special cased.
constraints = list()
ctrl_typevar = None
ctrl_typeset = typeset_limit
if i.is_polymorphic:
ctrl_typevar = i.ctrl_typevar
ctrl_typeset = type_sets.add(ctrl_typevar.type_set)
for idx in i.value_results:
constraints.append(
get_constraint(i.outs[idx], ctrl_typevar, type_sets))
for idx in i.format.value_operands:
constraints.append(
get_constraint(i.ins[idx], ctrl_typevar, type_sets))
offset = operand_seqs.add(constraints)
fixed_results = len(i.value_results)
use_typevar_operand = i.is_polymorphic and i.use_typevar_operand
fmt.comment(
'{}: fixed_results={}, use_typevar_operand={}'
.format(i.camel_name, fixed_results, use_typevar_operand))
fmt.comment('Constraints={}'.format(constraints))
if i.is_polymorphic:
fmt.comment(
'Polymorphic over {}'.format(ctrl_typevar.type_set))
# Compute the bit field encoding, c.f. instructions.rs.
assert fixed_results < 8, "Bit field encoding too tight"
flags = fixed_results
if use_typevar_operand:
flags |= 8
with fmt.indented('OpcodeConstraints {', '},'):
fmt.line('flags: {:#04x},'.format(flags))
fmt.line('typeset_offset: {},'.format(ctrl_typeset))
fmt.line('constraint_offset: {},'.format(offset))
fmt.comment('Table of value type sets.')
assert len(type_sets.table) <= typeset_limit, "Too many type sets"
with fmt.indented(
'const TYPE_SETS : [ValueTypeSet; {}] = ['
.format(len(type_sets.table)), '];'):
for ts in type_sets.table:
with fmt.indented('ValueTypeSet {', '},'):
ts.emit_fields(fmt)
fmt.comment('Table of operand constraint sequences.')
with fmt.indented(
'const OPERAND_CONSTRAINTS : [OperandConstraint; {}] = ['
.format(len(operand_seqs.table)), '];'):
for c in operand_seqs.table:
fmt.line('OperandConstraint::{},'.format(c))
def gen_format_constructor(iform, fmt):
"""
Emit a method for creating and inserting inserting an `iform` instruction,
where `iform` is an instruction format.
Instruction formats that can produce multiple results take a `ctrl_typevar`
argument for deducing the result types. Others take a `result_type`
argument.
"""
# Construct method arguments.
args = ['self', 'opcode: Opcode']
if iform.multiple_results:
args.append('ctrl_typevar: Type')
# `dfg::make_inst_results` will compute the result type.
result_type = 'types::VOID'
else:
args.append('result_type: Type')
result_type = 'result_type'
# Normal operand arguments.
for idx, kind in enumerate(iform.kinds):
args.append('op{}: {}'.format(idx, kind.rust_type))
proto = '{}({})'.format(iform.name, ', '.join(args))
proto += " -> (Inst, &'f mut DataFlowGraph)"
fmt.doc_comment(str(iform))
fmt.line('#[allow(non_snake_case)]')
with fmt.indented('fn {} {{'.format(proto), '}'):
# Generate the instruction data.
with fmt.indented(
'let data = InstructionData::{} {{'.format(iform.name), '};'):
fmt.line('opcode: opcode,')
fmt.line('ty: {},'.format(result_type))
if iform.multiple_results:
fmt.line('second_result: Value::default(),')
if iform.boxed_storage:
with fmt.indented(
'data: Box::new(instructions::{}Data {{'
.format(iform.name), '}),'):
gen_member_inits(iform, fmt)
else:
gen_member_inits(iform, fmt)
# Create result values if necessary.
if iform.multiple_results:
fmt.line('self.complex_instruction(data, ctrl_typevar)')
else:
fmt.line('self.simple_instruction(data)')
def gen_member_inits(iform, fmt):
"""
Emit member initializers for an `iform` instruction.
"""
# Values first.
if len(iform.value_operands) == 1:
fmt.line('arg: op{},'.format(iform.value_operands[0]))
elif len(iform.value_operands) > 1:
fmt.line('args: [{}],'.format(
', '.join('op{}'.format(i) for i in iform.value_operands)))
# Immediates and entity references.
for idx, member in enumerate(iform.members):
if member:
fmt.line('{}: op{},'.format(member, idx))
def gen_inst_builder(inst, fmt):
"""
Emit a method for generating the instruction `inst`.
The method will create and insert an instruction, then return the result
values, or the instruction reference itself for instructions that don't
have results.
"""
# Construct method arguments.
args = ['self']
# The controlling type variable will be inferred from the input values if
# possible. Otherwise, it is the first method argument.
if inst.is_polymorphic and not inst.use_typevar_operand:
args.append('{}: Type'.format(inst.ctrl_typevar.name))
tmpl_types = list()
into_args = list()
for op in inst.ins:
if isinstance(op.kind, cretonne.ImmediateKind):
t = 'T{}{}'.format(1 + len(tmpl_types), op.kind.name)
tmpl_types.append('{}: Into<{}>'.format(t, op.kind.rust_type))
into_args.append(op.name)
else:
t = op.kind.rust_type
args.append('{}: {}'.format(op.name, t))
# Return the inst reference for result-less instructions.
if len(inst.value_results) == 0:
rtype = 'Inst'
elif len(inst.value_results) == 1:
rtype = 'Value'
else:
rvals = ', '.join(len(inst.value_results) * ['Value'])
rtype = '({})'.format(rvals)
if len(tmpl_types) > 0:
tmpl = '<{}>'.format(', '.join(tmpl_types))
else:
tmpl = ''
proto = '{}{}({}) -> {}'.format(
inst.snake_name(), tmpl, ', '.join(args), rtype)
fmt.doc_comment('`{}`\n\n{}'.format(inst, inst.blurb()))
fmt.line('#[allow(non_snake_case)]')
with fmt.indented('fn {} {{'.format(proto), '}'):
# Convert all of the `Into<>` arguments.
for arg in into_args:
fmt.line('let {} = {}.into();'.format(arg, arg))
# Arguments for instruction constructor.
args = ['Opcode::' + inst.camel_name]
if inst.is_polymorphic and not inst.use_typevar_operand:
# This was an explicit method argument.
args.append(inst.ctrl_typevar.name)
elif len(inst.value_results) == 0:
args.append('types::VOID')
elif inst.is_polymorphic:
# Infer the controlling type variable from the input operands.
fmt.line(
'let ctrl_typevar = self.data_flow_graph().value_type({});'
.format(inst.ins[inst.format.typevar_operand].name))
if inst.format.multiple_results:
# The format constructor will resolve the result types from the
# type var.
args.append('ctrl_typevar')
elif inst.outs[inst.value_results[0]].typ == inst.ctrl_typevar:
# The format constructor expects a simple result type.
# No type transformation needed from the controlling type
# variable.
args.append('ctrl_typevar')
else:
# The format constructor expects a simple result type.
# TODO: This formula could be resolved ahead of time.
args.append(
'Opcode::{}.constraints().result_type(0, ctrl_typevar)'
.format(inst.camel_name))
else:
# This non-polymorphic instruction has a fixed result type.
args.append(
'types::' +
inst.outs[inst.value_results[0]].typ.name.upper())
args.extend(op.name for op in inst.ins)
args = ', '.join(args)
# Call to the format constructor,
fcall = 'self.{}({})'.format(inst.format.name, args)
if len(inst.value_results) == 0:
fmt.line(fcall + '.0')
return
if len(inst.value_results) == 1:
fmt.line('Value::new_direct({}.0)'.format(fcall))
return
fmt.line('let (inst, dfg) = {};'.format(fcall))
fmt.line('let mut results = dfg.inst_results(inst);')
fmt.line('({})'.format(', '.join(
len(inst.value_results) * ['results.next().unwrap()'])))
def gen_builder(insts, fmt):
"""
Generate a Builder trait with methods for all instructions.
"""
fmt.doc_comment("""
Convenience methods for building instructions.
The `InstrBuilder` trait has one method per instruction opcode for
conveniently constructing the instruction with minimum arguments.
Polymorphic instructions infer their result types from the input
arguments when possible. In some cases, an explicit `result_type`
or `ctrl_typevar` argument is required.
The opcode methods return the new instruction's result values, or
the `Inst` itself for instructions that don't have any results.
There is also a method per instruction format. These methods all
return an `Inst`.
""")
with fmt.indented(
"pub trait InstBuilder<'f>: InstBuilderBase<'f> {", '}'):
for inst in insts:
gen_inst_builder(inst, fmt)
for f in cretonne.InstructionFormat.all_formats:
gen_format_constructor(f, fmt)
def generate(isas, out_dir):
groups = collect_instr_groups(isas)
# opcodes.rs
fmt = srcgen.Formatter()
gen_formats(fmt)
gen_instruction_data_impl(fmt)
instrs = gen_opcodes(groups, fmt)
gen_type_constraints(fmt, instrs)
fmt.update_file('opcodes.rs', out_dir)
# builder.rs
fmt = srcgen.Formatter()
gen_builder(instrs, fmt)
fmt.update_file('builder.rs', out_dir)