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

View as plain text