compiler: maintain both the IR and iodelay forms of delay expressions.

After this commit, the delay instruction (again) does not generate
any LLVM IR: all heavy lifting is relegated to the delay and delay_mu
intrinsics. When the interleave transform needs to adjust the global
timeline, it synthesizes a delay_mu intrinsnic. This way,
the interleave transformation becomes composable, as the input and
the output IR invariants are the same.

Also, code generation is adjusted so that a basic block is split off
not only after a delay call, but also before one; otherwise, e.g.,
code immediately at the beginning of a `with parallel:` branch
would have no choice but to execute after another branch has already
advanced the timeline.

This takes care of issue #1 described in 50e7b44 and is a step
to solving issue #2.
This commit is contained in:
whitequark 2015-11-21 03:22:47 +08:00
parent 50e7b44d04
commit cb3b811fd7
7 changed files with 139 additions and 105 deletions

View File

@ -38,6 +38,12 @@ class TInt(types.TMono):
def one():
return 1
def TInt32():
return TInt(types.TValue(32))
def TInt64():
return TInt(types.TValue(64))
class TFloat(types.TMono):
def __init__(self):
super().__init__("float")

View File

@ -72,6 +72,16 @@ class Constant(Value):
return "{} {}".format(type_printer.name(self.type),
repr(self.value))
def __hash__(self):
return hash(self.value)
def __eq__(self, other):
return isinstance(other, Constant) and \
other.type == self.type and other.value == self.value
def __ne__(self, other):
return not (self == other)
class NamedValue(Value):
"""
An SSA value that has a name.
@ -190,7 +200,7 @@ class Instruction(User):
return ", ".join([operand.as_operand(type_printer) for operand in self.operands])
def as_entity(self, type_printer):
if builtins.is_none(self.type):
if builtins.is_none(self.type) and len(self.uses) == 0:
prefix = ""
else:
prefix = "%{} = {} ".format(escape_name(self.name),
@ -1198,35 +1208,46 @@ class Delay(Terminator):
:ivar expr: (:class:`iodelay.Expr`) expression
:ivar var_names: (list of string)
iodelay expression names corresponding to operands
iodelay variable names corresponding to operands
"""
"""
:param expr: (:class:`iodelay.Expr`) expression
:param substs: (dict of str to :class:`Value`)
SSA values corresponding to iodelay variable names
:param call: (:class:`Call` or ``Constant(None, builtins.TNone())``)
the call instruction that caused this delay, if any
:param target: (:class:`BasicBlock`) branch target
"""
def __init__(self, expr, substs, target, name=""):
def __init__(self, expr, substs, decomposition, target, name=""):
for var_name in substs: assert isinstance(var_name, str)
assert isinstance(decomposition, Call) or \
isinstance(decomposition, Builtin) and decomposition.op in ("delay", "delay_mu")
assert isinstance(target, BasicBlock)
super().__init__([target, *substs.values()], builtins.TNone(), name)
super().__init__([decomposition, target, *substs.values()], builtins.TNone(), name)
self.expr = expr
self.var_names = list(substs.keys())
def target(self):
def decomposition(self):
return self.operands[0]
def target(self):
return self.operands[1]
def set_target(self, new_target):
self.operands[0] = new_target
self.operands[1] = new_target
def substs(self):
return zip(self.var_names, self.operands[1:])
return zip(self.var_names, self.operands[2:])
def _operands_as_string(self, type_printer):
substs = []
for var_name, operand in self.substs():
substs.append("{}={}".format(var_name, operand))
return "[{}], {}".format(", ".join(substs), self.target().as_operand(type_printer))
result = "[{}]".format(", ".join(substs))
result += ", decomp {}, to {}".format(self.decomposition().as_operand(type_printer),
self.target().as_operand(type_printer))
return result
def opcode(self):
return "delay({})".format(self.expr)

View File

