diff options
-rw-r--r-- | compiler/main.nim | 3 | ||||
-rw-r--r-- | compiler/modules.nim | 6 | ||||
-rw-r--r-- | compiler/rod.nim | 127 | ||||
-rw-r--r-- | compiler/rodimpl.nim | 452 | ||||
-rw-r--r-- | compiler/rodread.nim | 5 | ||||
-rw-r--r-- | compiler/rodutils.nim | 4 | ||||
-rw-r--r-- | compiler/sem.nim | 3 | ||||
-rw-r--r-- | compiler/types.nim | 2 |
8 files changed, 594 insertions, 8 deletions
diff --git a/compiler/main.nim b/compiler/main.nim index 9bf8bb7c0..4a8fdf998 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -16,7 +16,7 @@ import cgen, jsgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, docgen2, service, parser, modules, ccgutils, sigmatch, ropes, - modulegraphs, tables + modulegraphs, tables, rod from magicsys import systemModule, resetSysTypes @@ -157,6 +157,7 @@ proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = when SimulateCaasMemReset: gGlobalOptions.incl(optCaasEnabled) + setupModuleCache() # In "nim serve" scenario, each command must reset the registered passes clearPasses() gLastCmdTime = epochTime() diff --git a/compiler/modules.nim b/compiler/modules.nim index cba152e21..ab384311e 100644 --- a/compiler/modules.nim +++ b/compiler/modules.nim @@ -11,7 +11,7 @@ import ast, astalgo, magicsys, std / sha1, rodread, msgs, cgendata, sigmatch, options, - idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs + idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod when false: type @@ -125,7 +125,7 @@ proc newModule(graph: ModuleGraph; fileIdx: int32): PSym = # We cannot call ``newSym`` here, because we have to circumvent the ID # mechanism, which we do in order to assign each module a persistent ID. new(result) - result.id = - 1 # for better error checking + result.id = -1 # for better error checking result.kind = skModule let filename = fileIdx.toFullPath result.name = getIdent(splitFile(filename).name) @@ -175,7 +175,7 @@ proc compileModule*(graph: ModuleGraph; fileIdx: int32; cache: IdentCache, flags internalError("handleSymbolFile should have set the module's ID") return else: - result.id = getID() + result.id = getModuleId(toFullPath(fileIdx)) discard processModule(graph, result, if sfMainModule in flags and gProjectIsStdin: stdin.llStreamOpen else: nil, rd, cache) diff --git a/compiler/rod.nim b/compiler/rod.nim new file mode 100644 index 000000000..6d34e9ea0 --- /dev/null +++ b/compiler/rod.nim @@ -0,0 +1,127 @@ +# +# +# The Nim Compiler +# (c) Copyright 2017 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements the canonalization for the various caching mechanisms. + +import ast, idgen + +when not defined(nimSymbolfiles): + template setupModuleCache* = discard + template storeNode*(module: PSym; n: PNode) = discard + + template getModuleId*(fullpath: string): int = getID() + +else: + include rodimpl + +when false: + type + BlobWriter* = object + buf: string + pos: int + + SerializationAction = enum acRead, acWrite + + # Varint implementation inspired by SQLite. + proc rdVaruint64(z: ptr UncheckedArray[byte]; n: int; pResult: var uint64): int = + if z[0] <= 240: + pResult = z[0] + return 1 + if z[0] <= 248: + if n < 2: return 0 + pResult = (z[0] - 241) * 256 + z[1] + 240 + return 2 + if n < z[0]-246: return 0 + if z[0] == 249: + pResult = 2288 + 256*z[1] + z[2] + return 3 + if z[0] == 250: + pResult = (z[1] shl 16u64) + (z[2] shl 8u64) + z[3] + return 4 + let x = (z[1] shl 24) + (z[2] shl 16) + (z[3] shl 8) + z[4] + if z[0] == 251: + pResult = x + return 5 + if z[0] == 252: + pResult = (((uint64)x) shl 8) + z[5] + return 6 + if z[0] == 253: + pResult = (((uint64)x) shl 16) + (z[5] shl 8) + z[6] + return 7 + if z[0] == 254: + pResult = (((uint64)x) shl 24) + (z[5] shl 16) + (z[6] shl 8) + z[7] + return 8 + pResult = (((uint64)x) shl 32) + + (0xffffffff & ((z[5] shl 24) + (z[6] shl 16) + (z[7] shl 8) + z[8])) + return 9 + + proc varintWrite32(z: ptr UncheckedArray[byte]; y: uint32) = + z[0] = uint8(y shr 24) + z[1] = uint8(y shr 16) + z[2] = uint8(y shr 8) + z[3] = uint8(y) + + proc sqlite4PutVarint64(z: ptr UncheckedArray[byte], x: uint64): int = + ## Write a varint into z. The buffer z must be at least 9 characters + ## long to accommodate the largest possible varint. Returns the number of + ## bytes used. + if x <= 240: + z[0] = uint8 x + return 1 + if x <= 2287: + y = uint32(x - 240) + z[0] = uint8(y shr 8 + 241) + z[1] = uint8(y and 255) + return 2 + if x <= 67823: + y = uint32(x - 2288) + z[0] = 249 + z[1] = uint8(y shr 8) + z[2] = uint8(y and 255) + return 3 + let y = uint32 x + let w = uint32(x shr 32) + if w == 0: + if y <= 16777215: + z[0] = 250 + z[1] = uint8(y shr 16) + z[2] = uint8(y shr 8) + z[3] = uint8(y) + return 4 + z[0] = 251 + varintWrite32(z+1, y) + return 5 + if w <= 255: + z[0] = 252 + z[1] = uint8 w + varintWrite32(z+2, y) + return 6 + if w <= 65535: + z[0] = 253 + z[1] = uint8(w shr 8) + z[2] = uint8 w + varintWrite32(z+3, y) + return 7 + if w <= 16777215: + z[0] = 254 + z[1] = uint8(w shr 16) + z[2] = uint8(w shr 8) + z[3] = uint8 w + varintWrite32(z+4, y) + return 8 + z[0] = 255 + varintWrite32(z+1, w) + varintWrite32(z+5, y) + return 9 + + template field(x: BiggestInt; action: SerializationAction) = + when action == acRead: + readBiggestInt(x) + else: + writeBiggestInt() diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim new file mode 100644 index 000000000..aca576b23 --- /dev/null +++ b/compiler/rodimpl.nim @@ -0,0 +1,452 @@ +# +# +# The Nim Compiler +# (c) Copyright 2018 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements the canonalization for the various caching mechanisms. + +import strutils, os, intsets, ropes, db_sqlite, msgs, options, types, + renderer, rodutils, std / sha1 + +var db: DbConn + +proc getModuleId*(fullpath: string): int = + if gSymbolFiles != v2Sf: return getID() + let module = db.getRow( + sql"select id, fullHash from modules where fullpath = ?", fullpath) + let currentFullhash = $secureHashFile(fullpath) + if module[0].len == 0: + result = int db.insertID(sql"insert into modules(fullpath, interfHash, fullHash) values (?, ?)", + fullpath, "", currentFullhash) + else: + result = parseInt(module[0]) + if currentFullhash == module[1]: + # not changed, so use the cached AST (even if it might be wrong + # due to its dependencies): + doAssert(result != 0) + result = -result + else: + db.exec(sql"update modules set fullHash = ? where id = ?", currentFullhash, module[0]) + db.exec(sql"delete from types where module = ?", module[0]) + db.exec(sql"delete from syms where module = ?", module[0]) + db.exec(sql"delete from toplevelstmts where module = ?", module[0]) + db.exec(sql"delete from statics where module = ?", module[0]) + +type + TRodWriter = object + module: PSym + sstack: seq[PSym] # a stack of symbols to process + tstack: seq[PType] # a stack of types to process + tmarks, smarks: IntSet + + PRodWriter = var TRodWriter + +proc initRodWriter(module: PSym): TRodWriter = + result = TRodWriter(module: module, sstack: @[], tstack: @[], + tmarks: initIntSet(), smarks: initIntSet()) + +when false: + proc getDefines(): string = + result = "" + for d in definedSymbolNames(): + if result.len != 0: add(result, " ") + add(result, d) + + proc addInclDep(w: PRodWriter, dep: string; info: TLineInfo) = + let resolved = dep.findModule(info.toFullPath) + encodeVInt(fileIdx(w, resolved), w.inclDeps) + add(w.inclDeps, " ") + encodeStr($secureHashFile(resolved), w.inclDeps) + add(w.inclDeps, rodNL) + +const + rodNL = "\L" + +proc pushType(w: PRodWriter, t: PType) = + if not containsOrIncl(w.tmarks, t.id): + w.tstack.add(t) + +proc pushSym(w: PRodWriter, s: PSym) = + if not containsOrIncl(w.smarks, s.id): + w.sstack.add(s) + +proc encodeNode(w: PRodWriter, fInfo: TLineInfo, n: PNode, + result: var string) = + if n == nil: + # nil nodes have to be stored too: + result.add("()") + return + result.add('(') + encodeVInt(ord(n.kind), result) + # we do not write comments for now + # Line information takes easily 20% or more of the filesize! Therefore we + # omit line information if it is the same as the parent's line information: + if fInfo.fileIndex != n.info.fileIndex: + result.add('?') + encodeVInt(n.info.col, result) + result.add(',') + encodeVInt(n.info.line, result) + result.add(',') + encodeVInt(n.info.fileIndex, result) + elif fInfo.line != n.info.line: + result.add('?') + encodeVInt(n.info.col, result) + result.add(',') + encodeVInt(n.info.line, result) + elif fInfo.col != n.info.col: + result.add('?') + encodeVInt(n.info.col, result) + # No need to output the file index, as this is the serialization of one + # file. + let f = n.flags * PersistentNodeFlags + if f != {}: + result.add('$') + encodeVInt(cast[int32](f), result) + if n.typ != nil: + result.add('^') + encodeVInt(n.typ.id, result) + pushType(w, n.typ) + case n.kind + of nkCharLit..nkUInt64Lit: + if n.intVal != 0: + result.add('!') + encodeVBiggestInt(n.intVal, result) + of nkFloatLit..nkFloat64Lit: + if n.floatVal != 0.0: + result.add('!') + encodeStr($n.floatVal, result) + of nkStrLit..nkTripleStrLit: + if n.strVal != "": + result.add('!') + encodeStr(n.strVal, result) + of nkIdent: + result.add('!') + encodeStr(n.ident.s, result) + of nkSym: + result.add('!') + encodeVInt(n.sym.id, result) + pushSym(w, n.sym) + else: + for i in countup(0, sonsLen(n) - 1): + encodeNode(w, n.info, n.sons[i], result) + add(result, ')') + +proc encodeLoc(w: PRodWriter, loc: TLoc, result: var string) = + var oldLen = result.len + result.add('<') + if loc.k != low(loc.k): encodeVInt(ord(loc.k), result) + if loc.storage != low(loc.storage): + add(result, '*') + encodeVInt(ord(loc.storage), result) + if loc.flags != {}: + add(result, '$') + encodeVInt(cast[int32](loc.flags), result) + if loc.lode != nil: + add(result, '^') + encodeNode(w, unknownLineInfo(), loc.lode, result) + #encodeVInt(cast[int32](loc.t.id), result) + #pushType(w, loc.t) + if loc.r != nil: + add(result, '!') + encodeStr($loc.r, result) + if oldLen + 1 == result.len: + # no data was necessary, so remove the '<' again: + setLen(result, oldLen) + else: + add(result, '>') + +proc encodeType(w: PRodWriter, t: PType, result: var string) = + if t == nil: + # nil nodes have to be stored too: + result.add("[]") + return + # we need no surrounding [] here because the type is in a line of its own + if t.kind == tyForward: internalError("encodeType: tyForward") + # for the new rodfile viewer we use a preceding [ so that the data section + # can easily be disambiguated: + add(result, '[') + encodeVInt(ord(t.kind), result) + add(result, '+') + encodeVInt(t.id, result) + if t.n != nil: + encodeNode(w, w.module.info, t.n, result) + if t.flags != {}: + add(result, '$') + encodeVInt(cast[int32](t.flags), result) + if t.callConv != low(t.callConv): + add(result, '?') + encodeVInt(ord(t.callConv), result) + if t.owner != nil: + add(result, '*') + encodeVInt(t.owner.id, result) + pushSym(w, t.owner) + if t.sym != nil: + add(result, '&') + encodeVInt(t.sym.id, result) + pushSym(w, t.sym) + if t.size != - 1: + add(result, '/') + encodeVBiggestInt(t.size, result) + if t.align != 2: + add(result, '=') + encodeVInt(t.align, result) + if t.lockLevel.ord != UnspecifiedLockLevel.ord: + add(result, '\14') + encodeVInt(t.lockLevel.int16, result) + if t.destructor != nil and t.destructor.id != 0: + add(result, '\15') + encodeVInt(t.destructor.id, result) + pushSym(w, t.destructor) + if t.deepCopy != nil: + add(result, '\16') + encodeVInt(t.deepcopy.id, result) + pushSym(w, t.deepcopy) + if t.assignment != nil: + add(result, '\17') + encodeVInt(t.assignment.id, result) + pushSym(w, t.assignment) + if t.sink != nil: + add(result, '\18') + encodeVInt(t.sink.id, result) + pushSym(w, t.sink) + for i, s in items(t.methods): + add(result, '\19') + encodeVInt(i, result) + add(result, '\20') + encodeVInt(s.id, result) + pushSym(w, s) + encodeLoc(w, t.loc, result) + for i in countup(0, sonsLen(t) - 1): + if t.sons[i] == nil: + add(result, "^()") + else: + add(result, '^') + encodeVInt(t.sons[i].id, result) + pushType(w, t.sons[i]) + +proc encodeLib(w: PRodWriter, lib: PLib, info: TLineInfo, result: var string) = + add(result, '|') + encodeVInt(ord(lib.kind), result) + add(result, '|') + encodeStr($lib.name, result) + add(result, '|') + encodeNode(w, info, lib.path, result) + +proc encodeInstantiations(w: PRodWriter; s: seq[PInstantiation]; + result: var string) = + for t in s: + result.add('\15') + encodeVInt(t.sym.id, result) + pushSym(w, t.sym) + for tt in t.concreteTypes: + result.add('\17') + encodeVInt(tt.id, result) + pushType(w, tt) + result.add('\20') + encodeVInt(t.compilesId, result) + +proc encodeSym(w: PRodWriter, s: PSym, result: var string) = + if s == nil: + # nil nodes have to be stored too: + result.add("{}") + return + # we need no surrounding {} here because the symbol is in a line of its own + encodeVInt(ord(s.kind), result) + result.add('+') + encodeVInt(s.id, result) + result.add('&') + encodeStr(s.name.s, result) + if s.typ != nil: + result.add('^') + encodeVInt(s.typ.id, result) + pushType(w, s.typ) + result.add('?') + if s.info.col != -1'i16: encodeVInt(s.info.col, result) + result.add(',') + if s.info.line != -1'i16: encodeVInt(s.info.line, result) + result.add(',') + encodeVInt(s.info.fileIndex, result) + if s.owner != nil: + result.add('*') + encodeVInt(s.owner.id, result) + pushSym(w, s.owner) + if s.flags != {}: + result.add('$') + encodeVInt(cast[int32](s.flags), result) + if s.magic != mNone: + result.add('@') + encodeVInt(ord(s.magic), result) + result.add('!') + encodeVInt(cast[int32](s.options), result) + if s.position != 0: + result.add('%') + encodeVInt(s.position, result) + if s.offset != - 1: + result.add('`') + encodeVInt(s.offset, result) + encodeLoc(w, s.loc, result) + if s.annex != nil: encodeLib(w, s.annex, s.info, result) + if s.constraint != nil: + add(result, '#') + encodeNode(w, unknownLineInfo(), s.constraint, result) + case s.kind + of skType, skGenericParam: + for t in s.typeInstCache: + result.add('\14') + encodeVInt(t.id, result) + pushType(w, t) + of routineKinds: + encodeInstantiations(w, s.procInstCache, result) + if s.gcUnsafetyReason != nil: + result.add('\16') + encodeVInt(s.gcUnsafetyReason.id, result) + pushSym(w, s.gcUnsafetyReason) + of skModule, skPackage: + encodeInstantiations(w, s.usedGenerics, result) + # we don't serialize: + #tab*: TStrTable # interface table for modules + of skLet, skVar, skField, skForVar: + if s.guard != nil: + result.add('\18') + encodeVInt(s.guard.id, result) + pushSym(w, s.guard) + if s.bitsize != 0: + result.add('\19') + encodeVInt(s.bitsize, result) + else: discard + # lazy loading will soon reload the ast lazily, so the ast needs to be + # the last entry of a symbol: + if s.ast != nil: + # we used to attempt to save space here by only storing a dummy AST if + # it is not necessary, but Nim's heavy compile-time evaluation features + # make that unfeasible nowadays: + encodeNode(w, s.info, s.ast, result) + +proc storeSym(w: PRodWriter; s: PSym) = + var buf = newStringOfCap(160) + encodeSym(w, s, buf) + # XXX only store the name for exported symbols in order to speed up lookup + # times once we enable the skStub logic. + db.exec(sql"insert into syms(nimid, module, name, data) values (?, ?, ?, ?)", + s.id, abs(w.module.id), s.name.s, buf) + +proc storeType(w: PRodWriter; t: PType) = + var buf = newStringOfCap(160) + encodeType(w, t, buf) + db.exec(sql"insert into types(nimid, module, data) values (?, ?, ?)", + t.id, abs(w.module.id), buf) + +var w = initRodWriter(nil) + +proc storeNode*(module: PSym; n: PNode) = + if gSymbolFiles != v2Sf: return + w.module = module + var buf = newStringOfCap(160) + encodeNode(w, module.info, n, buf) + db.exec(sql"insert into toplevelstmts(module, data) values (?, ?)", + abs(module.id), buf) + var i = 0 + while true: + if i > 10_000: + quit "loop never ends!" + if w.sstack.len > 0: + let s = w.sstack.pop() + when false: + echo "popped ", s.name.s, " ", s.id + storeSym(w, s) + elif w.tstack.len > 0: + let t = w.tstack.pop() + storeType(w, t) + when false: + echo "popped type ", typeToString(t), " ", t.id + else: + break + inc i + +proc createDb() = + db.exec(sql""" + create table if not exists controlblock( + idgen integer not null + ); + """) + + db.exec(sql""" + create table if not exists modules( + id integer primary key, + fullpath varchar(8000) not null, + interfHash varchar(256) not null, + fullHash varchar(256) not null, + + created timestamp not null default (DATETIME('now')) + );""") + db.exec(sql"""create unique index if not exists SymNameIx on modules(fullpath);""") + + db.exec(sql""" + create table if not exists types( + id integer primary key, + nimid integer not null, + module integer not null, + data blob not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index TypeByModuleIdx on types(module);" + db.exec sql"create index TypeByNimIdIdx on types(nimid);" + + db.exec(sql""" + create table if not exists syms( + id integer primary key, + nimid integer not null, + module integer not null, + name varchar(256) not null, + data blob not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index if not exists SymNameIx on syms(name);" + db.exec sql"create index SymByNameAndModuleIdx on syms(name, module);" + db.exec sql"create index SymByModuleIdx on syms(module);" + db.exec sql"create index SymByNimIdIdx on syms(nimid);" + + + db.exec(sql""" + create table if not exists toplevelstmts( + id integer primary key, + module integer not null, + data blob not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index TopLevelStmtByModuleIdx on toplevelstmts(module);" + + + db.exec(sql""" + create table if not exists statics( + id integer primary key, + module integer not null, + data blob not null, + foreign key (module) references module(id) + ); + """) + db.exec sql"create index StaticsByModuleIdx on toplevelstmts(module);" + db.exec sql"insert into controlblock(idgen) values (0)" + +proc setupModuleCache* = + if gSymbolFiles != v2Sf: return + let dbfile = getNimcacheDir() / "rodfiles.db" + if not fileExists(dbfile): + db = open(connection=dbfile, user="nim", password="", + database="nim") + createDb() + else: + db = open(connection=dbfile, user="nim", password="", + database="nim") + db.exec(sql"pragma journal_mode=off") + db.exec(sql"pragma SYNCHRONOUS=off") + db.exec(sql"pragma LOCKING_MODE=exclusive") + idgen.setId(parseInt db.getValue( + sql"select max(idgen) from controlblock")) diff --git a/compiler/rodread.nim b/compiler/rodread.nim index 6e952606e..84f175cb5 100644 --- a/compiler/rodread.nim +++ b/compiler/rodread.nim @@ -813,6 +813,7 @@ proc rrGetSym(r: PRodReader, id: int, info: TLineInfo): PSym = encodeVInt(id, x) internalError(info, "missing from both indexes: +" & x) var rd = getReader(moduleID) + doAssert rd != nil d = iiTableGet(rd.index.tab, id) if d != InvalidKey: result = decodeSymSafePos(rd, d, info) @@ -911,7 +912,7 @@ proc checkDep(fileIdx: int32; cache: IdentCache): TReasonForRecompile = proc handleSymbolFile*(module: PSym; cache: IdentCache): PRodReader = let fileIdx = module.fileIdx - if gSymbolFiles in {disabledSf, writeOnlySf}: + if gSymbolFiles in {disabledSf, writeOnlySf, v2Sf}: module.id = getID() return nil idgen.loadMaxIds(options.gProjectPath / options.gProjectName) @@ -1236,4 +1237,4 @@ proc viewFile(rodfile: string) = outf.close when isMainModule: - viewFile(paramStr(1).addFileExt(rodExt)) + viewFile(paramStr(1).addFileExt(RodExt)) diff --git a/compiler/rodutils.nim b/compiler/rodutils.nim index 0d9c7112f..66d7f63c2 100644 --- a/compiler/rodutils.nim +++ b/compiler/rodutils.nim @@ -67,6 +67,8 @@ proc decodeStr*(s: cstring, pos: var int): string = const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +{.push overflowChecks: off.} + # since negative numbers require a leading '-' they use up 1 byte. Thus we # subtract/add `vintDelta` here to save space for small negative numbers # which are common in ROD files: @@ -131,6 +133,8 @@ proc decodeVInt*(s: cstring, pos: var int): int = proc decodeVBiggestInt*(s: cstring, pos: var int): BiggestInt = decodeIntImpl() +{.pop.} + iterator decodeVIntArray*(s: cstring): int = var i = 0 while s[i] != '\0': diff --git a/compiler/sem.nim b/compiler/sem.nim index 39b40b2e6..053e739d3 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -16,7 +16,7 @@ import procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch, intsets, transf, vmdef, vm, idgen, aliases, cgmeth, lambdalifting, evaltempl, patterns, parampatterns, sempass2, nimfix.pretty, semmacrosanity, - semparallel, lowerings, pluginsupport, plugins.active + semparallel, lowerings, pluginsupport, plugins.active, rod from modulegraphs import ModuleGraph @@ -589,6 +589,7 @@ proc myProcess(context: PPassContext, n: PNode): PNode = else: result = ast.emptyNode #if gCmd == cmdIdeTools: findSuggest(c, n) + rod.storeNode(c.module, result) proc testExamples(c: PContext) = let inp = toFullPath(c.module.info) diff --git a/compiler/types.nim b/compiler/types.nim index 7878f87d7..7ae02486e 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -567,7 +567,7 @@ proc typeToString(typ: PType, prefer: TPreferedDesc = preferName): string = add(result, typeToString(t.sons[i])) if i < sonsLen(t) - 1: add(result, ", ") add(result, ')') - if t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0])) + if t.len > 0 and t.sons[0] != nil: add(result, ": " & typeToString(t.sons[0])) var prag = if t.callConv == ccDefault: "" else: CallingConvToStr[t.callConv] if tfNoSideEffect in t.flags: addSep(prag) |