github.com/gernest/nezuko@v0.1.2/internal/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  	"github.com/gernest/nezuko/internal/obj"
    12  	"github.com/gernest/nezuko/internal/objabi"
    13  	"github.com/gernest/nezuko/internal/sys"
    14  	"io"
    15  	"math"
    16  )
    17  
    18  var Register = map[string]int16{
    19  	"PC_F":  REG_PC_F,
    20  	"PC_B":  REG_PC_B,
    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  
    65  var registerNames []string
    66  
    67  func init() {
    68  	obj.RegisterRegister(MINREG, MAXREG, rconv)
    69  	obj.RegisterOpcode(obj.ABaseWasm, Anames)
    70  
    71  	registerNames = make([]string, MAXREG-MINREG)
    72  	for name, reg := range Register {
    73  		registerNames[reg-MINREG] = name
    74  	}
    75  }
    76  
    77  func rconv(r int) string {
    78  	return registerNames[r-MINREG]
    79  }
    80  
    81  var unaryDst = map[obj.As]bool{
    82  	ASet:          true,
    83  	ATee:          true,
    84  	ACall:         true,
    85  	ACallIndirect: true,
    86  	ACallImport:   true,
    87  	ABr:           true,
    88  	ABrIf:         true,
    89  	ABrTable:      true,
    90  	AI32Store:     true,
    91  	AI64Store:     true,
    92  	AF32Store:     true,
    93  	AF64Store:     true,
    94  	AI32Store8:    true,
    95  	AI32Store16:   true,
    96  	AI64Store8:    true,
    97  	AI64Store16:   true,
    98  	AI64Store32:   true,
    99  	ACALLNORESUME: true,
   100  }
   101  
   102  var Linkwasm = obj.LinkArch{
   103  	Arch:       sys.ArchWasm,
   104  	Init:       instinit,
   105  	Preprocess: preprocess,
   106  	Assemble:   assemble,
   107  	UnaryDst:   unaryDst,
   108  }
   109  
   110  var (
   111  	morestack       *obj.LSym
   112  	morestackNoCtxt *obj.LSym
   113  	gcWriteBarrier  *obj.LSym
   114  	sigpanic        *obj.LSym
   115  	deferreturn     *obj.LSym
   116  	jmpdefer        *obj.LSym
   117  )
   118  
   119  const (
   120  	/* mark flags */
   121  	WasmImport = 1 << 0
   122  )
   123  
   124  func instinit(ctxt *obj.Link) {
   125  	morestack = ctxt.Lookup("runtime.morestack")
   126  	morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
   127  	gcWriteBarrier = ctxt.Lookup("runtime.gcWriteBarrier")
   128  	sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
   129  	deferreturn = ctxt.LookupABI("runtime.deferreturn", obj.ABIInternal)
   130  	// jmpdefer is defined in assembly as ABI0, but what we're
   131  	// looking for is the *call* to jmpdefer from the Go function
   132  	// deferreturn, so we're looking for the ABIInternal version
   133  	// of jmpdefer that's called by Go.
   134  	jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABIInternal)
   135  }
   136  
   137  func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
   138  	appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog {
   139  		if p.As != obj.ANOP {
   140  			p2 := obj.Appendp(p, newprog)
   141  			p2.Pc = p.Pc
   142  			p = p2
   143  		}
   144  		p.As = as
   145  		switch len(args) {
   146  		case 0:
   147  			p.From = obj.Addr{}
   148  			p.To = obj.Addr{}
   149  		case 1:
   150  			if unaryDst[as] {
   151  				p.From = obj.Addr{}
   152  				p.To = args[0]
   153  			} else {
   154  				p.From = args[0]
   155  				p.To = obj.Addr{}
   156  			}
   157  		case 2:
   158  			p.From = args[0]
   159  			p.To = args[1]
   160  		default:
   161  			panic("bad args")
   162  		}
   163  		return p
   164  	}
   165  
   166  	framesize := s.Func.Text.To.Offset
   167  	if framesize < 0 {
   168  		panic("bad framesize")
   169  	}
   170  	s.Func.Args = s.Func.Text.To.Val.(int32)
   171  	s.Func.Locals = int32(framesize)
   172  
   173  	if s.Func.Text.From.Sym.Wrapper() {
   174  		// if g._panic != nil && g._panic.argp == FP {
   175  		//   g._panic.argp = bottom-of-frame
   176  		// }
   177  		//
   178  		// MOVD g_panic(g), R0
   179  		// Get R0
   180  		// I64Eqz
   181  		// Not
   182  		// If
   183  		//   Get SP
   184  		//   I64ExtendUI32
   185  		//   I64Const $framesize+8
   186  		//   I64Add
   187  		//   I64Load panic_argp(R0)
   188  		//   I64Eq
   189  		//   If
   190  		//     MOVD SP, panic_argp(R0)
   191  		//   End
   192  		// End
   193  
   194  		gpanic := obj.Addr{
   195  			Type:   obj.TYPE_MEM,
   196  			Reg:    REGG,
   197  			Offset: 4 * 8, // g_panic
   198  		}
   199  
   200  		panicargp := obj.Addr{
   201  			Type:   obj.TYPE_MEM,
   202  			Reg:    REG_R0,
   203  			Offset: 0, // panic.argp
   204  		}
   205  
   206  		p := s.Func.Text
   207  		p = appendp(p, AMOVD, gpanic, regAddr(REG_R0))
   208  
   209  		p = appendp(p, AGet, regAddr(REG_R0))
   210  		p = appendp(p, AI64Eqz)
   211  		p = appendp(p, ANot)
   212  		p = appendp(p, AIf)
   213  
   214  		p = appendp(p, AGet, regAddr(REG_SP))
   215  		p = appendp(p, AI64ExtendUI32)
   216  		p = appendp(p, AI64Const, constAddr(framesize+8))
   217  		p = appendp(p, AI64Add)
   218  		p = appendp(p, AI64Load, panicargp)
   219  
   220  		p = appendp(p, AI64Eq)
   221  		p = appendp(p, AIf)
   222  		p = appendp(p, AMOVD, regAddr(REG_SP), panicargp)
   223  		p = appendp(p, AEnd)
   224  
   225  		p = appendp(p, AEnd)
   226  	}
   227  
   228  	if framesize > 0 {
   229  		p := s.Func.Text
   230  		p = appendp(p, AGet, regAddr(REG_SP))
   231  		p = appendp(p, AI32Const, constAddr(framesize))
   232  		p = appendp(p, AI32Sub)
   233  		p = appendp(p, ASet, regAddr(REG_SP))
   234  		p.Spadj = int32(framesize)
   235  	}
   236  
   237  	// Introduce resume points for CALL instructions
   238  	// and collect other explicit resume points.
   239  	numResumePoints := 0
   240  	explicitBlockDepth := 0
   241  	pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction
   242  	var tableIdxs []uint64
   243  	tablePC := int64(0)
   244  	base := ctxt.PosTable.Pos(s.Func.Text.Pos).Base()
   245  	for p := s.Func.Text; p != nil; p = p.Link {
   246  		prevBase := base
   247  		base = ctxt.PosTable.Pos(p.Pos).Base()
   248  		switch p.As {
   249  		case ABlock, ALoop, AIf:
   250  			explicitBlockDepth++
   251  
   252  		case AEnd:
   253  			if explicitBlockDepth == 0 {
   254  				panic("End without block")
   255  			}
   256  			explicitBlockDepth--
   257  
   258  		case ARESUMEPOINT:
   259  			if explicitBlockDepth != 0 {
   260  				panic("RESUME can only be used on toplevel")
   261  			}
   262  			p.As = AEnd
   263  			for tablePC <= pc {
   264  				tableIdxs = append(tableIdxs, uint64(numResumePoints))
   265  				tablePC++
   266  			}
   267  			numResumePoints++
   268  			pc++
   269  
   270  		case obj.ACALL:
   271  			if explicitBlockDepth != 0 {
   272  				panic("CALL can only be used on toplevel, try CALLNORESUME instead")
   273  			}
   274  			appendp(p, ARESUMEPOINT)
   275  		}
   276  
   277  		p.Pc = pc
   278  
   279  		// Increase pc whenever some pc-value table needs a new entry. Don't increase it
   280  		// more often to avoid bloat of the BrTable instruction.
   281  		// The "base != prevBase" condition detects inlined instructions. They are an
   282  		// implicit call, so entering and leaving this section affects the stack trace.
   283  		if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase {
   284  			pc++
   285  			if p.To.Sym == sigpanic {
   286  				// The panic stack trace expects the PC at the call of sigpanic,
   287  				// not the next one. However, runtime.Caller subtracts 1 from the
   288  				// PC. To make both PC and PC-1 work (have the same line number),
   289  				// we advance the PC by 2 at sigpanic.
   290  				pc++
   291  			}
   292  		}
   293  	}
   294  	tableIdxs = append(tableIdxs, uint64(numResumePoints))
   295  	s.Size = pc + 1
   296  
   297  	if !s.Func.Text.From.Sym.NoSplit() {
   298  		p := s.Func.Text
   299  
   300  		if framesize <= objabi.StackSmall {
   301  			// small stack: SP <= stackguard
   302  			// Get SP
   303  			// Get g
   304  			// I32WrapI64
   305  			// I32Load $stackguard0
   306  			// I32GtU
   307  
   308  			p = appendp(p, AGet, regAddr(REG_SP))
   309  			p = appendp(p, AGet, regAddr(REGG))
   310  			p = appendp(p, AI32WrapI64)
   311  			p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
   312  			p = appendp(p, AI32LeU)
   313  		} else {
   314  			// large stack: SP-framesize <= stackguard-StackSmall
   315  			//              SP <= stackguard+(framesize-StackSmall)
   316  			// Get SP
   317  			// Get g
   318  			// I32WrapI64
   319  			// I32Load $stackguard0
   320  			// I32Const $(framesize-StackSmall)
   321  			// I32Add
   322  			// I32GtU
   323  
   324  			p = appendp(p, AGet, regAddr(REG_SP))
   325  			p = appendp(p, AGet, regAddr(REGG))
   326  			p = appendp(p, AI32WrapI64)
   327  			p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
   328  			p = appendp(p, AI32Const, constAddr(int64(framesize)-objabi.StackSmall))
   329  			p = appendp(p, AI32Add)
   330  			p = appendp(p, AI32LeU)
   331  		}
   332  		// TODO(neelance): handle wraparound case
   333  
   334  		p = appendp(p, AIf)
   335  		p = appendp(p, obj.ACALL, constAddr(0))
   336  		if s.Func.Text.From.Sym.NeedCtxt() {
   337  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack}
   338  		} else {
   339  			p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt}
   340  		}
   341  		p = appendp(p, AEnd)
   342  	}
   343  
   344  	// Add Block instructions for resume points and BrTable to jump to selected resume point.
   345  	if numResumePoints > 0 {
   346  		p := s.Func.Text
   347  		p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks
   348  
   349  		for i := 0; i < numResumePoints+1; i++ {
   350  			p = appendp(p, ABlock)
   351  		}
   352  		p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B
   353  		p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs})
   354  		p = appendp(p, AEnd) // end of Block
   355  
   356  		for p.Link != nil {
   357  			p = p.Link
   358  		}
   359  
   360  		p = appendp(p, AEnd) // end of entryPointLoop
   361  		p = appendp(p, obj.AUNDEF)
   362  	}
   363  
   364  	p := s.Func.Text
   365  	currentDepth := 0
   366  	blockDepths := make(map[*obj.Prog]int)
   367  	for p != nil {
   368  		switch p.As {
   369  		case ABlock, ALoop, AIf:
   370  			currentDepth++
   371  			blockDepths[p] = currentDepth
   372  		case AEnd:
   373  			currentDepth--
   374  		}
   375  
   376  		switch p.As {
   377  		case ABr, ABrIf:
   378  			if p.To.Type == obj.TYPE_BRANCH {
   379  				blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)]
   380  				if !ok {
   381  					panic("label not at block")
   382  				}
   383  				p.To = constAddr(int64(currentDepth - blockDepth))
   384  			}
   385  		case obj.AJMP:
   386  			jmp := *p
   387  			p.As = obj.ANOP
   388  
   389  			if jmp.To.Type == obj.TYPE_BRANCH {
   390  				// jump to basic block
   391  				p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc))
   392  				p = appendp(p, ASet, regAddr(REG_PC_B))               // write next basic block to PC_B
   393  				p = appendp(p, ABr, constAddr(int64(currentDepth-1))) // jump to beginning of entryPointLoop
   394  				break
   395  			}
   396  
   397  			// reset PC_B to function entry
   398  			p = appendp(p, AI32Const, constAddr(0))
   399  			p = appendp(p, ASet, regAddr(REG_PC_B))
   400  
   401  			// low-level WebAssembly call to function
   402  			switch jmp.To.Type {
   403  			case obj.TYPE_MEM:
   404  				p = appendp(p, ACall, jmp.To)
   405  			case obj.TYPE_NONE:
   406  				// (target PC is on stack)
   407  				p = appendp(p, AI32WrapI64)
   408  				p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
   409  				p = appendp(p, AI32ShrU)
   410  				p = appendp(p, ACallIndirect)
   411  			default:
   412  				panic("bad target for JMP")
   413  			}
   414  
   415  			p = appendp(p, AReturn)
   416  
   417  		case obj.ACALL, ACALLNORESUME:
   418  			call := *p
   419  			p.As = obj.ANOP
   420  
   421  			pcAfterCall := call.Link.Pc
   422  			if call.To.Sym == sigpanic {
   423  				pcAfterCall-- // sigpanic expects to be called without advancing the pc
   424  			}
   425  
   426  			// jmpdefer manipulates the return address on the stack so deferreturn gets called repeatedly.
   427  			// Model this in WebAssembly with a loop.
   428  			if call.To.Sym == deferreturn {
   429  				p = appendp(p, ALoop)
   430  			}
   431  
   432  			// SP -= 8
   433  			p = appendp(p, AGet, regAddr(REG_SP))
   434  			p = appendp(p, AI32Const, constAddr(8))
   435  			p = appendp(p, AI32Sub)
   436  			p = appendp(p, ASet, regAddr(REG_SP))
   437  
   438  			// write return address to Go stack
   439  			p = appendp(p, AGet, regAddr(REG_SP))
   440  			p = appendp(p, AI64Const, obj.Addr{
   441  				Type:   obj.TYPE_ADDR,
   442  				Name:   obj.NAME_EXTERN,
   443  				Sym:    s,           // PC_F
   444  				Offset: pcAfterCall, // PC_B
   445  			})
   446  			p = appendp(p, AI64Store, constAddr(0))
   447  
   448  			// reset PC_B to function entry
   449  			p = appendp(p, AI32Const, constAddr(0))
   450  			p = appendp(p, ASet, regAddr(REG_PC_B))
   451  
   452  			// low-level WebAssembly call to function
   453  			switch call.To.Type {
   454  			case obj.TYPE_MEM:
   455  				p = appendp(p, ACall, call.To)
   456  			case obj.TYPE_NONE:
   457  				// (target PC is on stack)
   458  				p = appendp(p, AI32WrapI64)
   459  				p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
   460  				p = appendp(p, AI32ShrU)
   461  				p = appendp(p, ACallIndirect)
   462  			default:
   463  				panic("bad target for CALL")
   464  			}
   465  
   466  			// gcWriteBarrier has no return value, it never unwinds the stack
   467  			if call.To.Sym == gcWriteBarrier {
   468  				break
   469  			}
   470  
   471  			// jmpdefer removes the frame of deferreturn from the Go stack.
   472  			// However, its WebAssembly function still returns normally,
   473  			// so we need to return from deferreturn without removing its
   474  			// stack frame (no RET), because the frame is already gone.
   475  			if call.To.Sym == jmpdefer {
   476  				p = appendp(p, AReturn)
   477  				break
   478  			}
   479  
   480  			// return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack
   481  			p = appendp(p, AIf)
   482  			if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes
   483  				// trying to unwind WebAssembly stack but call has no resume point, terminate with error
   484  				p = appendp(p, obj.AUNDEF)
   485  			} else {
   486  				// unwinding WebAssembly stack to switch goroutine, return 1
   487  				p = appendp(p, AI32Const, constAddr(1))
   488  				p = appendp(p, AReturn)
   489  			}
   490  			p = appendp(p, AEnd)
   491  
   492  			// jump to before the call if jmpdefer has reset the return address to the call's PC
   493  			if call.To.Sym == deferreturn {
   494  				p = appendp(p, AGet, regAddr(REG_PC_B))
   495  				p = appendp(p, AI32Const, constAddr(call.Pc))
   496  				p = appendp(p, AI32Eq)
   497  				p = appendp(p, ABrIf, constAddr(0))
   498  				p = appendp(p, AEnd) // end of Loop
   499  			}
   500  
   501  		case obj.ARET, ARETUNWIND:
   502  			ret := *p
   503  			p.As = obj.ANOP
   504  
   505  			if framesize > 0 {
   506  				// SP += framesize
   507  				p = appendp(p, AGet, regAddr(REG_SP))
   508  				p = appendp(p, AI32Const, constAddr(framesize))
   509  				p = appendp(p, AI32Add)
   510  				p = appendp(p, ASet, regAddr(REG_SP))
   511  				// TODO(neelance): This should theoretically set Spadj, but it only works without.
   512  				// p.Spadj = int32(-framesize)
   513  			}
   514  
   515  			if ret.To.Type == obj.TYPE_MEM {
   516  				// reset PC_B to function entry
   517  				p = appendp(p, AI32Const, constAddr(0))
   518  				p = appendp(p, ASet, regAddr(REG_PC_B))
   519  
   520  				// low-level WebAssembly call to function
   521  				p = appendp(p, ACall, ret.To)
   522  				p = appendp(p, AReturn)
   523  				break
   524  			}
   525  
   526  			// read return PC_F from Go stack
   527  			p = appendp(p, AGet, regAddr(REG_SP))
   528  			p = appendp(p, AI32Load16U, constAddr(2))
   529  			p = appendp(p, ASet, regAddr(REG_PC_F))
   530  
   531  			// read return PC_B from Go stack
   532  			p = appendp(p, AGet, regAddr(REG_SP))
   533  			p = appendp(p, AI32Load16U, constAddr(0))
   534  			p = appendp(p, ASet, regAddr(REG_PC_B))
   535  
   536  			// SP += 8
   537  			p = appendp(p, AGet, regAddr(REG_SP))
   538  			p = appendp(p, AI32Const, constAddr(8))
   539  			p = appendp(p, AI32Add)
   540  			p = appendp(p, ASet, regAddr(REG_SP))
   541  
   542  			if ret.As == ARETUNWIND {
   543  				// function needs to unwind the WebAssembly stack, return 1
   544  				p = appendp(p, AI32Const, constAddr(1))
   545  				p = appendp(p, AReturn)
   546  				break
   547  			}
   548  
   549  			// not unwinding the WebAssembly stack, return 0
   550  			p = appendp(p, AI32Const, constAddr(0))
   551  			p = appendp(p, AReturn)
   552  		}
   553  
   554  		p = p.Link
   555  	}
   556  
   557  	p = s.Func.Text
   558  	for p != nil {
   559  		switch p.From.Name {
   560  		case obj.NAME_AUTO:
   561  			p.From.Offset += int64(framesize)
   562  		case obj.NAME_PARAM:
   563  			p.From.Reg = REG_SP
   564  			p.From.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address
   565  		}
   566  
   567  		switch p.To.Name {
   568  		case obj.NAME_AUTO:
   569  			p.To.Offset += int64(framesize)
   570  		case obj.NAME_PARAM:
   571  			p.To.Reg = REG_SP
   572  			p.To.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address
   573  		}
   574  
   575  		switch p.As {
   576  		case AGet:
   577  			if p.From.Type == obj.TYPE_ADDR {
   578  				get := *p
   579  				p.As = obj.ANOP
   580  
   581  				switch get.From.Name {
   582  				case obj.NAME_EXTERN:
   583  					p = appendp(p, AI64Const, get.From)
   584  				case obj.NAME_AUTO, obj.NAME_PARAM:
   585  					p = appendp(p, AGet, regAddr(get.From.Reg))
   586  					if get.From.Reg == REG_SP {
   587  						p = appendp(p, AI64ExtendUI32)
   588  					}
   589  					if get.From.Offset != 0 {
   590  						p = appendp(p, AI64Const, constAddr(get.From.Offset))
   591  						p = appendp(p, AI64Add)
   592  					}
   593  				default:
   594  					panic("bad Get: invalid name")
   595  				}
   596  			}
   597  
   598  		case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
   599  			if p.From.Type == obj.TYPE_MEM {
   600  				as := p.As
   601  				from := p.From
   602  
   603  				p.As = AGet
   604  				p.From = regAddr(from.Reg)
   605  
   606  				if from.Reg != REG_SP {
   607  					p = appendp(p, AI32WrapI64)
   608  				}
   609  
   610  				p = appendp(p, as, constAddr(from.Offset))
   611  			}
   612  
   613  		case AMOVB, AMOVH, AMOVW, AMOVD:
   614  			mov := *p
   615  			p.As = obj.ANOP
   616  
   617  			var loadAs obj.As
   618  			var storeAs obj.As
   619  			switch mov.As {
   620  			case AMOVB:
   621  				loadAs = AI64Load8U
   622  				storeAs = AI64Store8
   623  			case AMOVH:
   624  				loadAs = AI64Load16U
   625  				storeAs = AI64Store16
   626  			case AMOVW:
   627  				loadAs = AI64Load32U
   628  				storeAs = AI64Store32
   629  			case AMOVD:
   630  				loadAs = AI64Load
   631  				storeAs = AI64Store
   632  			}
   633  
   634  			appendValue := func() {
   635  				switch mov.From.Type {
   636  				case obj.TYPE_CONST:
   637  					p = appendp(p, AI64Const, constAddr(mov.From.Offset))
   638  
   639  				case obj.TYPE_ADDR:
   640  					switch mov.From.Name {
   641  					case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO:
   642  						p = appendp(p, AGet, regAddr(mov.From.Reg))
   643  						if mov.From.Reg == REG_SP {
   644  							p = appendp(p, AI64ExtendUI32)
   645  						}
   646  						p = appendp(p, AI64Const, constAddr(mov.From.Offset))
   647  						p = appendp(p, AI64Add)
   648  					case obj.NAME_EXTERN:
   649  						p = appendp(p, AI64Const, mov.From)
   650  					default:
   651  						panic("bad name for MOV")
   652  					}
   653  
   654  				case obj.TYPE_REG:
   655  					p = appendp(p, AGet, mov.From)
   656  					if mov.From.Reg == REG_SP {
   657  						p = appendp(p, AI64ExtendUI32)
   658  					}
   659  
   660  				case obj.TYPE_MEM:
   661  					p = appendp(p, AGet, regAddr(mov.From.Reg))
   662  					if mov.From.Reg != REG_SP {
   663  						p = appendp(p, AI32WrapI64)
   664  					}
   665  					p = appendp(p, loadAs, constAddr(mov.From.Offset))
   666  
   667  				default:
   668  					panic("bad MOV type")
   669  				}
   670  			}
   671  
   672  			switch mov.To.Type {
   673  			case obj.TYPE_REG:
   674  				appendValue()
   675  				if mov.To.Reg == REG_SP {
   676  					p = appendp(p, AI32WrapI64)
   677  				}
   678  				p = appendp(p, ASet, mov.To)
   679  
   680  			case obj.TYPE_MEM:
   681  				switch mov.To.Name {
   682  				case obj.NAME_NONE, obj.NAME_PARAM:
   683  					p = appendp(p, AGet, regAddr(mov.To.Reg))
   684  					if mov.To.Reg != REG_SP {
   685  						p = appendp(p, AI32WrapI64)
   686  					}
   687  				case obj.NAME_EXTERN:
   688  					p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym})
   689  				default:
   690  					panic("bad MOV name")
   691  				}
   692  				appendValue()
   693  				p = appendp(p, storeAs, constAddr(mov.To.Offset))
   694  
   695  			default:
   696  				panic("bad MOV type")
   697  			}
   698  
   699  		case ACallImport:
   700  			p.As = obj.ANOP
   701  			p = appendp(p, AGet, regAddr(REG_SP))
   702  			p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s})
   703  			p.Mark = WasmImport
   704  		}
   705  
   706  		p = p.Link
   707  	}
   708  }
   709  
   710  func constAddr(value int64) obj.Addr {
   711  	return obj.Addr{Type: obj.TYPE_CONST, Offset: value}
   712  }
   713  
   714  func regAddr(reg int16) obj.Addr {
   715  	return obj.Addr{Type: obj.TYPE_REG, Reg: reg}
   716  }
   717  
   718  // countRegisters returns the number of integer and float registers used by s.
   719  // It does so by looking for the maximum I* and R* registers.
   720  func countRegisters(s *obj.LSym) (numI, numF int16) {
   721  	for p := s.Func.Text; p != nil; p = p.Link {
   722  		var reg int16
   723  		switch p.As {
   724  		case AGet:
   725  			reg = p.From.Reg
   726  		case ASet:
   727  			reg = p.To.Reg
   728  		case ATee:
   729  			reg = p.To.Reg
   730  		default:
   731  			continue
   732  		}
   733  		if reg >= REG_R0 && reg <= REG_R15 {
   734  			if n := reg - REG_R0 + 1; numI < n {
   735  				numI = n
   736  			}
   737  		} else if reg >= REG_F0 && reg <= REG_F15 {
   738  			if n := reg - REG_F0 + 1; numF < n {
   739  				numF = n
   740  			}
   741  		}
   742  	}
   743  	return
   744  }
   745  
   746  func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
   747  	w := new(bytes.Buffer)
   748  
   749  	numI, numF := countRegisters(s)
   750  
   751  	// Function starts with declaration of locals: numbers and types.
   752  	switch s.Name {
   753  	// memchr and memcmp don't use the normal Go calling convention and need i32 variables.
   754  	case "memchr":
   755  		writeUleb128(w, 1) // number of sets of locals
   756  		writeUleb128(w, 3) // number of locals
   757  		w.WriteByte(0x7F)  // i32
   758  	case "memcmp":
   759  		writeUleb128(w, 1) // number of sets of locals
   760  		writeUleb128(w, 2) // number of locals
   761  		w.WriteByte(0x7F)  // i32
   762  	default:
   763  		numTypes := 0
   764  		if numI > 0 {
   765  			numTypes++
   766  		}
   767  		if numF > 0 {
   768  			numTypes++
   769  		}
   770  
   771  		writeUleb128(w, uint64(numTypes))
   772  		if numI > 0 {
   773  			writeUleb128(w, uint64(numI)) // number of locals
   774  			w.WriteByte(0x7E)             // i64
   775  		}
   776  		if numF > 0 {
   777  			writeUleb128(w, uint64(numF)) // number of locals
   778  			w.WriteByte(0x7C)             // f64
   779  		}
   780  	}
   781  
   782  	for p := s.Func.Text; p != nil; p = p.Link {
   783  		switch p.As {
   784  		case AGet:
   785  			if p.From.Type != obj.TYPE_REG {
   786  				panic("bad Get: argument is not a register")
   787  			}
   788  			reg := p.From.Reg
   789  			switch {
   790  			case reg >= REG_PC_F && reg <= REG_PAUSE:
   791  				w.WriteByte(0x23) // get_global
   792  				writeUleb128(w, uint64(reg-REG_PC_F))
   793  			case reg >= REG_R0 && reg <= REG_R15:
   794  				w.WriteByte(0x20) // get_local (i64)
   795  				writeUleb128(w, uint64(reg-REG_R0))
   796  			case reg >= REG_F0 && reg <= REG_F15:
   797  				w.WriteByte(0x20) // get_local (f64)
   798  				writeUleb128(w, uint64(numI+(reg-REG_F0)))
   799  			default:
   800  				panic("bad Get: invalid register")
   801  			}
   802  			continue
   803  
   804  		case ASet:
   805  			if p.To.Type != obj.TYPE_REG {
   806  				panic("bad Set: argument is not a register")
   807  			}
   808  			reg := p.To.Reg
   809  			switch {
   810  			case reg >= REG_PC_F && reg <= REG_PAUSE:
   811  				w.WriteByte(0x24) // set_global
   812  				writeUleb128(w, uint64(reg-REG_PC_F))
   813  			case reg >= REG_R0 && reg <= REG_F15:
   814  				if p.Link.As == AGet && p.Link.From.Reg == reg {
   815  					w.WriteByte(0x22) // tee_local
   816  					p = p.Link
   817  				} else {
   818  					w.WriteByte(0x21) // set_local
   819  				}
   820  				if reg <= REG_R15 {
   821  					writeUleb128(w, uint64(reg-REG_R0))
   822  				} else {
   823  					writeUleb128(w, uint64(numI+(reg-REG_F0)))
   824  				}
   825  			default:
   826  				panic("bad Set: invalid register")
   827  			}
   828  			continue
   829  
   830  		case ATee:
   831  			if p.To.Type != obj.TYPE_REG {
   832  				panic("bad Tee: argument is not a register")
   833  			}
   834  			reg := p.To.Reg
   835  			switch {
   836  			case reg >= REG_R0 && reg <= REG_R15:
   837  				w.WriteByte(0x22) // tee_local (i64)
   838  				writeUleb128(w, uint64(reg-REG_R0))
   839  			case reg >= REG_F0 && reg <= REG_F15:
   840  				w.WriteByte(0x22) // tee_local (f64)
   841  				writeUleb128(w, uint64(numI+(reg-REG_F0)))
   842  			default:
   843  				panic("bad Tee: invalid register")
   844  			}
   845  			continue
   846  
   847  		case ANot:
   848  			w.WriteByte(0x45) // i32.eqz
   849  			continue
   850  
   851  		case obj.AUNDEF:
   852  			w.WriteByte(0x00) // unreachable
   853  			continue
   854  
   855  		case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA:
   856  			// ignore
   857  			continue
   858  		}
   859  
   860  		switch {
   861  		case p.As < AUnreachable || p.As > AF64ReinterpretI64:
   862  			panic(fmt.Sprintf("unexpected assembler op: %s", p.As))
   863  		case p.As < AEnd:
   864  			w.WriteByte(byte(p.As - AUnreachable + 0x00))
   865  		case p.As < ADrop:
   866  			w.WriteByte(byte(p.As - AEnd + 0x0B))
   867  		case p.As < AI32Load:
   868  			w.WriteByte(byte(p.As - ADrop + 0x1A))
   869  		default:
   870  			w.WriteByte(byte(p.As - AI32Load + 0x28))
   871  		}
   872  
   873  		switch p.As {
   874  		case ABlock, ALoop, AIf:
   875  			if p.From.Offset != 0 {
   876  				// block type, rarely used, e.g. for code compiled with emscripten
   877  				w.WriteByte(0x80 - byte(p.From.Offset))
   878  				continue
   879  			}
   880  			w.WriteByte(0x40)
   881  
   882  		case ABr, ABrIf:
   883  			if p.To.Type != obj.TYPE_CONST {
   884  				panic("bad Br/BrIf")
   885  			}
   886  			writeUleb128(w, uint64(p.To.Offset))
   887  
   888  		case ABrTable:
   889  			idxs := p.To.Val.([]uint64)
   890  			writeUleb128(w, uint64(len(idxs)-1))
   891  			for _, idx := range idxs {
   892  				writeUleb128(w, idx)
   893  			}
   894  
   895  		case ACall:
   896  			switch p.To.Type {
   897  			case obj.TYPE_CONST:
   898  				writeUleb128(w, uint64(p.To.Offset))
   899  
   900  			case obj.TYPE_MEM:
   901  				if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC {
   902  					fmt.Println(p.To)
   903  					panic("bad name for Call")
   904  				}
   905  				r := obj.Addrel(s)
   906  				r.Off = int32(w.Len())
   907  				r.Type = objabi.R_CALL
   908  				if p.Mark&WasmImport != 0 {
   909  					r.Type = objabi.R_WASMIMPORT
   910  				}
   911  				r.Sym = p.To.Sym
   912  
   913  			default:
   914  				panic("bad type for Call")
   915  			}
   916  
   917  		case ACallIndirect:
   918  			writeUleb128(w, uint64(p.To.Offset))
   919  			w.WriteByte(0x00) // reserved value
   920  
   921  		case AI32Const, AI64Const:
   922  			if p.From.Name == obj.NAME_EXTERN {
   923  				r := obj.Addrel(s)
   924  				r.Off = int32(w.Len())
   925  				r.Type = objabi.R_ADDR
   926  				r.Sym = p.From.Sym
   927  				r.Add = p.From.Offset
   928  				break
   929  			}
   930  			writeSleb128(w, p.From.Offset)
   931  
   932  		case AF64Const:
   933  			b := make([]byte, 8)
   934  			binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64)))
   935  			w.Write(b)
   936  
   937  		case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
   938  			if p.From.Offset < 0 {
   939  				panic("negative offset for *Load")
   940  			}
   941  			if p.From.Type != obj.TYPE_CONST {
   942  				panic("bad type for *Load")
   943  			}
   944  			if p.From.Offset > math.MaxUint32 {
   945  				ctxt.Diag("bad offset in %v", p)
   946  			}
   947  			writeUleb128(w, align(p.As))
   948  			writeUleb128(w, uint64(p.From.Offset))
   949  
   950  		case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32:
   951  			if p.To.Offset < 0 {
   952  				panic("negative offset")
   953  			}
   954  			if p.From.Offset > math.MaxUint32 {
   955  				ctxt.Diag("bad offset in %v", p)
   956  			}
   957  			writeUleb128(w, align(p.As))
   958  			writeUleb128(w, uint64(p.To.Offset))
   959  
   960  		case ACurrentMemory, AGrowMemory:
   961  			w.WriteByte(0x00)
   962  
   963  		}
   964  	}
   965  
   966  	w.WriteByte(0x0b) // end
   967  
   968  	s.P = w.Bytes()
   969  }
   970  
   971  func align(as obj.As) uint64 {
   972  	switch as {
   973  	case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8:
   974  		return 0
   975  	case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16:
   976  		return 1
   977  	case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32:
   978  		return 2
   979  	case AI64Load, AF64Load, AI64Store, AF64Store:
   980  		return 3
   981  	default:
   982  		panic("align: bad op")
   983  	}
   984  }
   985  
   986  func writeUleb128(w io.ByteWriter, v uint64) {
   987  	more := true
   988  	for more {
   989  		c := uint8(v & 0x7f)
   990  		v >>= 7
   991  		more = v != 0
   992  		if more {
   993  			c |= 0x80
   994  		}
   995  		w.WriteByte(c)
   996  	}
   997  }
   998  
   999  func writeSleb128(w io.ByteWriter, v int64) {
  1000  	more := true
  1001  	for more {
  1002  		c := uint8(v & 0x7f)
  1003  		s := uint8(v & 0x40)
  1004  		v >>= 7
  1005  		more = !((v == 0 && s == 0) || (v == -1 && s != 0))
  1006  		if more {
  1007  			c |= 0x80
  1008  		}
  1009  		w.WriteByte(c)
  1010  	}
  1011  }