Skip to content

perf: export reflectx types from reachable state roots - #23

Merged
MeteorsLiu merged 1 commit into
xgo-dev:mainfrom
MeteorsLiu:codex/reflectx-rooted-export
Sep 21, 2026
Merged

MeteorsLiu merged 1 commit into
xgo-dev:mainfrom
MeteorsLiu:codex/reflectx-rooted-export

Conversation

@MeteorsLiu

Copy link
Copy Markdown
Collaborator

No description provided.

@codecov

codecov Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 68.42105% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/reflectxtype/method.go 0.00% 4 Missing ⚠️
internal/reflectxtype/reflectxtype.go 80.00% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: roots-based reflectxtype.Export

This is a clean, well-scoped refactor. It replaces the parameterless Export() (which enumerated global reflect/reflectx caches via //go:linkname) with an explicit Export(roots []reflect.Type) that walks only the given roots and their dependencies, scoping each snapshot to a single object graph.

Strengths

  • Genuine isolation improvement. Types owned by unrelated interpreters that merely sit in the shared process-global reflect cache are no longer swept into a snapshot. Verified by the new reflectx_roots_linux_test.go (unrelated vs referenced_by_method cases).
  • Reduced unsafe surface. Removing the //go:linkname handles to reflect.ptrMap / lookupCache / funcLookupCache / structLookupCache and the reflectx.Default struct-punning eliminates a class of layout fragility and lock-discipline hazards from this package.
  • Correct method.go fix. The interface-entry detection now keeps an entry when either receiver has real method text (OR semantics) instead of letting the pointer-receiver pass clobber the value receiver — covered by the new assertion in method_missing_test.go.
  • Doc comments were rewritten accurately, all call sites updated consistently, and the loop termination / roots-monotonicity invariants are backed by the new tests.

The inline notes below are optional, low-priority observations — no blocking issues.

Additional findings

  • internal/state/encode.go:983: [P3] Nil-deref of extended rests on an unguarded invariant: extended.IDs[typ] is dereferenced here when a reflected type is absent from the reflecttype snapshot. extended can be nil when the export loop broke early at the len(roots) == 0 && extended == nil condition. This path is currently unreachable — when roots is empty, every reflected type is already present in snapshot.IDs, so !ok never fires — but the safety depends on a subtle invariant. A one-line comment noting that !ok implies extended != nil, or an explicit nil check, would guard against future changes to how roots is populated.
  • internal/reflectxtype/reflectxtype.go:88: [P3] concreteMethodSet computed twice per retained concrete type: In the previous != nil path, the pre-pass here calls concreteMethodSet(typ) for every retained concrete type to populate e.sharedMethods, and then the second pass at lines 102-111 calls e.encode(typ), which calls concreteMethodSet(typ) again (reflectxtype.go:175). So each retained type with methods pays for concreteMethodSet twice per Export — it iterates both method sets, resolves name/pkg offsets via linkname, and allocates a reflect.FuncOf per method. Threading the computed method set into encode (or caching it per-type on the exporter) would halve this cost. Minor; only matters for large retained type sets.

@MeteorsLiu
MeteorsLiu merged commit 9f036a6 into xgo-dev:main Sep 21, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant