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.
18 var Register = map[string]int16{
82 var registerNames []string
85 obj.RegisterRegister(MINREG, MAXREG, rconv)
86 obj.RegisterOpcode(obj.ABaseWasm, Anames)
88 registerNames = make([]string, MAXREG-MINREG)
89 for name, reg := range Register {
90 registerNames[reg-MINREG] = name
94 func rconv(r int) string {
95 return registerNames[r-MINREG]
98 var unaryDst = map[obj.As]bool{
118 var Linkwasm = obj.LinkArch{
121 Preprocess: preprocess,
128 morestackNoCtxt *obj.LSym
138 // This is a special wasm module name that when used as the module name
139 // in //go:wasmimport will cause the generated code to pass the stack pointer
140 // directly to the imported function. In other words, any function that
141 // uses the gojs module understands the internal Go WASM ABI directly.
145 func instinit(ctxt *obj.Link) {
146 morestack = ctxt.Lookup("runtime.morestack")
147 morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
148 sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
151 func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
152 appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog {
153 if p.As != obj.ANOP {
154 p2 := obj.Appendp(p, newprog)
180 framesize := s.Func().Text.To.Offset
182 panic("bad framesize")
184 s.Func().Args = s.Func().Text.To.Val.(int32)
185 s.Func().Locals = int32(framesize)
187 // If the function exits just to call out to a wasmimport, then
188 // generate the code to translate from our internal Go-stack
189 // based call convention to the native webassembly call convention.
190 if wi := s.Func().WasmImport; wi != nil {
191 s.Func().WasmImportSym = wi.CreateSym(ctxt)
194 panic("wrapper functions for WASM imports should not have a body")
198 Name: obj.NAME_EXTERN,
202 // If the module that the import is for is our magic "gojs" module, then this
203 // indicates that the called function understands the Go stack-based call convention
204 // so we just pass the stack pointer to it, knowing it will read the params directly
205 // off the stack and push the results into memory based on the stack pointer.
206 if wi.Module == GojsModule {
207 // The called function has a signature of 'func(sp int)'. It has access to the memory
208 // value somewhere to be able to address the memory based on the "sp" value.
210 p = appendp(p, AGet, regAddr(REG_SP))
211 p = appendp(p, ACall, to)
215 if len(wi.Results) > 1 {
216 // TODO(evanphx) implement support for the multi-value proposal:
217 // https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md
218 panic("invalid results type") // impossible until multi-value proposal has landed
220 if len(wi.Results) == 1 {
221 // If we have a result (rather than returning nothing at all), then
222 // we'll write the result to the Go stack relative to the current stack pointer.
223 // We cache the current stack pointer value on the wasm stack here and then use
224 // it after the Call instruction to store the result.
225 p = appendp(p, AGet, regAddr(REG_SP))
227 for _, f := range wi.Params {
228 // Each load instructions will consume the value of sp on the stack, so
229 // we need to read sp for each param. WASM appears to not have a stack dup instruction
230 // (a strange ommission for a stack-based VM), if it did, we'd be using the dup here.
231 p = appendp(p, AGet, regAddr(REG_SP))
233 // Offset is the location of the param on the Go stack (ie relative to sp).
234 // Because of our call convention, the parameters are located an additional 8 bytes
235 // from sp because we store the return address as a int64 at the bottom of the stack.
236 // Ie the stack looks like [return_addr, param3, param2, param1, etc]
238 // Ergo, we add 8 to the true byte offset of the param to skip the return address.
239 loadOffset := f.Offset + 8
241 // We're reading the value from the Go stack onto the WASM stack and leaving it there
242 // for CALL to pick them up.
245 p = appendp(p, AI32Load, constAddr(loadOffset))
247 p = appendp(p, AI64Load, constAddr(loadOffset))
249 p = appendp(p, AF32Load, constAddr(loadOffset))
251 p = appendp(p, AF64Load, constAddr(loadOffset))
253 p = appendp(p, AI64Load, constAddr(loadOffset))
254 p = appendp(p, AI32WrapI64)
256 panic("bad param type")
260 // The call instruction is marked as being for a wasm import so that a later phase
261 // will generate relocation information that allows us to patch this with then
262 // offset of the imported function in the wasm imports.
263 p = appendp(p, ACall, to)
266 if len(wi.Results) == 1 {
269 // Much like with the params, we need to adjust the offset we store the result value
270 // to by 8 bytes to account for the return address on the Go stack.
271 storeOffset := f.Offset + 8
273 // This code is paired the code above that reads the stack pointer onto the wasm
274 // stack. We've done this so we have a consistent view of the sp value as it might
275 // be manipulated by the call and we want to ignore that manipulation here.
278 p = appendp(p, AI32Store, constAddr(storeOffset))
280 p = appendp(p, AI64Store, constAddr(storeOffset))
282 p = appendp(p, AF32Store, constAddr(storeOffset))
284 p = appendp(p, AF64Store, constAddr(storeOffset))
286 p = appendp(p, AI64ExtendI32U)
287 p = appendp(p, AI64Store, constAddr(storeOffset))
289 panic("bad result type")
294 p = appendp(p, obj.ARET)
296 // It should be 0 already, but we'll set it to 0 anyway just to be sure
297 // that the code below which adds frame expansion code to the function body
298 // isn't run. We don't want the frame expansion code because our function
299 // body is just the code to translate and call the imported function.
301 } else if s.Func().Text.From.Sym.Wrapper() {
302 // if g._panic != nil && g._panic.argp == FP {
303 // g._panic.argp = bottom-of-frame
306 // MOVD g_panic(g), R0
313 // I64Const $framesize+8
315 // I64Load panic_argp(R0)
318 // MOVD SP, panic_argp(R0)
325 Offset: 4 * 8, // g_panic
328 panicargp := obj.Addr{
331 Offset: 0, // panic.argp
335 p = appendp(p, AMOVD, gpanic, regAddr(REG_R0))
337 p = appendp(p, AGet, regAddr(REG_R0))
338 p = appendp(p, AI64Eqz)
342 p = appendp(p, AGet, regAddr(REG_SP))
343 p = appendp(p, AI64ExtendI32U)
344 p = appendp(p, AI64Const, constAddr(framesize+8))
345 p = appendp(p, AI64Add)
346 p = appendp(p, AI64Load, panicargp)
348 p = appendp(p, AI64Eq)
350 p = appendp(p, AMOVD, regAddr(REG_SP), panicargp)
358 p = appendp(p, AGet, regAddr(REG_SP))
359 p = appendp(p, AI32Const, constAddr(framesize))
360 p = appendp(p, AI32Sub)
361 p = appendp(p, ASet, regAddr(REG_SP))
362 p.Spadj = int32(framesize)
365 // If the framesize is 0, then imply nosplit because it's a specially
366 // generated function.
367 needMoreStack := framesize > 0 && !s.Func().Text.From.Sym.NoSplit()
369 // If the maymorestack debug option is enabled, insert the
370 // call to maymorestack *before* processing resume points so
371 // we can construct a resume point after maymorestack for
372 // morestack to resume at.
373 var pMorestack = s.Func().Text
374 if needMoreStack && ctxt.Flag_maymorestack != "" {
377 // Save REGCTXT on the stack.
379 p = appendp(p, AGet, regAddr(REG_SP))
380 p = appendp(p, AI32Const, constAddr(tempFrame))
381 p = appendp(p, AI32Sub)
382 p = appendp(p, ASet, regAddr(REG_SP))
389 p = appendp(p, AMOVD, regAddr(REGCTXT), ctxtp)
391 // maymorestack must not itself preempt because we
392 // don't have full stack information, so this can be
394 p = appendp(p, ACALLNORESUME, constAddr(0))
395 // See ../x86/obj6.go
396 sym := ctxt.LookupABI(ctxt.Flag_maymorestack, s.ABI())
397 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym}
400 p = appendp(p, AMOVD, ctxtp, regAddr(REGCTXT))
401 p = appendp(p, AGet, regAddr(REG_SP))
402 p = appendp(p, AI32Const, constAddr(tempFrame))
403 p = appendp(p, AI32Add)
404 p = appendp(p, ASet, regAddr(REG_SP))
407 // Add an explicit ARESUMEPOINT after maymorestack for
408 // morestack to resume at.
409 pMorestack = appendp(p, ARESUMEPOINT)
412 // Introduce resume points for CALL instructions
413 // and collect other explicit resume points.
415 explicitBlockDepth := 0
416 pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction
417 var tableIdxs []uint64
419 base := ctxt.PosTable.Pos(s.Func().Text.Pos).Base()
420 for p := s.Func().Text; p != nil; p = p.Link {
422 base = ctxt.PosTable.Pos(p.Pos).Base()
424 case ABlock, ALoop, AIf:
428 if explicitBlockDepth == 0 {
429 panic("End without block")
434 if explicitBlockDepth != 0 {
435 panic("RESUME can only be used on toplevel")
439 tableIdxs = append(tableIdxs, uint64(numResumePoints))
446 if explicitBlockDepth != 0 {
447 panic("CALL can only be used on toplevel, try CALLNORESUME instead")
449 appendp(p, ARESUMEPOINT)
454 // Increase pc whenever some pc-value table needs a new entry. Don't increase it
455 // more often to avoid bloat of the BrTable instruction.
456 // The "base != prevBase" condition detects inlined instructions. They are an
457 // implicit call, so entering and leaving this section affects the stack trace.
458 if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase {
460 if p.To.Sym == sigpanic {
461 // The panic stack trace expects the PC at the call of sigpanic,
462 // not the next one. However, runtime.Caller subtracts 1 from the
463 // PC. To make both PC and PC-1 work (have the same line number),
464 // we advance the PC by 2 at sigpanic.
469 tableIdxs = append(tableIdxs, uint64(numResumePoints))
475 if framesize <= objabi.StackSmall {
476 // small stack: SP <= stackguard
480 // I32Load $stackguard0
483 p = appendp(p, AGet, regAddr(REG_SP))
484 p = appendp(p, AGet, regAddr(REGG))
485 p = appendp(p, AI32WrapI64)
486 p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
487 p = appendp(p, AI32LeU)
489 // large stack: SP-framesize <= stackguard-StackSmall
490 // SP <= stackguard+(framesize-StackSmall)
494 // I32Load $stackguard0
495 // I32Const $(framesize-StackSmall)
499 p = appendp(p, AGet, regAddr(REG_SP))
500 p = appendp(p, AGet, regAddr(REGG))
501 p = appendp(p, AI32WrapI64)
502 p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
503 p = appendp(p, AI32Const, constAddr(framesize-objabi.StackSmall))
504 p = appendp(p, AI32Add)
505 p = appendp(p, AI32LeU)
507 // TODO(neelance): handle wraparound case
510 // This CALL does *not* have a resume point after it
511 // (we already inserted all of the resume points). As
512 // a result, morestack will resume at the *previous*
513 // resume point (typically, the beginning of the
514 // function) and perform the morestack check again.
515 // This is why we don't need an explicit loop like
516 // other architectures.
517 p = appendp(p, obj.ACALL, constAddr(0))
518 if s.Func().Text.From.Sym.NeedCtxt() {
519 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack}
521 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt}
526 // record the branches targeting the entry loop and the unwind exit,
527 // their targets with be filled in later
528 var entryPointLoopBranches []*obj.Prog
529 var unwindExitBranches []*obj.Prog
531 for p := s.Func().Text; p != nil; p = p.Link {
533 case ABlock, ALoop, AIf:
544 if jmp.To.Type == obj.TYPE_BRANCH {
545 // jump to basic block
546 p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc))
547 p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B
548 p = appendp(p, ABr) // jump to beginning of entryPointLoop
549 entryPointLoopBranches = append(entryPointLoopBranches, p)
553 // low-level WebAssembly call to function
556 if !notUsePC_B[jmp.To.Sym.Name] {
557 // Set PC_B parameter to function entry.
558 p = appendp(p, AI32Const, constAddr(0))
560 p = appendp(p, ACall, jmp.To)
563 // (target PC is on stack)
564 p = appendp(p, AI32WrapI64)
565 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
566 p = appendp(p, AI32ShrU)
568 // Set PC_B parameter to function entry.
569 // We need to push this before pushing the target PC_F,
570 // so temporarily pop PC_F, using our REG_PC_B as a
571 // scratch register, and push it back after pushing 0.
572 p = appendp(p, ASet, regAddr(REG_PC_B))
573 p = appendp(p, AI32Const, constAddr(0))
574 p = appendp(p, AGet, regAddr(REG_PC_B))
576 p = appendp(p, ACallIndirect)
579 panic("bad target for JMP")
582 p = appendp(p, AReturn)
584 case obj.ACALL, ACALLNORESUME:
588 pcAfterCall := call.Link.Pc
589 if call.To.Sym == sigpanic {
590 pcAfterCall-- // sigpanic expects to be called without advancing the pc
594 p = appendp(p, AGet, regAddr(REG_SP))
595 p = appendp(p, AI32Const, constAddr(8))
596 p = appendp(p, AI32Sub)
597 p = appendp(p, ASet, regAddr(REG_SP))
599 // write return address to Go stack
600 p = appendp(p, AGet, regAddr(REG_SP))
601 p = appendp(p, AI64Const, obj.Addr{
603 Name: obj.NAME_EXTERN,
605 Offset: pcAfterCall, // PC_B
607 p = appendp(p, AI64Store, constAddr(0))
609 // low-level WebAssembly call to function
610 switch call.To.Type {
612 if !notUsePC_B[call.To.Sym.Name] {
613 // Set PC_B parameter to function entry.
614 p = appendp(p, AI32Const, constAddr(0))
616 p = appendp(p, ACall, call.To)
619 // (target PC is on stack)
620 p = appendp(p, AI32WrapI64)
621 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
622 p = appendp(p, AI32ShrU)
624 // Set PC_B parameter to function entry.
625 // We need to push this before pushing the target PC_F,
626 // so temporarily pop PC_F, using our PC_B as a
627 // scratch register, and push it back after pushing 0.
628 p = appendp(p, ASet, regAddr(REG_PC_B))
629 p = appendp(p, AI32Const, constAddr(0))
630 p = appendp(p, AGet, regAddr(REG_PC_B))
632 p = appendp(p, ACallIndirect)
635 panic("bad target for CALL")
638 // return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack
639 if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes
640 // trying to unwind WebAssembly stack but call has no resume point, terminate with error
642 p = appendp(p, obj.AUNDEF)
645 // unwinding WebAssembly stack to switch goroutine, return 1
646 p = appendp(p, ABrIf)
647 unwindExitBranches = append(unwindExitBranches, p)
650 case obj.ARET, ARETUNWIND:
656 p = appendp(p, AGet, regAddr(REG_SP))
657 p = appendp(p, AI32Const, constAddr(framesize))
658 p = appendp(p, AI32Add)
659 p = appendp(p, ASet, regAddr(REG_SP))
660 // TODO(neelance): This should theoretically set Spadj, but it only works without.
661 // p.Spadj = int32(-framesize)
664 if ret.To.Type == obj.TYPE_MEM {
665 // Set PC_B parameter to function entry.
666 p = appendp(p, AI32Const, constAddr(0))
668 // low-level WebAssembly call to function
669 p = appendp(p, ACall, ret.To)
670 p = appendp(p, AReturn)
675 p = appendp(p, AGet, regAddr(REG_SP))
676 p = appendp(p, AI32Const, constAddr(8))
677 p = appendp(p, AI32Add)
678 p = appendp(p, ASet, regAddr(REG_SP))
680 if ret.As == ARETUNWIND {
681 // function needs to unwind the WebAssembly stack, return 1
682 p = appendp(p, AI32Const, constAddr(1))
683 p = appendp(p, AReturn)
687 // not unwinding the WebAssembly stack, return 0
688 p = appendp(p, AI32Const, constAddr(0))
689 p = appendp(p, AReturn)
693 for p := s.Func().Text; p != nil; p = p.Link {
696 p.From.Offset += framesize
699 p.From.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address
704 p.To.Offset += framesize
707 p.To.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address
712 if p.From.Type == obj.TYPE_ADDR {
716 switch get.From.Name {
717 case obj.NAME_EXTERN:
718 p = appendp(p, AI64Const, get.From)
719 case obj.NAME_AUTO, obj.NAME_PARAM:
720 p = appendp(p, AGet, regAddr(get.From.Reg))
721 if get.From.Reg == REG_SP {
722 p = appendp(p, AI64ExtendI32U)
724 if get.From.Offset != 0 {
725 p = appendp(p, AI64Const, constAddr(get.From.Offset))
726 p = appendp(p, AI64Add)
729 panic("bad Get: invalid name")
733 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
734 if p.From.Type == obj.TYPE_MEM {
739 p.From = regAddr(from.Reg)
741 if from.Reg != REG_SP {
742 p = appendp(p, AI32WrapI64)
745 p = appendp(p, as, constAddr(from.Offset))
748 case AMOVB, AMOVH, AMOVW, AMOVD:
760 storeAs = AI64Store16
763 storeAs = AI64Store32
769 appendValue := func() {
770 switch mov.From.Type {
772 p = appendp(p, AI64Const, constAddr(mov.From.Offset))
775 switch mov.From.Name {
776 case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO:
777 p = appendp(p, AGet, regAddr(mov.From.Reg))
778 if mov.From.Reg == REG_SP {
779 p = appendp(p, AI64ExtendI32U)
781 p = appendp(p, AI64Const, constAddr(mov.From.Offset))
782 p = appendp(p, AI64Add)
783 case obj.NAME_EXTERN:
784 p = appendp(p, AI64Const, mov.From)
786 panic("bad name for MOV")
790 p = appendp(p, AGet, mov.From)
791 if mov.From.Reg == REG_SP {
792 p = appendp(p, AI64ExtendI32U)
796 p = appendp(p, AGet, regAddr(mov.From.Reg))
797 if mov.From.Reg != REG_SP {
798 p = appendp(p, AI32WrapI64)
800 p = appendp(p, loadAs, constAddr(mov.From.Offset))
803 panic("bad MOV type")
810 if mov.To.Reg == REG_SP {
811 p = appendp(p, AI32WrapI64)
813 p = appendp(p, ASet, mov.To)
817 case obj.NAME_NONE, obj.NAME_PARAM:
818 p = appendp(p, AGet, regAddr(mov.To.Reg))
819 if mov.To.Reg != REG_SP {
820 p = appendp(p, AI32WrapI64)
822 case obj.NAME_EXTERN:
823 p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym})
825 panic("bad MOV name")
828 p = appendp(p, storeAs, constAddr(mov.To.Offset))
831 panic("bad MOV type")
838 if len(unwindExitBranches) > 0 {
839 p = appendp(p, ABlock) // unwindExit, used to return 1 when unwinding the stack
840 for _, b := range unwindExitBranches {
841 b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
844 if len(entryPointLoopBranches) > 0 {
845 p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks
846 for _, b := range entryPointLoopBranches {
847 b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
850 if numResumePoints > 0 {
851 // Add Block instructions for resume points and BrTable to jump to selected resume point.
852 for i := 0; i < numResumePoints+1; i++ {
853 p = appendp(p, ABlock)
855 p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B
856 p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs})
857 p = appendp(p, AEnd) // end of Block
860 p = p.Link // function instructions
862 if len(entryPointLoopBranches) > 0 {
863 p = appendp(p, AEnd) // end of entryPointLoop
865 p = appendp(p, obj.AUNDEF)
866 if len(unwindExitBranches) > 0 {
867 p = appendp(p, AEnd) // end of unwindExit
868 p = appendp(p, AI32Const, constAddr(1))
873 blockDepths := make(map[*obj.Prog]int)
874 for p := s.Func().Text; p != nil; p = p.Link {
876 case ABlock, ALoop, AIf:
878 blockDepths[p] = currentDepth
885 if p.To.Type == obj.TYPE_BRANCH {
886 blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)]
888 panic("label not at block")
890 p.To = constAddr(int64(currentDepth - blockDepth))
896 func constAddr(value int64) obj.Addr {
897 return obj.Addr{Type: obj.TYPE_CONST, Offset: value}
900 func regAddr(reg int16) obj.Addr {
901 return obj.Addr{Type: obj.TYPE_REG, Reg: reg}
904 // Most of the Go functions has a single parameter (PC_B) in
905 // Wasm ABI. This is a list of exceptions.
906 var notUsePC_B = map[string]bool{
907 "_rt0_wasm_js": true,
908 "wasm_export_run": true,
909 "wasm_export_resume": true,
910 "wasm_export_getsp": true,
911 "wasm_pc_f_loop": true,
912 "gcWriteBarrier": true,
913 "runtime.gcWriteBarrier1": true,
914 "runtime.gcWriteBarrier2": true,
915 "runtime.gcWriteBarrier3": true,
916 "runtime.gcWriteBarrier4": true,
917 "runtime.gcWriteBarrier5": true,
918 "runtime.gcWriteBarrier6": true,
919 "runtime.gcWriteBarrier7": true,
920 "runtime.gcWriteBarrier8": true,
921 "runtime.wasmDiv": true,
922 "runtime.wasmTruncS": true,
923 "runtime.wasmTruncU": true,
930 func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
936 type varDecl struct {
942 regVars := [MAXREG - MINREG]*regVar{
943 REG_SP - MINREG: {true, 0},
944 REG_CTXT - MINREG: {true, 1},
945 REG_g - MINREG: {true, 2},
946 REG_RET0 - MINREG: {true, 3},
947 REG_RET1 - MINREG: {true, 4},
948 REG_RET2 - MINREG: {true, 5},
949 REG_RET3 - MINREG: {true, 6},
950 REG_PAUSE - MINREG: {true, 7},
952 var varDecls []*varDecl
953 useAssemblyRegMap := func() {
954 for i := int16(0); i < 16; i++ {
955 regVars[REG_R0+i-MINREG] = ®Var{false, uint64(i)}
959 // Function starts with declaration of locals: numbers and types.
960 // Some functions use a special calling convention.
962 case "_rt0_wasm_js", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp", "wasm_pc_f_loop",
963 "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody":
964 varDecls = []*varDecl{}
966 case "memchr", "memcmp":
967 varDecls = []*varDecl{{count: 2, typ: i32}}
970 varDecls = []*varDecl{{count: 2, typ: i64}}
972 case "gcWriteBarrier":
973 varDecls = []*varDecl{{count: 5, typ: i64}}
975 case "runtime.gcWriteBarrier1",
976 "runtime.gcWriteBarrier2",
977 "runtime.gcWriteBarrier3",
978 "runtime.gcWriteBarrier4",
979 "runtime.gcWriteBarrier5",
980 "runtime.gcWriteBarrier6",
981 "runtime.gcWriteBarrier7",
982 "runtime.gcWriteBarrier8":
986 // Normal calling convention: PC_B as WebAssembly parameter. First local variable is local SP cache.
987 regVars[REG_PC_B-MINREG] = ®Var{false, 0}
990 var regUsed [MAXREG - MINREG]bool
991 for p := s.Func().Text; p != nil; p = p.Link {
993 regUsed[p.From.Reg-MINREG] = true
996 regUsed[p.To.Reg-MINREG] = true
1000 regs := []int16{REG_SP}
1001 for reg := int16(REG_R0); reg <= REG_F31; reg++ {
1002 if regUsed[reg-MINREG] {
1003 regs = append(regs, reg)
1007 var lastDecl *varDecl
1008 for i, reg := range regs {
1010 if lastDecl == nil || lastDecl.typ != t {
1011 lastDecl = &varDecl{
1015 varDecls = append(varDecls, lastDecl)
1019 regVars[reg-MINREG] = ®Var{false, 1 + uint64(i)}
1024 w := new(bytes.Buffer)
1026 writeUleb128(w, uint64(len(varDecls)))
1027 for _, decl := range varDecls {
1028 writeUleb128(w, decl.count)
1029 w.WriteByte(byte(decl.typ))
1033 // Copy SP from its global variable into a local variable. Accessing a local variable is more efficient.
1037 for p := s.Func().Text; p != nil; p = p.Link {
1040 if p.From.Type != obj.TYPE_REG {
1041 panic("bad Get: argument is not a register")
1044 v := regVars[reg-MINREG]
1046 panic("bad Get: invalid register")
1048 if reg == REG_SP && hasLocalSP {
1049 writeOpcode(w, ALocalGet)
1050 writeUleb128(w, 1) // local SP
1054 writeOpcode(w, AGlobalGet)
1056 writeOpcode(w, ALocalGet)
1058 writeUleb128(w, v.index)
1062 if p.To.Type != obj.TYPE_REG {
1063 panic("bad Set: argument is not a register")
1066 v := regVars[reg-MINREG]
1068 panic("bad Set: invalid register")
1070 if reg == REG_SP && hasLocalSP {
1071 writeOpcode(w, ALocalTee)
1072 writeUleb128(w, 1) // local SP
1075 writeOpcode(w, AGlobalSet)
1077 if p.Link.As == AGet && p.Link.From.Reg == reg {
1078 writeOpcode(w, ALocalTee)
1081 writeOpcode(w, ALocalSet)
1084 writeUleb128(w, v.index)
1088 if p.To.Type != obj.TYPE_REG {
1089 panic("bad Tee: argument is not a register")
1092 v := regVars[reg-MINREG]
1094 panic("bad Tee: invalid register")
1096 writeOpcode(w, ALocalTee)
1097 writeUleb128(w, v.index)
1101 writeOpcode(w, AI32Eqz)
1105 writeOpcode(w, AUnreachable)
1108 case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA:
1113 writeOpcode(w, p.As)
1116 case ABlock, ALoop, AIf:
1117 if p.From.Offset != 0 {
1118 // block type, rarely used, e.g. for code compiled with emscripten
1119 w.WriteByte(0x80 - byte(p.From.Offset))
1125 if p.To.Type != obj.TYPE_CONST {
1126 panic("bad Br/BrIf")
1128 writeUleb128(w, uint64(p.To.Offset))
1131 idxs := p.To.Val.([]uint64)
1132 writeUleb128(w, uint64(len(idxs)-1))
1133 for _, idx := range idxs {
1134 writeUleb128(w, idx)
1139 case obj.TYPE_CONST:
1140 writeUleb128(w, uint64(p.To.Offset))
1143 if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC {
1145 panic("bad name for Call")
1148 r.Siz = 1 // actually variable sized
1149 r.Off = int32(w.Len())
1150 r.Type = objabi.R_CALL
1151 if p.Mark&WasmImport != 0 {
1152 r.Type = objabi.R_WASMIMPORT
1156 // The stack may have moved, which changes SP. Update the local SP variable.
1161 panic("bad type for Call")
1165 writeUleb128(w, uint64(p.To.Offset))
1166 w.WriteByte(0x00) // reserved value
1168 // The stack may have moved, which changes SP. Update the local SP variable.
1172 case AI32Const, AI64Const:
1173 if p.From.Name == obj.NAME_EXTERN {
1175 r.Siz = 1 // actually variable sized
1176 r.Off = int32(w.Len())
1177 r.Type = objabi.R_ADDR
1179 r.Add = p.From.Offset
1182 writeSleb128(w, p.From.Offset)
1185 b := make([]byte, 4)
1186 binary.LittleEndian.PutUint32(b, math.Float32bits(float32(p.From.Val.(float64))))
1190 b := make([]byte, 8)
1191 binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64)))
1194 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
1195 if p.From.Offset < 0 {
1196 panic("negative offset for *Load")
1198 if p.From.Type != obj.TYPE_CONST {
1199 panic("bad type for *Load")
1201 if p.From.Offset > math.MaxUint32 {
1202 ctxt.Diag("bad offset in %v", p)
1204 writeUleb128(w, align(p.As))
1205 writeUleb128(w, uint64(p.From.Offset))
1207 case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32:
1208 if p.To.Offset < 0 {
1209 panic("negative offset")
1211 if p.From.Offset > math.MaxUint32 {
1212 ctxt.Diag("bad offset in %v", p)
1214 writeUleb128(w, align(p.As))
1215 writeUleb128(w, uint64(p.To.Offset))
1217 case ACurrentMemory, AGrowMemory, AMemoryFill:
1227 w.WriteByte(0x0b) // end
1232 func updateLocalSP(w *bytes.Buffer) {
1233 writeOpcode(w, AGlobalGet)
1234 writeUleb128(w, 0) // global SP
1235 writeOpcode(w, ALocalSet)
1236 writeUleb128(w, 1) // local SP
1239 func writeOpcode(w *bytes.Buffer, as obj.As) {
1241 case as < AUnreachable:
1242 panic(fmt.Sprintf("unexpected assembler op: %s", as))
1244 w.WriteByte(byte(as - AUnreachable + 0x00))
1246 w.WriteByte(byte(as - AEnd + 0x0B))
1247 case as < ALocalGet:
1248 w.WriteByte(byte(as - ADrop + 0x1A))
1250 w.WriteByte(byte(as - ALocalGet + 0x20))
1251 case as < AI32TruncSatF32S:
1252 w.WriteByte(byte(as - AI32Load + 0x28))
1255 w.WriteByte(byte(as - AI32TruncSatF32S + 0x00))
1257 panic(fmt.Sprintf("unexpected assembler op: %s", as))
1264 i32 valueType = 0x7F
1265 i64 valueType = 0x7E
1266 f32 valueType = 0x7D
1267 f64 valueType = 0x7C
1270 func regType(reg int16) valueType {
1274 case reg >= REG_R0 && reg <= REG_R15:
1276 case reg >= REG_F0 && reg <= REG_F15:
1278 case reg >= REG_F16 && reg <= REG_F31:
1281 panic("invalid register")
1285 func align(as obj.As) uint64 {
1287 case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8:
1289 case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16:
1291 case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32:
1293 case AI64Load, AF64Load, AI64Store, AF64Store:
1296 panic("align: bad op")
1300 func writeUleb128(w io.ByteWriter, v uint64) {
1302 w.WriteByte(uint8(v))
1307 c := uint8(v & 0x7f)
1317 func writeSleb128(w io.ByteWriter, v int64) {
1320 c := uint8(v & 0x7f)
1321 s := uint8(v & 0x40)
1323 more = !((v == 0 && s == 0) || (v == -1 && s != 0))