Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 87 additions & 3 deletions compiler/ast2nif.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ from std / strutils import startsWith
from std / os import fileExists
import astdef, idents, msgs, options
import lineinfos as astli
import pathutils #, modulegraphs
import pathutils, typeext
import "../dist/nimony/src/lib" / [bitabs, nifstreams, nifcursors, lineinfos,
nifindexes, nifreader]
import "../dist/nimony/src/gear2" / modnames
Expand Down Expand Up @@ -148,6 +148,8 @@ let
sdefTag = registerTag(symDefTagName)
tdefTag = registerTag(typeDefTagName)
hiddenTypeTag = registerTag(hiddenTypeTagName)
typeExtensionsTag = registerTag("type_extensions")
extTag = registerTag("ext")

type
Writer = object
Expand All @@ -160,6 +162,8 @@ type
#writtenTypes: seq[PType] # types written in this module, to be unloaded later
#writtenSyms: seq[PSym] # symbols written in this module, to be unloaded later
writtenPackages: HashSet[string]
# Type extension side-table (passed from ModuleGraph)
typeExtensions: ptr Table[ItemId, seq[TypeExtension]]

const
# Symbol kinds that are always local to a proc and should never have module suffix
Expand Down Expand Up @@ -249,6 +253,30 @@ proc writeLoc(w: var Writer; dest: var TokenBuf; loc: TLoc) =
writeFlags(dest, loc.flags) # TLocFlags
dest.addStrLit loc.snippet

proc typeExtKindToStr(kind: TypeExtKind): string =
case kind
of extDeferredSize: "deferred_size"
of extDeferredAlign: "deferred_align"

proc writeTypeExtensions(w: var Writer; dest: var TokenBuf; typ: PType) =
## Serialize type extensions as NIF for persistence.
## Format: (type_extensions (ext "kind" <expr>) ...)
## If no extensions, writes DotToken (empty).
if w.typeExtensions == nil or not w.typeExtensions[].hasKey(typ.itemId):
dest.addDotToken()
return

let extensions = w.typeExtensions[][typ.itemId]
if extensions.len == 0:
dest.addDotToken()
return

dest.buildTree typeExtensionsTag:
for ext in extensions:
dest.buildTree extTag:
dest.addStrLit typeExtKindToStr(ext.kind)
writeNode(w, dest, ext.expr)

proc writeTypeDef(w: var Writer; dest: var TokenBuf; typ: PType) =
dest.buildTree tdefTag:
dest.addSymDef pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo
Expand All @@ -271,6 +299,8 @@ proc writeTypeDef(w: var Writer; dest: var TokenBuf; typ: PType) =

# Write TLoc structure
writeLoc w, dest, typ.locImpl
# Write type extensions (deferred pragmas)
writeTypeExtensions w, dest, typ
# we store the type's elements here at the end so that
# it is not ambiguous and saves space:
for ch in typ.sonsImpl:
Expand Down Expand Up @@ -706,8 +736,10 @@ proc writeOp(w: var Writer; content: var TokenBuf; op: LogEntry) =

proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
opsLog: seq[LogEntry];
replayActions: seq[PNode] = @[]) =
var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule)
replayActions: seq[PNode] = @[];
typeExtensions: ptr Table[ItemId, seq[TypeExtension]] = nil) =
var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule,
typeExtensions: typeExtensions)
var content = createTokenBuf(300)

let rootInfo = trLineInfo(w, n.info)
Expand Down Expand Up @@ -825,11 +857,19 @@ type
syms: Table[string, (PSym, NifIndexEntry)]
mods: Table[FileIndex, NifModule]
cache: IdentCache
# Type extension side-table (set via setTypeExtensions after ModuleGraph is created)
typeExtensions*: ptr Table[ItemId, seq[TypeExtension]]

proc createDecodeContext*(config: ConfigRef; cache: IdentCache): DecodeContext =
## Supposed to be a global variable
result = DecodeContext(infos: LineInfoWriter(config: config), cache: cache)

proc setTypeExtensions*(c: var DecodeContext;
typeExtensions: ptr Table[ItemId, seq[TypeExtension]]) =
## Sets the type extension table for loading.
## Must be called after createDecodeContext and after ModuleGraph is created.
c.typeExtensions = typeExtensions

