github.com/corona10/go@v0.0.0-20180224231303-7a218942be57/src/cmd/compile/internal/ssa/rewrite.go (about) 1 // Copyright 2015 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 ssa 6 7 import ( 8 "cmd/compile/internal/types" 9 "cmd/internal/obj" 10 "fmt" 11 "io" 12 "math" 13 "os" 14 "path/filepath" 15 ) 16 17 func applyRewrite(f *Func, rb blockRewriter, rv valueRewriter) { 18 // repeat rewrites until we find no more rewrites 19 for { 20 change := false 21 for _, b := range f.Blocks { 22 if b.Control != nil && b.Control.Op == OpCopy { 23 for b.Control.Op == OpCopy { 24 b.SetControl(b.Control.Args[0]) 25 } 26 } 27 if rb(b) { 28 change = true 29 } 30 for _, v := range b.Values { 31 change = phielimValue(v) || change 32 33 // Eliminate copy inputs. 34 // If any copy input becomes unused, mark it 35 // as invalid and discard its argument. Repeat 36 // recursively on the discarded argument. 37 // This phase helps remove phantom "dead copy" uses 38 // of a value so that a x.Uses==1 rule condition 39 // fires reliably. 40 for i, a := range v.Args { 41 if a.Op != OpCopy { 42 continue 43 } 44 v.SetArg(i, copySource(a)) 45 change = true 46 for a.Uses == 0 { 47 b := a.Args[0] 48 a.reset(OpInvalid) 49 a = b 50 } 51 } 52 53 // apply rewrite function 54 if rv(v) { 55 change = true 56 } 57 } 58 } 59 if !change { 60 break 61 } 62 } 63 // remove clobbered values 64 for _, b := range f.Blocks { 65 j := 0 66 for i, v := range b.Values { 67 if v.Op == OpInvalid { 68 f.freeValue(v) 69 continue 70 } 71 if i != j { 72 b.Values[j] = v 73 } 74 j++ 75 } 76 if j != len(b.Values) { 77 tail := b.Values[j:] 78 for j := range tail { 79 tail[j] = nil 80 } 81 b.Values = b.Values[:j] 82 } 83 } 84 } 85 86 // Common functions called from rewriting rules 87 88 func is64BitFloat(t *types.Type) bool { 89 return t.Size() == 8 && t.IsFloat() 90 } 91 92 func is32BitFloat(t *types.Type) bool { 93 return t.Size() == 4 && t.IsFloat() 94 } 95 96 func is64BitInt(t *types.Type) bool { 97 return t.Size() == 8 && t.IsInteger() 98 } 99 100 func is32BitInt(t *types.Type) bool { 101 return t.Size() == 4 && t.IsInteger() 102 } 103 104 func is16BitInt(t *types.Type) bool { 105 return t.Size() == 2 && t.IsInteger() 106 } 107 108 func is8BitInt(t *types.Type) bool { 109 return t.Size() == 1 && t.IsInteger() 110 } 111 112 func isPtr(t *types.Type) bool { 113 return t.IsPtrShaped() 114 } 115 116 func isSigned(t *types.Type) bool { 117 return t.IsSigned() 118 } 119 120 // mergeSym merges two symbolic offsets. There is no real merging of 121 // offsets, we just pick the non-nil one. 122 func mergeSym(x, y interface{}) interface{} { 123 if x == nil { 124 return y 125 } 126 if y == nil { 127 return x 128 } 129 panic(fmt.Sprintf("mergeSym with two non-nil syms %s %s", x, y)) 130 } 131 func canMergeSym(x, y interface{}) bool { 132 return x == nil || y == nil 133 } 134 135 // canMergeLoad reports whether the load can be merged into target without 136 // invalidating the schedule. 137 // It also checks that the other non-load argument x is something we 138 // are ok with clobbering (all our current load+op instructions clobber 139 // their input register). 140 func canMergeLoad(target, load, x *Value) bool { 141 if target.Block.ID != load.Block.ID { 142 // If the load is in a different block do not merge it. 143 return false 144 } 145 146 // We can't merge the load into the target if the load 147 // has more than one use. 148 if load.Uses != 1 { 149 return false 150 } 151 152 // The register containing x is going to get clobbered. 153 // Don't merge if we still need the value of x. 154 // We don't have liveness information here, but we can 155 // approximate x dying with: 156 // 1) target is x's only use. 157 // 2) target is not in a deeper loop than x. 158 if x.Uses != 1 { 159 return false 160 } 161 loopnest := x.Block.Func.loopnest() 162 loopnest.calculateDepths() 163 if loopnest.depth(target.Block.ID) > loopnest.depth(x.Block.ID) { 164 return false 165 } 166 167 mem := load.MemoryArg() 168 169 // We need the load's memory arg to still be alive at target. That 170 // can't be the case if one of target's args depends on a memory 171 // state that is a successor of load's memory arg. 172 // 173 // For example, it would be invalid to merge load into target in 174 // the following situation because newmem has killed oldmem 175 // before target is reached: 176 // load = read ... oldmem 177 // newmem = write ... oldmem 178 // arg0 = read ... newmem 179 // target = add arg0 load 180 // 181 // If the argument comes from a different block then we can exclude 182 // it immediately because it must dominate load (which is in the 183 // same block as target). 184 var args []*Value 185 for _, a := range target.Args { 186 if a != load && a.Block.ID == target.Block.ID { 187 args = append(args, a) 188 } 189 } 190 191 // memPreds contains memory states known to be predecessors of load's 192 // memory state. It is lazily initialized. 193 var memPreds map[*Value]bool 194 search: 195 for i := 0; len(args) > 0; i++ { 196 const limit = 100 197 if i >= limit { 198 // Give up if we have done a lot of iterations. 199 return false 200 } 201 v := args[len(args)-1] 202 args = args[:len(args)-1] 203 if target.Block.ID != v.Block.ID { 204 // Since target and load are in the same block 205 // we can stop searching when we leave the block. 206 continue search 207 } 208 if v.Op == OpPhi { 209 // A Phi implies we have reached the top of the block. 210 // The memory phi, if it exists, is always 211 // the first logical store in the block. 212 continue search 213 } 214 if v.Type.IsTuple() && v.Type.FieldType(1).IsMemory() { 215 // We could handle this situation however it is likely 216 // to be very rare. 217 return false 218 } 219 if v.Type.IsMemory() { 220 if memPreds == nil { 221 // Initialise a map containing memory states 222 // known to be predecessors of load's memory 223 // state. 224 memPreds = make(map[*Value]bool) 225 m := mem 226 const limit = 50 227 for i := 0; i < limit; i++ { 228 if m.Op == OpPhi { 229 // The memory phi, if it exists, is always 230 // the first logical store in the block. 231 break 232 } 233 if m.Block.ID != target.Block.ID { 234 break 235 } 236 if !m.Type.IsMemory() { 237 break 238 } 239 memPreds[m] = true 240 if len(m.Args) == 0 { 241 break 242 } 243 m = m.MemoryArg() 244 } 245 } 246 247 // We can merge if v is a predecessor of mem. 248 // 249 // For example, we can merge load into target in the 250 // following scenario: 251 // x = read ... v 252 // mem = write ... v 253 // load = read ... mem 254 // target = add x load 255 if memPreds[v] { 256 continue search 257 } 258 return false 259 } 260 if len(v.Args) > 0 && v.Args[len(v.Args)-1] == mem { 261 // If v takes mem as an input then we know mem 262 // is valid at this point. 263 continue search 264 } 265 for _, a := range v.Args { 266 if target.Block.ID == a.Block.ID { 267 args = append(args, a) 268 } 269 } 270 } 271 272 return true 273 } 274 275 // isSameSym returns whether sym is the same as the given named symbol 276 func isSameSym(sym interface{}, name string) bool { 277 s, ok := sym.(fmt.Stringer) 278 return ok && s.String() == name 279 } 280 281 // nlz returns the number of leading zeros. 282 func nlz(x int64) int64 { 283 // log2(0) == 1, so nlz(0) == 64 284 return 63 - log2(x) 285 } 286 287 // ntz returns the number of trailing zeros. 288 func ntz(x int64) int64 { 289 return 64 - nlz(^x&(x-1)) 290 } 291 292 func oneBit(x int64) bool { 293 return nlz(x)+ntz(x) == 63 294 } 295 296 // nlo returns the number of leading ones. 297 func nlo(x int64) int64 { 298 return nlz(^x) 299 } 300 301 // nto returns the number of trailing ones. 302 func nto(x int64) int64 { 303 return ntz(^x) 304 } 305 306 // log2 returns logarithm in base 2 of uint64(n), with log2(0) = -1. 307 // Rounds down. 308 func log2(n int64) (l int64) { 309 l = -1 310 x := uint64(n) 311 for ; x >= 0x8000; x >>= 16 { 312 l += 16 313 } 314 if x >= 0x80 { 315 x >>= 8 316 l += 8 317 } 318 if x >= 0x8 { 319 x >>= 4 320 l += 4 321 } 322 if x >= 0x2 { 323 x >>= 2 324 l += 2 325 } 326 if x >= 0x1 { 327 l++ 328 } 329 return 330 } 331 332 // isPowerOfTwo reports whether n is a power of 2. 333 func isPowerOfTwo(n int64) bool { 334 return n > 0 && n&(n-1) == 0 335 } 336 337 // is32Bit reports whether n can be represented as a signed 32 bit integer. 338 func is32Bit(n int64) bool { 339 return n == int64(int32(n)) 340 } 341 342 // is16Bit reports whether n can be represented as a signed 16 bit integer. 343 func is16Bit(n int64) bool { 344 return n == int64(int16(n)) 345 } 346 347 // isU12Bit reports whether n can be represented as an unsigned 12 bit integer. 348 func isU12Bit(n int64) bool { 349 return 0 <= n && n < (1<<12) 350 } 351 352 // isU16Bit reports whether n can be represented as an unsigned 16 bit integer. 353 func isU16Bit(n int64) bool { 354 return n == int64(uint16(n)) 355 } 356 357 // isU32Bit reports whether n can be represented as an unsigned 32 bit integer. 358 func isU32Bit(n int64) bool { 359 return n == int64(uint32(n)) 360 } 361 362 // is20Bit reports whether n can be represented as a signed 20 bit integer. 363 func is20Bit(n int64) bool { 364 return -(1<<19) <= n && n < (1<<19) 365 } 366 367 // b2i translates a boolean value to 0 or 1 for assigning to auxInt. 368 func b2i(b bool) int64 { 369 if b { 370 return 1 371 } 372 return 0 373 } 374 375 // i2f is used in rules for converting from an AuxInt to a float. 376 func i2f(i int64) float64 { 377 return math.Float64frombits(uint64(i)) 378 } 379 380 // i2f32 is used in rules for converting from an AuxInt to a float32. 381 func i2f32(i int64) float32 { 382 return float32(math.Float64frombits(uint64(i))) 383 } 384 385 // f2i is used in the rules for storing a float in AuxInt. 386 func f2i(f float64) int64 { 387 return int64(math.Float64bits(f)) 388 } 389 390 // uaddOvf returns true if unsigned a+b would overflow. 391 func uaddOvf(a, b int64) bool { 392 return uint64(a)+uint64(b) < uint64(a) 393 } 394 395 // de-virtualize an InterCall 396 // 'sym' is the symbol for the itab 397 func devirt(v *Value, sym interface{}, offset int64) *obj.LSym { 398 f := v.Block.Func 399 n, ok := sym.(*obj.LSym) 400 if !ok { 401 return nil 402 } 403 lsym := f.fe.DerefItab(n, offset) 404 if f.pass.debug > 0 { 405 if lsym != nil { 406 f.Warnl(v.Pos, "de-virtualizing call") 407 } else { 408 f.Warnl(v.Pos, "couldn't de-virtualize call") 409 } 410 } 411 return lsym 412 } 413 414 // isSamePtr reports whether p1 and p2 point to the same address. 415 func isSamePtr(p1, p2 *Value) bool { 416 if p1 == p2 { 417 return true 418 } 419 if p1.Op != p2.Op { 420 return false 421 } 422 switch p1.Op { 423 case OpOffPtr: 424 return p1.AuxInt == p2.AuxInt && isSamePtr(p1.Args[0], p2.Args[0]) 425 case OpAddr: 426 // OpAddr's 0th arg is either OpSP or OpSB, which means that it is uniquely identified by its Op. 427 // Checking for value equality only works after [z]cse has run. 428 return p1.Aux == p2.Aux && p1.Args[0].Op == p2.Args[0].Op 429 case OpAddPtr: 430 return p1.Args[1] == p2.Args[1] && isSamePtr(p1.Args[0], p2.Args[0]) 431 } 432 return false 433 } 434 435 // moveSize returns the number of bytes an aligned MOV instruction moves 436 func moveSize(align int64, c *Config) int64 { 437 switch { 438 case align%8 == 0 && c.PtrSize == 8: 439 return 8 440 case align%4 == 0: 441 return 4 442 case align%2 == 0: 443 return 2 444 } 445 return 1 446 } 447 448 // mergePoint finds a block among a's blocks which dominates b and is itself 449 // dominated by all of a's blocks. Returns nil if it can't find one. 450 // Might return nil even if one does exist. 451 func mergePoint(b *Block, a ...*Value) *Block { 452 // Walk backward from b looking for one of the a's blocks. 453 454 // Max distance 455 d := 100 456 457 for d > 0 { 458 for _, x := range a { 459 if b == x.Block { 460 goto found 461 } 462 } 463 if len(b.Preds) > 1 { 464 // Don't know which way to go back. Abort. 465 return nil 466 } 467 b = b.Preds[0].b 468 d-- 469 } 470 return nil // too far away 471 found: 472 // At this point, r is the first value in a that we find by walking backwards. 473 // if we return anything, r will be it. 474 r := b 475 476 // Keep going, counting the other a's that we find. They must all dominate r. 477 na := 0 478 for d > 0 { 479 for _, x := range a { 480 if b == x.Block { 481 na++ 482 } 483 } 484 if na == len(a) { 485 // Found all of a in a backwards walk. We can return r. 486 return r 487 } 488 if len(b.Preds) > 1 { 489 return nil 490 } 491 b = b.Preds[0].b 492 d-- 493 494 } 495 return nil // too far away 496 } 497 498 // clobber invalidates v. Returns true. 499 // clobber is used by rewrite rules to: 500 // A) make sure v is really dead and never used again. 501 // B) decrement use counts of v's args. 502 func clobber(v *Value) bool { 503 v.reset(OpInvalid) 504 // Note: leave v.Block intact. The Block field is used after clobber. 505 return true 506 } 507 508 // clobberIfDead resets v when use count is 1. Returns true. 509 // clobberIfDead is used by rewrite rules to decrement 510 // use counts of v's args when v is dead and never used. 511 func clobberIfDead(v *Value) bool { 512 if v.Uses == 1 { 513 v.reset(OpInvalid) 514 } 515 // Note: leave v.Block intact. The Block field is used after clobberIfDead. 516 return true 517 } 518 519 // noteRule is an easy way to track if a rule is matched when writing 520 // new ones. Make the rule of interest also conditional on 521 // noteRule("note to self: rule of interest matched") 522 // and that message will print when the rule matches. 523 func noteRule(s string) bool { 524 fmt.Println(s) 525 return true 526 } 527 528 // warnRule generates a compiler debug output with string s when 529 // cond is true and the rule is fired. 530 func warnRule(cond bool, v *Value, s string) bool { 531 if cond { 532 v.Block.Func.Warnl(v.Pos, s) 533 } 534 return true 535 } 536 537 // for a pseudo-op like (LessThan x), extract x 538 func flagArg(v *Value) *Value { 539 if len(v.Args) != 1 || !v.Args[0].Type.IsFlags() { 540 return nil 541 } 542 return v.Args[0] 543 } 544 545 // arm64Negate finds the complement to an ARM64 condition code, 546 // for example Equal -> NotEqual or LessThan -> GreaterEqual 547 // 548 // TODO: add floating-point conditions 549 func arm64Negate(op Op) Op { 550 switch op { 551 case OpARM64LessThan: 552 return OpARM64GreaterEqual 553 case OpARM64LessThanU: 554 return OpARM64GreaterEqualU 555 case OpARM64GreaterThan: 556 return OpARM64LessEqual 557 case OpARM64GreaterThanU: 558 return OpARM64LessEqualU 559 case OpARM64LessEqual: 560 return OpARM64GreaterThan 561 case OpARM64LessEqualU: 562 return OpARM64GreaterThanU 563 case OpARM64GreaterEqual: 564 return OpARM64LessThan 565 case OpARM64GreaterEqualU: 566 return OpARM64LessThanU 567 case OpARM64Equal: 568 return OpARM64NotEqual 569 case OpARM64NotEqual: 570 return OpARM64Equal 571 default: 572 panic("unreachable") 573 } 574 } 575 576 // arm64Invert evaluates (InvertFlags op), which 577 // is the same as altering the condition codes such 578 // that the same result would be produced if the arguments 579 // to the flag-generating instruction were reversed, e.g. 580 // (InvertFlags (CMP x y)) -> (CMP y x) 581 // 582 // TODO: add floating-point conditions 583 func arm64Invert(op Op) Op { 584 switch op { 585 case OpARM64LessThan: 586 return OpARM64GreaterThan 587 case OpARM64LessThanU: 588 return OpARM64GreaterThanU 589 case OpARM64GreaterThan: 590 return OpARM64LessThan 591 case OpARM64GreaterThanU: 592 return OpARM64LessThanU 593 case OpARM64LessEqual: 594 return OpARM64GreaterEqual 595 case OpARM64LessEqualU: 596 return OpARM64GreaterEqualU 597 case OpARM64GreaterEqual: 598 return OpARM64LessEqual 599 case OpARM64GreaterEqualU: 600 return OpARM64LessEqualU 601 case OpARM64Equal, OpARM64NotEqual: 602 return op 603 default: 604 panic("unreachable") 605 } 606 } 607 608 // evaluate an ARM64 op against a flags value 609 // that is potentially constant; return 1 for true, 610 // -1 for false, and 0 for not constant. 611 func ccARM64Eval(cc interface{}, flags *Value) int { 612 op := cc.(Op) 613 fop := flags.Op 614 switch fop { 615 case OpARM64InvertFlags: 616 return -ccARM64Eval(op, flags.Args[0]) 617 case OpARM64FlagEQ: 618 switch op { 619 case OpARM64Equal, OpARM64GreaterEqual, OpARM64LessEqual, 620 OpARM64GreaterEqualU, OpARM64LessEqualU: 621 return 1 622 default: 623 return -1 624 } 625 case OpARM64FlagLT_ULT: 626 switch op { 627 case OpARM64LessThan, OpARM64LessThanU, 628 OpARM64LessEqual, OpARM64LessEqualU: 629 return 1 630 default: 631 return -1 632 } 633 case OpARM64FlagLT_UGT: 634 switch op { 635 case OpARM64LessThan, OpARM64GreaterThanU, 636 OpARM64LessEqual, OpARM64GreaterEqualU: 637 return 1 638 default: 639 return -1 640 } 641 case OpARM64FlagGT_ULT: 642 switch op { 643 case OpARM64GreaterThan, OpARM64LessThanU, 644 OpARM64GreaterEqual, OpARM64LessEqualU: 645 return 1 646 default: 647 return -1 648 } 649 case OpARM64FlagGT_UGT: 650 switch op { 651 case OpARM64GreaterThan, OpARM64GreaterThanU, 652 OpARM64GreaterEqual, OpARM64GreaterEqualU: 653 return 1 654 default: 655 return -1 656 } 657 default: 658 return 0 659 } 660 } 661 662 // logRule logs the use of the rule s. This will only be enabled if 663 // rewrite rules were generated with the -log option, see gen/rulegen.go. 664 func logRule(s string) { 665 if ruleFile == nil { 666 // Open a log file to write log to. We open in append 667 // mode because all.bash runs the compiler lots of times, 668 // and we want the concatenation of all of those logs. 669 // This means, of course, that users need to rm the old log 670 // to get fresh data. 671 // TODO: all.bash runs compilers in parallel. Need to synchronize logging somehow? 672 w, err := os.OpenFile(filepath.Join(os.Getenv("GOROOT"), "src", "rulelog"), 673 os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 674 if err != nil { 675 panic(err) 676 } 677 ruleFile = w 678 } 679 _, err := fmt.Fprintf(ruleFile, "rewrite %s\n", s) 680 if err != nil { 681 panic(err) 682 } 683 } 684 685 var ruleFile io.Writer 686 687 func min(x, y int64) int64 { 688 if x < y { 689 return x 690 } 691 return y 692 } 693 694 func isConstZero(v *Value) bool { 695 switch v.Op { 696 case OpConstNil: 697 return true 698 case OpConst64, OpConst32, OpConst16, OpConst8, OpConstBool, OpConst32F, OpConst64F: 699 return v.AuxInt == 0 700 } 701 return false 702 } 703 704 // reciprocalExact64 reports whether 1/c is exactly representable. 705 func reciprocalExact64(c float64) bool { 706 b := math.Float64bits(c) 707 man := b & (1<<52 - 1) 708 if man != 0 { 709 return false // not a power of 2, denormal, or NaN 710 } 711 exp := b >> 52 & (1<<11 - 1) 712 // exponent bias is 0x3ff. So taking the reciprocal of a number 713 // changes the exponent to 0x7fe-exp. 714 switch exp { 715 case 0: 716 return false // ±0 717 case 0x7ff: 718 return false // ±inf 719 case 0x7fe: 720 return false // exponent is not representable 721 default: 722 return true 723 } 724 } 725 726 // reciprocalExact32 reports whether 1/c is exactly representable. 727 func reciprocalExact32(c float32) bool { 728 b := math.Float32bits(c) 729 man := b & (1<<23 - 1) 730 if man != 0 { 731 return false // not a power of 2, denormal, or NaN 732 } 733 exp := b >> 23 & (1<<8 - 1) 734 // exponent bias is 0x7f. So taking the reciprocal of a number 735 // changes the exponent to 0xfe-exp. 736 switch exp { 737 case 0: 738 return false // ±0 739 case 0xff: 740 return false // ±inf 741 case 0xfe: 742 return false // exponent is not representable 743 default: 744 return true 745 } 746 } 747 748 // check if an immediate can be directly encoded into an ARM's instruction 749 func isARMImmRot(v uint32) bool { 750 for i := 0; i < 16; i++ { 751 if v&^0xff == 0 { 752 return true 753 } 754 v = v<<2 | v>>30 755 } 756 757 return false 758 } 759 760 // overlap reports whether the ranges given by the given offset and 761 // size pairs overlap. 762 func overlap(offset1, size1, offset2, size2 int64) bool { 763 if offset1 >= offset2 && offset2+size2 > offset1 { 764 return true 765 } 766 if offset2 >= offset1 && offset1+size1 > offset2 { 767 return true 768 } 769 return false 770 } 771 772 // check if value zeroes out upper 32-bit of 64-bit register. 773 // depth limits recursion depth. In AMD64.rules 3 is used as limit, 774 // because it catches same amount of cases as 4. 775 func zeroUpper32Bits(x *Value, depth int) bool { 776 switch x.Op { 777 case OpAMD64MOVLconst, OpAMD64MOVLload, OpAMD64MOVLQZX, OpAMD64MOVLloadidx1, 778 OpAMD64MOVWload, OpAMD64MOVWloadidx1, OpAMD64MOVBload, OpAMD64MOVBloadidx1, 779 OpAMD64MOVLloadidx4, OpAMD64ADDLmem, OpAMD64SUBLmem, OpAMD64ANDLmem, 780 OpAMD64ORLmem, OpAMD64XORLmem, OpAMD64CVTTSD2SL, 781 OpAMD64ADDL, OpAMD64ADDLconst, OpAMD64SUBL, OpAMD64SUBLconst, 782 OpAMD64ANDL, OpAMD64ANDLconst, OpAMD64ORL, OpAMD64ORLconst, 783 OpAMD64XORL, OpAMD64XORLconst, OpAMD64NEGL, OpAMD64NOTL: 784 return true 785 case OpArg: 786 return x.Type.Width == 4 787 case OpSelect0, OpSelect1: 788 // Disabled for now. See issue 23305. 789 // TODO: we could look into the arg of the Select to decide. 790 return false 791 case OpPhi: 792 // Phis can use each-other as an arguments, instead of tracking visited values, 793 // just limit recursion depth. 794 if depth <= 0 { 795 return false 796 } 797 for i := range x.Args { 798 if !zeroUpper32Bits(x.Args[i], depth-1) { 799 return false 800 } 801 } 802 return true 803 804 } 805 return false 806 } 807 808 // inlineablememmovesize reports whether the given arch performs OpMove of the given size 809 // faster than memmove and in a safe way when src and dst overlap. 810 // This is used as a check for replacing memmove with OpMove. 811 func isInlinableMemmoveSize(sz int64, c *Config) bool { 812 switch c.arch { 813 case "amd64", "amd64p32": 814 return sz <= 16 815 case "386", "ppc64", "s390x", "ppc64le": 816 return sz <= 8 817 case "arm", "mips", "mips64", "mipsle", "mips64le": 818 return sz <= 4 819 } 820 return false 821 }