#
#
#           The Nim Compiler
#        (c) Copyright 2023 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

import std / [assertions, tables, sets]
import ".." / [ast, astalgo, types, options, lineinfos, msgs, magicsys,
  modulegraphs, renderer, transf, bitsets, trees, nimsets,
  expanddefaults]
from ".." / lowerings import lowerSwap, lowerTupleUnpacking
from ".." / pathutils import customPath
import .. / ic / bitabs

import nirtypes, nirinsts, nirlineinfos, nirslots, types2ir, nirfiles

when defined(nimCompilerStacktraceHints):
  import std/stackframes

type
  ModuleCon* = ref object
    nirm*: ref NirModule
    types: TypesCon
    module*: PSym
    graph*: ModuleGraph
    nativeIntId, nativeUIntId: TypeId
    strPayloadId: (TypeId, TypeId)
    idgen: IdGenerator
    processedProcs, pendingProcsAsSet: HashSet[ItemId]
    pendingProcs: seq[PSym] # procs we still need to generate code for
    pendingVarsAsSet: HashSet[ItemId]
    pendingVars: seq[PSym]
    noModularity*: bool
    inProc: int
    toSymId: Table[ItemId, SymId]
    symIdCounter: int32

  ProcCon* = object
    config*: ConfigRef
    lit: Literals
    lastFileKey: FileIndex
    lastFileVal: LitId
    labelGen: int
    exitLabel: LabelId
    #code*: Tree
    blocks: seq[(PSym, LabelId)]
    sm: SlotManager
    idgen: IdGenerator
    m: ModuleCon
    prc: PSym
    options: TOptions

template code(c: ProcCon): Tree = c.m.nirm.code

proc initModuleCon*(graph: ModuleGraph; config: ConfigRef; idgen: IdGenerator; module: PSym;
                    nirm: ref NirModule): ModuleCon =
  #let lit = Literals() # must be shared
  result = ModuleCon(graph: graph, types: initTypesCon(config), nirm: nirm,
    idgen: idgen, module: module)
  case config.target.intSize
  of 2:
    result.nativeIntId = Int16Id
    result.nativeUIntId = UInt16Id
  of 4:
    result.nativeIntId = Int32Id
    result.nativeUIntId = UInt16Id
  else:
    result.nativeIntId = Int64Id
    result.nativeUIntId = UInt16Id
  result.strPayloadId = strPayloadPtrType(result.types, result.nirm.types)
  nirm.namespace = nirm.lit.strings.getOrIncl(customPath(toFullPath(config, module.info)))
  nirm.intbits = uint32(config.target.intSize * 8)

proc initProcCon*(m: ModuleCon; prc: PSym; config: ConfigRef): ProcCon =
  result = ProcCon(m: m, sm: initSlotManager({}), prc: prc, config: config,
    lit: m.nirm.lit, idgen: m.idgen,
    options: if prc != nil: prc.options
             else: config.options)
  result.exitLabel = newLabel(result.labelGen)

proc toLineInfo(c: var ProcCon; i: TLineInfo): PackedLineInfo =
  var val: LitId
  if c.lastFileKey == i.fileIndex:
    val = c.lastFileVal
  else:
    val = c.lit.strings.getOrIncl(toFullPath(c.config, i.fileIndex))
    # remember the entry:
    c.lastFileKey = i.fileIndex
    c.lastFileVal = val
  result = pack(c.m.nirm.man, val, int32 i.line, int32 i.col)

proc bestEffort(c: ProcCon): TLineInfo =
  if c.prc != nil:
    c.prc.info
  else:
    c.m.module.info

proc popBlock(c: var ProcCon; oldLen: int) =
  c.blocks.setLen(oldLen)

template withBlock(labl: PSym; info: PackedLineInfo; asmLabl: LabelId; body: untyped) {.dirty.} =
  var oldLen {.gensym.} = c.blocks.len
  c.blocks.add (labl, asmLabl)
  body
  popBlock(c, oldLen)

type
  GenFlag = enum
    gfAddrOf # load the address of the expression
    gfToOutParam # the expression is passed to an `out` parameter
  GenFlags = set[GenFlag]

proc gen(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags = {})

proc genScope(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags = {}) =
  openScope c.sm
  gen c, n, d, flags
  closeScope c.sm

proc freeTemp(c: var ProcCon; tmp: Value) =
  let s = extractTemp(tmp)
  if s != SymId(-1):
    freeTemp(c.sm, s)

proc freeTemps(c: var ProcCon; tmps: openArray[Value]) =
  for t in tmps: freeTemp(c, t)

proc typeToIr(m: ModuleCon; t: PType): TypeId =
  typeToIr(m.types, m.nirm.types, t)

proc allocTemp(c: var ProcCon; t: TypeId): SymId =
  if c.m.noModularity:
    result = allocTemp(c.sm, t, c.m.symIdCounter)
  else:
    result = allocTemp(c.sm, t, c.idgen.symId)

const
  ListSymId = -1

proc toSymId(c: var ProcCon; s: PSym): SymId =
  if c.m.noModularity:
    result = c.m.toSymId.getOrDefault(s.itemId, SymId(-1))
    if result.int < 0:
      inc c.m.symIdCounter
      result = SymId(c.m.symIdCounter)
      c.m.toSymId[s.itemId] = result
      when ListSymId != -1:
        if result.int == ListSymId or s.name.s == "echoBinSafe":
          echo result.int, " is ", s.name.s, " ", c.m.graph.config $ s.info, " ", s.flags
          writeStackTrace()
  else:
    result = SymId(s.itemId.item)

proc getTemp(c: var ProcCon; n: PNode): Value =
  let info = toLineInfo(c, n.info)
  let t = typeToIr(c.m, n.typ)
  let tmp = allocTemp(c, t)
  c.code.addSummon info, tmp, t
  result = localToValue(info, tmp)

proc getTemp(c: var ProcCon; t: TypeId; info: PackedLineInfo): Value =
  let tmp = allocTemp(c, t)
  c.code.addSummon info, tmp, t
  result = localToValue(info, tmp)

proc gen(c: var ProcCon; n: PNode; flags: GenFlags = {}) =
  var tmp = default(Value)
  gen(c, n, tmp, flags)
  freeTemp c, tmp

proc genScope(c: var ProcCon; n: PNode; flags: GenFlags = {}) =
  openScope c.sm
  gen c, n, flags
  closeScope c.sm

proc genx(c: var ProcCon; n: PNode; flags: GenFlags = {}): Value =
  result = default(Value)
  gen(c, n, result, flags)
  assert Tree(result).len > 0, $n

proc clearDest(c: var ProcCon; n: PNode; d: var Value) {.inline.} =
  when false:
    if n.typ.isNil or n.typ.kind == tyVoid:
      let s = extractTemp(d)
      if s != SymId(-1):
        freeLoc(c.sm, s)

proc isNotOpr(n: PNode): bool =
  n.kind in nkCallKinds and n[0].kind == nkSym and n[0].sym.magic == mNot

proc jmpBack(c: var ProcCon; n: PNode; lab: LabelId) =
  c.code.gotoLabel toLineInfo(c, n.info), GotoLoop, lab

type
  JmpKind = enum opcFJmp, opcTJmp

proc xjmp(c: var ProcCon; n: PNode; jk: JmpKind; v: Value): LabelId =
  result = newLabel(c.labelGen)
  let info = toLineInfo(c, n.info)
  buildTyped c.code, info, Select, Bool8Id:
    c.code.copyTree Tree(v)
    build c.code, info, SelectPair:
      build c.code, info, SelectValue:
        c.code.boolVal(c.lit.numbers, info, jk == opcTJmp)
      c.code.gotoLabel info, Goto, result

proc patch(c: var ProcCon; n: PNode; L: LabelId) =
  addLabel c.code, toLineInfo(c, n.info), Label, L

proc genWhile(c: var ProcCon; n: PNode) =
  # lab1:
  #   cond, tmp
  #   fjmp tmp, lab2
  #   body
  #   jmp lab1
  # lab2:
  let info = toLineInfo(c, n.info)
  let lab1 = c.code.addNewLabel(c.labelGen, info, LoopLabel)
  withBlock(nil, info, lab1):
    if isTrue(n[0]):
      c.gen(n[1])
      c.jmpBack(n, lab1)
    elif isNotOpr(n[0]):
      var tmp = c.genx(n[0][1])
      let lab2 = c.xjmp(n, opcTJmp, tmp)
      c.freeTemp(tmp)
      c.gen(n[1])
      c.jmpBack(n, lab1)
      c.patch(n, lab2)
    else:
      var tmp = c.genx(n[0])
      let lab2 = c.xjmp(n, opcFJmp, tmp)
      c.freeTemp(tmp)
      c.gen(n[1])
      c.jmpBack(n, lab1)
      c.patch(n, lab2)

proc genBlock(c: var ProcCon; n: PNode; d: var Value) =
  openScope c.sm
  let info = toLineInfo(c, n.info)
  let lab1 = newLabel(c.labelGen)

  withBlock(n[0].sym, info, lab1):
    c.gen(n[1], d)

  c.code.addLabel(info, Label, lab1)
  closeScope c.sm
  c.clearDest(n, d)

proc jumpTo(c: var ProcCon; n: PNode; L: LabelId) =
  c.code.addLabel(toLineInfo(c, n.info), Goto, L)

proc genBreak(c: var ProcCon; n: PNode) =
  if n[0].kind == nkSym:
    for i in countdown(c.blocks.len-1, 0):
      if c.blocks[i][0] == n[0].sym:
        c.jumpTo n, c.blocks[i][1]
        return
    localError(c.config, n.info, "NIR problem: cannot find 'break' target")
  else:
    c.jumpTo n, c.blocks[c.blocks.high][1]

proc genIf(c: var ProcCon; n: PNode; d: var Value) =
  #  if (!expr1) goto lab1;
  #    thenPart
  #    goto LEnd
  #  lab1:
  #  if (!expr2) goto lab2;
  #    thenPart2
  #    goto LEnd
  #  lab2:
  #    elsePart
  #  Lend:
  if isEmpty(d) and not isEmptyType(n.typ): d = getTemp(c, n)
  var ending = newLabel(c.labelGen)
  for i in 0..<n.len:
    var it = n[i]
    if it.len == 2:
      let info = toLineInfo(c, it[0].info)
      var elsePos: LabelId
      if isNotOpr(it[0]):
        let tmp = c.genx(it[0][1])
        elsePos = c.xjmp(it[0][1], opcTJmp, tmp) # if true
        c.freeTemp tmp
      else:
        let tmp = c.genx(it[0])
        elsePos = c.xjmp(it[0], opcFJmp, tmp) # if false
        c.freeTemp tmp
      c.clearDest(n, d)
      if isEmptyType(it[1].typ): # maybe noreturn call, don't touch `d`
        c.genScope(it[1])
      else:
        c.genScope(it[1], d) # then part
      if i < n.len-1:
        c.jumpTo it[1], ending
      c.patch(it, elsePos)
    else:
      c.clearDest(n, d)
      if isEmptyType(it[0].typ): # maybe noreturn call, don't touch `d`
        c.genScope(it[0])
      else:
        c.genScope(it[0], d)
  c.patch(n, ending)
  c.clearDest(n, d)

proc tempToDest(c: var ProcCon; n: PNode; d: var Value; tmp: Value) =
  if isEmpty(d):
    d = tmp
  else:
    let info = toLineInfo(c, n.info)
    buildTyped c.code, info, Asgn, typeToIr(c.m, n.typ):
      c.code.copyTree d
      c.code.copyTree tmp
    freeTemp(c, tmp)

proc genAndOr(c: var ProcCon; n: PNode; opc: JmpKind; d: var Value) =
  #   asgn d, a
  #   tjmp|fjmp lab1
  #   asgn d, b
  # lab1:
  var tmp = getTemp(c, n)
  c.gen(n[1], tmp)
  let lab1 = c.xjmp(n, opc, tmp)
  c.gen(n[2], tmp)
  c.patch(n, lab1)
  tempToDest c, n, d, tmp

proc unused(c: var ProcCon; n: PNode; x: Value) {.inline.} =
  if hasValue(x):
    #debug(n)
    localError(c.config, n.info, "not unused")

proc caseValue(c: var ProcCon; n: PNode) =
  let info = toLineInfo(c, n.info)
  build c.code, info, SelectValue:
    let x = genx(c, n)
    c.code.copyTree x
    freeTemp(c, x)

proc caseRange(c: var ProcCon; n: PNode) =
  let info = toLineInfo(c, n.info)
  build c.code, info, SelectRange:
    let x = genx(c, n[0])
    let y = genx(c, n[1])
    c.code.copyTree x
    c.code.copyTree y
    freeTemp(c, y)
    freeTemp(c, x)

proc addUseCodegenProc(c: var ProcCon; dest: var Tree; name: string; info: PackedLineInfo) =
  let cp = getCompilerProc(c.m.graph, name)
  let theProc = c.genx newSymNode(cp)
  copyTree c.code, theProc

template buildCond(useNegation: bool; cond: typed; body: untyped) =
  let lab = newLabel(c.labelGen)
  buildTyped c.code, info, Select, Bool8Id:
    c.code.copyTree cond
    build c.code, info, SelectPair:
      build c.code, info, SelectValue:
        c.code.boolVal(c.lit.numbers, info, useNegation)
      c.code.gotoLabel info, Goto, lab

  body
  c.code.addLabel info, Label, lab

template buildIf(cond: typed; body: untyped) =
  buildCond false, cond, body