proc cursorFromIndexEntry(c: var DecodeContext; module: FileIndex; entry: NifIndexEntry;
buf: var TokenBuf): Cursor =
let s = addr c.mods[module].stream
Expand Down Expand Up @@ -1078,6 +1118,48 @@ proc loadLoc(c: var DecodeContext; n: var Cursor; loc: var TLoc) =
loadField loc.flags
loadField loc.snippet

proc strToTypeExtKind(s: string): TypeExtKind =
case s
of "deferred_size": extDeferredSize
of "deferred_align": extDeferredAlign
else: extDeferredSize # fallback, shouldn't happen

proc loadTypeExtensions(c: var DecodeContext; n: var Cursor; t: PType;
thisModule: string; localSyms: var Table[string, PSym]) =
## Load type extensions from NIF into side-table.
## Handles backwards compatibility: if no extensions present (old format), skips gracefully.
## Format: (type_extensions (ext "kind" <expr>) ...) or DotToken
if n.kind == DotToken:
# No extensions (or old format that didn't have this slot)
inc n
return

if n.kind == ParLe and n.tagId == typeExtensionsTag:
inc n # skip (type_extensions
while n.kind != ParRi:
if n.kind == ParLe and n.tagId == extTag:
inc n # skip (ext
if n.kind == StringLit:
let kindStr = pool.strings[n.litId]
inc n
let expr = loadNode(c, n, thisModule, localSyms)
if c.typeExtensions != nil and expr != nil:
let kind = strToTypeExtKind(kindStr)
if not c.typeExtensions[].hasKey(t.itemId):
c.typeExtensions[][t.itemId] = @[]
c.typeExtensions[][t.itemId].add(TypeExtension(kind: kind, expr: expr))
else:
skip n # skip malformed extension
if n.kind == ParRi:
inc n # skip ) of ext
else:
inc n # skip unexpected content
if n.kind == ParRi:
inc n # skip ) of type_extensions
elif n.kind != ParRi:
# Old format without extensions slot - this position is a type son, don't consume
return

proc loadTypeFromCursor(c: var DecodeContext; n: var Cursor; t: PType; localSyms: var Table[string, PSym]) =
expect n, ParLe
if n.tagId != tdefTag:
Expand Down Expand Up @@ -1106,6 +1188,8 @@ proc loadTypeFromCursor(c: var DecodeContext; n: var Cursor; t: PType; localSyms
t.ownerFieldImpl = loadSymStub(c, n, typesModule, localSyms)
t.symImpl = loadSymStub(c, n, typesModule, localSyms)
loadLoc c, n, t.locImpl
# Load type extensions (deferred pragmas) from NIF pragmas
loadTypeExtensions c, n, t, typesModule, localSyms

while n.kind != ParRi:
t.sonsImpl.add loadTypeStub(c, n, localSyms)
Expand Down
12 changes: 12 additions & 0 deletions compiler/modulegraphs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import std/[intsets, tables, hashes, strtabs, os, strutils, parseutils]
import ../dist/checksums/src/checksums/md5
import ast, astalgo, options, lineinfos,idents, btrees, ropes, msgs, pathutils, packages, suggestsymdb
import typeext
export typeext

when not defined(nimKochBootstrap):
import ast2nif
Expand Down Expand Up @@ -96,6 +98,11 @@ type
methodsPerType*: Table[ItemId, seq[PSym]]
dispatchers*: seq[PSym]

# Type extension side-table (sparse metadata storage)
typeExtensions*: Table[ItemId, seq[TypeExtension]]
## Generic type extensions that couldn't be stored on PType directly.
## Used for deferred pragmas, future metadata, etc.

systemModule*: PSym
sysTypes*: array[TTypeKind, PType]
compilerprocs*: TStrTable
Expand Down Expand Up @@ -538,13 +545,18 @@ proc initModuleGraphFields(result: ModuleGraph) =
result.emittedTypeInfo = initTable[string, FileIndex]()
result.cachedFiles = newStringTable()
result.cachedMods = initIntSet()
# Type extension side-table
result.typeExtensions = initTable[ItemId, seq[TypeExtension]]()

proc newModuleGraph*(cache: IdentCache; config: ConfigRef): ModuleGraph =
result = ModuleGraph()
result.config = config
result.cache = cache
initModuleGraphFields(result)
ast.setupProgram(config, cache)
when not defined(nimKochBootstrap):
# Set type extension table pointer on DecodeContext for NIF loading
setTypeExtensions(ast.program, addr result.typeExtensions)

proc resetAllModules*(g: ModuleGraph) =
g.packageSyms = initStrTable()
Expand Down
5 changes: 3 additions & 2 deletions compiler/pipelines.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import sem, cgen, modulegraphs, ast, llstream, parser, msgs,
lineinfos, reorder, options, semdata, cgendata, modules, pathutils,
packages, syntaxes, depends, vm, pragmas, idents, lookups, wordrecg,
liftdestructors, nifgen
liftdestructors, nifgen, types

when not defined(nimKochBootstrap):
import vmdef
Expand Down Expand Up @@ -259,7 +259,8 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
if m == module:
replayActions.add n

writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog, replayActions)
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog, replayActions,
addr graph.typeExtensions)

