8000 go-tools/go/ir/builder.go at master · dominikh/go-tools · GitHub
[go: up one dir, main page]

Skip to content
8000

Latest commit

 

History

History
3634 lines (3293 loc) · 102 KB

builder.go

File metadata and controls

3634 lines (3293 loc) · 102 KB
// C 8BD0 opyright 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 the builder, which builds SSA-form IR for function bodies.
//
// SSA construction has two phases, "create" and "build". First, one
// or more packages are created in any order by a sequence of calls to
// CreatePackage, either from syntax or from mere type information.
// Each created package has a complete set of Members (const, var,
// type, func) that can be accessed through methods like
// Program.FuncValue.
//
// It is not necessary to call CreatePackage for all dependencies of
// each syntax package, only for its direct imports. (In future
// perhaps even this restriction may be lifted.)
//
// Second, packages created from syntax are built, by one or more
// calls to Package.Build, which may be concurrent; or by a call to
// Program.Build, which builds all packages in parallel. Building
// traverses the type-annotated syntax tree of each function body and
// creates SSA-form IR, a control-flow graph of instructions,
// populating fields such as Function.Body, .Params, and others.
//
// Building may create additional methods, including:
// - wrapper methods (e.g. for embedding, or implicit &recv)
// - bound method closures (e.g. for use(recv.f))
// - thunks (e.g. for use(I.f) or use(T.f))
// - generic instances (e.g. to produce f[int] from f[any]).
// As these methods are created, they are added to the build queue,
// and then processed in turn, until a fixed point is reached,
// Since these methods might belong to packages that were not
// created (by a call to CreatePackage), their Pkg field is unset.
//
// Instances of generic functions may be either instantiated (f[int]
// is a copy of f[T] with substitutions) or wrapped (f[int] delegates
// to f[T]), depending on the availability of generic syntax and the
// InstantiateGenerics mode flag.
//
// Each package has an initializer function named "init" that calls
// the initializer functions of each direct import, computes and
// assigns the initial value of each global variable, and calls each
// source-level function named "init". (These generate SSA functions
// named "init#1", "init#2", etc.)
//
// Runtime types
//
// Each MakeInterface operation is a conversion from a non-interface
// type to an interface type. The semantics of this operation requires
// a runtime type descriptor, which is the type portion of an
// interface, and the value abstracted by reflect.Type.
//
// The program accumulates all non-parameterized types that are
// encountered as MakeInterface operands, along with all types that
// may be derived from them using reflection. This set is available as
// Program.RuntimeTypes, and the methods of these types may be
// reachable via interface calls or reflection even if they are never
// referenced from the SSA IR. (In practice, algorithms such as RTA
// that compute reachability from package main perform their own
// tracking of runtime types at a finer grain, so this feature is not
// very useful.)
//
// Function literals
//
// Anonymous functions must be built as soon as they are encountered,
// as it may affect locals of the enclosing function, but they are not
// marked 'built' until the end of the outermost enclosing function.
// (Among other things, this causes them to be logged in top-down order.)
//
// The Function.build fields determines the algorithm for building the
// function body. It is cleared to mark that building is complete.
import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"
"os"
"runtime"
"slices"
"sync"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/go/types/typeutil"
"honnef.co/go/tools/internal/xtools-internal/versions"
"golang.org/x/exp/typeparams"
)
var (
varOk = newVar("ok", tBool)
varIndex = newVar("index", tInt)
// Type constants.
tBool = types.Typ[types.Bool]
tByte = types.Typ[types.Byte]
tRune = types.Universe.Lookup("rune").Type() // prints as "rune" (Typ[Rune] is same as Int32)
tInt = types.Typ[types.Int]
tInvalid = types.Typ[types.Invalid]
tString = types.Typ[types.String]
tUntypedNil = types.Typ[types.UntypedNil]
tEface = types.NewInterfaceType(nil, nil).Complete()
tDeferStack = types.NewPointer(typeutil.NewDeferStack())
vOne = intConst(1, nil)
vTrue = NewConst(constant.MakeBool(true), tBool, nil)
vNoReturn = NewConst(constant.MakeString("noreturn"), tString, nil)
jReady = intConst(0, nil) // range-over-func jump is READY
jBusy = intConst(-1, nil) // range-over-func jump is BUSY
jDone = intConst(-2, nil) // range-over-func jump is DONE
jDroppedPanic = stringConst("iterator call did not preserve panic", nil)
jLateYield = stringConst("yield function called after range loop exit", nil)
vDeferStack = &Builtin{
name: "ssa:deferstack",
sig: types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(anonVar(tDeferStack)), false),
}
)
// builder holds state associated with the package currently being built.
// Its methods contain all the logic for AST-to-IR conversion.
//
// All Functions belong to the same Program.
//
// builders are not thread-safe.
type builder struct {
fns []*Function // Functions that have finished their CREATE phases.
finished int // finished is the length of the prefix of fns containing built functions.
// The task of building shared functions within the builder.
// Shared functions are ones the builder may either create or lookup.
// These may be built by other builders in parallel.
// The task is done when the builder has finished iterating, and it
// waits for all shared functions to finish building.
// nil implies there are no hared functions to wait on.
buildshared *task
}
// shared is done when the builder has built all of the
// enqueued functions to a fixed-point.
func (b *builder) shared() *task {
if b.buildshared == nil { // lazily-initialize
b.buildshared = &task{done: make(chan unit)}
}
return b.buildshared
}
// enqueue fn to be built by the builder.
func (b *builder) enqueue(fn *Function) {
b.fns = append(b.fns, fn)
}
// waitForSharedFunction indicates that the builder should wait until
// the potentially shared function fn has finished building.
//
// This should include any functions that may be built by other
// builders.
func (b *builder) waitForSharedFunction(fn *Function) {
// Check whether fn is already built before calling b.shared(),
// which allocates a task and a channel that b.iterate() would
// then have to close and wait on. The common case, a lookup of
// a method that was created and built long ago, needs neither.
if t := fn.buildshared; t != nil && !t.isTransitivelyDone() { // maybe need to wait?
b.shared().addEdge(t)
}
}
// cond emits to fn code to evaluate boolean condition e and jump
// to t or f depending on its value, performing various simplifications.
//
// Postcondition: fn.currentBlock is nil.
func (b *builder) cond(fn *Function, e ast.Expr, t, f *BasicBlock) *If {
switch e := e.(type) {
case *ast.ParenExpr:
return b.cond(fn, e.X, t, f)
case *ast.BinaryExpr:
switch e.Op {
case token.LAND:
ltrue := fn.newBasicBlock("cond.true")
b.cond(fn, e.X, ltrue, f)
fn.currentBlock = ltrue
return b.cond(fn, e.Y, t, f)
case token.LOR:
lfalse := fn.newBasicBlock("cond.false")
b.cond(fn, e.X, t, lfalse)
fn.currentBlock = lfalse
return b.cond(fn, e.Y, t, f)
}
case *ast.UnaryExpr:
if e.Op == token.NOT {
return b.cond(fn, e.X, f, t)
}
}
// A traditional compiler would simplify "if false" (etc) here
// but we do not, for better fidelity to the source code.
//
// The value of a constant condition may be platform-specific,
// and may cause blocks that are reachable in some configuration
// to be hidden from subsequent analyses such as bug-finding tools.
return emitIf(fn, b.expr(fn, e), t, f, e)
}
// logicalBinop emits code to fn to evaluate e, a &&- or
// ||-expression whose reified boolean value is wanted.
// The value is returned.
func (b *builder) logicalBinop(fn *Function, e *ast.BinaryExpr) Value {
rhs := fn.newBasicBlock("binop.rhs")
done := fn.newBasicBlock("binop.done")
// T(e) = T(e.X) = T(e.Y) after untyped constants have been
// eliminated.
// TODO(adonovan): not true; MyBool==MyBool yields UntypedBool.
t := fn.typeOf(e)
var short Value // value of the short-circuit path
switch e.Op {
case token.LAND:
b.cond(fn, e.X, rhs, done)
short = NewConst(constant.MakeBool(false), t, e)
case token.LOR:
b.cond(fn, e.X, done, rhs)
short = NewConst(constant.MakeBool(true), t, e)
}
// Is rhs unreachable?
if rhs.Preds == nil {
// Simplify false&&y to false, true||y to true.
fn.currentBlock = done
return short
}
// Is done unreachable?
if done.Preds == nil {
// Simplify true&&y (or false||y) to y.
fn.currentBlock = rhs
return b.expr(fn, e.Y)
}
// All edges from e.X to done carry the short-circuit value.
var edges []Value
for range done.Preds {
edges = append(edges, short)
}
// The edge from e.Y to done carries the value of e.Y.
fn.currentBlock = rhs
edges = append(edges, b.expr(fn, e.Y))
emitJump(fn, done, e)
fn.currentBlock = done
phi := &Phi{Edges: edges}
phi.typ = t
phi.comment = e.Op.String()
return done.emit(phi, e)
}
// exprN lowers a multi-result expression e to IR form, emitting code
// to fn and returning a single Value whose type is a *types.Tuple.
// The caller must access the components via Extract.
//
// Multi-result expressions include CallExprs in a multi-value
// assignment or return statement, and "value,ok" uses of
// TypeAssertExpr, IndexExpr (when X is a map), and Recv.
func (b *builder) exprN(fn *Function, e ast.Expr) Value {
typ := fn.typeOf(e).(*types.Tuple)
switch e := e.(type) {
case *ast.ParenExpr:
return b.exprN(fn, e.X)
case *ast.CallExpr:
// Currently, no built-in function nor type conversion
// has multiple results, so we can avoid some of the
// cases for single-valued CallExpr.
var c Call
b.setCall(fn, e, &c.Call)
c.typ = typ
return emitCall(fn, &c, e)
case *ast.IndexExpr:
mapt := typeutil.CoreType(fn.typeOf(e.X)).(*types.Map) // ,ok must be a map.
lookup := &MapLookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key(), e),
CommaOk: true,
}
lookup.setType(typ)
return fn.emit(lookup, e)
case *ast.TypeAssertExpr:
return emitTypeTest(fn, b.expr(fn, e.X), typ.At(0).Type(), e)
case *ast.UnaryExpr: // must be receive <-
return emitRecv(fn, b.expr(fn, e.X), true, typ, e)
}
panic(fmt.Sprintf("exprN(%T) in %s", e, fn))
}
// builtin emits to fn IR instructions to implement a call to the
// built-in function obj with the specified arguments
// and return type. It returns the value defined by the result.
//
// The result is nil if no special handling was required; in this case
// the caller should treat this like an ordinary library function
// call.
func (b *builder) builtin(fn *Function, obj *types.Builtin, args []ast.Expr, typ types.Type, source ast.Node) Value {
typ = fn.typ(typ)
switch obj.Name() {
case "make":
switch ct := typeutil.CoreType(typ).(type) {
case *types.Slice:
n := b.expr(fn, args[1])
m := n
if len(args) == 3 {
m = b.expr(fn, args[2])
}
if m, ok := m.(*Const); ok {
// treat make([]T, n, m) as new([m]T)[:n]
cap := m.Int64()
at := types.NewArray(ct.Elem(), cap)
v := &Slice{
X: emitNew(fn, at, source, "makeslice"),
High: n,
}
v.setType(typ)
return fn.emit(v, source)
}
v := &MakeSlice{
Len: n,
Cap: m,
}
v.setType(typ)
return fn.emit(v, source)
case *types.Map:
var res Value
if len(args) == 2 {
res = b.expr(fn, args[1])
}
v := &MakeMap{Reserve: res}
v.setType(typ)
return fn.emit(v, source)
case *types.Chan:
var sz Value = intConst(0, source)
if len(args) == 2 {
sz = b.expr(fn, args[1])
}
v := &MakeChan{Size: sz}
v.setType(typ)
return fn.emit(v, source)
default:
lint.ExhaustiveTypeSwitch(typ.Underlying())
}
case "new":
alloc := emitNew(fn, deref(typ), source, "new")
if !fn.info.Types[args[0]].IsType() {
// new(expr), requires go1.26
v := b.expr(fn, args[0])
emitStore(fn, alloc, v, source)
}
return alloc
case "len", "cap":
// Special case: len or cap of an array or *array is based on the type, not the value which may be nil. We must
// still evaluate the value, though. (If it was side-effect free, the whole call would have been
// constant-folded.)
//
// For example, for len(gen()), we need to evaluate gen() for its side-effects, but don't need the returned
// value to determine the length of the array, which is constant.
//
// Technically this shouldn't apply to type parameters because their length/capacity is never constant. We still
// choose to treat them as constant so that users of the IR get the practically constant length for free.
t := typeutil.CoreType(deref(fn.typeOf(args[0])))
if at, ok := t.(*types.Array); ok {
b.expr(fn, args[0]) // for effects only
return intConst(at.Len(), args[0])
}
// Otherwise treat as normal.
case "panic":
fn.emit(&Panic{
X: emitConv(fn, b.expr(fn, args[0]), tEface, source),
}, source)
fn.currentBlock = fn.newBasicBlock("unreachable")
return vTrue // any non-nil Value will do
}
return nil // treat all others as a regular function call
}
// addr lowers a single-result addressable expression e to IR form,
// emitting code to fn and returning the location (an lvalue) defined
// by the expression.
//
// If escaping is true, addr marks the base variable of the
// addressable expression e as being a potentially escaping pointer
// value. For example, in this code:
//
// a := A{
// b: [1]B{B{c: 1}}
// }
// return &a.b[0].c
//
// the application of & causes a.b[0].c to have its address taken,
// which means that ultimately the local variable a must be
// heap-allocated. This is a simple but very conservative escape
// analysis.
//
// Operations forming potentially escaping pointers include:
// - &x, including when implicit in method call or composite literals.
// - a[:] iff a is an array (not *array)
// - references to variables in lexically enclosing functions.
func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
switch e := e.(type) {
case *ast.Ident:
if isBlankIdent(e) {
return blank{}
}
obj := fn.objectOf(e).(*types.Var)
var v Value
if g := fn.Prog.packageLevelMember(obj); g != nil {
v = g.(*Global) // var (address)
} else {
v = fn.lookup(obj, escaping)
}
return &address{addr: v, expr: e}
case *ast.CompositeLit:
t := deref(fn.typeOf(e))
var v *Alloc
if escaping {
v = emitNew(fn, t, e, "complit")
} else {
v = emitLocal(fn, t, e, "complit")
}
var sb storebuf
b.compLit(fn, v, e, true, &sb)
sb.emit(fn)
return &address{addr: v, expr: e}
case *ast.ParenExpr:
return b.addr(fn, e.X, escaping)
case *ast.SelectorExpr:
sel := fn.selection(e)
if sel == nil {
// qualified identifier
return b.addr(fn, e.Sel, escaping)
}
if sel.kind != types.FieldVal {
panic(sel)
}
wantAddr := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel, e)
index := sel.index[len(sel.index)-1]
fld := fieldOf(deref(v.Type()), index) // v is an addr.
// Due to the two phases of resolving AssignStmt, a panic from x.f = p()
// when x is nil is required to come after the side-effects of
// evaluating x and p().
emit := func(fn *Function) Value {
return emitFieldSelection(fn, v, index, true, e.Sel)
}
return &lazyAddress{addr: emit, t: fld.Type(), expr: e.Sel}
case *ast.IndexExpr:
xt := fn.typeOf(e.X)
elem, mode := indexType(xt)
var x Value
var et types.Type
switch mode {
case ixArrVar: // array, array|slice, array|*array, or array|*array|slice.
x = b.addr(fn, e.X, escaping).address(fn)
et = types.NewPointer(elem)
case ixVar: // *array, slice, *array|slice
x = b.expr(fn, e.X)
et = types.NewPointer(elem)
case ixMap:
mt := typeutil.CoreType(xt).(*types.Map)
return &element{
m: b.expr(fn, e.X),
k: emitConv(fn, b.expr(fn, e.Index), mt.Key(), e.Index),
t: mt.Elem(),
}
default:
panic("unexpected container type in IndexExpr: " + xt.String())
}
index := b.expr(fn, e.Index)
if isUntyped(index.Type()) {
index = emitConv(fn, index, tInt, e.Index)
}
// Due to the two phases of resolving AssignStmt, a panic from x[i] = p()
// when x is nil or i is out-of-bounds is required to come after the
// side-effects of evaluating x, i and p().
emit := func(fn *Function) Value {
v := &IndexAddr{
X: x,
Index: index,
}
v.setType(et)
return fn.emit(v, e)
}
return &lazyAddress{addr: emit, t: deref(et), expr: e}
case *ast.StarExpr:
return &address{addr: b.expr(fn, e.X), expr: e}
}
panic(fmt.Sprintf("unexpected address expression: %T", e))
}
type store struct {
lhs lvalue
rhs Value
source ast.Node
// if debugRef is set no other fields will be set
debugRef *debugRef
}
type storebuf struct{ stores []store }
func (sb *storebuf) store(lhs lvalue, rhs Value, source ast.Node) {
sb.stores = append(sb.stores, store{lhs, rhs, source, nil})
}
func (sb *storebuf) storeDebugRef(ref *debugRef) {
sb.stores = append(sb.stores, store{debugRef: ref})
}
func (sb *storebuf) emit(fn *Function) {
for _, s := range sb.stores {
if s.debugRef == nil {
s.lhs.store(fn, s.rhs, s.source)
} else {
fn.emit(s.debugRef, nil)
}
}
}
// assign emits to fn code to initialize the lvalue loc with the value
// of expression e. If isZero is true, assign assumes that loc holds
// the zero value for its type.
//
// This is equivalent to loc.store(fn, b.expr(fn, e)), but may generate
// better code in some cases, e.g., for composite literals in an
// addressable location.
//
// If sb is not nil, assign generates code to evaluate expression e, but
// not to update loc. Instead, the necessary stores are appended to the
// storebuf sb so that they can be executed later. This allows correct
// in-place update of existing variables when the RHS is a composite
// literal that may reference parts of the LHS.
func (b *builder) assign(fn *Function, loc lvalue, e ast.Expr, isZero bool, sb *storebuf, source ast.Node) {
// Can we initialize it in place?
if e, ok := ast.Unparen(e).(*ast.CompositeLit); ok {
// A CompositeLit never evaluates to a pointer,
// so if the type of the location is a pointer,
// an &-operation is implied.
if _, ok := loc.(blank); !ok { // avoid calling blank.typ()
if isPointerCore(loc.typ()) {
// Example input that hits this code:
//
// type S1 struct{ X int }
// x := []*S1{
// {1}, // <-- & is implied
// }
// _ = x
ptr := b.addr(fn, e, true).address(fn)
// copy address
if sb != nil {
sb.store(loc, ptr, source)
} else {
loc.store(fn, ptr, source)
}
return
}
}
if _, ok := loc.(*address); ok {
if types.IsInterface(loc.typ()) && !typeparams.IsTypeParam(loc.typ()) {
// e.g. var x interface{} = T{...}
// Can't in-place initialize an interface value.
// Fall back to copying.
} else {
// x = T{...} or x := T{...}
addr := loc.address(fn)
if sb != nil {
b.compLit(fn, addr, e, isZero, sb)
} else {
var sb storebuf
b.compLit(fn, addr, e, isZero, &sb)
sb.emit(fn)
}
// Subtle: emit debug ref for aggregate types only;
// slice and map are handled by store ops in compLit.
switch typeutil.CoreType(loc.typ()).(type) {
case *types.Struct, *types.Array:
if sb != nil {
// Make sure we don't emit DebugRefs before the store has actually occurred
if ref := makeDebugRef(fn, e, addr, true); ref != nil {
sb.storeDebugRef(ref)
}
} else {
emitDebugRef(fn, e, addr, true)
}
}
return
}
}
}
// simple case: just copy
rhs := b.expr(fn, e)
if sb != nil {
sb.store(loc, rhs, source)
} else {
loc.store(fn, rhs, source)
}
}
// expr lowers a single-result expression e to IR form, emitting code
// to fn and returning the Value defined by the expression.
func (b *builder) expr(fn *Function, e ast.Expr) Value {
e = ast.Unparen(e)
tv := fn.info.Types[e]
// Is expression a constant?
if tv.Value != nil {
return NewConst(tv.Value, fn.typ(tv.Type), e)
}
var v Value
if tv.Addressable() {
// Prefer pointer arithmetic ({Index,Field}Addr) followed
// by Load over subelement extraction (e.g. Index, Field),
// to avoid large copies.
v = b.addr(fn, e, false).load(fn, e)
} else {
v = b.expr0(fn, e, tv)
}
if fn.debugInfo() {
emitDebugRef(fn, e, v, false)
}
return v
}
func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value {
switch e := e.(type) {
case *ast.BasicLit:
panic("non-constant BasicLit") // unreachable
case *ast.FuncLit:
/* function literal */
fn2 := &Function{
name: fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
Signature: fn.typeOf(e.Type).(*types.Signature),
pos: e.Type.Func,
parent: fn,
anonIdx: int32(len(fn.AnonFuncs)),
Pkg: fn.Pkg,
Prog: fn.Prog,
syntax: e,
info: fn.info,
goversion: fn.goversion,
build: (*builder).buildFromSyntax,
topLevelOrigin: nil, // use anonIdx to lookup an anon instance's origin.
recvtypeparams: fn.recvtypeparams, // share the parent's receiver type parameters.
recvtypeargs: fn.recvtypeargs, // share the parent's receiver type arguments.
typeparams: fn.typeparams, // share the parent's type parameters.
typeargs: fn.typeargs, // share the parent's type arguments.
subst: fn.subst, // share the parent's type substitutions.
}
fn2.uniq = fn.uniq // start from parent's unique values
fn.AnonFuncs = append(fn.AnonFuncs, fn2)
// Build anon immediately, as it may cause fn's locals to escape.
// (It is not marked 'built' until the end of the enclosing FuncDecl.)
fn2.build(b, fn2)
fn.uniq = fn2.uniq // resume after anon's unique values
if fn2.FreeVars == nil {
return fn2
}
v := &MakeClosure{Fn: fn2}
v.setType(fn.typ(tv.Type))
for _, fv := range fn2.FreeVars {
v.Bindings = append(v.Bindings, fv.outer)
fv.outer = nil
}
return fn.emit(v, e)
case *ast.TypeAssertExpr: // single-result form only
return emitTypeAssert(fn, b.expr(fn, e.X), fn.typ(tv.Type), e)
case *ast.CallExpr:
if fn.info.Types[e.Fun].IsType() {
// Explicit type conversion, e.g. string(x) or big.Int(x)
x := b.expr(fn, e.Args[0])
y := emitConv(fn, x, fn.typ(tv.Type), e)
return y
}
// Call to "intrinsic" built-ins, e.g. new, make, panic.
if id, ok := ast.Unparen(e.Fun).(*ast.Ident); ok {
if obj, ok := fn.info.Uses[id].(*types.Builtin); ok {
if v := b.builtin(fn, obj, e.Args, fn.typ(tv.Type), e); v != nil {
return v
}
}
}
// Regular function call.
var v Call
b.setCall(fn, e, &v.Call)
v.setType(fn.typ(tv.Type))
return emitCall(fn, &v, e)
case *ast.UnaryExpr:
switch e.Op {
case token.AND: // &X --- potentially escaping.
addr := b.addr(fn, e.X, true)
if _, ok := ast.Unparen(e.X).(*ast.StarExpr); ok {
// &*p must panic if p is nil (https://golang.org/s/go12nil).
// For simplicity, we'll just (suboptimally) rely
// on the side effects of a load.
// TODO(adonovan): emit dedicated nilcheck.
addr.load(fn, e)
}
return addr.address(fn)
case token.ADD:
return b.expr(fn, e.X)
case token.NOT, token.SUB, token.XOR: // ! <- - ^
v := &UnOp{
Op: e.Op,
X: b.expr(fn, e.X),
}
v.setType(fn.typ(tv.Type))
return fn.emit(v, e)
case token.ARROW:
return emitRecv(fn, b.expr(fn, e.X), false, fn.typ(tv.Type), e)
default:
panic(e.Op)
}
case *ast.BinaryExpr:
switch e.Op {
case token.LAND, token.LOR:
return b.logicalBinop(fn, e)
case token.SHL, token.SHR:
fallthrough
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT:
return emitArith(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), fn.typ(tv.Type), e)
case token.EQL, token.NEQ, token.GTR, token.LSS, token.LEQ, token.GEQ:
cmp := emitCompare(fn, e.Op, b.expr(fn, e.X), b.expr(fn, e.Y), e)
// The type of x==y may be UntypedBool.
return emitConv(fn, cmp, types.Default(fn.typ(tv.Type)), e)
default:
panic("illegal op in BinaryExpr: " + e.Op.String())
}
case *ast.SliceExpr:
var low, high, max Value
var x Value
xtyp := fn.typeOf(e.X)
switch typeutil.CoreType(xtyp).(type) {
case *types.Array:
// Potentially escaping.
x = b.addr(fn, e.X, true).address(fn)
case *types.Basic, *types.Slice, *types.Pointer: // *array
x = b.expr(fn, e.X)
default:
// core type exception?
if isBytestring(xtyp) {
x = b.expr(fn, e.X) // bytestring is handled as string and []byte.
} else {
panic("unexpected sequence type in SliceExpr")
}
}
if e.Low != nil {
low = b.expr(fn, e.Low)
}
if e.High != nil {
high = b.expr(fn, e.High)
}
if e.Slice3 {
max = b.expr(fn, e.Max)
}
v := &Slice{
X: x,
Low: low,
High: high,
Max: max,
}
v.setType(fn.typ(tv.Type))
return fn.emit(v, e)
case *ast.Ident:
obj := fn.info.Uses[e]
// Universal built-in or nil?
switch obj := obj.(type) {
case *types.Builtin:
return &Builtin{name: obj.Name(), sig: fn.instanceType(e).(*types.Signature)}
case *types.Nil:
return zeroConst(fn.instanceType(e), e)
}
// Package-level func or var?
// (obj must belong to same package or a direct import.)
if v := fn.Prog.packageLevelMember(obj); v != nil {
if g, ok := v.(*Global); ok {
return emitLoad(fn, g, e) // var (address)
}
callee := v.(*Function) // (func)
if callee.typeparams.Len() > 0 {
targs := fn.subtargs(e)
callee = callee.instance(nil, targs, b)
}
return callee
}
// Local var.
return emitLoad(fn, fn.lookup(obj.(*types.Var), false), e) // var (address)
case *ast.SelectorExpr:
sel := fn.selection(e)
if sel == nil {
// builtin unsafe.{Add,Slice}
if obj, ok := fn.info.Uses[e.Sel].(*types.Builtin); ok {
return &Builtin{name: "Unsafe" + obj.Name(), sig: fn.typ(tv.Type).(*types.Signature)}
}
// qualified identifier
return b.expr(fn, e.Sel)
}
switch sel.kind {
case types.MethodExpr:
// (*T).f or T.f, the method f from the method-set of type T.
// The result is a "thunk".
targs := fn.subtargs(e.Sel)
thunk := createThunk(fn.Prog, sel, targs)
b.enqueue(thunk)
return thunk
case types.MethodVal:
// e.f where e is an expression and f is a method.
// The result is a "bound".
m := sel.obj.(*types.Func)
rt := fn.typ(recvType(m))
wantAddr := isPointer(rt)
escaping := true
v := b.receiver(fn, e.X, wantAddr, escaping, sel, e)
if types.IsInterface(rt) {
// If v may be an interface type I (after instantiating),
// we must emit a check that v is non-nil.
if recv, ok := types.Unalias(sel.recv).(*types.TypeParam); ok {
// Emit a nil check if any possible instantiation of the
// type parameter is an interface type.
if !typeSetIsEmpty(recv) {
// recv has a concrete term its typeset.
// So it cannot be instantiated as an interface.
//
// Example:
// func _[T interface{~int; Foo()}] () {
// var v T
// _ = v.Foo // <-- MethodVal
// }
} else {
// rt may be instantiated as an interface.
// Emit nil check: typeassert (any(v)).(any).
emitTypeAssert(fn, emitConv(fn, v, tEface, nil), tEface, nil)
}
} else {
// non-type param interface
// Emit nil check: typeassert v.(I).
emitTypeAssert(fn, v, rt, e.Sel)
}
}
if rtargs := fn.subrtargs(m); len(rtargs) > 0 {
m = fn.Prog.canon.instantiateMethod(m, rtargs, fn.Prog.ctxt)
}
targs := fn.subtargs(e.Sel)
bound := createBound(fn.Prog, m, targs)
b.enqueue(bound)
// The assignment may widen a type parameter to its
// interface bound (case #3 of go.dev/issue.78110).
v = emitConv(fn, v, bound.FreeVars[0].Type(), nil)
c := &MakeClosure{
Fn: bound,
Bindings: []Value{v},
}
c.source = e.Sel
c.setType(bound.Signature)
return fn.emit(c, e.Sel)
case types.FieldVal:
indices := sel.index
last := len(indices) - 1
v := b.expr(fn, e.X)
v = emitImplicitSelections(fn, v, indices[:last], e)
v = emitFieldSelection(fn, v, indices[last], false, e.Sel)
return v
}
panic("unexpected expression-relative selector")
case *ast.IndexListExpr:
// f[X, Y] must be a generic function
if !instance(fn.info, e.X) {
panic("unexpected expression-could not match index list to instantiation")
}
return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases.
case *ast.IndexExpr:
if instance(fn.info, e.X) {
return b.expr(fn, e.X) // Handle instantiation within the *Ident or *SelectorExpr cases.
}
// not a generic instantiation.
xt := fn.typeOf(e.X)
switch et, mode := indexType(xt); mode {
case ixVar:
// Addressable slice/array; use IndexAddr and Load.
return b.addr(fn, e, false).load(fn, e)
case ixArrVar, ixValue:
// An array in a register, a string or a combined type that contains
// either an [_]array (ixArrVar) or string (ixValue).
// Note: for ixArrVar and CoreType(xt)==nil can be IndexAddr and Load.
index := b.expr(fn, e.Index)
if isUntyped(index.Type()) {
index = emitConv(fn, index, tInt, e.Index)
}
v := &Index{
X: b.expr(fn, e.X),
Index: index,
}
v.setType(et)
return fn.emit(v, e)
case ixMap:
ct := typeutil.CoreType(xt).(*types.Map)
v := &MapLookup{
X: b.expr(fn, e.X),
Index: emitConv(fn, b.expr(fn, e.Index), ct.Key(), e.Index),
}
v.setType(ct.Elem())
return fn.emit(v, e)
default:
panic("unexpected container type in IndexExpr: " + xt.String())
}
case *ast.CompositeLit, *ast.StarExpr:
// Addressable types (lvalues)
return b.addr(fn, e, false).load(fn, e)
}
panic(fmt.Sprintf("unexpected expr: %T", e))
}
// stmtList emits to fn code for all statements in list.
func (b *builder) stmtList(fn *Function, list []ast.Stmt) {
for _, s := range list {
b.stmt(fn, s)
}
}
// receiver emits to fn code for expression e in the "receiver"
// position of selection e.f (where f may be a field or a method) and
// returns the effective receiver after applying the implicit field
// selections of sel.
//
// wantAddr requests that the result is an address. If
// !sel.indirect, this may require that e be built in addr() mode; it
// must thus be addressable.
//
// escaping is defined as per builder.addr().
func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *selection, source ast.Node) Value {
var v Value
if wantAddr && !sel.indirect && !isPointerCore(fn.typeOf(e)) {
v = b.addr(fn, e, escaping).address(fn)
} else {
v = b.expr(fn, e)
}
0