Allow for multiple legalization patterns for the same opcode.

Each input pattern can have a predicate in addition to an opcode being
matched. When an opcode has multiple patterns, execute the first pattern
with a true predicate.

The predicates can be type checks or instruction predicates checking
immediate fields.
This commit is contained in:
Jakob Stoklund Olesen
2017-07-28 11:06:09 -07:00
parent b5076108c1
commit 1f02d1f880
2 changed files with 73 additions and 71 deletions

View File

@@ -9,6 +9,7 @@ the input instruction.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from srcgen import Formatter from srcgen import Formatter
from collections import defaultdict
from base import instructions from base import instructions
from cdsl.ast import Var from cdsl.ast import Var
from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\ from cdsl.ti import ti_rtl, TypeEnv, get_type_env, TypesEqual,\
@@ -18,7 +19,7 @@ from gen_instr import gen_typesets_table
from cdsl.typevar import TypeVar from cdsl.typevar import TypeVar
try: try:
from typing import Sequence, List, Dict, Set # noqa from typing import Sequence, List, Dict, Set, DefaultDict # noqa
from cdsl.isa import TargetISA # noqa from cdsl.isa import TargetISA # noqa
from cdsl.ast import Def # noqa from cdsl.ast import Def # noqa
from cdsl.xform import XForm, XFormGroup # noqa from cdsl.xform import XForm, XFormGroup # noqa
@@ -78,6 +79,11 @@ def emit_runtime_typecheck(check, fmt, type_sets):
# type: (TypeConstraint, Formatter, UniqueTable) -> None # type: (TypeConstraint, Formatter, UniqueTable) -> None
""" """
Emit rust code for the given check. Emit rust code for the given check.
The emitted code is a statement redefining the `predicate` variable like
this:
let predicate = predicate && ...
""" """
def build_derived_expr(tv): def build_derived_expr(tv):
# type: (TypeVar) -> str # type: (TypeVar) -> str
@@ -116,33 +122,26 @@ def emit_runtime_typecheck(check, fmt, type_sets):
if check.ts not in type_sets.index: if check.ts not in type_sets.index:
type_sets.add(check.ts) type_sets.add(check.ts)
ts = type_sets.index[check.ts] ts = type_sets.index[check.ts]
fmt.comment("{} must belong to {}".format(tv, check.ts)) fmt.comment("{} must belong to {}".format(tv, check.ts))
with fmt.indented('if !TYPE_SETS[{}].contains({}) {{'.format(ts, tv), fmt.format(
'};'): 'let predicate = predicate && TYPE_SETS[{}].contains({});',
fmt.line('return false;') ts, tv)
elif (isinstance(check, TypesEqual)): elif (isinstance(check, TypesEqual)):
with fmt.indented('{', '};'): with fmt.indented(
fmt.line('let a = {};'.format(build_derived_expr(check.tv1))) 'let predicate = predicate && match ({}, {}) {{'
fmt.line('let b = {};'.format(build_derived_expr(check.tv2))) .format(build_derived_expr(check.tv1),
build_derived_expr(check.tv2)), '};'):
fmt.comment('On overflow constraint doesn\'t appply') fmt.line('(Some(a), Some(b)) => a == b,')
with fmt.indented('if a.is_none() || b.is_none() {', '};'): fmt.comment('On overflow, constraint doesn\'t appply')
fmt.line('return false;') fmt.line('_ => false,')
with fmt.indented('if a != b {', '};'):
fmt.line('return false;')
elif (isinstance(check, WiderOrEq)): elif (isinstance(check, WiderOrEq)):
with fmt.indented('{', '};'): with fmt.indented(
fmt.line('let a = {};'.format(build_derived_expr(check.tv1))) 'let predicate = predicate && match ({}, {}) {{'
fmt.line('let b = {};'.format(build_derived_expr(check.tv2))) .format(build_derived_expr(check.tv1),
build_derived_expr(check.tv2)), '};'):
fmt.comment('On overflow constraint doesn\'t appply') fmt.line('(Some(a), Some(b)) => a.wider_or_equal(b),')
with fmt.indented('if a.is_none() || b.is_none() {', '};'): fmt.comment('On overflow, constraint doesn\'t appply')
fmt.line('return false;') fmt.line('_ => false,')
with fmt.indented('if !a.wider_or_equal(b) {', '};'):
fmt.line('return false;')
else: else:
assert False, "Unknown check {}".format(check) assert False, "Unknown check {}".format(check)
@@ -216,14 +215,12 @@ def unwrap_inst(iref, node, fmt):
replace_inst = True replace_inst = True
else: else:
# Boring case: Detach the result values, capture them in locals. # Boring case: Detach the result values, capture them in locals.
fmt.comment('Detaching results.')
for d in node.defs: for d in node.defs:
fmt.line('let {};'.format(d)) fmt.line('let {};'.format(d))
with fmt.indented('{', '}'): with fmt.indented('{', '}'):
fmt.line('let r = dfg.inst_results(inst);') fmt.line('let r = dfg.inst_results(inst);')
for i in range(len(node.defs)): for i in range(len(node.defs)):
fmt.line('{} = r[{}];'.format(node.defs[i], i)) fmt.line('{} = r[{}];'.format(node.defs[i], i))
fmt.line('dfg.clear_results(inst);')
for d in node.defs: for d in node.defs:
if d.has_free_typevar(): if d.has_free_typevar():
fmt.line( fmt.line(
@@ -312,7 +309,7 @@ def emit_dst_inst(node, fmt):
def gen_xform(xform, fmt, type_sets): def gen_xform(xform, fmt, type_sets):
# type: (XForm, Formatter, UniqueTable) -> None # type: (XForm, Formatter, UniqueTable) -> None
""" """
Emit code for `xform`, assuming the the opcode of xform's root instruction Emit code for `xform`, assuming that the opcode of xform's root instruction
has already been matched. has already been matched.
`inst: Inst` is the variable to be replaced. It is pointed to by `pos: `inst: Inst` is the variable to be replaced. It is pointed to by `pos:
@@ -323,16 +320,24 @@ def gen_xform(xform, fmt, type_sets):
# variables. # variables.
replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt) replace_inst = unwrap_inst('inst', xform.src.rtl[0], fmt)
# We could support instruction predicates, but not yet. Should we just # Check instruction predicate and emit type checks.
# return false if it fails? What about multiple patterns with different
# predicates for the same opcode?
instp = xform.src.rtl[0].expr.inst_predicate() instp = xform.src.rtl[0].expr.inst_predicate()
assert instp is None, "Instruction predicates not supported in legalizer" # TODO: The instruction predicate should be evaluated with all the inst
# immediate fields available. Probably by unwrap_inst().
fmt.format('let predicate = {};',
instp.rust_predicate(0) if instp else 'true')
# Emit any runtime checks. # Emit any runtime checks.
for check in get_runtime_typechecks(xform): for check in get_runtime_typechecks(xform):
emit_runtime_typecheck(check, fmt, type_sets) emit_runtime_typecheck(check, fmt, type_sets)
# Guard the actual expansion by `predicate`.
with fmt.indented('if predicate {', '}'):
# If we're going to delete `inst`, we need to detach its results first
# so they can be reattached during pattern expansion.
if not replace_inst:
fmt.line('dfg.clear_results(inst);')
# Emit the destination pattern. # Emit the destination pattern.
for dst in xform.dst.rtl: for dst in xform.dst.rtl:
emit_dst_inst(dst, fmt) emit_dst_inst(dst, fmt)
@@ -341,6 +346,7 @@ def gen_xform(xform, fmt, type_sets):
# replace it. # replace it.
if not replace_inst: if not replace_inst:
fmt.line('assert_eq!(pos.remove_inst(), inst);') fmt.line('assert_eq!(pos.remove_inst(), inst);')
fmt.line('return true;')
def gen_xform_group(xgrp, fmt, type_sets): def gen_xform_group(xgrp, fmt, type_sets):
@@ -358,19 +364,27 @@ def gen_xform_group(xgrp, fmt, type_sets):
# pointing at an instruction. # pointing at an instruction.
fmt.line('let inst = pos.current_inst().expect("need instruction");') fmt.line('let inst = pos.current_inst().expect("need instruction");')
with fmt.indented('match dfg[inst].opcode() {', '}'): # Group the xforms by opcode so we can generate a big switch.
# Preserve ordering.
xforms = defaultdict(list) # type: DefaultDict[str, List[XForm]]
for xform in xgrp.xforms: for xform in xgrp.xforms:
inst = xform.src.rtl[0].expr.inst inst = xform.src.rtl[0].expr.inst
xforms[inst.camel_name].append(xform)
with fmt.indented('match dfg[inst].opcode() {', '}'):
for camel_name in sorted(xforms.keys()):
with fmt.indented( with fmt.indented(
'ir::Opcode::{} => {{'.format(inst.camel_name), '}'): 'ir::Opcode::{} => {{'.format(camel_name), '}'):
for xform in xforms[camel_name]:
gen_xform(xform, fmt, type_sets) gen_xform(xform, fmt, type_sets)
# We'll assume there are uncovered opcodes. # We'll assume there are uncovered opcodes.
fmt.line('_ => {},')
# If we fall through, nothing was expanded. Call the chain if any.
if xgrp.chain: if xgrp.chain:
fmt.format('_ => return {}(dfg, cfg, pos),', fmt.format('{}(dfg, cfg, pos)', xgrp.chain.rust_name())
xgrp.chain.rust_name())
else: else:
fmt.line('_ => return false,') fmt.line('false')
fmt.line('true')
def gen_isa(isa, fmt, shared_groups): def gen_isa(isa, fmt, shared_groups):

View File

@@ -49,39 +49,27 @@ def typeset_check(v, ts):
# type: (Var, TypeSet) -> CheckProducer # type: (Var, TypeSet) -> CheckProducer
return lambda typesets: format_check( return lambda typesets: format_check(
typesets, typesets,
'if !TYPE_SETS[{}].contains(typeof_{}) ' + 'let predicate = predicate && TYPE_SETS[{}].contains(typeof_{});\n',
'{{\n return false;\n}};\n', ts, v) ts, v)
def equiv_check(tv1, tv2): def equiv_check(tv1, tv2):
# type: (TypeVar, TypeVar) -> CheckProducer # type: (str, str) -> CheckProducer
return lambda typesets: format_check( return lambda typesets: format_check(
typesets, typesets,
'{{\n' + 'let predicate = predicate && match ({}, {}) {{\n'
' let a = {};\n' + ' (Some(a), Some(b)) => a == b,\n'
' let b = {};\n' + ' _ => false,\n'
' if a.is_none() || b.is_none() {{\n' +
' return false;\n' +
' }};\n' +
' if a != b {{\n' +
' return false;\n' +
' }};\n' +
'}};\n', tv1, tv2) '}};\n', tv1, tv2)
def wider_check(tv1, tv2): def wider_check(tv1, tv2):
# type: (TypeVar, TypeVar) -> CheckProducer # type: (str, str) -> CheckProducer
return lambda typesets: format_check( return lambda typesets: format_check(
typesets, typesets,
'{{\n' + 'let predicate = predicate && match ({}, {}) {{\n'
' let a = {};\n' + ' (Some(a), Some(b)) => a.wider_or_equal(b),\n'
' let b = {};\n' + ' _ => false,\n'
' if a.is_none() || b.is_none() {{\n' +
' return false;\n' +
' }};\n' +
' if !a.wider_or_equal(b) {{\n' +
' return false;\n' +
' }};\n' +
'}};\n', tv1, tv2) '}};\n', tv1, tv2)