result = true

Expand Down
73 changes: 60 additions & 13 deletions compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import
condsyms, ast, astalgo, idents, semdata, msgs, renderer,
wordrecg, ropes, options, extccomp, magicsys, trees,
types, lookups, lineinfos, pathutils, linter, modulepaths
types, lookups, lineinfos, pathutils, linter, modulepaths,
modulegraphs

from sigmatch import trySuggestPragmas

Expand Down Expand Up @@ -117,6 +118,44 @@ const
errStringLiteralExpected = "string literal expected"
errIntLiteralExpected = "integer literal expected"

proc containsGenericParam(c: PContext; n: PNode): bool =
## Check if AST node contains any unresolved generic type parameter.
## Returns true for:
## - Symbols that are generic params (skGenericParam or tyGenericParam)
## - Identifiers that resolve to generic params
## - Identifiers that don't resolve to anything (likely generic params not yet in scope)
## BUT only if they also don't resolve to a known type like `cint`
if n == nil: return false
case n.kind
of nkSym:
result = n.sym.kind == skGenericParam or
(n.sym.kind == skType and n.sym.typ != nil and n.sym.typ.kind == tyGenericParam)
of nkType:
result = n.typ != nil and n.typ.kind == tyGenericParam
of nkIdent:
# Try to look up the identifier in current scope
var ambiguous = false
let sym = searchInScopes(c, n.ident, ambiguous)
if sym == nil:
# Unresolved identifier - likely a generic param not yet fully in scope
result = true
elif sym.kind == skGenericParam:
result = true
elif sym.kind == skType and sym.typ != nil and sym.typ.kind == tyGenericParam:
result = true
else:
# Resolves to a known type (like cint) - not a generic param
result = false
of nkEmpty, nkNilLit, nkCharLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit,
nkStrLit..nkTripleStrLit:
# Leaf nodes that are never generic params
result = false
else:
for child in n:
if containsGenericParam(c, child):
return true
result = false

proc invalidPragma*(c: PContext; n: PNode) =
localError(c.config, n.info, "invalid pragma: " & renderTree(n, {renderNoComments}))

Expand Down Expand Up @@ -942,20 +981,28 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
processImportObjC(c, sym, getOptionalStr(c, it, "$1"), it.info)
of wSize:
if sym.typ == nil: invalidPragma(c, it)
var size = expectIntLit(c, it)
if sfImportc in sym.flags:
# no restrictions on size for imported types
setImportedTypeSize(c.config, sym.typ, size)
elif it.kind in nkPragmaCallKinds and it.len == 2 and containsGenericParam(c, it[1]):
# Defer evaluation until generic type is instantiated
if sfImportc notin sym.flags:
localError(c.config, it.info,
"deferred size expressions only supported for imported types")
else:
c.graph.addTypeExtension(sym.typ, extDeferredSize, it[1])
else:
case size
of 1, 2, 4:
sym.typ.size = size
sym.typ.align = int16 size
of 8:
sym.typ.size = 8
sym.typ.align = floatInt64Align(c.config)
var size = expectIntLit(c, it)
if sfImportc in sym.flags:
# no restrictions on size for imported types
setImportedTypeSize(c.config, sym.typ, size)
else:
localError(c.config, it.info, "size may only be 1, 2, 4 or 8")
case size
of 1, 2, 4:
sym.typ.size = size
sym.typ.align = int16 size
of 8:
sym.typ.size = 8
sym.typ.align = floatInt64Align(c.config)
else:
localError(c.config, it.info, "size may only be 1, 2, 4 or 8")
of wAlign:
let alignment = expectIntLit(c, it)
if isPowerOfTwo(alignment) and alignment > 0:
Expand Down
Loading