github.com/cloudwego/frugal@v0.1.15/internal/binary/decoder/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 decoder
    18  
    19  import (
    20      `fmt`
    21      `sort`
    22      `strings`
    23  
    24      `github.com/cloudwego/frugal/internal/rt`
    25      `github.com/oleiade/lane`
    26  )
    27  
    28  type BasicBlock struct {
    29      P    Program
    30      Src  int
    31      End  int
    32      Link []*BasicBlock
    33  }
    34  
    35  func (self *BasicBlock) Len() int {
    36      return self.End - self.Src
    37  }
    38  
    39  func (self *BasicBlock) Free() {
    40      q := lane.NewQueue()
    41      m := make(map[*BasicBlock]struct{})
    42  
    43      /* traverse the graph with BFS */
    44      for q.Enqueue(self); !q.Empty(); {
    45          v := q.Dequeue()
    46          p := v.(*BasicBlock)
    47  
    48          /* add branch to queue */
    49          for _, b := range p.Link {
    50              q.Enqueue(b)
    51          }
    52  
    53          /* clear branch, and add to free list */
    54          m[p] = struct{}{}
    55          p.Link = p.Link[:0]
    56      }
    57  
    58      /* reset and free all the nodes */
    59      for p := range m {
    60          freeBasicBlock(p)
    61      }
    62  }
    63  
    64  func (self *BasicBlock) String() string {
    65      n := self.End - self.Src
    66      v := make([]string, n + 1)
    67  
    68      /* dump every instructions */
    69      for i := self.Src; i < self.End; i++ {
    70          v[i - self.Src + 1] = "    " + self.P[i].Disassemble()
    71      }
    72  
    73      /* add the entry label */
    74      v[0] = fmt.Sprintf("L_%d:", self.Src)
    75      return strings.Join(v, "\n")
    76  }
    77  
    78  type GraphBuilder struct {
    79      Pin   map[int]bool
    80      Graph map[int]*BasicBlock
    81  }
    82  
    83  func (self *GraphBuilder) scan(p Program) {
    84      for _, v := range p {
    85          if _OpBranches[v.Op] {
    86              self.Pin[v.To] = true
    87          }
    88      }
    89  }
    90  
    91  func (self *GraphBuilder) block(p Program, i int, bb *BasicBlock) {
    92      bb.Src = i
    93      bb.End = i
    94  
    95      /* traverse down until it hits a branch instruction */
    96      for i < len(p) && !_OpBranches[p[i].Op] {
    97          i++
    98          bb.End++
    99  
   100          /* hit a merge point, merge with existing block */
   101          if self.Pin[i] {
   102              bb.Link = append(bb.Link, self.branch(p, i))
   103              return
   104          }
   105      }
   106  
   107      /* end of basic block */
   108      if i == len(p) {
   109          return
   110      }
   111  
   112      /* also include the branch instruction */
   113      if bb.End++; p[i].Op != OP_struct_switch {
   114          bb.Link = append(bb.Link, self.branch(p, p[i].To))
   115      } else {
   116          for _, v := range p[i].IntSeq() {
   117              if v >= 0 {
   118                  bb.Link = append(bb.Link, self.branch(p, v))
   119              }
   120          }
   121      }
   122  
   123      /* GOTO instruction doesn't technically "branch", anything
   124       * sits between it and the next branch target are unreachable. */
   125      if p[i].Op != OP_goto {
   126          bb.Link = append(bb.Link, 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.Link = p, bb.Link[:0]
   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          for _, q := range b.Link {
   211              buf.Enqueue(q)
   212          }
   213      }
   214  
   215      /* sort the blocks by entry point */
   216      sort.Slice(ctx.buf, func(i int, j int) bool {
   217          return ctx.buf[i].Src < ctx.buf[j].Src
   218      })
   219  
   220      /* remap all the branch locations */
   221      for _, bb := range ctx.buf {
   222          ctx.refs[bb.Src] = acc
   223          acc += bb.End - bb.Src
   224      }
   225  
   226      /* adjust all the branch targets */
   227      for _, bb := range ctx.buf {
   228          if end := bb.End; bb.Src != end {
   229              if ins := &bb.P[end - 1]; _OpBranches[ins.Op] {
   230                  if ins.Op != OP_struct_switch {
   231                      ins.To = ctx.refs[ins.To]
   232                  } else {
   233                      for i, v := range ins.IntSeq() {
   234                          if v >= 0 {
   235                              ins.IntSeq()[i] = ctx.refs[v]
   236                          }
   237                      }
   238                  }
   239              }
   240          }
   241      }
   242  
   243      /* merge all the basic blocks */
   244      for _, bb := range ctx.buf {
   245          ret = append(ret, bb.P[bb.Src:bb.End]...)
   246      }
   247  
   248      /* release the original program */
   249      p.Free()
   250      freeOptimizerState(ctx)
   251      return ret
   252  }
   253  
   254  var _PassTab = [...]func(p *BasicBlock) {
   255      _PASS_SeekMerging,
   256      _PASS_NopElimination,
   257      _PASS_Compacting,
   258  }
   259  
   260  const (
   261      _NOP OpCode = 0xff
   262  )
   263  
   264  func init() {
   265      _OpNames[_NOP] = "(nop)"
   266  }
   267  
   268  // Seek Merging Pass: merges seeking instructions as much as possible.
   269  func _PASS_SeekMerging(bb *BasicBlock) {
   270      for i := bb.Src; i < bb.End; i++ {
   271          if p := &bb.P[i]; p.Op == OP_seek {
   272              for r, j := true, i + 1; r && j < bb.End; i, j = i + 1, j + 1 {
   273                  switch bb.P[j].Op {
   274                      case _NOP    : break
   275                      case OP_seek : p.Iv += bb.P[j].Iv; bb.P[j].Op = _NOP
   276                      default      : r = false
   277                  }
   278              }
   279          }
   280      }
   281  }
   282  
   283  // NOP Elimination Pass: remove instructions that are effectively NOPs (`seek 0`)
   284  func _PASS_NopElimination(bb *BasicBlock) {
   285      for i := bb.Src; i < bb.End; i++ {
   286          if bb.P[i].Iv == 0 && bb.P[i].Op == OP_seek {
   287              bb.P[i].Op = _NOP
   288          }
   289      }
   290  }
   291  
   292  // Compacting Pass: remove all the placeholder NOP instructions inserted in the previous pass.
   293  func _PASS_Compacting(bb *BasicBlock) {
   294      var i int
   295      var j int
   296  
   297      /* copy instructins excluding NOPs */
   298      for i, j = bb.Src, bb.Src; i < bb.End; i++ {
   299          if bb.P[i].Op != _NOP {
   300              bb.P[j] = bb.P[i]
   301              j++
   302          }
   303      }
   304  
   305      /* update basic block end if needed */
   306      if i != j {
   307          bb.End = j
   308      }
   309  }