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 }