Miscellaneous frontend optimizations (#26090)

Boostrap without linking is (conservatively) 17% faster on my machine.

Test setup runs the compiler compiling itself under ORC in release mode
from a clean cache and no C compilation (`--compileOnly`.)
Multiple samples are gathered before judging a potential optimization.
The test suite passes locally before push and bootstrap is tested with
strict views (mostly as a sanity check).
Tests are fair in the sense that they compile the same git worktree and
produce identical C output.

Refc will see less or no benefit.


Changes:
- Save tree traversals in `considerGenSyms` when no mappings exist
- Inline `maybeSkipDistinct` into `typeRel` for a safe cursor
- Only skip to static when there is a static type to reach in
`paramTypesMatchAux`
- Disable overflow checks for hashes
- Disable bounds checks for `nextIdentIter`
- Use cursor annotation when judged safe
- Use lent annotation for ast accessors


Spiritual companion to #26084
This commit is contained in:
SirOlaf
2026-08-08 23:28:22 +02:00
committed by GitHub
parent 050b38c749
commit f0e7969bb0
6 changed files with 82 additions and 57 deletions

View File

@@ -87,11 +87,11 @@ proc unsealForTransform*(t: PType) {.inline.} =
if t.state == Partial: loadType(t)
if t.state == Sealed: t.state = Complete
proc owner*(s: PSym): PSym {.inline.} =
proc owner*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.ownerFieldImpl
proc owner*(s: PType): PSym {.inline.} =
proc owner*(s: PType): lent PSym {.inline.} =
if s.state == Partial: loadType(s)
result = s.ownerFieldImpl
@@ -114,7 +114,7 @@ proc `kind=`*(s: PSym, val: TSymKind) {.inline.} =
if s.state == Partial: loadSym(s)
s.kindImpl = val
proc gcUnsafetyReason*(s: PSym): PSym {.inline.} =
proc gcUnsafetyReason*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.gcUnsafetyReasonImpl
@@ -123,7 +123,7 @@ proc `gcUnsafetyReason=`*(s: PSym, val: PSym) {.inline.} =
if s.state == Partial: loadSym(s)
s.gcUnsafetyReasonImpl = val
proc transformedBody*(s: PSym): PNode {.inline.} =
proc transformedBody*(s: PSym): lent PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.transformedBodyImpl
@@ -133,7 +133,7 @@ proc `transformedBody=`*(s: PSym, val: PNode) {.inline.} =
if s.state == Partial: loadSym(s)
s.transformedBodyImpl = val
proc guard*(s: PSym): PSym {.inline.} =
proc guard*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.guardImpl
@@ -169,7 +169,7 @@ proc `magic=`*(s: PSym, val: TMagic) {.inline.} =
if s.state == Partial: loadSym(s)
s.magicImpl = val
proc typ*(s: PSym): PType {.inline.} =
proc typ*(s: PSym): lent PType {.inline.} =
if s.state == Partial: loadSym(s)
result = s.typImpl
@@ -215,7 +215,7 @@ proc `flags=`*(s: PSym, val: TSymFlags) {.inline.} =
if s.state == Partial: loadSym(s)
s.flagsImpl = val
proc ast*(s: PSym): PNode {.inline.} =
proc ast*(s: PSym): lent PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.astImpl
@@ -263,7 +263,7 @@ proc `loc=`*(s: PSym, val: TLoc) {.inline.} =
if s.state == Partial: loadSym(s)
s.locImpl = val
proc annex*(s: PSym): PLib {.inline.} =
proc annex*(s: PSym): lent PLib {.inline.} =
if s.state == Partial: loadSym(s)
result = s.annexImpl
@@ -282,7 +282,7 @@ when hasFFI:
if s.state == Partial: loadSym(s)
s.cnameImpl = val
proc constraint*(s: PSym): PNode {.inline.} =
proc constraint*(s: PSym): lent PNode {.inline.} =
if s.state == Partial: loadSym(s)
result = s.constraintImpl
@@ -291,7 +291,7 @@ proc `constraint=`*(s: PSym, val: PNode) {.inline.} =
if s.state == Partial: loadSym(s)
s.constraintImpl = val
proc instantiatedFrom*(s: PSym): PSym {.inline.} =
proc instantiatedFrom*(s: PSym): lent PSym {.inline.} =
if s.state == Partial: loadSym(s)
result = s.instantiatedFromImpl
@@ -367,7 +367,7 @@ proc `sons=`*(t: PType, val: sink TTypeSeq) {.inline.} =
if t.state == Partial: loadType(t)
t.sonsImpl = val
proc n*(t: PType): PNode {.inline.} =
proc n*(t: PType): lent PNode {.inline.} =
if t.state == Partial: loadType(t)
result = t.nImpl
@@ -376,7 +376,7 @@ proc `n=`*(t: PType, val: PNode) {.inline.} =
if t.state == Partial: loadType(t)
t.nImpl = val
proc sym*(t: PType): PSym {.inline.} =
proc sym*(t: PType): lent PSym {.inline.} =
if t.state == Partial: loadType(t)
result = t.symImpl
@@ -418,7 +418,7 @@ proc `loc=`*(t: PType, val: TLoc) {.inline.} =
if t.state == Partial: loadType(t)
t.locImpl = val
proc typeInst*(t: PType): PType {.inline.} =
proc typeInst*(t: PType): lent PType {.inline.} =
if t.state == Partial: loadType(t)
result = t.typeInstImpl
@@ -447,7 +447,7 @@ proc excl*(t: PType; flags: set[TTypeFlag]) {.inline.} =
if t.state == Partial: loadType(t)
t.flagsImpl.excl(flags)
proc typ*(n: PNode): PType {.inline.} =
proc typ*(n: PNode): lent PType {.inline.} =
result = n.typField
if result == nil and nfLazyType in n.flags:
result = n.sym.typ
@@ -866,7 +866,7 @@ proc newIntNode*(kind: TNodeKind, intVal: Int128): PNode =
result = newNode(kind)
result.intVal = castToInt64(intVal)
proc lastSon*(n: PNode): PNode {.inline.} = n.sons[^1]
proc lastSon*(n: PNode): lent PNode {.inline.} = n.sons[^1]
template setLastSon*(n: PNode, s: PNode) = n.sons[^1] = s
template firstSon*(n: PNode): PNode = n.sons[0]
@@ -888,29 +888,29 @@ proc last*(n: PType): PType {.inline.} =
else:
n.sonsImpl[^1]
proc elementType*(n: PType): PType {.inline.} =
proc elementType*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
result = n.sonsImpl[^1]
proc skipModifier*(n: PType): PType {.inline.} =
proc skipModifier*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
result = n.sonsImpl[^1]
proc indexType*(n: PType): PType {.inline.} =
proc indexType*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
result = n.sonsImpl[0]
proc baseClass*(n: PType): PType {.inline.} =
proc baseClass*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
result = n.sonsImpl[0]
proc base*(t: PType): PType {.inline.} =
proc base*(t: PType): lent PType {.inline.} =
if t.state == Partial: loadType(t)
result = t.sonsImpl[0]
proc returnType*(n: PType): PType {.inline.} =
proc returnType*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
result = n.sonsImpl[0]
proc setReturnType*(n, r: PType) {.inline.} =
if n.state == Partial: loadType(n)
@@ -927,17 +927,17 @@ proc firstParamType*(n: PType): PType {.inline.} =
else:
n.sonsImpl[1]
proc firstGenericParam*(n: PType): PType {.inline.} =
proc firstGenericParam*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[1]
result = n.sonsImpl[1]
proc typeBodyImpl*(n: PType): PType {.inline.} =
proc typeBodyImpl*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[^1]
result = n.sonsImpl[^1]
proc genericHead*(n: PType): PType {.inline.} =
proc genericHead*(n: PType): lent PType {.inline.} =
if n.state == Partial: loadType(n)
n.sonsImpl[0]
result = n.sonsImpl[0]
proc skipTypes*(t: PType, kinds: TTypeKinds): PType =
## Used throughout the compiler code to test whether a type tree contains or

View File

@@ -529,8 +529,11 @@ proc objectSetContainsOrIncl*(t: var TObjectSet, obj: RootRef): bool =
type
TIdentIter* = object # iterator over all syms with same identifier
h*: Hash # current hash
name*: PIdent
name* {.cursor.}: PIdent
# String tables are always initialized with non-empty, power-of-two storage,
# and every probe is masked by `high(tab.data)`.
{.push boundChecks: off.}
proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
# hot spots
var h = ti.h and high(tab.data)
@@ -548,6 +551,7 @@ proc nextIdentIter*(ti: var TIdentIter, tab: TStrTable): PSym =
else:
result = nil
ti.h = nextTry(h, high(tab.data))
{.pop.}
proc initIdentIter*(ti: var TIdentIter, tab: TStrTable, s: PIdent): PSym =
ti.h = s.h

View File

@@ -1105,8 +1105,11 @@ const # for all kind of hash tables:
GrowthFactor* = 2 # must be power of 2, > 0
StartSize* = 8 # must be power of 2, > 0
{.push overflowChecks: off.}
proc nextTry*(h, maxHash: Hash): Hash {.inline.} =
# Overflow is intentional: only the low bits selected by maxHash are used.
result = ((5 * h) + 1) and maxHash
{.pop.}
# For any initial h in range(maxHash), repeating that maxHash times
# generates each int in range(maxHash) exactly once (see any text on
# random-number generation for proof).

View File

@@ -306,7 +306,7 @@ proc getGenSym*(c: PContext; s: PSym): PSym =
it = it.next
result = s
proc considerGenSyms*(c: PContext; n: PNode) =
proc considerGenSymsAux(c: PContext; n: PNode) =
if n == nil:
discard "can happen for nkFormalParams/nkArgList"
elif n.kind == nkSym:
@@ -315,7 +315,16 @@ proc considerGenSyms*(c: PContext; n: PNode) =
n.sym = s
else:
for i in 0..<n.safeLen:
considerGenSyms(c, n[i])
considerGenSymsAux(c, n[i])
proc considerGenSyms*(c: PContext; n: PNode) =
var it = c.p
while it != nil:
if it.mappingExists:
# Save a tree traversal when no mapping exists
considerGenSymsAux(c, n)
return
it = it.next
proc newOptionEntry*(conf: ConfigRef): POptionEntry =
result = POptionEntry(

View File

@@ -649,7 +649,7 @@ type
SkippedPtr = enum skippedNone, skippedRef, skippedPtr
proc skipToObject(t: PType; skipped: var SkippedPtr): PType =
var r = t
var r {.cursor.} = t
# we're allowed to skip one level of ptr/ref:
var ptrs = 0
while r != nil:
@@ -993,13 +993,6 @@ proc shouldSkipDistinct(m: TCandidate; rules: PNode, callIdent: PIdent): bool =
if considerQuotedIdent(m.c, r) == callIdent: return false
return true
proc maybeSkipDistinct(m: TCandidate; t: PType, callee: PSym): PType =
if t != nil and t.kind == tyDistinct and t.n != nil and
shouldSkipDistinct(m, t.n, callee.name):
result = t.base
else:
result = t
proc tryResolvingStaticExpr(c: var TCandidate, n: PNode,
allowUnresolved = false,
allowCalls = false,
@@ -1242,17 +1235,22 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
assert(aOrig != nil)
var
useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
not c.isNoCall and
f.kind != tyTypeDesc and
tfExplicit notin aOrig.flags and
tfConceptMatchedTypeSym notin aOrig.flags
let useTypeLoweringRuleInTypeClass = c.c.matchedConcept != nil and
not c.isNoCall and
f.kind != tyTypeDesc and
tfExplicit notin aOrig.flags and
tfConceptMatchedTypeSym notin aOrig.flags
aOrig = if useTypeLoweringRuleInTypeClass:
aOrig.skipTypes({tyTypeDesc})
else:
aOrig
template skipTypeCursor(it, kinds: untyped) =
while it.kind in kinds:
if it.kind == tyProc and it.nImpl.len > 1:
it = it.nImpl[^1].sym.typ
else:
it = it.sonsImpl[^1]
var aOrig {.cursor.} = aOrig
if useTypeLoweringRuleInTypeClass:
skipTypeCursor(aOrig, {tyTypeDesc})
if aOrig.kind == tyInferred:
let prev = aOrig.previouslyInferred
@@ -1289,8 +1287,14 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
template doBind: bool = trDontBind notin flags
# var, sink and static arguments match regular modifier-free types
var a = maybeSkipDistinct(c, aOrig.skipTypes({tyStatic, tyVar, tyLent, tySink}), c.calleeSym)
# XXX: Theoretically, maybeSkipDistinct could be called before we even
var a {.cursor.} = aOrig
skipTypeCursor(a, {tyStatic, tyVar, tyLent, tySink})
# Keep this expanded: an expression template materializes a PType temporary
# here, adding an otherwise avoidable reference-counting pair.
if a.kind == tyDistinct and a.n != nil and
shouldSkipDistinct(c, a.n, c.calleeSym.name):
a = a.base
# XXX: Theoretically, distinct types could be skipped before we even
# start the param matching process. This could be done in `prepareOperand`
# for example, but unfortunately `prepareOperand` is not called in certain
# situation when nkDotExpr are rotated to nkDotCalls
@@ -2448,11 +2452,13 @@ proc paramTypesMatchAux(m: var TCandidate, f, a: PType,
argSemantized, argOrig: PNode): PNode =
result = nil
var
fMaybeStatic = f.skipTypes({tyDistinct})
arg = argSemantized
a = a
c = m.c
if tfHasStatic in fMaybeStatic.flags:
let hasStatic = tfHasStatic in f.flags or
(f.kind == tyDistinct and tfHasStatic in f.skipTypes({tyDistinct}).flags)
if hasStatic:
let fMaybeStatic = if f.kind == tyDistinct: f.skipTypes({tyDistinct}) else: f
# XXX: When implicit statics are the default
# this will be done earlier - we just have to
# make sure that static types enter here

View File

@@ -56,11 +56,14 @@ proc mustRehash[T](t: T): bool {.inline.} =
assert length > t.counter
result = (length * 2 < t.counter * 3) or (length - t.counter < 4)
{.push overflowChecks: off.}
proc nextTry(h, maxHash: Hash, perturb: var Hash): Hash {.inline.} =
const PERTURB_SHIFT = 5
var perturb2 = cast[uint](perturb) shr PERTURB_SHIFT
perturb = cast[Hash](perturb2)
# Overflow is intentional: only the low bits selected by maxHash are used.
result = ((5 * h) + 1 + perturb) and maxHash
{.pop.}
proc packedSetGet[A](t: PackedSet[A], key: int): Trunk =
var h = key and t.max