Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/internal/obj/wasm

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

View as plain text