Source file src/cmd/internal/obj/wasm/wasmobj.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package wasm
     6  
     7  import (
     8  	"bytes"
     9  	"cmd/internal/obj"
    10  	"cmd/internal/objabi"
    11  	"cmd/internal/sys"
    12  	"encoding/binary"
    13  	"fmt"
    14  	"internal/abi"
    15  	"io"
    16  	"math"
    17  )
    18  
    19  var Register = map[string]int16{
    20  	"SP":    REG_SP,
    21  	"CTXT":  REG_CTXT,
    22  	"g":     REG_g,
    23  	"RET0":  REG_RET0,
    24  	"RET1":  REG_RET1,
    25  	"RET2":  REG_RET2,
    26  	"RET3":  REG_RET3,
    27  	"PAUSE": REG_PAUSE,
    28  
    29  	"R0":  REG_R0,
    30  	"R1":  REG_R1,
    31  	"R2":  REG_R2,
    32  	"R3":  REG_R3,
    33  	"R4":  REG_R4,
    34  	"R5":  REG_R5,
    35  	"R6":  REG_R6,
    36  	"R7":  REG_R7,
    37  	"R8":  REG_R8,
    38  	"R9":  REG_R9,
    39  	"R10": REG_R10,
    40  	"R11": REG_R11,
    41  	"R12": REG_R12,
    42  	"R13": REG_R13,
    43  	"R14": REG_R14,
    44  	"R15": REG_R15,
    45  
    46  	"F0":  REG_F0,
    47  	"F1":  REG_F1,
    48  	"F2":  REG_F2,
    49  	"F3":  REG_F3,
    50  	"F4":  REG_F4,
    51  	"F5":  REG_F5,
    52  	"F6":  REG_F6,
    53  	"F7":  REG_F7,
    54  	"F8":  REG_F8,
    55  	"F9":  REG_F9,
    56  	"F10": REG_F10,
    57  	"F11": REG_F11,
    58  	"F12": REG_F12,
    59  	"F13": REG_F13,
    60  	"F14": REG_F14,
    61  	"F15": REG_F15,
    62  
    63  	"F16": REG_F16,
    64  	"F17": REG_F17,
    65  	"F18": REG_F18,
    66  	"F19": REG_F19,
    67  	"F20": REG_F20,
    68  	"F21": REG_F21,
    69  	"F22": REG_F22,
    70  	"F23": REG_F23,
    71  	"F24": REG_F24,
    72  	"F25": REG_F25,
    73  	"F26": REG_F26,
    74  	"F27": REG_F27,
    75  	"F28": REG_F28,
    76  	"F29": REG_F29,
    77  	"F30": REG_F30,
    78  	"F31": REG_F31,
    79  
    80  	"V0":  REG_V0,
    81  	"V1":  REG_V1,
    82  	"V2":  REG_V2,
    83  	"V3":  REG_V3,
    84  	"V4":  REG_V4,
    85  	"V5":  REG_V5,
    86  	"V6":  REG_V6,
    87  	"V7":  REG_V7,
    88  	"V8":  REG_V8,
    89  	"V9":  REG_V9,
    90  	"V10": REG_V10,
    91  	"V11": REG_V11,
    92  	"V12": REG_V12,
    93  	"V13": REG_V13,
    94  	"V14": REG_V14,
    95  	"V15": REG_V15,
    96  
    97  	"PC_B": REG_PC_B,
    98  }
    99  
   100  var registerNames []string
   101  
   102  func init() {
   103  	obj.RegisterRegister(MINREG, MAXREG, rconv)
   104  	obj.RegisterOpcode(obj.ABaseWasm, Anames)
   105  
   106  	registerNames = make([]string, MAXREG-MINREG)
   107  	for name, reg := range Register {
   108  		registerNames[reg-MINREG] = name
   109  	}
   110  }
   111  
   112  func rconv(r int) string {
   113  	return registerNames[r-MINREG]
   114  }
   115  
   116  var unaryDst = map[obj.As]bool{
   117  	ASet:          true,
   118  	ATee:          true,
   119  	ACall:         true,
   120  	ACallIndirect: true,
   121  	ABr:           true,
   122  	ABrIf:         true,
   123  	ABrTable:      true,
   124  	AI32Store:     true,
   125  	AI64Store:     true,
   126  	AF32Store:     true,
   127  	AF64Store:     true,
   128  	AI32Store8:    true,
   129  	AI32Store16:   true,
   130  	AI64Store8:    true,
   131  	AI64Store16:   true,
   132  	AI64Store32:   true,
   133  	ACALLNORESUME: true,
   134  }
   135  
   136  var Linkwasm = obj.LinkArch{
   137  	Arch:       sys.ArchWasm,
   138  	Init:       instinit,
   139  	Preprocess: preprocess,
   140  	Assemble:   assemble,
   141  	UnaryDst:   unaryDst,
   142  }
   143  
   144  var (
   145  	morestack             *obj.LSym
   146  	morestackNoCtxt       *obj.LSym
   147  	sigpanic              *obj.LSym
   148  	wasm_pc_f_loop_export *obj.LSym
   149  	runtimeNotInitialized *obj.LSym
   150  )
   151  
   152  const (
   153  	/* mark flags */
   154  	WasmImport = 1 << 0
   155  )
   156  
   157  const (
   158  	// This is a special wasm module name that when used as the module name
   159  	// in //go:wasmimport will cause the generated code to pass the stack pointer
   160  	// directly to the imported function. In other words, any function that
   161  	// uses the gojs module understands the internal Go WASM ABI directly.
   162  	GojsModule = "gojs"
   163  )
   164  
   165  func instinit(ctxt *obj.Link) {
   166  	morestack = ctxt.Lookup("runtime.morestack")
   167  	morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
   168  	sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
   169  	wasm_pc_f_loop_export = ctxt.Lookup("wasm_pc_f_loop_export")
   170  	runtimeNotInitialized = ctxt.Lookup("runtime.notInitialized")
   171  }
   172  
   173  func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
   174  	appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog {
   175  		if p.As != obj.ANOP {
   176  			p2 := obj.Appendp(p, newprog)
   177  			p2.Pc = p.Pc
   178  			p = p2
   179  		}
   180  		p.As = as
   181  		switch len(args) {
   182  		case 0:
   183  			p.From = obj.Addr{}
   184  			p.To = obj.Addr{}
   185  		case 1:
   186  			if unaryDst[as] {
   187  				p.From = obj.Addr{}
   188  				p.To = args[0]
   189  			} else {
   190  				p.From = args[0]
   191  				p.To = obj.Addr{}
   192  			}
   193  		case 2:
   194  			p.From = args[0]
   195  			p.To = args[1]
   196  		default:
   197  			panic("bad args")
   198  		}
   199  		return p
   200  	}
   201  
   202  	framesize := s.Func().Text.To.Offset
   203  	if framesize < 0 {
   204  		panic("bad framesize")
   205  	}
   206  	s.Func().Args = s.Func().Text.To.Val.(int32)
   207  	s.Func().Locals = int32(framesize)
   208  
   209  	// If the function exits just to call out to a wasmimport, then
   210  	// generate the code to translate from our internal Go-stack
   211  	// based call convention to the native webassembly call convention.
   212  	if s.Func().WasmImport != nil {
   213  		genWasmImportWrapper(s, appendp)
   214  
   215  		// It should be 0 already, but we'll set it to 0 anyway just to be sure
   216  		// that the code below which adds frame expansion code to the function body
   217  		// isn't run. We don't want the frame expansion code because our function
   218  		// body is just the code to translate and call the imported function.
   219  		framesize = 0
   220  	} else if s.Func().WasmExport != nil {
   221  		genWasmExportWrapper(s, appendp)
   222  	}
   223  
   224  	if framesize > 0 && s.Func().WasmExport == nil { // genWasmExportWrapper has its own prologue generation
   225  		p := s.Func().Text
   226  		p = appendp(p, AGet, regAddr(REG_SP))
   227  		p = appendp(p, AI32Const, constAddr(framesize))
   228  		p = appendp(p, AI32Sub)
   229  		p = appendp(p, ASet, regAddr(REG_SP))
   230  		p.Spadj = int32(framesize)
   231  	}
   232  
   233  	// If the framesize is 0, then imply nosplit because it's a specially
   234  	// generated function.
   235  	needMoreStack := framesize > 0 && !s.Func().Text.From.Sym.NoSplit()
   236  
   237  	// If the maymorestack debug option is enabled, insert the
   238  	// call to maymorestack *before* processing resume points so
   239  	// we can construct a resume point after maymorestack for
   240  	// morestack to resume at.
   241  	var pMorestack = s.Func().Text
   242  	if needMoreStack && ctxt.Flag_maymorestack != "" {
   243  		p := pMorestack
   244  
   245  		// Save REGCTXT on the stack.
   246  		const tempFrame = 8
   247  		p = appendp(p, AGet, regAddr(REG_SP))
   248  		p = appendp(p, AI32Const, constAddr(tempFrame))
   249  		p = appendp(p, AI32Sub)
   250  		p = appendp(p, ASet, regAddr(REG_SP))
   251  		p.Spadj = tempFrame
   252  		ctxtp := obj.Addr{
   253  			Type:   obj.TYPE_MEM,
   254  			Reg:    REG_SP,
   255  			Offset: 0,
   256  		}
   257  		p = appendp(p, AMOVD, regAddr(REGCTXT), ctxtp)
   258  
   259  		// maymorestack must not itself preempt because we
   260  		// don't have full stack information, so this can be
   261  		// ACALLNORESUME.
   262  		p = appendp(p, ACALLNORESUME, constAddr(0))
   263  		// See ../x86/obj6.go
   264  		sym := ctxt.LookupABI(ctxt.Flag_maymorestack, s.ABI())
   265  		p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym}
   266  
   267  		// Restore REGCTXT.
   268  		p = appendp(p, AMOVD, ctxtp, regAddr(REGCTXT))
   269  		p = appendp(p, AGet, regAddr(REG_SP))
   270  		p = appendp(p, AI32Const, constAddr(tempFrame))
   271  		p = appendp(p, AI32Add)
   272  		p = appendp(p, ASet, regAddr(REG_SP))
   273  		p.Spadj = -tempFrame
   274  
   275  		// Add an explicit ARESUMEPOINT after maymorestack for
   276  		// morestack to resume at.
   277  		pMorestack = appendp(p, ARESUMEPOINT)
   278  	}
   279  
   280  	// Introduce resume points for CALL instructions
   281  	// and collect other explicit resume points.
   282  	numResumePoints := 0
   283  	explicitBlockDepth := 0
   284  	pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction
   285  	var tableIdxs []uint64
   286  	tablePC := int64(0)
   287  	base := ctxt.PosTable.Pos(s.Func().Text.Pos).Base()
   288  	for p := s.Func().Text; p != nil; p = p.Link {
   289  		prevBase := base
   290  		base = ctxt.PosTable.Pos(p.Pos).Base()
   291  		switch p.As {
   292  		case ABlock, ALoop, AIf:
   293  			explicitBlockDepth++
   294  
   295  		case AEnd:
   296  			if explicitBlockDepth == 0 {
   297  				panic("End without block")
   298  			}
   299  			explicitBlockDepth--
   300  
   301  		case ARESUMEPOINT:
   302  			if explicitBlockDepth != 0 {
   303  				panic("RESUME can only be used on toplevel")
   304  			}
   305  			p.As = AEnd
   306  			for tablePC <= pc {
   307  				tableIdxs = append(tableIdxs, uint64(numResumePoints))
   308  				tablePC++
   309  			}
   310  			numResumePoints++
   311  			pc++
   312  
   313  		case obj.ACALL:
   314  			if explicitBlockDepth != 0 {
   315  				panic("CALL can only be used on toplevel, try CALLNORESUME instead")
   316  			}
   317  			appendp(p, ARESUMEPOINT)
   318  		}
   319  
   320  		p.Pc = pc
   321  
   322  		// Increase pc whenever some pc-value table needs a new entry. Don't increase it
   323  		// more often to avoid bloat of the BrTable instruction.
   324  		// The "base != prevBase" condition detects inlined instructions. They are an
   325  		// implicit call, so entering and leaving this section affects the stack trace.
   326  		if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase {
   327  			pc++
   328  			if p.To.Sym == sigpanic {
   329  				// The panic stack trace expects the PC at the call of sigpanic,
   330  				// not the next one. However, runtime.Caller subtracts 1 from the
   331  				// PC. To make both PC and PC-1 work (have the same line number),
   332  				// we advance the PC by 2 at sigpanic.
   333  				pc++
   334  			}
   335  		}
   336  	}
   337  	tableIdxs = append(tableIdxs, uint64(numResumePoints))
   338  	s.Size = pc + 1
   339  	if pc >= 1<<16 {
   340  		ctxt.Diag("function too big: %s exceeds 65536 blocks", s)
   341  	}
   342  
   343  	if needMoreStack {
   344  		p := pMorestack
   345  
   346  		if framesize <= abi.StackSmall {
   347  			// small stack: SP <= stackguard
   348  			// Get SP
   349  			// Get g
   350  			// I32WrapI64
   351  			// I32Load $stackguard0
   352  			// I32GtU
   353  
   354  			p = appendp(p, AGet, regAddr(REG_SP))
   355  			p = appendp(p, AGet, regAddr(REGG))
   356  			p = appendp(p, AI32WrapI64)
   357  			p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
   358  			p = appendp(p, AI32LeU)
   359  		} else {
   360  			// large stack: SP-framesize <= stackguard-StackSmall
   361  			//              SP <= stackguard+(framesize-StackSmall)
   362  			// Get SP
   363  			// Get g
   364  			// I32WrapI64
   365  			// I32Load $stackguard0
   366  			// I32Const $(framesize-StackSmall)
   367  			// I32Add
   368  			// I32GtU
   369  
   370  			p = appendp(p, AGet, regAddr(REG_SP))
   371  			p = appendp(p, AGet, regAddr(REGG))
   372  			p = appendp(p, AI32WrapI64)
   373  			p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
   374  			p = appendp(p, AI32Const, constAddr(framesize-abi.StackSmall))
   375  			p = appendp(p, AI32Add)
   376  			p = appendp(p, AI32LeU)
   377  		}
   378  		// TODO(neelance): handle wraparound case
   379  
   380  		p = appendp(p, AIf)
   381  		// This CALL does *not* have a resume point after it
   382  		// (we already inserted all of the resume points). As
   383  		// a result, morestack will resume at the *previous*
   384  		// resume point (typically, the beginning of the
   385  		// function) and perform the morestack check again.
   386  		// This is why we don't need an explicit loop like
   387  		// other architectures.
   388  		p = appendp(p, obj.ACALL, constAddr(0))
   389  		if s.Func().Text.From.Sym.NeedCtxt() {
   390  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack}
   391  		} else {
   392  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt}
   393  		}
   394  		p = appendp(p, AEnd)
   395  	}
   396  
   397  	// record the branches targeting the entry loop and the unwind exit,
   398  	// their targets with be filled in later
   399  	var entryPointLoopBranches []*obj.Prog
   400  	var unwindExitBranches []*obj.Prog
   401  	currentDepth := 0
   402  	for p := s.Func().Text; p != nil; p = p.Link {
   403  		switch p.As {
   404  		case ABlock, ALoop, AIf:
   405  			currentDepth++
   406  		case AEnd:
   407  			currentDepth--
   408  		}
   409  
   410  		switch p.As {
   411  		case obj.AJMP:
   412  			jmp := *p
   413  			p.As = obj.ANOP
   414  
   415  			if jmp.To.Type == obj.TYPE_BRANCH {
   416  				// jump to basic block
   417  				p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc))
   418  				p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B
   419  				p = appendp(p, ABr)                     // jump to beginning of entryPointLoop
   420  				entryPointLoopBranches = append(entryPointLoopBranches, p)
   421  				break
   422  			}
   423  
   424  			// low-level WebAssembly call to function
   425  			switch jmp.To.Type {
   426  			case obj.TYPE_MEM:
   427  				if !notUsePC_B[jmp.To.Sym.Name] {
   428  					// Set PC_B parameter to function entry.
   429  					p = appendp(p, AI32Const, constAddr(0))
   430  				}
   431  				p = appendp(p, ACall, jmp.To)
   432  
   433  			case obj.TYPE_NONE:
   434  				// (target PC is on stack)
   435  				p = appendp(p, AI64Const, constAddr(16)) // only needs PC_F bits (16-63), PC_B bits (0-15) are zero
   436  				p = appendp(p, AI64ShrU)
   437  				p = appendp(p, AI32WrapI64)
   438  
   439  				// Set PC_B parameter to function entry.
   440  				// We need to push this before pushing the target PC_F,
   441  				// so temporarily pop PC_F, using our REG_PC_B as a
   442  				// scratch register, and push it back after pushing 0.
   443  				p = appendp(p, ASet, regAddr(REG_PC_B))
   444  				p = appendp(p, AI32Const, constAddr(0))
   445  				p = appendp(p, AGet, regAddr(REG_PC_B))
   446  
   447  				p = appendp(p, ACallIndirect)
   448  
   449  			default:
   450  				panic("bad target for JMP")
   451  			}
   452  
   453  			p = appendp(p, AReturn)
   454  
   455  		case obj.ACALL, ACALLNORESUME:
   456  			call := *p
   457  			p.As = obj.ANOP
   458  
   459  			pcAfterCall := call.Link.Pc
   460  			if call.To.Sym == sigpanic {
   461  				pcAfterCall-- // sigpanic expects to be called without advancing the pc
   462  			}
   463  
   464  			// SP -= 8
   465  			p = appendp(p, AGet, regAddr(REG_SP))
   466  			p = appendp(p, AI32Const, constAddr(8))
   467  			p = appendp(p, AI32Sub)
   468  			p = appendp(p, ASet, regAddr(REG_SP))
   469  
   470  			// write return address to Go stack
   471  			p = appendp(p, AGet, regAddr(REG_SP))
   472  			p = appendp(p, AI64Const, obj.Addr{
   473  				Type:   obj.TYPE_ADDR,
   474  				Name:   obj.NAME_EXTERN,
   475  				Sym:    s,           // PC_F
   476  				Offset: pcAfterCall, // PC_B
   477  			})
   478  			p = appendp(p, AI64Store, constAddr(0))
   479  
   480  			// low-level WebAssembly call to function
   481  			switch call.To.Type {
   482  			case obj.TYPE_MEM:
   483  				if !notUsePC_B[call.To.Sym.Name] {
   484  					// Set PC_B parameter to function entry.
   485  					p = appendp(p, AI32Const, constAddr(0))
   486  				}
   487  				p = appendp(p, ACall, call.To)
   488  
   489  			case obj.TYPE_NONE:
   490  				// (target PC is on stack)
   491  				p = appendp(p, AI64Const, constAddr(16)) // only needs PC_F bits (16-63), PC_B bits (0-15) are zero
   492  				p = appendp(p, AI64ShrU)
   493  				p = appendp(p, AI32WrapI64)
   494  
   495  				// Set PC_B parameter to function entry.
   496  				// We need to push this before pushing the target PC_F,
   497  				// so temporarily pop PC_F, using our PC_B as a
   498  				// scratch register, and push it back after pushing 0.
   499  				p = appendp(p, ASet, regAddr(REG_PC_B))
   500  				p = appendp(p, AI32Const, constAddr(0))
   501  				p = appendp(p, AGet, regAddr(REG_PC_B))
   502  
   503  				p = appendp(p, ACallIndirect)
   504  
   505  			default:
   506  				panic("bad target for CALL")
   507  			}
   508  
   509  			// return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack
   510  			if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes
   511  				// trying to unwind WebAssembly stack but call has no resume point, terminate with error
   512  				p = appendp(p, AIf)
   513  				p = appendp(p, obj.AUNDEF)
   514  				p = appendp(p, AEnd)
   515  			} else {
   516  				// unwinding WebAssembly stack to switch goroutine, return 1
   517  				p = appendp(p, ABrIf)
   518  				unwindExitBranches = append(unwindExitBranches, p)
   519  			}
   520  
   521  		case obj.ARET, ARETUNWIND:
   522  			ret := *p
   523  			p.As = obj.ANOP
   524  
   525  			if framesize > 0 {
   526  				// SP += framesize
   527  				p = appendp(p, AGet, regAddr(REG_SP))
   528  				p = appendp(p, AI32Const, constAddr(framesize))
   529  				p = appendp(p, AI32Add)
   530  				p = appendp(p, ASet, regAddr(REG_SP))
   531  				// TODO(neelance): This should theoretically set Spadj, but it only works without.
   532  				// p.Spadj = int32(-framesize)
   533  			}
   534  
   535  			if ret.To.Type == obj.TYPE_MEM {
   536  				// Set PC_B parameter to function entry.
   537  				p = appendp(p, AI32Const, constAddr(0))
   538  
   539  				// low-level WebAssembly call to function
   540  				p = appendp(p, ACall, ret.To)
   541  				p = appendp(p, AReturn)
   542  				break
   543  			}
   544  
   545  			// SP += 8
   546  			p = appendp(p, AGet, regAddr(REG_SP))
   547  			p = appendp(p, AI32Const, constAddr(8))
   548  			p = appendp(p, AI32Add)
   549  			p = appendp(p, ASet, regAddr(REG_SP))
   550  
   551  			if ret.As == ARETUNWIND {
   552  				// function needs to unwind the WebAssembly stack, return 1
   553  				p = appendp(p, AI32Const, constAddr(1))
   554  				p = appendp(p, AReturn)
   555  				break
   556  			}
   557  
   558  			// not unwinding the WebAssembly stack, return 0
   559  			p = appendp(p, AI32Const, constAddr(0))
   560  			p = appendp(p, AReturn)
   561  		}
   562  	}
   563  
   564  	for p := s.Func().Text; p != nil; p = p.Link {
   565  		switch p.From.Name {
   566  		case obj.NAME_AUTO:
   567  			p.From.Offset += framesize
   568  		case obj.NAME_PARAM:
   569  			p.From.Reg = REG_SP
   570  			p.From.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address
   571  		}
   572  
   573  		switch p.To.Name {
   574  		case obj.NAME_AUTO:
   575  			p.To.Offset += framesize
   576  		case obj.NAME_PARAM:
   577  			p.To.Reg = REG_SP
   578  			p.To.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address
   579  		}
   580  
   581  		switch p.As {
   582  		case AGet:
   583  			if p.From.Type == obj.TYPE_ADDR {
   584  				get := *p
   585  				p.As = obj.ANOP
   586  
   587  				switch get.From.Name {
   588  				case obj.NAME_EXTERN:
   589  					p = appendp(p, AI64Const, get.From)
   590  				case obj.NAME_AUTO, obj.NAME_PARAM:
   591  					p = appendp(p, AGet, regAddr(get.From.Reg))
   592  					if get.From.Reg == REG_SP {
   593  						p = appendp(p, AI64ExtendI32U)
   594  					}
   595  					if get.From.Offset != 0 {
   596  						p = appendp(p, AI64Const, constAddr(get.From.Offset))
   597  						p = appendp(p, AI64Add)
   598  					}
   599  				default:
   600  					panic("bad Get: invalid name")
   601  				}
   602  			}
   603  
   604  		case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U,
   605  			AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U, AV128Load:
   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  	}
   707  
   708  	{
   709  		p := s.Func().Text
   710  		if len(unwindExitBranches) > 0 {
   711  			p = appendp(p, ABlock) // unwindExit, used to return 1 when unwinding the stack
   712  			for _, b := range unwindExitBranches {
   713  				b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
   714  			}
   715  		}
   716  		if len(entryPointLoopBranches) > 0 {
   717  			p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks
   718  			for _, b := range entryPointLoopBranches {
   719  				b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
   720  			}
   721  		}
   722  		if numResumePoints > 0 {
   723  			// Add Block instructions for resume points and BrTable to jump to selected resume point.
   724  			for i := 0; i < numResumePoints+1; i++ {
   725  				p = appendp(p, ABlock)
   726  			}
   727  			p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B
   728  			p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs})
   729  			p = appendp(p, AEnd) // end of Block
   730  		}
   731  		for p.Link != nil {
   732  			p = p.Link // function instructions
   733  		}
   734  		if len(entryPointLoopBranches) > 0 {
   735  			p = appendp(p, AEnd) // end of entryPointLoop
   736  		}
   737  		p = appendp(p, obj.AUNDEF)
   738  		if len(unwindExitBranches) > 0 {
   739  			p = appendp(p, AEnd) // end of unwindExit
   740  			p = appendp(p, AI32Const, constAddr(1))
   741  		}
   742  	}
   743  
   744  	currentDepth = 0
   745  	blockDepths := make(map[*obj.Prog]int)
   746  	for p := s.Func().Text; p != nil; p = p.Link {
   747  		switch p.As {
   748  		case ABlock, ALoop, AIf:
   749  			currentDepth++
   750  			blockDepths[p] = currentDepth
   751  		case AEnd:
   752  			currentDepth--
   753  		}
   754  
   755  		switch p.As {
   756  		case ABr, ABrIf:
   757  			if p.To.Type == obj.TYPE_BRANCH {
   758  				blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)]
   759  				if !ok {
   760  					panic("label not at block")
   761  				}
   762  				p.To = constAddr(int64(currentDepth - blockDepth))
   763  			}
   764  		}
   765  	}
   766  }
   767  
   768  // Generate function body for wasmimport wrapper function.
   769  func genWasmImportWrapper(s *obj.LSym, appendp func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog) {
   770  	wi := s.Func().WasmImport
   771  	wi.CreateAuxSym()
   772  	p := s.Func().Text
   773  	if p.Link != nil {
   774  		panic("wrapper functions for WASM imports should not have a body")
   775  	}
   776  	to := obj.Addr{
   777  		Type: obj.TYPE_MEM,
   778  		Name: obj.NAME_EXTERN,
   779  		Sym:  s,
   780  	}
   781  
   782  	// If the module that the import is for is our magic "gojs" module, then this
   783  	// indicates that the called function understands the Go stack-based call convention
   784  	// so we just pass the stack pointer to it, knowing it will read the params directly
   785  	// off the stack and push the results into memory based on the stack pointer.
   786  	if wi.Module == GojsModule {
   787  		// The called function has a signature of 'func(sp int)'. It has access to the memory
   788  		// value somewhere to be able to address the memory based on the "sp" value.
   789  
   790  		p = appendp(p, AGet, regAddr(REG_SP))
   791  		p = appendp(p, ACall, to)
   792  
   793  		p.Mark = WasmImport
   794  	} else {
   795  		if len(wi.Results) > 1 {
   796  			// TODO(evanphx) implement support for the multi-value proposal:
   797  			// https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md
   798  			panic("invalid results type") // impossible until multi-value proposal has landed
   799  		}
   800  		for _, f := range wi.Params {
   801  			// Each load instructions will consume the value of sp on the stack, so
   802  			// we need to read sp for each param. WASM appears to not have a stack dup instruction
   803  			// (a strange omission for a stack-based VM), if it did, we'd be using the dup here.
   804  			p = appendp(p, AGet, regAddr(REG_SP))
   805  
   806  			// Offset is the location of the param on the Go stack (ie relative to sp).
   807  			// Because of our call convention, the parameters are located an additional 8 bytes
   808  			// from sp because we store the return address as an int64 at the bottom of the stack.
   809  			// Ie the stack looks like [return_addr, param3, param2, param1, etc]
   810  
   811  			// Ergo, we add 8 to the true byte offset of the param to skip the return address.
   812  			loadOffset := f.Offset + 8
   813  
   814  			// We're reading the value from the Go stack onto the WASM stack and leaving it there
   815  			// for CALL to pick them up.
   816  			switch f.Type {
   817  			case obj.WasmI32:
   818  				p = appendp(p, AI32Load, constAddr(loadOffset))
   819  			case obj.WasmI64:
   820  				p = appendp(p, AI64Load, constAddr(loadOffset))
   821  			case obj.WasmF32:
   822  				p = appendp(p, AF32Load, constAddr(loadOffset))
   823  			case obj.WasmF64:
   824  				p = appendp(p, AF64Load, constAddr(loadOffset))
   825  			case obj.WasmV128:
   826  				p = appendp(p, AV128Load, constAddr(loadOffset))
   827  			case obj.WasmPtr:
   828  				p = appendp(p, AI32Load, constAddr(loadOffset))
   829  			case obj.WasmBool:
   830  				p = appendp(p, AI32Load8U, constAddr(loadOffset))
   831  			default:
   832  				panic("bad param type")
   833  			}
   834  		}
   835  
   836  		// The call instruction is marked as being for a wasm import so that a later phase
   837  		// will generate relocation information that allows us to patch this with then
   838  		// offset of the imported function in the wasm imports.
   839  		p = appendp(p, ACall, to)
   840  		p.Mark = WasmImport
   841  
   842  		if len(wi.Results) == 1 {
   843  			f := wi.Results[0]
   844  
   845  			// Much like with the params, we need to adjust the offset we store the result value
   846  			// to by 8 bytes to account for the return address on the Go stack.
   847  			storeOffset := f.Offset + 8
   848  
   849  			// We need to push SP on the Wasm stack for the Store instruction, which needs to
   850  			// be pushed before the value (call result). So we pop the value into a register,
   851  			// push SP, and push the value back.
   852  			// We cannot get the SP onto the stack before the call, as if the host function
   853  			// calls back into Go, the Go stack may have moved.
   854  			switch f.Type {
   855  			case obj.WasmI32:
   856  				p = appendp(p, AI64ExtendI32U) // the register is 64-bit, so we have to extend
   857  				p = appendp(p, ASet, regAddr(REG_R0))
   858  				p = appendp(p, AGet, regAddr(REG_SP))
   859  				p = appendp(p, AGet, regAddr(REG_R0))
   860  				p = appendp(p, AI64Store32, constAddr(storeOffset))
   861  			case obj.WasmI64:
   862  				p = appendp(p, ASet, regAddr(REG_R0))
   863  				p = appendp(p, AGet, regAddr(REG_SP))
   864  				p = appendp(p, AGet, regAddr(REG_R0))
   865  				p = appendp(p, AI64Store, constAddr(storeOffset))
   866  			case obj.WasmF32:
   867  				p = appendp(p, ASet, regAddr(REG_F0))
   868  				p = appendp(p, AGet, regAddr(REG_SP))
   869  				p = appendp(p, AGet, regAddr(REG_F0))
   870  				p = appendp(p, AF32Store, constAddr(storeOffset))
   871  			case obj.WasmF64:
   872  				p = appendp(p, ASet, regAddr(REG_F16))
   873  				p = appendp(p, AGet, regAddr(REG_SP))
   874  				p = appendp(p, AGet, regAddr(REG_F16))
   875  				p = appendp(p, AF64Store, constAddr(storeOffset))
   876  			case obj.WasmV128:
   877  				p = appendp(p, ASet, regAddr(REG_V0))
   878  				p = appendp(p, AGet, regAddr(REG_SP))
   879  				p = appendp(p, AGet, regAddr(REG_V0))
   880  				p = appendp(p, AV128Store, constAddr(storeOffset))
   881  			case obj.WasmPtr:
   882  				p = appendp(p, AI64ExtendI32U)
   883  				p = appendp(p, ASet, regAddr(REG_R0))
   884  				p = appendp(p, AGet, regAddr(REG_SP))
   885  				p = appendp(p, AGet, regAddr(REG_R0))
   886  				p = appendp(p, AI64Store, constAddr(storeOffset))
   887  			case obj.WasmBool:
   888  				p = appendp(p, AI64ExtendI32U)
   889  				p = appendp(p, ASet, regAddr(REG_R0))
   890  				p = appendp(p, AGet, regAddr(REG_SP))
   891  				p = appendp(p, AGet, regAddr(REG_R0))
   892  				p = appendp(p, AI64Store8, constAddr(storeOffset))
   893  			default:
   894  				panic("bad result type")
   895  			}
   896  		}
   897  	}
   898  
   899  	p = appendp(p, obj.ARET)
   900  }
   901  
   902  // Generate function body for wasmexport wrapper function.
   903  func genWasmExportWrapper(s *obj.LSym, appendp func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog) {
   904  	we := s.Func().WasmExport
   905  	we.CreateAuxSym()
   906  	p := s.Func().Text
   907  	framesize := p.To.Offset
   908  	for p.Link != nil && p.Link.As == obj.AFUNCDATA {
   909  		p = p.Link
   910  	}
   911  	if p.Link != nil {
   912  		panic("wrapper functions for WASM export should not have a body")
   913  	}
   914  
   915  	// Detect and error out if called before runtime initialization
   916  	// SP is 0 if not initialized
   917  	p = appendp(p, AGet, regAddr(REG_SP))
   918  	p = appendp(p, AI32Eqz)
   919  	p = appendp(p, AIf)
   920  	p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: runtimeNotInitialized})
   921  	p = appendp(p, AEnd)
   922  
   923  	// Now that we've checked the SP, generate the prologue
   924  	if framesize > 0 {
   925  		p = appendp(p, AGet, regAddr(REG_SP))
   926  		p = appendp(p, AI32Const, constAddr(framesize))
   927  		p = appendp(p, AI32Sub)
   928  		p = appendp(p, ASet, regAddr(REG_SP))
   929  		p.Spadj = int32(framesize)
   930  	}
   931  
   932  	// Store args
   933  	for i, f := range we.Params {
   934  		p = appendp(p, AGet, regAddr(REG_SP))
   935  		p = appendp(p, AGet, regAddr(REG_R0+int16(i)))
   936  		switch f.Type {
   937  		case obj.WasmI32:
   938  			p = appendp(p, AI32Store, constAddr(f.Offset))
   939  		case obj.WasmI64:
   940  			p = appendp(p, AI64Store, constAddr(f.Offset))
   941  		case obj.WasmF32:
   942  			p = appendp(p, AF32Store, constAddr(f.Offset))
   943  		case obj.WasmF64:
   944  			p = appendp(p, AF64Store, constAddr(f.Offset))
   945  		case obj.WasmV128:
   946  			p = appendp(p, AV128Store, constAddr(f.Offset))
   947  		case obj.WasmPtr:
   948  			p = appendp(p, AI64ExtendI32U)
   949  			p = appendp(p, AI64Store, constAddr(f.Offset))
   950  		case obj.WasmBool:
   951  			p = appendp(p, AI32Store8, constAddr(f.Offset))
   952  		default:
   953  			panic("bad param type")
   954  		}
   955  	}
   956  
   957  	// Call the Go function.
   958  	// XXX maybe use ACALL and let later phase expand? But we don't use PC_B. Maybe we should?
   959  	// Go calling convention expects we push a return PC before call.
   960  	// SP -= 8
   961  	p = appendp(p, AGet, regAddr(REG_SP))
   962  	p = appendp(p, AI32Const, constAddr(8))
   963  	p = appendp(p, AI32Sub)
   964  	p = appendp(p, ASet, regAddr(REG_SP))
   965  	// write return address to Go stack
   966  	p = appendp(p, AGet, regAddr(REG_SP))
   967  	retAddr := obj.Addr{
   968  		Type:   obj.TYPE_ADDR,
   969  		Name:   obj.NAME_EXTERN,
   970  		Sym:    s, // PC_F
   971  		Offset: 1, // PC_B=1, past the prologue, so we have the right SP delta
   972  	}
   973  	if framesize == 0 {
   974  		// Frameless function, no prologue.
   975  		retAddr.Offset = 0
   976  	}
   977  	p = appendp(p, AI64Const, retAddr)
   978  	p = appendp(p, AI64Store, constAddr(0))
   979  	// Set PC_B parameter to function entry
   980  	p = appendp(p, AI32Const, constAddr(0))
   981  	p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: we.WrappedSym})
   982  	// Return value is on the top of the stack, indicating whether to unwind the Wasm stack.
   983  	// In the unwinding case, we call wasm_pc_f_loop_export to handle stack switch and rewinding,
   984  	// until a normal return (non-unwinding) back to this function.
   985  	p = appendp(p, AIf)
   986  	p = appendp(p, AI64Const, retAddr)
   987  	p = appendp(p, AI64Const, constAddr(16))
   988  	p = appendp(p, AI64ShrU)
   989  	p = appendp(p, AI32WrapI64)
   990  	p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: wasm_pc_f_loop_export})
   991  	p = appendp(p, AEnd)
   992  
   993  	// Load result
   994  	if len(we.Results) > 1 {
   995  		panic("invalid results type")
   996  	} else if len(we.Results) == 1 {
   997  		p = appendp(p, AGet, regAddr(REG_SP))
   998  		f := we.Results[0]
   999  		switch f.Type {
  1000  		case obj.WasmI32:
  1001  			p = appendp(p, AI32Load, constAddr(f.Offset))
  1002  		case obj.WasmI64:
  1003  			p = appendp(p, AI64Load, constAddr(f.Offset))
  1004  		case obj.WasmF32:
  1005  			p = appendp(p, AF32Load, constAddr(f.Offset))
  1006  		case obj.WasmF64:
  1007  			p = appendp(p, AF64Load, constAddr(f.Offset))
  1008  		case obj.WasmV128:
  1009  			p = appendp(p, AV128Load, constAddr(f.Offset))
  1010  		case obj.WasmPtr:
  1011  			p = appendp(p, AI32Load, constAddr(f.Offset))
  1012  		case obj.WasmBool:
  1013  			p = appendp(p, AI32Load8U, constAddr(f.Offset))
  1014  		default:
  1015  			panic("bad result type")
  1016  		}
  1017  	}
  1018  
  1019  	// Epilogue. Cannot use ARET as we don't follow Go calling convention.
  1020  	if framesize > 0 {
  1021  		// SP += framesize
  1022  		p = appendp(p, AGet, regAddr(REG_SP))
  1023  		p = appendp(p, AI32Const, constAddr(framesize))
  1024  		p = appendp(p, AI32Add)
  1025  		p = appendp(p, ASet, regAddr(REG_SP))
  1026  	}
  1027  	p = appendp(p, AReturn)
  1028  }
  1029  
  1030  func constAddr(value int64) obj.Addr {
  1031  	return obj.Addr{Type: obj.TYPE_CONST, Offset: value}
  1032  }
  1033  
  1034  func regAddr(reg int16) obj.Addr {
  1035  	return obj.Addr{Type: obj.TYPE_REG, Reg: reg}
  1036  }
  1037  
  1038  // Most of the Go functions has a single parameter (PC_B) in
  1039  // Wasm ABI. This is a list of exceptions.
  1040  var notUsePC_B = map[string]bool{
  1041  	"_rt0_wasm_js":            true,
  1042  	"_rt0_wasm_wasip1":        true,
  1043  	"_rt0_wasm_wasip1_lib":    true,
  1044  	"wasm_export_run":         true,
  1045  	"wasm_export_resume":      true,
  1046  	"wasm_export_getsp":       true,
  1047  	"wasm_pc_f_loop":          true,
  1048  	"wasm_pc_f_loop_export":   true,
  1049  	"gcWriteBarrier":          true,
  1050  	"runtime.gcWriteBarrier1": true,
  1051  	"runtime.gcWriteBarrier2": true,
  1052  	"runtime.gcWriteBarrier3": true,
  1053  	"runtime.gcWriteBarrier4": true,
  1054  	"runtime.gcWriteBarrier5": true,
  1055  	"runtime.gcWriteBarrier6": true,
  1056  	"runtime.gcWriteBarrier7": true,
  1057  	"runtime.gcWriteBarrier8": true,
  1058  	"runtime.notInitialized":  true,
  1059  	"runtime.wasmDiv":         true,
  1060  	"runtime.wasmTruncS":      true,
  1061  	"runtime.wasmTruncU":      true,
  1062  	"cmpbody":                 true,
  1063  	"memeqbody":               true,
  1064  	"memcmp":                  true,
  1065  	"memchr":                  true,
  1066  }
  1067  
  1068  func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
  1069  	type regVar struct {
  1070  		global bool
  1071  		index  uint64
  1072  	}
  1073  
  1074  	type varDecl struct {
  1075  		count uint64
  1076  		typ   valueType
  1077  	}
  1078  
  1079  	hasLocalSP := false
  1080  	regVars := [MAXREG - MINREG]*regVar{
  1081  		REG_SP - MINREG:    {true, 0},
  1082  		REG_CTXT - MINREG:  {true, 1},
  1083  		REG_g - MINREG:     {true, 2},
  1084  		REG_RET0 - MINREG:  {true, 3},
  1085  		REG_RET1 - MINREG:  {true, 4},
  1086  		REG_RET2 - MINREG:  {true, 5},
  1087  		REG_RET3 - MINREG:  {true, 6},
  1088  		REG_PAUSE - MINREG: {true, 7},
  1089  	}
  1090  	var varDecls []*varDecl
  1091  	useAssemblyRegMap := func() {
  1092  		for i := int16(0); i < 16; i++ {
  1093  			regVars[REG_R0+i-MINREG] = &regVar{false, uint64(i)}
  1094  		}
  1095  	}
  1096  
  1097  	// Function starts with declaration of locals: numbers and types.
  1098  	// Some functions use a special calling convention.
  1099  	switch s.Name {
  1100  	case "_rt0_wasm_js", "_rt0_wasm_wasip1", "_rt0_wasm_wasip1_lib",
  1101  		"wasm_export_run", "wasm_export_resume", "wasm_export_getsp",
  1102  		"wasm_pc_f_loop", "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody":
  1103  		varDecls = []*varDecl{}
  1104  		useAssemblyRegMap()
  1105  	case "wasm_pc_f_loop_export":
  1106  		varDecls = []*varDecl{{count: 2, typ: i32}}
  1107  		useAssemblyRegMap()
  1108  	case "memchr", "memcmp":
  1109  		varDecls = []*varDecl{{count: 2, typ: i32}}
  1110  		useAssemblyRegMap()
  1111  	case "cmpbody":
  1112  		varDecls = []*varDecl{{count: 2, typ: i64}}
  1113  		useAssemblyRegMap()
  1114  	case "gcWriteBarrier":
  1115  		varDecls = []*varDecl{{count: 5, typ: i64}}
  1116  		useAssemblyRegMap()
  1117  	case "runtime.gcWriteBarrier1",
  1118  		"runtime.gcWriteBarrier2",
  1119  		"runtime.gcWriteBarrier3",
  1120  		"runtime.gcWriteBarrier4",
  1121  		"runtime.gcWriteBarrier5",
  1122  		"runtime.gcWriteBarrier6",
  1123  		"runtime.gcWriteBarrier7",
  1124  		"runtime.gcWriteBarrier8",
  1125  		"runtime.notInitialized":
  1126  		// no locals
  1127  		useAssemblyRegMap()
  1128  	default:
  1129  		if s.Func().WasmExport != nil {
  1130  			// no local SP, not following Go calling convention
  1131  			useAssemblyRegMap()
  1132  			break
  1133  		}
  1134  
  1135  		// Normal calling convention: PC_B as WebAssembly parameter. First local variable is local SP cache.
  1136  		regVars[REG_PC_B-MINREG] = &regVar{false, 0}
  1137  		hasLocalSP = true
  1138  
  1139  		var regUsed [MAXREG - MINREG]bool
  1140  		for p := s.Func().Text; p != nil; p = p.Link {
  1141  			if p.From.Reg != 0 {
  1142  				regUsed[p.From.Reg-MINREG] = true
  1143  			}
  1144  			if p.To.Reg != 0 {
  1145  				regUsed[p.To.Reg-MINREG] = true
  1146  			}
  1147  		}
  1148  
  1149  		regs := []int16{REG_SP}
  1150  		for reg := int16(REG_R0); reg <= REG_V15; reg++ {
  1151  			if regUsed[reg-MINREG] {
  1152  				regs = append(regs, reg)
  1153  			}
  1154  		}
  1155  
  1156  		var lastDecl *varDecl
  1157  		for i, reg := range regs {
  1158  			t := regType(reg)
  1159  			if lastDecl == nil || lastDecl.typ != t {
  1160  				lastDecl = &varDecl{
  1161  					count: 0,
  1162  					typ:   t,
  1163  				}
  1164  				varDecls = append(varDecls, lastDecl)
  1165  			}
  1166  			lastDecl.count++
  1167  			if reg != REG_SP {
  1168  				regVars[reg-MINREG] = &regVar{false, 1 + uint64(i)}
  1169  			}
  1170  		}
  1171  	}
  1172  
  1173  	w := new(bytes.Buffer)
  1174  
  1175  	writeUleb128(w, uint64(len(varDecls)))
  1176  	for _, decl := range varDecls {
  1177  		writeUleb128(w, decl.count)
  1178  		w.WriteByte(byte(decl.typ))
  1179  	}
  1180  
  1181  	if hasLocalSP {
  1182  		// Copy SP from its global variable into a local variable. Accessing a local variable is more efficient.
  1183  		updateLocalSP(w)
  1184  	}
  1185  
  1186  	for p := s.Func().Text; p != nil; p = p.Link {
  1187  		switch p.As {
  1188  		case AGet:
  1189  			if p.From.Type != obj.TYPE_REG {
  1190  				panic("bad Get: argument is not a register")
  1191  			}
  1192  			reg := p.From.Reg
  1193  			v := regVars[reg-MINREG]
  1194  			if v == nil {
  1195  				panic("bad Get: invalid register")
  1196  			}
  1197  			if reg == REG_SP && hasLocalSP {
  1198  				writeOpcode(w, ALocalGet)
  1199  				writeUleb128(w, 1) // local SP
  1200  				continue
  1201  			}
  1202  			if v.global {
  1203  				writeOpcode(w, AGlobalGet)
  1204  			} else {
  1205  				writeOpcode(w, ALocalGet)
  1206  			}
  1207  			writeUleb128(w, v.index)
  1208  			continue
  1209  
  1210  		case ASet:
  1211  			if p.To.Type != obj.TYPE_REG {
  1212  				panic("bad Set: argument is not a register")
  1213  			}
  1214  			reg := p.To.Reg
  1215  			v := regVars[reg-MINREG]
  1216  			if v == nil {
  1217  				panic("bad Set: invalid register")
  1218  			}
  1219  			if reg == REG_SP && hasLocalSP {
  1220  				writeOpcode(w, ALocalTee)
  1221  				writeUleb128(w, 1) // local SP
  1222  			}
  1223  			if v.global {
  1224  				writeOpcode(w, AGlobalSet)
  1225  			} else {
  1226  				if p.Link.As == AGet && p.Link.From.Reg == reg {
  1227  					writeOpcode(w, ALocalTee)
  1228  					p = p.Link
  1229  				} else {
  1230  					writeOpcode(w, ALocalSet)
  1231  				}
  1232  			}
  1233  			writeUleb128(w, v.index)
  1234  			continue
  1235  
  1236  		case ATee:
  1237  			if p.To.Type != obj.TYPE_REG {
  1238  				panic("bad Tee: argument is not a register")
  1239  			}
  1240  			reg := p.To.Reg
  1241  			v := regVars[reg-MINREG]
  1242  			if v == nil {
  1243  				panic("bad Tee: invalid register")
  1244  			}
  1245  			writeOpcode(w, ALocalTee)
  1246  			writeUleb128(w, v.index)
  1247  			continue
  1248  
  1249  		case ANot:
  1250  			writeOpcode(w, AI32Eqz)
  1251  			continue
  1252  
  1253  		case obj.AUNDEF:
  1254  			writeOpcode(w, AUnreachable)
  1255  			continue
  1256  
  1257  		case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA:
  1258  			// ignore
  1259  			continue
  1260  
  1261  		case AV128Const:
  1262  			writeOpcode(w, AV128Const)
  1263  			// Despite what the spec implies, this is the format of a 128-bit constant.
  1264  			writeLE64(w, p.From.Offset)
  1265  			writeLE64(w, p.To.Offset)
  1266  			continue
  1267  		}
  1268  
  1269  		writeOpcode(w, p.As)
  1270  
  1271  		switch p.As {
  1272  		case ABlock, ALoop, AIf:
  1273  			if p.From.Offset != 0 {
  1274  				// block type, rarely used, e.g. for code compiled with emscripten
  1275  				w.WriteByte(0x80 - byte(p.From.Offset))
  1276  				continue
  1277  			}
  1278  			w.WriteByte(0x40)
  1279  
  1280  		case ABr, ABrIf:
  1281  			if p.To.Type != obj.TYPE_CONST {
  1282  				panic("bad Br/BrIf")
  1283  			}
  1284  			writeUleb128(w, uint64(p.To.Offset))
  1285  
  1286  		case ABrTable:
  1287  			idxs := p.To.Val.([]uint64)
  1288  			writeUleb128(w, uint64(len(idxs)-1))
  1289  			for _, idx := range idxs {
  1290  				writeUleb128(w, idx)
  1291  			}
  1292  
  1293  		case ACall:
  1294  			switch p.To.Type {
  1295  			case obj.TYPE_CONST:
  1296  				writeUleb128(w, uint64(p.To.Offset))
  1297  
  1298  			case obj.TYPE_MEM:
  1299  				if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC {
  1300  					fmt.Println(p.To)
  1301  					panic("bad name for Call")
  1302  				}
  1303  				typ := objabi.R_CALL
  1304  				if p.Mark&WasmImport != 0 {
  1305  					typ = objabi.R_WASMIMPORT
  1306  				}
  1307  				s.AddRel(ctxt, obj.Reloc{
  1308  					Type: typ,
  1309  					Off:  int32(w.Len()),
  1310  					Siz:  1, // actually variable sized
  1311  					Sym:  p.To.Sym,
  1312  				})
  1313  				if hasLocalSP {
  1314  					// The stack may have moved, which changes SP. Update the local SP variable.
  1315  					updateLocalSP(w)
  1316  				}
  1317  
  1318  			default:
  1319  				panic("bad type for Call")
  1320  			}
  1321  
  1322  		case AI8x16ExtractLaneS,
  1323  			AI8x16ExtractLaneU,
  1324  			AI8x16ReplaceLane,
  1325  			AI16x8ExtractLaneS,
  1326  			AI16x8ExtractLaneU,
  1327  			AI16x8ReplaceLane,
  1328  			AI32x4ExtractLane,
  1329  			AI32x4ReplaceLane,
  1330  			AI64x2ExtractLane,
  1331  			AI64x2ReplaceLane,
  1332  			AF32x4ExtractLane,
  1333  			AF32x4ReplaceLane,
  1334  			AF64x2ExtractLane,
  1335  			AF64x2ReplaceLane:
  1336  			writeUleb128(w, uint64(p.To.Offset))
  1337  
  1338  		case ACallIndirect:
  1339  			writeUleb128(w, uint64(p.To.Offset))
  1340  			w.WriteByte(0x00) // reserved value
  1341  			if hasLocalSP {
  1342  				// The stack may have moved, which changes SP. Update the local SP variable.
  1343  				updateLocalSP(w)
  1344  			}
  1345  
  1346  		case AI32Const, AI64Const:
  1347  			if p.From.Name == obj.NAME_EXTERN {
  1348  				s.AddRel(ctxt, obj.Reloc{
  1349  					Type: objabi.R_ADDR,
  1350  					Off:  int32(w.Len()),
  1351  					Siz:  1, // actually variable sized
  1352  					Sym:  p.From.Sym,
  1353  					Add:  p.From.Offset,
  1354  				})
  1355  				break
  1356  			}
  1357  			writeSleb128(w, p.From.Offset)
  1358  
  1359  		case AF32Const:
  1360  			b := make([]byte, 4)
  1361  			binary.LittleEndian.PutUint32(b, math.Float32bits(float32(p.From.Val.(float64))))
  1362  			w.Write(b)
  1363  
  1364  		case AF64Const:
  1365  			b := make([]byte, 8)
  1366  			binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64)))
  1367  			w.Write(b)
  1368  
  1369  		case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U,
  1370  			AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U, AV128Load:
  1371  			if p.From.Offset < 0 {
  1372  				panic("negative offset for *Load")
  1373  			}
  1374  			if p.From.Type != obj.TYPE_CONST {
  1375  				panic(fmt.Errorf("bad type for *Load, wanted TYPE_CONST, got %v", p.From.Type))
  1376  			}
  1377  			if p.From.Offset > math.MaxUint32 {
  1378  				ctxt.Diag("bad offset in %v", p)
  1379  			}
  1380  			writeUleb128(w, align(p.As))
  1381  			writeUleb128(w, uint64(p.From.Offset))
  1382  
  1383  		case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32, AV128Store:
  1384  			if p.To.Offset < 0 {
  1385  				panic("negative offset")
  1386  			}
  1387  			if p.To.Offset > math.MaxUint32 {
  1388  				ctxt.Diag("bad offset in %v", p)
  1389  			}
  1390  			writeUleb128(w, align(p.As))
  1391  			writeUleb128(w, uint64(p.To.Offset))
  1392  
  1393  		case ACurrentMemory, AGrowMemory, AMemoryFill:
  1394  			w.WriteByte(0x00)
  1395  
  1396  		case AMemoryCopy:
  1397  			w.WriteByte(0x00)
  1398  			w.WriteByte(0x00)
  1399  
  1400  		}
  1401  	}
  1402  
  1403  	w.WriteByte(0x0b) // end
  1404  
  1405  	s.P = w.Bytes()
  1406  }
  1407  
  1408  func updateLocalSP(w *bytes.Buffer) {
  1409  	writeOpcode(w, AGlobalGet)
  1410  	writeUleb128(w, 0) // global SP
  1411  	writeOpcode(w, ALocalSet)
  1412  	writeUleb128(w, 1) // local SP
  1413  }
  1414  
  1415  func writeOpcode(w *bytes.Buffer, as obj.As) {
  1416  	switch {
  1417  	case as < AUnreachable:
  1418  		panic(fmt.Sprintf("unexpected assembler op: %s", as))
  1419  	case as < AEnd:
  1420  		w.WriteByte(byte(as - AUnreachable + 0x00))
  1421  	case as < ADrop:
  1422  		w.WriteByte(byte(as - AEnd + 0x0B))
  1423  	case as < ALocalGet:
  1424  		w.WriteByte(byte(as - ADrop + 0x1A))
  1425  	case as < AI32Load:
  1426  		w.WriteByte(byte(as - ALocalGet + 0x20))
  1427  	case as < AI32TruncSatF32S:
  1428  		w.WriteByte(byte(as - AI32Load + 0x28))
  1429  	case as < AV128Load:
  1430  		w.WriteByte(0xFC)
  1431  		w.WriteByte(byte(as - AI32TruncSatF32S + 0x00))
  1432  	case as < AI16x8Abs: // [AV128Load, AI16x8Abs)
  1433  		w.WriteByte(0xFD)
  1434  		writeUleb128(w, uint64(as-AV128Load+0x00))
  1435  	case as < AI8x16RelaxedSwizzle: // [AI16x8Abs, AI8x16RelaxedSwizzle)
  1436  		w.WriteByte(0xFD)
  1437  		writeUleb128(w, uint64(as-AI16x8Abs+0x80))
  1438  		w.WriteByte(0x01)
  1439  	case as <= AI32x4RelaxedDotI8x16I7x16AddS: // [AI8x16RelaxedSwizzle, AI32x4RelaxedDotI8x16I7x16AddS]
  1440  		w.WriteByte(0xFD)
  1441  		writeUleb128(w, uint64(as-AI8x16RelaxedSwizzle+0x80))
  1442  		w.WriteByte(0x02)
  1443  
  1444  	default:
  1445  		panic(fmt.Sprintf("unexpected assembler op: %s", as))
  1446  	}
  1447  }
  1448  
  1449  type valueType byte
  1450  
  1451  const (
  1452  	i32  valueType = 0x7F
  1453  	i64  valueType = 0x7E
  1454  	f32  valueType = 0x7D
  1455  	f64  valueType = 0x7C
  1456  	v128 valueType = 0x7b
  1457  )
  1458  
  1459  func regType(reg int16) valueType {
  1460  	switch {
  1461  	case reg == REG_SP:
  1462  		return i32
  1463  	case reg >= REG_R0 && reg <= REG_R15:
  1464  		return i64
  1465  	case reg >= REG_F0 && reg <= REG_F15:
  1466  		return f32
  1467  	case reg >= REG_F16 && reg <= REG_F31:
  1468  		return f64
  1469  	case reg >= REG_V0 && reg <= REG_V15:
  1470  		return v128
  1471  	default:
  1472  		panic("invalid register")
  1473  	}
  1474  }
  1475  
  1476  func align(as obj.As) uint64 {
  1477  	switch as {
  1478  	case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8:
  1479  		return 0
  1480  	case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16:
  1481  		return 1
  1482  	case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32:
  1483  		return 2
  1484  	case AI64Load, AF64Load, AI64Store, AF64Store:
  1485  		return 3
  1486  	case AV128Load, AV128Store:
  1487  		return 0 // TODO do we want more alignment
  1488  	default:
  1489  		panic("align: bad op")
  1490  	}
  1491  }
  1492  
  1493  func writeUleb128(w io.ByteWriter, v uint64) {
  1494  	if v < 128 {
  1495  		w.WriteByte(uint8(v))
  1496  		return
  1497  	}
  1498  	more := true
  1499  	for more {
  1500  		c := uint8(v & 0x7f)
  1501  		v >>= 7
  1502  		more = v != 0
  1503  		if more {
  1504  			c |= 0x80
  1505  		}
  1506  		w.WriteByte(c)
  1507  	}
  1508  }
  1509  
  1510  func writeSleb128(w io.ByteWriter, v int64) {
  1511  	more := true
  1512  	for more {
  1513  		c := uint8(v & 0x7f)
  1514  		s := uint8(v & 0x40)
  1515  		v >>= 7
  1516  		more = !((v == 0 && s == 0) || (v == -1 && s != 0))
  1517  		if more {
  1518  			c |= 0x80
  1519  		}
  1520  		w.WriteByte(c)
  1521  	}
  1522  }
  1523  
  1524  func writeLE64(w io.ByteWriter, v int64) {
  1525  	for i := 0; i < 8; i++ {
  1526  		w.WriteByte(uint8(v & 0xff))
  1527  		v >>= 8
  1528  	}
  1529  }
  1530  

View as plain text