From eb8824d71cb36be8a9558bf572606a24825bb33b Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Sun, 5 Nov 2023 20:25:25 +0100 Subject: [PATCH] NIR: C codegen, WIP (#22903) --- compiler/ic/bitabs.nim | 4 +- compiler/ic/rodfiles.nim | 1 + compiler/nir/ast2ir.nim | 161 ++++--- compiler/nir/cir.nim | 881 +++++++++++++++++++++++++++++++++++++- compiler/nir/nirc.nim | 15 +- compiler/nir/nirfiles.nim | 10 + compiler/nir/nirinsts.nim | 34 +- compiler/nir/nirtypes.nim | 35 +- compiler/nir/nirvm.nim | 2 +- compiler/nir/types2ir.nim | 21 +- 10 files changed, 1072 insertions(+), 92 deletions(-) diff --git a/compiler/ic/bitabs.nim b/compiler/ic/bitabs.nim index 65b1e5a0ed076..c8798e0ca4bf2 100644 --- a/compiler/ic/bitabs.nim +++ b/compiler/ic/bitabs.nim @@ -36,9 +36,7 @@ proc mustRehash(length, counter: int): bool {.inline.} = result = (length * 2 < counter * 3) or (length - counter < 4) const - idStart = 256 ## - ## Ids do not start with 0 but with this value. The IR needs it. - ## TODO: explain why + idStart = 1 template idToIdx(x: LitId): int = x.int - idStart diff --git a/compiler/ic/rodfiles.nim b/compiler/ic/rodfiles.nim index db520527bce6d..be5095fbb70a7 100644 --- a/compiler/ic/rodfiles.nim +++ b/compiler/ic/rodfiles.nim @@ -98,6 +98,7 @@ type backendFlagsSection aliveSymsSection # beware, this is stored in a `.alivesyms` file. sideChannelSection + namespaceSection symnamesSection RodFileError* = enum diff --git a/compiler/nir/ast2ir.nim b/compiler/nir/ast2ir.nim index ad04dc103d217..2dc28e87bcaf2 100644 --- a/compiler/nir/ast2ir.nim +++ b/compiler/nir/ast2ir.nim @@ -31,6 +31,8 @@ type 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] @@ -69,6 +71,8 @@ proc initModuleCon*(graph: ModuleGraph; config: ConfigRef; idgen: IdGenerator; m 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, @@ -162,11 +166,6 @@ proc getTemp(c: var ProcCon; t: TypeId; info: PackedLineInfo): Value = c.code.addSummon info, tmp, t result = localToValue(info, tmp) -template withTemp(tmp, n, body: untyped) {.dirty.} = - var tmp = getTemp(c, n) - body - c.freeTemp(tmp) - proc gen(c: var ProcCon; n: PNode; flags: GenFlags = {}) = var tmp = default(Value) gen(c, n, tmp, flags) @@ -281,14 +280,15 @@ proc genIf(c: var ProcCon; n: PNode; d: var Value) = var it = n[i] if it.len == 2: let info = toLineInfo(c, it[0].info) - withTemp(tmp, it[0]): - var elsePos: LabelId - if isNotOpr(it[0]): - c.gen(it[0][1], tmp) - elsePos = c.xjmp(it[0][1], opcTJmp, tmp) # if true - else: - c.gen(it[0], tmp) - elsePos = c.xjmp(it[0], opcFJmp, tmp) # if false + 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]) @@ -404,22 +404,23 @@ proc genCase(c: var ProcCon; n: PNode; d: var Value) = var sections = newSeqOfCap[LabelId](n.len-1) let ending = newLabel(c.labelGen) let info = toLineInfo(c, n.info) - withTemp(tmp, n[0]): - buildTyped c.code, info, Select, typeToIr(c.m, n[0].typ): - c.gen(n[0], tmp) - for i in 1.. 0 and resultPos < prc.ast.len: let resNode = prc.ast[resultPos] - let res = resNode.sym # get result symbol - c.code.addSummon toLineInfo(c, res.info), toSymId(c, res), - typeToIr(c.m, res.typ), SummonResult + 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[0]) and not isCompileTimeOnly(prc.typ[0]): # happens for procs without bodies: let t = typeToIr(c.m, prc.typ[0]) @@ -2389,6 +2419,7 @@ proc addCallConv(c: var ProcCon; info: PackedLineInfo; callConv: TCallingConvent of ccNoConvention: 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 @@ -2399,14 +2430,22 @@ proc genProc(cOuter: var ProcCon; prc: PSym) = inc cOuter.m.inProc var c = initProcCon(cOuter.m, prc, cOuter.m.graph.config) - - let body = transformBody(c.m.graph, c.m.idgen, prc, {useCache, keepOpenArrayConversions}) - - let info = toLineInfo(c, body.info) - build c.code, info, ProcDecl: - let symId = toSymId(c, prc) - addSymDef c.code, info, symId - c.m.nirm.symnames[symId] = c.lit.strings.getOrIncl(prc.name.s) + 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: @@ -2435,11 +2474,16 @@ proc genProc(cOuter: var ProcCon; prc: PSym) = else: c.code.addPragmaId info, ObjExport - genParams(c, prc.typ.n, prc) - gen(c, body) - patch c, body, c.exitLabel - build c.code, info, Ret: - discard + 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 @@ -2569,10 +2613,13 @@ proc gen(c: var ProcCon; n: PNode; d: var Value; flags: GenFlags = {}) = localError(c.config, n.info, "cannot generate IR code for " & $n) proc genPendingProcs(c: var ProcCon) = - while c.m.pendingProcs.len > 0: + 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 diff --git a/compiler/nir/cir.nim b/compiler/nir/cir.nim index 90a3035ddb657..0b97468565257 100644 --- a/compiler/nir/cir.nim +++ b/compiler/nir/cir.nim @@ -9,8 +9,11 @@ # We produce C code as a list of tokens. -import std / assertions -import .. / ic / bitabs +import std / [assertions, syncio, tables, intsets] +from std / strutils import toOctal +import .. / ic / [bitabs, rodfiles] +import nirtypes, nirinsts, nirfiles +import ../../dist/checksums/src/checksums/md5 type Token = LitId # indexing into the tokens BiTable[string] @@ -18,7 +21,6 @@ type PredefinedToken = enum IgnoreMe = "" EmptyToken = "" - DeclPrefix = "" # the next token is the name of a definition CurlyLe = "{" CurlyRi = "}" ParLe = "(" @@ -29,7 +31,7 @@ type Semicolon = ";" Comma = ", " Space = " " - Colon = ":" + Colon = ": " Dot = "." Arrow = "->" Star = "*" @@ -38,36 +40,41 @@ type ScopeOpr = "::" ConstKeyword = "const " StaticKeyword = "static " - NimString = "NimString" - StrLitPrefix = "(NimChar*)" - StrLitNamePrefix = "Qstr" - LoopKeyword = "while (true) " - WhileKeyword = "while (" + ExternKeyword = "extern " + WhileKeyword = "while " IfKeyword = "if (" ElseKeyword = "else " - SwitchKeyword = "switch (" + SwitchKeyword = "switch " CaseKeyword = "case " DefaultKeyword = "default:" BreakKeyword = "break" NullPtr = "nullptr" IfNot = "if (!(" ReturnKeyword = "return " - -const - ModulePrefix = Token(int(ReturnKeyword)+1) + TypedefStruct = "typedef struct " + TypedefUnion = "typedef union " + IncludeKeyword = "#include " proc fillTokenTable(tab: var BiTable[string]) = for e in EmptyToken..high(PredefinedToken): let id = tab.getOrIncl $e - assert id == LitId(e) + assert id == LitId(e), $(id, " ", ord(e)) type GeneratedCode* = object + m: NirModule + includes: seq[LitId] + includedHeaders: IntSet + data: seq[LitId] + protos: seq[LitId] code: seq[LitId] tokens: BiTable[string] + emittedStrings: IntSet + needsPrefix: IntSet + mangledModules: Table[LitId, LitId] -proc initGeneratedCode*(): GeneratedCode = - result = GeneratedCode(code: @[], tokens: initBiTable[string]()) +proc initGeneratedCode*(m: sink NirModule): GeneratedCode = + result = GeneratedCode(m: m, code: @[], tokens: initBiTable[string]()) fillTokenTable(result.tokens) proc add*(g: var GeneratedCode; t: PredefinedToken) {.inline.} = @@ -76,3 +83,845 @@ proc add*(g: var GeneratedCode; t: PredefinedToken) {.inline.} = proc add*(g: var GeneratedCode; s: string) {.inline.} = g.code.add g.tokens.getOrIncl(s) +proc mangleModuleName(c: var GeneratedCode; key: LitId): LitId = + result = c.mangledModules.getOrDefault(key, LitId(0)) + if result == LitId(0): + let u {.cursor.} = c.m.lit.strings[key] + var last = u.len - len(".nim") - 1 + var start = last + while start >= 0 and u[start] != '/': dec start + var sum = getMD5(u) + sum.setLen(8) + let dest = u.substr(start+1, last) & sum + result = c.tokens.getOrIncl(dest) + c.mangledModules[key] = result + +type + CppFile = object + f: File + +proc write(f: var CppFile; s: string) = write(f.f, s) +proc write(f: var CppFile; c: char) = write(f.f, c) + +proc writeTokenSeq(f: var CppFile; s: seq[Token]; c: GeneratedCode) = + var indent = 0 + for i in 0.. 0: g.add Comma + genType g, types, lit, ch + inc i + g.add ParRi + of ObjectDecl, UnionDecl: + atom lit.strings[types[t.firstSon].litId] + of IntVal, SizeVal, AlignVal, OffsetVal, FieldDecl: + #raiseAssert "did not expect: " & $types[t].kind + g.add "BUG " + atom $types[t].kind + +proc generateTypes(g: var GeneratedCode; types: TypeGraph; lit: Literals; c: TypeOrder) = + for (t, declKeyword) in c.forwardedDecls.s: + let name = if types[t].kind == ArrayTy: arrayName(types, t) else: t.firstSon + let s {.cursor.} = lit.strings[types[name].litId] + g.add declKeyword + g.add s + g.add Space + g.add s + g.add Semicolon + + for (t, declKeyword) in c.ordered.s: + let name = if types[t].kind == ArrayTy: arrayName(types, t) else: t.firstSon + let s {.cursor.} = lit.strings[types[name].litId] + g.add declKeyword + g.add CurlyLe + if types[t].kind == ArrayTy: + #let name = arrayName(types, t) + + genType g, types, lit, elementType(types, t), "a" + g.add BracketLe + g.add $arrayLen(types, t) + g.add BracketRi + g.add Semicolon + else: + var i = 0 + for x in sons(types, t): + case types[x].kind + of FieldDecl: + genType g, types, lit, x.firstSon, "F" & $i + g.add Semicolon + inc i + of ObjectTy: + genType g, types, lit, x, "P" + g.add Semicolon + else: discard + + g.add CurlyRi + g.add s + g.add Semicolon + +# Procs + +proc toCChar*(c: char; result: var string) {.inline.} = + case c + of '\0'..'\x1F', '\x7F'..'\xFF': + result.add '\\' + result.add toOctal(c) + of '\'', '\"', '\\', '?': + result.add '\\' + result.add c + else: + result.add c + +proc makeCString(s: string): string = + result = newStringOfCap(s.len + 10) + result.add('"') + for c in s: toCChar(c, result) + result.add('"') + +template emitData(s: string) = c.data.add c.tokens.getOrIncl(s) +template emitData(t: Token) = c.data.add t +template emitData(t: PredefinedToken) = c.data.add Token(t) + +proc genStrLit(c: var GeneratedCode; lit: Literals; litId: LitId): Token = + result = Token(c.tokens.getOrIncl "QStr" & $litId) + if not containsOrIncl(c.emittedStrings, int(litId)): + let s {.cursor.} = lit.strings[litId] + emitData "static const struct " + emitData CurlyLe + emitData " NI cap" + emitData Semicolon + emitData "NC8 data" + emitData BracketLe + emitData $s.len + emitData "+1" + emitData BracketRi + emitData Semicolon + emitData CurlyRi + emitData result + emitData AsgnOpr + emitData CurlyLe + emitData $s.len + emitData " | NIM_STRLIT_FLAG" + emitData Comma + emitData makeCString(s) + emitData CurlyRi + emitData Semicolon + +proc genIntLit(c: var GeneratedCode; lit: Literals; litId: LitId) = + let i = lit.numbers[litId] + if i > low(int32) and i <= high(int32): + c.add $i + elif i == low(int32): + # Nim has the same bug for the same reasons :-) + c.add "(-2147483647 -1)" + elif i > low(int64): + c.add "IL64(" + c.add $i + c.add ")" + else: + c.add "(IL64(-9223372036854775807) - IL64(1))" + +proc gen(c: var GeneratedCode; t: Tree; n: NodePos) + +proc genSymDef(c: var GeneratedCode; t: Tree; n: NodePos) = + if t[n].kind == SymDef: + c.needsPrefix.incl t[n].symId.int + gen c, t, n + +proc genGlobal(c: var GeneratedCode; t: Tree; name, typ: NodePos; annotation: string) = + c.add annotation + let m: string + if t[name].kind == SymDef: + m = c.tokens[mangleModuleName(c, c.m.namespace)] & "__" & $t[name].symId + else: + assert t[name].kind == ModuleSymUse + let (x, y) = sons2(t, name) + m = c.tokens[mangleModuleName(c, t[x].litId)] & "__" & $t[y].immediateVal + genType c, c.m.types, c.m.lit, t[typ].typeId, m + +proc genLocal(c: var GeneratedCode; t: Tree; name, typ: NodePos; annotation: string) = + assert t[name].kind == SymDef + c.add annotation + genType c, c.m.types, c.m.lit, t[typ].typeId, "q" & $t[name].symId + # XXX Use proper names here + +proc genProcDecl(c: var GeneratedCode; t: Tree; n: NodePos; isExtern: bool) = + let signatureBegin = c.code.len + let name = n.firstSon + + var prc = n.firstSon + next t, prc + + while true: + case t[prc].kind + of PragmaPair: + let (x, y) = sons2(t, prc) + let key = cast[PragmaKey](t[x].rawOperand) + case key + of HeaderImport: + let lit = t[y].litId + let headerAsStr {.cursor.} = c.m.lit.strings[lit] + let header = c.tokens.getOrIncl(headerAsStr) + # headerAsStr can be empty, this has the semantics of the `nodecl` pragma: + if headerAsStr.len > 0 and not c.includedHeaders.containsOrIncl(int header): + if headerAsStr[0] == '#': + discard "skip the #include" + else: + c.includes.add Token(IncludeKeyword) + c.includes.add header + c.includes.add Token NewLine + # do not generate code for importc'ed procs: + return + of DllImport: + let lit = t[y].litId + raiseAssert "cannot eval: " & c.m.lit.strings[lit] + else: discard + of PragmaId: discard + else: break + next t, prc + + var resultDeclPos = NodePos(-1) + if t[prc].kind == SummonResult: + resultDeclPos = prc + gen c, t, prc.firstSon + next t, prc + else: + c.add "void" + c.add Space + genSymDef c, t, name + c.add ParLe + var params = 0 + while t[prc].kind == SummonParam: + if params > 0: c.add Comma + let (typ, sym) = sons2(t, prc) + genLocal c, t, sym, typ, "" + next t, prc + inc params + if params == 0: + c.add "void" + c.add ParRi + + for i in signatureBegin ..< c.code.len: + c.protos.add c.code[i] + c.protos.add Token Semicolon + + if isExtern: + c.code.setLen signatureBegin + else: + c.add CurlyLe + if resultDeclPos.int >= 0: + gen c, t, resultDeclPos + for ch in sonsRest(t, n, prc): + gen c, t, ch + c.add CurlyRi + +template triop(opr) = + let (typ, a, b) = sons3(t, n) + c.add ParLe + c.add ParLe + gen c, t, typ + c.add ParRi + gen c, t, a + c.add opr + gen c, t, b + c.add ParRi + +template cmpop(opr) = + let (_, a, b) = sons3(t, n) + c.add ParLe + gen c, t, a + c.add opr + gen c, t, b + c.add ParRi + +template binaryop(opr) = + let (typ, a) = sons2(t, n) + c.add ParLe + c.add ParLe + gen c, t, typ + c.add ParRi + c.add opr + gen c, t, a + c.add ParRi + +template checkedBinaryop(opr) = + let (typ, labIdx, a, b) = sons4(t, n) + let bits = integralBits(c.m.types[t[typ].typeId]) + let lab = t[labIdx].label + + c.add (opr & $bits) + c.add ParLe + c.gen t, a + c.add Comma + c.gen t, b + c.add Comma + c.add "L" & $lab.int + c.add ParRi + +template moveToDataSection(body: untyped) = + let oldLen = c.code.len + body + for i in oldLen ..< c.code.len: + c.data.add c.code[i] + setLen c.code, oldLen + +proc gen(c: var GeneratedCode; t: Tree; n: NodePos) = + case t[n].kind + of Nop: + discard "nothing to emit" + of ImmediateVal: + c.add "BUG: " & $t[n].kind + of IntVal: + genIntLit c, c.m.lit, t[n].litId + of StrVal: + c.code.add genStrLit(c, c.m.lit, t[n].litId) + of Typed: + genType c, c.m.types, c.m.lit, t[n].typeId + of SymDef, SymUse: + let s = t[n].symId + if c.needsPrefix.contains(s.int): + c.code.add mangleModuleName(c, c.m.namespace) + c.add "__" + c.add $s + else: + # XXX Use proper names here + c.add "q" + c.add $s + + of ModuleSymUse: + let (x, y) = sons2(t, n) + let u = mangleModuleName(c, t[x].litId) + let s = t[y].immediateVal + c.code.add u + c.add "__" + c.add $s + + of NilVal: + c.add NullPtr + of LoopLabel: + c.add WhileKeyword + c.add ParLe + c.add "1" + c.add ParRi + c.add CurlyLe + of GotoLoop: + c.add CurlyRi + of Label: + let lab = t[n].label + c.add "L" + c.add $lab.int + c.add Colon + c.add Semicolon + of Goto: + let lab = t[n].label + c.add "goto L" + c.add $lab.int + c.add Semicolon + of CheckedGoto: + discard "XXX todo" + of ArrayConstr: + c.add CurlyLe + var i = 0 + for ch in sonsFrom1(t, n): + if i > 0: c.add Comma + c.gen t, ch + inc i + c.add CurlyRi + of ObjConstr: + c.add CurlyLe + var i = 0 + for ch in sonsFrom1(t, n): + if i mod 2 == 0: + if i > 0: c.add Comma + c.add ".F" & $t[ch].immediateVal + c.add AsgnOpr + else: + c.gen t, ch + inc i + c.add CurlyRi + of Ret: + c.add ReturnKeyword + c.gen t, n.firstSon + c.add Semicolon + of Select: + c.add SwitchKeyword + c.add ParLe + let (_, selector) = sons2(t, n) + c.gen t, selector + c.add ParRi + c.add CurlyLe + for ch in sonsFromN(t, n, 2): + c.gen t, ch + c.add CurlyRi + of SelectPair: + let (le, ri) = sons2(t, n) + c.gen t, le + c.gen t, ri + of SelectList: + for ch in sons(t, n): + c.gen t, ch + of SelectValue: + c.add CaseKeyword + c.gen t, n.firstSon + c.add Colon + of SelectRange: + let (le, ri) = sons2(t, n) + c.add CaseKeyword + c.gen t, le + c.add " ... " + c.gen t, ri + c.add Colon + of ForeignDecl: + c.data.add LitId(ExternKeyword) + c.gen t, n.firstSon + of SummonGlobal: + moveToDataSection: + let (typ, sym) = sons2(t, n) + c.genGlobal t, sym, typ, "" + c.add Semicolon + of SummonThreadLocal: + moveToDataSection: + let (typ, sym) = sons2(t, n) + c.genGlobal t, sym, typ, "__thread " + c.add Semicolon + of SummonConst: + moveToDataSection: + let (typ, sym) = sons2(t, n) + c.genGlobal t, sym, typ, "const " + c.add Semicolon + of Summon, SummonResult: + let (typ, sym) = sons2(t, n) + c.genLocal t, sym, typ, "" + c.add Semicolon + + of SummonParam: + raiseAssert "SummonParam should have been handled in genProc" + of Kill: + discard "we don't care about Kill instructions" + of AddrOf: + let (_, arg) = sons2(t, n) + c.add "&" + gen c, t, arg + of DerefArrayAt: + let (_, a, i) = sons3(t, n) + gen c, t, a + c.add BracketLe + gen c, t, i + c.add BracketRi + of ArrayAt: + let (_, a, i) = sons3(t, n) + gen c, t, a + c.add Dot + c.add "a" + c.add BracketLe + gen c, t, i + c.add BracketRi + of FieldAt: + let (_, a, b) = sons3(t, n) + gen c, t, a + let field = t[b].immediateVal + c.add Dot + c.add "F" & $field + of DerefFieldAt: + let (_, a, b) = sons3(t, n) + gen c, t, a + let field = t[b].immediateVal + c.add Arrow + c.add "F" & $field + of Load: + let (_, arg) = sons2(t, n) + c.add ParLe + c.add "*" + gen c, t, arg + c.add ParRi + of Store: + raiseAssert "Assumption was that Store is unused!" + of Asgn: + let (_, dest, src) = sons3(t, n) + gen c, t, dest + c.add AsgnOpr + gen c, t, src + c.add Semicolon + of CheckedRange: + c.add "nimCheckRange" + c.add ParLe + let (_, gotoInstr, x, a, b) = sons5(t, n) + gen c, t, x + c.add Comma + gen c, t, a + c.add Comma + gen c, t, b + c.add Comma + c.add "L" & $t[gotoInstr].label.int + c.add ParRi + of CheckedIndex: + c.add "nimCheckIndex" + c.add ParLe + let (gotoInstr, x, a) = sons3(t, n) + gen c, t, x + c.add Comma + gen c, t, a + c.add Comma + c.add "L" & $t[gotoInstr].label.int + c.add ParRi + of Call, IndirectCall: + let (typ, fn) = sons2(t, n) + gen c, t, fn + c.add ParLe + var i = 0 + for ch in sonsFromN(t, n, 2): + if i > 0: c.add Comma + gen c, t, ch + inc i + c.add ParRi + if c.m.types[t[typ].typeId].kind == VoidTy: + c.add Semicolon + of CheckedCall, CheckedIndirectCall: + let (typ, gotoInstr, fn) = sons3(t, n) + gen c, t, fn + c.add ParLe + var i = 0 + for ch in sonsFromN(t, n, 3): + if i > 0: c.add Comma + gen c, t, ch + inc i + c.add ParRi + if c.m.types[t[typ].typeId].kind == VoidTy: + c.add Semicolon + + of CheckedAdd: checkedBinaryop "nimAddInt" + of CheckedSub: checkedBinaryop "nimSubInt" + of CheckedMul: checkedBinaryop "nimMulInt" + of CheckedDiv: checkedBinaryop "nimDivInt" + of CheckedMod: checkedBinaryop "nimModInt" + of Add: triop " + " + of Sub: triop " - " + of Mul: triop " * " + of Div: triop " / " + of Mod: triop " % " + of BitShl: triop " << " + of BitShr: triop " >> " + of BitAnd: triop " & " + of BitOr: triop " | " + of BitXor: triop " ^ " + of BitNot: binaryop " ~ " + of BoolNot: binaryop " !" + of Eq: cmpop " == " + of Le: cmpop " <= " + of Lt: cmpop " < " + of Cast: binaryop "" + of NumberConv: binaryop "" + of CheckedObjConv: binaryop "" + of ObjConv: binaryop "" + of Emit: raiseAssert "cannot interpret: Emit" + of ProcDecl: genProcDecl c, t, n, false + of ForeignProcDecl: genProcDecl c, t, n, true + of PragmaPair, PragmaId, TestOf, Yld, SetExc, TestExc: + c.add "cannot interpret: " & $t[n].kind + +const + Prelude = """ +/* GENERATED CODE. DO NOT EDIT. */ + +#ifdef __cplusplus +#define NB8 bool +#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901) +// see #13798: to avoid conflicts for code emitting `#include ` +#define NB8 _Bool +#else +typedef unsigned char NB8; // best effort +#endif + +typedef unsigned char NC8; + +typedef float NF32; +typedef double NF64; +#if defined(__BORLANDC__) || defined(_MSC_VER) +typedef signed char NI8; +typedef signed short int NI16; +typedef signed int NI32; +typedef __int64 NI64; +/* XXX: Float128? */ +typedef unsigned char NU8; +typedef unsigned short int NU16; +typedef unsigned int NU32; +typedef unsigned __int64 NU64; +#elif defined(HAVE_STDINT_H) +#ifndef USE_NIM_NAMESPACE +# include +#endif +typedef int8_t NI8; +typedef int16_t NI16; +typedef int32_t NI32; +typedef int64_t NI64; +typedef uint8_t NU8; +typedef uint16_t NU16; +typedef uint32_t NU32; +typedef uint64_t NU64; +#elif defined(HAVE_CSTDINT) +#ifndef USE_NIM_NAMESPACE +# include +#endif +typedef std::int8_t NI8; +typedef std::int16_t NI16; +typedef std::int32_t NI32; +typedef std::int64_t NI64; +typedef std::uint8_t NU8; +typedef std::uint16_t NU16; +typedef std::uint32_t NU32; +typedef std::uint64_t NU64; +#else +/* Unknown compiler/version, do our best */ +#ifdef __INT8_TYPE__ +typedef __INT8_TYPE__ NI8; +#else +typedef signed char NI8; +#endif +#ifdef __INT16_TYPE__ +typedef __INT16_TYPE__ NI16; +#else +typedef signed short int NI16; +#endif +#ifdef __INT32_TYPE__ +typedef __INT32_TYPE__ NI32; +#else +typedef signed int NI32; +#endif +#ifdef __INT64_TYPE__ +typedef __INT64_TYPE__ NI64; +#else +typedef long long int NI64; +#endif +/* XXX: Float128? */ +#ifdef __UINT8_TYPE__ +typedef __UINT8_TYPE__ NU8; +#else +typedef unsigned char NU8; +#endif +#ifdef __UINT16_TYPE__ +typedef __UINT16_TYPE__ NU16; +#else +typedef unsigned short int NU16; +#endif +#ifdef __UINT32_TYPE__ +typedef __UINT32_TYPE__ NU32; +#else +typedef unsigned int NU32; +#endif +#ifdef __UINT64_TYPE__ +typedef __UINT64_TYPE__ NU64; +#else +typedef unsigned long long int NU64; +#endif +#endif + +#ifdef NIM_INTBITS +# if NIM_INTBITS == 64 +typedef NI64 NI; +typedef NU64 NU; +# elif NIM_INTBITS == 32 +typedef NI32 NI; +typedef NU32 NU; +# elif NIM_INTBITS == 16 +typedef NI16 NI; +typedef NU16 NU; +# elif NIM_INTBITS == 8 +typedef NI8 NI; +typedef NU8 NU; +# else +# error "invalid bit width for int" +# endif +#endif + +#define NIM_STRLIT_FLAG ((NU64)(1) << ((NIM_INTBITS) - 2)) /* This has to be the same as system.strlitFlag! */ + +#define nimAddInt64(a, b, L) ({long long int res; if(__builtin_saddll_overflow(a, b, &res)) goto L; res}) +#define nimSubInt64(a, b, L) ({long long int res; if(__builtin_ssubll_overflow(a, b, &res)) goto L; res}) +#define nimMulInt64(a, b, L) ({long long int res; if(__builtin_smulll_overflow(a, b, &res)) goto L; res}) + +#define nimAddInt32(a, b, L) ({long int res; if(__builtin_sadd_overflow(a, b, &res)) goto L; res}) +#define nimSubInt32(a, b, L) ({long int res; if(__builtin_ssub_overflow(a, b, &res)) goto L; res}) +#define nimMulInt32(a, b, L) ({long int res; if(__builtin_smul_overflow(a, b, &res)) goto L; res}) + +#define nimCheckRange(x, a, b, L) ({if (x < a || x > b) goto L; x}) +#define nimCheckIndex(x, a, L) ({if (x >= a) goto L; x}) + +""" + +proc generateCode*(inp, outp: string) = + var c = initGeneratedCode(load(inp)) + + var co = TypeOrder() + traverseTypes(c.m.types, c.m.lit, co) + + generateTypes(c, c.m.types, c.m.lit, co) + let typeDecls = move c.code + + var i = NodePos(0) + while i.int < c.m.code.len: + gen c, c.m.code, NodePos(i) + next c.m.code, i + + var f = CppFile(f: open(outp, fmWrite)) + f.write "#define NIM_INTBITS " & $c.m.intbits & "\n" + f.write Prelude + writeTokenSeq f, c.includes, c + writeTokenSeq f, typeDecls, c + writeTokenSeq f, c.data, c + writeTokenSeq f, c.protos, c + writeTokenSeq f, c.code, c + f.f.close diff --git a/compiler/nir/nirc.nim b/compiler/nir/nirc.nim index 363507ca6537f..a2cf69988abed 100644 --- a/compiler/nir/nirc.nim +++ b/compiler/nir/nirc.nim @@ -7,10 +7,10 @@ # distribution, for details about the copyright. # -## Nir Compiler. Currently only supports a "view" command. +## Nir Compiler. import ".." / ic / [bitabs, rodfiles] -import nirinsts, nirtypes, nirlineinfos, nirfiles #, nir2gcc +import nirinsts, nirtypes, nirlineinfos, nirfiles, cir proc view(filename: string) = let m = load(filename) @@ -20,14 +20,10 @@ proc view(filename: string) = nirtypes.toString res, m.types echo res -proc libgcc(filename: string) = - let m = load(filename) - #gcc m, filename - import std / [syncio, parseopt] proc writeHelp = - echo """Usage: nirc view|gcc """ + echo """Usage: nirc view|c """ quit 0 proc main = @@ -49,7 +45,8 @@ proc main = case cmd of "", "view": view inp - of "gcc": - libgcc inp + of "c": + let outp = inp & ".c" + cir.generateCode inp, outp main() diff --git a/compiler/nir/nirfiles.nim b/compiler/nir/nirfiles.nim index f6c73178b0ba0..cd5a79f06e444 100644 --- a/compiler/nir/nirfiles.nim +++ b/compiler/nir/nirfiles.nim @@ -16,6 +16,8 @@ type man*: LineInfoManager types*: TypeGraph lit*: Literals + namespace*: LitId + intbits*: uint32 symnames*: SymNames proc load*(filename: string): NirModule = @@ -39,6 +41,10 @@ proc load*(filename: string): NirModule = r.loadSection sideChannelSection r.load result.man + r.loadSection namespaceSection + r.loadPrim result.namespace + r.loadPrim result.intbits + r.loadSection symnamesSection r.load result.symnames @@ -64,6 +70,10 @@ proc store*(m: NirModule; outp: string) = r.storeSection sideChannelSection r.store m.man + r.storeSection namespaceSection + r.storePrim m.namespace + r.storePrim m.intbits + r.storeSection symnamesSection r.store m.symnames diff --git a/compiler/nir/nirinsts.nim b/compiler/nir/nirinsts.nim index 5c29015403961..6cffc1a8938d4 100644 --- a/compiler/nir/nirinsts.nim +++ b/compiler/nir/nirinsts.nim @@ -54,6 +54,7 @@ type SelectList, # (values...) SelectValue, # (value) SelectRange, # (valueA..valueB) + ForeignDecl, # Can wrap SummonGlobal, SummonThreadLocal, SummonConst SummonGlobal, SummonThreadLocal, Summon, # x = Summon Typed ; x begins to live @@ -108,6 +109,7 @@ type TestOf, Emit, ProcDecl, + ForeignProcDecl, PragmaPair type @@ -278,6 +280,14 @@ iterator sonsFromN*(tree: Tree; n: NodePos; toSkip = 2): NodePos = template `[]`*(t: Tree; n: NodePos): Instr = t.nodes[n.int] +iterator sonsRest*(tree: Tree; parent, n: NodePos): NodePos = + var pos = n.int + assert tree[parent].kind > LastAtomicValue + let last = parent.int + tree[parent].rawSpan + while pos < last: + yield NodePos pos + nextChild tree, pos + proc span(tree: Tree; pos: int): int {.inline.} = if tree.nodes[pos].kind <= LastAtomicValue: 1 else: int(tree.nodes[pos].operand) @@ -290,19 +300,36 @@ proc copyTree*(dest: var Tree; src: Tree) = for i in 0..= 0 t.nodes.add Instr(x: toX(Typed, uint32(typ)), info: info) diff --git a/compiler/nir/nirtypes.nim b/compiler/nir/nirtypes.nim index d1c7c6084139c..a79bf6d012a35 100644 --- a/compiler/nir/nirtypes.nim +++ b/compiler/nir/nirtypes.nim @@ -177,11 +177,35 @@ proc sons3(tree: TypeGraph; n: TypeId): (TypeId, TypeId, TypeId) = let c = b + span(tree, b) result = (TypeId a, TypeId b, TypeId c) +proc arrayName*(tree: TypeGraph; n: TypeId): TypeId {.inline.} = + assert tree[n].kind == ArrayTy + let (_, _, c) = sons3(tree, n) + result = c + proc arrayLen*(tree: TypeGraph; n: TypeId): BiggestInt = assert tree[n].kind == ArrayTy let (_, b) = sons2(tree, n) result = tree.lit.numbers[LitId tree[b].operand] +proc returnType*(tree: TypeGraph; n: TypeId): (TypeId, TypeId) = + # Returns the positions of the return type + calling convention. + var pos = n.int + assert tree.nodes[pos].kind == ProcTy + let a = n.int+1 + let b = a + span(tree, a) + result = (TypeId b, TypeId a) # not a typo, order is reversed + +iterator params*(tree: TypeGraph; n: TypeId): TypeId = + var pos = n.int + assert tree.nodes[pos].kind == ProcTy + let last = pos + tree.nodes[pos].rawSpan + inc pos + nextChild tree, pos + nextChild tree, pos + while pos < last: + yield TypeId pos + nextChild tree, pos + proc openType*(tree: var TypeGraph; kind: NirTypeKind): TypePatchPos = assert kind in {APtrTy, UPtrTy, AArrayPtrTy, UArrayPtrTy, ArrayTy, LastArrayTy, ProcTy, ObjectDecl, UnionDecl, @@ -356,10 +380,12 @@ proc toString*(dest: var string; g: TypeGraph; i: TypeId) = dest.add "]" of ArrayTy: dest.add "Array[" - let (elems, len) = g.sons2(i) + let (elems, len, name) = g.sons3(i) toString(dest, g, elems) dest.add ", " toString(dest, g, len) + dest.add ", " + toString(dest, g, name) dest.add "]" of LastArrayTy: # array of unspecified size as a last field inside an object @@ -421,6 +447,12 @@ iterator allTypes*(g: TypeGraph; start = 0): TypeId = yield TypeId i nextChild g, i +iterator allTypesIncludingInner*(g: TypeGraph; start = 0): TypeId = + var i = start + while i < g.len: + yield TypeId i + inc i + proc `$`(g: TypeGraph): string = result = "" toString(result, g) @@ -431,6 +463,7 @@ when isMainModule: let a = g.openType ArrayTy g.addBuiltinType Int8Id g.addArrayLen 5 + g.addName "SomeArray" let finalArrayType = finishType(g, a) let obj = g.openType ObjectDecl diff --git a/compiler/nir/nirvm.nim b/compiler/nir/nirvm.nim index a3d69a2962d35..faa7a1fb7eb3f 100644 --- a/compiler/nir/nirvm.nim +++ b/compiler/nir/nirvm.nim @@ -421,7 +421,7 @@ proc preprocess(c: var Preprocessing; bc: var Bytecode; t: Tree; n: NodePos; fla for ch in sons(t, n): preprocess(c, bc, t, ch, {WantAddr}) case t[n].kind - of Nop: + of Nop, ForeignDecl, ForeignProcDecl: discard "don't use Nop" of ImmediateVal: bc.add info, ImmediateValM, t[n].rawOperand diff --git a/compiler/nir/types2ir.nim b/compiler/nir/types2ir.nim index 4c3ce7001f873..9c951328451cf 100644 --- a/compiler/nir/types2ir.nim +++ b/compiler/nir/types2ir.nim @@ -14,6 +14,7 @@ import nirtypes type TypesCon* = object processed: Table[ItemId, TypeId] + processedByName: Table[string, TypeId] recursionCheck: HashSet[ItemId] conf: ConfigRef stringType: TypeId @@ -30,6 +31,13 @@ template cached(c: var TypesCon; t: PType; body: untyped) = body c.processed[t.itemId] = result +template cachedByName(c: var TypesCon; t: PType; body: untyped) = + let key = mangle(c, t) + result = c.processedByName.getOrDefault(key) + if result.int == 0: + body + c.processedByName[key] = result + proc typeToIr*(c: var TypesCon; g: var TypeGraph; t: PType): TypeId proc collectFieldTypes(c: var TypesCon; g: var TypeGraph; n: PNode; dest: var Table[ItemId, TypeId]) = @@ -273,10 +281,15 @@ proc seqPayloadType(c: var TypesCon; g: var TypeGraph; t: PType): (string, TypeI let f = g.openType FieldDecl let arr = g.openType LastArrayTy g.addType elementType - result[1] = finishType(g, arr) # LastArrayTy + # DO NOT USE `finishType` here as it is an inner type. This is subtle and we + # probably need an even better API here. + sealType(g, arr) + result[1] = TypeId(arr) + g.addOffset c.conf.target.ptrSize g.addName "data" sealType(g, f) # FieldDecl + sealType(g, p) proc seqPayloadPtrType*(c: var TypesCon; g: var TypeGraph; t: PType): (TypeId, TypeId) = @@ -413,6 +426,7 @@ proc typeToIr*(c: var TypesCon; g: var TypeGraph; t: PType): TypeId = let a = openType(g, ArrayTy) g.addType(elemType) g.addArrayLen n + g.addName mangle(c, t) result = finishType(g, a) of tyPtr, tyRef: cached(c, t): @@ -451,6 +465,7 @@ proc typeToIr*(c: var TypesCon; g: var TypeGraph; t: PType): TypeId = let a = openType(g, ArrayTy) g.addType(UInt8Id) g.addArrayLen s + g.addName mangle(c, t) result = finishType(g, a) of tyPointer, tyNil: # tyNil can happen for code like: `const CRAP = nil` which we have in posix.nim @@ -467,7 +482,7 @@ proc typeToIr*(c: var TypesCon; g: var TypeGraph; t: PType): TypeId = else: result = objectHeaderToIr(c, g, t) of tyTuple: - cached(c, t): + cachedByName(c, t): result = tupleToIr(c, g, t) of tyProc: cached(c, t): @@ -485,7 +500,7 @@ proc typeToIr*(c: var TypesCon; g: var TypeGraph; t: PType): TypeId = else: result = c.stringType of tySequence: - cached(c, t): + cachedByName(c, t): result = seqToIr(c, g, t) of tyCstring: cached(c, t):