Skip to content

[WIP] ssa: check abiInit flag for abiInitList/abiPrune#1635

Closed
visualfc wants to merge 5 commits intoxgo-dev:mainfrom
visualfc:callgraph_abiinit
Closed

[WIP] ssa: check abiInit flag for abiInitList/abiPrune#1635
visualfc wants to merge 5 commits intoxgo-dev:mainfrom
visualfc:callgraph_abiinit

Conversation

@visualfc
Copy link
Copy Markdown
Collaborator

@visualfc visualfc commented Feb 12, 2026

based of #1590

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @visualfc, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the ABI (Application Binary Interface) generation and reflection metadata handling within the SSA compiler. The primary goal is to optimize the generated code by introducing a sophisticated pruning mechanism that only includes necessary reflection information. This is achieved through granular control flags for ABI initialization and by analyzing actual usage of reflection functions and method invocations, leading to potentially smaller binaries and improved runtime efficiency.

Highlights

  • Granular ABI Initialization Control: The NeedAbiInit flag has been converted from a boolean to an integer bitmask, allowing for more precise control over which types of reflection metadata (e.g., ArrayOf, ChanOf, FuncOf, StructOf, MethodByIndex, MethodByName, MethodDynamic) are generated during ABI initialization.
  • ABI Type Pruning: Introduced a new mechanism to prune unneeded ABI types and methods. This feature analyzes the call graph and reflect package usage to selectively generate only the ABI metadata that is actually invoked or explicitly required for reflection, aiming to reduce binary size and improve performance.
  • Centralized Linkname Resolution: The resolveLinkname logic has been moved from the context struct in cl/compile.go to the Program struct in ssa/package.go, centralizing its implementation and ensuring consistent behavior across the compiler.
  • Method Invocation Tracking: The compiler now tracks invoked methods using p.prog.AddInvoke in cl/instr.go, populating an invokeMethods map. This information is crucial for the ABI pruning logic to determine which methods are actually used at runtime.
  • Reflect Call Analysis: A new checkReflect function in ssa/expr.go analyzes calls to the reflect package, identifying specific reflection operations (e.g., reflect.ArrayOf, reflect.Value.Method) and extracting constant arguments to inform the granular NeedAbiInit flags and method pruning.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • cl/builtin_test.go
    • Removed TestContextResolveLinkname test case.
  • cl/compile.go
    • Updated SetResolveLinkname to use ctx.prog.ResolveLinkname instead of a local context method.
    • Removed the resolveLinkname method from the context struct.
  • cl/instr.go
    • Added p.prog.AddInvoke(mthd) to track invoked methods during method calls.
  • internal/build/build.go
    • Changed needAbiInit from boolean to integer and added methodByIndex and methodByName maps.
    • Modified the call to genMainModule to pass a new genConfig struct containing ABI initialization and pruning details.
  • internal/build/main_module.go
    • Introduced genConfig struct to consolidate configuration for main module generation.
    • Updated genMainModule to accept genConfig and implemented ABI pruning logic using call graph analysis.
    • Modified InitAbiTypes to accept an abiInit integer flag for selective type initialization.
  • internal/build/main_module_test.go
    • Updated TestGenMainModuleExecutable and TestGenMainModuleLibrary to use the new genConfig struct.
  • ssa/abitype.go
    • Modified abiUncommonMethodSet and abiUncommonMethods to conditionally compile methods based on abiTypePruning.
    • Introduced AbiSymbol struct to store raw type, ABI type, package path, and uncommon status.
    • Updated getAbiTypes to filter ABI types based on the abiInit flag.
    • Modified InitAbiTypes to accept an abiInit flag and use it for getAbiTypes.
    • Added PruneAbiTypes function to perform ABI type and method pruning based on invocation and reflection flags.
  • ssa/expr.go
    • Added checkReflect function to analyze reflect package calls and set NeedAbiInit flags, including tracking methods by index or name.
    • Introduced extractConstInt and extractConstString helper functions to parse constant arguments.
    • Updated Call method to invoke checkReflect for non-reflect packages.
  • ssa/package.go
    • Changed NeedAbiInit in aPackage to an integer bitmask and added MethodByIndex and MethodByName maps.
    • Added abiTypeName, abiTypePruning, methodIsInvoke, and invokeMethods fields to aProgram.
    • Introduced AddInvoke method to track invoked methods.
    • Renamed patch method to Patch for consistency.
    • Added ResolveLinkname method to Program for centralized linkname resolution.
    • Defined new Reflect constants for granular ABI initialization control.
  • ssa/ssa_test.go
    • Added TestAbiPrune to validate the ABI pruning functionality.
    • Updated TestAbiTables to use the new abiInit parameter and mainpkg.InitAbiTypes.
    • Added TestResolveLinkname to test the centralized linkname resolution.
  • ssa/type.go
    • Updated toLLVMFields and toLLVMTypes to use p.Patch instead of p.patch.