template buildIfNot(cond: typed; body: untyped) =
  buildCond true, cond, body

template buildIfThenElse(cond: typed; then, otherwise: untyped) =
  let lelse = newLabel(c.labelGen)
  let lend = newLabel(c.labelGen)
  buildTyped c.code, info, Select, Bool8Id:
    c.code.copyTree cond
    build c.code, info, SelectPair:
      build c.code, info, SelectValue:
        c.code.boolVal(c.lit.numbers, info, false)
      c.code.gotoLabel info, Goto, lelse

  then()
  c.code.gotoLabel info, Goto, lend
  c.code.addLabel info, Label, lelse
  otherwise()
  c.code.addLabel info, Label, lend

include stringcases

proc genCase(c: var ProcCon; n: PNode; d: var Value) =
  if not isEmptyType(n.typ):
    if isEmpty(d): d = getTemp(c, n)
  else:
    unused(c, n, d)

  if n[0].typ.skipTypes(abstractInst).kind == tyString:
    genStringCase(c, n, d)
    return

  var sections = newSeqOfCap[LabelId](n.len-1)
  let ending = newLabel(c.labelGen)
  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[0])
  buildTyped c.code, info, Select, typeToIr(c.m, n[0].typ):
    c.code.copyTree tmp
    for i in 1..<n.len:
      let section = newLabel(c.labelGen)
      sections.add section
      let it = n[i]
      let itinfo = toLineInfo(c, it.info)
      build c.code, itinfo, SelectPair:
        build c.code, itinfo, SelectList:
          for j in 0..<it.len-1:
            if it[j].kind == nkRange:
              caseRange c, it[j]
            else:
              caseValue c, it[j]
        c.code.addLabel itinfo, Goto, section
  c.freeTemp tmp
  for i in 1..<n.len:
    let it = n[i]
    let itinfo = toLineInfo(c, it.info)
    c.code.addLabel itinfo, Label, sections[i-1]
    c.gen it.lastSon
    if i != n.len-1:
      c.code.addLabel itinfo, Goto, ending
  c.code.addLabel info, Label, ending

proc rawCall(c: var ProcCon; info: PackedLineInfo; opc: Opcode; t: TypeId; args: var openArray[Value]) =
  buildTyped c.code, info, opc, t:
    if opc in {CheckedCall, CheckedIndirectCall}:
      c.code.addLabel info, CheckedGoto, c.exitLabel
    for a in mitems(args):
      c.code.copyTree a
      freeTemp c, a

proc canRaiseDisp(c: ProcCon; n: PNode): bool =
  # we assume things like sysFatal cannot raise themselves
  if n.kind == nkSym and {sfNeverRaises, sfImportc, sfCompilerProc} * n.sym.flags != {}:
    result = false
  elif optPanics in c.config.globalOptions or
      (n.kind == nkSym and sfSystemModule in getModule(n.sym).flags and
       sfSystemRaisesDefect notin n.sym.flags):
    # we know we can be strict:
    result = canRaise(n)
  else:
    # we have to be *very* conservative:
    result = canRaiseConservative(n)

proc genCall(c: var ProcCon; n: PNode; d: var Value) =
  let canRaise = canRaiseDisp(c, n[0])

  let opc = if n[0].kind == nkSym and n[0].sym.kind in routineKinds:
              (if canRaise: CheckedCall else: Call)
            else:
              (if canRaise: CheckedIndirectCall else: IndirectCall)
  let info = toLineInfo(c, n.info)

  # In the IR we cannot nest calls. Thus we use two passes:
  var args: seq[Value] = @[]
  var t = n[0].typ
  if t != nil: t = t.skipTypes(abstractInst)
  args.add genx(c, n[0])
  for i in 1..<n.len:
    if t != nil and i < t.len:
      if isCompileTimeOnly(t[i]): discard
      elif isOutParam(t[i]): args.add genx(c, n[i], {gfToOutParam})
      else: args.add genx(c, n[i])
    else:
      args.add genx(c, n[i])

  let tb = typeToIr(c.m, n.typ)
  if not isEmptyType(n.typ):
    if isEmpty(d): d = getTemp(c, n)
    # XXX Handle problematic aliasing here: `a = f_canRaise(a)`.
    buildTyped c.code, info, Asgn, tb:
      c.code.copyTree d
      rawCall c, info, opc, tb, args
  else:
    rawCall c, info, opc, tb, args
  freeTemps c, args

proc genRaise(c: var ProcCon; n: PNode) =
  let info = toLineInfo(c, n.info)
  let tb = typeToIr(c.m, n[0].typ)

  let d = genx(c, n[0])
  buildTyped c.code, info, SetExc, tb:
    c.code.copyTree d
  c.freeTemp(d)
  c.code.addLabel info, Goto, c.exitLabel

proc genReturn(c: var ProcCon; n: PNode) =
  if n[0].kind != nkEmpty:
    gen(c, n[0])
  # XXX Block leave actions?
  let info = toLineInfo(c, n.info)
  c.code.addLabel info, Goto, c.exitLabel

proc genTry(c: var ProcCon; n: PNode; d: var Value) =
  if isEmpty(d) and not isEmptyType(n.typ): d = getTemp(c, n)
  var endings: seq[LabelId] = @[]
  let ehPos = newLabel(c.labelGen)
  let oldExitLab = c.exitLabel
  c.exitLabel = ehPos
  if isEmptyType(n[0].typ): # maybe noreturn call, don't touch `d`
    c.gen(n[0])
  else:
    c.gen(n[0], d)
  c.clearDest(n, d)

  # Add a jump past the exception handling code
  let jumpToFinally = newLabel(c.labelGen)
  c.jumpTo n, jumpToFinally
  # This signals where the body ends and where the exception handling begins
  c.patch(n, ehPos)
  c.exitLabel = oldExitLab
  for i in 1..<n.len:
    let it = n[i]
    if it.kind != nkFinally:
      # first opcExcept contains the end label of the 'except' block:
      let endExcept = newLabel(c.labelGen)
      for j in 0..<it.len - 1:
        assert(it[j].kind == nkType)
        let typ = it[j].typ.skipTypes(abstractPtrs-{tyTypeDesc})
        let itinfo = toLineInfo(c, it[j].info)
        build c.code, itinfo, TestExc:
          c.code.addTyped itinfo, typeToIr(c.m, typ)
      if it.len == 1:
        let itinfo = toLineInfo(c, it.info)
        build c.code, itinfo, TestExc:
          c.code.addTyped itinfo, VoidId
      let body = it.lastSon
      if isEmptyType(body.typ): # maybe noreturn call, don't touch `d`
        c.gen(body)
      else:
        c.gen(body, d)
      c.clearDest(n, d)
      if i < n.len:
        endings.add newLabel(c.labelGen)
      c.patch(it, endExcept)
  let fin = lastSon(n)
  # we always generate an 'opcFinally' as that pops the safepoint
  # from the stack if no exception is raised in the body.
  c.patch(fin, jumpToFinally)
  #c.gABx(fin, opcFinally, 0, 0)
  for endPos in endings: c.patch(n, endPos)
  if fin.kind == nkFinally:
    c.gen(fin[0])
    c.clearDest(n, d)
  #c.gABx(fin, opcFinallyEnd, 0, 0)

template isGlobal(s: PSym): bool = sfGlobal in s.flags and s.kind != skForVar
proc isGlobal(n: PNode): bool = n.kind == nkSym and isGlobal(n.sym)

proc genField(c: var ProcCon; n: PNode; d: var Value) =
  var pos: int
  if n.kind != nkSym or n.sym.kind != skField:
    localError(c.config, n.info, "no field symbol")
    pos = 0
  else:
    pos = n.sym.position
  d.addImmediateVal toLineInfo(c, n.info), pos

proc genIndex(c: var ProcCon; n: PNode; arr: PType; d: var Value) =
  let info = toLineInfo(c, n.info)
  if arr.skipTypes(abstractInst).kind == tyArray and
      (let offset = firstOrd(c.config, arr); offset != Zero):
    let x = c.genx(n)
    buildTyped d, info, Sub, c.m.nativeIntId:
      copyTree d.Tree, x
      d.addImmediateVal toLineInfo(c, n.info), toInt(offset)
  else:
    c.gen(n, d)
  if optBoundsCheck in c.options:
    let idx = move d
    build d, info, CheckedIndex:
      d.Tree.addLabel info, CheckedGoto, c.exitLabel
      copyTree d.Tree, idx
      let x = toInt64 lengthOrd(c.config, arr)
      d.addIntVal c.lit.numbers, info, c.m.nativeIntId, x

proc rawGenNew(c: var ProcCon; d: Value; refType: PType; ninfo: TLineInfo; needsInit: bool) =
  assert refType.kind == tyRef
  let baseType = refType.elementType

  let info = toLineInfo(c, ninfo)
  let codegenProc = magicsys.getCompilerProc(c.m.graph,
    if needsInit: "nimNewObj" else: "nimNewObjUninit")
  let refTypeIr = typeToIr(c.m, refType)
  buildTyped c.code, info, Asgn, refTypeIr:
    copyTree c.code, d
    buildTyped c.code, info, Cast, refTypeIr:
      buildTyped c.code, info, Call, VoidPtrId:
        let theProc = c.genx newSymNode(codegenProc, ninfo)
        copyTree c.code, theProc
        c.code.addImmediateVal info, int(getSize(c.config, baseType))
        c.code.addImmediateVal info, int(getAlign(c.config, baseType))

proc genNew(c: var ProcCon; n: PNode; needsInit: bool) =
  # If in doubt, always follow the blueprint of the C code generator for `mm:orc`.
  let refType = n[1].typ.skipTypes(abstractInstOwned)
  let d = genx(c, n[1])
  rawGenNew c, d, refType, n.info, needsInit
  freeTemp c, d

proc genNewSeqOfCap(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let seqtype = skipTypes(n.typ, abstractVarRange)
  let baseType = seqtype.elementType
  var a = c.genx(n[1])
  if isEmpty(d): d = getTemp(c, n)
  # $1.len = 0
  buildTyped c.code, info, Asgn, c.m.nativeIntId:
    buildTyped c.code, info, FieldAt, typeToIr(c.m, seqtype):
      copyTree c.code, d
      c.code.addImmediateVal info, 0
    c.code.addImmediateVal info, 0
  # $1.p = ($4*) #newSeqPayloadUninit($2, sizeof($3), NIM_ALIGNOF($3))
  let payloadPtr = seqPayloadPtrType(c.m.types, c.m.nirm.types, seqtype)[0]
  buildTyped c.code, info, Asgn, payloadPtr:
    # $1.p
    buildTyped c.code, info, FieldAt, typeToIr(c.m, seqtype):
      copyTree c.code, d
      c.code.addImmediateVal info, 1
    # ($4*) #newSeqPayloadUninit($2, sizeof($3), NIM_ALIGNOF($3))
    buildTyped c.code, info, Cast, payloadPtr:
      buildTyped c.code, info, Call, VoidPtrId:
        let codegenProc = magicsys.getCompilerProc(c.m.graph, "newSeqPayloadUninit")
        let theProc = c.genx newSymNode(codegenProc, n.info)
        copyTree c.code, theProc
        copyTree c.code, a
        c.code.addImmediateVal info, int(getSize(c.config, baseType))
        c.code.addImmediateVal info, int(getAlign(c.config, baseType))
  freeTemp c, a

proc genNewSeqPayload(c: var ProcCon; info: PackedLineInfo; d, b: Value; seqtype: PType) =
  let baseType = seqtype.elementType
  # $1.p = ($4*) #newSeqPayload($2, sizeof($3), NIM_ALIGNOF($3))
  let payloadPtr = seqPayloadPtrType(c.m.types, c.m.nirm.types, seqtype)[0]

  # $1.len = $2
  buildTyped c.code, info, Asgn, c.m.nativeIntId:
    buildTyped c.code, info, FieldAt, typeToIr(c.m, seqtype):
      copyTree c.code, d
      c.code.addImmediateVal info, 0
    copyTree c.code, b

  buildTyped c.code, info, Asgn, payloadPtr:
    # $1.p
    buildTyped c.code, info, FieldAt, typeToIr(c.m, seqtype):
      copyTree c.code, d
      c.code.addImmediateVal info, 1
    # ($4*) #newSeqPayload($2, sizeof($3), NIM_ALIGNOF($3))
    buildTyped c.code, info, Cast, payloadPtr:
      buildTyped c.code, info, Call, VoidPtrId:
        let codegenProc = magicsys.getCompilerProc(c.m.graph, "newSeqPayload")
        let theProc = c.genx newSymNode(codegenProc)
        copyTree c.code, theProc
        copyTree c.code, b
        c.code.addImmediateVal info, int(getSize(c.config, baseType))
        c.code.addImmediateVal info, int(getAlign(c.config, baseType))

proc genNewSeq(c: var ProcCon; n: PNode) =
  let info = toLineInfo(c, n.info)
  let seqtype = skipTypes(n[1].typ, abstractVarRange)
  var d = c.genx(n[1])
  var b = c.genx(n[2])

  genNewSeqPayload(c, info, d, b, seqtype)

  freeTemp c, b
  freeTemp c, d

template intoDest*(d: var Value; info: PackedLineInfo; typ: TypeId; body: untyped) =
  if typ == VoidId:
    body(c.code)
  elif isEmpty(d):
    body(Tree(d))
  else:
    buildTyped c.code, info, Asgn, typ:
      copyTree c.code, d
      body(c.code)

template valueIntoDest(c: var ProcCon; info: PackedLineInfo; d: var Value; typ: PType; body: untyped) =
  if isEmpty(d):
    body(Tree d)
  else:
    buildTyped c.code, info, Asgn, typeToIr(c.m, typ):
      copyTree c.code, d
      body(c.code)

template constrIntoDest(c: var ProcCon; info: PackedLineInfo; d: var Value; typ: PType; body: untyped) =
  var tmp = default(Value)
  body(Tree tmp)
  if isEmpty(d):
    d = tmp
  else:
    buildTyped c.code, info, Asgn, typeToIr(c.m, typ):
      copyTree c.code, d
      copyTree c.code, tmp

proc genBinaryOp(c: var ProcCon; n: PNode; d: var Value; opc: Opcode) =
  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[1])
  let tmp2 = c.genx(n[2])
  let t = typeToIr(c.m, n.typ)
  template body(target) =
    buildTyped target, info, opc, t:
      if opc in {CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, CheckedMod}:
        target.addLabel info, CheckedGoto, c.exitLabel
      copyTree target, tmp
      copyTree target, tmp2
  intoDest d, info, t, body
  c.freeTemp(tmp)
  c.freeTemp(tmp2)

