diff --git a/compiler/mir/mirbridge.nim b/compiler/mir/mirbridge.nim index 70c06bb94cf..6ea1cf121ef 100644 --- a/compiler/mir/mirbridge.nim +++ b/compiler/mir/mirbridge.nim @@ -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 == {}: @@ -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: diff --git a/compiler/mir/mirconstr.nim b/compiler/mir/mirconstr.nim index 9f877b0498d..672b17c9f35 100644 --- a/compiler/mir/mirconstr.nim +++ b/compiler/mir/mirconstr.nim @@ -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/[ @@ -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 = @@ -45,6 +143,191 @@ 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) = + ## Ends the input MIR expression as a void-expression (i.e., the + ## computed result of the expression is discarded). Corresponds + ## to ``mnkVoid``. + 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.} = diff --git a/compiler/mir/mirgen.nim b/compiler/mir/mirgen.nim index dc03d664f23..b1fe8ae7ee8 100644 --- a/compiler/mir/mirgen.nim +++ b/compiler/mir/mirgen.nim @@ -12,9 +12,6 @@ ## they're used, either a temporary or existing lvalue expression. The latter ## are forwarded to the generation procedures via ``Destination``. ## -## To shorten the emit code and also make it a bit easier to read and -## understand, a mini DSL realised through templates is used. -## ## Origin information ## ================== ## @@ -70,13 +67,6 @@ import ] type - 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 - # XXX: maybe rename to ``Computation``? - typ {.cursor.}: PType - NodeSpan = HOslice[NodeIndex] ## Refers to a span of nodes in a ``Fragment`` @@ -104,11 +94,6 @@ type flags: set[DestFlag] - 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`` - Block = object ## Information about a ``block`` label: PSym ## the symbol of the block's label. 'nil' if the block has no @@ -362,104 +347,7 @@ func popSlice(frag: var CodeFragment, span: NodeSpan) = frag.nodes.setLen(span.a) frag.source.setLen(span.a) -# -------------- DSL routines ------------- - -template genValueAdapter(name: untyped) = - template `name`(): SinkAndValue = - mixin value, buffer - `name`(buffer, value) - SinkAndValue(false) - -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) - -template genInputAdapter(name, arg: untyped) = - template `name`(c: var TCtx, arg: untyped): EValue = - # for backwards compatibility - name(c.stmts.nodes, arg) - - template `name`(arg: untyped): EValue = - # the actual adapter - mixin buffer - name(buffer, arg) - -template genInputAdapter2(name, arg1, arg2: untyped) = - template `name`(c: var TCtx, arg1, arg2: untyped): EValue = - # for backwards compatibility - name(c.stmts.nodes, arg1, arg2) - - template `name`(arg1, arg2: untyped): EValue = - # the actual adapter - mixin buffer - name(buffer, arg1, arg2) - -template `=>`(a: EValue, b: SinkAndValue): Value = - mixin value - value = a - discard b - Value(false) - -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 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(c: var TCtx, 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.} = c.stmts.nodes - discardTypeCheck[ChainEnd](x) - -template forward(c: var TCtx, x: untyped) = - ## Provides the context for chaining operators together via ``=>`` that end - ## in *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.} = c.stmts.nodes - - type T = Value or EValue - discardTypeCheck[T](x) - -template eval(c: var TCtx, 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.} = c.stmts.nodes - discardTypeCheck[Value](x) - value +# -------------- convenience templates ------------- template forward(v: EValue) = ## Acts the same as discarding the expression's result, but hints to @@ -479,132 +367,53 @@ template stmtList(f: var CodeFragment, body: untyped) = f.nodes.stmtList: body -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 genVoid(s: var MirNodeSeq, val: EValue) = - s.add MirNode(kind: mnkVoid) - -func tag(s: var MirNodeSeq, effect: EffectKind, val: var EValue) = - s.add MirNode(kind: mnkTag, effect: effect, typ: val.typ) - -func modify(s: var MirNodeSeq, val: var EValue) = - tag(s, ekMutate, val) - -func outOp(s: var MirNodeSeq, val: var EValue) = - tag(s, ekReassign, val) - -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) - # don't change ownership. If the l-value is owned so is the resulting - # pointer - 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) = - let objType = objType.skipTypes(abstractInstTypeClass) - 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 - -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) - -func tupleAccess(s: var MirNodeSeq, pos: uint32, typ: PType, val: var EValue) = - s.add MirNode(kind: mnkPathPos, typ: typ, position: pos) - val.typ = typ - -# generate the adapters: -genSinkAdapter(arg) -genSinkAdapter(name) -genSinkAdapter(consume) -genSinkAdapter(genVoid) - -genValueAdapter(modify) -genValueAdapter(outOp) - -genValueAdapter1(castOp, typ) -genValueAdapter1(stdConvOp, typ) -genValueAdapter1(convOp, typ) -genValueAdapter1(addrOp, typ) -genValueAdapter1(viewOp, typ) -genValueAdapter1(derefOp, typ) -genValueAdapter1(derefViewOp, typ) -genValueAdapter1(pathObj, field) -genValueAdapter1(tag, effect) - -genValueAdapter2(pathPos, typ, pos) -genValueAdapter2(pathVariant, typ, field) -genValueAdapter2(tupleAccess, pos, typ) -genValueAdapter2(unaryMagicCall, m, typ) - -func constr(s: var MirNodeSeq, typ: PType): EValue = - s.add MirNode(kind: mnkConstr, typ: typ) - result = EValue(typ: typ) - -func tempNode(s: var MirNodeSeq, typ: PType, id: TempId): EValue = - s.add MirNode(kind: mnkTemp, typ: typ, temp: id) - result = EValue(typ: typ) - -func procLit(s: var MirNodeSeq, sym: PSym): EValue = - s.add MirNode(kind: mnkProc, typ: sym.typ, sym: sym) - result = EValue(typ: sym.typ) - -func genTypeLit(s: var MirNodeSeq, t: PType): EValue = - s.add MirNode(kind: mnkType, typ: t) - result = EValue(typ: t) +# for convenience, we introduce wrapper template accepting a +# ``TCtx`` for some DSL atoms + +template chain(c: var TCtx, x: untyped) = + chain(c.stmts.nodes, x) + +template eval(c: var TCtx, x: untyped): EValue = + eval(c.stmts.nodes, x) + +template forward(c: var TCtx, x: untyped) = + forward(c.stmts.nodes, x) + +template procLit(c: var TCtx, s: PSym): EValue = + procLit(c.stmts.nodes, s) + +template genTypeLit(c: var TCtx, t: PType): EValue = + typeLit(c.stmts.nodes, t) + +template genLit(c: var TCtx, n: PNode): EValue = + literal(c.stmts.nodes, n) + +template constr(c: var TCtx, t: PType): EValue = + constr(c.stmts.nodes, t) + +template tempNode(c: var TCtx, t: PType, id: TempId): EValue = + temp(c.stmts.nodes, t, id) + +template magicCall(c: var TCtx, m: TMagic, typ: PType): EValue = + magicCall(c.stmts.nodes, m, typ) + +template modify(): untyped = + tag(ekMutate) + +template outOp(): untyped = + tag(ekReassign) + +template notOp(c: var TCtx): untyped = + unaryMagicCall(mNot, getSysType(c.graph, c.sp.active.n.info, tyBool)) + +template tupleAccess(pos: uint32, elem: PType): untyped = + pathPos(elem, pos) proc genEmpty(c: var TCtx, n: PNode): EValue = c.stmts.nodes.add MirNode(kind: mnkNone, typ: n.typ) result = EValue(typ: c.graph.getSysType(n.info, tyVoid)) -func nameNode(s: PSym, n: PNode): MirNode = +func nameNode(s: PSym): MirNode = if sfGlobal in s.flags: MirNode(kind: mnkGlobal, typ: s.typ, sym: s) elif s.kind == skParam: @@ -616,26 +425,8 @@ func nameNode(s: PSym, n: PNode): MirNode = else: unreachable(s.kind) -func genLocation(s: var MirNodeSeq; n: PNode): EValue = - let mn = nameNode(n.sym, n) - s.add mn - - result = EValue(typ: mn.typ) - -proc genLit(s: var MirNodeSeq; n: PNode): EValue = - s.add MirNode(kind: mnkLiteral, typ: n.typ, lit: n) - result = EValue(typ: n.typ) - -genInputAdapter2(magicCall, typ, id) -genInputAdapter(constr, typ) -genInputAdapter2(tempNode, typ, id) -genInputAdapter(procLit, sym) -genInputAdapter(genTypeLit, n) -genInputAdapter(genLit, n) -genInputAdapter(genLocation, n) - -template notOp(c: var TCtx): untyped = - unaryMagicCall(mNot, getSysType(c.graph, c.sp.active.n.info, tyBool)) +template genLocation(c: var TCtx, n: PNode): EValue = + c.stmts.nodes.emit(nameNode(n.sym)) func emit(dest: var CodeFragment, sp: var SourceProvider, src: CodeFragment, span: NodeSpan): EValue = @@ -654,7 +445,7 @@ func emit(dest: var CodeFragment, sp: var SourceProvider, src: CodeFragment, func genDefault(c: var TCtx, typ: PType): EValue = ## Emits a call to the ``default`` operator for the given `typ` argBlock(c.stmts): discard - magicCall(c, mDefault, typ) + magicCall(c.stmts.nodes, mDefault, typ) proc gen(c: var TCtx; n: PNode) proc genx(c: var TCtx; n: PNode, consume: bool = false): EValue @@ -1073,13 +864,13 @@ proc genMagic(c: var TCtx, n: PNode; m: TMagic): EValue = # that's what we emit argBlock(c.stmts): # skip the surrounding typedesc - chain(c): genTypeLit(n[1].typ.skipTypes({tyTypeDesc})) => arg() + chain(c): typeLit(n[1].typ.skipTypes({tyTypeDesc})) => arg() magicCall(c, m, n.typ) of mGetTypeInfoV2: if n[0].typ == nil: # the compiler-generated version always uses a type as the argument argBlock(c.stmts): - chain(c): genTypeLit(n[1].typ) => arg() + chain(c): typeLit(n[1].typ) => arg() magicCall(c, m, n.typ) else: # only the compiler-generated version of the magic has a type parameter. @@ -1407,7 +1198,7 @@ proc genVarTuple(c: var TCtx, n: PNode) = # generate the assignment: argBlock(c.stmts): chain(c): genx(c, lhs) => outOp() => name() - chain(c): tempNode(typ, tmp) => tupleAccess(i.uint32, lhs.sym.typ) => consume() + chain(c): temp(typ, tmp) => tupleAccess(i.uint32, lhs.sym.typ) => consume() c.stmts.add MirNode(kind: mnkInit) proc genVarSection(c: var TCtx, n: PNode) = @@ -1964,14 +1755,14 @@ proc gen(c: var TCtx, n: PNode) = genFastAsgn(c, n[0], n[1]) of nkCallKinds: - chain(c): genCallOrMagic(c, n) => genVoid() + chain(c): genCallOrMagic(c, n) => voidOut() of nkProcDef, nkFuncDef, nkIteratorDef, nkMethodDef, nkConverterDef: c.stmts.subTree MirNode(kind: mnkDef): c.stmts.add procNode(n[namePos].sym) of nkDiscardStmt: if n[0].kind != nkEmpty: - chain(c): genx(c, n[0]) => genVoid() + chain(c): genx(c, n[0]) => voidOut() of nkNilLit: # a 'nil' literals can be used as a statement, in which case it is treated diff --git a/compiler/sem/injectdestructors.nim b/compiler/sem/injectdestructors.nim index f24115d28a6..759cde3d57d 100644 --- a/compiler/sem/injectdestructors.nim +++ b/compiler/sem/injectdestructors.nim @@ -602,35 +602,6 @@ func needsReset(tree: MirTree, cfg: ControlFlowGraph, ar: AnalysisResults, # ------- code generation routines -------- -# XXX: there are two problems here: -# 1. the code is unnecessarily complex, manual, and error-prone -# 2. the generated code is invalid, due to the missing type -# information on some nodes -# The second one only causes no issues because there are no further -# passes happening past ``rewriteAssignments``, ``astgen`` is not as -# strict as it should be, and the nodes in question don't translate -# to ``PNode``s. -# -# The way ``mirgen`` emits nodes is much simpler, easier to follow, and -# also less error prone. It also enforces that nodes are correctly typed -# (in the sense that types are present, not that they're valid). The -# attempt at generalizing the builder routines (i.e. ``mirconstr``) is -# not developed far enough yet to be of much use here. -# -# There is also the problem that the order in which it makes sense to -# generate node sequences is in many cases not the order in which they -# need to appear in the final stream. This causes friction with the -# ``Changesets`` API and is the cause of the rather nested and awkward -# code that is currently present. Some way of passing abstract node -# sequences around / forwarding them would help here. One approach could -# be to represent them as closures (lazy generation, dynamic dispatch, -# the environment creation overhead might be significant), another one to -# emit them into a staging buffer first and then pass a handle to said -# span around. I'd favor the latter. - -proc compilerProc(graph: ModuleGraph, name: string): MirNode = - MirNode(kind: mnkProc, sym: getCompilerProc(graph, name)) - func undoConversions(buf: var MirNodeSeq, tree: MirTree, src: OpValue) = ## When performing a destructive move for ``ref`` values, it's possible for ## the source to be an lvalue conversion -- in that case, we want pass the @@ -642,93 +613,65 @@ func undoConversions(buf: var MirNodeSeq, tree: MirTree, src: OpValue) = p = previous(tree, p) buf.add MirNode(kind: mnkConv, typ: tree[p].typ) +template voidCallWithArgs(buf: var MirNodeSeq, body: untyped) = + argBlock(buf): + body + buf.add MirNode(kind: mnkCall, typ: getVoidType(graph)) + buf.add MirNode(kind: mnkVoid) + func genDefTemp(buf: var MirNodeSeq, id: TempId, typ: PType) = buf.subTree MirNode(kind: mnkDef): buf.add MirNode(kind: mnkTemp, typ: typ, temp: id) -template genWasMoved(buf: var MirNodeSeq, graph: ModuleGraph, body: untyped) = +template genWasMoved(buf: var MirNodeSeq, graph: ModuleGraph, target: EValue) = argBlock(buf): - body - buf.add MirNode(kind: mnkTag, effect: ekKill) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkMagic, - typ: graph.getSysType(unknownLineInfo, tyVoid), - magic: mWasMoved) - buf.add MirNode(kind: mnkVoid) + chain(buf): target => tag(ekKill) => arg() + chain(buf): magicCall(mWasMoved, getVoidType(graph)) => voidOut() proc genDestroy*(buf: var MirNodeSeq, graph: ModuleGraph, t: PType, target: sink MirNode) = let destr = getOp(graph, t, attachedDestructor) - argBlock(buf): - buf.add MirNode(kind: mnkProc, sym: destr) - buf.add MirNode(kind: mnkArg) - buf.add target - buf.add MirNode(kind: mnkTag, typ: destr.paramType(0), effect: ekMutate) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkCall, typ: getVoidType(graph)) - buf.add MirNode(kind: mnkVoid) + voidCallWithArgs(buf): + chain(buf): procLit(destr) => arg() + chain(buf): emit(target) => tag(ekMutate) => arg() -proc genInjectedSink(buf: var MirNodeSeq, graph: ModuleGraph, t: PType) = +proc genInjectedSink(buf: var MirNodeSeq, graph: ModuleGraph, t: PType, + source: sink MirNode) = ## Generates either a call to the ``=sink`` hook, or (if none exists), a ## sink emulated via a destructor-call + bitwise-copy. The output is meant ## to be placed inside a region. let op = getOp(graph, t, attachedSink) if op != nil: - argBlock(buf): - buf.add MirNode(kind: mnkProc, sym: op) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkTag, typ: op.paramType(0), effect: ekMutate) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkOpParam, param: 1) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkCall, typ: getVoidType(graph)) - buf.add MirNode(kind: mnkVoid) + voidCallWithArgs(buf): + chain(buf): procLit(op) => arg() + chain(buf): opParam(0, t) => tag(ekMutate) => arg() + chain(buf): emit(source) => arg() else: # without a sink hook, a ``=destroy`` + blit-copy is used genDestroy(buf, graph, t, MirNode(kind: mnkOpParam, param: 0)) argBlock(buf): - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkOpParam, param: 1) - buf.add MirNode(kind: mnkArg) + chain(buf): opParam(0, t) => arg() + chain(buf): emit(source) => arg() buf.add MirNode(kind: mnkFastAsgn) +proc genInjectedSink(buf: var MirNodeSeq, graph: ModuleGraph, t: PType) = + genInjectedSink(buf, graph, t): + MirNode(kind: mnkOpParam, typ: t, param: 1) + proc genSinkFromTemporary(buf: var MirNodeSeq, graph: ModuleGraph, t: PType, tmp: TempId) = ## Similar to ``genInjectedSink`` but generates code for destructively ## moving the source operand into a temporary first - let op = getOp(graph, t, attachedSink) - buf.add MirNode(kind: mnkOpParam, param: 1) buf.genDefTemp(tmp, t) genWasMoved(buf, graph): - buf.add MirNode(kind: mnkOpParam, param: 1) + opParam(buf, 1, t) - if op != nil: - argBlock(buf): - buf.add MirNode(kind: mnkProc, sym: op) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkTag, typ: op.paramType(0), effect: ekMutate) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkTemp, temp: tmp) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkCall, typ: getVoidType(graph)) - buf.add MirNode(kind: mnkVoid) - else: - # without a sink hook, a ``=destroy`` + blit-copy is used - genDestroy(buf, graph, t, MirNode(kind: mnkOpParam, param: 0)) - - argBlock(buf): - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkTemp, temp: tmp) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkFastAsgn) + genInjectedSink(buf, graph, t): + MirNode(kind: mnkTemp, typ: t, temp: tmp) proc genCopy(buf: var MirTree, graph: ModuleGraph, t: PType, dst, src: sink MirNode, maybeCyclic: bool) = @@ -737,25 +680,16 @@ proc genCopy(buf: var MirTree, graph: ModuleGraph, t: PType, let op = getOp(graph, t, attachedAsgn) assert op != nil - argBlock(buf): - buf.add MirNode(kind: mnkProc, sym: op) - buf.add MirNode(kind: mnkArg) - buf.add dst - buf.add MirNode(kind: mnkTag, typ: op.paramType(0), effect: ekMutate) - buf.add MirNode(kind: mnkArg) - buf.add src - buf.add MirNode(kind: mnkArg) + voidCallWithArgs(buf): + chain(buf): procLit(op) => arg() + chain(buf): emit(dst) => tag(ekMutate) => arg() + chain(buf): emit(src) => arg() if graph.config.selectedGC == gcOrc and cyclicType(t.skipTypes(skipAliases + {tyDistinct}), graph): # pass whether the copy can potentially introduce cycles as the third # parameter: - buf.add MirNode(kind: mnkLiteral, - lit: boolLit(graph, unknownLineInfo, maybeCyclic)) - buf.add MirNode(kind: mnkArg) - - buf.add MirNode(kind: mnkCall, typ: getVoidType(graph)) - buf.add MirNode(kind: mnkVoid) + chain(buf): literal(boolLit(graph, unknownLineInfo, maybeCyclic)) => arg() proc genMarkCyclic(buf: var MirTree, graph: ModuleGraph, typ: PType, dest: sink MirNode) = @@ -765,21 +699,14 @@ proc genMarkCyclic(buf: var MirTree, graph: ModuleGraph, typ: PType, # cyclic too let t = typ.skipTypes(skipAliases + {tyDistinct}) if cyclicType(t, graph): - argBlock(buf): - buf.add compilerProc(graph, "nimMarkCyclic") - buf.add MirNode(kind: mnkArg) - - buf.add dest - if t.kind == tyProc: - # a closure. Only the environment needs to be marked as potentially - # cyclic - buf.add MirNode(kind: mnkMagic, typ: getSysType(graph, unknownLineInfo, tyPointer), - magic: mAccessEnv) - - buf.add MirNode(kind: mnkArg) - - buf.add MirNode(kind: mnkCall, typ: getVoidType(graph)) - buf.add MirNode(kind: mnkVoid) + voidCallWithArgs(buf): + chain(buf): procLit(getCompilerProc(graph, "nimMarkCyclic")) => arg() + # for closures, only the environment needs to be marked as potentially + # cyclic + chain(buf): emit(dest) => predicate(t.kind == tyProc) => + unaryMagicCall(mAccessEnv, + getSysType(graph, unknownLineInfo, tyPointer)) => + arg() proc expandAsgn(tree: MirTree, ctx: AnalyseCtx, ar: AnalysisResults, typ: PType, source: OpValue, asgn: Operation, @@ -823,6 +750,7 @@ proc expandAsgn(tree: MirTree, ctx: AnalyseCtx, ar: AnalysisResults, genWasMoved(buf, ctx.graph): buf.add MirNode(kind: mnkOpParam, param: 1) undoConversions(buf, tree, source) + EValue(typ: buf[^1].typ) else: # the value is only accessible through the source expression, a @@ -838,10 +766,8 @@ proc expandAsgn(tree: MirTree, ctx: AnalyseCtx, ar: AnalysisResults, # from it, in which case it doesn't matter when the reset happens buf.subTree MirNode(kind: mnkRegion): argBlock(buf): - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkOpParam, param: 1) - buf.add MirNode(kind: mnkArg) + chain(buf): opParam(0, typ) => arg() + chain(buf): opParam(1, typ) => arg() buf.add MirNode(kind: mnkFastAsgn) @@ -850,6 +776,7 @@ proc expandAsgn(tree: MirTree, ctx: AnalyseCtx, ar: AnalysisResults, genWasMoved(buf, ctx.graph): buf.add MirNode(kind: mnkOpParam, param: 1) undoConversions(buf, tree, source) + EValue(typ: buf[^1].typ) else: # no hook call nor destructive move is required @@ -899,6 +826,7 @@ proc consumeArg(tree: MirTree, ctx: AnalyseCtx, ar: AnalysisResults, genWasMoved(buf, ctx.graph): buf.add MirNode(kind: mnkOpParam, param: 0) undoConversions(buf, tree, src) + EValue(typ: buf[^1].typ) buf.add MirNode(kind: mnkTemp, typ: typ, temp: tmp) @@ -1092,10 +1020,9 @@ proc injectDestructors(tree: MirTree, graph: ModuleGraph, c.replaceMulti(buf): buf.subTree MirNode(kind: mnkRegion): argBlock(buf): - buf.add tree[getDefEntity(tree, pos)] # the entity (e.g. local) - buf.add MirNode(kind: mnkName) - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkArg) + let e = getDefEntity(tree, pos) # the entity (e.g. local) + chain(buf): emit(tree[e]) => name() + chain(buf): opParam(0, tree[e].typ) => arg() buf.add MirNode(kind: mnkInit) else: @@ -1237,24 +1164,17 @@ proc lowerBranchSwitch(buf: var MirNodeSeq, body: MirTree, graph: ModuleGraph, # the VM's behaviour. There, the branch is only reset if it's # actually changed argBlock(buf): - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkOpParam, param: 1) - buf.add MirNode(kind: mnkArg) + chain(buf): opParam(0, typ) => arg() + chain(buf): opParam(1, typ) => arg() - buf.add magic(getMagicEqForType(typ), boolTyp) - buf.add magic(mNot, boolTyp) + forward(buf): magicCall(getMagicEqForType(typ), boolTyp) => + unaryMagicCall(mNot, boolTyp) buf.subTree MirNode(kind: mnkIf): stmtList(buf): # ``=destroy`` call: - argBlock(buf): - buf.add MirNode(kind: mnkProc, sym: branchDestructor) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkTag, effect: ekInvalidate) - buf.add MirNode(kind: mnkArg) - buf.add MirNode(kind: mnkCall, typ: voidTyp) - buf.add MirNode(kind: mnkVoid) + voidCallWithArgs(buf): + chain(buf): procLit(buf, branchDestructor) => arg() + chain(buf): opParam(buf, 0, typ) => tag(ekInvalidate) => arg() else: # the object doesn't need destruction, which means that neither does one @@ -1266,11 +1186,8 @@ proc lowerBranchSwitch(buf: var MirNodeSeq, body: MirTree, graph: ModuleGraph, # generate the ``discriminant = newValue`` assignment: argBlock(buf): - buf.add MirNode(kind: mnkOpParam, param: 0) - buf.add MirNode(kind: mnkTag, effect: ekReassign, typ: typ) - buf.add MirNode(kind: mnkName) - buf.add MirNode(kind: mnkOpParam, param: 1) - buf.add MirNode(kind: mnkArg) + chain(buf): opParam(0, typ) => tag(ekReassign) => name() + chain(buf): opParam(1, typ) => arg() buf.add MirNode(kind: mnkFastAsgn) buf.add endNode(mnkRegion) @@ -1289,20 +1206,14 @@ proc lowerNew(tree: MirTree, g: ModuleGraph, c: var Changeset) = # re-insert the call to ``new`` argBlock(buf): - buf.add MirNode(kind: mnkOpParam, param: 0, typ: typ) - buf.add MirNode(kind: mnkTag, effect: ekReassign, typ: typ) - buf.add MirNode(kind: mnkName, typ: typ) + chain(buf): opParam(0, typ) => tag(ekReassign) => name() # add the remaining arguments (if any) for j in 1.. arg() + + chain(buf): emit(n) => voidOut() # use the original 'new' operator c.remove() # remove the ``mnkVoid`` node