github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/walk/switch.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 "github.com/bir3/gocompiler/src/go/constant" 9 "github.com/bir3/gocompiler/src/go/token" 10 "sort" 11 12 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 13 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 14 "github.com/bir3/gocompiler/src/cmd/compile/internal/ssagen" 15 "github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck" 16 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 17 "github.com/bir3/gocompiler/src/cmd/internal/src" 18 ) 19 20 // walkSwitch walks a switch statement. 21 func walkSwitch(sw *ir.SwitchStmt) { 22 // Guard against double walk, see #25776. 23 if sw.Walked() { 24 return // Was fatal, but eliminating every possible source of double-walking is hard 25 } 26 sw.SetWalked(true) 27 28 if sw.Tag != nil && sw.Tag.Op() == ir.OTYPESW { 29 walkSwitchType(sw) 30 } else { 31 walkSwitchExpr(sw) 32 } 33 } 34 35 // walkSwitchExpr generates an AST implementing sw. sw is an 36 // expression switch. 37 func walkSwitchExpr(sw *ir.SwitchStmt) { 38 lno := ir.SetPos(sw) 39 40 cond := sw.Tag 41 sw.Tag = nil 42 43 // convert switch {...} to switch true {...} 44 if cond == nil { 45 cond = ir.NewBool(true) 46 cond = typecheck.Expr(cond) 47 cond = typecheck.DefaultLit(cond, nil) 48 } 49 50 // Given "switch string(byteslice)", 51 // with all cases being side-effect free, 52 // use a zero-cost alias of the byte slice. 53 // Do this before calling walkExpr on cond, 54 // because walkExpr will lower the string 55 // conversion into a runtime call. 56 // See issue 24937 for more discussion. 57 if cond.Op() == ir.OBYTES2STR && allCaseExprsAreSideEffectFree(sw) { 58 cond := cond.(*ir.ConvExpr) 59 cond.SetOp(ir.OBYTES2STRTMP) 60 } 61 62 cond = walkExpr(cond, sw.PtrInit()) 63 if cond.Op() != ir.OLITERAL && cond.Op() != ir.ONIL { 64 cond = copyExpr(cond, cond.Type(), &sw.Compiled) 65 } 66 67 base.Pos = lno 68 69 s := exprSwitch{ 70 pos: lno, 71 exprname: cond, 72 } 73 74 var defaultGoto ir.Node 75 var body ir.Nodes 76 for _, ncase := range sw.Cases { 77 label := typecheck.AutoLabel(".s") 78 jmp := ir.NewBranchStmt(ncase.Pos(), ir.OGOTO, label) 79 80 // Process case dispatch. 81 if len(ncase.List) == 0 { 82 if defaultGoto != nil { 83 base.Fatalf("duplicate default case not detected during typechecking") 84 } 85 defaultGoto = jmp 86 } 87 88 for i, n1 := range ncase.List { 89 var rtype ir.Node 90 if i < len(ncase.RTypes) { 91 rtype = ncase.RTypes[i] 92 } 93 s.Add(ncase.Pos(), n1, rtype, jmp) 94 } 95 96 // Process body. 97 body.Append(ir.NewLabelStmt(ncase.Pos(), label)) 98 body.Append(ncase.Body...) 99 if fall, pos := endsInFallthrough(ncase.Body); !fall { 100 br := ir.NewBranchStmt(base.Pos, ir.OBREAK, nil) 101 br.SetPos(pos) 102 body.Append(br) 103 } 104 } 105 sw.Cases = nil 106 107 if defaultGoto == nil { 108 br := ir.NewBranchStmt(base.Pos, ir.OBREAK, nil) 109 br.SetPos(br.Pos().WithNotStmt()) 110 defaultGoto = br 111 } 112 113 s.Emit(&sw.Compiled) 114 sw.Compiled.Append(defaultGoto) 115 sw.Compiled.Append(body.Take()...) 116 walkStmtList(sw.Compiled) 117 } 118 119 // An exprSwitch walks an expression switch. 120 type exprSwitch struct { 121 pos src.XPos 122 exprname ir.Node // value being switched on 123 124 done ir.Nodes 125 clauses []exprClause 126 } 127 128 type exprClause struct { 129 pos src.XPos 130 lo, hi ir.Node 131 rtype ir.Node // *runtime._type for OEQ node 132 jmp ir.Node 133 } 134 135 func (s *exprSwitch) Add(pos src.XPos, expr, rtype, jmp ir.Node) { 136 c := exprClause{pos: pos, lo: expr, hi: expr, rtype: rtype, jmp: jmp} 137 if types.IsOrdered[s.exprname.Type().Kind()] && expr.Op() == ir.OLITERAL { 138 s.clauses = append(s.clauses, c) 139 return 140 } 141 142 s.flush() 143 s.clauses = append(s.clauses, c) 144 s.flush() 145 } 146 147 func (s *exprSwitch) Emit(out *ir.Nodes) { 148 s.flush() 149 out.Append(s.done.Take()...) 150 } 151 152 func (s *exprSwitch) flush() { 153 cc := s.clauses 154 s.clauses = nil 155 if len(cc) == 0 { 156 return 157 } 158 159 // Caution: If len(cc) == 1, then cc[0] might not an OLITERAL. 160 // The code below is structured to implicitly handle this case 161 // (e.g., sort.Slice doesn't need to invoke the less function 162 // when there's only a single slice element). 163 164 if s.exprname.Type().IsString() && len(cc) >= 2 { 165 // Sort strings by length and then by value. It is 166 // much cheaper to compare lengths than values, and 167 // all we need here is consistency. We respect this 168 // sorting below. 169 sort.Slice(cc, func(i, j int) bool { 170 si := ir.StringVal(cc[i].lo) 171 sj := ir.StringVal(cc[j].lo) 172 if len(si) != len(sj) { 173 return len(si) < len(sj) 174 } 175 return si < sj 176 }) 177 178 // runLen returns the string length associated with a 179 // particular run of exprClauses. 180 runLen := func(run []exprClause) int64 { return int64(len(ir.StringVal(run[0].lo))) } 181 182 // Collapse runs of consecutive strings with the same length. 183 var runs [][]exprClause 184 start := 0 185 for i := 1; i < len(cc); i++ { 186 if runLen(cc[start:]) != runLen(cc[i:]) { 187 runs = append(runs, cc[start:i]) 188 start = i 189 } 190 } 191 runs = append(runs, cc[start:]) 192 193 // We have strings of more than one length. Generate an 194 // outer switch which switches on the length of the string 195 // and an inner switch in each case which resolves all the 196 // strings of the same length. The code looks something like this: 197 198 // goto outerLabel 199 // len5: 200 // ... search among length 5 strings ... 201 // goto endLabel 202 // len8: 203 // ... search among length 8 strings ... 204 // goto endLabel 205 // ... other lengths ... 206 // outerLabel: 207 // switch len(s) { 208 // case 5: goto len5 209 // case 8: goto len8 210 // ... other lengths ... 211 // } 212 // endLabel: 213 214 outerLabel := typecheck.AutoLabel(".s") 215 endLabel := typecheck.AutoLabel(".s") 216 217 // Jump around all the individual switches for each length. 218 s.done.Append(ir.NewBranchStmt(s.pos, ir.OGOTO, outerLabel)) 219 220 var outer exprSwitch 221 outer.exprname = ir.NewUnaryExpr(s.pos, ir.OLEN, s.exprname) 222 outer.exprname.SetType(types.Types[types.TINT]) 223 224 for _, run := range runs { 225 // Target label to jump to when we match this length. 226 label := typecheck.AutoLabel(".s") 227 228 // Search within this run of same-length strings. 229 pos := run[0].pos 230 s.done.Append(ir.NewLabelStmt(pos, label)) 231 stringSearch(s.exprname, run, &s.done) 232 s.done.Append(ir.NewBranchStmt(pos, ir.OGOTO, endLabel)) 233 234 // Add length case to outer switch. 235 cas := ir.NewBasicLit(pos, constant.MakeInt64(runLen(run))) 236 jmp := ir.NewBranchStmt(pos, ir.OGOTO, label) 237 outer.Add(pos, cas, nil, jmp) 238 } 239 s.done.Append(ir.NewLabelStmt(s.pos, outerLabel)) 240 outer.Emit(&s.done) 241 s.done.Append(ir.NewLabelStmt(s.pos, endLabel)) 242 return 243 } 244 245 sort.Slice(cc, func(i, j int) bool { 246 return constant.Compare(cc[i].lo.Val(), token.LSS, cc[j].lo.Val()) 247 }) 248 249 // Merge consecutive integer cases. 250 if s.exprname.Type().IsInteger() { 251 consecutive := func(last, next constant.Value) bool { 252 delta := constant.BinaryOp(next, token.SUB, last) 253 return constant.Compare(delta, token.EQL, constant.MakeInt64(1)) 254 } 255 256 merged := cc[:1] 257 for _, c := range cc[1:] { 258 last := &merged[len(merged)-1] 259 if last.jmp == c.jmp && consecutive(last.hi.Val(), c.lo.Val()) { 260 last.hi = c.lo 261 } else { 262 merged = append(merged, c) 263 } 264 } 265 cc = merged 266 } 267 268 s.search(cc, &s.done) 269 } 270 271 func (s *exprSwitch) search(cc []exprClause, out *ir.Nodes) { 272 if s.tryJumpTable(cc, out) { 273 return 274 } 275 binarySearch(len(cc), out, 276 func(i int) ir.Node { 277 return ir.NewBinaryExpr(base.Pos, ir.OLE, s.exprname, cc[i-1].hi) 278 }, 279 func(i int, nif *ir.IfStmt) { 280 c := &cc[i] 281 nif.Cond = c.test(s.exprname) 282 nif.Body = []ir.Node{c.jmp} 283 }, 284 ) 285 } 286 287 // Try to implement the clauses with a jump table. Returns true if successful. 288 func (s *exprSwitch) tryJumpTable(cc []exprClause, out *ir.Nodes) bool { 289 const go119UseJumpTables = true 290 const minCases = 8 // have at least minCases cases in the switch 291 const minDensity = 4 // use at least 1 out of every minDensity entries 292 293 if !go119UseJumpTables || base.Flag.N != 0 || !ssagen.Arch.LinkArch.CanJumpTable || base.Ctxt.Retpoline { 294 return false 295 } 296 if len(cc) < minCases { 297 return false // not enough cases for it to be worth it 298 } 299 if cc[0].lo.Val().Kind() != constant.Int { 300 return false // e.g. float 301 } 302 if s.exprname.Type().Size() > int64(types.PtrSize) { 303 return false // 64-bit switches on 32-bit archs 304 } 305 min := cc[0].lo.Val() 306 max := cc[len(cc)-1].hi.Val() 307 width := constant.BinaryOp(constant.BinaryOp(max, token.SUB, min), token.ADD, constant.MakeInt64(1)) 308 limit := constant.MakeInt64(int64(len(cc)) * minDensity) 309 if constant.Compare(width, token.GTR, limit) { 310 // We disable jump tables if we use less than a minimum fraction of the entries. 311 // i.e. for switch x {case 0: case 1000: case 2000:} we don't want to use a jump table. 312 return false 313 } 314 jt := ir.NewJumpTableStmt(base.Pos, s.exprname) 315 for _, c := range cc { 316 jmp := c.jmp.(*ir.BranchStmt) 317 if jmp.Op() != ir.OGOTO || jmp.Label == nil { 318 panic("bad switch case body") 319 } 320 for i := c.lo.Val(); constant.Compare(i, token.LEQ, c.hi.Val()); i = constant.BinaryOp(i, token.ADD, constant.MakeInt64(1)) { 321 jt.Cases = append(jt.Cases, i) 322 jt.Targets = append(jt.Targets, jmp.Label) 323 } 324 } 325 out.Append(jt) 326 return true 327 } 328 329 func (c *exprClause) test(exprname ir.Node) ir.Node { 330 // Integer range. 331 if c.hi != c.lo { 332 low := ir.NewBinaryExpr(c.pos, ir.OGE, exprname, c.lo) 333 high := ir.NewBinaryExpr(c.pos, ir.OLE, exprname, c.hi) 334 return ir.NewLogicalExpr(c.pos, ir.OANDAND, low, high) 335 } 336 337 // Optimize "switch true { ...}" and "switch false { ... }". 338 if ir.IsConst(exprname, constant.Bool) && !c.lo.Type().IsInterface() { 339 if ir.BoolVal(exprname) { 340 return c.lo 341 } else { 342 return ir.NewUnaryExpr(c.pos, ir.ONOT, c.lo) 343 } 344 } 345 346 n := ir.NewBinaryExpr(c.pos, ir.OEQ, exprname, c.lo) 347 n.RType = c.rtype 348 return n 349 } 350 351 func allCaseExprsAreSideEffectFree(sw *ir.SwitchStmt) bool { 352 // In theory, we could be more aggressive, allowing any 353 // side-effect-free expressions in cases, but it's a bit 354 // tricky because some of that information is unavailable due 355 // to the introduction of temporaries during order. 356 // Restricting to constants is simple and probably powerful 357 // enough. 358 359 for _, ncase := range sw.Cases { 360 for _, v := range ncase.List { 361 if v.Op() != ir.OLITERAL { 362 return false 363 } 364 } 365 } 366 return true 367 } 368 369 // endsInFallthrough reports whether stmts ends with a "fallthrough" statement. 370 func endsInFallthrough(stmts []ir.Node) (bool, src.XPos) { 371 if len(stmts) == 0 { 372 return false, src.NoXPos 373 } 374 i := len(stmts) - 1 375 return stmts[i].Op() == ir.OFALL, stmts[i].Pos() 376 } 377 378 // walkSwitchType generates an AST that implements sw, where sw is a 379 // type switch. 380 func walkSwitchType(sw *ir.SwitchStmt) { 381 var s typeSwitch 382 s.facename = sw.Tag.(*ir.TypeSwitchGuard).X 383 sw.Tag = nil 384 385 s.facename = walkExpr(s.facename, sw.PtrInit()) 386 s.facename = copyExpr(s.facename, s.facename.Type(), &sw.Compiled) 387 s.okname = typecheck.Temp(types.Types[types.TBOOL]) 388 389 // Get interface descriptor word. 390 // For empty interfaces this will be the type. 391 // For non-empty interfaces this will be the itab. 392 itab := ir.NewUnaryExpr(base.Pos, ir.OITAB, s.facename) 393 394 // For empty interfaces, do: 395 // if e._type == nil { 396 // do nil case if it exists, otherwise default 397 // } 398 // h := e._type.hash 399 // Use a similar strategy for non-empty interfaces. 400 ifNil := ir.NewIfStmt(base.Pos, nil, nil, nil) 401 ifNil.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, itab, typecheck.NodNil()) 402 base.Pos = base.Pos.WithNotStmt() // disable statement marks after the first check. 403 ifNil.Cond = typecheck.Expr(ifNil.Cond) 404 ifNil.Cond = typecheck.DefaultLit(ifNil.Cond, nil) 405 // ifNil.Nbody assigned at end. 406 sw.Compiled.Append(ifNil) 407 408 // Load hash from type or itab. 409 dotHash := typeHashFieldOf(base.Pos, itab) 410 s.hashname = copyExpr(dotHash, dotHash.Type(), &sw.Compiled) 411 412 br := ir.NewBranchStmt(base.Pos, ir.OBREAK, nil) 413 var defaultGoto, nilGoto ir.Node 414 var body ir.Nodes 415 for _, ncase := range sw.Cases { 416 caseVar := ncase.Var 417 418 // For single-type cases with an interface type, 419 // we initialize the case variable as part of the type assertion. 420 // In other cases, we initialize it in the body. 421 var singleType *types.Type 422 if len(ncase.List) == 1 && ncase.List[0].Op() == ir.OTYPE { 423 singleType = ncase.List[0].Type() 424 } 425 caseVarInitialized := false 426 427 label := typecheck.AutoLabel(".s") 428 jmp := ir.NewBranchStmt(ncase.Pos(), ir.OGOTO, label) 429 430 if len(ncase.List) == 0 { // default: 431 if defaultGoto != nil { 432 base.Fatalf("duplicate default case not detected during typechecking") 433 } 434 defaultGoto = jmp 435 } 436 437 for _, n1 := range ncase.List { 438 if ir.IsNil(n1) { // case nil: 439 if nilGoto != nil { 440 base.Fatalf("duplicate nil case not detected during typechecking") 441 } 442 nilGoto = jmp 443 continue 444 } 445 446 if singleType != nil && singleType.IsInterface() { 447 s.Add(ncase.Pos(), n1, caseVar, jmp) 448 caseVarInitialized = true 449 } else { 450 s.Add(ncase.Pos(), n1, nil, jmp) 451 } 452 } 453 454 body.Append(ir.NewLabelStmt(ncase.Pos(), label)) 455 if caseVar != nil && !caseVarInitialized { 456 val := s.facename 457 if singleType != nil { 458 // We have a single concrete type. Extract the data. 459 if singleType.IsInterface() { 460 base.Fatalf("singleType interface should have been handled in Add") 461 } 462 val = ifaceData(ncase.Pos(), s.facename, singleType) 463 } 464 if len(ncase.List) == 1 && ncase.List[0].Op() == ir.ODYNAMICTYPE { 465 dt := ncase.List[0].(*ir.DynamicType) 466 x := ir.NewDynamicTypeAssertExpr(ncase.Pos(), ir.ODYNAMICDOTTYPE, val, dt.RType) 467 x.ITab = dt.ITab 468 x.SetType(caseVar.Type()) 469 x.SetTypecheck(1) 470 val = x 471 } 472 l := []ir.Node{ 473 ir.NewDecl(ncase.Pos(), ir.ODCL, caseVar), 474 ir.NewAssignStmt(ncase.Pos(), caseVar, val), 475 } 476 typecheck.Stmts(l) 477 body.Append(l...) 478 } 479 body.Append(ncase.Body...) 480 body.Append(br) 481 } 482 sw.Cases = nil 483 484 if defaultGoto == nil { 485 defaultGoto = br 486 } 487 if nilGoto == nil { 488 nilGoto = defaultGoto 489 } 490 ifNil.Body = []ir.Node{nilGoto} 491 492 s.Emit(&sw.Compiled) 493 sw.Compiled.Append(defaultGoto) 494 sw.Compiled.Append(body.Take()...) 495 496 walkStmtList(sw.Compiled) 497 } 498 499 // typeHashFieldOf returns an expression to select the type hash field 500 // from an interface's descriptor word (whether a *runtime._type or 501 // *runtime.itab pointer). 502 func typeHashFieldOf(pos src.XPos, itab *ir.UnaryExpr) *ir.SelectorExpr { 503 if itab.Op() != ir.OITAB { 504 base.Fatalf("expected OITAB, got %v", itab.Op()) 505 } 506 var hashField *types.Field 507 if itab.X.Type().IsEmptyInterface() { 508 // runtime._type's hash field 509 if rtypeHashField == nil { 510 rtypeHashField = runtimeField("hash", int64(2*types.PtrSize), types.Types[types.TUINT32]) 511 } 512 hashField = rtypeHashField 513 } else { 514 // runtime.itab's hash field 515 if itabHashField == nil { 516 itabHashField = runtimeField("hash", int64(2*types.PtrSize), types.Types[types.TUINT32]) 517 } 518 hashField = itabHashField 519 } 520 return boundedDotPtr(pos, itab, hashField) 521 } 522 523 var rtypeHashField, itabHashField *types.Field 524 525 // A typeSwitch walks a type switch. 526 type typeSwitch struct { 527 // Temporary variables (i.e., ONAMEs) used by type switch dispatch logic: 528 facename ir.Node // value being type-switched on 529 hashname ir.Node // type hash of the value being type-switched on 530 okname ir.Node // boolean used for comma-ok type assertions 531 532 done ir.Nodes 533 clauses []typeClause 534 } 535 536 type typeClause struct { 537 hash uint32 538 body ir.Nodes 539 } 540 541 func (s *typeSwitch) Add(pos src.XPos, n1 ir.Node, caseVar *ir.Name, jmp ir.Node) { 542 typ := n1.Type() 543 var body ir.Nodes 544 if caseVar != nil { 545 l := []ir.Node{ 546 ir.NewDecl(pos, ir.ODCL, caseVar), 547 ir.NewAssignStmt(pos, caseVar, nil), 548 } 549 typecheck.Stmts(l) 550 body.Append(l...) 551 } else { 552 caseVar = ir.BlankNode.(*ir.Name) 553 } 554 555 // cv, ok = iface.(type) 556 as := ir.NewAssignListStmt(pos, ir.OAS2, nil, nil) 557 as.Lhs = []ir.Node{caseVar, s.okname} // cv, ok = 558 switch n1.Op() { 559 case ir.OTYPE: 560 // Static type assertion (non-generic) 561 dot := ir.NewTypeAssertExpr(pos, s.facename, typ) // iface.(type) 562 as.Rhs = []ir.Node{dot} 563 case ir.ODYNAMICTYPE: 564 // Dynamic type assertion (generic) 565 dt := n1.(*ir.DynamicType) 566 dot := ir.NewDynamicTypeAssertExpr(pos, ir.ODYNAMICDOTTYPE, s.facename, dt.RType) 567 dot.ITab = dt.ITab 568 dot.SetType(typ) 569 dot.SetTypecheck(1) 570 as.Rhs = []ir.Node{dot} 571 default: 572 base.Fatalf("unhandled type case %s", n1.Op()) 573 } 574 appendWalkStmt(&body, as) 575 576 // if ok { goto label } 577 nif := ir.NewIfStmt(pos, nil, nil, nil) 578 nif.Cond = s.okname 579 nif.Body = []ir.Node{jmp} 580 body.Append(nif) 581 582 if n1.Op() == ir.OTYPE && !typ.IsInterface() { 583 // Defer static, noninterface cases so they can be binary searched by hash. 584 s.clauses = append(s.clauses, typeClause{ 585 hash: types.TypeHash(n1.Type()), 586 body: body, 587 }) 588 return 589 } 590 591 s.flush() 592 s.done.Append(body.Take()...) 593 } 594 595 func (s *typeSwitch) Emit(out *ir.Nodes) { 596 s.flush() 597 out.Append(s.done.Take()...) 598 } 599 600 func (s *typeSwitch) flush() { 601 cc := s.clauses 602 s.clauses = nil 603 if len(cc) == 0 { 604 return 605 } 606 607 sort.Slice(cc, func(i, j int) bool { return cc[i].hash < cc[j].hash }) 608 609 // Combine adjacent cases with the same hash. 610 merged := cc[:1] 611 for _, c := range cc[1:] { 612 last := &merged[len(merged)-1] 613 if last.hash == c.hash { 614 last.body.Append(c.body.Take()...) 615 } else { 616 merged = append(merged, c) 617 } 618 } 619 cc = merged 620 621 // TODO: figure out if we could use a jump table using some low bits of the type hashes. 622 binarySearch(len(cc), &s.done, 623 func(i int) ir.Node { 624 return ir.NewBinaryExpr(base.Pos, ir.OLE, s.hashname, ir.NewInt(int64(cc[i-1].hash))) 625 }, 626 func(i int, nif *ir.IfStmt) { 627 // TODO(mdempsky): Omit hash equality check if 628 // there's only one type. 629 c := cc[i] 630 nif.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, s.hashname, ir.NewInt(int64(c.hash))) 631 nif.Body.Append(c.body.Take()...) 632 }, 633 ) 634 } 635 636 // binarySearch constructs a binary search tree for handling n cases, 637 // and appends it to out. It's used for efficiently implementing 638 // switch statements. 639 // 640 // less(i) should return a boolean expression. If it evaluates true, 641 // then cases before i will be tested; otherwise, cases i and later. 642 // 643 // leaf(i, nif) should setup nif (an OIF node) to test case i. In 644 // particular, it should set nif.Cond and nif.Body. 645 func binarySearch(n int, out *ir.Nodes, less func(i int) ir.Node, leaf func(i int, nif *ir.IfStmt)) { 646 const binarySearchMin = 4 // minimum number of cases for binary search 647 648 var do func(lo, hi int, out *ir.Nodes) 649 do = func(lo, hi int, out *ir.Nodes) { 650 n := hi - lo 651 if n < binarySearchMin { 652 for i := lo; i < hi; i++ { 653 nif := ir.NewIfStmt(base.Pos, nil, nil, nil) 654 leaf(i, nif) 655 base.Pos = base.Pos.WithNotStmt() 656 nif.Cond = typecheck.Expr(nif.Cond) 657 nif.Cond = typecheck.DefaultLit(nif.Cond, nil) 658 out.Append(nif) 659 out = &nif.Else 660 } 661 return 662 } 663 664 half := lo + n/2 665 nif := ir.NewIfStmt(base.Pos, nil, nil, nil) 666 nif.Cond = less(half) 667 base.Pos = base.Pos.WithNotStmt() 668 nif.Cond = typecheck.Expr(nif.Cond) 669 nif.Cond = typecheck.DefaultLit(nif.Cond, nil) 670 do(lo, half, &nif.Body) 671 do(half, hi, &nif.Else) 672 out.Append(nif) 673 } 674 675 do(0, n, out) 676 } 677 678 func stringSearch(expr ir.Node, cc []exprClause, out *ir.Nodes) { 679 if len(cc) < 4 { 680 // Short list, just do brute force equality checks. 681 for _, c := range cc { 682 nif := ir.NewIfStmt(base.Pos.WithNotStmt(), typecheck.DefaultLit(typecheck.Expr(c.test(expr)), nil), []ir.Node{c.jmp}, nil) 683 out.Append(nif) 684 out = &nif.Else 685 } 686 return 687 } 688 689 // The strategy here is to find a simple test to divide the set of possible strings 690 // that might match expr approximately in half. 691 // The test we're going to use is to do an ordered comparison of a single byte 692 // of expr to a constant. We will pick the index of that byte and the value we're 693 // comparing against to make the split as even as possible. 694 // if expr[3] <= 'd' { ... search strings with expr[3] at 'd' or lower ... } 695 // else { ... search strings with expr[3] at 'e' or higher ... } 696 // 697 // To add complication, we will do the ordered comparison in the signed domain. 698 // The reason for this is to prevent CSE from merging the load used for the 699 // ordered comparison with the load used for the later equality check. 700 // if expr[3] <= 'd' { ... if expr[0] == 'f' && expr[1] == 'o' && expr[2] == 'o' && expr[3] == 'd' { ... } } 701 // If we did both expr[3] loads in the unsigned domain, they would be CSEd, and that 702 // would in turn defeat the combining of expr[0]...expr[3] into a single 4-byte load. 703 // See issue 48222. 704 // By using signed loads for the ordered comparison and unsigned loads for the 705 // equality comparison, they don't get CSEd and the equality comparisons will be 706 // done using wider loads. 707 708 n := len(ir.StringVal(cc[0].lo)) // Length of the constant strings. 709 bestScore := int64(0) // measure of how good the split is. 710 bestIdx := 0 // split using expr[bestIdx] 711 bestByte := int8(0) // compare expr[bestIdx] against bestByte 712 for idx := 0; idx < n; idx++ { 713 for b := int8(-128); b < 127; b++ { 714 le := 0 715 for _, c := range cc { 716 s := ir.StringVal(c.lo) 717 if int8(s[idx]) <= b { 718 le++ 719 } 720 } 721 score := int64(le) * int64(len(cc)-le) 722 if score > bestScore { 723 bestScore = score 724 bestIdx = idx 725 bestByte = b 726 } 727 } 728 } 729 730 // The split must be at least 1:n-1 because we have at least 2 distinct strings; they 731 // have to be different somewhere. 732 // TODO: what if the best split is still pretty bad? 733 if bestScore == 0 { 734 base.Fatalf("unable to split string set") 735 } 736 737 // Convert expr to a []int8 738 slice := ir.NewConvExpr(base.Pos, ir.OSTR2BYTESTMP, types.NewSlice(types.Types[types.TINT8]), expr) 739 slice.SetTypecheck(1) // legacy typechecker doesn't handle this op 740 // Load the byte we're splitting on. 741 load := ir.NewIndexExpr(base.Pos, slice, ir.NewInt(int64(bestIdx))) 742 // Compare with the value we're splitting on. 743 cmp := ir.Node(ir.NewBinaryExpr(base.Pos, ir.OLE, load, ir.NewInt(int64(bestByte)))) 744 cmp = typecheck.DefaultLit(typecheck.Expr(cmp), nil) 745 nif := ir.NewIfStmt(base.Pos, cmp, nil, nil) 746 747 var le []exprClause 748 var gt []exprClause 749 for _, c := range cc { 750 s := ir.StringVal(c.lo) 751 if int8(s[bestIdx]) <= bestByte { 752 le = append(le, c) 753 } else { 754 gt = append(gt, c) 755 } 756 } 757 stringSearch(expr, le, &nif.Body) 758 stringSearch(expr, gt, &nif.Else) 759 out.Append(nif) 760 761 // TODO: if expr[bestIdx] has enough different possible values, use a jump table. 762 }