github.com/gernest/nezuko@v0.1.2/internal/obj/wasm/wasmobj.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package wasm 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "fmt" 11 "github.com/gernest/nezuko/internal/obj" 12 "github.com/gernest/nezuko/internal/objabi" 13 "github.com/gernest/nezuko/internal/sys" 14 "io" 15 "math" 16 ) 17 18 var Register = map[string]int16{ 19 "PC_F": REG_PC_F, 20 "PC_B": REG_PC_B, 21 "SP": REG_SP, 22 "CTXT": REG_CTXT, 23 "g": REG_g, 24 "RET0": REG_RET0, 25 "RET1": REG_RET1, 26 "RET2": REG_RET2, 27 "RET3": REG_RET3, 28 "PAUSE": REG_PAUSE, 29 30 "R0": REG_R0, 31 "R1": REG_R1, 32 "R2": REG_R2, 33 "R3": REG_R3, 34 "R4": REG_R4, 35 "R5": REG_R5, 36 "R6": REG_R6, 37 "R7": REG_R7, 38 "R8": REG_R8, 39 "R9": REG_R9, 40 "R10": REG_R10, 41 "R11": REG_R11, 42 "R12": REG_R12, 43 "R13": REG_R13, 44 "R14": REG_R14, 45 "R15": REG_R15, 46 47 "F0": REG_F0, 48 "F1": REG_F1, 49 "F2": REG_F2, 50 "F3": REG_F3, 51 "F4": REG_F4, 52 "F5": REG_F5, 53 "F6": REG_F6, 54 "F7": REG_F7, 55 "F8": REG_F8, 56 "F9": REG_F9, 57 "F10": REG_F10, 58 "F11": REG_F11, 59 "F12": REG_F12, 60 "F13": REG_F13, 61 "F14": REG_F14, 62 "F15": REG_F15, 63 } 64 65 var registerNames []string 66 67 func init() { 68 obj.RegisterRegister(MINREG, MAXREG, rconv) 69 obj.RegisterOpcode(obj.ABaseWasm, Anames) 70 71 registerNames = make([]string, MAXREG-MINREG) 72 for name, reg := range Register { 73 registerNames[reg-MINREG] = name 74 } 75 } 76 77 func rconv(r int) string { 78 return registerNames[r-MINREG] 79 } 80 81 var unaryDst = map[obj.As]bool{ 82 ASet: true, 83 ATee: true, 84 ACall: true, 85 ACallIndirect: true, 86 ACallImport: true, 87 ABr: true, 88 ABrIf: true, 89 ABrTable: true, 90 AI32Store: true, 91 AI64Store: true, 92 AF32Store: true, 93 AF64Store: true, 94 AI32Store8: true, 95 AI32Store16: true, 96 AI64Store8: true, 97 AI64Store16: true, 98 AI64Store32: true, 99 ACALLNORESUME: true, 100 } 101 102 var Linkwasm = obj.LinkArch{ 103 Arch: sys.ArchWasm, 104 Init: instinit, 105 Preprocess: preprocess, 106 Assemble: assemble, 107 UnaryDst: unaryDst, 108 } 109 110 var ( 111 morestack *obj.LSym 112 morestackNoCtxt *obj.LSym 113 gcWriteBarrier *obj.LSym 114 sigpanic *obj.LSym 115 deferreturn *obj.LSym 116 jmpdefer *obj.LSym 117 ) 118 119 const ( 120 /* mark flags */ 121 WasmImport = 1 << 0 122 ) 123 124 func instinit(ctxt *obj.Link) { 125 morestack = ctxt.Lookup("runtime.morestack") 126 morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt") 127 gcWriteBarrier = ctxt.Lookup("runtime.gcWriteBarrier") 128 sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) 129 deferreturn = ctxt.LookupABI("runtime.deferreturn", obj.ABIInternal) 130 // jmpdefer is defined in assembly as ABI0, but what we're 131 // looking for is the *call* to jmpdefer from the Go function 132 // deferreturn, so we're looking for the ABIInternal version 133 // of jmpdefer that's called by Go. 134 jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABIInternal) 135 } 136 137 func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { 138 appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog { 139 if p.As != obj.ANOP { 140 p2 := obj.Appendp(p, newprog) 141 p2.Pc = p.Pc 142 p = p2 143 } 144 p.As = as 145 switch len(args) { 146 case 0: 147 p.From = obj.Addr{} 148 p.To = obj.Addr{} 149 case 1: 150 if unaryDst[as] { 151 p.From = obj.Addr{} 152 p.To = args[0] 153 } else { 154 p.From = args[0] 155 p.To = obj.Addr{} 156 } 157 case 2: 158 p.From = args[0] 159 p.To = args[1] 160 default: 161 panic("bad args") 162 } 163 return p 164 } 165 166 framesize := s.Func.Text.To.Offset 167 if framesize < 0 { 168 panic("bad framesize") 169 } 170 s.Func.Args = s.Func.Text.To.Val.(int32) 171 s.Func.Locals = int32(framesize) 172 173 if s.Func.Text.From.Sym.Wrapper() { 174 // if g._panic != nil && g._panic.argp == FP { 175 // g._panic.argp = bottom-of-frame 176 // } 177 // 178 // MOVD g_panic(g), R0 179 // Get R0 180 // I64Eqz 181 // Not 182 // If 183 // Get SP 184 // I64ExtendUI32 185 // I64Const $framesize+8 186 // I64Add 187 // I64Load panic_argp(R0) 188 // I64Eq 189 // If 190 // MOVD SP, panic_argp(R0) 191 // End 192 // End 193 194 gpanic := obj.Addr{ 195 Type: obj.TYPE_MEM, 196 Reg: REGG, 197 Offset: 4 * 8, // g_panic 198 } 199 200 panicargp := obj.Addr{ 201 Type: obj.TYPE_MEM, 202 Reg: REG_R0, 203 Offset: 0, // panic.argp 204 } 205 206 p := s.Func.Text 207 p = appendp(p, AMOVD, gpanic, regAddr(REG_R0)) 208 209 p = appendp(p, AGet, regAddr(REG_R0)) 210 p = appendp(p, AI64Eqz) 211 p = appendp(p, ANot) 212 p = appendp(p, AIf) 213 214 p = appendp(p, AGet, regAddr(REG_SP)) 215 p = appendp(p, AI64ExtendUI32) 216 p = appendp(p, AI64Const, constAddr(framesize+8)) 217 p = appendp(p, AI64Add) 218 p = appendp(p, AI64Load, panicargp) 219 220 p = appendp(p, AI64Eq) 221 p = appendp(p, AIf) 222 p = appendp(p, AMOVD, regAddr(REG_SP), panicargp) 223 p = appendp(p, AEnd) 224 225 p = appendp(p, AEnd) 226 } 227 228 if framesize > 0 { 229 p := s.Func.Text 230 p = appendp(p, AGet, regAddr(REG_SP)) 231 p = appendp(p, AI32Const, constAddr(framesize)) 232 p = appendp(p, AI32Sub) 233 p = appendp(p, ASet, regAddr(REG_SP)) 234 p.Spadj = int32(framesize) 235 } 236 237 // Introduce resume points for CALL instructions 238 // and collect other explicit resume points. 239 numResumePoints := 0 240 explicitBlockDepth := 0 241 pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction 242 var tableIdxs []uint64 243 tablePC := int64(0) 244 base := ctxt.PosTable.Pos(s.Func.Text.Pos).Base() 245 for p := s.Func.Text; p != nil; p = p.Link { 246 prevBase := base 247 base = ctxt.PosTable.Pos(p.Pos).Base() 248 switch p.As { 249 case ABlock, ALoop, AIf: 250 explicitBlockDepth++ 251 252 case AEnd: 253 if explicitBlockDepth == 0 { 254 panic("End without block") 255 } 256 explicitBlockDepth-- 257 258 case ARESUMEPOINT: 259 if explicitBlockDepth != 0 { 260 panic("RESUME can only be used on toplevel") 261 } 262 p.As = AEnd 263 for tablePC <= pc { 264 tableIdxs = append(tableIdxs, uint64(numResumePoints)) 265 tablePC++ 266 } 267 numResumePoints++ 268 pc++ 269 270 case obj.ACALL: 271 if explicitBlockDepth != 0 { 272 panic("CALL can only be used on toplevel, try CALLNORESUME instead") 273 } 274 appendp(p, ARESUMEPOINT) 275 } 276 277 p.Pc = pc 278 279 // Increase pc whenever some pc-value table needs a new entry. Don't increase it 280 // more often to avoid bloat of the BrTable instruction. 281 // The "base != prevBase" condition detects inlined instructions. They are an 282 // implicit call, so entering and leaving this section affects the stack trace. 283 if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase { 284 pc++ 285 if p.To.Sym == sigpanic { 286 // The panic stack trace expects the PC at the call of sigpanic, 287 // not the next one. However, runtime.Caller subtracts 1 from the 288 // PC. To make both PC and PC-1 work (have the same line number), 289 // we advance the PC by 2 at sigpanic. 290 pc++ 291 } 292 } 293 } 294 tableIdxs = append(tableIdxs, uint64(numResumePoints)) 295 s.Size = pc + 1 296 297 if !s.Func.Text.From.Sym.NoSplit() { 298 p := s.Func.Text 299 300 if framesize <= objabi.StackSmall { 301 // small stack: SP <= stackguard 302 // Get SP 303 // Get g 304 // I32WrapI64 305 // I32Load $stackguard0 306 // I32GtU 307 308 p = appendp(p, AGet, regAddr(REG_SP)) 309 p = appendp(p, AGet, regAddr(REGG)) 310 p = appendp(p, AI32WrapI64) 311 p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0 312 p = appendp(p, AI32LeU) 313 } else { 314 // large stack: SP-framesize <= stackguard-StackSmall 315 // SP <= stackguard+(framesize-StackSmall) 316 // Get SP 317 // Get g 318 // I32WrapI64 319 // I32Load $stackguard0 320 // I32Const $(framesize-StackSmall) 321 // I32Add 322 // I32GtU 323 324 p = appendp(p, AGet, regAddr(REG_SP)) 325 p = appendp(p, AGet, regAddr(REGG)) 326 p = appendp(p, AI32WrapI64) 327 p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0 328 p = appendp(p, AI32Const, constAddr(int64(framesize)-objabi.StackSmall)) 329 p = appendp(p, AI32Add) 330 p = appendp(p, AI32LeU) 331 } 332 // TODO(neelance): handle wraparound case 333 334 p = appendp(p, AIf) 335 p = appendp(p, obj.ACALL, constAddr(0)) 336 if s.Func.Text.From.Sym.NeedCtxt() { 337 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack} 338 } else { 339 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt} 340 } 341 p = appendp(p, AEnd) 342 } 343 344 // Add Block instructions for resume points and BrTable to jump to selected resume point. 345 if numResumePoints > 0 { 346 p := s.Func.Text 347 p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks 348 349 for i := 0; i < numResumePoints+1; i++ { 350 p = appendp(p, ABlock) 351 } 352 p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B 353 p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs}) 354 p = appendp(p, AEnd) // end of Block 355 356 for p.Link != nil { 357 p = p.Link 358 } 359 360 p = appendp(p, AEnd) // end of entryPointLoop 361 p = appendp(p, obj.AUNDEF) 362 } 363 364 p := s.Func.Text 365 currentDepth := 0 366 blockDepths := make(map[*obj.Prog]int) 367 for p != nil { 368 switch p.As { 369 case ABlock, ALoop, AIf: 370 currentDepth++ 371 blockDepths[p] = currentDepth 372 case AEnd: 373 currentDepth-- 374 } 375 376 switch p.As { 377 case ABr, ABrIf: 378 if p.To.Type == obj.TYPE_BRANCH { 379 blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)] 380 if !ok { 381 panic("label not at block") 382 } 383 p.To = constAddr(int64(currentDepth - blockDepth)) 384 } 385 case obj.AJMP: 386 jmp := *p 387 p.As = obj.ANOP 388 389 if jmp.To.Type == obj.TYPE_BRANCH { 390 // jump to basic block 391 p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc)) 392 p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B 393 p = appendp(p, ABr, constAddr(int64(currentDepth-1))) // jump to beginning of entryPointLoop 394 break 395 } 396 397 // reset PC_B to function entry 398 p = appendp(p, AI32Const, constAddr(0)) 399 p = appendp(p, ASet, regAddr(REG_PC_B)) 400 401 // low-level WebAssembly call to function 402 switch jmp.To.Type { 403 case obj.TYPE_MEM: 404 p = appendp(p, ACall, jmp.To) 405 case obj.TYPE_NONE: 406 // (target PC is on stack) 407 p = appendp(p, AI32WrapI64) 408 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero 409 p = appendp(p, AI32ShrU) 410 p = appendp(p, ACallIndirect) 411 default: 412 panic("bad target for JMP") 413 } 414 415 p = appendp(p, AReturn) 416 417 case obj.ACALL, ACALLNORESUME: 418 call := *p 419 p.As = obj.ANOP 420 421 pcAfterCall := call.Link.Pc 422 if call.To.Sym == sigpanic { 423 pcAfterCall-- // sigpanic expects to be called without advancing the pc 424 } 425 426 // jmpdefer manipulates the return address on the stack so deferreturn gets called repeatedly. 427 // Model this in WebAssembly with a loop. 428 if call.To.Sym == deferreturn { 429 p = appendp(p, ALoop) 430 } 431 432 // SP -= 8 433 p = appendp(p, AGet, regAddr(REG_SP)) 434 p = appendp(p, AI32Const, constAddr(8)) 435 p = appendp(p, AI32Sub) 436 p = appendp(p, ASet, regAddr(REG_SP)) 437 438 // write return address to Go stack 439 p = appendp(p, AGet, regAddr(REG_SP)) 440 p = appendp(p, AI64Const, obj.Addr{ 441 Type: obj.TYPE_ADDR, 442 Name: obj.NAME_EXTERN, 443 Sym: s, // PC_F 444 Offset: pcAfterCall, // PC_B 445 }) 446 p = appendp(p, AI64Store, constAddr(0)) 447 448 // reset PC_B to function entry 449 p = appendp(p, AI32Const, constAddr(0)) 450 p = appendp(p, ASet, regAddr(REG_PC_B)) 451 452 // low-level WebAssembly call to function 453 switch call.To.Type { 454 case obj.TYPE_MEM: 455 p = appendp(p, ACall, call.To) 456 case obj.TYPE_NONE: 457 // (target PC is on stack) 458 p = appendp(p, AI32WrapI64) 459 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero 460 p = appendp(p, AI32ShrU) 461 p = appendp(p, ACallIndirect) 462 default: 463 panic("bad target for CALL") 464 } 465 466 // gcWriteBarrier has no return value, it never unwinds the stack 467 if call.To.Sym == gcWriteBarrier { 468 break 469 } 470 471 // jmpdefer removes the frame of deferreturn from the Go stack. 472 // However, its WebAssembly function still returns normally, 473 // so we need to return from deferreturn without removing its 474 // stack frame (no RET), because the frame is already gone. 475 if call.To.Sym == jmpdefer { 476 p = appendp(p, AReturn) 477 break 478 } 479 480 // return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack 481 p = appendp(p, AIf) 482 if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes 483 // trying to unwind WebAssembly stack but call has no resume point, terminate with error 484 p = appendp(p, obj.AUNDEF) 485 } else { 486 // unwinding WebAssembly stack to switch goroutine, return 1 487 p = appendp(p, AI32Const, constAddr(1)) 488 p = appendp(p, AReturn) 489 } 490 p = appendp(p, AEnd) 491 492 // jump to before the call if jmpdefer has reset the return address to the call's PC 493 if call.To.Sym == deferreturn { 494 p = appendp(p, AGet, regAddr(REG_PC_B)) 495 p = appendp(p, AI32Const, constAddr(call.Pc)) 496 p = appendp(p, AI32Eq) 497 p = appendp(p, ABrIf, constAddr(0)) 498 p = appendp(p, AEnd) // end of Loop 499 } 500 501 case obj.ARET, ARETUNWIND: 502 ret := *p 503 p.As = obj.ANOP 504 505 if framesize > 0 { 506 // SP += framesize 507 p = appendp(p, AGet, regAddr(REG_SP)) 508 p = appendp(p, AI32Const, constAddr(framesize)) 509 p = appendp(p, AI32Add) 510 p = appendp(p, ASet, regAddr(REG_SP)) 511 // TODO(neelance): This should theoretically set Spadj, but it only works without. 512 // p.Spadj = int32(-framesize) 513 } 514 515 if ret.To.Type == obj.TYPE_MEM { 516 // reset PC_B to function entry 517 p = appendp(p, AI32Const, constAddr(0)) 518 p = appendp(p, ASet, regAddr(REG_PC_B)) 519 520 // low-level WebAssembly call to function 521 p = appendp(p, ACall, ret.To) 522 p = appendp(p, AReturn) 523 break 524 } 525 526 // read return PC_F from Go stack 527 p = appendp(p, AGet, regAddr(REG_SP)) 528 p = appendp(p, AI32Load16U, constAddr(2)) 529 p = appendp(p, ASet, regAddr(REG_PC_F)) 530 531 // read return PC_B from Go stack 532 p = appendp(p, AGet, regAddr(REG_SP)) 533 p = appendp(p, AI32Load16U, constAddr(0)) 534 p = appendp(p, ASet, regAddr(REG_PC_B)) 535 536 // SP += 8 537 p = appendp(p, AGet, regAddr(REG_SP)) 538 p = appendp(p, AI32Const, constAddr(8)) 539 p = appendp(p, AI32Add) 540 p = appendp(p, ASet, regAddr(REG_SP)) 541 542 if ret.As == ARETUNWIND { 543 // function needs to unwind the WebAssembly stack, return 1 544 p = appendp(p, AI32Const, constAddr(1)) 545 p = appendp(p, AReturn) 546 break 547 } 548 549 // not unwinding the WebAssembly stack, return 0 550 p = appendp(p, AI32Const, constAddr(0)) 551 p = appendp(p, AReturn) 552 } 553 554 p = p.Link 555 } 556 557 p = s.Func.Text 558 for p != nil { 559 switch p.From.Name { 560 case obj.NAME_AUTO: 561 p.From.Offset += int64(framesize) 562 case obj.NAME_PARAM: 563 p.From.Reg = REG_SP 564 p.From.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address 565 } 566 567 switch p.To.Name { 568 case obj.NAME_AUTO: 569 p.To.Offset += int64(framesize) 570 case obj.NAME_PARAM: 571 p.To.Reg = REG_SP 572 p.To.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address 573 } 574 575 switch p.As { 576 case AGet: 577 if p.From.Type == obj.TYPE_ADDR { 578 get := *p 579 p.As = obj.ANOP 580 581 switch get.From.Name { 582 case obj.NAME_EXTERN: 583 p = appendp(p, AI64Const, get.From) 584 case obj.NAME_AUTO, obj.NAME_PARAM: 585 p = appendp(p, AGet, regAddr(get.From.Reg)) 586 if get.From.Reg == REG_SP { 587 p = appendp(p, AI64ExtendUI32) 588 } 589 if get.From.Offset != 0 { 590 p = appendp(p, AI64Const, constAddr(get.From.Offset)) 591 p = appendp(p, AI64Add) 592 } 593 default: 594 panic("bad Get: invalid name") 595 } 596 } 597 598 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U: 599 if p.From.Type == obj.TYPE_MEM { 600 as := p.As 601 from := p.From 602 603 p.As = AGet 604 p.From = regAddr(from.Reg) 605 606 if from.Reg != REG_SP { 607 p = appendp(p, AI32WrapI64) 608 } 609 610 p = appendp(p, as, constAddr(from.Offset)) 611 } 612 613 case AMOVB, AMOVH, AMOVW, AMOVD: 614 mov := *p 615 p.As = obj.ANOP 616 617 var loadAs obj.As 618 var storeAs obj.As 619 switch mov.As { 620 case AMOVB: 621 loadAs = AI64Load8U 622 storeAs = AI64Store8 623 case AMOVH: 624 loadAs = AI64Load16U 625 storeAs = AI64Store16 626 case AMOVW: 627 loadAs = AI64Load32U 628 storeAs = AI64Store32 629 case AMOVD: 630 loadAs = AI64Load 631 storeAs = AI64Store 632 } 633 634 appendValue := func() { 635 switch mov.From.Type { 636 case obj.TYPE_CONST: 637 p = appendp(p, AI64Const, constAddr(mov.From.Offset)) 638 639 case obj.TYPE_ADDR: 640 switch mov.From.Name { 641 case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO: 642 p = appendp(p, AGet, regAddr(mov.From.Reg)) 643 if mov.From.Reg == REG_SP { 644 p = appendp(p, AI64ExtendUI32) 645 } 646 p = appendp(p, AI64Const, constAddr(mov.From.Offset)) 647 p = appendp(p, AI64Add) 648 case obj.NAME_EXTERN: 649 p = appendp(p, AI64Const, mov.From) 650 default: 651 panic("bad name for MOV") 652 } 653 654 case obj.TYPE_REG: 655 p = appendp(p, AGet, mov.From) 656 if mov.From.Reg == REG_SP { 657 p = appendp(p, AI64ExtendUI32) 658 } 659 660 case obj.TYPE_MEM: 661 p = appendp(p, AGet, regAddr(mov.From.Reg)) 662 if mov.From.Reg != REG_SP { 663 p = appendp(p, AI32WrapI64) 664 } 665 p = appendp(p, loadAs, constAddr(mov.From.Offset)) 666 667 default: 668 panic("bad MOV type") 669 } 670 } 671 672 switch mov.To.Type { 673 case obj.TYPE_REG: 674 appendValue() 675 if mov.To.Reg == REG_SP { 676 p = appendp(p, AI32WrapI64) 677 } 678 p = appendp(p, ASet, mov.To) 679 680 case obj.TYPE_MEM: 681 switch mov.To.Name { 682 case obj.NAME_NONE, obj.NAME_PARAM: 683 p = appendp(p, AGet, regAddr(mov.To.Reg)) 684 if mov.To.Reg != REG_SP { 685 p = appendp(p, AI32WrapI64) 686 } 687 case obj.NAME_EXTERN: 688 p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym}) 689 default: 690 panic("bad MOV name") 691 } 692 appendValue() 693 p = appendp(p, storeAs, constAddr(mov.To.Offset)) 694 695 default: 696 panic("bad MOV type") 697 } 698 699 case ACallImport: 700 p.As = obj.ANOP 701 p = appendp(p, AGet, regAddr(REG_SP)) 702 p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s}) 703 p.Mark = WasmImport 704 } 705 706 p = p.Link 707 } 708 } 709 710 func constAddr(value int64) obj.Addr { 711 return obj.Addr{Type: obj.TYPE_CONST, Offset: value} 712 } 713 714 func regAddr(reg int16) obj.Addr { 715 return obj.Addr{Type: obj.TYPE_REG, Reg: reg} 716 } 717 718 // countRegisters returns the number of integer and float registers used by s. 719 // It does so by looking for the maximum I* and R* registers. 720 func countRegisters(s *obj.LSym) (numI, numF int16) { 721 for p := s.Func.Text; p != nil; p = p.Link { 722 var reg int16 723 switch p.As { 724 case AGet: 725 reg = p.From.Reg 726 case ASet: 727 reg = p.To.Reg 728 case ATee: 729 reg = p.To.Reg 730 default: 731 continue 732 } 733 if reg >= REG_R0 && reg <= REG_R15 { 734 if n := reg - REG_R0 + 1; numI < n { 735 numI = n 736 } 737 } else if reg >= REG_F0 && reg <= REG_F15 { 738 if n := reg - REG_F0 + 1; numF < n { 739 numF = n 740 } 741 } 742 } 743 return 744 } 745 746 func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { 747 w := new(bytes.Buffer) 748 749 numI, numF := countRegisters(s) 750 751 // Function starts with declaration of locals: numbers and types. 752 switch s.Name { 753 // memchr and memcmp don't use the normal Go calling convention and need i32 variables. 754 case "memchr": 755 writeUleb128(w, 1) // number of sets of locals 756 writeUleb128(w, 3) // number of locals 757 w.WriteByte(0x7F) // i32 758 case "memcmp": 759 writeUleb128(w, 1) // number of sets of locals 760 writeUleb128(w, 2) // number of locals 761 w.WriteByte(0x7F) // i32 762 default: 763 numTypes := 0 764 if numI > 0 { 765 numTypes++ 766 } 767 if numF > 0 { 768 numTypes++ 769 } 770 771 writeUleb128(w, uint64(numTypes)) 772 if numI > 0 { 773 writeUleb128(w, uint64(numI)) // number of locals 774 w.WriteByte(0x7E) // i64 775 } 776 if numF > 0 { 777 writeUleb128(w, uint64(numF)) // number of locals 778 w.WriteByte(0x7C) // f64 779 } 780 } 781 782 for p := s.Func.Text; p != nil; p = p.Link { 783 switch p.As { 784 case AGet: 785 if p.From.Type != obj.TYPE_REG { 786 panic("bad Get: argument is not a register") 787 } 788 reg := p.From.Reg 789 switch { 790 case reg >= REG_PC_F && reg <= REG_PAUSE: 791 w.WriteByte(0x23) // get_global 792 writeUleb128(w, uint64(reg-REG_PC_F)) 793 case reg >= REG_R0 && reg <= REG_R15: 794 w.WriteByte(0x20) // get_local (i64) 795 writeUleb128(w, uint64(reg-REG_R0)) 796 case reg >= REG_F0 && reg <= REG_F15: 797 w.WriteByte(0x20) // get_local (f64) 798 writeUleb128(w, uint64(numI+(reg-REG_F0))) 799 default: 800 panic("bad Get: invalid register") 801 } 802 continue 803 804 case ASet: 805 if p.To.Type != obj.TYPE_REG { 806 panic("bad Set: argument is not a register") 807 } 808 reg := p.To.Reg 809 switch { 810 case reg >= REG_PC_F && reg <= REG_PAUSE: 811 w.WriteByte(0x24) // set_global 812 writeUleb128(w, uint64(reg-REG_PC_F)) 813 case reg >= REG_R0 && reg <= REG_F15: 814 if p.Link.As == AGet && p.Link.From.Reg == reg { 815 w.WriteByte(0x22) // tee_local 816 p = p.Link 817 } else { 818 w.WriteByte(0x21) // set_local 819 } 820 if reg <= REG_R15 { 821 writeUleb128(w, uint64(reg-REG_R0)) 822 } else { 823 writeUleb128(w, uint64(numI+(reg-REG_F0))) 824 } 825 default: 826 panic("bad Set: invalid register") 827 } 828 continue 829 830 case ATee: 831 if p.To.Type != obj.TYPE_REG { 832 panic("bad Tee: argument is not a register") 833 } 834 reg := p.To.Reg 835 switch { 836 case reg >= REG_R0 && reg <= REG_R15: 837 w.WriteByte(0x22) // tee_local (i64) 838 writeUleb128(w, uint64(reg-REG_R0)) 839 case reg >= REG_F0 && reg <= REG_F15: 840 w.WriteByte(0x22) // tee_local (f64) 841 writeUleb128(w, uint64(numI+(reg-REG_F0))) 842 default: 843 panic("bad Tee: invalid register") 844 } 845 continue 846 847 case ANot: 848 w.WriteByte(0x45) // i32.eqz 849 continue 850 851 case obj.AUNDEF: 852 w.WriteByte(0x00) // unreachable 853 continue 854 855 case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA: 856 // ignore 857 continue 858 } 859 860 switch { 861 case p.As < AUnreachable || p.As > AF64ReinterpretI64: 862 panic(fmt.Sprintf("unexpected assembler op: %s", p.As)) 863 case p.As < AEnd: 864 w.WriteByte(byte(p.As - AUnreachable + 0x00)) 865 case p.As < ADrop: 866 w.WriteByte(byte(p.As - AEnd + 0x0B)) 867 case p.As < AI32Load: 868 w.WriteByte(byte(p.As - ADrop + 0x1A)) 869 default: 870 w.WriteByte(byte(p.As - AI32Load + 0x28)) 871 } 872 873 switch p.As { 874 case ABlock, ALoop, AIf: 875 if p.From.Offset != 0 { 876 // block type, rarely used, e.g. for code compiled with emscripten 877 w.WriteByte(0x80 - byte(p.From.Offset)) 878 continue 879 } 880 w.WriteByte(0x40) 881 882 case ABr, ABrIf: 883 if p.To.Type != obj.TYPE_CONST { 884 panic("bad Br/BrIf") 885 } 886 writeUleb128(w, uint64(p.To.Offset)) 887 888 case ABrTable: 889 idxs := p.To.Val.([]uint64) 890 writeUleb128(w, uint64(len(idxs)-1)) 891 for _, idx := range idxs { 892 writeUleb128(w, idx) 893 } 894 895 case ACall: 896 switch p.To.Type { 897 case obj.TYPE_CONST: 898 writeUleb128(w, uint64(p.To.Offset)) 899 900 case obj.TYPE_MEM: 901 if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC { 902 fmt.Println(p.To) 903 panic("bad name for Call") 904 } 905 r := obj.Addrel(s) 906 r.Off = int32(w.Len()) 907 r.Type = objabi.R_CALL 908 if p.Mark&WasmImport != 0 { 909 r.Type = objabi.R_WASMIMPORT 910 } 911 r.Sym = p.To.Sym 912 913 default: 914 panic("bad type for Call") 915 } 916 917 case ACallIndirect: 918 writeUleb128(w, uint64(p.To.Offset)) 919 w.WriteByte(0x00) // reserved value 920 921 case AI32Const, AI64Const: 922 if p.From.Name == obj.NAME_EXTERN { 923 r := obj.Addrel(s) 924 r.Off = int32(w.Len()) 925 r.Type = objabi.R_ADDR 926 r.Sym = p.From.Sym 927 r.Add = p.From.Offset 928 break 929 } 930 writeSleb128(w, p.From.Offset) 931 932 case AF64Const: 933 b := make([]byte, 8) 934 binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64))) 935 w.Write(b) 936 937 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U: 938 if p.From.Offset < 0 { 939 panic("negative offset for *Load") 940 } 941 if p.From.Type != obj.TYPE_CONST { 942 panic("bad type for *Load") 943 } 944 if p.From.Offset > math.MaxUint32 { 945 ctxt.Diag("bad offset in %v", p) 946 } 947 writeUleb128(w, align(p.As)) 948 writeUleb128(w, uint64(p.From.Offset)) 949 950 case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32: 951 if p.To.Offset < 0 { 952 panic("negative offset") 953 } 954 if p.From.Offset > math.MaxUint32 { 955 ctxt.Diag("bad offset in %v", p) 956 } 957 writeUleb128(w, align(p.As)) 958 writeUleb128(w, uint64(p.To.Offset)) 959 960 case ACurrentMemory, AGrowMemory: 961 w.WriteByte(0x00) 962 963 } 964 } 965 966 w.WriteByte(0x0b) // end 967 968 s.P = w.Bytes() 969 } 970 971 func align(as obj.As) uint64 { 972 switch as { 973 case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8: 974 return 0 975 case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16: 976 return 1 977 case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32: 978 return 2 979 case AI64Load, AF64Load, AI64Store, AF64Store: 980 return 3 981 default: 982 panic("align: bad op") 983 } 984 } 985 986 func writeUleb128(w io.ByteWriter, v uint64) { 987 more := true 988 for more { 989 c := uint8(v & 0x7f) 990 v >>= 7 991 more = v != 0 992 if more { 993 c |= 0x80 994 } 995 w.WriteByte(c) 996 } 997 } 998 999 func writeSleb128(w io.ByteWriter, v int64) { 1000 more := true 1001 for more { 1002 c := uint8(v & 0x7f) 1003 s := uint8(v & 0x40) 1004 v >>= 7 1005 more = !((v == 0 && s == 0) || (v == -1 && s != 0)) 1006 if more { 1007 c |= 0x80 1008 } 1009 w.WriteByte(c) 1010 } 1011 }