| // Copyright 2009 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 walk |
| |
| import ( |
| "encoding/binary" |
| "go/constant" |
| |
| "cmd/compile/internal/base" |
| "cmd/compile/internal/ir" |
| "cmd/compile/internal/reflectdata" |
| "cmd/compile/internal/ssagen" |
| "cmd/compile/internal/typecheck" |
| "cmd/compile/internal/types" |
| "cmd/internal/src" |
| "cmd/internal/sys" |
| ) |
| |
| // walkConv walks an OCONV or OCONVNOP (but not OCONVIFACE) node. |
| func walkConv(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| n.X = walkExpr(n.X, init) |
| if n.Op() == ir.OCONVNOP && n.Type() == n.X.Type() { |
| return n.X |
| } |
| if n.Op() == ir.OCONVNOP && ir.ShouldCheckPtr(ir.CurFunc, 1) { |
| if n.Type().IsUnsafePtr() && n.X.Type().IsUintptr() { // uintptr to unsafe.Pointer |
| return walkCheckPtrArithmetic(n, init) |
| } |
| } |
| param, result := rtconvfn(n.X.Type(), n.Type()) |
| if param == types.Txxx { |
| return n |
| } |
| fn := types.BasicTypeNames[param] + "to" + types.BasicTypeNames[result] |
| return typecheck.Conv(mkcall(fn, types.Types[result], init, typecheck.Conv(n.X, types.Types[param])), n.Type()) |
| } |
| |
| // walkConvInterface walks an OCONVIFACE node. |
| func walkConvInterface(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| |
| n.X = walkExpr(n.X, init) |
| |
| fromType := n.X.Type() |
| toType := n.Type() |
| if !fromType.IsInterface() && !ir.IsBlank(ir.CurFunc.Nname) { |
| // skip unnamed functions (func _()) |
| reflectdata.MarkTypeUsedInInterface(fromType, ir.CurFunc.LSym) |
| } |
| |
| if !fromType.IsInterface() { |
| var typeWord ir.Node |
| if toType.IsEmptyInterface() { |
| typeWord = reflectdata.TypePtr(fromType) |
| } else { |
| typeWord = reflectdata.ITabAddr(fromType, toType) |
| } |
| l := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeWord, dataWord(n.Pos(), n.X, init, n.Esc() != ir.EscNone)) |
| l.SetType(toType) |
| l.SetTypecheck(n.Typecheck()) |
| return l |
| } |
| if fromType.IsEmptyInterface() { |
| base.Fatalf("OCONVIFACE can't operate on an empty interface") |
| } |
| |
| // Evaluate the input interface. |
| c := typecheck.Temp(fromType) |
| init.Append(ir.NewAssignStmt(base.Pos, c, n.X)) |
| |
| // Grab its parts. |
| itab := ir.NewUnaryExpr(base.Pos, ir.OITAB, c) |
| itab.SetType(types.Types[types.TUINTPTR].PtrTo()) |
| itab.SetTypecheck(1) |
| data := ir.NewUnaryExpr(n.Pos(), ir.OIDATA, c) |
| data.SetType(types.Types[types.TUINT8].PtrTo()) // Type is generic pointer - we're just passing it through. |
| data.SetTypecheck(1) |
| |
| var typeWord ir.Node |
| if toType.IsEmptyInterface() { |
| // Implement interface to empty interface conversion. |
| // res = itab |
| // if res != nil { |
| // res = res.type |
| // } |
| typeWord = typecheck.Temp(types.NewPtr(types.Types[types.TUINT8])) |
| init.Append(ir.NewAssignStmt(base.Pos, typeWord, itab)) |
| nif := ir.NewIfStmt(base.Pos, typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.ONE, typeWord, typecheck.NodNil())), nil, nil) |
| nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, typeWord, itabType(typeWord))} |
| init.Append(nif) |
| } else { |
| // Must be converting I2I (more specific to less specific interface). |
| // res = convI2I(toType, itab) |
| fn := typecheck.LookupRuntime("convI2I") |
| types.CalcSize(fn.Type()) |
| call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) |
| call.Args = []ir.Node{reflectdata.TypePtr(toType), itab} |
| typeWord = walkExpr(typecheck.Expr(call), init) |
| } |
| |
| // Build the result. |
| // e = iface{typeWord, data} |
| e := ir.NewBinaryExpr(base.Pos, ir.OEFACE, typeWord, data) |
| e.SetType(toType) // assign type manually, typecheck doesn't understand OEFACE. |
| e.SetTypecheck(1) |
| return e |
| } |
| |
| // Returns the data word (the second word) used to represent n in an interface. |
| // n must not be of interface type. |
| // esc describes whether the result escapes. |
| func dataWord(pos src.XPos, n ir.Node, init *ir.Nodes, escapes bool) ir.Node { |
| fromType := n.Type() |
| |
| // If it's a pointer, it is its own representation. |
| if types.IsDirectIface(fromType) { |
| return n |
| } |
| |
| // Try a bunch of cases to avoid an allocation. |
| var value ir.Node |
| switch { |
| case fromType.Size() == 0: |
| // n is zero-sized. Use zerobase. |
| cheapExpr(n, init) // Evaluate n for side-effects. See issue 19246. |
| value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR]) |
| case fromType.IsBoolean() || (fromType.Size() == 1 && fromType.IsInteger()): |
| // n is a bool/byte. Use staticuint64s[n * 8] on little-endian |
| // and staticuint64s[n * 8 + 7] on big-endian. |
| n = cheapExpr(n, init) |
| // byteindex widens n so that the multiplication doesn't overflow. |
| index := ir.NewBinaryExpr(base.Pos, ir.OLSH, byteindex(n), ir.NewInt(3)) |
| if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian { |
| index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(7)) |
| } |
| // The actual type is [256]uint64, but we use [256*8]uint8 so we can address |
| // individual bytes. |
| staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, types.NewArray(types.Types[types.TUINT8], 256*8)) |
| xe := ir.NewIndexExpr(base.Pos, staticuint64s, index) |
| xe.SetBounded(true) |
| value = xe |
| case n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PEXTERN && n.(*ir.Name).Readonly(): |
| // n is a readonly global; use it directly. |
| value = n |
| case !escapes && fromType.Size() <= 1024: |
| // n does not escape. Use a stack temporary initialized to n. |
| value = typecheck.Temp(fromType) |
| init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, value, n))) |
| } |
| if value != nil { |
| // The interface data word is &value. |
| return typecheck.Expr(typecheck.NodAddr(value)) |
| } |
| |
| // Time to do an allocation. We'll call into the runtime for that. |
| fnname, argType, needsaddr := dataWordFuncName(fromType) |
| fn := typecheck.LookupRuntime(fnname) |
| |
| var args []ir.Node |
| if needsaddr { |
| // Types of large or unknown size are passed by reference. |
| // Orderexpr arranged for n to be a temporary for all |
| // the conversions it could see. Comparison of an interface |
| // with a non-interface, especially in a switch on interface value |
| // with non-interface cases, is not visible to order.stmt, so we |
| // have to fall back on allocating a temp here. |
| if !ir.IsAddressable(n) { |
| n = copyExpr(n, fromType, init) |
| } |
| fn = typecheck.SubstArgTypes(fn, fromType) |
| args = []ir.Node{reflectdata.TypePtr(fromType), typecheck.NodAddr(n)} |
| } else { |
| // Use a specialized conversion routine that takes the type being |
| // converted by value, not by pointer. |
| var arg ir.Node |
| switch { |
| case fromType == argType: |
| // already in the right type, nothing to do |
| arg = n |
| case fromType.Kind() == argType.Kind(), |
| fromType.IsPtrShaped() && argType.IsPtrShaped(): |
| // can directly convert (e.g. named type to underlying type, or one pointer to another) |
| // TODO: never happens because pointers are directIface? |
| arg = ir.NewConvExpr(pos, ir.OCONVNOP, argType, n) |
| case fromType.IsInteger() && argType.IsInteger(): |
| // can directly convert (e.g. int32 to uint32) |
| arg = ir.NewConvExpr(pos, ir.OCONV, argType, n) |
| default: |
| // unsafe cast through memory |
| arg = copyExpr(n, fromType, init) |
| var addr ir.Node = typecheck.NodAddr(arg) |
| addr = ir.NewConvExpr(pos, ir.OCONVNOP, argType.PtrTo(), addr) |
| arg = ir.NewStarExpr(pos, addr) |
| arg.SetType(argType) |
| } |
| args = []ir.Node{arg} |
| } |
| call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) |
| call.Args = args |
| return safeExpr(walkExpr(typecheck.Expr(call), init), init) |
| } |
| |
| // walkConvIData walks an OCONVIDATA node. |
| func walkConvIData(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| n.X = walkExpr(n.X, init) |
| return dataWord(n.Pos(), n.X, init, n.Esc() != ir.EscNone) |
| } |
| |
| // walkBytesRunesToString walks an OBYTES2STR or ORUNES2STR node. |
| func walkBytesRunesToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| a := typecheck.NodNil() |
| if n.Esc() == ir.EscNone { |
| // Create temporary buffer for string on stack. |
| a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8]) |
| } |
| if n.Op() == ir.ORUNES2STR { |
| // slicerunetostring(*[32]byte, []rune) string |
| return mkcall("slicerunetostring", n.Type(), init, a, n.X) |
| } |
| // slicebytetostring(*[32]byte, ptr *byte, n int) string |
| n.X = cheapExpr(n.X, init) |
| ptr, len := backingArrayPtrLen(n.X) |
| return mkcall("slicebytetostring", n.Type(), init, a, ptr, len) |
| } |
| |
| // walkBytesToStringTemp walks an OBYTES2STRTMP node. |
| func walkBytesToStringTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| n.X = walkExpr(n.X, init) |
| if !base.Flag.Cfg.Instrumenting { |
| // Let the backend handle OBYTES2STRTMP directly |
| // to avoid a function call to slicebytetostringtmp. |
| return n |
| } |
| // slicebytetostringtmp(ptr *byte, n int) string |
| n.X = cheapExpr(n.X, init) |
| ptr, len := backingArrayPtrLen(n.X) |
| return mkcall("slicebytetostringtmp", n.Type(), init, ptr, len) |
| } |
| |
| // walkRuneToString walks an ORUNESTR node. |
| func walkRuneToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| a := typecheck.NodNil() |
| if n.Esc() == ir.EscNone { |
| a = stackBufAddr(4, types.Types[types.TUINT8]) |
| } |
| // intstring(*[4]byte, rune) |
| return mkcall("intstring", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TINT64])) |
| } |
| |
| // walkStringToBytes walks an OSTR2BYTES node. |
| func walkStringToBytes(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| s := n.X |
| if ir.IsConst(s, constant.String) { |
| sc := ir.StringVal(s) |
| |
| // Allocate a [n]byte of the right size. |
| t := types.NewArray(types.Types[types.TUINT8], int64(len(sc))) |
| var a ir.Node |
| if n.Esc() == ir.EscNone && len(sc) <= int(ir.MaxImplicitStackVarSize) { |
| a = stackBufAddr(t.NumElem(), t.Elem()) |
| } else { |
| types.CalcSize(t) |
| a = ir.NewUnaryExpr(base.Pos, ir.ONEW, nil) |
| a.SetType(types.NewPtr(t)) |
| a.SetTypecheck(1) |
| a.MarkNonNil() |
| } |
| p := typecheck.Temp(t.PtrTo()) // *[n]byte |
| init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, p, a))) |
| |
| // Copy from the static string data to the [n]byte. |
| if len(sc) > 0 { |
| as := ir.NewAssignStmt(base.Pos, ir.NewStarExpr(base.Pos, p), ir.NewStarExpr(base.Pos, typecheck.ConvNop(ir.NewUnaryExpr(base.Pos, ir.OSPTR, s), t.PtrTo()))) |
| appendWalkStmt(init, as) |
| } |
| |
| // Slice the [n]byte to a []byte. |
| slice := ir.NewSliceExpr(n.Pos(), ir.OSLICEARR, p, nil, nil, nil) |
| slice.SetType(n.Type()) |
| slice.SetTypecheck(1) |
| return walkExpr(slice, init) |
| } |
| |
| a := typecheck.NodNil() |
| if n.Esc() == ir.EscNone { |
| // Create temporary buffer for slice on stack. |
| a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8]) |
| } |
| // stringtoslicebyte(*32[byte], string) []byte |
| return mkcall("stringtoslicebyte", n.Type(), init, a, typecheck.Conv(s, types.Types[types.TSTRING])) |
| } |
| |
| // walkStringToBytesTemp walks an OSTR2BYTESTMP node. |
| func walkStringToBytesTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| // []byte(string) conversion that creates a slice |
| // referring to the actual string bytes. |
| // This conversion is handled later by the backend and |
| // is only for use by internal compiler optimizations |
| // that know that the slice won't be mutated. |
| // The only such case today is: |
| // for i, c := range []byte(string) |
| n.X = walkExpr(n.X, init) |
| return n |
| } |
| |
| // walkStringToRunes walks an OSTR2RUNES node. |
| func walkStringToRunes(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| a := typecheck.NodNil() |
| if n.Esc() == ir.EscNone { |
| // Create temporary buffer for slice on stack. |
| a = stackBufAddr(tmpstringbufsize, types.Types[types.TINT32]) |
| } |
| // stringtoslicerune(*[32]rune, string) []rune |
| return mkcall("stringtoslicerune", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TSTRING])) |
| } |
| |
| // dataWordFuncName returns the name of the function used to convert a value of type "from" |
| // to the data word of an interface. |
| // argType is the type the argument needs to be coerced to. |
| // needsaddr reports whether the value should be passed (needaddr==false) or its address (needsaddr==true). |
| func dataWordFuncName(from *types.Type) (fnname string, argType *types.Type, needsaddr bool) { |
| if from.IsInterface() { |
| base.Fatalf("can only handle non-interfaces") |
| } |
| switch { |
| case from.Size() == 2 && uint8(from.Alignment()) == 2: |
| return "convT16", types.Types[types.TUINT16], false |
| case from.Size() == 4 && uint8(from.Alignment()) == 4 && !from.HasPointers(): |
| return "convT32", types.Types[types.TUINT32], false |
| case from.Size() == 8 && uint8(from.Alignment()) == uint8(types.Types[types.TUINT64].Alignment()) && !from.HasPointers(): |
| return "convT64", types.Types[types.TUINT64], false |
| } |
| if sc := from.SoleComponent(); sc != nil { |
| switch { |
| case sc.IsString(): |
| return "convTstring", types.Types[types.TSTRING], false |
| case sc.IsSlice(): |
| return "convTslice", types.NewSlice(types.Types[types.TUINT8]), false // the element type doesn't matter |
| } |
| } |
| |
| if from.HasPointers() { |
| return "convT", types.Types[types.TUNSAFEPTR], true |
| } |
| return "convTnoptr", types.Types[types.TUNSAFEPTR], true |
| } |
| |
| // rtconvfn returns the parameter and result types that will be used by a |
| // runtime function to convert from type src to type dst. The runtime function |
| // name can be derived from the names of the returned types. |
| // |
| // If no such function is necessary, it returns (Txxx, Txxx). |
| func rtconvfn(src, dst *types.Type) (param, result types.Kind) { |
| if ssagen.Arch.SoftFloat { |
| return types.Txxx, types.Txxx |
| } |
| |
| switch ssagen.Arch.LinkArch.Family { |
| case sys.ARM, sys.MIPS: |
| if src.IsFloat() { |
| switch dst.Kind() { |
| case types.TINT64, types.TUINT64: |
| return types.TFLOAT64, dst.Kind() |
| } |
| } |
| if dst.IsFloat() { |
| switch src.Kind() { |
| case types.TINT64, types.TUINT64: |
| return src.Kind(), types.TFLOAT64 |
| } |
| } |
| |
| case sys.I386: |
| if src.IsFloat() { |
| switch dst.Kind() { |
| case types.TINT64, types.TUINT64: |
| return types.TFLOAT64, dst.Kind() |
| case types.TUINT32, types.TUINT, types.TUINTPTR: |
| return types.TFLOAT64, types.TUINT32 |
| } |
| } |
| if dst.IsFloat() { |
| switch src.Kind() { |
| case types.TINT64, types.TUINT64: |
| return src.Kind(), types.TFLOAT64 |
| case types.TUINT32, types.TUINT, types.TUINTPTR: |
| return types.TUINT32, types.TFLOAT64 |
| } |
| } |
| } |
| return types.Txxx, types.Txxx |
| } |
| |
| // byteindex converts n, which is byte-sized, to an int used to index into an array. |
| // We cannot use conv, because we allow converting bool to int here, |
| // which is forbidden in user code. |
| func byteindex(n ir.Node) ir.Node { |
| // We cannot convert from bool to int directly. |
| // While converting from int8 to int is possible, it would yield |
| // the wrong result for negative values. |
| // Reinterpreting the value as an unsigned byte solves both cases. |
| if !types.Identical(n.Type(), types.Types[types.TUINT8]) { |
| n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) |
| n.SetType(types.Types[types.TUINT8]) |
| n.SetTypecheck(1) |
| } |
| n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) |
| n.SetType(types.Types[types.TINT]) |
| n.SetTypecheck(1) |
| return n |
| } |
| |
| func walkCheckPtrArithmetic(n *ir.ConvExpr, init *ir.Nodes) ir.Node { |
| // Calling cheapExpr(n, init) below leads to a recursive call to |
| // walkExpr, which leads us back here again. Use n.Checkptr to |
| // prevent infinite loops. |
| if n.CheckPtr() { |
| return n |
| } |
| n.SetCheckPtr(true) |
| defer n.SetCheckPtr(false) |
| |
| // TODO(mdempsky): Make stricter. We only need to exempt |
| // reflect.Value.Pointer and reflect.Value.UnsafeAddr. |
| switch n.X.Op() { |
| case ir.OCALLMETH: |
| base.FatalfAt(n.X.Pos(), "OCALLMETH missed by typecheck") |
| case ir.OCALLFUNC, ir.OCALLINTER: |
| return n |
| } |
| |
| if n.X.Op() == ir.ODOTPTR && ir.IsReflectHeaderDataField(n.X) { |
| return n |
| } |
| |
| // Find original unsafe.Pointer operands involved in this |
| // arithmetic expression. |
| // |
| // "It is valid both to add and to subtract offsets from a |
| // pointer in this way. It is also valid to use &^ to round |
| // pointers, usually for alignment." |
| var originals []ir.Node |
| var walk func(n ir.Node) |
| walk = func(n ir.Node) { |
| switch n.Op() { |
| case ir.OADD: |
| n := n.(*ir.BinaryExpr) |
| walk(n.X) |
| walk(n.Y) |
| case ir.OSUB, ir.OANDNOT: |
| n := n.(*ir.BinaryExpr) |
| walk(n.X) |
| case ir.OCONVNOP: |
| n := n.(*ir.ConvExpr) |
| if n.X.Type().IsUnsafePtr() { |
| n.X = cheapExpr(n.X, init) |
| originals = append(originals, typecheck.ConvNop(n.X, types.Types[types.TUNSAFEPTR])) |
| } |
| } |
| } |
| walk(n.X) |
| |
| cheap := cheapExpr(n, init) |
| |
| slice := typecheck.MakeDotArgs(base.Pos, types.NewSlice(types.Types[types.TUNSAFEPTR]), originals) |
| slice.SetEsc(ir.EscNone) |
| |
| init.Append(mkcall("checkptrArithmetic", nil, init, typecheck.ConvNop(cheap, types.Types[types.TUNSAFEPTR]), slice)) |
| // TODO(khr): Mark backing store of slice as dead. This will allow us to reuse |
| // the backing store for multiple calls to checkptrArithmetic. |
| |
| return cheap |
| } |