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.
451 lines
12 KiB
Go
451 lines
12 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
|
|
|
|
// Helpers for emitting IR instructions.
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/constant"
|
|
"go/token"
|
|
"go/types"
|
|
)
|
|
|
|
// emitNew emits to f a new (heap Alloc) instruction allocating an
|
|
// object of type typ. pos is the optional source location.
|
|
//
|
|
func emitNew(f *Function, typ types.Type, source ast.Node) *Alloc {
|
|
v := &Alloc{Heap: true}
|
|
v.setType(types.NewPointer(typ))
|
|
f.emit(v, source)
|
|
return v
|
|
}
|
|
|
|
// 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, source ast.Node) *Load {
|
|
v := &Load{X: addr}
|
|
v.setType(deref(addr.Type()))
|
|
f.emit(v, source)
|
|
return v
|
|
}
|
|
|
|
func emitRecv(f *Function, ch Value, commaOk bool, typ types.Type, source ast.Node) Value {
|
|
recv := &Recv{
|
|
Chan: ch,
|
|
CommaOk: commaOk,
|
|
}
|
|
recv.setType(typ)
|
|
return f.emit(recv, source)
|
|
}
|
|
|
|
// 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.Pkg.objectOf(id)
|
|
switch obj.(type) {
|
|
case *types.Nil, *types.Const, *types.Builtin:
|
|
return
|
|
}
|
|
}
|
|
f.emit(&DebugRef{
|
|
X: v,
|
|
Expr: e,
|
|
IsAddr: isAddr,
|
|
object: obj,
|
|
}, nil)
|
|
}
|
|
|
|
// 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, source ast.Node) Value {
|
|
switch op {
|
|
case token.SHL, token.SHR:
|
|
x = emitConv(f, x, t, source)
|
|
// y may be signed or an 'untyped' constant.
|
|
// TODO(adonovan): whence signed values?
|
|
if b, ok := y.Type().Underlying().(*types.Basic); ok && b.Info()&types.IsUnsigned == 0 {
|
|
y = emitConv(f, y, types.Typ[types.Uint64], source)
|
|
}
|
|
|
|
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, source)
|
|
y = emitConv(f, y, t, source)
|
|
|
|
default:
|
|
panic("illegal op in emitArith: " + op.String())
|
|
|
|
}
|
|
v := &BinOp{
|
|
Op: op,
|
|
X: x,
|
|
Y: y,
|
|
}
|
|
v.setType(t)
|
|
return f.emit(v, source)
|
|
}
|
|
|
|
// emitCompare emits to f code compute the boolean result of
|
|
// comparison comparison 'x op y'.
|
|
//
|
|
func emitCompare(f *Function, op token.Token, x, y Value, source ast.Node) 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, ok := x.(*Const); ok && op == token.EQL && x.Value != nil && x.Value.Kind() == constant.Bool && constant.BoolVal(x.Value) {
|
|
if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 {
|
|
return y
|
|
}
|
|
}
|
|
|
|
if types.Identical(xt, yt) {
|
|
// no conversion necessary
|
|
} else if _, ok := xt.(*types.Interface); ok {
|
|
y = emitConv(f, y, x.Type(), source)
|
|
} else if _, ok := yt.(*types.Interface); ok {
|
|
x = emitConv(f, x, y.Type(), source)
|
|
} else if _, ok := x.(*Const); ok {
|
|
x = emitConv(f, x, y.Type(), source)
|
|
} else if _, ok := y.(*Const); ok {
|
|
y = emitConv(f, y, x.Type(), source)
|
|
//lint:ignore SA9003 no-op
|
|
} else {
|
|
// other cases, e.g. channels. No-op.
|
|
}
|
|
|
|
v := &BinOp{
|
|
Op: op,
|
|
X: x,
|
|
Y: y,
|
|
}
|
|
v.setType(tBool)
|
|
return f.emit(v, source)
|
|
}
|
|
|
|
// 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 type.
|
|
//
|
|
func isValuePreserving(ut_src, ut_dst types.Type) bool {
|
|
// Identical underlying types?
|
|
if structTypesIdentical(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. Conversions cannot fail dynamically.
|
|
//
|
|
func emitConv(f *Function, val Value, typ types.Type, source ast.Node) 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()
|
|
|
|
// Just a change of type, but not value or representation?
|
|
if isValuePreserving(ut_src, ut_dst) {
|
|
c := &ChangeType{X: val}
|
|
c.setType(typ)
|
|
return f.emit(c, source)
|
|
}
|
|
|
|
// Conversion to, or construction of a value of, an interface type?
|
|
if _, ok := ut_dst.(*types.Interface); ok {
|
|
// Assignment from one interface type to another?
|
|
if _, ok := ut_src.(*types.Interface); ok {
|
|
c := &ChangeInterface{X: val}
|
|
c.setType(typ)
|
|
return f.emit(c, source)
|
|
}
|
|
|
|
// Untyped nil constant? Return interface-typed nil constant.
|
|
if ut_src == tUntypedNil {
|
|
return emitConst(f, nilConst(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), source)
|
|
}
|
|
|
|
f.Pkg.Prog.needMethodsOf(val.Type())
|
|
mi := &MakeInterface{X: val}
|
|
mi.setType(typ)
|
|
return f.emit(mi, source)
|
|
}
|
|
|
|
// Conversion of a compile-time constant value?
|
|
if c, ok := val.(*Const); ok {
|
|
if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() {
|
|
// 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 emitConst(f, NewConst(c.Value, typ))
|
|
}
|
|
|
|
// We're converting from constant to non-constant type,
|
|
// e.g. string -> []byte/[]rune.
|
|
}
|
|
|
|
// A representation-changing conversion?
|
|
// At least one of {ut_src,ut_dst} must be *Basic.
|
|
// (The other may be []byte or []rune.)
|
|
_, ok1 := ut_src.(*types.Basic)
|
|
_, ok2 := ut_dst.(*types.Basic)
|
|
if ok1 || ok2 {
|
|
c := &Convert{X: val}
|
|
c.setType(typ)
|
|
return f.emit(c, source)
|
|
}
|
|
|
|
panic(fmt.Sprintf("in %s: cannot convert %s (%s) to %s", f, val, val.Type(), typ))
|
|
}
|
|
|
|
// 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, source ast.Node) *Store {
|
|
s := &Store{
|
|
Addr: addr,
|
|
Val: emitConv(f, val, deref(addr.Type()), source),
|
|
}
|
|
// make sure we call getMem after the call to emitConv, which may
|
|
// itself update the memory state
|
|
f.emit(s, source)
|
|
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, source ast.Node) *Jump {
|
|
b := f.currentBlock
|
|
j := new(Jump)
|
|
b.emit(j, source)
|
|
addEdge(b, target)
|
|
f.currentBlock = nil
|
|
return j
|
|
}
|
|
|
|
// 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, source ast.Node) *If {
|
|
b := f.currentBlock
|
|
stmt := &If{Cond: cond}
|
|
b.emit(stmt, source)
|
|
addEdge(b, tblock)
|
|
addEdge(b, fblock)
|
|
f.currentBlock = nil
|
|
return stmt
|
|
}
|
|
|
|
// 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, source ast.Node) Value {
|
|
e := &Extract{Tuple: tuple, Index: index}
|
|
e.setType(tuple.Type().(*types.Tuple).At(index).Type())
|
|
return f.emit(e, source)
|
|
}
|
|
|
|
// 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, source ast.Node) Value {
|
|
a := &TypeAssert{X: x, AssertedType: t}
|
|
a.setType(t)
|
|
return f.emit(a, source)
|
|
}
|
|
|
|
// 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, source ast.Node) Value {
|
|
a := &TypeAssert{
|
|
X: x,
|
|
AssertedType: t,
|
|
CommaOk: true,
|
|
}
|
|
a.setType(types.NewTuple(
|
|
newVar("value", t),
|
|
varOk,
|
|
))
|
|
return f.emit(a, source)
|
|
}
|
|
|
|
// 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, source ast.Node) {
|
|
tresults := f.Signature.Results()
|
|
nr := tresults.Len()
|
|
if nr == 1 {
|
|
call.typ = tresults.At(0).Type()
|
|
} else {
|
|
call.typ = tresults
|
|
}
|
|
tuple := f.emit(call, source)
|
|
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, source)
|
|
// 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.Exit = f.newBasicBlock("exit")
|
|
emitJump(f, f.Exit, source)
|
|
f.currentBlock = f.Exit
|
|
f.emit(&ret, source)
|
|
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, source ast.Node) Value {
|
|
for _, index := range indices {
|
|
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
|
|
|
if isPointer(v.Type()) {
|
|
instr := &FieldAddr{
|
|
X: v,
|
|
Field: index,
|
|
}
|
|
instr.setType(types.NewPointer(fld.Type()))
|
|
v = f.emit(instr, source)
|
|
// Load the field's value iff indirectly embedded.
|
|
if isPointer(fld.Type()) {
|
|
v = emitLoad(f, v, source)
|
|
}
|
|
} else {
|
|
instr := &Field{
|
|
X: v,
|
|
Field: index,
|
|
}
|
|
instr.setType(fld.Type())
|
|
v = f.emit(instr, source)
|
|
}
|
|
}
|
|
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 {
|
|
fld := deref(v.Type()).Underlying().(*types.Struct).Field(index)
|
|
if isPointer(v.Type()) {
|
|
instr := &FieldAddr{
|
|
X: v,
|
|
Field: index,
|
|
}
|
|
instr.setSource(id)
|
|
instr.setType(types.NewPointer(fld.Type()))
|
|
v = f.emit(instr, id)
|
|
// Load the field's value iff we don't want its address.
|
|
if !wantAddr {
|
|
v = emitLoad(f, v, id)
|
|
}
|
|
} else {
|
|
instr := &Field{
|
|
X: v,
|
|
Field: index,
|
|
}
|
|
instr.setSource(id)
|
|
instr.setType(fld.Type())
|
|
v = f.emit(instr, id)
|
|
}
|
|
emitDebugRef(f, id, v, wantAddr)
|
|
return v
|
|
}
|
|
|
|
// zeroValue emits to f code to produce a zero value of type t,
|
|
// and returns it.
|
|
//
|
|
func zeroValue(f *Function, t types.Type, source ast.Node) Value {
|
|
switch t.Underlying().(type) {
|
|
case *types.Struct, *types.Array:
|
|
return emitLoad(f, f.addLocal(t, source), source)
|
|
default:
|
|
return emitConst(f, zeroConst(t))
|
|
}
|
|
}
|
|
|
|
func emitConst(f *Function, c *Const) *Const {
|
|
f.consts = append(f.consts, c)
|
|
return c
|
|
}
|