| // 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" | |
| "fmt" | |
| "go/constant" | |
| "hash/fnv" | |
| "io" | |
| "cmd/compile/internal/base" | |
| "cmd/compile/internal/compare" | |
| "cmd/compile/internal/ir" | |
| "cmd/compile/internal/reflectdata" | |
| "cmd/compile/internal/ssagen" | |
| "cmd/compile/internal/typecheck" | |
| "cmd/compile/internal/types" | |
| ) | |
| func fakePC(n ir.Node) ir.Node { | |
| // In order to get deterministic IDs, we include the package path, absolute filename, line number, column number | |
| // in the calculation of the fakePC for the IR node. | |
| hash := fnv.New32() | |
| // We ignore the errors here because the `io.Writer` in the `hash.Hash` interface never returns an error. | |
| io.WriteString(hash, base.Ctxt.Pkgpath) | |
| io.WriteString(hash, base.Ctxt.PosTable.Pos(n.Pos()).AbsFilename()) | |
| binary.Write(hash, binary.LittleEndian, int64(n.Pos().Line())) | |
| binary.Write(hash, binary.LittleEndian, int64(n.Pos().Col())) | |
| // We also include the string representation of the node to distinguish autogenerated expression since | |
| // those get the same `src.XPos` | |
| io.WriteString(hash, fmt.Sprintf("%v", n)) | |
| return ir.NewInt(base.Pos, int64(hash.Sum32())) | |
| } | |
| // The result of walkCompare MUST be assigned back to n, e.g. | |
| // | |
| // n.Left = walkCompare(n.Left, init) | |
| func walkCompare(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { | |
| if n.X.Type().IsInterface() && n.Y.Type().IsInterface() && n.X.Op() != ir.ONIL && n.Y.Op() != ir.ONIL { | |
| return walkCompareInterface(n, init) | |
| } | |
| if n.X.Type().IsString() && n.Y.Type().IsString() { | |
| return walkCompareString(n, init) | |
| } | |
| n.X = walkExpr(n.X, init) | |
| n.Y = walkExpr(n.Y, init) | |
| // Given mixed interface/concrete comparison, | |
| // rewrite into types-equal && data-equal. | |
| // This is efficient, avoids allocations, and avoids runtime calls. | |
| // | |
| // TODO(mdempsky): It would be more general and probably overall | |
| // simpler to just extend walkCompareInterface to optimize when one | |
| // operand is an OCONVIFACE. | |
| if n.X.Type().IsInterface() != n.Y.Type().IsInterface() { | |
| // Preserve side-effects in case of short-circuiting; see #32187. | |
| l := cheapExpr(n.X, init) | |
| r := cheapExpr(n.Y, init) | |
| // Swap so that l is the interface value and r is the concrete value. | |
| if n.Y.Type().IsInterface() { | |
| l, r = r, l | |
| } | |
| // Handle both == and !=. | |
| eq := n.Op() | |
| andor := ir.OOROR | |
| if eq == ir.OEQ { | |
| andor = ir.OANDAND | |
| } | |
| // Check for types equal. | |
| // For empty interface, this is: | |
| // l.tab == type(r) | |
| // For non-empty interface, this is: | |
| // l.tab != nil && l.tab._type == type(r) | |
| // | |
| // TODO(mdempsky): For non-empty interface comparisons, just | |
| // compare against the itab address directly? | |
| var eqtype ir.Node | |
| tab := ir.NewUnaryExpr(base.Pos, ir.OITAB, l) | |
| rtyp := reflectdata.CompareRType(base.Pos, n) | |
| if l.Type().IsEmptyInterface() { | |
| tab.SetType(types.NewPtr(types.Types[types.TUINT8])) | |
| tab.SetTypecheck(1) | |
| eqtype = ir.NewBinaryExpr(base.Pos, eq, tab, rtyp) | |
| } else { | |
| nonnil := ir.NewBinaryExpr(base.Pos, brcom(eq), typecheck.NodNil(), tab) | |
| match := ir.NewBinaryExpr(base.Pos, eq, itabType(tab), rtyp) | |
| eqtype = ir.NewLogicalExpr(base.Pos, andor, nonnil, match) | |
| } | |
| // Check for data equal. | |
| eqdata := ir.NewBinaryExpr(base.Pos, eq, ifaceData(n.Pos(), l, r.Type()), r) | |
| // Put it all together. | |
| expr := ir.NewLogicalExpr(base.Pos, andor, eqtype, eqdata) | |
| return finishCompare(n, expr, init) | |
| } | |
| // Must be comparison of array or struct. | |
| // Otherwise back end handles it. | |
| // While we're here, decide whether to | |
| // inline or call an eq alg. | |
| t := n.X.Type() | |
| var inline bool | |
| maxcmpsize := int64(4) | |
| unalignedLoad := ssagen.Arch.LinkArch.CanMergeLoads | |
| if unalignedLoad { | |
| // Keep this low enough to generate less code than a function call. | |
| maxcmpsize = 2 * int64(ssagen.Arch.LinkArch.RegSize) | |
| } | |
| switch t.Kind() { | |
| default: | |
| if base.Debug.Libfuzzer != 0 && t.IsInteger() && (n.X.Name() == nil || !n.X.Name().Libfuzzer8BitCounter()) { | |
| n.X = cheapExpr(n.X, init) | |
| n.Y = cheapExpr(n.Y, init) | |
| // If exactly one comparison operand is | |
| // constant, invoke the constcmp functions | |
| // instead, and arrange for the constant | |
| // operand to be the first argument. | |
| l, r := n.X, n.Y | |
| if r.Op() == ir.OLITERAL { | |
| l, r = r, l | |
| } | |
| constcmp := l.Op() == ir.OLITERAL && r.Op() != ir.OLITERAL | |
| var fn string | |
| var paramType *types.Type | |
| switch t.Size() { | |
| case 1: | |
| fn = "libfuzzerTraceCmp1" | |
| if constcmp { | |
| fn = "libfuzzerTraceConstCmp1" | |
| } | |
| paramType = types.Types[types.TUINT8] | |
| case 2: | |
| fn = "libfuzzerTraceCmp2" | |
| if constcmp { | |
| fn = "libfuzzerTraceConstCmp2" | |
| } | |
| paramType = types.Types[types.TUINT16] | |
| case 4: | |
| fn = "libfuzzerTraceCmp4" | |
| if constcmp { | |
| fn = "libfuzzerTraceConstCmp4" | |
| } | |
| paramType = types.Types[types.TUINT32] | |
| case 8: | |
| fn = "libfuzzerTraceCmp8" | |
| if constcmp { | |
| fn = "libfuzzerTraceConstCmp8" | |
| } | |
| paramType = types.Types[types.TUINT64] | |
| default: | |
| base.Fatalf("unexpected integer size %d for %v", t.Size(), t) | |
| } | |
| init.Append(mkcall(fn, nil, init, tracecmpArg(l, paramType, init), tracecmpArg(r, paramType, init), fakePC(n))) | |
| } | |
| return n | |
| case types.TARRAY: | |
| // We can compare several elements at once with 2/4/8 byte integer compares | |
| inline = t.NumElem() <= 1 || (types.IsSimple[t.Elem().Kind()] && (t.NumElem() <= 4 || t.Elem().Size()*t.NumElem() <= maxcmpsize)) | |
| case types.TSTRUCT: | |
| inline = compare.EqStructCost(t) <= 4 | |
| } | |
| cmpl := n.X | |
| for cmpl != nil && cmpl.Op() == ir.OCONVNOP { | |
| cmpl = cmpl.(*ir.ConvExpr).X | |
| } | |
| cmpr := n.Y | |
| for cmpr != nil && cmpr.Op() == ir.OCONVNOP { | |
| cmpr = cmpr.(*ir.ConvExpr).X | |
| } | |
| // Chose not to inline. Call equality function directly. | |
| if !inline { | |
| // eq algs take pointers; cmpl and cmpr must be addressable | |
| if !ir.IsAddressable(cmpl) || !ir.IsAddressable(cmpr) { | |
| base.Fatalf("arguments of comparison must be lvalues - %v %v", cmpl, cmpr) | |
| } | |
| // Should only arrive here with large memory or | |
| // a struct/array containing a non-memory field/element. | |
| // Small memory is handled inline, and single non-memory | |
| // is handled by walkCompare. | |
| fn, needsLength := reflectdata.EqFor(t) | |
| call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) | |
| addrCmpL := typecheck.NodAddr(cmpl) | |
| addrCmpR := typecheck.NodAddr(cmpr) | |
| if !types.IsNoRacePkg(types.LocalPkg) && base.Flag.Race { | |
| ptrL := typecheck.Conv(typecheck.Conv(addrCmpL, types.Types[types.TUNSAFEPTR]), types.Types[types.TUINTPTR]) | |
| ptrR := typecheck.Conv(typecheck.Conv(addrCmpR, types.Types[types.TUNSAFEPTR]), types.Types[types.TUINTPTR]) | |
| raceFn := typecheck.LookupRuntime("racereadrange") | |
| size := ir.NewInt(base.Pos, t.Size()) | |
| call.PtrInit().Append(mkcall1(raceFn, nil, init, ptrL, size)) | |
| call.PtrInit().Append(mkcall1(raceFn, nil, init, ptrR, size)) | |
| } | |
| call.Args.Append(typecheck.Conv(addrCmpL, types.Types[types.TUNSAFEPTR])) | |
| call.Args.Append(typecheck.Conv(addrCmpR, types.Types[types.TUNSAFEPTR])) | |
| if needsLength { | |
| call.Args.Append(ir.NewInt(base.Pos, t.Size())) | |
| } | |
| res := ir.Node(call) | |
| if n.Op() != ir.OEQ { | |
| res = ir.NewUnaryExpr(base.Pos, ir.ONOT, res) | |
| } | |
| return finishCompare(n, res, init) | |
| } | |
| // inline: build boolean expression comparing element by element | |
| andor := ir.OANDAND | |
| if n.Op() == ir.ONE { | |
| andor = ir.OOROR | |
| } | |
| var expr ir.Node | |
| comp := func(el, er ir.Node) { | |
| a := ir.NewBinaryExpr(base.Pos, n.Op(), el, er) | |
| if expr == nil { | |
| expr = a | |
| } else { | |
| expr = ir.NewLogicalExpr(base.Pos, andor, expr, a) | |
| } | |
| } | |
| and := func(cond ir.Node) { | |
| if expr == nil { | |
| expr = cond | |
| } else { | |
| expr = ir.NewLogicalExpr(base.Pos, andor, expr, cond) | |
| } | |
| } | |
| cmpl = safeExpr(cmpl, init) | |
| cmpr = safeExpr(cmpr, init) | |
| if t.IsStruct() { | |
| conds, _ := compare.EqStruct(t, cmpl, cmpr) | |
| if n.Op() == ir.OEQ { | |
| for _, cond := range conds { | |
| and(cond) | |
| } | |
| } else { | |
| for _, cond := range conds { | |
| notCond := ir.NewUnaryExpr(base.Pos, ir.ONOT, cond) | |
| and(notCond) | |
| } | |
| } | |
| } else { | |
| step := int64(1) | |
| remains := t.NumElem() * t.Elem().Size() | |
| combine64bit := unalignedLoad && types.RegSize == 8 && t.Elem().Size() <= 4 && t.Elem().IsInteger() | |
| combine32bit := unalignedLoad && t.Elem().Size() <= 2 && t.Elem().IsInteger() | |
| combine16bit := unalignedLoad && t.Elem().Size() == 1 && t.Elem().IsInteger() | |
| for i := int64(0); remains > 0; { | |
| var convType *types.Type | |
| switch { | |
| case remains >= 8 && combine64bit: | |
| convType = types.Types[types.TINT64] | |
| step = 8 / t.Elem().Size() | |
| case remains >= 4 && combine32bit: | |
| convType = types.Types[types.TUINT32] | |
| step = 4 / t.Elem().Size() | |
| case remains >= 2 && combine16bit: | |
| convType = types.Types[types.TUINT16] | |
| step = 2 / t.Elem().Size() | |
| default: | |
| step = 1 | |
| } | |
| if step == 1 { | |
| comp( | |
| ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(base.Pos, i)), | |
| ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(base.Pos, i)), | |
| ) | |
| i++ | |
| remains -= t.Elem().Size() | |
| } else { | |
| elemType := t.Elem().ToUnsigned() | |
| cmplw := ir.Node(ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(base.Pos, i))) | |
| cmplw = typecheck.Conv(cmplw, elemType) // convert to unsigned | |
| cmplw = typecheck.Conv(cmplw, convType) // widen | |
| cmprw := ir.Node(ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(base.Pos, i))) | |
| cmprw = typecheck.Conv(cmprw, elemType) | |
| cmprw = typecheck.Conv(cmprw, convType) | |
| // For code like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... | |
| // ssa will generate a single large load. | |
| for offset := int64(1); offset < step; offset++ { | |
| lb := ir.Node(ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(base.Pos, i+offset))) | |
| lb = typecheck.Conv(lb, elemType) | |
| lb = typecheck.Conv(lb, convType) | |
| lb = ir.NewBinaryExpr(base.Pos, ir.OLSH, lb, ir.NewInt(base.Pos, 8*t.Elem().Size()*offset)) | |
| cmplw = ir.NewBinaryExpr(base.Pos, ir.OOR, cmplw, lb) | |
| rb := ir.Node(ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(base.Pos, i+offset))) | |
| rb = typecheck.Conv(rb, elemType) | |
| rb = typecheck.Conv(rb, convType) | |
| rb = ir.NewBinaryExpr(base.Pos, ir.OLSH, rb, ir.NewInt(base.Pos, 8*t.Elem().Size()*offset)) | |
| cmprw = ir.NewBinaryExpr(base.Pos, ir.OOR, cmprw, rb) | |
| } | |
| comp(cmplw, cmprw) | |
| i += step | |
| remains -= step * t.Elem().Size() | |
| } | |
| } | |
| } | |
| if expr == nil { | |
| expr = ir.NewBool(base.Pos, n.Op() == ir.OEQ) | |
| // We still need to use cmpl and cmpr, in case they contain | |
| // an expression which might panic. See issue 23837. | |
| a1 := typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.BlankNode, cmpl)) | |
| a2 := typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.BlankNode, cmpr)) | |
| init.Append(a1, a2) | |
| } | |
| return finishCompare(n, expr, init) | |
| } | |
| func walkCompareInterface(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { | |
| swap := n.X.Op() != ir.OCONVIFACE && n.Y.Op() == ir.OCONVIFACE | |
| n.Y = cheapExpr(n.Y, init) | |
| n.X = cheapExpr(n.X, init) | |
| if swap { | |
| // Put the concrete type first in the comparison. | |
| // This passes a constant type (itab) to efaceeq (ifaceeq) | |
| // which is easier to match against in rewrite rules. | |
| // See issue 70738. | |
| n.X, n.Y = n.Y, n.X | |
| } | |
| eqtab, eqdata := compare.EqInterface(n.X, n.Y) | |
| var cmp ir.Node | |
| if n.Op() == ir.OEQ { | |
| cmp = ir.NewLogicalExpr(base.Pos, ir.OANDAND, eqtab, eqdata) | |
| } else { | |
| eqtab.SetOp(ir.ONE) | |
| cmp = ir.NewLogicalExpr(base.Pos, ir.OOROR, eqtab, ir.NewUnaryExpr(base.Pos, ir.ONOT, eqdata)) | |
| } | |
| return finishCompare(n, cmp, init) | |
| } | |
| func walkCompareString(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { | |
| if base.Debug.Libfuzzer != 0 { | |
| if !ir.IsConst(n.X, constant.String) || !ir.IsConst(n.Y, constant.String) { | |
| fn := "libfuzzerHookStrCmp" | |
| n.X = cheapExpr(n.X, init) | |
| n.Y = cheapExpr(n.Y, init) | |
| paramType := types.Types[types.TSTRING] | |
| init.Append(mkcall(fn, nil, init, tracecmpArg(n.X, paramType, init), tracecmpArg(n.Y, paramType, init), fakePC(n))) | |
| } | |
| } | |
| // Rewrite comparisons to short constant strings as length+byte-wise comparisons. | |
| var cs, ncs ir.Node // const string, non-const string | |
| switch { | |
| case ir.IsConst(n.X, constant.String) && ir.IsConst(n.Y, constant.String): | |
| // ignore; will be constant evaluated | |
| case ir.IsConst(n.X, constant.String): | |
| cs = n.X | |
| ncs = n.Y | |
| case ir.IsConst(n.Y, constant.String): | |
| cs = n.Y | |
| ncs = n.X | |
| } | |
| if cs != nil { | |
| cmp := n.Op() | |
| // Our comparison below assumes that the non-constant string | |
| // is on the left hand side, so rewrite "" cmp x to x cmp "". | |
| // See issue 24817. | |
| if ir.IsConst(n.X, constant.String) { | |
| cmp = brrev(cmp) | |
| } | |
| // maxRewriteLen was chosen empirically. | |
| // It is the value that minimizes cmd/go file size | |
| // across most architectures. | |
| // See the commit description for CL 26758 for details. | |
| maxRewriteLen := 6 | |
| // Some architectures can load unaligned byte sequence as 1 word. | |
| // So we can cover longer strings with the same amount of code. | |
| canCombineLoads := ssagen.Arch.LinkArch.CanMergeLoads | |
| combine64bit := false | |
| if canCombineLoads { | |
| // Keep this low enough to generate less code than a function call. | |
| maxRewriteLen = 2 * ssagen.Arch.LinkArch.RegSize | |
| combine64bit = ssagen.Arch.LinkArch.RegSize >= 8 | |
| } | |
| var and ir.Op | |
| switch cmp { | |
| case ir.OEQ: | |
| and = ir.OANDAND | |
| case ir.ONE: | |
| and = ir.OOROR | |
| default: | |
| // Don't do byte-wise comparisons for <, <=, etc. | |
| // They're fairly complicated. | |
| // Length-only checks are ok, though. | |
| maxRewriteLen = 0 | |
| } | |
| if s := ir.StringVal(cs); len(s) <= maxRewriteLen { | |
| if len(s) > 0 { | |
| ncs = safeExpr(ncs, init) | |
| } | |
| r := ir.Node(ir.NewBinaryExpr(base.Pos, cmp, ir.NewUnaryExpr(base.Pos, ir.OLEN, ncs), ir.NewInt(base.Pos, int64(len(s))))) | |
| remains := len(s) | |
| for i := 0; remains > 0; { | |
| if remains == 1 || !canCombineLoads { | |
| cb := ir.NewInt(base.Pos, int64(s[i])) | |
| ncb := ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(base.Pos, int64(i))) | |
| r = ir.NewLogicalExpr(base.Pos, and, r, ir.NewBinaryExpr(base.Pos, cmp, ncb, cb)) | |
| remains-- | |
| i++ | |
| continue | |
| } | |
| var step int | |
| var convType *types.Type | |
| switch { | |
| case remains >= 8 && combine64bit: | |
| convType = types.Types[types.TINT64] | |
| step = 8 | |
| case remains >= 4: | |
| convType = types.Types[types.TUINT32] | |
| step = 4 | |
| case remains >= 2: | |
| convType = types.Types[types.TUINT16] | |
| step = 2 | |
| } | |
| ncsubstr := typecheck.Conv(ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(base.Pos, int64(i))), convType) | |
| csubstr := int64(s[i]) | |
| // Calculate large constant from bytes as sequence of shifts and ors. | |
| // Like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... | |
| // ssa will combine this into a single large load. | |
| for offset := 1; offset < step; offset++ { | |
| b := typecheck.Conv(ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(base.Pos, int64(i+offset))), convType) | |
| b = ir.NewBinaryExpr(base.Pos, ir.OLSH, b, ir.NewInt(base.Pos, int64(8*offset))) | |
| ncsubstr = ir.NewBinaryExpr(base.Pos, ir.OOR, ncsubstr, b) | |
| csubstr |= int64(s[i+offset]) << uint8(8*offset) | |
| } | |
| csubstrPart := ir.NewInt(base.Pos, csubstr) | |
| // Compare "step" bytes as once | |
| r = ir.NewLogicalExpr(base.Pos, and, r, ir.NewBinaryExpr(base.Pos, cmp, csubstrPart, ncsubstr)) | |
| remains -= step | |
| i += step | |
| } | |
| return finishCompare(n, r, init) | |
| } | |
| } | |
| var r ir.Node | |
| if n.Op() == ir.OEQ || n.Op() == ir.ONE { | |
| // prepare for rewrite below | |
| n.X = cheapExpr(n.X, init) | |
| n.Y = cheapExpr(n.Y, init) | |
| eqlen, eqmem := compare.EqString(n.X, n.Y) | |
| // quick check of len before full compare for == or !=. | |
| // memequal then tests equality up to length len. | |
| if n.Op() == ir.OEQ { | |
| // len(left) == len(right) && memequal(left, right, len) | |
| r = ir.NewLogicalExpr(base.Pos, ir.OANDAND, eqlen, eqmem) | |
| } else { | |
| // len(left) != len(right) || !memequal(left, right, len) | |
| eqlen.SetOp(ir.ONE) | |
| r = ir.NewLogicalExpr(base.Pos, ir.OOROR, eqlen, ir.NewUnaryExpr(base.Pos, ir.ONOT, eqmem)) | |
| } | |
| } else { | |
| // sys_cmpstring(s1, s2) :: 0 | |
| r = mkcall("cmpstring", types.Types[types.TINT], init, typecheck.Conv(n.X, types.Types[types.TSTRING]), typecheck.Conv(n.Y, types.Types[types.TSTRING])) | |
| r = ir.NewBinaryExpr(base.Pos, n.Op(), r, ir.NewInt(base.Pos, 0)) | |
| } | |
| return finishCompare(n, r, init) | |
| } | |
| // The result of finishCompare MUST be assigned back to n, e.g. | |
| // | |
| // n.Left = finishCompare(n.Left, x, r, init) | |
| func finishCompare(n *ir.BinaryExpr, r ir.Node, init *ir.Nodes) ir.Node { | |
| r = typecheck.Expr(r) | |
| r = typecheck.Conv(r, n.Type()) | |
| r = walkExpr(r, init) | |
| return r | |
| } | |
| // brcom returns !(op). | |
| // For example, brcom(==) is !=. | |
| func brcom(op ir.Op) ir.Op { | |
| switch op { | |
| case ir.OEQ: | |
| return ir.ONE | |
| case ir.ONE: | |
| return ir.OEQ | |
| case ir.OLT: | |
| return ir.OGE | |
| case ir.OGT: | |
| return ir.OLE | |
| case ir.OLE: | |
| return ir.OGT | |
| case ir.OGE: | |
| return ir.OLT | |
| } | |
| base.Fatalf("brcom: no com for %v\n", op) | |
| return op | |
| } | |
| // brrev returns reverse(op). | |
| // For example, Brrev(<) is >. | |
| func brrev(op ir.Op) ir.Op { | |
| switch op { | |
| case ir.OEQ: | |
| return ir.OEQ | |
| case ir.ONE: | |
| return ir.ONE | |
| case ir.OLT: | |
| return ir.OGT | |
| case ir.OGT: | |
| return ir.OLT | |
| case ir.OLE: | |
| return ir.OGE | |
| case ir.OGE: | |
| return ir.OLE | |
| } | |
| base.Fatalf("brrev: no rev for %v\n", op) | |
| return op | |
| } | |
| func tracecmpArg(n ir.Node, t *types.Type, init *ir.Nodes) ir.Node { | |
| // Ugly hack to avoid "constant -1 overflows uintptr" errors, etc. | |
| if n.Op() == ir.OLITERAL && n.Type().IsSigned() && ir.Int64Val(n) < 0 { | |
| n = copyExpr(n, n.Type(), init) | |
| } | |
| return typecheck.Conv(n, t) | |
| } |