Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

internal: move MIR generation DSL to mirconstr #793

Merged
merged 9 commits into from
Jul 12, 2023
14 changes: 4 additions & 10 deletions compiler/mir/mirbridge.nim
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,8 @@ proc rewriteGlobalDefs*(body: var MirTree, sourceMap: var SourceMap,
buf.add MirNode(kind: mnkTemp, temp: tmp, typ: typ)

argBlock(buf):
buf.add MirNode(kind: mnkGlobal, sym: sym, typ: typ)
buf.add MirNode(kind: mnkTag, effect: ekReassign, typ: typ)
buf.add MirNode(kind: mnkName, typ: typ)
buf.add MirNode(kind: mnkTemp, temp: tmp, typ: typ)
buf.add MirNode(kind: mnkConsume, typ: typ)
chain(buf): symbol(mnkGlobal, sym) => tag(ekReassign) => name()
chain(buf): temp(typ, tmp) => consume()
buf.add MirNode(kind: mnkInit)
elif {sfImportc, sfNoInit} * sym.flags == {} and
{lfDynamicLib, lfNoDecl} * sym.locFlags == {}:
Expand All @@ -132,12 +129,9 @@ proc rewriteGlobalDefs*(body: var MirTree, sourceMap: var SourceMap,
# it to the type's default value.
changes.replaceMulti(buf):
argBlock(buf):
buf.add MirNode(kind: mnkGlobal, sym: sym, typ: typ)
buf.add MirNode(kind: mnkTag, effect: ekReassign, typ: typ)
buf.add MirNode(kind: mnkName, typ: typ)
chain(buf): symbol(mnkGlobal, sym) => tag(ekReassign) => name()
argBlock(buf): discard
buf.add MirNode(kind: mnkMagic, magic: mDefault, typ: typ)
buf.add MirNode(kind: mnkConsume, typ: typ)
chain(buf): magicCall(mDefault, typ) => consume()
buf.add MirNode(kind: mnkInit)
else:
# just remove the def:
Expand Down
310 changes: 295 additions & 15 deletions compiler/mir/mirconstr.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
## This module contains constructor procedures for the different kinds of
## ``MirNode``s plus convenience procedures for generating expressions.
## This module implements a small domain-specific language (=DSL) for
## constructing sequences of MIR code, and constructor routines for
## ``MirNode``s.
##
## Chain DSL
## ---------
##
## The idea is to provide a simple DSL that makes generating MIR code more
## ergonomic than manually adding nodes to a buffer. It is entirely realized
## via templates, and looks like this:
##
## .. code-block:: nim
##
## chain: input() => in_out() => in_out() => sink()
##
## The `chain <#chain.t,MirNodeSeq,untyped>`_ template provides the context
## for the generator routines. The ``=>`` operator does the actual chaining --
## each call of it yields a sentinel value that informs the next ``=>``
## invocation on how to interpet the sub-expression. The `input`, `in_out`,
## and `sink` calls are referred to as *operands*.
##
## The first operand must always be an *input* (something that provides an
## initial `EValue <#EValue>`_), the following operands must be an
## *input-output*, and the last operand, depending on the the context,
## either an *sink* or *input-output*.
##
## There are two kinds of operands:
## - those that emit an MIR node (or sequence) and update (or return) an
## ``EValue``
## - those that emit neither MIR code nor modify the ``EValue``, but
## instead affect how the operands following it are interpreted

import
compiler/ast/[
Expand All @@ -15,28 +44,97 @@ type
Sink* = distinct bool
## Used to mark a procedure as generating code that acts as a sink
SinkAndValue* = distinct bool
## Marks a procedure as generating code that acts as a sink and
## yields a value.

template `=>`*(v: Value, sink: Sink) =
discard v
discard sink
Cond = distinct bool
## Marks a procedure as being a meta-operand that acts as a condition.
Predicate = distinct bool
## Depending on the boolean value, excludes the next item in the chain.

template `=>`*(v: Value, sink: SinkAndValue): Value =
discard v
discard sink
ChainEnd = distinct bool
## Sentinel type used to mark an operator in a chain as ending the chain.
## No further operators can be chained after an operator marked as
## ``ChainEnd``

EValue* = object
## Encapsulates information about the abstract value that results from
## an operation sequence. It's used as a way to transport said information
## between the generator procedures for operators.
typ* {.cursor.}: PType

# -------- chain templates:
# the templates that provide the context for the mini DSL

template discardTypeCheck[T](x: T) =
## Helper to discard the expression `x` while still requiring it to be of
## the given type. The templates making use of this helper can't use typed
## parameters directly, as the arguments (which require a ``value`` symbol
## to exist) would be sem'-checked before the `value` symbol is injected
discard x

template chain*(buf: var MirNodeSeq, x: untyped) =
## Provides the context for chaining operators together via ``=>`` that end
## in a value sink
block:
var value {.inject, used.}: EValue
template buffer: untyped {.inject, used.} = buf
discardTypeCheck[ChainEnd](x)

template forward*(buf: var MirNodeSeq, x: untyped) =
## Provides the context for chaining operators together via ``=>`` that end
## in a *value*. This is used in places where the consuming operator can't be
## expressed as part of the chain and is expected to be emitted immediately
## after
block:
var value {.inject, used.}: EValue
template buffer: untyped {.inject, used.} = buf

type T = Value or EValue
discardTypeCheck[T](x)

template eval*(buf: var MirNodeSeq, x: untyped): EValue =
## Similar to ``forward``, but returns the resulting ``EValue`` to be used
## as the input to another generator chain
block:
var value {.inject, used.}: EValue
template buffer: untyped {.inject, used.} = buf
discardTypeCheck[Value](x)
value

template `=>`*(a: EValue, b: SinkAndValue): Value =
mixin value
value = a
discard b
Value(false)

template `=>|`*(v: Value, sink: SinkAndValue) =
template `=>`*(a: Value, b: Sink): ChainEnd =
discard a
discard b
ChainEnd(false)

template `=>`*(a: EValue, b: Sink): ChainEnd =
mixin value
value = a
discard b
ChainEnd(false)

template `=>`*(v: Value, c: Cond): Predicate =
discard v
discard sink
Predicate(c)

template `|=>`*(sink: Sink) =
discard sink
template `=>`*(v: EValue, c: Cond): Predicate =
value = v
Predicate(c)

template `|=>`*(sink: SinkAndValue): Value =
discard sink
template `=>`*(v: Predicate, sink: SinkAndValue): Value =
if bool(v):
discard sink
Value(false)

template previous*(): Value =
template `=>`*(v: Value, sink: SinkAndValue): Value =
discard v
discard sink
Value(false)

template follows*(): Sink =
Expand All @@ -45,6 +143,188 @@ template follows*(): Sink =
## where ``follows`` is used, comes next
Sink(false)

# ----------- atoms:
# The routines representing DSL operands that cannot be decomposed further.
# They append to a provided node buffer and update the associated ``EValue``
# instance. The routines are not very useful on their on own -- their
# integration into the chain DSL is what makes them ergonomic to use.

{.push inline.}

func emit*(s: var MirNodeSeq; n: sink MirNode): EValue =
## Adds `n` to the node sequences, with its type providing
## the initial type information for the result.
result = EValue(typ: n.typ)
s.add n

func procLit*(s: var MirNodeSeq, sym: PSym): EValue =
s.add MirNode(kind: mnkProc, typ: sym.typ, sym: sym)
result = EValue(typ: sym.typ)

func typeLit*(s: var MirNodeSeq, t: PType): EValue =
s.add MirNode(kind: mnkType, typ: t)
result = EValue(typ: t)

func literal*(s: var MirNodeSeq; n: PNode): EValue =
s.add MirNode(kind: mnkLiteral, typ: n.typ, lit: n)
result = EValue(typ: n.typ)

func constr*(s: var MirNodeSeq, typ: PType): EValue =
s.add MirNode(kind: mnkConstr, typ: typ)
result = EValue(typ: typ)

func temp*(s: var MirNodeSeq, typ: PType, id: TempId): EValue =
s.add MirNode(kind: mnkTemp, typ: typ, temp: id)
result = EValue(typ: typ)

func symbol*(s: var MirNodeSeq, kind: range[mnkConst..mnkLocal],
sym: PSym): EValue =
s.add MirNode(kind: kind, typ: sym.typ, sym: sym)
result = EValue(typ: sym.typ)

func opParam*(s: var MirNodeSeq, i: uint32, typ: PType): EValue =
assert typ != nil
s.add MirNode(kind: mnkOpParam, param: i, typ: typ)
result = EValue(typ: typ)

func magicCall*(s: var MirNodeSeq, m: TMagic, typ: PType): EValue =
assert typ != nil
s.add MirNode(kind: mnkMagic, typ: typ, magic: m)
result = EValue(typ: typ)

# input/output:

func tag*(s: var MirNodeSeq, effect: EffectKind, val: var EValue) =
s.add MirNode(kind: mnkTag, effect: effect, typ: val.typ)

func castOp*(s: var MirNodeSeq, typ: PType, val: var EValue) =
s.add MirNode(kind: mnkCast, typ: typ)
val.typ = typ

func stdConvOp*(s: var MirNodeSeq, typ: PType, val: var EValue) =
s.add MirNode(kind: mnkStdConv, typ: typ)
val.typ = typ

func convOp*(s: var MirNodeSeq, typ: PType, val: var EValue) =
s.add MirNode(kind: mnkConv, typ: typ)
val.typ = typ

func addrOp*(s: var MirNodeSeq, typ: PType, val: var EValue) =
s.add MirNode(kind: mnkAddr, typ: typ)
val.typ = typ

func viewOp*(s: var MirNodeSeq, typ: PType, val: var EValue) =
s.add MirNode(kind: mnkView, typ: typ)
val.typ = typ

func derefOp*(s: var MirNodeSeq, typ: PType, val: var EValue) =
s.add MirNode(kind: mnkDeref, typ: typ)
val.typ = typ

func derefViewOp*(s: var MirNodeSeq, typ: PType, val: var EValue) =
s.add MirNode(kind: mnkDerefView, typ: typ)
val.typ = typ

func pathObj*(s: var MirNodeSeq, field: PSym, val: var EValue) =
assert field.kind == skField
s.add MirNode(kind: mnkPathNamed, typ: field.typ, field: field)
val.typ = field.typ

func pathPos*(s: var MirNodeSeq, elemType: PType, position: uint32, val: var EValue) =
s.add MirNode(kind: mnkPathPos, typ: elemType, position: position)
val.typ = elemType

func pathVariant*(s: var MirNodeSeq, objType: PType, field: PSym, val: var EValue) =
s.add MirNode(kind: mnkPathVariant, typ: objType, field: field)
val.typ = objType

func unaryMagicCall*(s: var MirNodeSeq, m: TMagic, typ: PType, val: var EValue) =
assert typ != nil
s.add MirNode(kind: mnkMagic, typ: typ, magic: m)
val.typ = typ

# sinks:

func arg*(s: var MirNodeSeq, val: EValue) =
s.add MirNode(kind: mnkArg, typ: val.typ)

func consume*(s: var MirNodeSeq, val: EValue) =
s.add MirNode(kind: mnkConsume, typ: val.typ)

func name*(s: var MirNodeSeq, val: EValue) =
s.add MirNode(kind: mnkName, typ: val.typ)

func voidOut*(s: var MirNodeSeq, val: EValue) =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func voidOut*(s: var MirNodeSeq, val: EValue) =
func voidOut*(s: var MirNodeSeq, val: EValue) =
## end the mir input expression as a void-expression

not sure I have that quite right, this is from what I could gather based on usage. initially I was seeing if I could come up with a better name, settled on a doc string might help. (soft suggestion)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that sounds correct. I added it plus some additional elaboration.

In general, the 'void' operator/node acts as both a discard and as a delimiter for expressions ('call' operators that yield nothing also need to be followed by 'void').

s.add MirNode(kind: mnkVoid)

# special operators:

template predicate*(val: bool): Cond =
## If `val` evaluates to 'false', the operand following next in the chain
## is not evaluated.
Cond(val)

{.pop.} # inline

# ----------- chain DSL adapters:

template genInputAdapter1(name, arg1: untyped) =
template `name`*(arg1: untyped): EValue =
mixin buffer
name(buffer, arg1)

template genInputAdapter2(name, arg1, arg2: untyped) =
template `name`*(arg1, arg2: untyped): EValue =
mixin buffer
name(buffer, arg1, arg2)

template genValueAdapter1(name, arg1: untyped) =
template `name`*(arg1: untyped): SinkAndValue =
mixin value, buffer
name(buffer, arg1, value)
SinkAndValue(false)

template genValueAdapter2(name, arg1, arg2: untyped) =
template `name`*(arg1, arg2: untyped): SinkAndValue =
mixin value, buffer
name(buffer, arg1, arg2, value)
SinkAndValue(false)

template genSinkAdapter(name: untyped) =
template `name`*(): Sink =
mixin value, buffer
name(buffer, value)
Sink(false)

# generate the adapters:
genInputAdapter1(emit, n)
genInputAdapter1(procLit, sym)
genInputAdapter1(typeLit, n)
genInputAdapter1(literal, n)
genInputAdapter1(constr, typ)
genInputAdapter2(temp, typ, id)
genInputAdapter2(symbol, kind, sym)
genInputAdapter2(opParam, i, typ)
genInputAdapter2(magicCall, typ, id)

genValueAdapter1(tag, effect)
genValueAdapter1(castOp, typ)
genValueAdapter1(stdConvOp, typ)
genValueAdapter1(convOp, typ)
genValueAdapter1(addrOp, typ)
genValueAdapter1(viewOp, typ)
genValueAdapter1(derefOp, typ)
genValueAdapter1(derefViewOp, typ)
genValueAdapter1(pathObj, field)
genValueAdapter2(pathPos, typ, pos)
genValueAdapter2(pathVariant, typ, field)
genValueAdapter2(unaryMagicCall, m, typ)

genSinkAdapter(arg)
genSinkAdapter(name)
genSinkAdapter(consume)
genSinkAdapter(voidOut)

# --------- node constructors:

func procNode*(s: PSym): MirNode {.inline.} =
Expand Down
Loading