1
0
mirror of https://github.com/golang/go synced 2024-11-19 04:14:45 -07:00
go/pointer/api.go
Alan Donovan 5b55a71008 go.tools/pointer: strength reduction during constraint generation.
Motivation: simple constraints---copy and addr---are more
amenable to pre-solver optimizations (forthcoming) than
complex constraints: load, store, and all others.

In code such as the following:

         t0 = new struct { x, y int }
         t1 = &t0.y
         t2 = *t1

there's no need for the full generality of a (complex)
load constraint for t2=*t1 since t1 can only point to t0.y.
All we need is a (simple) copy constraint t2 = (t0.y)
where (t0.y) is the object node label for that field.

For all "addressable" SSA instructions, we tabulate
whether their points-to set is necessarily a singleton.  For
some (e.g. Alloc, MakeSlice, etc) this is always true by
design.  For others (e.g. FieldAddr) it depends on their
operands.

We exploit this information when generating constraints:
all load-form and store-form constraints are reduced to copy
constraints if the pointer's PTS is a singleton.
Similarly all FieldAddr (y=&x.f) and IndexAddr (y=&x[0])
constraints are reduced to offset addition, for singleton
operands.

Here's the constraint mix when running on the oracle itself.
The total number of constraints is unchanged but the fraction
that are complex has gone down to 21% from 53%.

                before    after
--simple--
 addr		20682     46949
 copy        	61454     91211
--complex--
 offsetAddr  	41621     15325
 load        	18769     12925
 store       	30758     6908
 invoke      	758       760
 typeAssert  	1688      1689
total           175832    175869

Also:
- Add Pointer.Context() for local variables,
  since we now plumb cgnodes throughout. Nice.
- Refactor all load-form (load, receive, lookup) and
  store-form (Store, send, MapUpdate) constraints to use
  genLoad and genStore.
- Log counts of constraints by type.
- valNodes split into localval and globalval maps;
  localval is purged after each function.
- analogous maps localobj[v] and globalobj[v] hold sole label
  for pts(v), if singleton.
- fnObj map subsumed by globalobj.
- make{Function/Global/Constant} inlined into objectValue.
  Much cleaner.

R=crawshaw
CC=golang-dev
https://golang.org/cl/13979043
2013-09-27 11:33:01 -04:00

235 lines
6.4 KiB
Go

