github.com/cloudwego/frugal@v0.1.15/internal/binary/encoder/optimizer.go (about)

     1  /*
     2   * Copyright 2022 ByteDance Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package encoder
    18  
    19  import (
    20      `encoding/binary`
    21      `fmt`
    22      `sort`
    23      `strings`
    24      `unsafe`
    25  
    26      `github.com/cloudwego/frugal/internal/rt`
    27      `github.com/oleiade/lane`
    28  )
    29  
    30  type BasicBlock struct {
    31      P    Program
    32      Src  int
    33      End  int
    34      Next *BasicBlock
    35      Link *BasicBlock
    36  }
    37  
    38  func (self *BasicBlock) Len() int {
    39      return self.End - self.Src
    40  }
    41  
    42  func (self *BasicBlock) Free() {
    43      q := lane.NewQueue()
    44      m := make(map[*BasicBlock]struct{})
    45  
    46      /* traverse the graph with BFS */
    47      for q.Enqueue(self); !q.Empty(); {
    48          v := q.Dequeue()
    49          p := v.(*BasicBlock)
    50  
    51          /* add branch to queue */
    52          if p.Link != nil {
    53              if _, ok := m[p.Link]; !ok {
    54                  q.Enqueue(p.Link)
    55              }
    56          }
    57  
    58          /* clear branch, and add to free list */
    59          m[p] = struct{}{}
    60          p.Link = nil
    61      }
    62  
    63      /* reset and free all the nodes */
    64      for p := range m {
    65          p.Next = nil
    66          freeBasicBlock(p)
    67      }
    68  }
    69  
    70  func (self *BasicBlock) String() string {
    71      n := self.End - self.Src
    72      v := make([]string, n + 1)
    73  
    74      /* dump every instructions */
    75      for i := self.Src; i < self.End; i++ {
    76          v[i - self.Src + 1] = "    " + self.P[i].Disassemble()
    77      }
    78  
    79      /* add the entry label */
    80      v[0] = fmt.Sprintf("L_%d:", self.Src)
    81      return strings.Join(v, "\n")
    82  }
    83  
    84  type GraphBuilder struct {
    85      Pin   map[int]bool
    86      Graph map[int]*BasicBlock
    87  }
    88  
    89  func (self *GraphBuilder) scan(p Program) {
    90      for _, v := range p {
    91          if _OpBranches[v.Op] {
    92              self.Pin[v.To] = true
    93          }
    94      }
    95  }
    96  
    97  func (self *GraphBuilder) block(p Program, i int, bb *BasicBlock) {
    98      bb.Src = i
    99      bb.End = i
   100  
   101      /* traverse down until it hits a branch instruction */
   102      for i < len(p) && !_OpBranches[p[i].Op] {
   103          i++
   104          bb.End++
   105  
   106          /* hit a merge point, merge with existing block */
   107          if self.Pin[i] {
   108              bb.Next = self.branch(p, i)
   109              return
   110          }
   111      }
   112  
   113      /* end of basic block */
   114      if i == len(p) {
   115          return
   116      }
   117  
   118      /* also include the branch instruction */
   119      bb.End++
   120      bb.Next = self.branch(p, p[i].To)
   121  
   122      /* GOTO instruction doesn't technically "branch", anything
   123       * sits between it and the next branch target are unreachable. */
   124      if p[i].Op != OP_goto {
   125          bb.Link = bb.Next
   126          bb.Next = self.branch(p, i + 1)
   127      }
   128  }
   129  
   130  func (self *GraphBuilder) branch(p Program, i int) *BasicBlock {
   131      var ok bool
   132      var bb *BasicBlock
   133  
   134      /* check for existing basic blocks */
   135      if bb, ok = self.Graph[i]; ok {
   136          return bb
   137      }
   138  
   139      /* create a new block */
   140      bb = newBasicBlock()
   141      bb.P, bb.Next, bb.Link = p, nil, nil
   142  
   143      /* process the new block */
   144      self.Graph[i] = bb
   145      self.block(p, i, bb)
   146      return bb
   147  }
   148  
   149  func (self *GraphBuilder) Free() {
   150      rt.MapClear(self.Pin)
   151      rt.MapClear(self.Graph)
   152      freeGraphBuilder(self)
   153  }
   154  
   155  func (self *GraphBuilder) Build(p Program) *BasicBlock {
   156      self.scan(p)
   157      return self.branch(p, 0)
   158  }
   159  
   160  func (self *GraphBuilder) BuildAndFree(p Program) (bb *BasicBlock) {
   161      bb = self.Build(p)
   162      self.Free()
   163      return
   164  }
   165  
   166  type _OptimizerState struct {
   167      buf  []*BasicBlock
   168      refs map[int]int
   169      mask map[*BasicBlock]bool
   170  }
   171  
   172  func (self *_OptimizerState) visit(bb *BasicBlock) bool {
   173      var mm bool
   174      var ok bool
   175  
   176      /* check for duplication */
   177      if mm, ok = self.mask[bb]; mm && ok {
   178          return false
   179      }
   180  
   181      /* add to block buffer */
   182      self.buf = append(self.buf, bb)
   183      self.mask[bb] = true
   184      return true
   185  }
   186  
   187  func Optimize(p Program) Program {
   188      acc := 0
   189      ret := newProgram()
   190      buf := lane.NewQueue()
   191      ctx := newOptimizerState()
   192      cfg := newGraphBuilder().BuildAndFree(p)
   193  
   194      /* travel with BFS */
   195      for buf.Enqueue(cfg); !buf.Empty(); {
   196          v := buf.Dequeue()
   197          b := v.(*BasicBlock)
   198  
   199          /* check for duplication, and then mark as visited */
   200          if !ctx.visit(b) {
   201              continue
   202          }
   203  
   204          /* optimize each block */
   205          for _, pass := range _PassTab {
   206              pass(b)
   207          }
   208  
   209          /* add conditional branches if any */
   210          if b.Next != nil { buf.Enqueue(b.Next) }
   211          if b.Link != nil { buf.Enqueue(b.Link) }
   212      }
   213  
   214      /* sort the blocks by entry point */
   215      sort.Slice(ctx.buf, func(i int, j int) bool {
   216          return ctx.buf[i].Src < ctx.buf[j].Src
   217      })
   218  
   219      /* remap all the branch locations */
   220      for _, bb := range ctx.buf {
   221          ctx.refs[bb.Src] = acc
   222          acc += bb.End - bb.Src
   223      }
   224  
   225      /* adjust all the branch targets */
   226      for _, bb := range ctx.buf {
   227          if end := bb.End; bb.Src != end {
   228              if ins := &bb.P[end - 1]; _OpBranches[ins.Op] {
   229                  ins.To = ctx.refs[ins.To]
   230              }
   231          }
   232      }
   233  
   234      /* merge all the basic blocks */
   235      for _, bb := range ctx.buf {
   236          ret = append(ret, bb.P[bb.Src:bb.End]...)
   237      }
   238  
   239      /* release the original program */
   240      p.Free()
   241      freeOptimizerState(ctx)
   242      return ret
   243  }
   244  
   245  var _PassTab = [...]func(p *BasicBlock) {
   246      _PASS_StaticSizeMerging,
   247      _PASS_SeekMerging,
   248      _PASS_NopElimination,
   249      _PASS_SizeCheckMerging,
   250      _PASS_LiteralMerging,
   251      _PASS_Compacting,
   252  }
   253  
   254  const (
   255      _NOP OpCode = 0xff
   256  )
   257  
   258  func init() {
   259      _OpNames[_NOP] = "(nop)"
   260  }
   261  
   262  func checksl(s *[]byte, n int) *rt.GoSlice {
   263      sl := (*rt.GoSlice)(unsafe.Pointer(s))
   264      sn := sl.Len
   265  
   266      /* check for length */
   267      if sn + n > sl.Cap {
   268          panic("slice overflow")
   269      } else {
   270          return sl
   271      }
   272  }
   273  
   274  func append1(s *[]byte, v byte) {
   275      sl := checksl(s, 1)
   276      sl.Set(sl.Len, v)
   277      sl.Len++
   278  }
   279  
   280  func append2(s *[]byte, v uint16) {
   281      sl := checksl(s, 2)
   282      sl.Set(sl.Len + 0, byte(v >> 8))
   283      sl.Set(sl.Len + 1, byte(v))
   284      sl.Len += 2
   285  }
   286  
   287  func append4(s *[]byte, v uint32) {
   288      sl := checksl(s, 4)
   289      sl.Set(sl.Len + 0, byte(v >> 24))
   290      sl.Set(sl.Len + 1, byte(v >> 16))
   291      sl.Set(sl.Len + 2, byte(v >> 8))
   292      sl.Set(sl.Len + 3, byte(v))
   293      sl.Len += 4
   294  }
   295  
   296  func append8(s *[]byte, v uint64) {
   297      sl := checksl(s, 8)
   298      sl.Set(sl.Len + 0, byte(v >> 56))
   299      sl.Set(sl.Len + 1, byte(v >> 48))
   300      sl.Set(sl.Len + 2, byte(v >> 40))
   301      sl.Set(sl.Len + 3, byte(v >> 32))
   302      sl.Set(sl.Len + 4, byte(v >> 24))
   303      sl.Set(sl.Len + 5, byte(v >> 16))
   304      sl.Set(sl.Len + 6, byte(v >> 8))
   305      sl.Set(sl.Len + 7, byte(v))
   306      sl.Len += 8
   307  }
   308  
   309  // Static Size Merging Pass: merges constant size instructions as much as possible.
   310  func _PASS_StaticSizeMerging(bb *BasicBlock) {
   311      for i := bb.Src; i < bb.End; i++ {
   312          if p := &bb.P[i]; p.Op == OP_size_const {
   313              for r, j := true, i + 1; r && j < bb.End; i, j = i + 1, j + 1 {
   314                  switch bb.P[j].Op {
   315                      case _NOP          : break
   316                      case OP_seek       : break
   317                      case OP_deref      : break
   318                      case OP_size_dyn   : break
   319                      case OP_size_const : p.Iv += bb.P[j].Iv; bb.P[j].Op = _NOP
   320                      default            : r = false
   321                  }
   322              }
   323          }
   324      }
   325  }
   326  
   327  // Seek Merging Pass: merges seeking instructions as much as possible.
   328  func _PASS_SeekMerging(bb *BasicBlock) {
   329      for i := bb.Src; i < bb.End; i++ {
   330          if p := &bb.P[i]; p.Op == OP_seek {
   331              for r, j := true, i + 1; r && j < bb.End; i, j = i + 1, j + 1 {
   332                  switch bb.P[j].Op {
   333                      case _NOP    : break
   334                      case OP_seek : p.Iv += bb.P[j].Iv; bb.P[j].Op = _NOP
   335                      default      : r = false
   336                  }
   337              }
   338          }
   339      }
   340  }
   341  
   342  // NOP Elimination Pass: remove instructions that are effectively NOPs (`seek 0`, `size_const 0`)
   343  func _PASS_NopElimination(bb *BasicBlock) {
   344      for i := bb.Src; i < bb.End; i++ {
   345          if bb.P[i].Iv == 0 && (bb.P[i].Op == OP_seek || bb.P[i].Op == OP_size_const) {
   346              bb.P[i].Op = _NOP
   347          }
   348      }
   349  }
   350  
   351  // Size Check Merging Pass: merges size-checking instructions as much as possible.
   352  func _PASS_SizeCheckMerging(bb *BasicBlock) {
   353      for i := bb.Src; i < bb.End; i++ {
   354          if p := &bb.P[i]; p.Op == OP_size_check {
   355              for r, j := true, i + 1; r && j < bb.End; i, j = i + 1, j + 1 {
   356                  switch bb.P[j].Op {
   357                      case _NOP          : break
   358                      case OP_byte       : break
   359                      case OP_word       : break
   360                      case OP_long       : break
   361                      case OP_quad       : break
   362                      case OP_sint       : break
   363                      case OP_seek       : break
   364                      case OP_deref      : break
   365                      case OP_length     : break
   366                      case OP_memcpy_be  : break
   367                      case OP_size_check : p.Iv += bb.P[j].Iv; bb.P[j].Op = _NOP
   368                      default            : r = false
   369                  }
   370              }
   371          }
   372      }
   373  }
   374  
   375  // Literal Merging Pass: merges all consectutive byte, word or long instructions.
   376  func _PASS_LiteralMerging(bb *BasicBlock) {
   377      p := bb.P
   378      i := bb.Src
   379  
   380      /* scan every instruction */
   381      for i < bb.End {
   382          iv := p[i]
   383          op := iv.Op
   384  
   385          /* only interested in literal instructions */
   386          if op < OP_byte || op > OP_quad {
   387              i++
   388              continue
   389          }
   390  
   391          /* byte merging buffer */
   392          ip := i
   393          mm := [15]byte{}
   394          sl := mm[:0:cap(mm)]
   395  
   396          /* scan for consecutive bytes */
   397          loop: for i < bb.End {
   398              iv = p[i]
   399              op = iv.Op
   400  
   401              /* check for OpCode */
   402              switch op {
   403                  case _NOP     : i++; continue
   404                  case OP_seek  : i++; continue
   405                  case OP_deref : i++; continue
   406                  case OP_byte  : append1(&sl, byte(iv.Iv))
   407                  case OP_word  : append2(&sl, uint16(iv.Iv))
   408                  case OP_long  : append4(&sl, uint32(iv.Iv))
   409                  case OP_quad  : append8(&sl, uint64(iv.Iv))
   410                  default       : break loop
   411              }
   412  
   413              /* adjust the program counter */
   414              p[i].Op = _NOP
   415              i++
   416  
   417              /* commit the buffer if needed */
   418              for len(sl) >= 8 {
   419                  p[ip] = Instr{Op: OP_quad, Iv: int64(binary.BigEndian.Uint64(sl))}
   420                  sl = sl[8:]
   421                  ip++
   422              }
   423  
   424              /* move the remaining bytes to the front */
   425              copy(mm[:], sl)
   426              sl = mm[:len(sl):cap(mm)]
   427          }
   428  
   429          /* add the remaining bytes */
   430          if len(sl) >= 4 { p[ip] = Instr{Op: OP_long, Iv: int64(binary.BigEndian.Uint32(sl))} ; sl = sl[4:]; ip++ }
   431          if len(sl) >= 2 { p[ip] = Instr{Op: OP_word, Iv: int64(binary.BigEndian.Uint16(sl))} ; sl = sl[2:]; ip++ }
   432          if len(sl) >= 1 { p[ip] = Instr{Op: OP_byte, Iv: int64(sl[0])}                       ; sl = sl[1:]; ip++ }
   433      }
   434  }
   435  
   436  // Compacting Pass: remove all the placeholder NOP instructions inserted in the previous pass.
   437  func _PASS_Compacting(bb *BasicBlock) {
   438      var i int
   439      var j int
   440  
   441      /* copy instructins excluding NOPs */
   442      for i, j = bb.Src, bb.Src; i < bb.End; i++ {
   443          if bb.P[i].Op != _NOP {
   444              bb.P[j] = bb.P[i]
   445              j++
   446          }
   447      }
   448  
   449      /* update basic block end if needed */
   450      if i != j {
   451          bb.End = j
   452      }
   453  }