// 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 ssa // Helpers for emitting SSA instructions. import ( "fmt" "go/ast" "go/token" "go/types" "golang.org/x/tools/internal/typeparams" ) // emitAlloc emits to f a new Alloc instruction allocating a variable // of type typ. // // The caller must set Alloc.Heap=true (for an heap-allocated variable) // or add the Alloc to f.Locals (for a frame-allocated variable). // // During building, a variable in f.Locals may have its Heap flag // set when it is discovered that its address is taken. // These Allocs are removed from f.Locals at the end. // // The builder should generally call one of the emit{New,Local,LocalVar} wrappers instead. func emitAlloc(f *Function, typ types.Type, pos token.Pos, comment string) *Alloc { v := &Alloc{Comment: comment} v.setType(types.NewPointer(typ)) v.setPos(pos) f.emit(v) return v } // emitNew emits to f a new Alloc instruction heap-allocating a // variable of type typ. pos is the optional source location. func emitNew(f *Function, typ types.Type, pos token.Pos, comment string) *Alloc { alloc := emitAlloc(f, typ, pos, comment) alloc.Heap = true return alloc } // emitLocal creates a local var for (t, pos, comment) and // emits an Alloc instruction for it. // // (Use this function or emitNew for synthetic variables; // for source-level variables in the same function, use emitLocalVar.) func emitLocal(f *Function, t types.Type, pos token.Pos, comment string) *Alloc { local := emitAlloc(f, t, pos, comment) f.Locals = append(f.Locals, local) return local } // emitLocalVar creates a local var for v and emits an Alloc instruction for it. // Subsequent calls to f.lookup(v) return it. // It applies the appropriate generic instantiation to the type. func emitLocalVar(f *Function, v *types.Var) *Alloc { alloc := emitLocal(f, f.typ(v.Type()), v.Pos(), v.Name()) f.vars[v] = alloc return alloc } // emitLoad emits to f an instruction to load the address addr into a // new temporary, and returns the value so defined. func emitLoad(f *Function, addr Value) *UnOp { v := &UnOp{Op: token.MUL, X: addr} v.setType(typeparams.MustDeref(addr.Type())) f.emit(v) return v } // emitDebugRef emits to f a DebugRef pseudo-instruction associating // expression e with value v. func emitDebugRef(f *Function, e ast.Expr, v Value, isAddr bool) { if !f.debugInfo() { return // debugging not enabled } if v == nil || e == nil { panic("nil") } var obj types.Object e = unparen(e) if id, ok := e.(*ast.Ident); ok { if isBlankIdent(id) { return } obj = f.objectOf(id) switch obj.(type) { case *types.Nil, *types.Const, *types.Builtin: return } } f.emit(&DebugRef{ X: v, Expr: e, IsAddr: isAddr, object: obj, }) } // emitArith emits to f code to compute the binary operation op(x, y) // where op is an eager shift, logical or arithmetic operation. // (Use emitCompare() for comparisons and Builder.logicalBinop() for // non-eager operations.) func emitArith(f *Function, op token.Token, x, y Value, t types.Type, pos token.Pos) Value { switch op { case token.SHL, token.SHR: x = emitConv(f, x, t) // y may be signed or an 'untyped' constant. // There is a runtime panic if y is signed and <0. Instead of inserting a check for y<0 // and converting to an unsigned value (like the compiler) leave y as is. if isUntyped(y.Type().Underlying()) { // Untyped conversion: // Spec https://go.dev/ref/spec#Operators: // The right operand in a shift expression must have integer type or be an untyped constant // representable by a value of type uint. y = emitConv(f, y, types.Typ[types.Uint]) } case token.ADD, token.SUB, token.MUL, token.QUO, token.REM, token.AND, token.OR, token.XOR, token.AND_NOT: x = emitConv(f, x, t) y = emitConv(f, y, t) default: panic("illegal op in emitArith: " + op.String()) } v := &BinOp{ Op: op, X: x, Y: y, } v.setPos(pos) v.setType(t) return f.emit(v) } // emitCompare emits to f code compute the boolean result of // comparison 'x op y'. func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { xt := x.Type().Underlying() yt := y.Type().Underlying() // Special case to optimise a tagless SwitchStmt so that // these are equivalent // switch { case e: ...} // switch true { case e: ... } // if e==true { ... } // even in the case when e's type is an interface. // TODO(adonovan): opt: generalise to x==true, false!=y, etc. if x == vTrue && op == token.EQL { if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 { return y } } if types.Identical(xt, yt) { // no conversion necessary } else if isNonTypeParamInterface(x.Type()) { y = emitConv(f, y, x.Type()) } else if isNonTypeParamInterface(y.Type()) { x = emitConv(f, x, y.Type()) } else if _, ok := x.(*Const); ok { x = emitConv(f, x, y.Type()) } else if _, ok := y.(*Const); ok { y = emitConv(f, y, x.Type()) } else { // other cases, e.g. channels. No-op. } v := &BinOp{ Op: op, X: x, Y: y, } v.setPos(pos) v.setType(tBool) return f.emit(v) } // isValuePreserving returns true if a conversion from ut_src to // ut_dst is value-preserving, i.e. just a change of type. // Precondition: neither argument is a named or alias type. func isValuePreserving(ut_src, ut_dst types.Type) bool { // Identical underlying types? if types.IdenticalIgnoreTags(ut_dst, ut_src) { return true } switch ut_dst.(type) { case *types.Chan: // Conversion between channel types? _, ok := ut_src.(*types.Chan) return ok case *types.Pointer: // Conversion between pointers with identical base types? _, ok := ut_src.(*types.Pointer) return ok } return false } // emitConv emits to f code to convert Value val to exactly type typ, // and returns the converted value. Implicit conversions are required // by language assignability rules in assignments, parameter passing, // etc. func emitConv(f *Function, val Value, typ types.Type) Value { t_src := val.Type() // Identical types? Conversion is a no-op. if types.Identical(t_src, typ) { return val } ut_dst := typ.Underlying() ut_src := t_src.Underlying() // Conversion to, or construction of a value of, an interface type? if isNonTypeParamInterface(typ) { // Interface name change? if isValuePreserving(ut_src, ut_dst) { c := &ChangeType{X: val} c.setType(typ) return f.emit(c) } // Assignment from one interface type to another? if isNonTypeParamInterface(t_src) { c := &ChangeInterface{X: val} c.setType(typ) return f.emit(c) } // Untyped nil constant? Return interface-typed nil constant. if ut_src == tUntypedNil { return zeroConst(typ) } // Convert (non-nil) "untyped" literals to their default type. if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 { val = emitConv(f, val, types.Default(ut_src)) } // Record the types of operands to MakeInterface, if // non-parameterized, as they are the set of runtime types. t := val.Type() if f.typeparams.Len() == 0 || !f.Prog.isParameterized(t) { addRuntimeType(f.Prog, t) } mi := &MakeInterface{X: val} mi.setType(typ) return f.emit(mi) } // In the common case, the typesets of src and dst are singletons // and we emit an appropriate conversion. But if either contains // a type parameter, the conversion may represent a cross product, // in which case which we emit a MultiConvert. dst_terms := typeSetOf(ut_dst) src_terms := typeSetOf(ut_src) // conversionCase describes an instruction pattern that maybe emitted to // model d <- s for d in dst_terms and s in src_terms. // Multiple conversions can match the same pattern. type conversionCase uint8 const ( changeType conversionCase = 1 << iota sliceToArray sliceToArrayPtr sliceTo0Array sliceTo0ArrayPtr convert ) // classify the conversion case of a source type us to a destination type ud. // us and ud are underlying types (not *Named or *Alias) classify := func(us, ud types.Type) conversionCase { // Just a change of type, but not value or representation? if isValuePreserving(us, ud) { return changeType } // Conversion from slice to array or slice to array pointer? if slice, ok := us.(*types.Slice); ok { var arr *types.Array var ptr bool // Conversion from slice to array pointer? switch d := ud.(type) { case *types.Array: arr = d case *types.Pointer: arr, _ = d.Elem().Underlying().(*types.Array) ptr = true } if arr != nil && types.Identical(slice.Elem(), arr.Elem()) { if arr.Len() == 0 { if ptr { return sliceTo0ArrayPtr } else { return sliceTo0Array } } if ptr { return sliceToArrayPtr } else { return sliceToArray } } } // The only remaining case in well-typed code is a representation- // changing conversion of basic types (possibly with []byte/[]rune). if !isBasic(us) && !isBasic(ud) { panic(fmt.Sprintf("in %s: cannot convert term %s (%s [within %s]) to type %s [within %s]", f, val, val.Type(), us, typ, ud)) } return convert } var classifications conversionCase for _, s := range src_terms { us := s.Type().Underlying() for _, d := range dst_terms { ud := d.Type().Underlying() classifications |= classify(us, ud) } } if classifications == 0 { panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ)) } // Conversion of a compile-time constant value? if c, ok := val.(*Const); ok { // Conversion to a basic type? if isBasic(ut_dst) { // Conversion of a compile-time constant to // another constant type results in a new // constant of the destination type and // (initially) the same abstract value. // We don't truncate the value yet. return NewConst(c.Value, typ) } // Can we always convert from zero value without panicking? const mayPanic = sliceToArray | sliceToArrayPtr if c.Value == nil && classifications&mayPanic == 0 { return NewConst(nil, typ) } // We're converting from constant to non-constant type, // e.g. string -> []byte/[]rune. } switch classifications { case changeType: // representation-preserving change c := &ChangeType{X: val} c.setType(typ) return f.emit(c) case sliceToArrayPtr, sliceTo0ArrayPtr: // slice to array pointer c := &SliceToArrayPointer{X: val} c.setType(typ) return f.emit(c) case sliceToArray: // slice to arrays (not zero-length) ptype := types.NewPointer(typ) p := &SliceToArrayPointer{X: val} p.setType(ptype) x := f.emit(p) unOp := &UnOp{Op: token.MUL, X: x} unOp.setType(typ) return f.emit(unOp) case sliceTo0Array: // slice to zero-length arrays (constant) return zeroConst(typ) case convert: // representation-changing conversion c := &Convert{X: val} c.setType(typ) return f.emit(c) default: // multiple conversion c := &MultiConvert{X: val, from: src_terms, to: dst_terms} c.setType(typ) return f.emit(c) } } // emitTypeCoercion emits to f code to coerce the type of a // Value v to exactly type typ, and returns the coerced value. // // Requires that coercing v.Typ() to typ is a value preserving change. // // Currently used only when v.Type() is a type instance of typ or vice versa. // A type v is a type instance of a type t if there exists a // type parameter substitution σ s.t. σ(v) == t. Example: // // σ(func(T) T) == func(int) int for σ == [T ↦ int] // // This happens in instantiation wrappers for conversion // from an instantiation to a parameterized type (and vice versa) // with σ substituting f.typeparams by f.typeargs. func emitTypeCoercion(f *Function, v Value, typ types.Type) Value { if types.Identical(v.Type(), typ) { return v // no coercion needed } // TODO(taking): for instances should we record which side is the instance? c := &ChangeType{ X: v, } c.setType(typ) f.emit(c) return c } // emitStore emits to f an instruction to store value val at location // addr, applying implicit conversions as required by assignability rules. func emitStore(f *Function, addr, val Value, pos token.Pos) *Store { typ := typeparams.MustDeref(addr.Type()) s := &Store{ Addr: addr, Val: emitConv(f, val, typ), pos: pos, } f.emit(s) return s } // emitJump emits to f a jump to target, and updates the control-flow graph. // Postcondition: f.currentBlock is nil. func emitJump(f *Function, target *BasicBlock) { b := f.currentBlock b.emit(new(Jump)) addEdge(b, target) f.currentBlock = nil } // emitIf emits to f a conditional jump to tblock or fblock based on // cond, and updates the control-flow graph. // Postcondition: f.currentBlock is nil. func emitIf(f *Function, cond Value, tblock, fblock *BasicBlock) { b := f.currentBlock b.emit(&If{Cond: cond}) addEdge(b, tblock) addEdge(b, fblock) f.currentBlock = nil } // emitExtract emits to f an instruction to extract the index'th // component of tuple. It returns the extracted value. func emitExtract(f *Function, tuple Value, index int) Value { e := &Extract{Tuple: tuple, Index: index} e.setType(tuple.Type().(*types.Tuple).At(index).Type()) return f.emit(e) } // emitTypeAssert emits to f a type assertion value := x.(t) and // returns the value. x.Type() must be an interface. func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value { a := &TypeAssert{X: x, AssertedType: t} a.setPos(pos) a.setType(t) return f.emit(a) } // emitTypeTest emits to f a type test value,ok := x.(t) and returns // a (value, ok) tuple. x.Type() must be an interface. func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value { a := &TypeAssert{ X: x, AssertedType: t, CommaOk: true, } a.setPos(pos) a.setType(types.NewTuple( newVar("value", t), varOk, )) return f.emit(a) } // emitTailCall emits to f a function call in tail position. The // caller is responsible for all fields of 'call' except its type. // Intended for wrapper methods. // Precondition: f does/will not use deferred procedure calls. // Postcondition: f.currentBlock is nil. func emitTailCall(f *Function, call *Call) { tresults := f.Signature.Results() nr := tresults.Len() if nr == 1 { call.typ = tresults.At(0).Type() } else { call.typ = tresults } tuple := f.emit(call) var ret Return switch nr { case 0: // no-op case 1: ret.Results = []Value{tuple} default: for i := 0; i < nr; i++ { v := emitExtract(f, tuple, i) // TODO(adonovan): in principle, this is required: // v = emitConv(f, o.Type, f.Signature.Results[i].Type) // but in practice emitTailCall is only used when // the types exactly match. ret.Results = append(ret.Results, v) } } f.emit(&ret) f.currentBlock = nil } // emitImplicitSelections emits to f code to apply the sequence of // implicit field selections specified by indices to base value v, and // returns the selected value. // // If v is the address of a struct, the result will be the address of // a field; if it is the value of a struct, the result will be the // value of a field. func emitImplicitSelections(f *Function, v Value, indices []int, pos token.Pos) Value { for _, index := range indices { if isPointerCore(v.Type()) { fld := fieldOf(typeparams.MustDeref(v.Type()), index) instr := &FieldAddr{ X: v, Field: index, } instr.setPos(pos) instr.setType(types.NewPointer(fld.Type())) v = f.emit(instr) // Load the field's value iff indirectly embedded. if isPointerCore(fld.Type()) { v = emitLoad(f, v) } } else { fld := fieldOf(v.Type(), index) instr := &Field{ X: v, Field: index, } instr.setPos(pos) instr.setType(fld.Type()) v = f.emit(instr) } } return v } // emitFieldSelection emits to f code to select the index'th field of v. // // If wantAddr, the input must be a pointer-to-struct and the result // will be the field's address; otherwise the result will be the // field's value. // Ident id is used for position and debug info. func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value { if isPointerCore(v.Type()) { fld := fieldOf(typeparams.MustDeref(v.Type()), index) instr := &FieldAddr{ X: v, Field: index, } instr.setPos(id.Pos()) instr.setType(types.NewPointer(fld.Type())) v = f.emit(instr) // Load the field's value iff we don't want its address. if !wantAddr { v = emitLoad(f, v) } } else { fld := fieldOf(v.Type(), index) instr := &Field{ X: v, Field: index, } instr.setPos(id.Pos()) instr.setType(fld.Type()) v = f.emit(instr) } emitDebugRef(f, id, v, wantAddr) return v } // createRecoverBlock emits to f a block of code to return after a // recovered panic, and sets f.Recover to it. // // If f's result parameters are named, the code loads and returns // their current values, otherwise it returns the zero values of their // type. // // Idempotent. func createRecoverBlock(f *Function) { if f.Recover != nil { return // already created } saved := f.currentBlock f.Recover = f.newBasicBlock("recover") f.currentBlock = f.Recover var results []Value // Reload NRPs to form value tuple. for _, nr := range f.results { results = append(results, emitLoad(f, nr)) } f.emit(&Return{Results: results}) f.currentBlock = saved }