// Copyright 2013 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 pointer
import (
"fmt"
"go/token"
"io"
"code.google.com/p/go.tools/call"
"code.google.com/p/go.tools/go/types/typemap"
"code.google.com/p/go.tools/ssa"
)
type Config struct {
// -------- Scope of the analysis --------
// Clients must provide the analysis with at least one package defining a main() function.
Mains []*ssa.Package // set of 'main' packages to analyze
root *ssa.Function // synthetic analysis root
// Reflection determines whether to handle reflection
// operators soundly, which is currently rather slow since it
// causes constraint to be generated during solving
// proportional to the number of constraint variables, which
// has not yet been reduced by presolver optimisation.
Reflection bool
// BuildCallGraph determines whether to construct a callgraph.
// If enabled, the graph will be available in Result.CallGraph.
BuildCallGraph bool
// -------- Optional callbacks invoked by the analysis --------
// Warn is invoked for each warning encountered by the analysis,
// e.g. unknown external function, unsound use of unsafe.Pointer.
// pos may be zero if the position is not known.
Warn func(pos token.Pos, format string, args ...interface{})
// Print is invoked during the analysis for each discovered
// call to the built-in print(x).
//
// Pointer p may be saved until the analysis is complete, at
// which point its methods provide access to the analysis
// (The result of callings its methods within the Print
// callback is undefined.) p is nil if x is non-pointerlike.
//
// TODO(adonovan): this was a stop-gap measure for identifing
// arbitrary expressions of interest in the tests. Now that
// ssa.ValueForExpr exists, we should use that instead.
//
Print func(site *ssa.CallCommon, p Pointer)
// The client populates Queries[v] for each ssa.Value v of
// interest.
//
// The boolean (Indirect) indicates whether to compute the
// points-to set for v (false) or *v (true): the latter is
// typically wanted for Values corresponding to source-level
// lvalues, e.g. an *ssa.Global.
//
// The pointer analysis will populate the corresponding
// Results.Queries value when it creates the pointer variable
// for v or *v. Upon completion the client can inspect that
// map for the results.
//
// If a Value belongs to a function that the analysis treats
// context-sensitively, the corresponding Results.Queries slice
// may have multiple Pointers, one per distinct context. Use
// PointsToCombined to merge them.
//
Queries map[ssa.Value]Indirect
// -------- Other configuration options --------
// If Log is non-nil, a log messages are written to it.
// Logging is extremely verbose.
Log io.Writer
}
type Indirect bool // map[ssa.Value]Indirect is not a set
func (c *Config) prog() *ssa.Program {
for _, main := range c.Mains {
return main.Prog
}
panic("empty scope")
}
// A Result contains the results of a pointer analysis.
//
// See Config for how to request the various Result components.
//
type Result struct {
CallGraph call.Graph // discovered call graph
Queries map[ssa.Value][]Pointer // points-to sets for queried ssa.Values
}
// A Pointer is an equivalence class of pointerlike values.
type Pointer interface {
// PointsTo returns the points-to set of this pointer.
PointsTo() PointsToSet
// MayAlias reports whether the receiver pointer may alias
// the argument pointer.
MayAlias(Pointer) bool
// Context returns the context of this pointer,
// if it corresponds to a local variable.
Context() call.GraphNode
String() string
}
// A PointsToSet is a set of labels (locations or allocations).
//
type PointsToSet interface {
// PointsTo returns the set of labels that this points-to set
// contains.
Labels() []*Label
// Intersects reports whether this points-to set and the
// argument points-to set contain common members.
Intersects(PointsToSet) bool
// If this PointsToSet came from a Pointer of interface kind
// or a reflect.Value, DynamicTypes returns the set of dynamic
// types that it may contain. (For an interface, they will
// always be concrete types.)
//
// The result is a mapping whose keys are the dynamic types to
// which it may point. For each pointer-like key type, the
// corresponding map value is a set of pointer abstractions of
// that dynamic type, represented as a []Pointer slice. Use
// PointsToCombined to merge them.
//
// The result is empty unless CanHaveDynamicTypes(T).
//
DynamicTypes() *typemap.M
}
// Union returns the set containing all the elements of each set in sets.
func Union(sets ...PointsToSet) PointsToSet {
var union ptset
for _, set := range sets {
set := set.(ptset)
union.a = set.a
union.pts.addAll(set.pts)
}
return union
}
// PointsToCombined returns the combined points-to set of all the
// specified pointers.
func PointsToCombined(ptrs []Pointer) PointsToSet {
var ptsets []PointsToSet
for _, ptr := range ptrs {
ptsets = append(ptsets, ptr.PointsTo())
}
return Union(ptsets...)
}
// ---- PointsToSet public interface
type ptset struct {
a *analysis // may be nil if pts is nil
pts nodeset
}
func (s ptset) Labels() []*Label {
var labels []*Label
for l := range s.pts {
labels = append(labels, s.a.labelFor(l))
}
return labels
}
func (s ptset) DynamicTypes() *typemap.M {
var tmap typemap.M
tmap.SetHasher(s.a.hasher)
for ifaceObjId := range s.pts {
tDyn, v, indirect := s.a.taggedValue(ifaceObjId)
if tDyn == nil {
continue // !CanHaveDynamicTypes(tDyn)
}
if indirect {
panic("indirect tagged object") // implement later
}
prev, _ := tmap.At(tDyn).([]Pointer)
tmap.Set(tDyn, append(prev, ptr{s.a, nil, v}))
}
return &tmap
}
func (x ptset) Intersects(y_ PointsToSet) bool {
y := y_.(ptset)
for l := range x.pts {
if _, ok := y.pts[l]; ok {
return true
}
}
return false
}
// ---- Pointer public interface
// ptr adapts a node to the Pointer interface.
type ptr struct {
a *analysis
cgn *cgnode
n nodeid // non-zero
}
func (p ptr) String() string {
return fmt.Sprintf("n%d", p.n)
}
func (p ptr) Context() call.GraphNode {
return p.cgn
}
func (p ptr) PointsTo() PointsToSet {
return ptset{p.a, p.a.nodes[p.n].pts}
}
func (p ptr) MayAlias(q Pointer) bool {
return p.PointsTo().Intersects(q.PointsTo())
}
func (p ptr) DynamicTypes() *typemap.M {
return p.PointsTo().DynamicTypes()
}