1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:54:43 -07:00

go.tools/ssa: extend debug information to arbitrary ast.Exprs.

CanonicalPos was inadequate since many pairs of instruction share the same pos (e.g. Allocs and Phis).  Instead, we generalize the DebugRef instruction to associate not just Idents but Exprs with ssa.Values.

We no longer store any DebugRefs for constant expressions, to save space.  (The type and value of such expressions can be obtained by other means, at a cost in complexity.)

Function.ValueForExpr queries the DebugRef info to return the ssa.Value of a given Expr.

Added tests.

Also:
- the DebugInfo flag is now per package, not global.
   It must be set between Create and Build phases if desired.
- {Value,Instruction}.Pos() documentation updated: we still maintain
  this information in the instruction stream even in non-debug mode,
  but we make fewer claims about its invariants.
- Go and Defer instructions can now use their respective go/defer
   token positions (not the call's lparen), so they do.
- SelectState:
     Posn token.Pos indicates the <- position
     DebugNode ast.Expr is the send stmt or receive expr.
- In building SelectStmt, we introduce extra temporaries in debug
   mode to hold the result of the receive in 'case <-ch' even though
   this value isn't ordinarily needed.
- Use *SelectState (indirectly) since the struct is getting bigger.
- Document some missing instructions in doc.go.

R=gri
CC=golang-dev
https://golang.org/cl/12147043
This commit is contained in:
Alan Donovan 2013-07-31 13:13:05 -04:00
parent e5cfd92deb
commit c28bf6e069
14 changed files with 271 additions and 143 deletions

View File

@ -38,7 +38,7 @@ func main() {
f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors)
if err != nil {
t.Errorf("parse error: %s", err)
t.Error(err)
return
}

View File

@ -23,7 +23,6 @@ const (
SanityCheckFunctions // Perform sanity checking of function bodies
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
BuildSerially // Build packages serially, not in parallel.
DebugInfo // Include DebugRef instructions [TODO(adonovan): finer grain?]
)
// NewProgram returns a new SSA Program initially containing no

View File

@ -59,6 +59,7 @@
// *ChangeType ✔ ✔
// *Const ✔
// *Convert ✔ ✔
// *DebugRef ✔
// *Defer ✔
// *Extract ✔ ✔
// *Field ✔ ✔
@ -86,7 +87,9 @@
// *Ret ✔
// *RunDefers ✔
// *Select ✔ ✔
// *Send ✔
// *Slice ✔ ✔
// *Store ✔
// *Type ✔ (type)
// *TypeAssert ✔ ✔
// *UnOp ✔ ✔
@ -110,16 +113,14 @@
// TODO(adonovan): Consider the exceptional control-flow implications
// of defer and recover().
//
// TODO(adonovan): Consider how token.Pos source location information
// should be made available generally. Currently it is only present
// in package Members and selected Instructions for which there is a
// direct source correspondence. We'll need to work harder to tie all
// defs/uses of named variables together, esp. because SSA splits them
// into separate webs.
//
// TODO(adonovan): write an example showing how to visit all functions
// in a Program, including package init functions, methods of named
// and anon types, and functions used as values but never called
// directly.
// directly. See AllFunctions().
//
// TODO(adonovan): write a how-to document for all the various cases
// of trying to determine corresponding elements across the four
// domains of source locations, ast.Nodes, types.Objects,
// ssa.Values/Instructions.
//
package ssa

View File

@ -31,22 +31,28 @@ func emitLoad(f *Function, addr Value) *UnOp {
}
// emitDebugRef emits to f a DebugRef pseudo-instruction associating
// reference id with local var/const value v.
// expression e with value v.
//
func emitDebugRef(f *Function, id *ast.Ident, v Value) {
func emitDebugRef(f *Function, e ast.Expr, v Value) {
if !f.debugInfo() {
return // debugging not enabled
}
if isBlankIdent(id) {
return
if v == nil || e == nil {
panic("nil")
}
obj := f.Pkg.objectOf(id)
if obj.Parent() == types.Universe {
return // skip nil/true/false
var obj types.Object
if id, ok := e.(*ast.Ident); ok {
if isBlankIdent(id) {
return
}
obj = f.Pkg.objectOf(id)
if _, ok := obj.(*types.Const); ok {
return
}
}
f.emit(&DebugRef{
X: v,
pos: id.Pos(),
Expr: unparen(e),
object: obj,
})
}

View File

@ -42,14 +42,14 @@ func main() {
// Parse the input file.
file, err := parser.ParseFile(imp.Fset, "hello.go", hello, parser.DeclarationErrors)
if err != nil {
fmt.Printf(err.Error()) // parse error
fmt.Print(err.Error()) // parse error
return
}
// Create a "main" package containing one file.
info := imp.CreateSourcePackage("main", []*ast.File{file})
if info.Err != nil {
fmt.Printf(info.Err.Error()) // type error
fmt.Print(info.Err.Error()) // type error
return
}

View File

@ -374,10 +374,18 @@ func (f *Function) removeNilBlocks() {
f.Blocks = f.Blocks[:j]
}
// SetDebugMode sets the debug mode for package pkg. If true, all its
// functions will include full debug info. This greatly increases
// the size of the instruction stream.
//
func (pkg *Package) SetDebugMode(debug bool) {
// TODO(adonovan): do we want ast.File granularity?
pkg.debug = debug
}
// debugInfo reports whether debug info is wanted for this function.
func (f *Function) debugInfo() bool {
// TODO(adonovan): make the policy finer grained.
return f.Prog.mode&DebugInfo != 0
return f.Pkg.debug
}
// addNamedLocal creates a local variable, adds it to function f and

View File

@ -25,7 +25,7 @@ type lvalue interface {
type address struct {
addr Value
starPos token.Pos // source position, if from explicit *addr
id *ast.Ident // source syntax, if from *ast.Ident
expr ast.Expr // source syntax [debug mode]
object types.Object // source var, if from *ast.Ident
}
@ -38,16 +38,16 @@ func (a *address) load(fn *Function) Value {
func (a *address) store(fn *Function, v Value) {
store := emitStore(fn, a.addr, v)
store.pos = a.starPos
if a.id != nil {
if a.expr != nil {
// store.Val is v converted for assignability.
emitDebugRef(fn, a.id, store.Val)
emitDebugRef(fn, a.expr, store.Val)
}
}
func (a *address) address(fn *Function) Value {
if a.id != nil {
if a.expr != nil {
// NB: this kind of DebugRef yields the object's address.
emitDebugRef(fn, a.id, a.addr)
emitDebugRef(fn, a.expr, a.addr)
}
return a.addr
}

View File

@ -8,6 +8,7 @@ import (
"fmt"
"go/ast"
"io"
"reflect"
"sort"
"code.google.com/p/go.tools/go/types"
@ -348,8 +349,14 @@ func (s *MapUpdate) String() string {
}
func (s *DebugRef) String() string {
p := s.Parent().Prog.Fset.Position(s.pos)
return fmt.Sprintf("; %s is %s @ %d:%d", s.X.Name(), s.object, p.Line, p.Column)
p := s.Parent().Prog.Fset.Position(s.Pos())
var descr interface{}
if s.object != nil {
descr = s.object // e.g. "var x int"
} else {
descr = reflect.TypeOf(s.Expr) // e.g. "*ast.CallExpr"
}
return fmt.Sprintf("; %s is %s @ %d:%d", s.X.Name(), descr, p.Line, p.Column)
}
func (p *Package) String() string {

View File

@ -3,6 +3,9 @@ package ssa
// 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"
@ -114,78 +117,39 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
return nil
}
// CanonicalPos returns the canonical position of the AST node n,
// ValueForExpr returns the SSA Value that corresponds to non-constant
// expression e.
//
// For each Node kind that may generate an SSA Value or Instruction,
// exactly one token within it is designated as "canonical". The
// position of that token is returned by {Value,Instruction}.Pos().
// The specifications of those methods determine the implementation of
// this function.
// 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
// importer.PackageInfo.ValueOf(e) instead.)
// - the value was optimised away.
//
// TODO(adonovan): test coverage.
// The types of e and the result are equal (modulo "untyped" bools
// resulting from comparisons) and they have equal "pointerness".
//
func CanonicalPos(n ast.Node) token.Pos {
// Comments show the Value/Instruction kinds v that may be
// created by n such that CanonicalPos(n) == v.Pos().
switch n := n.(type) {
case *ast.ParenExpr:
return CanonicalPos(n.X)
case *ast.CallExpr:
// f(x): *Call, *Go, *Defer.
// T(x): *ChangeType, *Convert, *MakeInterface, *ChangeInterface.
// make(): *MakeMap, *MakeChan, *MakeSlice.
// new(): *Alloc.
// panic(): *Panic.
return n.Lparen
case *ast.Ident:
return n.NamePos // *Parameter, *Alloc, *Capture
case *ast.TypeAssertExpr:
return n.Lparen // *ChangeInterface or *TypeAssertExpr
case *ast.SelectorExpr:
return n.Sel.NamePos // *MakeClosure, *Field, *FieldAddr
case *ast.FuncLit:
return n.Type.Func // *Function or *MakeClosure
case *ast.CompositeLit:
return n.Lbrace // *Alloc or *Slice
case *ast.BinaryExpr:
return n.OpPos // *Phi or *BinOp
case *ast.UnaryExpr:
return n.OpPos // *Phi or *UnOp
case *ast.IndexExpr:
return n.Lbrack // *Index or *IndexAddr
case *ast.SliceExpr:
return n.Lbrack // *Slice
case *ast.SelectStmt:
return n.Select // *Select
case *ast.RangeStmt:
return n.For // *Range
case *ast.ReturnStmt:
return n.Return // *Ret
case *ast.SendStmt:
return n.Arrow // *Send
case *ast.StarExpr:
return n.Star // *Store
case *ast.KeyValueExpr:
return n.Colon // *MapUpdate
// (Tip: to find the ssa.Value given a source position, use
// importer.PathEnclosingInterval to locate the ast.Node, then
// EnclosingFunction to locate the Function, then ValueForExpr to find
// the ssa.Value.)
//
func (f *Function) ValueForExpr(e ast.Expr) Value {
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
}
}
}
}
}
return token.NoPos
return nil
}
// --- Lookup functions for source-level named entities (types.Objects) ---
@ -233,7 +197,7 @@ func (prog *Program) FuncValue(obj *types.Func) Value {
//
func (prog *Program) ConstValue(obj *types.Const) *Const {
// TODO(adonovan): opt: share (don't reallocate)
// Consts for const objects.
// Consts for const objects and constant ast.Exprs.
// Universal constant? {true,false,nil}
if obj.Parent() == types.Universe {
@ -250,10 +214,8 @@ func (prog *Program) ConstValue(obj *types.Const) *Const {
// 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 DebugInfo flag was not set
// during SSA construction, or the value was optimized away.
//
// TODO(adonovan): test on x.f where x is a field.
// because its package was not built, the debug information was not
// requested during SSA construction, or the value was optimized away.
//
// ref must be the path to an ast.Ident (e.g. from
// PathEnclosingInterval), and that ident must resolve to obj.
@ -315,5 +277,5 @@ func (prog *Program) VarValue(obj *types.Var, ref []ast.Node) Value {
}
}
return nil // e.g. DebugInfo unset, or var optimized away
return nil // e.g. debug info not requested, or var optimized away
}

View File

@ -7,7 +7,9 @@ import (
"go/ast"
"go/parser"
"go/token"
"os"
"regexp"
"strings"
"testing"
"code.google.com/p/go.tools/go/exact"
@ -20,7 +22,7 @@ func TestObjValueLookup(t *testing.T) {
imp := importer.New(new(importer.Config)) // (uses GCImporter)
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.DeclarationErrors|parser.ParseComments)
if err != nil {
t.Errorf("parse error: %s", err)
t.Error(err)
return
}
@ -46,12 +48,13 @@ func TestObjValueLookup(t *testing.T) {
return
}
prog := ssa.NewProgram(imp.Fset, ssa.DebugInfo /*|ssa.LogFunctions*/)
prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
for _, info := range imp.Packages {
prog.CreatePackage(info)
}
pkg := prog.Package(info.Pkg)
pkg.Build()
mainPkg := prog.Package(info.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
// Gather all idents and objects in file.
objs := make(map[types.Object]bool)
@ -178,3 +181,75 @@ func checkVarValue(t *testing.T, prog *ssa.Program, ref []ast.Node, obj *types.V
}
}
}
// Ensure that, in debug mode, we can determine the ssa.Value
// corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) {
imp := importer.New(new(importer.Config)) // (uses GCImporter)
f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil,
parser.DeclarationErrors|parser.ParseComments)
if err != nil {
t.Error(err)
return
}
info := imp.CreateSourcePackage("main", []*ast.File{f})
if info.Err != nil {
t.Error(info.Err.Error())
return
}
prog := ssa.NewProgram(imp.Fset, 0)
for _, info := range imp.Packages {
prog.CreatePackage(info)
}
mainPkg := prog.Package(info.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
fn := mainPkg.Func("f")
if false {
fn.DumpTo(os.Stderr) // debugging
}
// Find the actual AST node for each canonical position.
parenExprByPos := make(map[token.Pos]*ast.ParenExpr)
ast.Inspect(f, func(n ast.Node) bool {
if n != nil {
if e, ok := n.(*ast.ParenExpr); ok {
parenExprByPos[e.Pos()] = e
}
}
return true
})
// Find all annotations of form /*@kind*/.
for _, c := range f.Comments {
text := strings.TrimSpace(c.Text())
if text == "" || text[0] != '@' {
continue
}
text = text[1:]
pos := c.End() + 1
position := imp.Fset.Position(pos)
var e ast.Expr
if target := parenExprByPos[pos]; target == nil {
t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)
continue
} else {
e = target.X
}
v := fn.ValueForExpr(e) // (may be nil)
got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.")
if want := text; got != want {
t.Errorf("%s: got value %q, want %q", position, got, want)
}
if v != nil {
if !types.IsIdentical(v.Type(), info.TypeOf(e)) {
t.Errorf("%s: got type %s, want %s", position, info.TypeOf(e), v.Type())
}
}
}
}

View File

@ -41,6 +41,7 @@ type Package struct {
Members map[string]Member // all package members keyed by name
values map[types.Object]Value // package-level vars & funcs (incl. methods), keyed by object
init *Function // Func("init"); the package's (concatenated) init function
debug bool // include full debug info in this package.
// The following fields are set transiently, then cleared
// after building.
@ -125,14 +126,20 @@ type Value interface {
// Instruction.Operands contains the inverse of this relation.
Referrers() *[]Instruction
// Pos returns the location of the source construct that
// gave rise to this value, or token.NoPos if it was not
// explicit in the source.
// Pos returns the location of the AST token most closely
// associated with the operation that gave rise to this value,
// or token.NoPos if it was not explicit in the source.
//
// For each ast.Expr type, a particular field is designated as
// the canonical location for the expression, e.g. the Lparen
// for an *ast.CallExpr. This enables us to find the value
// corresponding to a given piece of source syntax.
// For each ast.Node type, a particular token is designated as
// the closest location for the expression, e.g. the Lparen
// for an *ast.CallExpr. This permits a compact but
// approximate mapping from Values to source positions for use
// in diagnostic messages, for example.
//
// (Do not use this position to determine which Value
// corresponds to an ast.Expr; use Function.ValueForExpr
// instead. NB: it requires that the function was built with
// debug information.)
//
Pos() token.Pos
}
@ -191,15 +198,22 @@ type Instruction interface {
// Values.)
Operands(rands []*Value) []*Value
// Pos returns the location of the source construct that
// gave rise to this instruction, or token.NoPos if it was not
// explicit in the source.
// Pos returns the location of the AST token most closely
// associated with the operation that gave rise to this
// instruction, or token.NoPos if it was not explicit in the
// source.
//
// For each ast.Expr type, a particular field is designated as
// the canonical location for the expression, e.g. the Lparen
// for an *ast.CallExpr. This enables us to find the
// instruction corresponding to a given piece of source
// syntax.
// For each ast.Node type, a particular token is designated as
// the closest location for the expression, e.g. the Go token
// for an *ast.GoStmt. This permits a compact but approximate
// mapping from Instructions to source positions for use in
// diagnostic messages, for example.
//
// (Do not use this position to determine which Instruction
// corresponds to an ast.Expr; see the notes for Value.Pos.
// This position may be used to determine which non-Value
// Instruction corresponds to some ast.Stmts, but not all: If
// and Jump instructions have no Pos(), for example.)
//
Pos() token.Pos
}
@ -791,10 +805,11 @@ type Lookup struct {
// It represents one goal state and its corresponding communication.
//
type SelectState struct {
Dir ast.ChanDir // direction of case
Chan Value // channel to use (for send or receive)
Send Value // value to send (for send)
Pos token.Pos // position of token.ARROW
Dir ast.ChanDir // direction of case
Chan Value // channel to use (for send or receive)
Send Value // value to send (for send)
Pos token.Pos // position of token.ARROW
DebugNode ast.Node // ast.SendStmt or ast.UnaryExpr(<-) [debug mode]
}
// The Select instruction tests whether (or blocks until) one or more
@ -834,7 +849,7 @@ type SelectState struct {
//
type Select struct {
Register
States []SelectState
States []*SelectState
Blocking bool
}
@ -906,8 +921,9 @@ type Next struct {
//
// Pos() returns the ast.CallExpr.Lparen if the instruction arose from
// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the
// instruction arose from an explicit e.(T) operation; or token.NoPos
// otherwise.
// instruction arose from an explicit e.(T) operation; or the
// ast.CaseClause.Case if the instruction arose from a case of a
// type-switch statement.
//
// Example printed form:
// t1 = typeassert t0.(int)
@ -1037,6 +1053,8 @@ type Panic struct {
//
// See CallCommon for generic function call documentation.
//
// Pos() returns the ast.GoStmt.Go.
//
// Example printed form:
// go println(t0, t1)
// go t3()
@ -1045,6 +1063,7 @@ type Panic struct {
type Go struct {
anInstruction
Call CallCommon
pos token.Pos
}
// The Defer instruction pushes the specified call onto a stack of
@ -1052,6 +1071,8 @@ type Go struct {
//
// See CallCommon for generic function call documentation.
//
// Pos() returns the ast.DeferStmt.Defer.
//
// Example printed form:
// defer println(t0, t1)
// defer t3()
@ -1060,6 +1081,7 @@ type Go struct {
type Defer struct {
anInstruction
Call CallCommon
pos token.Pos
}
// The Send instruction sends X on channel Chan.
@ -1107,15 +1129,15 @@ type MapUpdate struct {
}
// A DebugRef instruction provides the position information for a
// specific source-level reference that denotes the SSA value X.
// specific source-level expression that compiles to the SSA value X.
//
// DebugRef is a pseudo-instruction: it has no dynamic effect.
//
// Pos() returns the ast.Ident or ast.Selector.Sel of the source-level
// reference.
// Pos() returns Expr.Pos(), the position of the source-level
// expression.
//
// Object() returns the source-level (var/const) object denoted by
// that reference.
// Object() returns the source-level (var/const/func) object denoted
// by Expr if it is an *ast.Ident; otherwise it is nil.
//
// (By representing these as instructions, rather than out-of-band,
// consistency is maintained during transformation passes by the
@ -1124,8 +1146,8 @@ type MapUpdate struct {
type DebugRef struct {
anInstruction
X Value // the value whose position we're declaring
pos token.Pos // location of the reference
object types.Object // the identity of the source var/const
Expr ast.Expr // the referring expression
object types.Object // the identity of the source var/const/func
}
// Embeddable mix-ins and helpers for common parts of other structs. -----------
@ -1385,8 +1407,8 @@ func (p *Package) Type(name string) (t *Type) {
}
func (v *Call) Pos() token.Pos { return v.Call.pos }
func (s *Defer) Pos() token.Pos { return s.Call.pos }
func (s *Go) Pos() token.Pos { return s.Call.pos }
func (s *Defer) Pos() token.Pos { return s.pos }
func (s *Go) Pos() token.Pos { return s.pos }
func (s *MapUpdate) Pos() token.Pos { return s.pos }
func (s *Panic) Pos() token.Pos { return s.pos }
func (s *Ret) Pos() token.Pos { return s.pos }
@ -1395,7 +1417,7 @@ func (s *Store) Pos() token.Pos { return s.pos }
func (s *If) Pos() token.Pos { return token.NoPos }
func (s *Jump) Pos() token.Pos { return token.NoPos }
func (s *RunDefers) Pos() token.Pos { return token.NoPos }
func (s *DebugRef) Pos() token.Pos { return s.pos }
func (s *DebugRef) Pos() token.Pos { return s.Expr.Pos() }
// Operands.

View File

@ -54,11 +54,12 @@ func main() {
impctx := importer.Config{Loader: importer.MakeGoBuildLoader(nil)}
var debugMode bool
var mode ssa.BuilderMode
for _, c := range *buildFlag {
switch c {
case 'D':
mode |= ssa.DebugInfo
debugMode = true
case 'P':
mode |= ssa.LogPackages | ssa.BuildSerially
case 'F':
@ -115,7 +116,7 @@ func main() {
// Create and build SSA-form program representation.
prog := ssa.NewProgram(imp.Fset, mode)
for _, info := range imp.Packages {
prog.CreatePackage(info)
prog.CreatePackage(info).SetDebugMode(debugMode)
}
prog.BuildAll()

View File

@ -20,6 +20,8 @@ import (
"code.google.com/p/go.tools/ssa"
)
const debugMode = false
func allPackages() []string {
var pkgs []string
root := filepath.Join(runtime.GOROOT(), "src/pkg") + "/"
@ -70,10 +72,10 @@ func TestStdlib(t *testing.T) {
alloc := memstats.Alloc
// Create SSA packages.
prog := ssa.NewProgram(imp.Fset, ssa.DebugInfo|ssa.SanityCheckFunctions)
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
for _, info := range imp.Packages {
if info.Err == nil {
prog.CreatePackage(info)
prog.CreatePackage(info).SetDebugMode(debugMode)
}
}

45
ssa/testdata/valueforexpr.go vendored Normal file
View File

@ -0,0 +1,45 @@
//+build ignore
package main
// This file is the input to TestCanonicalPos in source_test.go, which
// ensures that each expression e immediately following a /*@kind*/(x)
// annotation, when passed to Function.ValueForExpr(e), returns a
// non-nil Value of the same type as e and of kind 'kind'.
func f(param int) {
_ = /*@<nil>*/ (1 + 2) // (constant)
i := 0
/*@Call*/ (print( /*@BinOp*/ (i + 1)))
ch := /*@MakeChan*/ (make(chan int))
/*@UnOp*/ (<-ch)
x := /*@UnOp*/ (<-ch)
select {
case /*@Extract*/ (<-ch):
case x := /*@Extract*/ (<-ch):
}
defer /*@Function*/ (func() {
})()
go /*@Function*/ (func() {
})()
y := 0
if true && /*@BinOp*/ (bool(y > 0)) {
y = 1
}
_ = /*@Phi*/ (y)
map1 := /*@MakeMap*/ (make(map[string]string))
_ = /*@MakeMap*/ (map[string]string{"": ""})
_ = /*@MakeSlice*/ (make([]int, 0))
_ = /*@MakeClosure*/ (func() { print(param) })
sl := /*@Slice*/ ([]int{})
_ = /*@Alloc*/ (&struct{}{})
_ = /*@Slice*/ (sl[:0])
_ = /*@Alloc*/ (new(int))
var iface interface{}
_ = /*@TypeAssert*/ (iface.(int))
_ = /*@UnOp*/ (sl[0])
_ = /*@IndexAddr*/ (&sl[0])
_ = /*@Index*/ ([2]int{}[0])
var p *int
_ = /*@UnOp*/ (*p)
}