github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/obj/wasm/wasmobj.go (about)

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