435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
271 lines
8.4 KiB
Go
271 lines
8.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 ir
|
|
|
|
// This file defines utilities for working with source positions
|
|
// or source-level named entities ("objects").
|
|
|
|
// TODO(adonovan): test that {Value,Instruction}.Pos() positions match
|
|
// the originating syntax, as specified.
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
)
|
|
|
|
// EnclosingFunction returns the function that contains the syntax
|
|
// node denoted by path.
|
|
//
|
|
// Syntax associated with package-level variable specifications is
|
|
// enclosed by the package's init() function.
|
|
//
|
|
// Returns nil if not found; reasons might include:
|
|
// - the node is not enclosed by any function.
|
|
// - the node is within an anonymous function (FuncLit) and
|
|
// its IR function has not been created yet
|
|
// (pkg.Build() has not yet been called).
|
|
//
|
|
func EnclosingFunction(pkg *Package, path []ast.Node) *Function {
|
|
// Start with package-level function...
|
|
fn := findEnclosingPackageLevelFunction(pkg, path)
|
|
if fn == nil {
|
|
return nil // not in any function
|
|
}
|
|
|
|
// ...then walk down the nested anonymous functions.
|
|
n := len(path)
|
|
outer:
|
|
for i := range path {
|
|
if lit, ok := path[n-1-i].(*ast.FuncLit); ok {
|
|
for _, anon := range fn.AnonFuncs {
|
|
if anon.Pos() == lit.Type.Func {
|
|
fn = anon
|
|
continue outer
|
|
}
|
|
}
|
|
// IR function not found:
|
|
// - package not yet built, or maybe
|
|
// - builder skipped FuncLit in dead block
|
|
// (in principle; but currently the Builder
|
|
// generates even dead FuncLits).
|
|
return nil
|
|
}
|
|
}
|
|
return fn
|
|
}
|
|
|
|
// HasEnclosingFunction returns true if the AST node denoted by path
|
|
// is contained within the declaration of some function or
|
|
// package-level variable.
|
|
//
|
|
// Unlike EnclosingFunction, the behaviour of this function does not
|
|
// depend on whether IR code for pkg has been built, so it can be
|
|
// used to quickly reject check inputs that will cause
|
|
// EnclosingFunction to fail, prior to IR building.
|
|
//
|
|
func HasEnclosingFunction(pkg *Package, path []ast.Node) bool {
|
|
return findEnclosingPackageLevelFunction(pkg, path) != nil
|
|
}
|
|
|
|
// findEnclosingPackageLevelFunction returns the Function
|
|
// corresponding to the package-level function enclosing path.
|
|
//
|
|
func findEnclosingPackageLevelFunction(pkg *Package, path []ast.Node) *Function {
|
|
if n := len(path); n >= 2 { // [... {Gen,Func}Decl File]
|
|
switch decl := path[n-2].(type) {
|
|
case *ast.GenDecl:
|
|
if decl.Tok == token.VAR && n >= 3 {
|
|
// Package-level 'var' initializer.
|
|
return pkg.init
|
|
}
|
|
|
|
case *ast.FuncDecl:
|
|
// Declared function/method.
|
|
fn := findNamedFunc(pkg, decl.Pos())
|
|
if fn == nil && decl.Recv == nil && decl.Name.Name == "init" {
|
|
// Hack: return non-nil when IR is not yet
|
|
// built so that HasEnclosingFunction works.
|
|
return pkg.init
|
|
}
|
|
return fn
|
|
}
|
|
}
|
|
return nil // not in any function
|
|
}
|
|
|
|
// findNamedFunc returns the named function whose FuncDecl.Ident is at
|
|
// position pos.
|
|
//
|
|
func findNamedFunc(pkg *Package, pos token.Pos) *Function {
|
|
for _, fn := range pkg.Functions {
|
|
if fn.Pos() == pos {
|
|
return fn
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValueForExpr returns the IR Value that corresponds to non-constant
|
|
// expression e.
|
|
//
|
|
// It returns nil if no value was found, e.g.
|
|
// - the expression is not lexically contained within f;
|
|
// - f was not built with debug information; or
|
|
// - e is a constant expression. (For efficiency, no debug
|
|
// information is stored for constants. Use
|
|
// go/types.Info.Types[e].Value instead.)
|
|
// - e is a reference to nil or a built-in function.
|
|
// - the value was optimised away.
|
|
//
|
|
// If e is an addressable expression used in an lvalue context,
|
|
// value is the address denoted by e, and isAddr is true.
|
|
//
|
|
// The types of e (or &e, if isAddr) and the result are equal
|
|
// (modulo "untyped" bools resulting from comparisons).
|
|
//
|
|
// (Tip: to find the ir.Value given a source position, use
|
|
// astutil.PathEnclosingInterval to locate the ast.Node, then
|
|
// EnclosingFunction to locate the Function, then ValueForExpr to find
|
|
// the ir.Value.)
|
|
//
|
|
func (f *Function) ValueForExpr(e ast.Expr) (value Value, isAddr bool) {
|
|
if f.debugInfo() { // (opt)
|
|
e = unparen(e)
|
|
for _, b := range f.Blocks {
|
|
for _, instr := range b.Instrs {
|
|
if ref, ok := instr.(*DebugRef); ok {
|
|
if ref.Expr == e {
|
|
return ref.X, ref.IsAddr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// --- Lookup functions for source-level named entities (types.Objects) ---
|
|
|
|
// Package returns the IR Package corresponding to the specified
|
|
// type-checker package object.
|
|
// It returns nil if no such IR package has been created.
|
|
//
|
|
func (prog *Program) Package(obj *types.Package) *Package {
|
|
return prog.packages[obj]
|
|
}
|
|
|
|
// packageLevelValue returns the package-level value corresponding to
|
|
// the specified named object, which may be a package-level const
|
|
// (*Const), var (*Global) or func (*Function) of some package in
|
|
// prog. It returns nil if the object is not found.
|
|
//
|
|
func (prog *Program) packageLevelValue(obj types.Object) Value {
|
|
if pkg, ok := prog.packages[obj.Pkg()]; ok {
|
|
return pkg.values[obj]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FuncValue returns the concrete Function denoted by the source-level
|
|
// named function obj, or nil if obj denotes an interface method.
|
|
//
|
|
// TODO(adonovan): check the invariant that obj.Type() matches the
|
|
// result's Signature, both in the params/results and in the receiver.
|
|
//
|
|
func (prog *Program) FuncValue(obj *types.Func) *Function {
|
|
fn, _ := prog.packageLevelValue(obj).(*Function)
|
|
return fn
|
|
}
|
|
|
|
// ConstValue returns the IR Value denoted by the source-level named
|
|
// constant obj.
|
|
//
|
|
func (prog *Program) ConstValue(obj *types.Const) *Const {
|
|
// TODO(adonovan): opt: share (don't reallocate)
|
|
// Consts for const objects and constant ast.Exprs.
|
|
|
|
// Universal constant? {true,false,nil}
|
|
if obj.Parent() == types.Universe {
|
|
return NewConst(obj.Val(), obj.Type())
|
|
}
|
|
// Package-level named constant?
|
|
if v := prog.packageLevelValue(obj); v != nil {
|
|
return v.(*Const)
|
|
}
|
|
return NewConst(obj.Val(), obj.Type())
|
|
}
|
|
|
|
// VarValue returns the IR Value that corresponds to a specific
|
|
// identifier denoting the source-level named variable obj.
|
|
//
|
|
// VarValue returns nil if a local variable was not found, perhaps
|
|
// because its package was not built, the debug information was not
|
|
// requested during IR construction, or the value was optimized away.
|
|
//
|
|
// ref is the path to an ast.Ident (e.g. from PathEnclosingInterval),
|
|
// and that ident must resolve to obj.
|
|
//
|
|
// pkg is the package enclosing the reference. (A reference to a var
|
|
// always occurs within a function, so we need to know where to find it.)
|
|
//
|
|
// If the identifier is a field selector and its base expression is
|
|
// non-addressable, then VarValue returns the value of that field.
|
|
// For example:
|
|
// func f() struct {x int}
|
|
// f().x // VarValue(x) returns a *Field instruction of type int
|
|
//
|
|
// All other identifiers denote addressable locations (variables).
|
|
// For them, VarValue may return either the variable's address or its
|
|
// value, even when the expression is evaluated only for its value; the
|
|
// situation is reported by isAddr, the second component of the result.
|
|
//
|
|
// If !isAddr, the returned value is the one associated with the
|
|
// specific identifier. For example,
|
|
// var x int // VarValue(x) returns Const 0 here
|
|
// x = 1 // VarValue(x) returns Const 1 here
|
|
//
|
|
// It is not specified whether the value or the address is returned in
|
|
// any particular case, as it may depend upon optimizations performed
|
|
// during IR code generation, such as registerization, constant
|
|
// folding, avoidance of materialization of subexpressions, etc.
|
|
//
|
|
func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) {
|
|
// All references to a var are local to some function, possibly init.
|
|
fn := EnclosingFunction(pkg, ref)
|
|
if fn == nil {
|
|
return // e.g. def of struct field; IR not built?
|
|
}
|
|
|
|
id := ref[0].(*ast.Ident)
|
|
|
|
// Defining ident of a parameter?
|
|
if id.Pos() == obj.Pos() {
|
|
for _, param := range fn.Params {
|
|
if param.Object() == obj {
|
|
return param, false
|
|
}
|
|
}
|
|
}
|
|
|
|
// Other ident?
|
|
for _, b := range fn.Blocks {
|
|
for _, instr := range b.Instrs {
|
|
if dr, ok := instr.(*DebugRef); ok {
|
|
if dr.Pos() == id.Pos() {
|
|
return dr.X, dr.IsAddr
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Defining ident of package-level var?
|
|
if v := prog.packageLevelValue(obj); v != nil {
|
|
return v.(*Global), true
|
|
}
|
|
|
|
return // e.g. debug info not requested, or var optimized away
|
|
}
|