@ -1418,15 +1418,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
return self.append(ir.Alloc(attributes, typ))
def _make_delay(self, delay):
if not iodelay.is_const(delay, 0):
after_delay = self.add_block()
self.append(ir.Delay(delay,
{var_name: self.current_args[var_name]
for var_name in delay.free_vars()},
after_delay))
self.current_block = after_delay
def visit_builtin_call(self, node):
# A builtin by any other name... Ignore node.func, just use the type.
typ = node.func.type
@ -1529,7 +1520,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
return self.append(ir.Arith(ast.Mult(loc=None), now_mu_float, self.ref_period))
else:
assert False
elif types.is_builtin(typ, "at"):
elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "at"):
if len(node.args) == 1 and len(node.keywords) == 0:
arg = self.visit(node.args[0])
arg_mu_float = self.append(ir.Arith(ast.Div(loc=None), arg, self.ref_period))
@ -1537,7 +1528,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.append(ir.Builtin(typ.name + "_mu", [arg_mu], builtins.TNone()))
else:
assert False
elif types.is_builtin(typ, "now_mu") or types.is_builtin(typ, "at_mu"):
elif types.is_builtin(typ, "now_mu") or types.is_builtin(typ, "delay_mu") \
or types.is_builtin(typ, "at_mu"):
return self.append(ir.Builtin(typ.name,
[self.visit(arg) for arg in node.args], node.type))
elif types.is_builtin(typ, "mu_to_seconds"):
@ -1554,9 +1546,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
return self.append(ir.Coerce(arg_mu, builtins.TInt(types.TValue(64))))
else:
assert False
elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "delay_mu"):
assert node.iodelay is not None
self._make_delay(node.iodelay)
elif types.is_exn_constructor(typ):
return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
elif types.is_constructor(typ):
@ -1567,77 +1556,82 @@ class ARTIQIRGenerator(algorithm.Visitor):
def visit_CallT(self, node):
typ = node.func.type.find()
if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0):
before_delay = self.current_block
during_delay = self.add_block()
before_delay.append(ir.Branch(during_delay))
self.current_block = during_delay
if types.is_builtin(typ):
return self.visit_builtin_call(node)
if types.is_function(typ):
func = self.visit(node.func)
self_arg = None
fn_typ = typ
offset = 0
elif types.is_method(typ):
method = self.visit(node.func)
func = self.append(ir.GetAttr(method, "__func__"))
self_arg = self.append(ir.GetAttr(method, "__self__"))
fn_typ = types.get_method_function(typ)
offset = 1
insn = self.visit_builtin_call(node)
else:
assert False
args = [None] * (len(fn_typ.args) + len(fn_typ.optargs))
for index, arg_node in enumerate(node.args):
arg = self.visit(arg_node)
if index < len(fn_typ.args):
args[index + offset] = arg
if types.is_function(typ):
func = self.visit(node.func)
self_arg = None
fn_typ = typ
offset = 0
elif types.is_method(typ):
method = self.visit(node.func)
func = self.append(ir.GetAttr(method, "__func__"))
self_arg = self.append(ir.GetAttr(method, "__self__"))
fn_typ = types.get_method_function(typ)
offset = 1
else:
args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type)))
assert False
for keyword in node.keywords:
arg = self.visit(keyword.value)
if keyword.arg in fn_typ.args:
for index, arg_name in enumerate(fn_typ.args):
if keyword.arg == arg_name:
assert args[index] is None
args[index] = arg
break
elif keyword.arg in fn_typ.optargs:
for index, optarg_name in enumerate(fn_typ.optargs):
if keyword.arg == optarg_name:
assert args[len(fn_typ.args) + index] is None
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([arg], ir.TOption(arg.type)))
break
args = [None] * (len(fn_typ.args) + len(fn_typ.optargs))
for index, optarg_name in enumerate(fn_typ.optargs):
if args[len(fn_typ.args) + index] is None:
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name])))
for index, arg_node in enumerate(node.args):
arg = self.visit(arg_node)
if index < len(fn_typ.args):
args[index + offset] = arg
else:
args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type)))
if self_arg is not None:
assert args[0] is None
args[0] = self_arg
for keyword in node.keywords:
arg = self.visit(keyword.value)
if keyword.arg in fn_typ.args:
for index, arg_name in enumerate(fn_typ.args):
if keyword.arg == arg_name:
assert args[index] is None
args[index] = arg
break
elif keyword.arg in fn_typ.optargs:
for index, optarg_name in enumerate(fn_typ.optargs):
if keyword.arg == optarg_name:
assert args[len(fn_typ.args) + index] is None
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([arg], ir.TOption(arg.type)))
break
assert None not in args
for index, optarg_name in enumerate(fn_typ.optargs):
if args[len(fn_typ.args) + index] is None:
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name])))
if self.unwind_target is None:
insn = self.append(ir.Call(func, args))
else:
after_invoke = self.add_block()
insn = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target))
self.current_block = after_invoke
if self_arg is not None:
assert args[0] is None
args[0] = self_arg
method_key = None
if isinstance(node.func, asttyped.AttributeT):
attr_node = node.func
self.method_map[(attr_node.value.type, attr_node.attr)].append(insn)
assert None not in args
if self.unwind_target is None:
insn = self.append(ir.Call(func, args))
else:
after_invoke = self.add_block()
insn = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target))
self.current_block = after_invoke
method_key = None
if isinstance(node.func, asttyped.AttributeT):
attr_node = node.func
self.method_map[(attr_node.value.type, attr_node.attr)].append(insn)
if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0):
after_delay = self.add_block()
self.append(ir.Delay(node.iodelay,
{var_name: self.current_args[var_name]
for var_name in node.iodelay.free_vars()},
after_delay))
substs = {var_name: self.current_args[var_name]
for var_name in node.iodelay.free_vars()}
self.append(ir.Delay(node.iodelay, substs, insn, after_delay))
self.current_block = after_delay
return insn

