github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/walk/compare.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 "fmt" 10 "github.com/bir3/gocompiler/src/go/constant" 11 "hash/fnv" 12 "io" 13 14 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 15 "github.com/bir3/gocompiler/src/cmd/compile/internal/compare" 16 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 17 "github.com/bir3/gocompiler/src/cmd/compile/internal/reflectdata" 18 "github.com/bir3/gocompiler/src/cmd/compile/internal/ssagen" 19 "github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck" 20 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 21 ) 22 23 func fakePC(n ir.Node) ir.Node { 24 // In order to get deterministic IDs, we include the package path, absolute filename, line number, column number 25 // in the calculation of the fakePC for the IR node. 26 hash := fnv.New32() 27 // We ignore the errors here because the `io.Writer` in the `hash.Hash` interface never returns an error. 28 io.WriteString(hash, base.Ctxt.Pkgpath) 29 io.WriteString(hash, base.Ctxt.PosTable.Pos(n.Pos()).AbsFilename()) 30 binary.Write(hash, binary.LittleEndian, int64(n.Pos().Line())) 31 binary.Write(hash, binary.LittleEndian, int64(n.Pos().Col())) 32 // We also include the string representation of the node to distinguish autogenerated expression since 33 // those get the same `src.XPos` 34 io.WriteString(hash, fmt.Sprintf("%v", n)) 35 36 return ir.NewInt(int64(hash.Sum32())) 37 } 38 39 // The result of walkCompare MUST be assigned back to n, e.g. 40 // 41 // n.Left = walkCompare(n.Left, init) 42 func walkCompare(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { 43 if n.X.Type().IsInterface() && n.Y.Type().IsInterface() && n.X.Op() != ir.ONIL && n.Y.Op() != ir.ONIL { 44 return walkCompareInterface(n, init) 45 } 46 47 if n.X.Type().IsString() && n.Y.Type().IsString() { 48 return walkCompareString(n, init) 49 } 50 51 n.X = walkExpr(n.X, init) 52 n.Y = walkExpr(n.Y, init) 53 54 // Given mixed interface/concrete comparison, 55 // rewrite into types-equal && data-equal. 56 // This is efficient, avoids allocations, and avoids runtime calls. 57 // 58 // TODO(mdempsky): It would be more general and probably overall 59 // simpler to just extend walkCompareInterface to optimize when one 60 // operand is an OCONVIFACE. 61 if n.X.Type().IsInterface() != n.Y.Type().IsInterface() { 62 // Preserve side-effects in case of short-circuiting; see #32187. 63 l := cheapExpr(n.X, init) 64 r := cheapExpr(n.Y, init) 65 // Swap so that l is the interface value and r is the concrete value. 66 if n.Y.Type().IsInterface() { 67 l, r = r, l 68 } 69 70 // Handle both == and !=. 71 eq := n.Op() 72 andor := ir.OOROR 73 if eq == ir.OEQ { 74 andor = ir.OANDAND 75 } 76 // Check for types equal. 77 // For empty interface, this is: 78 // l.tab == type(r) 79 // For non-empty interface, this is: 80 // l.tab != nil && l.tab._type == type(r) 81 // 82 // TODO(mdempsky): For non-empty interface comparisons, just 83 // compare against the itab address directly? 84 var eqtype ir.Node 85 tab := ir.NewUnaryExpr(base.Pos, ir.OITAB, l) 86 rtyp := reflectdata.CompareRType(base.Pos, n) 87 if l.Type().IsEmptyInterface() { 88 tab.SetType(types.NewPtr(types.Types[types.TUINT8])) 89 tab.SetTypecheck(1) 90 eqtype = ir.NewBinaryExpr(base.Pos, eq, tab, rtyp) 91 } else { 92 nonnil := ir.NewBinaryExpr(base.Pos, brcom(eq), typecheck.NodNil(), tab) 93 match := ir.NewBinaryExpr(base.Pos, eq, itabType(tab), rtyp) 94 eqtype = ir.NewLogicalExpr(base.Pos, andor, nonnil, match) 95 } 96 // Check for data equal. 97 eqdata := ir.NewBinaryExpr(base.Pos, eq, ifaceData(n.Pos(), l, r.Type()), r) 98 // Put it all together. 99 expr := ir.NewLogicalExpr(base.Pos, andor, eqtype, eqdata) 100 return finishCompare(n, expr, init) 101 } 102 103 // Must be comparison of array or struct. 104 // Otherwise back end handles it. 105 // While we're here, decide whether to 106 // inline or call an eq alg. 107 t := n.X.Type() 108 var inline bool 109 110 maxcmpsize := int64(4) 111 unalignedLoad := ssagen.Arch.LinkArch.CanMergeLoads 112 if unalignedLoad { 113 // Keep this low enough to generate less code than a function call. 114 maxcmpsize = 2 * int64(ssagen.Arch.LinkArch.RegSize) 115 } 116 117 switch t.Kind() { 118 default: 119 if base.Debug.Libfuzzer != 0 && t.IsInteger() && (n.X.Name() == nil || !n.X.Name().Libfuzzer8BitCounter()) { 120 n.X = cheapExpr(n.X, init) 121 n.Y = cheapExpr(n.Y, init) 122 123 // If exactly one comparison operand is 124 // constant, invoke the constcmp functions 125 // instead, and arrange for the constant 126 // operand to be the first argument. 127 l, r := n.X, n.Y 128 if r.Op() == ir.OLITERAL { 129 l, r = r, l 130 } 131 constcmp := l.Op() == ir.OLITERAL && r.Op() != ir.OLITERAL 132 133 var fn string 134 var paramType *types.Type 135 switch t.Size() { 136 case 1: 137 fn = "libfuzzerTraceCmp1" 138 if constcmp { 139 fn = "libfuzzerTraceConstCmp1" 140 } 141 paramType = types.Types[types.TUINT8] 142 case 2: 143 fn = "libfuzzerTraceCmp2" 144 if constcmp { 145 fn = "libfuzzerTraceConstCmp2" 146 } 147 paramType = types.Types[types.TUINT16] 148 case 4: 149 fn = "libfuzzerTraceCmp4" 150 if constcmp { 151 fn = "libfuzzerTraceConstCmp4" 152 } 153 paramType = types.Types[types.TUINT32] 154 case 8: 155 fn = "libfuzzerTraceCmp8" 156 if constcmp { 157 fn = "libfuzzerTraceConstCmp8" 158 } 159 paramType = types.Types[types.TUINT64] 160 default: 161 base.Fatalf("unexpected integer size %d for %v", t.Size(), t) 162 } 163 init.Append(mkcall(fn, nil, init, tracecmpArg(l, paramType, init), tracecmpArg(r, paramType, init), fakePC(n))) 164 } 165 return n 166 case types.TARRAY: 167 // We can compare several elements at once with 2/4/8 byte integer compares 168 inline = t.NumElem() <= 1 || (types.IsSimple[t.Elem().Kind()] && (t.NumElem() <= 4 || t.Elem().Size()*t.NumElem() <= maxcmpsize)) 169 case types.TSTRUCT: 170 inline = compare.EqStructCost(t) <= 4 171 } 172 173 cmpl := n.X 174 for cmpl != nil && cmpl.Op() == ir.OCONVNOP { 175 cmpl = cmpl.(*ir.ConvExpr).X 176 } 177 cmpr := n.Y 178 for cmpr != nil && cmpr.Op() == ir.OCONVNOP { 179 cmpr = cmpr.(*ir.ConvExpr).X 180 } 181 182 // Chose not to inline. Call equality function directly. 183 if !inline { 184 // eq algs take pointers; cmpl and cmpr must be addressable 185 if !ir.IsAddressable(cmpl) || !ir.IsAddressable(cmpr) { 186 base.Fatalf("arguments of comparison must be lvalues - %v %v", cmpl, cmpr) 187 } 188 189 fn, needsize := eqFor(t) 190 call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil) 191 call.Args.Append(typecheck.NodAddr(cmpl)) 192 call.Args.Append(typecheck.NodAddr(cmpr)) 193 if needsize { 194 call.Args.Append(ir.NewInt(t.Size())) 195 } 196 res := ir.Node(call) 197 if n.Op() != ir.OEQ { 198 res = ir.NewUnaryExpr(base.Pos, ir.ONOT, res) 199 } 200 return finishCompare(n, res, init) 201 } 202 203 // inline: build boolean expression comparing element by element 204 andor := ir.OANDAND 205 if n.Op() == ir.ONE { 206 andor = ir.OOROR 207 } 208 var expr ir.Node 209 comp := func(el, er ir.Node) { 210 a := ir.NewBinaryExpr(base.Pos, n.Op(), el, er) 211 if expr == nil { 212 expr = a 213 } else { 214 expr = ir.NewLogicalExpr(base.Pos, andor, expr, a) 215 } 216 } 217 and := func(cond ir.Node) { 218 if expr == nil { 219 expr = cond 220 } else { 221 expr = ir.NewLogicalExpr(base.Pos, andor, expr, cond) 222 } 223 } 224 cmpl = safeExpr(cmpl, init) 225 cmpr = safeExpr(cmpr, init) 226 if t.IsStruct() { 227 conds := compare.EqStruct(t, cmpl, cmpr) 228 if n.Op() == ir.OEQ { 229 for _, cond := range conds { 230 and(cond) 231 } 232 } else { 233 for _, cond := range conds { 234 notCond := ir.NewUnaryExpr(base.Pos, ir.ONOT, cond) 235 and(notCond) 236 } 237 } 238 } else { 239 step := int64(1) 240 remains := t.NumElem() * t.Elem().Size() 241 combine64bit := unalignedLoad && types.RegSize == 8 && t.Elem().Size() <= 4 && t.Elem().IsInteger() 242 combine32bit := unalignedLoad && t.Elem().Size() <= 2 && t.Elem().IsInteger() 243 combine16bit := unalignedLoad && t.Elem().Size() == 1 && t.Elem().IsInteger() 244 for i := int64(0); remains > 0; { 245 var convType *types.Type 246 switch { 247 case remains >= 8 && combine64bit: 248 convType = types.Types[types.TINT64] 249 step = 8 / t.Elem().Size() 250 case remains >= 4 && combine32bit: 251 convType = types.Types[types.TUINT32] 252 step = 4 / t.Elem().Size() 253 case remains >= 2 && combine16bit: 254 convType = types.Types[types.TUINT16] 255 step = 2 / t.Elem().Size() 256 default: 257 step = 1 258 } 259 if step == 1 { 260 comp( 261 ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(i)), 262 ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(i)), 263 ) 264 i++ 265 remains -= t.Elem().Size() 266 } else { 267 elemType := t.Elem().ToUnsigned() 268 cmplw := ir.Node(ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(i))) 269 cmplw = typecheck.Conv(cmplw, elemType) // convert to unsigned 270 cmplw = typecheck.Conv(cmplw, convType) // widen 271 cmprw := ir.Node(ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(i))) 272 cmprw = typecheck.Conv(cmprw, elemType) 273 cmprw = typecheck.Conv(cmprw, convType) 274 // For code like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... 275 // ssa will generate a single large load. 276 for offset := int64(1); offset < step; offset++ { 277 lb := ir.Node(ir.NewIndexExpr(base.Pos, cmpl, ir.NewInt(i+offset))) 278 lb = typecheck.Conv(lb, elemType) 279 lb = typecheck.Conv(lb, convType) 280 lb = ir.NewBinaryExpr(base.Pos, ir.OLSH, lb, ir.NewInt(8*t.Elem().Size()*offset)) 281 cmplw = ir.NewBinaryExpr(base.Pos, ir.OOR, cmplw, lb) 282 rb := ir.Node(ir.NewIndexExpr(base.Pos, cmpr, ir.NewInt(i+offset))) 283 rb = typecheck.Conv(rb, elemType) 284 rb = typecheck.Conv(rb, convType) 285 rb = ir.NewBinaryExpr(base.Pos, ir.OLSH, rb, ir.NewInt(8*t.Elem().Size()*offset)) 286 cmprw = ir.NewBinaryExpr(base.Pos, ir.OOR, cmprw, rb) 287 } 288 comp(cmplw, cmprw) 289 i += step 290 remains -= step * t.Elem().Size() 291 } 292 } 293 } 294 if expr == nil { 295 expr = ir.NewBool(n.Op() == ir.OEQ) 296 // We still need to use cmpl and cmpr, in case they contain 297 // an expression which might panic. See issue 23837. 298 a1 := typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.BlankNode, cmpl)) 299 a2 := typecheck.Stmt(ir.NewAssignStmt(base.Pos, ir.BlankNode, cmpr)) 300 init.Append(a1, a2) 301 } 302 return finishCompare(n, expr, init) 303 } 304 305 func walkCompareInterface(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { 306 n.Y = cheapExpr(n.Y, init) 307 n.X = cheapExpr(n.X, init) 308 eqtab, eqdata := compare.EqInterface(n.X, n.Y) 309 var cmp ir.Node 310 if n.Op() == ir.OEQ { 311 cmp = ir.NewLogicalExpr(base.Pos, ir.OANDAND, eqtab, eqdata) 312 } else { 313 eqtab.SetOp(ir.ONE) 314 cmp = ir.NewLogicalExpr(base.Pos, ir.OOROR, eqtab, ir.NewUnaryExpr(base.Pos, ir.ONOT, eqdata)) 315 } 316 return finishCompare(n, cmp, init) 317 } 318 319 func walkCompareString(n *ir.BinaryExpr, init *ir.Nodes) ir.Node { 320 if base.Debug.Libfuzzer != 0 { 321 if !ir.IsConst(n.X, constant.String) || !ir.IsConst(n.Y, constant.String) { 322 fn := "libfuzzerHookStrCmp" 323 n.X = cheapExpr(n.X, init) 324 n.Y = cheapExpr(n.Y, init) 325 paramType := types.Types[types.TSTRING] 326 init.Append(mkcall(fn, nil, init, tracecmpArg(n.X, paramType, init), tracecmpArg(n.Y, paramType, init), fakePC(n))) 327 } 328 } 329 // Rewrite comparisons to short constant strings as length+byte-wise comparisons. 330 var cs, ncs ir.Node // const string, non-const string 331 switch { 332 case ir.IsConst(n.X, constant.String) && ir.IsConst(n.Y, constant.String): 333 // ignore; will be constant evaluated 334 case ir.IsConst(n.X, constant.String): 335 cs = n.X 336 ncs = n.Y 337 case ir.IsConst(n.Y, constant.String): 338 cs = n.Y 339 ncs = n.X 340 } 341 if cs != nil { 342 cmp := n.Op() 343 // Our comparison below assumes that the non-constant string 344 // is on the left hand side, so rewrite "" cmp x to x cmp "". 345 // See issue 24817. 346 if ir.IsConst(n.X, constant.String) { 347 cmp = brrev(cmp) 348 } 349 350 // maxRewriteLen was chosen empirically. 351 // It is the value that minimizes cmd/go file size 352 // across most architectures. 353 // See the commit description for CL 26758 for details. 354 maxRewriteLen := 6 355 // Some architectures can load unaligned byte sequence as 1 word. 356 // So we can cover longer strings with the same amount of code. 357 canCombineLoads := ssagen.Arch.LinkArch.CanMergeLoads 358 combine64bit := false 359 if canCombineLoads { 360 // Keep this low enough to generate less code than a function call. 361 maxRewriteLen = 2 * ssagen.Arch.LinkArch.RegSize 362 combine64bit = ssagen.Arch.LinkArch.RegSize >= 8 363 } 364 365 var and ir.Op 366 switch cmp { 367 case ir.OEQ: 368 and = ir.OANDAND 369 case ir.ONE: 370 and = ir.OOROR 371 default: 372 // Don't do byte-wise comparisons for <, <=, etc. 373 // They're fairly complicated. 374 // Length-only checks are ok, though. 375 maxRewriteLen = 0 376 } 377 if s := ir.StringVal(cs); len(s) <= maxRewriteLen { 378 if len(s) > 0 { 379 ncs = safeExpr(ncs, init) 380 } 381 r := ir.Node(ir.NewBinaryExpr(base.Pos, cmp, ir.NewUnaryExpr(base.Pos, ir.OLEN, ncs), ir.NewInt(int64(len(s))))) 382 remains := len(s) 383 for i := 0; remains > 0; { 384 if remains == 1 || !canCombineLoads { 385 cb := ir.NewInt(int64(s[i])) 386 ncb := ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(int64(i))) 387 r = ir.NewLogicalExpr(base.Pos, and, r, ir.NewBinaryExpr(base.Pos, cmp, ncb, cb)) 388 remains-- 389 i++ 390 continue 391 } 392 var step int 393 var convType *types.Type 394 switch { 395 case remains >= 8 && combine64bit: 396 convType = types.Types[types.TINT64] 397 step = 8 398 case remains >= 4: 399 convType = types.Types[types.TUINT32] 400 step = 4 401 case remains >= 2: 402 convType = types.Types[types.TUINT16] 403 step = 2 404 } 405 ncsubstr := typecheck.Conv(ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(int64(i))), convType) 406 csubstr := int64(s[i]) 407 // Calculate large constant from bytes as sequence of shifts and ors. 408 // Like this: uint32(s[0]) | uint32(s[1])<<8 | uint32(s[2])<<16 ... 409 // ssa will combine this into a single large load. 410 for offset := 1; offset < step; offset++ { 411 b := typecheck.Conv(ir.NewIndexExpr(base.Pos, ncs, ir.NewInt(int64(i+offset))), convType) 412 b = ir.NewBinaryExpr(base.Pos, ir.OLSH, b, ir.NewInt(int64(8*offset))) 413 ncsubstr = ir.NewBinaryExpr(base.Pos, ir.OOR, ncsubstr, b) 414 csubstr |= int64(s[i+offset]) << uint8(8*offset) 415 } 416 csubstrPart := ir.NewInt(csubstr) 417 // Compare "step" bytes as once 418 r = ir.NewLogicalExpr(base.Pos, and, r, ir.NewBinaryExpr(base.Pos, cmp, csubstrPart, ncsubstr)) 419 remains -= step 420 i += step 421 } 422 return finishCompare(n, r, init) 423 } 424 } 425 426 var r ir.Node 427 if n.Op() == ir.OEQ || n.Op() == ir.ONE { 428 // prepare for rewrite below 429 n.X = cheapExpr(n.X, init) 430 n.Y = cheapExpr(n.Y, init) 431 eqlen, eqmem := compare.EqString(n.X, n.Y) 432 // quick check of len before full compare for == or !=. 433 // memequal then tests equality up to length len. 434 if n.Op() == ir.OEQ { 435 // len(left) == len(right) && memequal(left, right, len) 436 r = ir.NewLogicalExpr(base.Pos, ir.OANDAND, eqlen, eqmem) 437 } else { 438 // len(left) != len(right) || !memequal(left, right, len) 439 eqlen.SetOp(ir.ONE) 440 r = ir.NewLogicalExpr(base.Pos, ir.OOROR, eqlen, ir.NewUnaryExpr(base.Pos, ir.ONOT, eqmem)) 441 } 442 } else { 443 // sys_cmpstring(s1, s2) :: 0 444 r = mkcall("cmpstring", types.Types[types.TINT], init, typecheck.Conv(n.X, types.Types[types.TSTRING]), typecheck.Conv(n.Y, types.Types[types.TSTRING])) 445 r = ir.NewBinaryExpr(base.Pos, n.Op(), r, ir.NewInt(0)) 446 } 447 448 return finishCompare(n, r, init) 449 } 450 451 // The result of finishCompare MUST be assigned back to n, e.g. 452 // 453 // n.Left = finishCompare(n.Left, x, r, init) 454 func finishCompare(n *ir.BinaryExpr, r ir.Node, init *ir.Nodes) ir.Node { 455 r = typecheck.Expr(r) 456 r = typecheck.Conv(r, n.Type()) 457 r = walkExpr(r, init) 458 return r 459 } 460 461 func eqFor(t *types.Type) (n ir.Node, needsize bool) { 462 // Should only arrive here with large memory or 463 // a struct/array containing a non-memory field/element. 464 // Small memory is handled inline, and single non-memory 465 // is handled by walkCompare. 466 switch a, _ := types.AlgType(t); a { 467 case types.AMEM: 468 n := typecheck.LookupRuntime("memequal") 469 n = typecheck.SubstArgTypes(n, t, t) 470 return n, true 471 case types.ASPECIAL: 472 sym := reflectdata.TypeSymPrefix(".eq", t) 473 // TODO(austin): This creates an ir.Name with a nil Func. 474 n := typecheck.NewName(sym) 475 ir.MarkFunc(n) 476 n.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{ 477 types.NewField(base.Pos, nil, types.NewPtr(t)), 478 types.NewField(base.Pos, nil, types.NewPtr(t)), 479 }, []*types.Field{ 480 types.NewField(base.Pos, nil, types.Types[types.TBOOL]), 481 })) 482 return n, false 483 } 484 base.Fatalf("eqFor %v", t) 485 return nil, false 486 } 487 488 // brcom returns !(op). 489 // For example, brcom(==) is !=. 490 func brcom(op ir.Op) ir.Op { 491 switch op { 492 case ir.OEQ: 493 return ir.ONE 494 case ir.ONE: 495 return ir.OEQ 496 case ir.OLT: 497 return ir.OGE 498 case ir.OGT: 499 return ir.OLE 500 case ir.OLE: 501 return ir.OGT 502 case ir.OGE: 503 return ir.OLT 504 } 505 base.Fatalf("brcom: no com for %v\n", op) 506 return op 507 } 508 509 // brrev returns reverse(op). 510 // For example, Brrev(<) is >. 511 func brrev(op ir.Op) ir.Op { 512 switch op { 513 case ir.OEQ: 514 return ir.OEQ 515 case ir.ONE: 516 return ir.ONE 517 case ir.OLT: 518 return ir.OGT 519 case ir.OGT: 520 return ir.OLT 521 case ir.OLE: 522 return ir.OGE 523 case ir.OGE: 524 return ir.OLE 525 } 526 base.Fatalf("brrev: no rev for %v\n", op) 527 return op 528 } 529 530 func tracecmpArg(n ir.Node, t *types.Type, init *ir.Nodes) ir.Node { 531 // Ugly hack to avoid "constant -1 overflows uintptr" errors, etc. 532 if n.Op() == ir.OLITERAL && n.Type().IsSigned() && ir.Int64Val(n) < 0 { 533 n = copyExpr(n, n.Type(), init) 534 } 535 536 return typecheck.Conv(n, t) 537 }