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