github.com/bytedance/sonic@v1.11.7-0.20240517092252-d2edb31b167b/internal/jit/assembler_amd64.go (about)

     1  /*
     2   * Copyright 2021 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 jit
    18  
    19  import (
    20      `encoding/binary`
    21      `strconv`
    22      `strings`
    23      `sync`
    24  
    25      `github.com/bytedance/sonic/loader`
    26      `github.com/bytedance/sonic/internal/rt`
    27      `github.com/twitchyliquid64/golang-asm/obj`
    28      `github.com/twitchyliquid64/golang-asm/obj/x86`
    29  )
    30  
    31  const (
    32      _LB_jump_pc = "_jump_pc_"
    33  )
    34  
    35  type BaseAssembler struct {
    36      i        int
    37      f        func()
    38      c        []byte
    39      o        sync.Once
    40      pb       *Backend
    41      xrefs    map[string][]*obj.Prog
    42      labels   map[string]*obj.Prog
    43      pendings map[string][]*obj.Prog
    44  }
    45  
    46  /** Instruction Encoders **/
    47  
    48  var _NOPS = [][16]byte {
    49      {0x90},                                                     // NOP
    50      {0x66, 0x90},                                               // 66 NOP
    51      {0x0f, 0x1f, 0x00},                                         // NOP DWORD ptr [EAX]
    52      {0x0f, 0x1f, 0x40, 0x00},                                   // NOP DWORD ptr [EAX + 00H]
    53      {0x0f, 0x1f, 0x44, 0x00, 0x00},                             // NOP DWORD ptr [EAX + EAX*1 + 00H]
    54      {0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00},                       // 66 NOP DWORD ptr [EAX + EAX*1 + 00H]
    55      {0x0f, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00},                 // NOP DWORD ptr [EAX + 00000000H]
    56      {0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00},           // NOP DWORD ptr [EAX + EAX*1 + 00000000H]
    57      {0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00},     // 66 NOP DWORD ptr [EAX + EAX*1 + 00000000H]
    58  }
    59  
    60  func (self *BaseAssembler) NOP() *obj.Prog {
    61      p := self.pb.New()
    62      p.As = obj.ANOP
    63      self.pb.Append(p)
    64      return p
    65  }
    66  
    67  func (self *BaseAssembler) NOPn(n int) {
    68      for i := len(_NOPS); i > 0 && n > 0; i-- {
    69          for ; n >= i; n -= i {
    70              self.Byte(_NOPS[i - 1][:i]...)
    71          }
    72      }
    73  }
    74  
    75  func (self *BaseAssembler) Byte(v ...byte) {
    76      for ; len(v) >= 8; v = v[8:] { self.From("QUAD", Imm(rt.Get64(v))) }
    77      for ; len(v) >= 4; v = v[4:] { self.From("LONG", Imm(int64(rt.Get32(v)))) }
    78      for ; len(v) >= 2; v = v[2:] { self.From("WORD", Imm(int64(rt.Get16(v)))) }
    79      for ; len(v) >= 1; v = v[1:] { self.From("BYTE", Imm(int64(v[0]))) }
    80  }
    81  
    82  func (self *BaseAssembler) Mark(pc int) {
    83      self.i++
    84      self.Link(_LB_jump_pc + strconv.Itoa(pc))
    85  }
    86  
    87  func (self *BaseAssembler) Link(to string) {
    88      var p *obj.Prog
    89      var v []*obj.Prog
    90  
    91      /* placeholder substitution */
    92      if strings.Contains(to, "{n}") {
    93          to = strings.ReplaceAll(to, "{n}", strconv.Itoa(self.i))
    94      }
    95  
    96      /* check for duplications */
    97      if _, ok := self.labels[to]; ok {
    98          panic("label " + to + " has already been linked")
    99      }
   100  
   101      /* get the pending links */
   102      p = self.NOP()
   103      v = self.pendings[to]
   104  
   105      /* patch all the pending jumps */
   106      for _, q := range v {
   107          q.To.Val = p
   108      }
   109  
   110      /* mark the label as resolved */
   111      self.labels[to] = p
   112      delete(self.pendings, to)
   113  }
   114  
   115  func (self *BaseAssembler) Xref(pc int, d int64) {
   116      self.Sref(_LB_jump_pc + strconv.Itoa(pc), d)
   117  }
   118  
   119  func (self *BaseAssembler) Sref(to string, d int64) {
   120      p := self.pb.New()
   121      p.As = x86.ALONG
   122      p.From = Imm(-d)
   123  
   124      /* placeholder substitution */
   125      if strings.Contains(to, "{n}") {
   126          to = strings.ReplaceAll(to, "{n}", strconv.Itoa(self.i))
   127      }
   128  
   129      /* record the patch point */
   130      self.pb.Append(p)
   131      self.xrefs[to] = append(self.xrefs[to], p)
   132  }
   133  
   134  func (self *BaseAssembler) Xjmp(op string, to int) {
   135      self.Sjmp(op, _LB_jump_pc + strconv.Itoa(to))
   136  }
   137  
   138  func (self *BaseAssembler) Sjmp(op string, to string) {
   139      p := self.pb.New()
   140      p.As = As(op)
   141  
   142      /* placeholder substitution */
   143      if strings.Contains(to, "{n}") {
   144          to = strings.ReplaceAll(to, "{n}", strconv.Itoa(self.i))
   145      }
   146  
   147      /* check for backward jumps */
   148      if v, ok := self.labels[to]; ok {
   149          p.To.Val = v
   150      } else {
   151          self.pendings[to] = append(self.pendings[to], p)
   152      }
   153  
   154      /* mark as a branch, and add to instruction buffer */
   155      p.To.Type = obj.TYPE_BRANCH
   156      self.pb.Append(p)
   157  }
   158  
   159  func (self *BaseAssembler) Rjmp(op string, to obj.Addr) {
   160      p := self.pb.New()
   161      p.To = to
   162      p.As = As(op)
   163      self.pb.Append(p)
   164  }
   165  
   166  func (self *BaseAssembler) From(op string, val obj.Addr) {
   167      p := self.pb.New()
   168      p.As = As(op)
   169      p.From = val
   170      self.pb.Append(p)
   171  }
   172  
   173  func (self *BaseAssembler) Emit(op string, args ...obj.Addr) {
   174      p := self.pb.New()
   175      p.As = As(op)
   176      self.assignOperands(p, args)
   177      self.pb.Append(p)
   178  }
   179  
   180  func (self *BaseAssembler) assignOperands(p *obj.Prog, args []obj.Addr) {
   181      switch len(args) {
   182          case 0  :
   183          case 1  : p.To                     = args[0]
   184          case 2  : p.To, p.From             = args[1], args[0]
   185          case 3  : p.To, p.From, p.RestArgs = args[2], args[0], args[1:2]
   186          case 4  : p.To, p.From, p.RestArgs = args[2], args[3], args[:2]
   187          default : panic("invalid operands")
   188      }
   189  }
   190  
   191  /** Assembler Helpers **/
   192  
   193  func (self *BaseAssembler) Size() int {
   194      self.build()
   195      return len(self.c)
   196  }
   197  
   198  func (self *BaseAssembler) Init(f func()) {
   199      self.i = 0
   200      self.f = f
   201      self.c = nil
   202      self.o = sync.Once{}
   203  }
   204  
   205  var jitLoader = loader.Loader{
   206      Name: "sonic.jit.",
   207      File: "github.com/bytedance/sonic/jit.go",
   208      Options: loader.Options{
   209          NoPreempt: true,
   210      },
   211  }
   212  
   213  func (self *BaseAssembler) Load(name string, frameSize int, argSize int, argStackmap []bool, localStackmap []bool) loader.Function {
   214      self.build()
   215      return jitLoader.LoadOne(self.c, name, frameSize, argSize, argStackmap, localStackmap)
   216  }
   217  
   218  /** Assembler Stages **/
   219  
   220  func (self *BaseAssembler) init() {
   221      self.pb       = newBackend("amd64")
   222      self.xrefs    = map[string][]*obj.Prog{}
   223      self.labels   = map[string]*obj.Prog{}
   224      self.pendings = map[string][]*obj.Prog{}
   225  }
   226  
   227  func (self *BaseAssembler) build() {
   228      self.o.Do(func() {
   229          self.init()
   230          self.f()
   231          self.validate()
   232          self.assemble()
   233          self.resolve()
   234          self.release()
   235      })
   236  }
   237  
   238  func (self *BaseAssembler) release() {
   239      self.pb.Release()
   240      self.pb = nil
   241      self.xrefs = nil
   242      self.labels = nil
   243      self.pendings = nil
   244  }
   245  
   246  func (self *BaseAssembler) resolve() {
   247      for s, v := range self.xrefs {
   248          for _, prog := range v {
   249              if prog.As != x86.ALONG {
   250                  panic("invalid RIP relative reference")
   251              } else if p, ok := self.labels[s]; !ok {
   252                  panic("links are not fully resolved: " + s)
   253              } else {
   254                  off := prog.From.Offset + p.Pc - prog.Pc
   255                  binary.LittleEndian.PutUint32(self.c[prog.Pc:], uint32(off))
   256              }
   257          }
   258      }
   259  }
   260  
   261  func (self *BaseAssembler) validate() {
   262      for key := range self.pendings {
   263          panic("links are not fully resolved: " + key)
   264      }
   265  }
   266  
   267  func (self *BaseAssembler) assemble() {
   268      self.c = self.pb.Assemble()
   269  }