Documentation
¶
Overview ¶
Package resolve defines the cross-language binding resolvers — the piece that turns per-language symbol islands into one connected graph. Each resolver detects one FFI boundary kind and emits edges (ECgo, EAsm, ELaunch). New languages are added by implementing BindingResolver and registering it; nothing else in the pipeline changes.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type BindingResolver ¶
type BindingResolver interface {
Name() string // "cgo", "plan9asm", "cuda", "gpupipe"
Langs() []graph.Lang // languages whose file changes require a re-run
Resolve(ctx context.Context, g graph.Graph, fs FileSet) ([]graph.Edge, error)
}
BindingResolver inspects the graph plus the source files of its languages and returns cross-language edges to be added.
type CgoResolver ¶
type CgoResolver struct{}
CgoResolver links the Go <-> C boundary (edge kind ECgo).
Detection contract:
- A Go file with `import "C"` is a cgo file. Every call C.<ident>(…) whose ident is not a C primitive-type conversion yields an ECgo edge from the enclosing Go function node to node "c:<ident>". The C symbol is left unresolved here (the C parser lands in M2); the indexer materializes "c:<ident>" as a stub node so the impact radius stays complete.
- A `//export Name` directive immediately above a Go function yields the reverse ECgo edge from "c:Name" to that Go function node (C calling into Go).
#include preamble edges (Go file -> header file) arrive with file-level nodes in a later milestone; they are disconnected from the symbol radius without them, so M1 omits them to keep `get depth` results clean.
func (CgoResolver) Langs ¶
func (CgoResolver) Langs() []graph.Lang
Langs are the languages whose changes require re-running this resolver.
type CudaResolver ¶
type CudaResolver struct{}
CudaResolver links host code to CUDA kernels (edge kind ELaunch).
Detection contract:
- `__global__` function definitions in .cu/.cuh files become KKernel nodes with IDs "cu:<name>" (index.CudaParser owns that).
- `extern "C"` host wrapper functions in .cu files are minted with "c:" IDs (not "cu:"), so the cgo resolver chains Go -> c:wrapper and this resolver chains c:wrapper -launch-> cu:kernel — completing the Go -> C -> CUDA impact path.
- A triple-chevron launch `name<<<grid, block>>>(args)` inside the body of the *currently open* extern "C" wrapper yields an ELaunch edge from "c:<wrapper>" to "cu:<name>". A launch outside any wrapper (e.g. inside a plain __device__/__global__ helper) is skipped — this resolver only tracks the host FFI boundary, not device-side dynamic parallelism.
This is a line-oriented scanner mirroring index.CudaParser (no cgo tree-sitter dependency); it tracks the "currently active wrapper" via the same naive brace-depth walk used there.
func (CudaResolver) Langs ¶
func (CudaResolver) Langs() []graph.Lang
func (CudaResolver) Name ¶
func (CudaResolver) Name() string
type FFIResolver ¶
type FFIResolver struct{}
FFIResolver links the C <-> C++ FFI boundary left dangling by cSpec's and cppSpec's CallRe (LSP-001): a cpp: call site invoking a name that only exists as a c: symbol (extern "C" declarations, C headers called from C++) or vice versa.
Detection contract (RSV-001: edges only, no node minting):
- This mirrors — rather than imports — cSpec's/cppSpec's function Defs (plain function, and cppSpec's out-of-line `Foo::bar(` method) and CallRe/Stop (see internal/langspec/c.go, cpp.go): resolve is a lower layer than internal/index, which internal/langspec imports, so resolve cannot depend on langspec (same reason CudaResolver mirrors index.cudaExternCRe instead of importing internal/index). Body-span scanning itself does NOT carry this restriction: T-0054 extracted that logic into internal/cspan, a leaf package with zero non-stdlib imports that both langspec and resolve depend on directly, so this resolver's brace-span handling (K&R, prototypes, multi-line headers, Allman) is byte-for-byte the same code the primary langspec pass runs — not a second, independently-maintained copy (see docs/validation-ddnet.md's "## ffi re-validation" section for why that duplication used to block the cpp:->c: FFI crossing entirely).
- For every c:/cpp: function def found this way, every call inside its brace-counted body that is neither Stop-listed nor the def's own name is a callee candidate exactly like the primary langspec pass. If the callee already resolves within the same language (g.Node finds lang:callee), there is nothing to bridge — the primary pass already connected it. Only when it is dangling in its own language AND the sibling language's node (siblingLang:callee) exists in g does this resolver emit a NEW edge Src -> sibling, Kind ECall, same File/Line as the call site. Only g.Node lookups are used — no g.Find/g.Neighbors graph enumeration, no node minting; the original dangling edge from the primary pass is left untouched, resolvers only add.
func (FFIResolver) Langs ¶
func (FFIResolver) Langs() []graph.Lang
Langs are the languages whose changes require re-running this resolver.
type FileSet ¶
FileSet gives resolvers read access to the indexed source files without binding them to the filesystem layout (tests use in-memory sets).
type GpuPipeResolver ¶
type GpuPipeResolver struct{}
GpuPipeResolver links pipeline-based GPU dispatch (Metal, Vulkan) where the binding is by name at runtime, not by symbol (edge kind ELaunch, best effort).
Detection contract (Metal implemented, Vulkan future):
- Metal: a string literal "kern_name" appearing as the argument of newFunctionWithName: (ObjC) is matched against [[kernel]] entry points of the same name in .metal files -> ELaunch edge from the enclosing host function to "msl:<name>".
- Vulkan: host code binds a compiled SPIR-V module blob by FILE PATH via vkCreateShaderModule and names the entry point through VkPipelineShaderStageCreateInfo.pName (almost always the literal "main") — unlike Metal's newFunctionWithName:@"<kernelName>", there is no host-side kernel-name literal to string-match against a glsl entry point. A meaningful Vulkan launch edge needs module-provenance tracking (which .spv/.comp compiles into which module handle), a separate harder problem than the line-scanner name-match this resolver does for Metal. GLSL shader SOURCE is now parsed (glsl: nodes, P-0046) so the target nodes exist; only the host->module binding is unresolved.
- These edges are heuristic string matches (RSV-002): the resolver emits edges only — it never mints or mutates nodes (RSV-001).
func (GpuPipeResolver) Langs ¶
func (GpuPipeResolver) Langs() []graph.Lang
func (GpuPipeResolver) Name ¶
func (GpuPipeResolver) Name() string
type Plan9AsmResolver ¶
type Plan9AsmResolver struct{}
Plan9AsmResolver links Go declarations to their assembly implementations (edge kind EAsm).
Detection contract:
- A `TEXT ·Name(SB)` (or `TEXT pkg·Name(SB)`) definition in a .s file mints the same qualified name as AsmParser: "<dir-base>.Name", where dir-base is the .s file's containing directory (the Go package convention, e.g. mat_amd64.s in package mat -> "mat.mulVec").
- If both go:<dir-base>.Name and asm:<dir-base>.Name already exist as nodes in the graph, the pair yields an EAsm edge go:<dir-base>.Name -> asm:<dir-base>.Name, sited at the TEXT line. Neither side is speculatively created here — the indexer materializes stub nodes for any edge endpoint a parser didn't produce, so a TEXT-only or Go-only side still surfaces as a stub, not a dangling edge.
- GLOBL data symbols are not resolved to Go references in M1.5; that needs linkname/relocation analysis and is left for a later milestone.
func (Plan9AsmResolver) Langs ¶
func (Plan9AsmResolver) Langs() []graph.Lang
Langs are the languages whose changes require re-running this resolver.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the active resolvers in registration order.
func (*Registry) All ¶
func (r *Registry) All() []BindingResolver
func (*Registry) Register ¶
func (r *Registry) Register(b BindingResolver)
type VulkanResolver ¶
type VulkanResolver struct{}
VulkanResolver links Vulkan compute/graphics dispatch (edge kind ELaunch) where the binding is neither a symbol reference (cgo/cuda) nor a host-side kernel-name string literal (Metal/gpupipe, see gpupipe.go): Vulkan host code loads a compiled SPIR-V blob by FILE PATH via vkCreateShaderModule and names the entry point through VkPipelineShaderStageCreateInfo.pName — almost always the literal "main". Recovering the binding therefore requires tracing module *provenance* across three statements in one host file:
- BLOB a string literal ending in ".spv" is bound to an identifier (its assignment target, if the line has one) or, absent one, remembered as the file's most recent "pending" blob. The blob's *stem* — its basename with every extension stripped ("blur.comp.spv" -> "blur") — is what ties it back to the glsl source node the primary indexing pass minted from "blur.comp".
- MODULE a vkCreateShaderModule(...) call binds its output module identifier (the call's last argument, leading '&' stripped) to a blob stem: the stem bound to the createInfo's pCode identifier if traceable, else the file's pending blob stem.
- STAGE a VkPipelineShaderStageCreateInfo struct literal whose .module field names a bound module identifier and whose .pName field carries a non-empty entry-point literal resolves to (stem, entry). There is no default pName: an omitted or empty pName means no edge (RSV-004) — this resolver never assumes "main".
(stem, entry) is then resolved to a real glsl: node BY GRAPH LOOKUP (graph.Graph.Find), never by ids.Mint: GLSL is QualFlat (internal/langspec/glsl.go), so every shader's `void main()` mints the same base ID "glsl:main" and the indexer disambiguates same-named defs across files with "~2", "~3", ... in file-path order (internal/ids). Minting "glsl:<entry>" directly would silently target the first-indexed shader by that name and wire every other same-named shader's launch to the wrong node — RSV-001 also forbids resolvers from minting nodes outright; only the parser passes do that.
Ambiguity is not an edge (RSV-004): if graph lookup finds zero or more than one glsl node matching (stem, entry), this resolver emits nothing for that dispatch rather than guessing.
func (VulkanResolver) Langs ¶
func (VulkanResolver) Langs() []graph.Lang
func (VulkanResolver) Name ¶
func (VulkanResolver) Name() string
func (VulkanResolver) Resolve ¶
Resolve scans every C, C++ and Go source file for the BLOB -> MODULE -> STAGE provenance chain described in the type doc and emits one ELaunch edge per resolved dispatch. Edges are sorted before returning so the result is deterministic regardless of FileSet.ByLang iteration order (map-backed FileSet implementations, including tests, do not guarantee order).