Activity
  • The pull request is currently marked as 'Work In Progress' (WIP), indicating that the author is still actively developing and refining the changes.
  • Significant refactoring has been performed across multiple core components of the compiler, including ABI type generation, build process, and SSA expression handling.
  • New test cases have been added to cover the ABI pruning and granular initialization logic, suggesting a focus on correctness and robustness for these new features.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a significant optimization by adding ABI type pruning, which should help reduce final binary sizes. The mechanism tracks reflection usage and method invocations to determine which ABI information can be safely removed. The refactoring to centralize resolveLinkname and to use a genConfig struct for genMainModule are positive changes for code clarity. I have a couple of suggestions to improve the performance and readability of the new pruning logic.

Comment on lines +125 to +135
for v := range invoked {
if v.Name() == name {
if !types.Identical(prog.Patch(v.Type().(*types.Signature).Recv().Type()), method.Type().(*types.Signature).Recv().Type()) {
continue
}
if !types.Identical(prog.Patch(v.Type()), method.Type()) {
continue
}
return true
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

This loop iterates over all invoked functions for every method being checked for pruning. This can be inefficient if the number of invoked functions and methods is large, leading to O(M*N) complexity where M is the number of methods and N is the number of invoked functions.

To optimize this, you can pre-process the invoked map into a map[string][]*ssa.Function before calling PruneAbiTypes. This allows for a much faster lookup by method name inside this callback.

Example of pre-processing (to be added after line 102):

invokedByName := make(map[string][]*ssa.Function)
for f := range invoked {
    invokedByName[f.Name()] = append(invokedByName[f.Name()], f)
}

Then, you can use this map for an efficient lookup as suggested below.

if funcs, ok := invokedByName[name]; ok {
	for _, v := range funcs {
		if !types.Identical(prog.Patch(v.Type().(*types.Signature).Recv().Type()), method.Type().(*types.Signature).Recv().Type()) {
			continue
		}
		if !types.Identical(prog.Patch(v.Type()), method.Type()) {
			continue
		}
		return true
	}
}

Comment thread internal/build/main_module.go
Comment thread ssa/package.go
@@ -322,6 +341,17 @@ func (p Program) Linkname(name string) (link string, ok bool) {
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Documentation/Security] ResolveLinkname was moved from context to Program and is now public. The panic behavior on invalid links (non-C. prefix) is now reachable from more call sites. Please add a doc comment noting:

  • This method panics if the link prefix is not C
  • What callers should expect for valid linknames

Comment thread ssa/package.go
Comment on lines +739 to +750
const (
ReflectArrayOf = 1 << iota
ReflectChanOf
ReflectFuncOf
ReflectMapOf
ReflectPointerTo
ReflectSliceOf
ReflectStructOf
ReflectMethodByIndex
ReflectMethodByName
ReflectMethodDynamic
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Documentation] The Reflect* constants are the core of the new NeedAbiInit bitmask system. Please add a doc comment block explaining:

  • These are bitmask flags meant to be combined with bitwise OR
  • Each corresponds to a specific reflect package function
  • The distinction between ReflectMethodByIndex, ReflectMethodByName, and ReflectMethodDynamic

Comment thread ssa/abitype.go
return initFn
}

func (p Package) PruneAbiTypes(needAbiInit int, methodIsInvoke func(index int, method *types.Selection) bool) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Documentation] PruneAbiTypes is the central new public method and has no doc comment. Please document:

  • How needAbiInit relates to the Reflect* bitmask constants
  • The semantics of methodIsInvoke callback (what index refers to, what returning true means)
  • That passing nil for methodIsInvoke provides a default implementation

