github.com/goshafaq/sonic@v0.0.0-20231026082336-871835fb94c6/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/goshafaq/sonic/internal/rt"
    26  	"github.com/goshafaq/sonic/loader"
    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:] {
    77  		self.From("QUAD", Imm(rt.Get64(v)))
    78  	}
    79  	for ; len(v) >= 4; v = v[4:] {
    80  		self.From("LONG", Imm(int64(rt.Get32(v))))
    81  	}
    82  	for ; len(v) >= 2; v = v[2:] {
    83  		self.From("WORD", Imm(int64(rt.Get16(v))))
    84  	}
    85  	for ; len(v) >= 1; v = v[1:] {
    86  		self.From("BYTE", Imm(int64(v[0])))
    87  	}
    88  }
    89  
    90  func (self *BaseAssembler) Mark(pc int) {
    91  	self.i++
    92  	self.Link(_LB_jump_pc + strconv.Itoa(pc))
    93  }
    94  
    95  func (self *BaseAssembler) Link(to string) {
    96  	var p *obj.Prog
    97  	var v []*obj.Prog
    98  
    99  	/* placeholder substitution */
   100  	if strings.Contains(to, "{n}") {
   101  		to = strings.ReplaceAll(to, "{n}", strconv.Itoa(self.i))
   102  	}
   103  
   104  	/* check for duplications */
   105  	if _, ok := self.labels[to]; ok {
   106  		panic("label " + to + " has already been linked")
   107  	}
   108  
   109  	/* get the pending links */
   110  	p = self.NOP()
   111  	v = self.pendings[to]
   112  
   113  	/* patch all the pending jumps */
   114  	for _, q := range v {
   115  		q.To.Val = p
   116  	}
   117  
   118  	/* mark the label as resolved */
   119  	self.labels[to] = p
   120  	delete(self.pendings, to)
   121  }
   122  
   123  func (self *BaseAssembler) Xref(pc int, d int64) {
   124  	self.Sref(_LB_jump_pc+strconv.Itoa(pc), d)
   125  }
   126  
   127  func (self *BaseAssembler) Sref(to string, d int64) {
   128  	p := self.pb.New()
   129  	p.As = x86.ALONG
   130  	p.From = Imm(-d)
   131  
   132  	/* placeholder substitution */
   133  	if strings.Contains(to, "{n}") {
   134  		to = strings.ReplaceAll(to, "{n}", strconv.Itoa(self.i))
   135  	}
   136  
   137  	/* record the patch point */
   138  	self.pb.Append(p)
   139  	self.xrefs[to] = append(self.xrefs[to], p)
   140  }
   141  
   142  func (self *BaseAssembler) Xjmp(op string, to int) {
   143  	self.Sjmp(op, _LB_jump_pc+strconv.Itoa(to))
   144  }
   145  
   146  func (self *BaseAssembler) Sjmp(op string, to string) {
   147  	p := self.pb.New()
   148  	p.As = As(op)
   149  
   150  	/* placeholder substitution */
   151  	if strings.Contains(to, "{n}") {
   152  		to = strings.ReplaceAll(to, "{n}", strconv.Itoa(self.i))
   153  	}
   154  
   155  	/* check for backward jumps */
   156  	if v, ok := self.labels[to]; ok {
   157  		p.To.Val = v
   158  	} else {
   159  		self.pendings[to] = append(self.pendings[to], p)
   160  	}
   161  
   162  	/* mark as a branch, and add to instruction buffer */
   163  	p.To.Type = obj.TYPE_BRANCH
   164  	self.pb.Append(p)
   165  }
   166  
   167  func (self *BaseAssembler) Rjmp(op string, to obj.Addr) {
   168  	p := self.pb.New()
   169  	p.To = to
   170  	p.As = As(op)
   171  	self.pb.Append(p)
   172  }
   173  
   174  func (self *BaseAssembler) From(op string, val obj.Addr) {
   175  	p := self.pb.New()
   176  	p.As = As(op)
   177  	p.From = val
   178  	self.pb.Append(p)
   179  }
   180  
   181  func (self *BaseAssembler) Emit(op string, args ...obj.Addr) {
   182  	p := self.pb.New()
   183  	p.As = As(op)
   184  	self.assignOperands(p, args)
   185  	self.pb.Append(p)
   186  }
   187  
   188  func (self *BaseAssembler) assignOperands(p *obj.Prog, args []obj.Addr) {
   189  	switch len(args) {
   190  	case 0:
   191  	case 1:
   192  		p.To = args[0]
   193  	case 2:
   194  		p.To, p.From = args[1], args[0]
   195  	case 3:
   196  		p.To, p.From, p.RestArgs = args[2], args[0], args[1:2]
   197  	case 4:
   198  		p.To, p.From, p.RestArgs = args[2], args[3], args[:2]
   199  	default:
   200  		panic("invalid operands")
   201  	}
   202  }
   203  
   204  /** Assembler Helpers **/
   205  
   206  func (self *BaseAssembler) Size() int {
   207  	self.build()
   208  	return len(self.c)
   209  }
   210  
   211  func (self *BaseAssembler) Init(f func()) {
   212  	self.i = 0
   213  	self.f = f
   214  	self.c = nil
   215  	self.o = sync.Once{}
   216  }
   217  
   218  var jitLoader = loader.Loader{
   219  	Name: "sonic.jit.",
   220  	File: "github.com/goshafaq/sonic/jit.go",
   221  	Options: loader.Options{
   222  		NoPreempt: true,
   223  	},
   224  }
   225  
   226  func (self *BaseAssembler) Load(name string, frameSize int, argSize int, argStackmap []bool, localStackmap []bool) loader.Function {
   227  	self.build()
   228  	return jitLoader.LoadOne(self.c, name, frameSize, argSize, argStackmap, localStackmap)
   229  }
   230  
   231  /** Assembler Stages **/
   232  
   233  func (self *BaseAssembler) init() {
   234  	self.pb = newBackend("amd64")
   235  	self.xrefs = map[string][]*obj.Prog{}
   236  	self.labels = map[string]*obj.Prog{}
   237  	self.pendings = map[string][]*obj.Prog{}
   238  }
   239  
   240  func (self *BaseAssembler) build() {
   241  	self.o.Do(func() {
   242  		self.init()
   243  		self.f()
   244  		self.validate()
   245  		self.assemble()
   246  		self.resolve()
   247  		self.release()
   248  	})
   249  }
   250  
   251  func (self *BaseAssembler) release() {
   252  	self.pb.Release()
   253  	self.pb = nil
   254  	self.xrefs = nil
   255  	self.labels = nil
   256  	self.pendings = nil
   257  }
   258  
   259  func (self *BaseAssembler) resolve() {
   260  	for s, v := range self.xrefs {
   261  		for _, prog := range v {
   262  			if prog.As != x86.ALONG {
   263  				panic("invalid RIP relative reference")
   264  			} else if p, ok := self.labels[s]; !ok {
   265  				panic("links are not fully resolved: " + s)
   266  			} else {
   267  				off := prog.From.Offset + p.Pc - prog.Pc
   268  				binary.LittleEndian.PutUint32(self.c[prog.Pc:], uint32(off))
   269  			}
   270  		}
   271  	}
   272  }
   273  
   274  func (self *BaseAssembler) validate() {
   275  	for key := range self.pendings {
   276  		panic("links are not fully resolved: " + key)
   277  	}
   278  }
   279  
   280  func (self *BaseAssembler) assemble() {
   281  	self.c = self.pb.Assemble()
   282  }