github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/walk/convert.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package walk 6 7 import ( 8 "encoding/binary" 9 "go/constant" 10 11 "github.com/go-asm/go/cmd/compile/base" 12 "github.com/go-asm/go/cmd/compile/ir" 13 "github.com/go-asm/go/cmd/compile/reflectdata" 14 "github.com/go-asm/go/cmd/compile/ssagen" 15 "github.com/go-asm/go/cmd/compile/typecheck" 16 "github.com/go-asm/go/cmd/compile/types" 17 "github.com/go-asm/go/cmd/sys" 18 ) 19 20 // walkConv walks an OCONV or OCONVNOP (but not OCONVIFACE) node. 21 func walkConv(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 22 n.X = walkExpr(n.X, init) 23 if n.Op() == ir.OCONVNOP && n.Type() == n.X.Type() { 24 return n.X 25 } 26 if n.Op() == ir.OCONVNOP && ir.ShouldCheckPtr(ir.CurFunc, 1) { 27 if n.Type().IsUnsafePtr() && n.X.Type().IsUintptr() { // uintptr to unsafe.Pointer 28 return walkCheckPtrArithmetic(n, init) 29 } 30 } 31 param, result := rtconvfn(n.X.Type(), n.Type()) 32 if param == types.Txxx { 33 return n 34 } 35 fn := types.BasicTypeNames[param] + "to" + types.BasicTypeNames[result] 36 return typecheck.Conv(mkcall(fn, types.Types[result], init, typecheck.Conv(n.X, types.Types[param])), n.Type()) 37 } 38 39 // walkConvInterface walks an OCONVIFACE node. 40 func walkConvInterface(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 41 42 n.X = walkExpr(n.X, init) 43 44 fromType := n.X.Type() 45 toType := n.Type() 46 if !fromType.IsInterface() && !ir.IsBlank(ir.CurFunc.Nname) { 47 // skip unnamed functions (func _()) 48 if fromType.HasShape() { 49 // Unified IR uses OCONVIFACE for converting all derived types 50 // to interface type. Avoid assertion failure in 51 // MarkTypeUsedInInterface, because we've marked used types 52 // separately anyway. 53 } else { 54 reflectdata.MarkTypeUsedInInterface(fromType, ir.CurFunc.LSym) 55 } 56 } 57 58 if !fromType.IsInterface() { 59 typeWord := reflectdata.ConvIfaceTypeWord(base.Pos, n) 60 l := ir.NewBinaryExpr(base.Pos, ir.OMAKEFACE, typeWord, dataWord(n, init)) 61 l.SetType(toType) 62 l.SetTypecheck(n.Typecheck()) 63 return l 64 } 65 if fromType.IsEmptyInterface() { 66 base.Fatalf("OCONVIFACE can't operate on an empty interface") 67 } 68 69 // Evaluate the input interface. 70 c := typecheck.TempAt(base.Pos, ir.CurFunc, fromType) 71 init.Append(ir.NewAssignStmt(base.Pos, c, n.X)) 72 73 if toType.IsEmptyInterface() { 74 // Implement interface to empty interface conversion: 75 // 76 // var res *uint8 77 // res = (*uint8)(unsafe.Pointer(itab)) 78 // if res != nil { 79 // res = res.type 80 // } 81 82 // Grab its parts. 83 itab := ir.NewUnaryExpr(base.Pos, ir.OITAB, c) 84 itab.SetType(types.Types[types.TUINTPTR].PtrTo()) 85 itab.SetTypecheck(1) 86 data := ir.NewUnaryExpr(n.Pos(), ir.OIDATA, c) 87 data.SetType(types.Types[types.TUINT8].PtrTo()) // Type is generic pointer - we're just passing it through. 88 data.SetTypecheck(1) 89 90 typeWord := typecheck.TempAt(base.Pos, ir.CurFunc, types.NewPtr(types.Types[types.TUINT8])) 91 init.Append(ir.NewAssignStmt(base.Pos, typeWord, typecheck.Conv(typecheck.Conv(itab, types.Types[types.TUNSAFEPTR]), typeWord.Type()))) 92 nif := ir.NewIfStmt(base.Pos, typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.ONE, typeWord, typecheck.NodNil())), nil, nil) 93 nif.Body = []ir.Node{ir.NewAssignStmt(base.Pos, typeWord, itabType(typeWord))} 94 init.Append(nif) 95 96 // Build the result. 97 // e = iface{typeWord, data} 98 e := ir.NewBinaryExpr(base.Pos, ir.OMAKEFACE, typeWord, data) 99 e.SetType(toType) // assign type manually, typecheck doesn't understand OEFACE. 100 e.SetTypecheck(1) 101 return e 102 } 103 104 // Must be converting I2I (more specific to less specific interface). 105 // Use the same code as e, _ = c.(T). 106 var rhs ir.Node 107 if n.TypeWord == nil || n.TypeWord.Op() == ir.OADDR && n.TypeWord.(*ir.AddrExpr).X.Op() == ir.OLINKSYMOFFSET { 108 // Fixed (not loaded from a dictionary) type. 109 ta := ir.NewTypeAssertExpr(base.Pos, c, toType) 110 ta.SetOp(ir.ODOTTYPE2) 111 // Allocate a descriptor for this conversion to pass to the runtime. 112 ta.Descriptor = makeTypeAssertDescriptor(toType, true) 113 rhs = ta 114 } else { 115 ta := ir.NewDynamicTypeAssertExpr(base.Pos, ir.ODYNAMICDOTTYPE2, c, n.TypeWord) 116 rhs = ta 117 } 118 rhs.SetType(toType) 119 rhs.SetTypecheck(1) 120 121 res := typecheck.TempAt(base.Pos, ir.CurFunc, toType) 122 as := ir.NewAssignListStmt(base.Pos, ir.OAS2DOTTYPE, []ir.Node{res, ir.BlankNode}, []ir.Node{rhs}) 123 init.Append(as) 124 return res 125 } 126 127 // Returns the data word (the second word) used to represent conv.X in 128 // an interface. 129 func dataWord(conv *ir.ConvExpr, init *ir.Nodes) ir.Node { 130 pos, n := conv.Pos(), conv.X 131 fromType := n.Type() 132 133 // If it's a pointer, it is its own representation. 134 if types.IsDirectIface(fromType) { 135 return n 136 } 137 138 isInteger := fromType.IsInteger() 139 isBool := fromType.IsBoolean() 140 if sc := fromType.SoleComponent(); sc != nil { 141 isInteger = sc.IsInteger() 142 isBool = sc.IsBoolean() 143 } 144 // Try a bunch of cases to avoid an allocation. 145 var value ir.Node 146 switch { 147 case fromType.Size() == 0: 148 // n is zero-sized. Use zerobase. 149 cheapExpr(n, init) // Evaluate n for side-effects. See issue 19246. 150 value = ir.NewLinksymExpr(base.Pos, ir.Syms.Zerobase, types.Types[types.TUINTPTR]) 151 case isBool || fromType.Size() == 1 && isInteger: 152 // n is a bool/byte. Use staticuint64s[n * 8] on little-endian 153 // and staticuint64s[n * 8 + 7] on big-endian. 154 n = cheapExpr(n, init) 155 n = soleComponent(init, n) 156 // byteindex widens n so that the multiplication doesn't overflow. 157 index := ir.NewBinaryExpr(base.Pos, ir.OLSH, byteindex(n), ir.NewInt(base.Pos, 3)) 158 if ssagen.Arch.LinkArch.ByteOrder == binary.BigEndian { 159 index = ir.NewBinaryExpr(base.Pos, ir.OADD, index, ir.NewInt(base.Pos, 7)) 160 } 161 // The actual type is [256]uint64, but we use [256*8]uint8 so we can address 162 // individual bytes. 163 staticuint64s := ir.NewLinksymExpr(base.Pos, ir.Syms.Staticuint64s, types.NewArray(types.Types[types.TUINT8], 256*8)) 164 xe := ir.NewIndexExpr(base.Pos, staticuint64s, index) 165 xe.SetBounded(true) 166 value = xe 167 case n.Op() == ir.ONAME && n.(*ir.Name).Class == ir.PEXTERN && n.(*ir.Name).Readonly(): 168 // n is a readonly global; use it directly. 169 value = n 170 case conv.Esc() == ir.EscNone && fromType.Size() <= 1024: 171 // n does not escape. Use a stack temporary initialized to n. 172 value = typecheck.TempAt(base.Pos, ir.CurFunc, fromType) 173 init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, value, n))) 174 } 175 if value != nil { 176 // The interface data word is &value. 177 return typecheck.Expr(typecheck.NodAddr(value)) 178 } 179 180 // Time to do an allocation. We'll call into the runtime for that. 181 fnname, argType, needsaddr := dataWordFuncName(fromType) 182 var fn *ir.Name 183 184 var args []ir.Node 185 if needsaddr { 186 // Types of large or unknown size are passed by reference. 187 // Orderexpr arranged for n to be a temporary for all 188 // the conversions it could see. Comparison of an interface 189 // with a non-interface, especially in a switch on interface value 190 // with non-interface cases, is not visible to order.stmt, so we 191 // have to fall back on allocating a temp here. 192 if !ir.IsAddressable(n) { 193 n = copyExpr(n, fromType, init) 194 } 195 fn = typecheck.LookupRuntime(fnname, fromType) 196 args = []ir.Node{reflectdata.ConvIfaceSrcRType(base.Pos, conv), typecheck.NodAddr(n)} 197 } else { 198 // Use a specialized conversion routine that takes the type being 199 // converted by value, not by pointer. 200 fn = typecheck.LookupRuntime(fnname) 201 var arg ir.Node 202 switch { 203 case fromType == argType: 204 // already in the right type, nothing to do 205 arg = n 206 case fromType.Kind() == argType.Kind(), 207 fromType.IsPtrShaped() && argType.IsPtrShaped(): 208 // can directly convert (e.g. named type to underlying type, or one pointer to another) 209 // TODO: never happens because pointers are directIface? 210 arg = ir.NewConvExpr(pos, ir.OCONVNOP, argType, n) 211 case fromType.IsInteger() && argType.IsInteger(): 212 // can directly convert (e.g. int32 to uint32) 213 arg = ir.NewConvExpr(pos, ir.OCONV, argType, n) 214 default: 215 // unsafe cast through memory 216 arg = copyExpr(n, fromType, init) 217 var addr ir.Node = typecheck.NodAddr(arg) 218 addr = ir.NewConvExpr(pos, ir.OCONVNOP, argType.PtrTo(), addr) 219 arg = ir.NewStarExpr(pos, addr) 220 arg.SetType(argType) 221 } 222 args = []ir.Node{arg} 223 } 224 call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) 225 call.Args = args 226 return safeExpr(walkExpr(typecheck.Expr(call), init), init) 227 } 228 229 // walkBytesRunesToString walks an OBYTES2STR or ORUNES2STR node. 230 func walkBytesRunesToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 231 a := typecheck.NodNil() 232 if n.Esc() == ir.EscNone { 233 // Create temporary buffer for string on stack. 234 a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8]) 235 } 236 if n.Op() == ir.ORUNES2STR { 237 // slicerunetostring(*[32]byte, []rune) string 238 return mkcall("slicerunetostring", n.Type(), init, a, n.X) 239 } 240 // slicebytetostring(*[32]byte, ptr *byte, n int) string 241 n.X = cheapExpr(n.X, init) 242 ptr, len := backingArrayPtrLen(n.X) 243 return mkcall("slicebytetostring", n.Type(), init, a, ptr, len) 244 } 245 246 // walkBytesToStringTemp walks an OBYTES2STRTMP node. 247 func walkBytesToStringTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 248 n.X = walkExpr(n.X, init) 249 if !base.Flag.Cfg.Instrumenting { 250 // Let the backend handle OBYTES2STRTMP directly 251 // to avoid a function call to slicebytetostringtmp. 252 return n 253 } 254 // slicebytetostringtmp(ptr *byte, n int) string 255 n.X = cheapExpr(n.X, init) 256 ptr, len := backingArrayPtrLen(n.X) 257 return mkcall("slicebytetostringtmp", n.Type(), init, ptr, len) 258 } 259 260 // walkRuneToString walks an ORUNESTR node. 261 func walkRuneToString(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 262 a := typecheck.NodNil() 263 if n.Esc() == ir.EscNone { 264 a = stackBufAddr(4, types.Types[types.TUINT8]) 265 } 266 // intstring(*[4]byte, rune) 267 return mkcall("intstring", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TINT64])) 268 } 269 270 // walkStringToBytes walks an OSTR2BYTES node. 271 func walkStringToBytes(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 272 s := n.X 273 if ir.IsConst(s, constant.String) { 274 sc := ir.StringVal(s) 275 276 // Allocate a [n]byte of the right size. 277 t := types.NewArray(types.Types[types.TUINT8], int64(len(sc))) 278 var a ir.Node 279 if n.Esc() == ir.EscNone && len(sc) <= int(ir.MaxImplicitStackVarSize) { 280 a = stackBufAddr(t.NumElem(), t.Elem()) 281 } else { 282 types.CalcSize(t) 283 a = ir.NewUnaryExpr(base.Pos, ir.ONEW, nil) 284 a.SetType(types.NewPtr(t)) 285 a.SetTypecheck(1) 286 a.MarkNonNil() 287 } 288 p := typecheck.TempAt(base.Pos, ir.CurFunc, t.PtrTo()) // *[n]byte 289 init.Append(typecheck.Stmt(ir.NewAssignStmt(base.Pos, p, a))) 290 291 // Copy from the static string data to the [n]byte. 292 if len(sc) > 0 { 293 sptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, s) 294 sptr.SetBounded(true) 295 as := ir.NewAssignStmt(base.Pos, ir.NewStarExpr(base.Pos, p), ir.NewStarExpr(base.Pos, typecheck.ConvNop(sptr, t.PtrTo()))) 296 appendWalkStmt(init, as) 297 } 298 299 // Slice the [n]byte to a []byte. 300 slice := ir.NewSliceExpr(n.Pos(), ir.OSLICEARR, p, nil, nil, nil) 301 slice.SetType(n.Type()) 302 slice.SetTypecheck(1) 303 return walkExpr(slice, init) 304 } 305 306 a := typecheck.NodNil() 307 if n.Esc() == ir.EscNone { 308 // Create temporary buffer for slice on stack. 309 a = stackBufAddr(tmpstringbufsize, types.Types[types.TUINT8]) 310 } 311 // stringtoslicebyte(*32[byte], string) []byte 312 return mkcall("stringtoslicebyte", n.Type(), init, a, typecheck.Conv(s, types.Types[types.TSTRING])) 313 } 314 315 // walkStringToBytesTemp walks an OSTR2BYTESTMP node. 316 func walkStringToBytesTemp(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 317 // []byte(string) conversion that creates a slice 318 // referring to the actual string bytes. 319 // This conversion is handled later by the backend and 320 // is only for use by internal compiler optimizations 321 // that know that the slice won't be mutated. 322 // The only such case today is: 323 // for i, c := range []byte(string) 324 n.X = walkExpr(n.X, init) 325 return n 326 } 327 328 // walkStringToRunes walks an OSTR2RUNES node. 329 func walkStringToRunes(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 330 a := typecheck.NodNil() 331 if n.Esc() == ir.EscNone { 332 // Create temporary buffer for slice on stack. 333 a = stackBufAddr(tmpstringbufsize, types.Types[types.TINT32]) 334 } 335 // stringtoslicerune(*[32]rune, string) []rune 336 return mkcall("stringtoslicerune", n.Type(), init, a, typecheck.Conv(n.X, types.Types[types.TSTRING])) 337 } 338 339 // dataWordFuncName returns the name of the function used to convert a value of type "from" 340 // to the data word of an interface. 341 // argType is the type the argument needs to be coerced to. 342 // needsaddr reports whether the value should be passed (needaddr==false) or its address (needsaddr==true). 343 func dataWordFuncName(from *types.Type) (fnname string, argType *types.Type, needsaddr bool) { 344 if from.IsInterface() { 345 base.Fatalf("can only handle non-interfaces") 346 } 347 switch { 348 case from.Size() == 2 && uint8(from.Alignment()) == 2: 349 return "convT16", types.Types[types.TUINT16], false 350 case from.Size() == 4 && uint8(from.Alignment()) == 4 && !from.HasPointers(): 351 return "convT32", types.Types[types.TUINT32], false 352 case from.Size() == 8 && uint8(from.Alignment()) == uint8(types.Types[types.TUINT64].Alignment()) && !from.HasPointers(): 353 return "convT64", types.Types[types.TUINT64], false 354 } 355 if sc := from.SoleComponent(); sc != nil { 356 switch { 357 case sc.IsString(): 358 return "convTstring", types.Types[types.TSTRING], false 359 case sc.IsSlice(): 360 return "convTslice", types.NewSlice(types.Types[types.TUINT8]), false // the element type doesn't matter 361 } 362 } 363 364 if from.HasPointers() { 365 return "convT", types.Types[types.TUNSAFEPTR], true 366 } 367 return "convTnoptr", types.Types[types.TUNSAFEPTR], true 368 } 369 370 // rtconvfn returns the parameter and result types that will be used by a 371 // runtime function to convert from type src to type dst. The runtime function 372 // name can be derived from the names of the returned types. 373 // 374 // If no such function is necessary, it returns (Txxx, Txxx). 375 func rtconvfn(src, dst *types.Type) (param, result types.Kind) { 376 if ssagen.Arch.SoftFloat { 377 return types.Txxx, types.Txxx 378 } 379 380 switch ssagen.Arch.LinkArch.Family { 381 case sys.ARM, sys.MIPS: 382 if src.IsFloat() { 383 switch dst.Kind() { 384 case types.TINT64, types.TUINT64: 385 return types.TFLOAT64, dst.Kind() 386 } 387 } 388 if dst.IsFloat() { 389 switch src.Kind() { 390 case types.TINT64, types.TUINT64: 391 return src.Kind(), dst.Kind() 392 } 393 } 394 395 case sys.I386: 396 if src.IsFloat() { 397 switch dst.Kind() { 398 case types.TINT64, types.TUINT64: 399 return types.TFLOAT64, dst.Kind() 400 case types.TUINT32, types.TUINT, types.TUINTPTR: 401 return types.TFLOAT64, types.TUINT32 402 } 403 } 404 if dst.IsFloat() { 405 switch src.Kind() { 406 case types.TINT64, types.TUINT64: 407 return src.Kind(), dst.Kind() 408 case types.TUINT32, types.TUINT, types.TUINTPTR: 409 return types.TUINT32, types.TFLOAT64 410 } 411 } 412 } 413 return types.Txxx, types.Txxx 414 } 415 416 func soleComponent(init *ir.Nodes, n ir.Node) ir.Node { 417 if n.Type().SoleComponent() == nil { 418 return n 419 } 420 // Keep in sync with github.com/go-asm/go/cmd/compile/types/type.go:Type.SoleComponent. 421 for { 422 switch { 423 case n.Type().IsStruct(): 424 if n.Type().Field(0).Sym.IsBlank() { 425 // Treat blank fields as the zero value as the Go language requires. 426 n = typecheck.TempAt(base.Pos, ir.CurFunc, n.Type().Field(0).Type) 427 appendWalkStmt(init, ir.NewAssignStmt(base.Pos, n, nil)) 428 continue 429 } 430 n = typecheck.DotField(n.Pos(), n, 0) 431 case n.Type().IsArray(): 432 n = typecheck.Expr(ir.NewIndexExpr(n.Pos(), n, ir.NewInt(base.Pos, 0))) 433 default: 434 return n 435 } 436 } 437 } 438 439 // byteindex converts n, which is byte-sized, to an int used to index into an array. 440 // We cannot use conv, because we allow converting bool to int here, 441 // which is forbidden in user code. 442 func byteindex(n ir.Node) ir.Node { 443 // We cannot convert from bool to int directly. 444 // While converting from int8 to int is possible, it would yield 445 // the wrong result for negative values. 446 // Reinterpreting the value as an unsigned byte solves both cases. 447 if !types.Identical(n.Type(), types.Types[types.TUINT8]) { 448 n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) 449 n.SetType(types.Types[types.TUINT8]) 450 n.SetTypecheck(1) 451 } 452 n = ir.NewConvExpr(base.Pos, ir.OCONV, nil, n) 453 n.SetType(types.Types[types.TINT]) 454 n.SetTypecheck(1) 455 return n 456 } 457 458 func walkCheckPtrArithmetic(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 459 // Calling cheapExpr(n, init) below leads to a recursive call to 460 // walkExpr, which leads us back here again. Use n.Checkptr to 461 // prevent infinite loops. 462 if n.CheckPtr() { 463 return n 464 } 465 n.SetCheckPtr(true) 466 defer n.SetCheckPtr(false) 467 468 // TODO(mdempsky): Make stricter. We only need to exempt 469 // reflect.Value.Pointer and reflect.Value.UnsafeAddr. 470 switch n.X.Op() { 471 case ir.OCALLMETH: 472 base.FatalfAt(n.X.Pos(), "OCALLMETH missed by typecheck") 473 case ir.OCALLFUNC, ir.OCALLINTER: 474 return n 475 } 476 477 if n.X.Op() == ir.ODOTPTR && ir.IsReflectHeaderDataField(n.X) { 478 return n 479 } 480 481 // Find original unsafe.Pointer operands involved in this 482 // arithmetic expression. 483 // 484 // "It is valid both to add and to subtract offsets from a 485 // pointer in this way. It is also valid to use &^ to round 486 // pointers, usually for alignment." 487 var originals []ir.Node 488 var walk func(n ir.Node) 489 walk = func(n ir.Node) { 490 switch n.Op() { 491 case ir.OADD: 492 n := n.(*ir.BinaryExpr) 493 walk(n.X) 494 walk(n.Y) 495 case ir.OSUB, ir.OANDNOT: 496 n := n.(*ir.BinaryExpr) 497 walk(n.X) 498 case ir.OCONVNOP: 499 n := n.(*ir.ConvExpr) 500 if n.X.Type().IsUnsafePtr() { 501 n.X = cheapExpr(n.X, init) 502 originals = append(originals, typecheck.ConvNop(n.X, types.Types[types.TUNSAFEPTR])) 503 } 504 } 505 } 506 walk(n.X) 507 508 cheap := cheapExpr(n, init) 509 510 slice := typecheck.MakeDotArgs(base.Pos, types.NewSlice(types.Types[types.TUNSAFEPTR]), originals) 511 slice.SetEsc(ir.EscNone) 512 513 init.Append(mkcall("checkptrArithmetic", nil, init, typecheck.ConvNop(cheap, types.Types[types.TUNSAFEPTR]), slice)) 514 // TODO(khr): Mark backing store of slice as dead. This will allow us to reuse 515 // the backing store for multiple calls to checkptrArithmetic. 516 517 return cheap 518 } 519 520 // walkSliceToArray walks an OSLICE2ARR expression. 521 func walkSliceToArray(n *ir.ConvExpr, init *ir.Nodes) ir.Node { 522 // Replace T(x) with *(*T)(x). 523 conv := typecheck.Expr(ir.NewConvExpr(base.Pos, ir.OCONV, types.NewPtr(n.Type()), n.X)).(*ir.ConvExpr) 524 deref := typecheck.Expr(ir.NewStarExpr(base.Pos, conv)).(*ir.StarExpr) 525 526 // The OSLICE2ARRPTR conversion handles checking the slice length, 527 // so the dereference can't fail. 528 // 529 // However, this is more than just an optimization: if T is a 530 // zero-length array, then x (and thus (*T)(x)) can be nil, but T(x) 531 // should *not* panic. So suppressing the nil check here is 532 // necessary for correctness in that case. 533 deref.SetBounded(true) 534 535 return walkExpr(deref, init) 536 }