Comment on lines +125 to +135
for v := range invoked {
if v.Name() == name {
if !types.Identical(prog.Patch(v.Type().(*types.Signature).Recv().Type()), method.Type().(*types.Signature).Recv().Type()) {
continue
}
if !types.Identical(prog.Patch(v.Type()), method.Type()) {
continue
}
return true
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Performance] The inner loop for v := range invoked iterates over all invoked functions for every method being checked, resulting in O(S × M × I) complexity where S = uncommon ABI symbols, M = methods per type, I = invoked functions.

Consider building a name-indexed lookup (e.g., map[string][]*ssa.Function) during buildInvokeIndex to make name-based lookup O(1) amortized:

// In buildInvokeIndex, also build:
invokedByName := make(map[string][]*ssa.Function)
for fn := range invoked {
    invokedByName[fn.Name()] = append(invokedByName[fn.Name()], fn)
}

Comment thread internal/build/main_module.go Outdated
}
for v := range invoked {
if v.Name() == name {
if !types.Identical(prog.Patch(v.Type().(*types.Signature).Recv().Type()), method.Type().(*types.Signature).Recv().Type()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Security] The chained type assertions and dereferences v.Type().(*types.Signature).Recv().Type() could panic if:

  1. The type is not a *types.Signature
  2. Recv() returns nil (non-method function)

While CHA should only populate invoked with interface dispatch targets, consider adding defensive guards:

sig, ok := v.Type().(*types.Signature)
if !ok || sig.Recv() == nil {
    continue
}

Comment thread ssa/expr.go
Comment on lines 1138 to 1150
var (
reflectFunc = map[string]struct{}{
"reflect.ArrayOf": {},
"reflect.ChanOf": {},
"reflect.FuncOf": {},
"reflect.MapOf": {},
"reflect.SliceOf": {},
"reflect.StructOf": {},
"reflect.PointerTo": {},
"reflect.PtrTo": {},
"reflect.SliceOf": {},
"reflect.StructOf": {},
"reflect.Value.Method": {},
"reflect.Value.MethodByName": {},
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Code Quality] The reflectFunc map is now dead code since checkReflect() uses a switch statement instead. Consider removing it to avoid confusion and linter warnings.

Comment thread ssa/package.go
Comment on lines +234 to 241
m = make(map[types.Type]none)
p.invokeMethods[name] = m
}
m[p.Patch(fn.Type())] = none{}
}

// A Program presents a program.
type Program = *aProgram
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Documentation] Program.AddInvoke is a new public method critical for the pruning algorithm. Please add a doc comment explaining:

  • What "invoke" means (interface dispatch vs direct call)
  • When callers should use this method
  • The relationship between AddInvoke and PruneAbiTypes

Comment thread ssa/abitype.go
Comment on lines +668 to +672
prog.abiTypePruning = true
defer func() {
prog.abiTypePruning = false
}()
prog.methodIsInvoke = methodIsInvoke
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Code Quality] Storing transient state (abiTypePruning, methodIsInvoke) on the long-lived Program object is fragile. If PruneAbiTypes were called reentrantly or concurrently, these flags could be incorrectly reset. Consider passing the pruning predicate through the call chain instead of storing on the program object.

@fennoai
Copy link
Copy Markdown
Contributor

fennoai Bot commented Feb 12, 2026

Code Review Summary

This PR introduces ABI type pruning via call graph analysis - a valuable optimization to reduce binary size by eliminating unused method function pointers. The implementation approach using CHA and tracking reflect usage at compile-time is architecturally sound.

Key findings:

  1. Documentation gaps: Several new public APIs (AddInvoke, ResolveLinkname, PruneAbiTypes, Reflect* constants) lack doc comments. Given the complexity, documentation would significantly improve maintainability.

  2. Performance concern: The invoke matching loop at main_module.go:125-135 has O(I) complexity per method. Consider building a name-indexed lookup for O(1) amortized access.

  3. Safety: Chained type assertions without ok-checks at main_module.go:127 could panic on unexpected types.

  4. Dead code: The reflectFunc map in expr.go is now unused after switching to checkReflect().

See inline comments for specific suggestions.

@visualfc visualfc force-pushed the callgraph_abiinit branch 2 times, most recently from fe18da7 to a62c57a Compare February 12, 2026 11:05
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 12, 2026

Codecov Report

❌ Patch coverage is 73.80952% with 44 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.09%. Comparing base (c18be41) to head (6eb6051).
⚠️ Report is 414 commits behind head on main.

Files with missing lines Patch % Lines
ssa/expr.go 43.47% 26 Missing ⚠️
ssa/abitype.go 82.17% 16 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1635      +/-   ##
==========================================
- Coverage   91.35%   91.09%   -0.26%     
==========================================
  Files          47       47              
  Lines       12681    12806     +125     
==========================================
+ Hits        11585    11666      +81     
- Misses        906      948      +42     
- Partials      190      192       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@visualfc visualfc force-pushed the callgraph_abiinit branch 3 times, most recently from 7ba67aa to a90fe5d Compare February 28, 2026 09:08
@visualfc visualfc force-pushed the callgraph_abiinit branch from a90fe5d to 6eb6051 Compare March 1, 2026 23:27
@visualfc visualfc closed this Mar 27, 2026
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