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 }