github.com/bir3/gocompiler@v0.3.205/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 "github.com/bir3/gocompiler/src/cmd/internal/obj" 10 "github.com/bir3/gocompiler/src/cmd/internal/objabi" 11 "github.com/bir3/gocompiler/src/cmd/internal/sys" 12 "encoding/binary" 13 "fmt" 14 "io" 15 "math" 16 ) 17 18 var Register = map[string]int16{ 19 "SP": REG_SP, 20 "CTXT": REG_CTXT, 21 "g": REG_g, 22 "RET0": REG_RET0, 23 "RET1": REG_RET1, 24 "RET2": REG_RET2, 25 "RET3": REG_RET3, 26 "PAUSE": REG_PAUSE, 27 28 "R0": REG_R0, 29 "R1": REG_R1, 30 "R2": REG_R2, 31 "R3": REG_R3, 32 "R4": REG_R4, 33 "R5": REG_R5, 34 "R6": REG_R6, 35 "R7": REG_R7, 36 "R8": REG_R8, 37 "R9": REG_R9, 38 "R10": REG_R10, 39 "R11": REG_R11, 40 "R12": REG_R12, 41 "R13": REG_R13, 42 "R14": REG_R14, 43 "R15": REG_R15, 44 45 "F0": REG_F0, 46 "F1": REG_F1, 47 "F2": REG_F2, 48 "F3": REG_F3, 49 "F4": REG_F4, 50 "F5": REG_F5, 51 "F6": REG_F6, 52 "F7": REG_F7, 53 "F8": REG_F8, 54 "F9": REG_F9, 55 "F10": REG_F10, 56 "F11": REG_F11, 57 "F12": REG_F12, 58 "F13": REG_F13, 59 "F14": REG_F14, 60 "F15": REG_F15, 61 62 "F16": REG_F16, 63 "F17": REG_F17, 64 "F18": REG_F18, 65 "F19": REG_F19, 66 "F20": REG_F20, 67 "F21": REG_F21, 68 "F22": REG_F22, 69 "F23": REG_F23, 70 "F24": REG_F24, 71 "F25": REG_F25, 72 "F26": REG_F26, 73 "F27": REG_F27, 74 "F28": REG_F28, 75 "F29": REG_F29, 76 "F30": REG_F30, 77 "F31": REG_F31, 78 79 "PC_B": REG_PC_B, 80 } 81 82 var registerNames []string 83 84 func init() { 85 obj.RegisterRegister(MINREG, MAXREG, rconv) 86 obj.RegisterOpcode(obj.ABaseWasm, Anames) 87 88 registerNames = make([]string, MAXREG-MINREG) 89 for name, reg := range Register { 90 registerNames[reg-MINREG] = name 91 } 92 } 93 94 func rconv(r int) string { 95 return registerNames[r-MINREG] 96 } 97 98 var unaryDst = map[obj.As]bool{ 99 ASet: true, 100 ATee: true, 101 ACall: true, 102 ACallIndirect: true, 103 ACallImport: true, 104 ABr: true, 105 ABrIf: true, 106 ABrTable: true, 107 AI32Store: true, 108 AI64Store: true, 109 AF32Store: true, 110 AF64Store: true, 111 AI32Store8: true, 112 AI32Store16: true, 113 AI64Store8: true, 114 AI64Store16: true, 115 AI64Store32: true, 116 ACALLNORESUME: true, 117 } 118 119 var Linkwasm = obj.LinkArch{ 120 Arch: sys.ArchWasm, 121 Init: instinit, 122 Preprocess: preprocess, 123 Assemble: assemble, 124 UnaryDst: unaryDst, 125 } 126 127 var ( 128 morestack *obj.LSym 129 morestackNoCtxt *obj.LSym 130 gcWriteBarrier *obj.LSym 131 sigpanic *obj.LSym 132 ) 133 134 const ( 135 /* mark flags */ 136 WasmImport = 1 << 0 137 ) 138 139 func instinit(ctxt *obj.Link) { 140 morestack = ctxt.Lookup("runtime.morestack") 141 morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt") 142 gcWriteBarrier = ctxt.LookupABI("runtime.gcWriteBarrier", obj.ABIInternal) 143 sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) 144 } 145 146 func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { 147 appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog { 148 if p.As != obj.ANOP { 149 p2 := obj.Appendp(p, newprog) 150 p2.Pc = p.Pc 151 p = p2 152 } 153 p.As = as 154 switch len(args) { 155 case 0: 156 p.From = obj.Addr{} 157 p.To = obj.Addr{} 158 case 1: 159 if unaryDst[as] { 160 p.From = obj.Addr{} 161 p.To = args[0] 162 } else { 163 p.From = args[0] 164 p.To = obj.Addr{} 165 } 166 case 2: 167 p.From = args[0] 168 p.To = args[1] 169 default: 170 panic("bad args") 171 } 172 return p 173 } 174 175 framesize := s.Func().Text.To.Offset 176 if framesize < 0 { 177 panic("bad framesize") 178 } 179 s.Func().Args = s.Func().Text.To.Val.(int32) 180 s.Func().Locals = int32(framesize) 181 182 if s.Func().Text.From.Sym.Wrapper() { 183 // if g._panic != nil && g._panic.argp == FP { 184 // g._panic.argp = bottom-of-frame 185 // } 186 // 187 // MOVD g_panic(g), R0 188 // Get R0 189 // I64Eqz 190 // Not 191 // If 192 // Get SP 193 // I64ExtendI32U 194 // I64Const $framesize+8 195 // I64Add 196 // I64Load panic_argp(R0) 197 // I64Eq 198 // If 199 // MOVD SP, panic_argp(R0) 200 // End 201 // End 202 203 gpanic := obj.Addr{ 204 Type: obj.TYPE_MEM, 205 Reg: REGG, 206 Offset: 4 * 8, // g_panic 207 } 208 209 panicargp := obj.Addr{ 210 Type: obj.TYPE_MEM, 211 Reg: REG_R0, 212 Offset: 0, // panic.argp 213 } 214 215 p := s.Func().Text 216 p = appendp(p, AMOVD, gpanic, regAddr(REG_R0)) 217 218 p = appendp(p, AGet, regAddr(REG_R0)) 219 p = appendp(p, AI64Eqz) 220 p = appendp(p, ANot) 221 p = appendp(p, AIf) 222 223 p = appendp(p, AGet, regAddr(REG_SP)) 224 p = appendp(p, AI64ExtendI32U) 225 p = appendp(p, AI64Const, constAddr(framesize+8)) 226 p = appendp(p, AI64Add) 227 p = appendp(p, AI64Load, panicargp) 228 229 p = appendp(p, AI64Eq) 230 p = appendp(p, AIf) 231 p = appendp(p, AMOVD, regAddr(REG_SP), panicargp) 232 p = appendp(p, AEnd) 233 234 p = appendp(p, AEnd) 235 } 236 237 if framesize > 0 { 238 p := s.Func().Text 239 p = appendp(p, AGet, regAddr(REG_SP)) 240 p = appendp(p, AI32Const, constAddr(framesize)) 241 p = appendp(p, AI32Sub) 242 p = appendp(p, ASet, regAddr(REG_SP)) 243 p.Spadj = int32(framesize) 244 } 245 246 needMoreStack := !s.Func().Text.From.Sym.NoSplit() 247 248 // If the maymorestack debug option is enabled, insert the 249 // call to maymorestack *before* processing resume points so 250 // we can construct a resume point after maymorestack for 251 // morestack to resume at. 252 var pMorestack = s.Func().Text 253 if needMoreStack && ctxt.Flag_maymorestack != "" { 254 p := pMorestack 255 256 // Save REGCTXT on the stack. 257 const tempFrame = 8 258 p = appendp(p, AGet, regAddr(REG_SP)) 259 p = appendp(p, AI32Const, constAddr(tempFrame)) 260 p = appendp(p, AI32Sub) 261 p = appendp(p, ASet, regAddr(REG_SP)) 262 p.Spadj = tempFrame 263 ctxtp := obj.Addr{ 264 Type: obj.TYPE_MEM, 265 Reg: REG_SP, 266 Offset: 0, 267 } 268 p = appendp(p, AMOVD, regAddr(REGCTXT), ctxtp) 269 270 // maymorestack must not itself preempt because we 271 // don't have full stack information, so this can be 272 // ACALLNORESUME. 273 p = appendp(p, ACALLNORESUME, constAddr(0)) 274 // See ../x86/obj6.go 275 sym := ctxt.LookupABI(ctxt.Flag_maymorestack, s.ABI()) 276 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym} 277 278 // Restore REGCTXT. 279 p = appendp(p, AMOVD, ctxtp, regAddr(REGCTXT)) 280 p = appendp(p, AGet, regAddr(REG_SP)) 281 p = appendp(p, AI32Const, constAddr(tempFrame)) 282 p = appendp(p, AI32Add) 283 p = appendp(p, ASet, regAddr(REG_SP)) 284 p.Spadj = -tempFrame 285 286 // Add an explicit ARESUMEPOINT after maymorestack for 287 // morestack to resume at. 288 pMorestack = appendp(p, ARESUMEPOINT) 289 } 290 291 // Introduce resume points for CALL instructions 292 // and collect other explicit resume points. 293 numResumePoints := 0 294 explicitBlockDepth := 0 295 pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction 296 var tableIdxs []uint64 297 tablePC := int64(0) 298 base := ctxt.PosTable.Pos(s.Func().Text.Pos).Base() 299 for p := s.Func().Text; p != nil; p = p.Link { 300 prevBase := base 301 base = ctxt.PosTable.Pos(p.Pos).Base() 302 switch p.As { 303 case ABlock, ALoop, AIf: 304 explicitBlockDepth++ 305 306 case AEnd: 307 if explicitBlockDepth == 0 { 308 panic("End without block") 309 } 310 explicitBlockDepth-- 311 312 case ARESUMEPOINT: 313 if explicitBlockDepth != 0 { 314 panic("RESUME can only be used on toplevel") 315 } 316 p.As = AEnd 317 for tablePC <= pc { 318 tableIdxs = append(tableIdxs, uint64(numResumePoints)) 319 tablePC++ 320 } 321 numResumePoints++ 322 pc++ 323 324 case obj.ACALL: 325 if explicitBlockDepth != 0 { 326 panic("CALL can only be used on toplevel, try CALLNORESUME instead") 327 } 328 appendp(p, ARESUMEPOINT) 329 } 330 331 p.Pc = pc 332 333 // Increase pc whenever some pc-value table needs a new entry. Don't increase it 334 // more often to avoid bloat of the BrTable instruction. 335 // The "base != prevBase" condition detects inlined instructions. They are an 336 // implicit call, so entering and leaving this section affects the stack trace. 337 if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase { 338 pc++ 339 if p.To.Sym == sigpanic { 340 // The panic stack trace expects the PC at the call of sigpanic, 341 // not the next one. However, runtime.Caller subtracts 1 from the 342 // PC. To make both PC and PC-1 work (have the same line number), 343 // we advance the PC by 2 at sigpanic. 344 pc++ 345 } 346 } 347 } 348 tableIdxs = append(tableIdxs, uint64(numResumePoints)) 349 s.Size = pc + 1 350 351 if needMoreStack { 352 p := pMorestack 353 354 if framesize <= objabi.StackSmall { 355 // small stack: SP <= stackguard 356 // Get SP 357 // Get g 358 // I32WrapI64 359 // I32Load $stackguard0 360 // I32GtU 361 362 p = appendp(p, AGet, regAddr(REG_SP)) 363 p = appendp(p, AGet, regAddr(REGG)) 364 p = appendp(p, AI32WrapI64) 365 p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0 366 p = appendp(p, AI32LeU) 367 } else { 368 // large stack: SP-framesize <= stackguard-StackSmall 369 // SP <= stackguard+(framesize-StackSmall) 370 // Get SP 371 // Get g 372 // I32WrapI64 373 // I32Load $stackguard0 374 // I32Const $(framesize-StackSmall) 375 // I32Add 376 // I32GtU 377 378 p = appendp(p, AGet, regAddr(REG_SP)) 379 p = appendp(p, AGet, regAddr(REGG)) 380 p = appendp(p, AI32WrapI64) 381 p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0 382 p = appendp(p, AI32Const, constAddr(framesize-objabi.StackSmall)) 383 p = appendp(p, AI32Add) 384 p = appendp(p, AI32LeU) 385 } 386 // TODO(neelance): handle wraparound case 387 388 p = appendp(p, AIf) 389 // This CALL does *not* have a resume point after it 390 // (we already inserted all of the resume points). As 391 // a result, morestack will resume at the *previous* 392 // resume point (typically, the beginning of the 393 // function) and perform the morestack check again. 394 // This is why we don't need an explicit loop like 395 // other architectures. 396 p = appendp(p, obj.ACALL, constAddr(0)) 397 if s.Func().Text.From.Sym.NeedCtxt() { 398 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack} 399 } else { 400 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt} 401 } 402 p = appendp(p, AEnd) 403 } 404 405 // record the branches targeting the entry loop and the unwind exit, 406 // their targets with be filled in later 407 var entryPointLoopBranches []*obj.Prog 408 var unwindExitBranches []*obj.Prog 409 currentDepth := 0 410 for p := s.Func().Text; p != nil; p = p.Link { 411 switch p.As { 412 case ABlock, ALoop, AIf: 413 currentDepth++ 414 case AEnd: 415 currentDepth-- 416 } 417 418 switch p.As { 419 case obj.AJMP: 420 jmp := *p 421 p.As = obj.ANOP 422 423 if jmp.To.Type == obj.TYPE_BRANCH { 424 // jump to basic block 425 p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc)) 426 p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B 427 p = appendp(p, ABr) // jump to beginning of entryPointLoop 428 entryPointLoopBranches = append(entryPointLoopBranches, p) 429 break 430 } 431 432 // low-level WebAssembly call to function 433 switch jmp.To.Type { 434 case obj.TYPE_MEM: 435 if !notUsePC_B[jmp.To.Sym.Name] { 436 // Set PC_B parameter to function entry. 437 p = appendp(p, AI32Const, constAddr(0)) 438 } 439 p = appendp(p, ACall, jmp.To) 440 441 case obj.TYPE_NONE: 442 // (target PC is on stack) 443 p = appendp(p, AI32WrapI64) 444 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero 445 p = appendp(p, AI32ShrU) 446 447 // Set PC_B parameter to function entry. 448 // We need to push this before pushing the target PC_F, 449 // so temporarily pop PC_F, using our REG_PC_B as a 450 // scratch register, and push it back after pushing 0. 451 p = appendp(p, ASet, regAddr(REG_PC_B)) 452 p = appendp(p, AI32Const, constAddr(0)) 453 p = appendp(p, AGet, regAddr(REG_PC_B)) 454 455 p = appendp(p, ACallIndirect) 456 457 default: 458 panic("bad target for JMP") 459 } 460 461 p = appendp(p, AReturn) 462 463 case obj.ACALL, ACALLNORESUME: 464 call := *p 465 p.As = obj.ANOP 466 467 pcAfterCall := call.Link.Pc 468 if call.To.Sym == sigpanic { 469 pcAfterCall-- // sigpanic expects to be called without advancing the pc 470 } 471 472 // SP -= 8 473 p = appendp(p, AGet, regAddr(REG_SP)) 474 p = appendp(p, AI32Const, constAddr(8)) 475 p = appendp(p, AI32Sub) 476 p = appendp(p, ASet, regAddr(REG_SP)) 477 478 // write return address to Go stack 479 p = appendp(p, AGet, regAddr(REG_SP)) 480 p = appendp(p, AI64Const, obj.Addr{ 481 Type: obj.TYPE_ADDR, 482 Name: obj.NAME_EXTERN, 483 Sym: s, // PC_F 484 Offset: pcAfterCall, // PC_B 485 }) 486 p = appendp(p, AI64Store, constAddr(0)) 487 488 // low-level WebAssembly call to function 489 switch call.To.Type { 490 case obj.TYPE_MEM: 491 if !notUsePC_B[call.To.Sym.Name] { 492 // Set PC_B parameter to function entry. 493 p = appendp(p, AI32Const, constAddr(0)) 494 } 495 p = appendp(p, ACall, call.To) 496 497 case obj.TYPE_NONE: 498 // (target PC is on stack) 499 p = appendp(p, AI32WrapI64) 500 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero 501 p = appendp(p, AI32ShrU) 502 503 // Set PC_B parameter to function entry. 504 // We need to push this before pushing the target PC_F, 505 // so temporarily pop PC_F, using our PC_B as a 506 // scratch register, and push it back after pushing 0. 507 p = appendp(p, ASet, regAddr(REG_PC_B)) 508 p = appendp(p, AI32Const, constAddr(0)) 509 p = appendp(p, AGet, regAddr(REG_PC_B)) 510 511 p = appendp(p, ACallIndirect) 512 513 default: 514 panic("bad target for CALL") 515 } 516 517 // gcWriteBarrier has no return value, it never unwinds the stack 518 if call.To.Sym == gcWriteBarrier { 519 break 520 } 521 522 // return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack 523 if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes 524 // trying to unwind WebAssembly stack but call has no resume point, terminate with error 525 p = appendp(p, AIf) 526 p = appendp(p, obj.AUNDEF) 527 p = appendp(p, AEnd) 528 } else { 529 // unwinding WebAssembly stack to switch goroutine, return 1 530 p = appendp(p, ABrIf) 531 unwindExitBranches = append(unwindExitBranches, p) 532 } 533 534 case obj.ARET, ARETUNWIND: 535 ret := *p 536 p.As = obj.ANOP 537 538 if framesize > 0 { 539 // SP += framesize 540 p = appendp(p, AGet, regAddr(REG_SP)) 541 p = appendp(p, AI32Const, constAddr(framesize)) 542 p = appendp(p, AI32Add) 543 p = appendp(p, ASet, regAddr(REG_SP)) 544 // TODO(neelance): This should theoretically set Spadj, but it only works without. 545 // p.Spadj = int32(-framesize) 546 } 547 548 if ret.To.Type == obj.TYPE_MEM { 549 // Set PC_B parameter to function entry. 550 p = appendp(p, AI32Const, constAddr(0)) 551 552 // low-level WebAssembly call to function 553 p = appendp(p, ACall, ret.To) 554 p = appendp(p, AReturn) 555 break 556 } 557 558 // SP += 8 559 p = appendp(p, AGet, regAddr(REG_SP)) 560 p = appendp(p, AI32Const, constAddr(8)) 561 p = appendp(p, AI32Add) 562 p = appendp(p, ASet, regAddr(REG_SP)) 563 564 if ret.As == ARETUNWIND { 565 // function needs to unwind the WebAssembly stack, return 1 566 p = appendp(p, AI32Const, constAddr(1)) 567 p = appendp(p, AReturn) 568 break 569 } 570 571 // not unwinding the WebAssembly stack, return 0 572 p = appendp(p, AI32Const, constAddr(0)) 573 p = appendp(p, AReturn) 574 } 575 } 576 577 for p := s.Func().Text; p != nil; p = p.Link { 578 switch p.From.Name { 579 case obj.NAME_AUTO: 580 p.From.Offset += framesize 581 case obj.NAME_PARAM: 582 p.From.Reg = REG_SP 583 p.From.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address 584 } 585 586 switch p.To.Name { 587 case obj.NAME_AUTO: 588 p.To.Offset += framesize 589 case obj.NAME_PARAM: 590 p.To.Reg = REG_SP 591 p.To.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address 592 } 593 594 switch p.As { 595 case AGet: 596 if p.From.Type == obj.TYPE_ADDR { 597 get := *p 598 p.As = obj.ANOP 599 600 switch get.From.Name { 601 case obj.NAME_EXTERN: 602 p = appendp(p, AI64Const, get.From) 603 case obj.NAME_AUTO, obj.NAME_PARAM: 604 p = appendp(p, AGet, regAddr(get.From.Reg)) 605 if get.From.Reg == REG_SP { 606 p = appendp(p, AI64ExtendI32U) 607 } 608 if get.From.Offset != 0 { 609 p = appendp(p, AI64Const, constAddr(get.From.Offset)) 610 p = appendp(p, AI64Add) 611 } 612 default: 613 panic("bad Get: invalid name") 614 } 615 } 616 617 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U: 618 if p.From.Type == obj.TYPE_MEM { 619 as := p.As 620 from := p.From 621 622 p.As = AGet 623 p.From = regAddr(from.Reg) 624 625 if from.Reg != REG_SP { 626 p = appendp(p, AI32WrapI64) 627 } 628 629 p = appendp(p, as, constAddr(from.Offset)) 630 } 631 632 case AMOVB, AMOVH, AMOVW, AMOVD: 633 mov := *p 634 p.As = obj.ANOP 635 636 var loadAs obj.As 637 var storeAs obj.As 638 switch mov.As { 639 case AMOVB: 640 loadAs = AI64Load8U 641 storeAs = AI64Store8 642 case AMOVH: 643 loadAs = AI64Load16U 644 storeAs = AI64Store16 645 case AMOVW: 646 loadAs = AI64Load32U 647 storeAs = AI64Store32 648 case AMOVD: 649 loadAs = AI64Load 650 storeAs = AI64Store 651 } 652 653 appendValue := func() { 654 switch mov.From.Type { 655 case obj.TYPE_CONST: 656 p = appendp(p, AI64Const, constAddr(mov.From.Offset)) 657 658 case obj.TYPE_ADDR: 659 switch mov.From.Name { 660 case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO: 661 p = appendp(p, AGet, regAddr(mov.From.Reg)) 662 if mov.From.Reg == REG_SP { 663 p = appendp(p, AI64ExtendI32U) 664 } 665 p = appendp(p, AI64Const, constAddr(mov.From.Offset)) 666 p = appendp(p, AI64Add) 667 case obj.NAME_EXTERN: 668 p = appendp(p, AI64Const, mov.From) 669 default: 670 panic("bad name for MOV") 671 } 672 673 case obj.TYPE_REG: 674 p = appendp(p, AGet, mov.From) 675 if mov.From.Reg == REG_SP { 676 p = appendp(p, AI64ExtendI32U) 677 } 678 679 case obj.TYPE_MEM: 680 p = appendp(p, AGet, regAddr(mov.From.Reg)) 681 if mov.From.Reg != REG_SP { 682 p = appendp(p, AI32WrapI64) 683 } 684 p = appendp(p, loadAs, constAddr(mov.From.Offset)) 685 686 default: 687 panic("bad MOV type") 688 } 689 } 690 691 switch mov.To.Type { 692 case obj.TYPE_REG: 693 appendValue() 694 if mov.To.Reg == REG_SP { 695 p = appendp(p, AI32WrapI64) 696 } 697 p = appendp(p, ASet, mov.To) 698 699 case obj.TYPE_MEM: 700 switch mov.To.Name { 701 case obj.NAME_NONE, obj.NAME_PARAM: 702 p = appendp(p, AGet, regAddr(mov.To.Reg)) 703 if mov.To.Reg != REG_SP { 704 p = appendp(p, AI32WrapI64) 705 } 706 case obj.NAME_EXTERN: 707 p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym}) 708 default: 709 panic("bad MOV name") 710 } 711 appendValue() 712 p = appendp(p, storeAs, constAddr(mov.To.Offset)) 713 714 default: 715 panic("bad MOV type") 716 } 717 718 case ACallImport: 719 p.As = obj.ANOP 720 p = appendp(p, AGet, regAddr(REG_SP)) 721 p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s}) 722 p.Mark = WasmImport 723 } 724 } 725 726 { 727 p := s.Func().Text 728 if len(unwindExitBranches) > 0 { 729 p = appendp(p, ABlock) // unwindExit, used to return 1 when unwinding the stack 730 for _, b := range unwindExitBranches { 731 b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p} 732 } 733 } 734 if len(entryPointLoopBranches) > 0 { 735 p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks 736 for _, b := range entryPointLoopBranches { 737 b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p} 738 } 739 } 740 if numResumePoints > 0 { 741 // Add Block instructions for resume points and BrTable to jump to selected resume point. 742 for i := 0; i < numResumePoints+1; i++ { 743 p = appendp(p, ABlock) 744 } 745 p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B 746 p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs}) 747 p = appendp(p, AEnd) // end of Block 748 } 749 for p.Link != nil { 750 p = p.Link // function instructions 751 } 752 if len(entryPointLoopBranches) > 0 { 753 p = appendp(p, AEnd) // end of entryPointLoop 754 } 755 p = appendp(p, obj.AUNDEF) 756 if len(unwindExitBranches) > 0 { 757 p = appendp(p, AEnd) // end of unwindExit 758 p = appendp(p, AI32Const, constAddr(1)) 759 } 760 } 761 762 currentDepth = 0 763 blockDepths := make(map[*obj.Prog]int) 764 for p := s.Func().Text; p != nil; p = p.Link { 765 switch p.As { 766 case ABlock, ALoop, AIf: 767 currentDepth++ 768 blockDepths[p] = currentDepth 769 case AEnd: 770 currentDepth-- 771 } 772 773 switch p.As { 774 case ABr, ABrIf: 775 if p.To.Type == obj.TYPE_BRANCH { 776 blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)] 777 if !ok { 778 panic("label not at block") 779 } 780 p.To = constAddr(int64(currentDepth - blockDepth)) 781 } 782 } 783 } 784 } 785 786 func constAddr(value int64) obj.Addr { 787 return obj.Addr{Type: obj.TYPE_CONST, Offset: value} 788 } 789 790 func regAddr(reg int16) obj.Addr { 791 return obj.Addr{Type: obj.TYPE_REG, Reg: reg} 792 } 793 794 // Most of the Go functions has a single parameter (PC_B) in 795 // Wasm ABI. This is a list of exceptions. 796 var notUsePC_B = map[string]bool{ 797 "_rt0_wasm_js": true, 798 "wasm_export_run": true, 799 "wasm_export_resume": true, 800 "wasm_export_getsp": true, 801 "wasm_pc_f_loop": true, 802 "runtime.wasmDiv": true, 803 "runtime.wasmTruncS": true, 804 "runtime.wasmTruncU": true, 805 "runtime.gcWriteBarrier": true, 806 "cmpbody": true, 807 "memeqbody": true, 808 "memcmp": true, 809 "memchr": true, 810 } 811 812 func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { 813 type regVar struct { 814 global bool 815 index uint64 816 } 817 818 type varDecl struct { 819 count uint64 820 typ valueType 821 } 822 823 hasLocalSP := false 824 regVars := [MAXREG - MINREG]*regVar{ 825 REG_SP - MINREG: {true, 0}, 826 REG_CTXT - MINREG: {true, 1}, 827 REG_g - MINREG: {true, 2}, 828 REG_RET0 - MINREG: {true, 3}, 829 REG_RET1 - MINREG: {true, 4}, 830 REG_RET2 - MINREG: {true, 5}, 831 REG_RET3 - MINREG: {true, 6}, 832 REG_PAUSE - MINREG: {true, 7}, 833 } 834 var varDecls []*varDecl 835 useAssemblyRegMap := func() { 836 for i := int16(0); i < 16; i++ { 837 regVars[REG_R0+i-MINREG] = ®Var{false, uint64(i)} 838 } 839 } 840 841 // Function starts with declaration of locals: numbers and types. 842 // Some functions use a special calling convention. 843 switch s.Name { 844 case "_rt0_wasm_js", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp", "wasm_pc_f_loop", 845 "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody": 846 varDecls = []*varDecl{} 847 useAssemblyRegMap() 848 case "memchr", "memcmp": 849 varDecls = []*varDecl{{count: 2, typ: i32}} 850 useAssemblyRegMap() 851 case "cmpbody": 852 varDecls = []*varDecl{{count: 2, typ: i64}} 853 useAssemblyRegMap() 854 case "runtime.gcWriteBarrier": 855 varDecls = []*varDecl{{count: 4, typ: i64}} 856 useAssemblyRegMap() 857 default: 858 // Normal calling convention: PC_B as WebAssembly parameter. First local variable is local SP cache. 859 regVars[REG_PC_B-MINREG] = ®Var{false, 0} 860 hasLocalSP = true 861 862 var regUsed [MAXREG - MINREG]bool 863 for p := s.Func().Text; p != nil; p = p.Link { 864 if p.From.Reg != 0 { 865 regUsed[p.From.Reg-MINREG] = true 866 } 867 if p.To.Reg != 0 { 868 regUsed[p.To.Reg-MINREG] = true 869 } 870 } 871 872 regs := []int16{REG_SP} 873 for reg := int16(REG_R0); reg <= REG_F31; reg++ { 874 if regUsed[reg-MINREG] { 875 regs = append(regs, reg) 876 } 877 } 878 879 var lastDecl *varDecl 880 for i, reg := range regs { 881 t := regType(reg) 882 if lastDecl == nil || lastDecl.typ != t { 883 lastDecl = &varDecl{ 884 count: 0, 885 typ: t, 886 } 887 varDecls = append(varDecls, lastDecl) 888 } 889 lastDecl.count++ 890 if reg != REG_SP { 891 regVars[reg-MINREG] = ®Var{false, 1 + uint64(i)} 892 } 893 } 894 } 895 896 w := new(bytes.Buffer) 897 898 writeUleb128(w, uint64(len(varDecls))) 899 for _, decl := range varDecls { 900 writeUleb128(w, decl.count) 901 w.WriteByte(byte(decl.typ)) 902 } 903 904 if hasLocalSP { 905 // Copy SP from its global variable into a local variable. Accessing a local variable is more efficient. 906 updateLocalSP(w) 907 } 908 909 for p := s.Func().Text; p != nil; p = p.Link { 910 switch p.As { 911 case AGet: 912 if p.From.Type != obj.TYPE_REG { 913 panic("bad Get: argument is not a register") 914 } 915 reg := p.From.Reg 916 v := regVars[reg-MINREG] 917 if v == nil { 918 panic("bad Get: invalid register") 919 } 920 if reg == REG_SP && hasLocalSP { 921 writeOpcode(w, ALocalGet) 922 writeUleb128(w, 1) // local SP 923 continue 924 } 925 if v.global { 926 writeOpcode(w, AGlobalGet) 927 } else { 928 writeOpcode(w, ALocalGet) 929 } 930 writeUleb128(w, v.index) 931 continue 932 933 case ASet: 934 if p.To.Type != obj.TYPE_REG { 935 panic("bad Set: argument is not a register") 936 } 937 reg := p.To.Reg 938 v := regVars[reg-MINREG] 939 if v == nil { 940 panic("bad Set: invalid register") 941 } 942 if reg == REG_SP && hasLocalSP { 943 writeOpcode(w, ALocalTee) 944 writeUleb128(w, 1) // local SP 945 } 946 if v.global { 947 writeOpcode(w, AGlobalSet) 948 } else { 949 if p.Link.As == AGet && p.Link.From.Reg == reg { 950 writeOpcode(w, ALocalTee) 951 p = p.Link 952 } else { 953 writeOpcode(w, ALocalSet) 954 } 955 } 956 writeUleb128(w, v.index) 957 continue 958 959 case ATee: 960 if p.To.Type != obj.TYPE_REG { 961 panic("bad Tee: argument is not a register") 962 } 963 reg := p.To.Reg 964 v := regVars[reg-MINREG] 965 if v == nil { 966 panic("bad Tee: invalid register") 967 } 968 writeOpcode(w, ALocalTee) 969 writeUleb128(w, v.index) 970 continue 971 972 case ANot: 973 writeOpcode(w, AI32Eqz) 974 continue 975 976 case obj.AUNDEF: 977 writeOpcode(w, AUnreachable) 978 continue 979 980 case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA: 981 // ignore 982 continue 983 } 984 985 writeOpcode(w, p.As) 986 987 switch p.As { 988 case ABlock, ALoop, AIf: 989 if p.From.Offset != 0 { 990 // block type, rarely used, e.g. for code compiled with emscripten 991 w.WriteByte(0x80 - byte(p.From.Offset)) 992 continue 993 } 994 w.WriteByte(0x40) 995 996 case ABr, ABrIf: 997 if p.To.Type != obj.TYPE_CONST { 998 panic("bad Br/BrIf") 999 } 1000 writeUleb128(w, uint64(p.To.Offset)) 1001 1002 case ABrTable: 1003 idxs := p.To.Val.([]uint64) 1004 writeUleb128(w, uint64(len(idxs)-1)) 1005 for _, idx := range idxs { 1006 writeUleb128(w, idx) 1007 } 1008 1009 case ACall: 1010 switch p.To.Type { 1011 case obj.TYPE_CONST: 1012 writeUleb128(w, uint64(p.To.Offset)) 1013 1014 case obj.TYPE_MEM: 1015 if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC { 1016 fmt.Println(p.To) 1017 panic("bad name for Call") 1018 } 1019 r := obj.Addrel(s) 1020 r.Siz = 1 // actually variable sized 1021 r.Off = int32(w.Len()) 1022 r.Type = objabi.R_CALL 1023 if p.Mark&WasmImport != 0 { 1024 r.Type = objabi.R_WASMIMPORT 1025 } 1026 r.Sym = p.To.Sym 1027 if hasLocalSP { 1028 // The stack may have moved, which changes SP. Update the local SP variable. 1029 updateLocalSP(w) 1030 } 1031 1032 default: 1033 panic("bad type for Call") 1034 } 1035 1036 case ACallIndirect: 1037 writeUleb128(w, uint64(p.To.Offset)) 1038 w.WriteByte(0x00) // reserved value 1039 if hasLocalSP { 1040 // The stack may have moved, which changes SP. Update the local SP variable. 1041 updateLocalSP(w) 1042 } 1043 1044 case AI32Const, AI64Const: 1045 if p.From.Name == obj.NAME_EXTERN { 1046 r := obj.Addrel(s) 1047 r.Siz = 1 // actually variable sized 1048 r.Off = int32(w.Len()) 1049 r.Type = objabi.R_ADDR 1050 r.Sym = p.From.Sym 1051 r.Add = p.From.Offset 1052 break 1053 } 1054 writeSleb128(w, p.From.Offset) 1055 1056 case AF32Const: 1057 b := make([]byte, 4) 1058 binary.LittleEndian.PutUint32(b, math.Float32bits(float32(p.From.Val.(float64)))) 1059 w.Write(b) 1060 1061 case AF64Const: 1062 b := make([]byte, 8) 1063 binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64))) 1064 w.Write(b) 1065 1066 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U: 1067 if p.From.Offset < 0 { 1068 panic("negative offset for *Load") 1069 } 1070 if p.From.Type != obj.TYPE_CONST { 1071 panic("bad type for *Load") 1072 } 1073 if p.From.Offset > math.MaxUint32 { 1074 ctxt.Diag("bad offset in %v", p) 1075 } 1076 writeUleb128(w, align(p.As)) 1077 writeUleb128(w, uint64(p.From.Offset)) 1078 1079 case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32: 1080 if p.To.Offset < 0 { 1081 panic("negative offset") 1082 } 1083 if p.From.Offset > math.MaxUint32 { 1084 ctxt.Diag("bad offset in %v", p) 1085 } 1086 writeUleb128(w, align(p.As)) 1087 writeUleb128(w, uint64(p.To.Offset)) 1088 1089 case ACurrentMemory, AGrowMemory, AMemoryFill: 1090 w.WriteByte(0x00) 1091 1092 case AMemoryCopy: 1093 w.WriteByte(0x00) 1094 w.WriteByte(0x00) 1095 1096 } 1097 } 1098 1099 w.WriteByte(0x0b) // end 1100 1101 s.P = w.Bytes() 1102 } 1103 1104 func updateLocalSP(w *bytes.Buffer) { 1105 writeOpcode(w, AGlobalGet) 1106 writeUleb128(w, 0) // global SP 1107 writeOpcode(w, ALocalSet) 1108 writeUleb128(w, 1) // local SP 1109 } 1110 1111 func writeOpcode(w *bytes.Buffer, as obj.As) { 1112 switch { 1113 case as < AUnreachable: 1114 panic(fmt.Sprintf("unexpected assembler op: %s", as)) 1115 case as < AEnd: 1116 w.WriteByte(byte(as - AUnreachable + 0x00)) 1117 case as < ADrop: 1118 w.WriteByte(byte(as - AEnd + 0x0B)) 1119 case as < ALocalGet: 1120 w.WriteByte(byte(as - ADrop + 0x1A)) 1121 case as < AI32Load: 1122 w.WriteByte(byte(as - ALocalGet + 0x20)) 1123 case as < AI32TruncSatF32S: 1124 w.WriteByte(byte(as - AI32Load + 0x28)) 1125 case as < ALast: 1126 w.WriteByte(0xFC) 1127 w.WriteByte(byte(as - AI32TruncSatF32S + 0x00)) 1128 default: 1129 panic(fmt.Sprintf("unexpected assembler op: %s", as)) 1130 } 1131 } 1132 1133 type valueType byte 1134 1135 const ( 1136 i32 valueType = 0x7F 1137 i64 valueType = 0x7E 1138 f32 valueType = 0x7D 1139 f64 valueType = 0x7C 1140 ) 1141 1142 func regType(reg int16) valueType { 1143 switch { 1144 case reg == REG_SP: 1145 return i32 1146 case reg >= REG_R0 && reg <= REG_R15: 1147 return i64 1148 case reg >= REG_F0 && reg <= REG_F15: 1149 return f32 1150 case reg >= REG_F16 && reg <= REG_F31: 1151 return f64 1152 default: 1153 panic("invalid register") 1154 } 1155 } 1156 1157 func align(as obj.As) uint64 { 1158 switch as { 1159 case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8: 1160 return 0 1161 case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16: 1162 return 1 1163 case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32: 1164 return 2 1165 case AI64Load, AF64Load, AI64Store, AF64Store: 1166 return 3 1167 default: 1168 panic("align: bad op") 1169 } 1170 } 1171 1172 func writeUleb128(w io.ByteWriter, v uint64) { 1173 if v < 128 { 1174 w.WriteByte(uint8(v)) 1175 return 1176 } 1177 more := true 1178 for more { 1179 c := uint8(v & 0x7f) 1180 v >>= 7 1181 more = v != 0 1182 if more { 1183 c |= 0x80 1184 } 1185 w.WriteByte(c) 1186 } 1187 } 1188 1189 func writeSleb128(w io.ByteWriter, v int64) { 1190 more := true 1191 for more { 1192 c := uint8(v & 0x7f) 1193 s := uint8(v & 0x40) 1194 v >>= 7 1195 more = !((v == 0 && s == 0) || (v == -1 && s != 0)) 1196 if more { 1197 c |= 0x80 1198 } 1199 w.WriteByte(c) 1200 } 1201 }