proc genCmpOp(c: var ProcCon; n: PNode; d: var Value; opc: Opcode) =
  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[1])
  let tmp2 = c.genx(n[2])
  let t = typeToIr(c.m, n[1].typ)
  template body(target) =
    buildTyped target, info, opc, t:
      copyTree target, tmp
      copyTree target, tmp2
  intoDest d, info, Bool8Id, body
  c.freeTemp(tmp)
  c.freeTemp(tmp2)

proc genUnaryOp(c: var ProcCon; n: PNode; d: var Value; opc: Opcode) =
  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[1])
  let t = typeToIr(c.m, n.typ)
  template body(target) =
    buildTyped target, info, opc, t:
      copyTree target, tmp
  intoDest d, info, t, body
  c.freeTemp(tmp)

proc genIncDec(c: var ProcCon; n: PNode; opc: Opcode) =
  let info = toLineInfo(c, n.info)
  let t = typeToIr(c.m, skipTypes(n[1].typ, abstractVar))

  let d = c.genx(n[1])
  let tmp = c.genx(n[2])
  # we produce code like:  i = i + 1
  buildTyped c.code, info, Asgn, t:
    copyTree c.code, d
    buildTyped c.code, info, opc, t:
      if opc in {CheckedAdd, CheckedSub}:
        c.code.addLabel info, CheckedGoto, c.exitLabel
      copyTree c.code, d
      copyTree c.code, tmp
  c.freeTemp(tmp)
  #c.genNarrow(n[1], d)
  c.freeTemp(d)

proc genArrayLen(c: var ProcCon; n: PNode; d: var Value) =
  #echo c.m.graph.config $ n.info, " ", n
  let info = toLineInfo(c, n.info)
  var a = n[1]
  #if a.kind == nkHiddenAddr: a = a[0]
  var typ = skipTypes(a.typ, abstractVar + tyUserTypeClasses)
  case typ.kind
  of tyOpenArray, tyVarargs:
    let xa = c.genx(a)
    template body(target) =
      buildTyped target, info, FieldAt, typeToIr(c.m, typ):
        copyTree target, xa
        target.addImmediateVal info, 1 # (p, len)-pair so len is at index 1
    intoDest d, info, c.m.nativeIntId, body

  of tyCstring:
    let xa = c.genx(a)
    if isEmpty(d): d = getTemp(c, n)
    buildTyped c.code, info, Call, c.m.nativeIntId:
      let codegenProc = magicsys.getCompilerProc(c.m.graph, "nimCStrLen")
      assert codegenProc != nil
      let theProc = c.genx newSymNode(codegenProc, n.info)
      copyTree c.code, theProc
      copyTree c.code, xa

  of tyString, tySequence:
    let xa = c.genx(a)

    if typ.kind == tySequence:
      # we go through a temporary here because people write bullshit code.
      if isEmpty(d): d = getTemp(c, n)

    template body(target) =
      buildTyped target, info, FieldAt, typeToIr(c.m, typ):
        copyTree target, xa
        target.addImmediateVal info, 0 # (len, p)-pair so len is at index 0
    intoDest d, info, c.m.nativeIntId, body

  of tyArray:
    template body(target) =
      target.addIntVal(c.lit.numbers, info, c.m.nativeIntId, toInt lengthOrd(c.config, typ))
    intoDest d, info, c.m.nativeIntId, body
  else: internalError(c.config, n.info, "genArrayLen()")

proc genUnaryMinus(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[1])
  let t = typeToIr(c.m, n.typ)
  template body(target) =
    buildTyped target, info, Sub, t:
      # Little hack: This works because we know that `0.0` is all 0 bits:
      target.addIntVal(c.lit.numbers, info, t, 0)
      copyTree target, tmp
  intoDest d, info, t, body
  c.freeTemp(tmp)

