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