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