proc genHigh(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let t = typeToIr(c.m, n.typ)
  var x = default(Value)
  genArrayLen(c, n, x)
  template body(target) =
    buildTyped target, info, Sub, t:
      copyTree target, x
      target.addIntVal(c.lit.numbers, info, t, 1)
  intoDest d, info, t, body
  c.freeTemp x

proc genBinaryCp(c: var ProcCon; n: PNode; d: var Value; compilerProc: string) =
  let info = toLineInfo(c, n.info)
  let xa = c.genx(n[1])
  let xb = c.genx(n[2])
  if isEmpty(d) and not isEmptyType(n.typ): d = getTemp(c, n)

  let t = typeToIr(c.m, n.typ)
  template body(target) =
    buildTyped target, info, Call, t:
      let codegenProc = magicsys.getCompilerProc(c.m.graph, compilerProc)
      #assert codegenProc != nil, $n & " " & (c.m.graph.config $ n.info)
      let theProc = c.genx newSymNode(codegenProc, n.info)
      copyTree target, theProc
      copyTree target, xa
      copyTree target, xb

  intoDest d, info, t, body
  c.freeTemp xb
  c.freeTemp xa

proc genUnaryCp(c: var ProcCon; n: PNode; d: var Value; compilerProc: string; argAt = 1) =
  let info = toLineInfo(c, n.info)
  let xa = c.genx(n[argAt])
  if isEmpty(d) and not isEmptyType(n.typ): d = getTemp(c, n)

  let t = typeToIr(c.m, n.typ)
  template body(target) =
    buildTyped target, info, Call, t:
      let codegenProc = magicsys.getCompilerProc(c.m.graph, compilerProc)
      let theProc = c.genx newSymNode(codegenProc, n.info)
      copyTree target, theProc
      copyTree target, xa

  intoDest d, info, t, body
  c.freeTemp xa

proc genEnumToStr(c: var ProcCon; n: PNode; d: var Value) =
  let t = n[1].typ.skipTypes(abstractInst+{tyRange})
  let toStrProc = getToStringProc(c.m.graph, t)
  # XXX need to modify this logic for IC.
  var nb = copyTree(n)
  nb[0] = newSymNode(toStrProc)
  gen(c, nb, d)

proc genOf(c: var ProcCon; n: PNode; d: var Value) =
  genUnaryOp c, n, d, TestOf

template sizeOfLikeMsg(name): string =
  "'" & name & "' requires '.importc' types to be '.completeStruct'"

proc genIsNil(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[1])
  let t = typeToIr(c.m, n[1].typ)
  template body(target) =
    buildTyped target, info, Eq, t:
      copyTree target, tmp
      addNilVal target, info, t
  intoDest d, info, Bool8Id, body
  c.freeTemp(tmp)

proc fewCmps(conf: ConfigRef; s: PNode): bool =
  # this function estimates whether it is better to emit code
  # for constructing the set or generating a bunch of comparisons directly
  if s.kind != nkCurly:
    result = false
  elif (getSize(conf, s.typ) <= conf.target.intSize) and (nfAllConst in s.flags):
    result = false            # it is better to emit the set generation code
  elif elemType(s.typ).kind in {tyInt, tyInt16..tyInt64}:
    result = true             # better not emit the set if int is basetype!
  else:
    result = s.len <= 8  # 8 seems to be a good value

proc genInBitset(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let a = c.genx(n[1])
  let b = c.genx(n[2])

  let t = bitsetBasetype(c.m.types, c.m.nirm.types, n[1].typ)
  let setType = typeToIr(c.m, n[1].typ)
  let mask =
    case t
    of UInt8Id: 7
    of UInt16Id: 15
    of UInt32Id: 31
    else: 63
  let expansion = if t == UInt64Id: UInt64Id else: c.m.nativeUIntId
    # "(($1              &(1U<<((NU)($2)&7U)))!=0)"  - or -
    # "(($1[(NU)($2)>>3] &(1U<<((NU)($2)&7U)))!=0)"

  template body(target) =
    buildTyped target, info, BoolNot, Bool8Id:
      buildTyped target, info, Eq, t:
        buildTyped target, info, BitAnd, t:
          if c.m.nirm.types[setType].kind != ArrayTy:
            copyTree target, a
          else:
            buildTyped target, info, ArrayAt, setType:
              copyTree target, a
              buildTyped target, info, BitShr, t:
                buildTyped target, info, Cast, expansion:
                  copyTree target, b
                addIntVal target, c.lit.numbers, info, expansion, 3

          buildTyped target, info, BitShl, t:
            addIntVal target, c.lit.numbers, info, t, 1
            buildTyped target, info, BitAnd, t:
              buildTyped target, info, Cast, expansion:
                copyTree target, b
              addIntVal target, c.lit.numbers, info, expansion, mask
        addIntVal target, c.lit.numbers, info, t, 0
  intoDest d, info, t, body

  c.freeTemp(b)
  c.freeTemp(a)

proc genInSet(c: var ProcCon; n: PNode; d: var Value) =
  let g {.cursor.} = c.m.graph
  if n[1].kind == nkCurly and fewCmps(g.config, n[1]):
    # a set constructor but not a constant set:
    # do not emit the set, but generate a bunch of comparisons; and if we do
    # so, we skip the unnecessary range check: This is a semantical extension
    # that code now relies on. :-/ XXX
    let elem = if n[2].kind in {nkChckRange, nkChckRange64}: n[2][0]
               else: n[2]
    let curly = n[1]
    var ex: PNode = nil
    for it in curly:
      var test: PNode
      if it.kind == nkRange:
        test = newTree(nkCall, g.operators.opAnd.newSymNode,
          newTree(nkCall, g.operators.opLe.newSymNode, it[0], elem), # a <= elem
          newTree(nkCall, g.operators.opLe.newSymNode, elem, it[1])
        )
      else:
        test = newTree(nkCall, g.operators.opEq.newSymNode, elem, it)
      test.typ = getSysType(g, it.info, tyBool)

      if ex == nil: ex = test
      else: ex = newTree(nkCall, g.operators.opOr.newSymNode, ex, test)

    if ex == nil:
      let info = toLineInfo(c, n.info)
      template body(target) =
        boolVal target, c.lit.numbers, info, false
      intoDest d, info, Bool8Id, body
    else:
      gen c, ex, d
  else:
    genInBitset c, n, d

proc genCard(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let a = c.genx(n[1])
  let t = typeToIr(c.m, n.typ)

  let setType = typeToIr(c.m, n[1].typ)
  if isEmpty(d): d = getTemp(c, n)

  buildTyped c.code, info, Asgn, t:
    copyTree c.code, d
    buildTyped c.code, info, Call, t:
      if c.m.nirm.types[setType].kind == ArrayTy:
        let codegenProc = magicsys.getCompilerProc(c.m.graph, "cardSet")
        let theProc = c.genx newSymNode(codegenProc, n.info)
        copyTree c.code, theProc
        buildTyped c.code, info, AddrOf, ptrTypeOf(c.m.nirm.types, setType):
          copyTree c.code, a
        c.code.addImmediateVal info, int(getSize(c.config, n[1].typ))
      elif t == UInt64Id:
        let codegenProc = magicsys.getCompilerProc(c.m.graph, "countBits64")
        let theProc = c.genx newSymNode(codegenProc, n.info)
        copyTree c.code, theProc
        copyTree c.code, a
      else:
        let codegenProc = magicsys.getCompilerProc(c.m.graph, "countBits32")
        let theProc = c.genx newSymNode(codegenProc, n.info)
        copyTree c.code, theProc
        buildTyped c.code, info, Cast, UInt32Id:
          copyTree c.code, a
  freeTemp c, a

proc genEqSet(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let a = c.genx(n[1])
  let b = c.genx(n[2])
  let t = typeToIr(c.m, n.typ)

  let setType = typeToIr(c.m, n[1].typ)

  if c.m.nirm.types[setType].kind == ArrayTy:
    if isEmpty(d): d = getTemp(c, n)

    buildTyped c.code, info, Asgn, t:
      copyTree c.code, d
      buildTyped c.code, info, Eq, t:
        buildTyped c.code, info, Call, t:
          let codegenProc = magicsys.getCompilerProc(c.m.graph, "nimCmpMem")
          let theProc = c.genx newSymNode(codegenProc, n.info)
          copyTree c.code, theProc
          buildTyped c.code, info, AddrOf, ptrTypeOf(c.m.nirm.types, setType):
            copyTree c.code, a
          buildTyped c.code, info, AddrOf, ptrTypeOf(c.m.nirm.types, setType):
            copyTree c.code, b
          c.code.addImmediateVal info, int(getSize(c.config, n[1].typ))
        c.code.addIntVal c.lit.numbers, info, c.m.nativeIntId, 0

  else:
    template body(target) =
      buildTyped target, info, Eq, setType:
        copyTree target, a
        copyTree target, b
    intoDest d, info, Bool8Id, body

  freeTemp c, b
  freeTemp c, a

proc beginCountLoop(c: var ProcCon; info: PackedLineInfo; first, last: int): (SymId, LabelId, LabelId) =
  let tmp = allocTemp(c, c.m.nativeIntId)
  c.code.addSummon info, tmp, c.m.nativeIntId
  buildTyped c.code, info, Asgn, c.m.nativeIntId:
    c.code.addSymUse info, tmp
    c.code.addIntVal c.lit.numbers, info, c.m.nativeIntId, first
  let lab1 = c.code.addNewLabel(c.labelGen, info, LoopLabel)
  result = (tmp, lab1, newLabel(c.labelGen))

  buildTyped c.code, info, Select, Bool8Id:
    buildTyped c.code, info, Lt, c.m.nativeIntId:
      c.code.addSymUse info, tmp
      c.code.addIntVal c.lit.numbers, info, c.m.nativeIntId, last
    build c.code, info, SelectPair:
      build c.code, info, SelectValue:
        c.code.boolVal(c.lit.numbers, info, false)
      c.code.gotoLabel info, Goto, result[2]

proc beginCountLoop(c: var ProcCon; info: PackedLineInfo; first, last: Value): (SymId, LabelId, LabelId) =
  let tmp = allocTemp(c, c.m.nativeIntId)
  c.code.addSummon info, tmp, c.m.nativeIntId
  buildTyped c.code, info, Asgn, c.m.nativeIntId:
    c.code.addSymUse info, tmp
    copyTree c.code, first
  let lab1 = c.code.addNewLabel(c.labelGen, info, LoopLabel)
  result = (tmp, lab1, newLabel(c.labelGen))

  buildTyped c.code, info, Select, Bool8Id:
    buildTyped c.code, info, Le, c.m.nativeIntId:
      c.code.addSymUse info, tmp
      copyTree c.code, last
    build c.code, info, SelectPair:
      build c.code, info, SelectValue:
        c.code.boolVal(c.lit.numbers, info, false)
      c.code.gotoLabel info, Goto, result[2]

proc endLoop(c: var ProcCon; info: PackedLineInfo; s: SymId; back, exit: LabelId) =
  buildTyped c.code, info, Asgn, c.m.nativeIntId:
    c.code.addSymUse info, s
    buildTyped c.code, info, Add, c.m.nativeIntId:
      c.code.addSymUse info, s
      c.code.addIntVal c.lit.numbers, info, c.m.nativeIntId, 1
  c.code.addLabel info, GotoLoop, back
  c.code.addLabel info, Label, exit
  freeTemp(c.sm, s)

proc genLeSet(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let a = c.genx(n[1])
  let b = c.genx(n[2])
  let t = typeToIr(c.m, n.typ)

  let setType = typeToIr(c.m, n[1].typ)

  if c.m.nirm.types[setType].kind == ArrayTy:
    let elemType = bitsetBasetype(c.m.types, c.m.nirm.types, n[1].typ)
    if isEmpty(d): d = getTemp(c, n)
    #    "for ($1 = 0; $1 < $2; $1++):"
    #    "  $3 = (($4[$1] & ~ $5[$1]) == 0)"
    #    "  if (!$3) break;"
    let (idx, backLabel, endLabel) = beginCountLoop(c, info, 0, int(getSize(c.config, n[1].typ)))
    buildTyped c.code, info, Asgn, Bool8Id:
      copyTree c.code, d
      buildTyped c.code, info, Eq, elemType:
        buildTyped c.code, info, BitAnd, elemType:
          buildTyped c.code, info, ArrayAt, setType:
            copyTree c.code, a
            c.code.addSymUse info, idx
          buildTyped c.code, info, BitNot, elemType:
            buildTyped c.code, info, ArrayAt, setType:
              copyTree c.code, b
              c.code.addSymUse info, idx
        c.code.addIntVal c.lit.numbers, info, elemType, 0

    # if !$3: break
    buildTyped c.code, info, Select, Bool8Id:
      c.code.copyTree d
      build c.code, info, SelectPair:
        build c.code, info, SelectValue:
          c.code.boolVal(c.lit.numbers, info, false)
        c.code.gotoLabel info, Goto, endLabel

    endLoop(c, info, idx, backLabel, endLabel)
  else:
    # "(($1 & ~ $2)==0)"
    template body(target) =
      buildTyped target, info, Eq, setType:
        buildTyped target, info, BitAnd, setType:
          copyTree target, a
          buildTyped target, info, BitNot, setType:
            copyTree target, b
        target.addIntVal c.lit.numbers, info, setType, 0

    intoDest d, info, Bool8Id, body

  freeTemp c, b
  freeTemp c, a

proc genLtSet(c: var ProcCon; n: PNode; d: var Value) =
  localError(c.m.graph.config, n.info, "`<` for sets not implemented")

proc genBinarySet(c: var ProcCon; n: PNode; d: var Value; m: TMagic) =
  let info = toLineInfo(c, n.info)
  let a = c.genx(n[1])
  let b = c.genx(n[2])
  let t = typeToIr(c.m, n.typ)

  let setType = typeToIr(c.m, n[1].typ)

  if c.m.nirm.types[setType].kind == ArrayTy:
    let elemType = bitsetBasetype(c.m.types, c.m.nirm.types, n[1].typ)
    if isEmpty(d): d = getTemp(c, n)
    #    "for ($1 = 0; $1 < $2; $1++):"
    #    "  $3 = (($4[$1] & ~ $5[$1]) == 0)"
    #    "  if (!$3) break;"
    let (idx, backLabel, endLabel) = beginCountLoop(c, info, 0, int(getSize(c.config, n[1].typ)))
    buildTyped c.code, info, Asgn, elemType:
      buildTyped c.code, info, ArrayAt, setType:
        copyTree c.code, d
        c.code.addSymUse info, idx
      buildTyped c.code, info, (if m == mPlusSet: BitOr else: BitAnd), elemType:
        buildTyped c.code, info, ArrayAt, setType:
          copyTree c.code, a
          c.code.addSymUse info, idx
        if m == mMinusSet:
          buildTyped c.code, info, BitNot, elemType:
            buildTyped c.code, info, ArrayAt, setType:
              copyTree c.code, b
              c.code.addSymUse info, idx
        else:
          buildTyped c.code, info, ArrayAt, setType:
            copyTree c.code, b
            c.code.addSymUse info, idx

    endLoop(c, info, idx, backLabel, endLabel)
  else:
    # "(($1 & ~ $2)==0)"
    template body(target) =
      buildTyped target, info, (if m == mPlusSet: BitOr else: BitAnd), setType:
        copyTree target, a
        if m == mMinusSet:
          buildTyped target, info, BitNot, setType:
            copyTree target, b
        else:
          copyTree target, b

    intoDest d, info, setType, body

  freeTemp c, b
  freeTemp c, a

proc genInclExcl(c: var ProcCon; n: PNode; m: TMagic) =
  let info = toLineInfo(c, n.info)
  let a = c.genx(n[1])
  let b = c.genx(n[2])

  let setType = typeToIr(c.m, n[1].typ)

  let t = bitsetBasetype(c.m.types, c.m.nirm.types, n[1].typ)
  let mask =
    case t
    of UInt8Id: 7
    of UInt16Id: 15
    of UInt32Id: 31
    else: 63

  buildTyped c.code, info, Asgn, setType:
    if c.m.nirm.types[setType].kind == ArrayTy:
      if m == mIncl:
        # $1[(NU)($2)>>3] |=(1U<<($2&7U))
        buildTyped c.code, info, ArrayAt, setType:
          copyTree c.code, a
          buildTyped c.code, info, BitShr, t:
            buildTyped c.code, info, Cast, c.m.nativeUIntId:
              copyTree c.code, b
            addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
        buildTyped c.code, info, BitOr, t:
          buildTyped c.code, info, ArrayAt, setType:
            copyTree c.code, a
            buildTyped c.code, info, BitShr, t:
              buildTyped c.code, info, Cast, c.m.nativeUIntId:
                copyTree c.code, b
              addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
          buildTyped c.code, info, BitShl, t:
            c.code.addIntVal c.lit.numbers, info, t, 1
            buildTyped c.code, info, BitAnd, t:
              copyTree c.code, b
              c.code.addIntVal c.lit.numbers, info, t, 7
      else:
        # $1[(NU)($2)>>3] &= ~(1U<<($2&7U))
        buildTyped c.code, info, ArrayAt, setType:
          copyTree c.code, a
          buildTyped c.code, info, BitShr, t:
            buildTyped c.code, info, Cast, c.m.nativeUIntId:
              copyTree c.code, b
            addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
        buildTyped c.code, info, BitAnd, t:
          buildTyped c.code, info, ArrayAt, setType:
            copyTree c.code, a
            buildTyped c.code, info, BitShr, t:
              buildTyped c.code, info, Cast, c.m.nativeUIntId:
                copyTree c.code, b
              addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
          buildTyped c.code, info, BitNot, t:
            buildTyped c.code, info, BitShl, t:
              c.code.addIntVal c.lit.numbers, info, t, 1
              buildTyped c.code, info, BitAnd, t:
                copyTree c.code, b
                c.code.addIntVal c.lit.numbers, info, t, 7

    else:
      copyTree c.code, a
      if m == mIncl:
        # $1 |= ((NU8)1)<<(($2) & 7)
        buildTyped c.code, info, BitOr, setType:
          copyTree c.code, a
          buildTyped c.code, info, BitShl, t:
            c.code.addIntVal c.lit.numbers, info, t, 1
            buildTyped c.code, info, BitAnd, t:
              copyTree c.code, b
              c.code.addIntVal c.lit.numbers, info, t, mask
      else:
        # $1 &= ~(((NU8)1) << (($2) & 7))
        buildTyped c.code, info, BitAnd, setType:
          copyTree c.code, a
          buildTyped c.code, info, BitNot, t:
            buildTyped c.code, info, BitShl, t:
              c.code.addIntVal c.lit.numbers, info, t, 1
              buildTyped c.code, info, BitAnd, t:
                copyTree c.code, b
                c.code.addIntVal c.lit.numbers, info, t, mask
  freeTemp c, b
  freeTemp c, a

proc genSetConstrDyn(c: var ProcCon; n: PNode; d: var Value) =
  # example: { a..b, c, d, e, f..g }
  # we have to emit an expression of the form:
  # nimZeroMem(tmp, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c);
  # incl(tmp, d); incl(tmp, e); inclRange(tmp, f, g);
  let info = toLineInfo(c, n.info)
  let setType = typeToIr(c.m, n.typ)
  let size = int(getSize(c.config, n.typ))
  let t = bitsetBasetype(c.m.types, c.m.nirm.types, n.typ)
  let mask =
    case t
    of UInt8Id: 7
    of UInt16Id: 15
    of UInt32Id: 31
    else: 63

  if isEmpty(d): d = getTemp(c, n)
  if c.m.nirm.types[setType].kind != ArrayTy:
    buildTyped c.code, info, Asgn, setType:
      copyTree c.code, d
      c.code.addIntVal c.lit.numbers, info, t, 0

    for it in n:
      if it.kind == nkRange:
        let a = genx(c, it[0])
        let b = genx(c, it[1])
        let (idx, backLabel, endLabel) = beginCountLoop(c, info, a, b)
        buildTyped c.code, info, Asgn, setType:
          copyTree c.code, d
          buildTyped c.code, info, BitAnd, setType:
            copyTree c.code, d
            buildTyped c.code, info, BitNot, t:
              buildTyped c.code, info, BitShl, t:
                c.code.addIntVal c.lit.numbers, info, t, 1
                buildTyped c.code, info, BitAnd, t:
                  c.code.addSymUse info, idx
                  c.code.addIntVal c.lit.numbers, info, t, mask

        endLoop(c, info, idx, backLabel, endLabel)
        freeTemp c, b
        freeTemp c, a

      else:
        let a = genx(c, it)
        buildTyped c.code, info, Asgn, setType:
          copyTree c.code, d
          buildTyped c.code, info, BitAnd, setType:
            copyTree c.code, d
            buildTyped c.code, info, BitNot, t:
              buildTyped c.code, info, BitShl, t:
                c.code.addIntVal c.lit.numbers, info, t, 1
                buildTyped c.code, info, BitAnd, t:
                  copyTree c.code, a
                  c.code.addIntVal c.lit.numbers, info, t, mask
        freeTemp c, a

  else:
    # init loop:
    let (idx, backLabel, endLabel) = beginCountLoop(c, info, 0, size)
    buildTyped c.code, info, Asgn, t:
      copyTree c.code, d
      c.code.addIntVal c.lit.numbers, info, t, 0
    endLoop(c, info, idx, backLabel, endLabel)

    # incl elements:
    for it in n:
      if it.kind == nkRange:
        let a = genx(c, it[0])
        let b = genx(c, it[1])
        let (idx, backLabel, endLabel) = beginCountLoop(c, info, a, b)

        buildTyped c.code, info, Asgn, t:
          buildTyped c.code, info, ArrayAt, setType:
            copyTree c.code, d
            buildTyped c.code, info, BitShr, t:
              buildTyped c.code, info, Cast, c.m.nativeUIntId:
                c.code.addSymUse info, idx
              addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
          buildTyped c.code, info, BitOr, t:
            buildTyped c.code, info, ArrayAt, setType:
              copyTree c.code, d
              buildTyped c.code, info, BitShr, t:
                buildTyped c.code, info, Cast, c.m.nativeUIntId:
                  c.code.addSymUse info, idx
                addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
            buildTyped c.code, info, BitShl, t:
              c.code.addIntVal c.lit.numbers, info, t, 1
              buildTyped c.code, info, BitAnd, t:
                c.code.addSymUse info, idx
                c.code.addIntVal c.lit.numbers, info, t, 7

        endLoop(c, info, idx, backLabel, endLabel)
        freeTemp c, b
        freeTemp c, a

      else:
        let a = genx(c, it)
        # $1[(NU)($2)>>3] |=(1U<<($2&7U))
        buildTyped c.code, info, Asgn, t:
          buildTyped c.code, info, ArrayAt, setType:
            copyTree c.code, d
            buildTyped c.code, info, BitShr, t:
              buildTyped c.code, info, Cast, c.m.nativeUIntId:
                copyTree c.code, a
              addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
          buildTyped c.code, info, BitOr, t:
            buildTyped c.code, info, ArrayAt, setType:
              copyTree c.code, d
              buildTyped c.code, info, BitShr, t:
                buildTyped c.code, info, Cast, c.m.nativeUIntId:
                  copyTree c.code, a
                addIntVal c.code, c.lit.numbers, info, c.m.nativeUIntId, 3
            buildTyped c.code, info, BitShl, t:
              c.code.addIntVal c.lit.numbers, info, t, 1
              buildTyped c.code, info, BitAnd, t:
                copyTree c.code, a
                c.code.addIntVal c.lit.numbers, info, t, 7
        freeTemp c, a

proc genSetConstr(c: var ProcCon; n: PNode; d: var Value) =
  if isDeepConstExpr(n):
    let info = toLineInfo(c, n.info)
    let setType = typeToIr(c.m, n.typ)
    let size = int(getSize(c.config, n.typ))
    let cs = toBitSet(c.config, n)

    if c.m.nirm.types[setType].kind != ArrayTy:
      template body(target) =
        target.addIntVal c.lit.numbers, info, setType, cast[BiggestInt](bitSetToWord(cs, size))
      intoDest d, info, setType, body
    else:
      let t = bitsetBasetype(c.m.types, c.m.nirm.types, n.typ)
      template body(target) =
        buildTyped target, info, ArrayConstr, setType:
          for i in 0..high(cs):
            target.addIntVal c.lit.numbers, info, t, int64 cs[i]
      intoDest d, info, setType, body
  else:
    genSetConstrDyn c, n, d

proc genStrConcat(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  #   <Nim code>
  #   s = "Hello " & name & ", how do you feel?" & 'z'
  #
  #   <generated code>
  #  {
  #    string tmp0;
  #    ...
  #    tmp0 = rawNewString(6 + 17 + 1 + s2->len);
  #    // we cannot generate s = rawNewString(...) here, because
  #    // ``s`` may be used on the right side of the expression
  #    appendString(tmp0, strlit_1);
  #    appendString(tmp0, name);
  #    appendString(tmp0, strlit_2);
  #    appendChar(tmp0, 'z');
  #    asgn(s, tmp0);
  #  }
  var args: seq[Value] = @[]
  var argsRuntimeLen: seq[Value] = @[]

  var precomputedLen = 0
  for i in 1 ..< n.len:
    let it = n[i]
    args.add genx(c, it)
    if skipTypes(it.typ, abstractVarRange).kind == tyChar:
      inc precomputedLen
    elif it.kind in {nkStrLit..nkTripleStrLit}:
      inc precomputedLen, it.strVal.len
    else:
      argsRuntimeLen.add args[^1]

  # generate length computation:
  var tmpLen = allocTemp(c, c.m.nativeIntId)
  buildTyped c.code, info, Asgn, c.m.nativeIntId:
    c.code.addSymUse info, tmpLen
    c.code.addIntVal c.lit.numbers, info, c.m.nativeIntId, precomputedLen
  for a in mitems(argsRuntimeLen):
    buildTyped c.code, info, Asgn, c.m.nativeIntId:
      c.code.addSymUse info, tmpLen
      buildTyped c.code, info, CheckedAdd, c.m.nativeIntId:
        c.code.addLabel info, CheckedGoto, c.exitLabel
        c.code.addSymUse info, tmpLen
        buildTyped c.code, info, FieldAt, typeToIr(c.m, n.typ):
          copyTree c.code, a
          c.code.addImmediateVal info, 0 # (len, p)-pair so len is at index 0

  var tmpStr = getTemp(c, n)
  #    ^ because of aliasing, we always go through a temporary
  let t = typeToIr(c.m, n.typ)
  buildTyped c.code, info, Asgn, t:
    copyTree c.code, tmpStr
    buildTyped c.code, info, Call, t:
      let codegenProc = magicsys.getCompilerProc(c.m.graph, "rawNewString")
      #assert codegenProc != nil, $n & " " & (c.m.graph.config $ n.info)
      let theProc = c.genx newSymNode(codegenProc, n.info)
      copyTree c.code, theProc
      c.code.addSymUse info, tmpLen
  freeTemp c.sm, tmpLen

  for i in 1 ..< n.len:
    let it = n[i]
    let isChar = skipTypes(it.typ, abstractVarRange).kind == tyChar
    buildTyped c.code, info, Call, VoidId:
      let codegenProc = magicsys.getCompilerProc(c.m.graph,
        (if isChar: "appendChar" else: "appendString"))
      #assert codegenProc != nil, $n & " " & (c.m.graph.config $ n.info)
      let theProc = c.genx newSymNode(codegenProc, n.info)
      copyTree c.code, theProc
      buildTyped c.code, info, AddrOf, ptrTypeOf(c.m.nirm.types, t):
        copyTree c.code, tmpStr
      copyTree c.code, args[i-1]
    freeTemp c, args[i-1]

  if isEmpty(d):
    d = tmpStr
  else:
    # XXX Test that this does not cause memory leaks!
    buildTyped c.code, info, Asgn, t:
      copyTree c.code, d
      copyTree c.code, tmpStr

proc genDefault(c: var ProcCon; n: PNode; d: var Value) =
  let m = expandDefault(n.typ, n.info)
  gen c, m, d

proc genWasMoved(c: var ProcCon; n: PNode) =
  let n1 = n[1].skipAddr
  # XXX We need a way to replicate this logic or better yet a better
  # solution for injectdestructors.nim:
  #if c.withinBlockLeaveActions > 0 and notYetAlive(n1):
  var d = c.genx(n1)
  assert not isEmpty(d)
  let m = expandDefault(n1.typ, n1.info)
  gen c, m, d

proc genMove(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  let n1 = n[1].skipAddr
  var a = c.genx(n1)
  if n.len == 4:
    # generated by liftdestructors:
    let src = c.genx(n[2])
    # if ($1.p == $2.p) goto lab1
    let lab1 = newLabel(c.labelGen)

    let n1t = typeToIr(c.m, n1.typ)
    let payloadType = seqPayloadPtrType(c.m.types, c.m.nirm.types, n1.typ)[0]
    buildTyped c.code, info, Select, Bool8Id:
      buildTyped c.code, info, Eq, payloadType:
        buildTyped c.code, info, FieldAt, n1t:
          copyTree c.code, a
          c.code.addImmediateVal info, 1 # (len, p)-pair
        buildTyped c.code, info, FieldAt, n1t:
          copyTree c.code, src
          c.code.addImmediateVal info, 1 # (len, p)-pair

      build c.code, info, SelectPair:
        build c.code, info, SelectValue:
          c.code.boolVal(c.lit.numbers, info, true)
        c.code.gotoLabel info, Goto, lab1

    gen(c, n[3])
    c.patch n, lab1

    buildTyped c.code, info, Asgn, typeToIr(c.m, n1.typ):
      copyTree c.code, a
      copyTree c.code, src

  else:
    if isEmpty(d): d = getTemp(c, n)
    buildTyped c.code, info, Asgn, typeToIr(c.m, n1.typ):
      copyTree c.code, d
      copyTree c.code, a
    var op = getAttachedOp(c.m.graph, n.typ, attachedWasMoved)
    if op == nil or skipTypes(n1.typ, abstractVar+{tyStatic}).kind in {tyOpenArray, tyVarargs}:
      let m = expandDefault(n1.typ, n1.info)
      gen c, m, a
    else:
      var opB = c.genx(newSymNode(op))
      buildTyped c.code, info, Call, typeToIr(c.m, n.typ):
        copyTree c.code, opB
        buildTyped c.code, info, AddrOf, ptrTypeOf(c.m.nirm.types, typeToIr(c.m, n1.typ)):
          copyTree c.code, a

template fieldAt(x: Value; i: int; t: TypeId): Tree =
  var result = default(Tree)
  buildTyped result, info, FieldAt, t:
    copyTree result, x
    result.addImmediateVal info, i
  result

template eqNil(x: Tree; t: TypeId): Tree =
  var result = default(Tree)
  buildTyped result, info, Eq, t:
    copyTree result, x
    result.addNilVal info, t
  result

template eqZero(x: Tree): Tree =
  var result = default(Tree)
  buildTyped result, info, Eq, c.m.nativeIntId:
    copyTree result, x
    result.addIntVal c.lit.numbers, info, c.m.nativeIntId, 0
  result

template bitOp(x: Tree; opc: Opcode; y: int): Tree =
  var result = default(Tree)
  buildTyped result, info, opc, c.m.nativeIntId:
    copyTree result, x
    result.addIntVal c.lit.numbers, info, c.m.nativeIntId, y
  result

proc genDestroySeq(c: var ProcCon; n: PNode; t: PType) =
  let info = toLineInfo(c, n.info)
  let strLitFlag = 1 shl (c.m.graph.config.target.intSize * 8 - 2) # see also NIM_STRLIT_FLAG

  let x = c.genx(n[1])
  let baseType = t.elementType

  let seqType = typeToIr(c.m, t)
  let p = fieldAt(x, 0, seqType)

  # if $1.p != nil and ($1.p.cap and NIM_STRLIT_FLAG) == 0:
  #   alignedDealloc($1.p, NIM_ALIGNOF($2))
  buildIfNot p.eqNil(seqType):
    buildIf fieldAt(Value(p), 0, seqPayloadPtrType(c.m.types, c.m.nirm.types, t)[0]).bitOp(BitAnd, 0).eqZero():
      let codegenProc = getCompilerProc(c.m.graph, "alignedDealloc")
      buildTyped c.code, info, Call, VoidId:
        let theProc = c.genx newSymNode(codegenProc, n.info)
        copyTree c.code, theProc
        copyTree c.code, p
        c.code.addImmediateVal info, int(getAlign(c.config, baseType))

  freeTemp c, x

proc genDestroy(c: var ProcCon; n: PNode) =
  let t = n[1].typ.skipTypes(abstractInst)
  case t.kind
  of tyString:
    var unused = default(Value)
    genUnaryCp(c, n, unused, "nimDestroyStrV1")
  of tySequence:
    genDestroySeq(c, n, t)
  else: discard "nothing to do"

type
  IndexFor = enum
    ForSeq, ForStr, ForOpenArray, ForArray

proc genIndexCheck(c: var ProcCon; n: PNode; a: Value; kind: IndexFor; arr: PType): Value =
  if optBoundsCheck in c.options:
    let info = toLineInfo(c, n.info)
    result = default(Value)
    let idx = genx(c, n)
    build result, info, CheckedIndex:
      result.Tree.addLabel info, CheckedGoto, c.exitLabel
      copyTree result.Tree, idx
      case kind
      of ForSeq, ForStr:
        buildTyped result, info, FieldAt, typeToIr(c.m, arr):
          copyTree result.Tree, a
          result.addImmediateVal info, 0 # (len, p)-pair
      of ForOpenArray:
        buildTyped result, info, FieldAt, typeToIr(c.m, arr):
          copyTree result.Tree, a
          result.addImmediateVal info, 1 # (p, len)-pair
      of ForArray:
        let x = toInt64 lengthOrd(c.config, arr)
        result.addIntVal c.lit.numbers, info, c.m.nativeIntId, x
    freeTemp c, idx
  else:
    result = genx(c, n)

proc addSliceFields(c: var ProcCon; target: var Tree; info: PackedLineInfo;
                    x: Value; n: PNode; arrType: PType) =
  let elemType = arrayPtrTypeOf(c.m.nirm.types, typeToIr(c.m, arrType.elementType))
  case arrType.kind
  of tyString, tySequence:
    let checkKind = if arrType.kind == tyString: ForStr else: ForSeq
    let pay = if checkKind == ForStr: c.m.strPayloadId
              else: seqPayloadPtrType(c.m.types, c.m.nirm.types, arrType)

    let y = genIndexCheck(c, n[2], x, checkKind, arrType)
    let z = genIndexCheck(c, n[3], x, checkKind, arrType)

    buildTyped target, info, ObjConstr, typeToIr(c.m, n.typ):
      target.addImmediateVal info, 0
      buildTyped target, info, AddrOf, elemType:
        buildTyped target, info, DerefArrayAt, pay[1]:
          buildTyped target, info, FieldAt, typeToIr(c.m, arrType):
            copyTree target, x
            target.addImmediateVal info, 1 # (len, p)-pair
          copyTree target, y

      # len:
      target.addImmediateVal info, 1
      buildTyped target, info, Add, c.m.nativeIntId:
        buildTyped target, info, Sub, c.m.nativeIntId:
          copyTree target, z
          copyTree target, y
        target.addIntVal c.lit.numbers, info, c.m.nativeIntId, 1

    freeTemp c, z
    freeTemp c, y
  of tyArray:
    # XXX This evaluates the index check for `y` twice.
    # This check is also still insufficient for non-zero based arrays.
    let y = genIndexCheck(c, n[2], x, ForArray, arrType)
    let z = genIndexCheck(c, n[3], x, ForArray, arrType)

    buildTyped target, info, ObjConstr, typeToIr(c.m, n.typ):
      target.addImmediateVal info, 0
      buildTyped target, info, AddrOf, elemType:
        buildTyped target, info, ArrayAt, typeToIr(c.m, arrType):
          copyTree target, x
          copyTree target, y

      target.addImmediateVal info, 1
      buildTyped target, info, Add, c.m.nativeIntId:
        buildTyped target, info, Sub, c.m.nativeIntId:
          copyTree target, z
          copyTree target, y
        target.addIntVal c.lit.numbers, info, c.m.nativeIntId, 1

    freeTemp c, z
    freeTemp c, y
  of tyOpenArray:
    # XXX This evaluates the index check for `y` twice.
    let y = genIndexCheck(c, n[2], x, ForOpenArray, arrType)
    let z = genIndexCheck(c, n[3], x, ForOpenArray, arrType)
    let pay = openArrayPayloadType(c.m.types, c.m.nirm.types, arrType)

    buildTyped target, info, ObjConstr, typeToIr(c.m, n.typ):
      target.addImmediateVal info, 0
      buildTyped target, info, AddrOf, elemType:
        buildTyped target, info, DerefArrayAt, pay:
          buildTyped target, info, FieldAt, typeToIr(c.m, arrType):
            copyTree target, x
            target.addImmediateVal info, 0 # (p, len)-pair
          copyTree target, y

      target.addImmediateVal info, 1
      buildTyped target, info, Add, c.m.nativeIntId:
        buildTyped target, info, Sub, c.m.nativeIntId:
          copyTree target, z
          copyTree target, y
        target.addIntVal c.lit.numbers, info, c.m.nativeIntId, 1

    freeTemp c, z
    freeTemp c, y
  else:
    raiseAssert "addSliceFields: " & typeToString(arrType)

proc genSlice(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)

  let x = c.genx(n[1])

  let arrType = n[1].typ.skipTypes(abstractVar)

  template body(target) =
    c.addSliceFields target, info, x, n, arrType

  valueIntoDest c, info, d, arrType, body
  freeTemp c, x

proc genMagic(c: var ProcCon; n: PNode; d: var Value; m: TMagic) =
  case m
  of mAnd: c.genAndOr(n, opcFJmp, d)
  of mOr: c.genAndOr(n, opcTJmp, d)
  of mPred, mSubI: c.genBinaryOp(n, d, if optOverflowCheck in c.options: CheckedSub else: Sub)
  of mSucc, mAddI: c.genBinaryOp(n, d, if optOverflowCheck in c.options: CheckedAdd else: Add)
  of mInc:
    unused(c, n, d)
    c.genIncDec(n, if optOverflowCheck in c.options: CheckedAdd else: Add)
  of mDec:
    unused(c, n, d)
    c.genIncDec(n, if optOverflowCheck in c.options: CheckedSub else: Sub)
  of mOrd, mChr, mUnown:
    c.gen(n[1], d)
  of generatedMagics:
    genCall(c, n, d)
  of mNew, mNewFinalize:
    unused(c, n, d)
    c.genNew(n, needsInit = true)
  of mNewSeq:
    unused(c, n, d)
    c.genNewSeq(n)
  of mNewSeqOfCap: c.genNewSeqOfCap(n, d)
  of mNewString, mNewStringOfCap, mExit: c.genCall(n, d)
  of mLengthOpenArray, mLengthArray, mLengthSeq, mLengthStr:
    genArrayLen(c, n, d)
  of mMulI: genBinaryOp(c, n, d, if optOverflowCheck in c.options: CheckedMul else: Mul)
  of mDivI: genBinaryOp(c, n, d, if optOverflowCheck in c.options: CheckedDiv else: Div)
  of mModI: genBinaryOp(c, n, d, if optOverflowCheck in c.options: CheckedMod else: Mod)
  of mAddF64: genBinaryOp(c, n, d, Add)
  of mSubF64: genBinaryOp(c, n, d, Sub)
  of mMulF64: genBinaryOp(c, n, d, Mul)
  of mDivF64: genBinaryOp(c, n, d, Div)
  of mShrI: genBinaryOp(c, n, d, BitShr)
  of mShlI: genBinaryOp(c, n, d, BitShl)
  of mAshrI: genBinaryOp(c, n, d, BitShr)
  of mBitandI: genBinaryOp(c, n, d, BitAnd)
  of mBitorI: genBinaryOp(c, n, d, BitOr)
  of mBitxorI: genBinaryOp(c, n, d, BitXor)
  of mAddU: genBinaryOp(c, n, d, Add)
  of mSubU: genBinaryOp(c, n, d, Sub)
  of mMulU: genBinaryOp(c, n, d, Mul)
  of mDivU: genBinaryOp(c, n, d, Div)
  of mModU: genBinaryOp(c, n, d, Mod)
  of mEqI, mEqB, mEqEnum, mEqCh:
    genCmpOp(c, n, d, Eq)
  of mLeI, mLeEnum, mLeCh, mLeB:
    genCmpOp(c, n, d, Le)
  of mLtI, mLtEnum, mLtCh, mLtB:
    genCmpOp(c, n, d, Lt)
  of mEqF64: genCmpOp(c, n, d, Eq)
  of mLeF64: genCmpOp(c, n, d, Le)
  of mLtF64: genCmpOp(c, n, d, Lt)
  of mLePtr, mLeU: genCmpOp(c, n, d, Le)
  of mLtPtr, mLtU: genCmpOp(c, n, d, Lt)
  of mEqProc, mEqRef:
    genCmpOp(c, n, d, Eq)
  of mXor: genBinaryOp(c, n, d, BitXor)
  of mNot: genUnaryOp(c, n, d, BoolNot)
  of mUnaryMinusI, mUnaryMinusI64:
    genUnaryMinus(c, n, d)
    #genNarrow(c, n, d)
  of mUnaryMinusF64: genUnaryMinus(c, n, d)
  of mUnaryPlusI, mUnaryPlusF64: gen(c, n[1], d)
  of mBitnotI:
    genUnaryOp(c, n, d, BitNot)
    when false:
      # XXX genNarrowU modified, do not narrow signed types
      let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
      let size = getSize(c.config, t)
      if t.kind in {tyUInt8..tyUInt32} or (t.kind == tyUInt and size < 8):
        c.gABC(n, opcNarrowU, d, TRegister(size*8))
  of mStrToStr, mEnsureMove: c.gen n[1], d
  of mBoolToStr: genUnaryCp(c, n, d, "nimBoolToStr")
  of mCharToStr: genUnaryCp(c, n, d, "nimCharToStr")
  of mCStrToStr: genUnaryCp(c, n, d, "cstrToNimstr")
  of mEnumToStr: genEnumToStr(c, n, d)

  of mEqStr: genBinaryCp(c, n, d, "eqStrings")
  of mEqCString: genCall(c, n, d)
  of mLeStr: genBinaryCp(c, n, d, "leStrings")
  of mLtStr: genBinaryCp(c, n, d, "ltStrings")

  of mSetLengthStr:
    unused(c, n, d)
    let nb = copyTree(n)
    nb[1] = makeAddr(nb[1], c.m.idgen)
    genBinaryCp(c, nb, d, "setLengthStrV2")

  of mSetLengthSeq:
    unused(c, n, d)
    let nb = copyTree(n)
    nb[1] = makeAddr(nb[1], c.m.idgen)
    genCall(c, nb, d)

  of mSwap:
    unused(c, n, d)
    c.gen(lowerSwap(c.m.graph, n, c.m.idgen,
      if c.prc == nil: c.m.module else: c.prc), d)
  of mParseBiggestFloat:
    genCall c, n, d
  of mHigh:
    c.genHigh n, d

  of mEcho:
    unused(c, n, d)
    genUnaryCp c, n, d, "echoBinSafe"

  of mAppendStrCh:
    unused(c, n, d)
    let nb = copyTree(n)
    nb[1] = makeAddr(nb[1], c.m.idgen)
    genBinaryCp(c, nb, d, "nimAddCharV1")
  of mMinI, mMaxI, mAbsI, mDotDot:
    c.genCall(n, d)
  of mSizeOf:
    localError(c.config, n.info, sizeOfLikeMsg("sizeof"))
  of mAlignOf:
    localError(c.config, n.info, sizeOfLikeMsg("alignof"))
  of mOffsetOf:
    localError(c.config, n.info, sizeOfLikeMsg("offsetof"))
  of mRunnableExamples:
    discard "just ignore any call to runnableExamples"
  of mOf: genOf(c, n, d)
  of mAppendStrStr:
    unused(c, n, d)
    let nb = copyTree(n)
    nb[1] = makeAddr(nb[1], c.m.idgen)
    genBinaryCp(c, nb, d, "nimAddStrV1")
  of mAppendSeqElem:
    unused(c, n, d)
    let nb = copyTree(n)
    nb[1] = makeAddr(nb[1], c.m.idgen)
    genCall(c, nb, d)
  of mIsNil: genIsNil(c, n, d)
  of mInSet: genInSet(c, n, d)
  of mCard: genCard(c, n, d)
  of mEqSet: genEqSet(c, n, d)
  of mLeSet: genLeSet(c, n, d)
  of mLtSet: genLtSet(c, n, d)
  of mMulSet: genBinarySet(c, n, d, m)
  of mPlusSet: genBinarySet(c, n, d, m)
  of mMinusSet: genBinarySet(c, n, d, m)
  of mIncl, mExcl:
    unused(c, n, d)
    genInclExcl(c, n, m)
  of mConStrStr: genStrConcat(c, n, d)
  of mDefault, mZeroDefault:
    genDefault c, n, d
  of mMove: genMove(c, n, d)
  of mWasMoved:
    unused(c, n, d)
    genWasMoved(c, n)
  of mDestroy: genDestroy(c, n)
  #of mAccessEnv: unaryExpr(d, n, d, "$1.ClE_0")
  #of mAccessTypeField: genAccessTypeField(c, n, d)
  of mSlice: genSlice(c, n, d)
  of mTrace: discard "no code to generate"
  else:
    # mGCref, mGCunref: unused by ORC
    globalError(c.config, n.info, "cannot generate code for: " & $m)

proc canElimAddr(n: PNode; idgen: IdGenerator): PNode =
  result = nil
  case n[0].kind
  of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
    var m = n[0][0]
    if m.kind in {nkDerefExpr, nkHiddenDeref}:
      # addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
      result = copyNode(n[0])
      result.add m[0]
      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
        result.typ = n.typ
      elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
        result.typ = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, idgen)
  of nkHiddenStdConv, nkHiddenSubConv, nkConv:
    var m = n[0][1]
    if m.kind in {nkDerefExpr, nkHiddenDeref}:
      # addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
      result = copyNode(n[0])
      result.add n[0][0]
      result.add m[0]
      if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
        result.typ = n.typ
      elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
        result.typ = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, idgen)
  else:
    if n[0].kind in {nkDerefExpr, nkHiddenDeref}:
      # addr ( deref ( x )) --> x
      result = n[0][0]

proc genAddr(c: var ProcCon; n: PNode; d: var Value, flags: GenFlags) =
  if (let m = canElimAddr(n, c.m.idgen); m != nil):
    gen(c, m, d, flags)
    return

  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[0], flags)
  template body(target) =
    buildTyped target, info, AddrOf, typeToIr(c.m, n.typ):
      copyTree target, tmp

  valueIntoDest c, info, d, n.typ, body
  freeTemp c, tmp

proc genDeref(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
  let info = toLineInfo(c, n.info)
  let tmp = c.genx(n[0], flags)
  template body(target) =
    buildTyped target, info, Load, typeToIr(c.m, n.typ):
      copyTree target, tmp

  valueIntoDest c, info, d, n.typ, body
  freeTemp c, tmp

proc addAddrOfFirstElem(c: var ProcCon; target: var Tree; info: PackedLineInfo; tmp: Value; typ: PType) =
  let arrType = typ.skipTypes(abstractVar)
  let elemType = arrayPtrTypeOf(c.m.nirm.types, typeToIr(c.m, arrType.elementType))
  case arrType.kind
  of tyString:
    let t = typeToIr(c.m, typ)
    target.addImmediateVal info, 0
    buildTyped target, info, AddrOf, elemType:
      buildTyped target, info, DerefArrayAt, c.m.strPayloadId[1]:
        buildTyped target, info, FieldAt, typeToIr(c.m, arrType):
          copyTree target, tmp
          target.addImmediateVal info, 1 # (len, p)-pair
        target.addIntVal c.lit.numbers, info, c.m.nativeIntId, 0
    # len:
    target.addImmediateVal info, 1
    buildTyped target, info, FieldAt, typeToIr(c.m, arrType):
      copyTree target, tmp
      target.addImmediateVal info, 0 # (len, p)-pair so len is at index 0

  of tySequence:
    let t = typeToIr(c.m, typ)
    target.addImmediateVal info, 0
    buildTyped target, info, AddrOf, elemType:
      buildTyped target, info, DerefArrayAt, seqPayloadPtrType(c.m.types, c.m.nirm.types, typ)[1]:
        buildTyped target, info, FieldAt, typeToIr(c.m, arrType):
          copyTree target, tmp
          target.addImmediateVal info, 1 # (len, p)-pair
        target.addIntVal c.lit.numbers, info, c.m.nativeIntId, 0
    # len:
    target.addImmediateVal info, 1
    buildTyped target, info, FieldAt, typeToIr(c.m, arrType):
      copyTree target, tmp
      target.addImmediateVal info, 0 # (len, p)-pair so len is at index 0

  of tyArray:
    let t = typeToIr(c.m, arrType)
    target.addImmediateVal info, 0
    buildTyped target, info, AddrOf, elemType:
      buildTyped target, info, ArrayAt, t:
        copyTree target, tmp
        target.addIntVal c.lit.numbers, info, c.m.nativeIntId, 0
    target.addImmediateVal info, 1
    target.addIntVal(c.lit.numbers, info, c.m.nativeIntId, toInt lengthOrd(c.config, arrType))
  else:
    raiseAssert "addAddrOfFirstElem: " & typeToString(typ)

proc genToOpenArrayConv(c: var ProcCon; arg: PNode; d: var Value; flags: GenFlags; destType: PType) =
  let info = toLineInfo(c, arg.info)
  let tmp = c.genx(arg, flags)
  let arrType = destType.skipTypes(abstractVar)
  template body(target) =
    buildTyped target, info, ObjConstr, typeToIr(c.m, arrType):
      c.addAddrOfFirstElem target, info, tmp, arg.typ

  valueIntoDest c, info, d, arrType, body
  freeTemp c, tmp

proc genConv(c: var ProcCon; n, arg: PNode; d: var Value; flags: GenFlags; opc: Opcode) =
  let targetType = n.typ.skipTypes({tyDistinct})
  let argType = arg.typ.skipTypes({tyDistinct})

  if sameBackendType(targetType, argType) or (
      argType.kind == tyProc and targetType.kind == argType.kind):
    # don't do anything for lambda lifting conversions:
    gen c, arg, d
    return

  if opc != Cast and targetType.skipTypes({tyVar, tyLent}).kind in {tyOpenArray, tyVarargs} and
      argType.skipTypes({tyVar, tyLent}).kind notin {tyOpenArray, tyVarargs}:
    genToOpenArrayConv c, arg, d, flags, n.typ
    return

  let info = toLineInfo(c, n.info)
  let tmp = c.genx(arg, flags)
  template body(target) =
    buildTyped target, info, opc, typeToIr(c.m, n.typ):
      if opc == CheckedObjConv:
        target.addLabel info, CheckedGoto, c.exitLabel
      copyTree target, tmp

  valueIntoDest c, info, d, n.typ, body
  freeTemp c, tmp

proc genObjOrTupleConstr(c: var ProcCon; n: PNode; d: var Value; t: PType) =
  # XXX x = (x.old, 22)  produces wrong code ... stupid self assignments
  let info = toLineInfo(c, n.info)
  template body(target) =
    buildTyped target, info, ObjConstr, typeToIr(c.m, t):
      for i in ord(n.kind == nkObjConstr)..<n.len:
        let it = n[i]
        if it.kind == nkExprColonExpr:
          genField(c, it[0], Value target)
          let tmp = c.genx(it[1])
          copyTree target, tmp
          c.freeTemp(tmp)
        else:
          let tmp = c.genx(it)
          target.addImmediateVal info, i
          copyTree target, tmp
          c.freeTemp(tmp)

      if isException(t):
        target.addImmediateVal info, 1 # "name" field is at position after the "parent". See system.nim
        target.addStrVal c.lit.strings, info, t.skipTypes(abstractInst).sym.name.s

  constrIntoDest c, info, d, t, body

proc genRefObjConstr(c: var ProcCon; n: PNode; d: var Value) =
  if isEmpty(d): d = getTemp(c, n)
  let info = toLineInfo(c, n.info)
  let refType = n.typ.skipTypes(abstractInstOwned)
  let objType = refType.elementType

  rawGenNew(c, d, refType, n.info, needsInit = nfAllFieldsSet notin n.flags)
  var deref = default(Value)
  deref.buildTyped info, Load, typeToIr(c.m, objType):
    deref.Tree.copyTree d
  genObjOrTupleConstr c, n, deref, objType

proc genSeqConstr(c: var ProcCon; n: PNode; d: var Value) =
  if isEmpty(d): d = getTemp(c, n)

  let info = toLineInfo(c, n.info)
  let seqtype = skipTypes(n.typ, abstractVarRange)
  let baseType = seqtype.elementType

  var b = default(Value)
  b.addIntVal c.lit.numbers, info, c.m.nativeIntId, n.len

  genNewSeqPayload(c, info, d, b, seqtype)

  for i in 0..<n.len:
    var dd = default(Value)
    buildTyped dd, info, DerefArrayAt, seqPayloadPtrType(c.m.types, c.m.nirm.types, seqtype)[1]:
      buildTyped dd, info, FieldAt, typeToIr(c.m, seqtype):
        copyTree Tree(dd), d
        dd.addImmediateVal info, 1 # (len, p)-pair
      dd.addIntVal c.lit.numbers, info, c.m.nativeIntId, i
    gen(c, n[i], dd)

  freeTemp c, d

proc genArrayConstr(c: var ProcCon; n: PNode, d: var Value) =
  let seqType = n.typ.skipTypes(abstractVar-{tyTypeDesc})
  if seqType.kind == tySequence:
    genSeqConstr(c, n, d)
    return

  let info = toLineInfo(c, n.info)
  template body(target) =
    buildTyped target, info, ArrayConstr, typeToIr(c.m, n.typ):
      for i in 0..<n.len:
        let tmp = c.genx(n[i])
        copyTree target, tmp
        c.freeTemp(tmp)

  constrIntoDest c, info, d, n.typ, body

proc genAsgn2(c: var ProcCon; a, b: PNode) =
  assert a != nil
  assert b != nil
  var d = c.genx(a)
  c.gen b, d

proc irModule(c: var ProcCon; owner: PSym): string =
  #if owner == c.m.module: "" else:
  customPath(toFullPath(c.config, owner.info))

proc fromForeignModule(c: ProcCon; s: PSym): bool {.inline.} =
  result = ast.originatingModule(s) != c.m.module and not c.m.noModularity

proc genForeignVar(c: var ProcCon; s: PSym) =
  var opc: Opcode
  if s.kind == skConst:
    opc = SummonConst
  elif sfThread in s.flags:
    opc = SummonThreadLocal
  else:
    assert sfGlobal in s.flags
    opc = SummonGlobal
  let t = typeToIr(c.m, s.typ)
  let info = toLineInfo(c, s.info)
  build c.code, info, ForeignDecl:
    buildTyped c.code, info, opc, t:
      build c.code, info, ModuleSymUse:
        c.code.addStrVal c.lit.strings, info, irModule(c, ast.originatingModule(s))
        c.code.addImmediateVal info, s.itemId.item.int

proc genVarSection(c: var ProcCon; n: PNode) =
  for a in n:
    if a.kind == nkCommentStmt: continue
    #assert(a[0].kind == nkSym) can happen for transformed vars
    if a.kind == nkVarTuple:
      c.gen(lowerTupleUnpacking(c.m.graph, a, c.m.idgen, c.prc))
    else:
      var vn = a[0]
      if vn.kind == nkPragmaExpr: vn = vn[0]
      if vn.kind == nkSym:
        let s = vn.sym
        if s.kind == skConst:
          if dontInlineConstant(n, s.astdef):
            let symId = toSymId(c, s)
            c.m.nirm.symnames[symId] = c.lit.strings.getOrIncl(s.name.s)
            let val = c.genx(s.astdef)
            let info = toLineInfo(c, a.info)
            buildTyped c.code, info, SummonConst, typeToIr(c.m, s.typ):
              c.code.addSymDef info, symId
              c.code.copyTree val
            freeTemp c, val
        else:
          var opc: Opcode
          if sfThread in s.flags:
            opc = SummonThreadLocal
          elif sfGlobal in s.flags:
            opc = SummonGlobal
          else:
            opc = Summon
          #assert t.int >= 0, typeToString(s.typ) & (c.config $ n.info)
          let symId = toSymId(c, s)
          c.code.addSummon toLineInfo(c, a.info), symId, typeToIr(c.m, s.typ), opc
          c.m.nirm.symnames[symId] = c.lit.strings.getOrIncl(s.name.s)
          if a[2].kind != nkEmpty:
            genAsgn2(c, vn, a[2])
      else:
        if a[2].kind == nkEmpty:
          genAsgn2(c, vn, expandDefault(vn.typ, vn.info))
        else:
          genAsgn2(c, vn, a[2])

proc genAsgn(c: var ProcCon; n: PNode) =
  var d = c.genx(n[0])
  c.gen n[1], d

proc convStrToCStr(c: var ProcCon; n: PNode; d: var Value) =
  genUnaryCp(c, n, d, "nimToCStringConv", argAt = 0)

proc convCStrToStr(c: var ProcCon; n: PNode; d: var Value) =
  genUnaryCp(c, n, d, "cstrToNimstr", argAt = 0)

proc genRdVar(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
  let info = toLineInfo(c, n.info)
  let s = n.sym
  if fromForeignModule(c, s):
    if s.kind in {skVar, skConst, skLet} and not c.m.pendingVarsAsSet.containsOrIncl(s.itemId):
      c.m.pendingVars.add s

    template body(target) =
      build target, info, ModuleSymUse:
        target.addStrVal c.lit.strings, info, irModule(c, ast.originatingModule(s))
        target.addImmediateVal info, s.itemId.item.int

    valueIntoDest c, info, d, s.typ, body
  else:
    template body(target) =
      target.addSymUse info, toSymId(c, s)
    valueIntoDest c, info, d, s.typ, body

proc genSym(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags = {}) =
  let s = n.sym
  case s.kind
  of skConst:
    if dontInlineConstant(n, s.astdef):
      genRdVar(c, n, d, flags)
    else:
      gen(c, s.astdef, d, flags)
  of skVar, skForVar, skTemp, skLet, skResult, skParam:
    genRdVar(c, n, d, flags)
  of skProc, skFunc, skConverter, skMethod, skIterator:
    if not c.m.noModularity:
      # anon and generic procs have no AST so we need to remember not to forget
      # to emit these:
      if not c.m.processedProcs.contains(s.itemId):
        if not c.m.pendingProcsAsSet.containsOrIncl(s.itemId):
          c.m.pendingProcs.add s
    genRdVar(c, n, d, flags)
  of skEnumField:
    let info = toLineInfo(c, n.info)
    template body(target) =
      target.addIntVal c.lit.numbers, info, typeToIr(c.m, n.typ), s.position
    valueIntoDest c, info, d, n.typ, body
  else:
    localError(c.config, n.info, "cannot generate code for: " & s.name.s)

proc genNumericLit(c: var ProcCon; n: PNode; d: var Value; bits: int64) =
  let info = toLineInfo(c, n.info)
  template body(target) =
    target.addIntVal c.lit.numbers, info, typeToIr(c.m, n.typ), bits
  valueIntoDest c, info, d, n.typ, body

proc genStringLit(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  template body(target) =
    target.addStrVal c.lit.strings, info, n.strVal
  valueIntoDest c, info, d, n.typ, body

proc genNilLit(c: var ProcCon; n: PNode; d: var Value) =
  let info = toLineInfo(c, n.info)
  template body(target) =
    target.addNilVal info, typeToIr(c.m, n.typ)
  valueIntoDest c, info, d, n.typ, body

proc genRangeCheck(c: var ProcCon; n: PNode; d: var Value) =
  if optRangeCheck in c.options:
    let info = toLineInfo(c, n.info)
    let tmp = c.genx n[0]
    let a = c.genx n[1]
    let b = c.genx n[2]
    template body(target) =
      buildTyped target, info, CheckedRange, typeToIr(c.m, n.typ):
        target.addLabel info, CheckedGoto, c.exitLabel
        copyTree target, tmp
        copyTree target, a
        copyTree target, b
    valueIntoDest c, info, d, n.typ, body
    freeTemp c, tmp
    freeTemp c, a
    freeTemp c, b
  else:
    gen c, n[0], d

proc genArrAccess(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
  let arrayType = n[0].typ.skipTypes(abstractVarRange-{tyTypeDesc})
  let arrayKind = arrayType.kind
  let info = toLineInfo(c, n.info)
  case arrayKind
  of tyString:
    let a = genx(c, n[0], flags)
    let b = genIndexCheck(c, n[1], a, ForStr, arrayType)
    let t = typeToIr(c.m, n.typ)
    template body(target) =
      buildTyped target, info, DerefArrayAt, c.m.strPayloadId[1]:
        buildTyped target, info, FieldAt, typeToIr(c.m, arrayType):
          copyTree target, a
          target.addImmediateVal info, 1 # (len, p)-pair
        copyTree target, b
    intoDest d, info, t, body
    freeTemp c, b
    freeTemp c, a

  of tyCstring, tyPtr, tyUncheckedArray:
    let a = genx(c, n[0], flags)
    let b = genx(c, n[1])
    template body(target) =
      buildTyped target, info, DerefArrayAt, typeToIr(c.m, arrayType):
        copyTree target, a
        copyTree target, b
    valueIntoDest c, info, d, n.typ, body

    freeTemp c, b
    freeTemp c, a
  of tyTuple:
    let a = genx(c, n[0], flags)
    let b = int n[1].intVal
    template body(target) =
      buildTyped target, info, FieldAt, typeToIr(c.m, arrayType):
        copyTree target, a
        target.addImmediateVal info, b
    valueIntoDest c, info, d, n.typ, body

    freeTemp c, a
  of tyOpenArray, tyVarargs:
    let a = genx(c, n[0], flags)
    let b = genIndexCheck(c, n[1], a, ForOpenArray, arrayType)
    let t = typeToIr(c.m, n.typ)
    template body(target) =
      buildTyped target, info, DerefArrayAt, openArrayPayloadType(c.m.types, c.m.nirm.types, n[0].typ):
        buildTyped target, info, FieldAt, typeToIr(c.m, arrayType):
          copyTree target, a
          target.addImmediateVal info, 0 # (p, len)-pair
        copyTree target, b
    intoDest d, info, t, body

    freeTemp c, b
    freeTemp c, a
  of tyArray:
    let a = genx(c, n[0], flags)
    var b = default(Value)
    genIndex(c, n[1], n[0].typ, b)

    template body(target) =
      buildTyped target, info, ArrayAt, typeToIr(c.m, arrayType):
        copyTree target, a
        copyTree target, b
    valueIntoDest c, info, d, n.typ, body
    freeTemp c, b
    freeTemp c, a
  of tySequence:
    let a = genx(c, n[0], flags)
    let b = genIndexCheck(c, n[1], a, ForSeq, arrayType)
    let t = typeToIr(c.m, n.typ)
    template body(target) =
      buildTyped target, info, DerefArrayAt, seqPayloadPtrType(c.m.types, c.m.nirm.types, n[0].typ)[1]:
        buildTyped target, info, FieldAt, t:
          copyTree target, a
          target.addImmediateVal info, 1 # (len, p)-pair
        copyTree target, b
    intoDest d, info, t, body
    freeTemp c, b
    freeTemp c, a
  else:
    localError c.config, n.info, "invalid type for nkBracketExpr: " & $arrayKind

proc genObjAccess(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags) =
  let info = toLineInfo(c, n.info)

  var n0 = n[0]
  var opc = FieldAt
  if n0.kind == nkDotExpr:
    # obj[].a --> DerefFieldAt instead of FieldAt:
    n0 = n[0]
    opc = DerefFieldAt

  let a = genx(c, n0, flags)

  template body(target) =
    buildTyped target, info, opc, typeToIr(c.m, n0.typ):
      copyTree target, a
      genField c, n[1], Value(target)

  valueIntoDest c, info, d, n.typ, body
  freeTemp c, a

proc genParams(c: var ProcCon; params: PNode; prc: PSym): PSym =
  result = nil
  if params.len > 0 and resultPos < prc.ast.len:
    let resNode = prc.ast[resultPos]
    result = resNode.sym # get result symbol
    c.code.addSummon toLineInfo(c, result.info), toSymId(c, result),
      typeToIr(c.m, result.typ), SummonResult
  elif prc.typ.len > 0 and not isEmptyType(prc.typ.returnType) and not isCompileTimeOnly(prc.typ.returnType):
    # happens for procs without bodies:
    let t = typeToIr(c.m, prc.typ.returnType)
    let tmp = allocTemp(c, t)
    c.code.addSummon toLineInfo(c, params.info), tmp, t, SummonResult

  for i in 1..<params.len:
    let s = params[i].sym
    if not isCompileTimeOnly(s.typ):
      let t = typeToIr(c.m, s.typ)
      assert t.int != -1, typeToString(s.typ)
      let symId = toSymId(c, s)
      c.code.addSummon toLineInfo(c, params[i].info), symId, t, SummonParam
      c.m.nirm.symnames[symId] = c.lit.strings.getOrIncl(s.name.s)

proc addCallConv(c: var ProcCon; info: PackedLineInfo; callConv: TCallingConvention) =
  template ann(s: untyped) = c.code.addPragmaId info, s
  case callConv
  of ccNimCall, ccFastCall, ccClosure: ann FastCall
  of ccStdCall: ann StdCall
  of ccCDecl: ann CDeclCall
  of ccSafeCall: ann SafeCall
  of ccSysCall: ann SysCall
  of ccInline: ann InlineCall
  of ccNoInline: ann NoinlineCall
  of ccThisCall: ann ThisCall
  of ccNoConvention, ccMember: ann NoCall

proc genProc(cOuter: var ProcCon; prc: PSym) =
  if prc.magic notin generatedMagics: return
  if cOuter.m.processedProcs.containsOrIncl(prc.itemId):
    return
  #assert cOuter.m.inProc == 0, " in nested proc! " & prc.name.s
  if cOuter.m.inProc > 0:
    if not cOuter.m.pendingProcsAsSet.containsOrIncl(prc.itemId):
      cOuter.m.pendingProcs.add prc
    return
  inc cOuter.m.inProc

  var c = initProcCon(cOuter.m, prc, cOuter.m.graph.config)
  let body =
    if not fromForeignModule(c, prc):
      transformBody(c.m.graph, c.m.idgen, prc, {useCache, keepOpenArrayConversions})
    else:
      nil

  let info = toLineInfo(c, prc.info)
  build c.code, info, (if body != nil: ProcDecl else: ForeignProcDecl):
    if body != nil:
      let symId = toSymId(c, prc)
      addSymDef c.code, info, symId
      c.m.nirm.symnames[symId] = c.lit.strings.getOrIncl(prc.name.s)
    else:
      build c.code, info, ModuleSymUse:
        c.code.addStrVal c.lit.strings, info, irModule(c, ast.originatingModule(prc))
        c.code.addImmediateVal info, prc.itemId.item.int
    addCallConv c, info, prc.typ.callConv
    if sfCompilerProc in prc.flags:
      build c.code, info, PragmaPair:
        c.code.addPragmaId info, CoreName
        c.code.addStrVal c.lit.strings, info, prc.name.s
    if {sfImportc, sfExportc} * prc.flags != {}:
      build c.code, info, PragmaPair:
        c.code.addPragmaId info, ExternName
        c.code.addStrVal c.lit.strings, info, prc.loc.r
      if sfImportc in prc.flags:
        if lfHeader in prc. loc.flags:
          assert(prc. annex != nil)
          let str = getStr(prc. annex.path)
          build c.code, info, PragmaPair:
            c.code.addPragmaId info, HeaderImport
            c.code.addStrVal c.lit.strings, info, str
        elif lfDynamicLib in prc. loc.flags:
          assert(prc. annex != nil)
          let str = getStr(prc. annex.path)
          build c.code, info, PragmaPair:
            c.code.addPragmaId info, DllImport
            c.code.addStrVal c.lit.strings, info, str
      elif sfExportc in prc.flags:
        if lfDynamicLib in prc. loc.flags:
          c.code.addPragmaId info, DllExport
        else:
          c.code.addPragmaId info, ObjExport

    let resultSym = genParams(c, prc.typ.n, prc)
    if body != nil:
      gen(c, body)
      patch c, body, c.exitLabel
      if resultSym != nil:
        build c.code, info, Ret:
          c.code.addSymUse info, toSymId(c, resultSym)
      else:
        build c.code, info, Ret:
          c.code.addNop info

  #copyTree cOuter.code, c.code
  dec cOuter.m.inProc

proc genProc(cOuter: var ProcCon; n: PNode) =
  if n.len == 0 or n[namePos].kind != nkSym: return
  let prc = n[namePos].sym
  if isGenericRoutineStrict(prc) or isCompileTimeProc(prc) or sfForward in prc.flags: return
  genProc cOuter, prc

proc genClosureCall(c: var ProcCon; n: PNode; d: var Value) =
  let typ = skipTypes(n[0].typ, abstractInstOwned)
  if tfIterator in typ.flags:
    const PatIter = "$1.ClP_0($3, $1.ClE_0)" # we know the env exists

  else:
    const PatProc = "$1.ClE_0? $1.ClP_0($3, $1.ClE_0):(($4)($1.ClP_0))($2)"


proc genComplexCall(c: var ProcCon; n: PNode; d: var Value) =
  if n[0].typ != nil and n[0].typ.skipTypes({tyGenericInst, tyAlias, tySink, tyOwned}).callConv == ccClosure:
    # XXX genClosureCall p, n, d
    genCall c, n, d
  else:
    genCall c, n, d

proc gen(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags = {}) =
  when defined(nimCompilerStacktraceHints):
    setFrameMsg c.config$n.info & " " & $n.kind & " " & $flags
  case n.kind
  of nkSym: genSym(c, n, d, flags)
  of nkCallKinds:
    if n[0].kind == nkSym:
      let s = n[0].sym
      if s.magic != mNone:
        genMagic(c, n, d, s.magic)
      elif s.kind == skMethod:
        localError(c.config, n.info, "cannot call method " & s.name.s &
          " at compile time")
      else:
        genComplexCall(c, n, d)
    else:
      genComplexCall(c, n, d)
  of nkCharLit..nkInt64Lit, nkUIntLit..nkUInt64Lit:
    genNumericLit(c, n, d, n.intVal)
  of nkFloatLit..nkFloat128Lit:
    genNumericLit(c, n, d, cast[int64](n.floatVal))
  of nkStrLit..nkTripleStrLit:
    genStringLit(c, n, d)
  of nkNilLit:
    if not n.typ.isEmptyType: genNilLit(c, n, d)
    else: unused(c, n, d)
  of nkAsgn, nkFastAsgn, nkSinkAsgn:
    unused(c, n, d)
    genAsgn(c, n)
  of nkDotExpr: genObjAccess(c, n, d, flags)
  of nkCheckedFieldExpr: genObjAccess(c, n[0], d, flags)
  of nkBracketExpr: genArrAccess(c, n, d, flags)
  of nkDerefExpr, nkHiddenDeref: genDeref(c, n, d, flags)
  of nkAddr, nkHiddenAddr: genAddr(c, n, d, flags)
  of nkIfStmt, nkIfExpr: genIf(c, n, d)
  of nkWhenStmt:
    # This is "when nimvm" node. Chose the first branch.
    gen(c, n[0][1], d)
  of nkCaseStmt: genCase(c, n, d)
  of nkWhileStmt:
    unused(c, n, d)
    genWhile(c, n)
  of nkBlockExpr, nkBlockStmt: genBlock(c, n, d)
  of nkReturnStmt: genReturn(c, n)
  of nkRaiseStmt: genRaise(c, n)
  of nkBreakStmt: genBreak(c, n)
  of nkTryStmt, nkHiddenTryStmt: genTry(c, n, d)
  of nkStmtList:
    #unused(c, n, d)
    # XXX Fix this bug properly, lexim triggers it
    for x in n: gen(c, x)
  of nkStmtListExpr:
    for i in 0..<n.len-1: gen(c, n[i])
    gen(c, n[^1], d, flags)
  of nkPragmaBlock:
    gen(c, n.lastSon, d, flags)
  of nkDiscardStmt:
    unused(c, n, d)
    gen(c, n[0], d)
  of nkHiddenStdConv, nkHiddenSubConv, nkConv:
    genConv(c, n, n[1], d, flags, NumberConv) # misnomer?
  of nkObjDownConv:
    genConv(c, n, n[0], d, flags, ObjConv)
  of nkObjUpConv:
    genConv(c, n, n[0], d, flags, CheckedObjConv)
  of nkVarSection, nkLetSection, nkConstSection:
    unused(c, n, d)
    genVarSection(c, n)
  of nkLambdaKinds:
    #let s = n[namePos].sym
    #discard genProc(c, s)
    gen(c, newSymNode(n[namePos].sym), d)
  of nkChckRangeF, nkChckRange64, nkChckRange:
    genRangeCheck(c, n, d)
  of declarativeDefs - {nkIteratorDef}:
    unused(c, n, d)
    genProc(c, n)
  of nkEmpty, nkCommentStmt, nkTypeSection, nkPragma,
     nkTemplateDef, nkIncludeStmt, nkImportStmt, nkFromStmt, nkExportStmt,
     nkMixinStmt, nkBindStmt, nkMacroDef, nkIteratorDef:
    unused(c, n, d)
  of nkStringToCString: convStrToCStr(c, n, d)
  of nkCStringToString: convCStrToStr(c, n, d)
  of nkBracket: genArrayConstr(c, n, d)
  of nkCurly: genSetConstr(c, n, d)
  of nkObjConstr:
    if n.typ.skipTypes(abstractInstOwned).kind == tyRef:
      genRefObjConstr(c, n, d)
    else:
      genObjOrTupleConstr(c, n, d, n.typ)
  of nkPar, nkClosure, nkTupleConstr:
    genObjOrTupleConstr(c, n, d, n.typ)
  of nkCast:
    genConv(c, n, n[1], d, flags, Cast)
  of nkComesFrom:
    discard "XXX to implement for better stack traces"
  #of nkState: genState(c, n)
  #of nkGotoState: genGotoState(c, n)
  #of nkBreakState: genBreakState(c, n, d)
  else:
    localError(c.config, n.info, "cannot generate IR code for " & $n)

proc genPendingProcs(c: var ProcCon) =
  while c.m.pendingProcs.len > 0 or c.m.pendingVars.len > 0:
    let procs = move(c.m.pendingProcs)
    for v in procs:
      genProc(c, v)
    let vars = move(c.m.pendingVars)
    for v in vars:
      genForeignVar(c, v)

proc genStmt*(c: var ProcCon; n: PNode): NodePos =
  result = NodePos c.code.len
  var d = default(Value)
  c.gen(n, d)
  unused c, n, d
  genPendingProcs c

proc genExpr*(c: var ProcCon; n: PNode, requiresValue = true): int =
  result = c.code.len
  var d = default(Value)
  c.gen(n, d)
  genPendingProcs c
  if isEmpty d:
    if requiresValue:
      globalError(c.config, n.info, "VM problem: d register is not set")