View File

@ -3,7 +3,7 @@
the timestamp would always monotonically nondecrease.
"""
from .. import ir, iodelay
from .. import types, builtins, ir, iodelay
from ..analyses import domination
def delay_free_subgraph(root, limit):
@ -81,20 +81,39 @@ class Interleaver:
target_time_delta = new_target_time - target_time
target_terminator = target_block.terminator()
if isinstance(target_terminator, (ir.Delay, ir.Branch)):
target_terminator.set_target(source_block)
elif isinstance(target_terminator, ir.Parallel):
if isinstance(target_terminator, ir.Parallel):
target_terminator.replace_with(ir.Branch(source_block))
else:
assert False
assert isinstance(target_terminator, (ir.Delay, ir.Branch))
target_terminator.set_target(source_block)
source_terminator = source_block.terminator()
if isinstance(source_terminator, ir.Delay):
old_decomp = source_terminator.decomposition()
else:
old_decomp = None
if target_time_delta > 0:
assert isinstance(source_terminator, ir.Delay)
source_terminator.expr = iodelay.Const(target_time_delta)
if isinstance(old_decomp, ir.Builtin) and \
old_decomp.op in ("delay", "delay_mu"):
new_decomp_expr = ir.Constant(target_time_delta, builtins.TInt64())
new_decomp = ir.Builtin("delay_mu", [new_decomp_expr], builtins.TNone())
new_decomp.loc = old_decomp.loc
source_terminator.basic_block.insert(source_terminator, new_decomp)
else:
assert False # TODO
source_terminator.replace_with(ir.Delay(iodelay.Const(target_time_delta), {},
new_decomp, source_terminator.target()))
else:
source_terminator.replace_with(ir.Branch(source_terminator.target()))
if old_decomp is not None:
old_decomp.erase()
target_block = source_block
target_time = new_target_time

View File

@ -7,7 +7,7 @@ import os
from pythonparser import ast, diagnostic
from llvmlite_artiq import ir as ll
from ...language import core as language_core
from .. import types, builtins, ir, iodelay
from .. import types, builtins, ir
llvoid = ll.VoidType()
@ -787,6 +787,12 @@ class LLVMIRGenerator:
elif insn.op == "at_mu":
time, = insn.operands
return self.llbuilder.store(self.map(time), self.llbuiltin("now"))
elif insn.op == "delay_mu":
interval, = insn.operands
llnowptr = self.llbuiltin("now")
llnow = self.llbuilder.load(llnowptr)
lladjusted = self.llbuilder.add(llnow, self.map(interval))
return self.llbuilder.store(lladjusted, llnowptr)
else:
assert False
@ -1062,6 +1068,8 @@ class LLVMIRGenerator:
def process_Branch(self, insn):
return self.llbuilder.branch(self.map(insn.target()))
process_Delay = process_Branch
def process_BranchIf(self, insn):
return self.llbuilder.cbranch(self.map(insn.condition()),
self.map(insn.if_true()), self.map(insn.if_false()))
@ -1141,17 +1149,3 @@ class LLVMIRGenerator:
self.llbuilder.branch(self.map(insn.cleanup()))
return llexn
def process_Delay(self, insn):
def map_delay(expr):
if isinstance(expr, iodelay.Const):
return ll.Constant(lli64, int(expr.value))
else:
assert False
llnowptr = self.llbuiltin("now")
llnow = self.llbuilder.load(llnowptr)
lladjusted = self.llbuilder.add(llnow, map_delay(insn.expr))
self.llbuilder.store(lladjusted, llnowptr)
return self.llbuilder.branch(self.map(insn.target()))

View File

@ -18,8 +18,8 @@ def g():
print("E", now_mu())
# CHECK-L: A 0
# CHECK-L: C 0
# CHECK-L: B 2
# CHECK-L: C 2
# CHECK-L: D 2
# CHECK-L: E 4
g()

View File

@ -17,9 +17,9 @@ def g():
#
print("E", now_mu())
# CHECK-L: A 0
# CHECK-L: C 0
# CHECK-L: A 2
# CHECK-L: D 2
# CHECK-L: B 3
# CHECK-L: D 3
# CHECK-L: E 4
g()