| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package midway |
| |
| import ( |
| "cmd/compile/internal/base" |
| "cmd/compile/internal/syntax" |
| "cmd/compile/internal/types2" |
| ) |
| |
| // Analyzer holds the state for SIMD dependency analysis |
| type Analyzer struct { |
| pkg *types2.Package |
| info *types2.Info |
| isDependentObj map[types2.Object]bool // does an Object depend on a simd type in some way? |
| isDependentMethod map[types2.Object]bool // is this dependent Object also a method? (methods are not renamed, their types are) |
| hasDependentMethod map[types2.Type]bool // is this a type that has a dependent method? |
| visited map[types2.Type]bool // if in map, type has been visited, and value is whether type is dependent |
| inSimd bool // true if the current package is the simd package (which is a special case) |
| } |
| |
| func NewAnalyzer(pkg *types2.Package, info *types2.Info) *Analyzer { |
| return &Analyzer{ |
| pkg: pkg, |
| info: info, |
| isDependentObj: make(map[types2.Object]bool), |
| isDependentMethod: make(map[types2.Object]bool), |
| hasDependentMethod: make(map[types2.Type]bool), |
| visited: make(map[types2.Type]bool), |
| inSimd: pkg.Path() == simdPkg, |
| } |
| } |
| |
| // Analyze builds the set of SIMD-dependent objects |
| func (a *Analyzer) Analyze(files []*syntax.File) bool { |
| // Phase 1: Seed dependence from types and signatures |
| hdmsize := len(a.hasDependentMethod) |
| |
| for { |
| for _, obj := range a.info.Defs { |
| if obj != nil { |
| a.markIfDependent(obj) |
| } |
| } |
| for _, obj := range a.info.Uses { |
| if obj != nil { |
| a.markIfDependent(obj) |
| } |
| } |
| if hdmsize == len(a.hasDependentMethod) { |
| break |
| } |
| if base.Debug.Simd > 0 { |
| base.Warn("hasDependentMethod increased from %d to %d", hdmsize, len(a.hasDependentMethod)) |
| } |
| hdmsize = len(a.hasDependentMethod) |
| clear(a.visited) |
| } |
| |
| // Phase 2: Transitive closure via function bodies |
| changed := true |
| for changed { |
| changed = false |
| for _, file := range files { |
| for _, decl := range file.DeclList { |
| if fn, ok := decl.(*syntax.FuncDecl); ok { |
| if fn.Name == nil { |
| continue |
| } |
| obj := a.info.Defs[fn.Name] |
| if obj == nil || a.isDependentObj[obj] { |
| continue |
| } |
| |
| if a.hasBodyDependency(fn) { |
| a.isDependentObj[obj] = true |
| changed = true |
| } |
| } |
| } |
| } |
| } |
| |
| return len(a.isDependentObj) > 0 |
| } |
| |
| func (a *Analyzer) hasBodyDependency(fn *syntax.FuncDecl) bool { |
| if fn.Body == nil { |
| return false |
| } |
| // Walk the body and check identifiers |
| // This will also note any variable references that are dependent. |
| found := false |
| syntax.Inspect(fn.Body, func(n syntax.Node) bool { |
| if id, ok := n.(*syntax.Name); ok { |
| obj := a.info.Uses[id] |
| if obj == nil { |
| obj = a.info.Defs[id] |
| } |
| if obj != nil { |
| if _, isFunc := obj.(*types2.Func); !isFunc { |
| if a.isDependentObj[obj] { |
| found = true |
| return false |
| } |
| } else { |
| sig := obj.Type().(*types2.Signature) |
| if a.HasDependentSignature(sig) { |
| found = true |
| return false |
| } |
| } |
| if a.isDependentType(obj.Type()) { |
| // Whatever this is, it makes the outer object dependent. |
| // If this is a package variable with dependent type, mark the |
| // variable as dependent, so that references to it become dependent. |
| if obj, ok := obj.(*types2.Var); ok && obj.Kind() == types2.PackageVar { |
| // everything else is nested within a dependent function/struct/scope |
| // and does not need its own renaming |
| a.isDependentObj[obj] = true |
| } |
| found = true |
| return false |
| } |
| if isBaseSimdTypeObj(obj) { |
| found = true |
| return false |
| } |
| } |
| } |
| return true |
| }) |
| return found |
| } |
| |
| func (a *Analyzer) markIfDependent(obj types2.Object) bool { |
| if a.isDependentObj[obj] { |
| return true |
| } |
| |
| isDep := false |
| isDepMeth := false |
| switch obj := obj.(type) { |
| case *types2.Var: |
| if obj.Pkg() == a.pkg && obj.Parent() == a.pkg.Scope() { |
| isDep = a.isDependentType(obj.Type()) |
| } |
| case *types2.TypeName: |
| isDep = a.isDependentType(obj.Type()) |
| case *types2.Func: |
| sig := obj.Type().(*types2.Signature) |
| if a.HasDependentSignature(sig) { |
| // NOT dependent if it is a method of one of the base SIMD types. |
| // TODO: what about aliases of base SIMD types? |
| if rcv := sig.Recv(); rcv == nil { |
| isDep = true |
| } else if named, ok := rcv.Type().(*types2.Named); !ok || !isBaseSimdType(named) { |
| isDep = true |
| t := rcv.Type() |
| if !a.isDependentType(t) { |
| a.markHasMethod(t) |
| } |
| isDepMeth = true |
| } |
| } |
| } |
| |
| // Also check if obj name is "simd.Type" (base case) |
| if isBaseSimdTypeObj(obj) { |
| isDep = true |
| } |
| |
| if isDep { |
| if base.Debug.Simd > 0 { |
| base.Warn("%s: %v is simd-dependent", obj.Pos().String(), obj) |
| } |
| a.isDependentObj[obj] = true |
| } |
| if isDepMeth { |
| if base.Debug.Simd > 0 { |
| base.Warn("%s: %v is simd-dependent method", obj.Pos().String(), obj) |
| } |
| a.isDependentMethod[obj] = true |
| } |
| return isDep |
| } |
| |
| func (a *Analyzer) isDependentType(t types2.Type) bool { |
| return a.checkTypeRecursive(t) |
| } |
| |
| func (a *Analyzer) checkTypeRecursive(t types2.Type) bool { |
| if t == nil { |
| return false |
| } |
| if a.hasDependentMethod[t] { |
| a.visited[t] = true |
| } |
| if b, ok := a.visited[t]; ok { |
| return b // Break cycles |
| } |
| a.visited[t] = false |
| |
| memo := func(b bool) bool { |
| a.visited[t] = b |
| return b |
| } |
| |
| // Unwrap aliases |
| if named, ok := t.(*types2.Named); ok { |
| if isBaseSimdType(named) { |
| return memo(true) |
| } |
| if a.checkTypeRecursive(named.Underlying()) { |
| return memo(true) |
| } |
| } |
| |
| switch t := t.(type) { |
| case *types2.Basic: |
| return false |
| case *types2.Pointer: |
| return memo(a.checkTypeRecursive(t.Elem())) |
| case *types2.Slice: |
| return memo(a.checkTypeRecursive(t.Elem())) |
| case *types2.Array: |
| return memo(a.checkTypeRecursive(t.Elem())) |
| case *types2.Map: |
| return memo(a.checkTypeRecursive(t.Key()) || |
| a.checkTypeRecursive(t.Elem())) |
| case *types2.Chan: |
| return memo(a.checkTypeRecursive(t.Elem())) |
| case *types2.Struct: |
| for i := 0; i < t.NumFields(); i++ { |
| if a.checkTypeRecursive(t.Field(i).Type()) { |
| return memo(true) |
| } |
| } |
| case *types2.Signature: |
| return memo(a.HasDependentSignature(t)) |
| case *types2.Tuple: |
| for i := 0; i < t.Len(); i++ { |
| if a.checkTypeRecursive(t.At(i).Type()) { |
| return memo(true) |
| } |
| } |
| case *types2.Alias: |
| return memo(a.checkTypeRecursive(types2.Unalias(t))) |
| } |
| return false |
| } |
| |
| // This attempts to mark types that are not otherwise dependent |
| // as being dependent, if they have a method with a dependent |
| // signature. |
| func (a *Analyzer) markHasMethod(t types2.Type) { |
| if t == nil { |
| return |
| } |
| if a.hasDependentMethod[t] { |
| return |
| } |
| |
| a.hasDependentMethod[t] = true |
| |
| switch t := t.(type) { |
| case *types2.Pointer: |
| a.markHasMethod(t.Elem()) |
| case *types2.Alias: |
| a.markHasMethod(t.Rhs()) |
| } |
| return |
| } |
| |
| func isBaseSimdType(t *types2.Named) bool { |
| return isBaseSimdTypeObj(t.Obj()) |
| } |
| |
| func isBaseSimdTypeObj(obj types2.Object) bool { |
| if obj == nil || obj.Pkg() == nil { |
| return false |
| } |
| if obj.Pkg().Path() != simdPkg { |
| return false |
| } |
| return isSimdTypeName(obj.Name()) |
| } |
| |
| func (a *Analyzer) HasDependentSignature(sig *types2.Signature) bool { |
| // TODO what about type parameters? Need to invent a test that provokes that case. |
| return a.isDependentType(sig.Params()) || |
| a.isDependentType(sig.Results()) || |
| (sig.Recv() != nil && a.isDependentType(sig.Recv().Type())) |
| } |