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