...

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

     1	// Copyright 2018 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	package wasm
     6	
     7	import (
     8		"bytes"
     9		"cmd/internal/obj"
    10		"cmd/internal/objabi"
    11		"cmd/internal/sys"
    12		"encoding/binary"
    13		"fmt"
    14		"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		"PC_B": REG_PC_B,
    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			//   I64ExtendI32U
   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, AI64ExtendI32U)
   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		// record the branches targeting the entry loop and the unwind exit,
   345		// their targets with be filled in later
   346		var entryPointLoopBranches []*obj.Prog
   347		var unwindExitBranches []*obj.Prog
   348		currentDepth := 0
   349		for p := s.Func.Text; p != nil; p = p.Link {
   350			switch p.As {
   351			case ABlock, ALoop, AIf:
   352				currentDepth++
   353			case AEnd:
   354				currentDepth--
   355			}
   356	
   357			switch p.As {
   358			case obj.AJMP:
   359				jmp := *p
   360				p.As = obj.ANOP
   361	
   362				if jmp.To.Type == obj.TYPE_BRANCH {
   363					// jump to basic block
   364					p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc))
   365					p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B
   366					p = appendp(p, ABr)                     // jump to beginning of entryPointLoop
   367					entryPointLoopBranches = append(entryPointLoopBranches, p)
   368					break
   369				}
   370	
   371				// low-level WebAssembly call to function
   372				switch jmp.To.Type {
   373				case obj.TYPE_MEM:
   374					if !notUsePC_B[jmp.To.Sym.Name] {
   375						// Set PC_B parameter to function entry.
   376						p = appendp(p, AI32Const, constAddr(0))
   377					}
   378					p = appendp(p, ACall, jmp.To)
   379	
   380				case obj.TYPE_NONE:
   381					// (target PC is on stack)
   382					p = appendp(p, AI32WrapI64)
   383					p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
   384					p = appendp(p, AI32ShrU)
   385	
   386					// Set PC_B parameter to function entry.
   387					// We need to push this before pushing the target PC_F,
   388					// so temporarily pop PC_F, using our REG_PC_B as a
   389					// scratch register, and push it back after pushing 0.
   390					p = appendp(p, ASet, regAddr(REG_PC_B))
   391					p = appendp(p, AI32Const, constAddr(0))
   392					p = appendp(p, AGet, regAddr(REG_PC_B))
   393	
   394					p = appendp(p, ACallIndirect)
   395	
   396				default:
   397					panic("bad target for JMP")
   398				}
   399	
   400				p = appendp(p, AReturn)
   401	
   402			case obj.ACALL, ACALLNORESUME:
   403				call := *p
   404				p.As = obj.ANOP
   405	
   406				pcAfterCall := call.Link.Pc
   407				if call.To.Sym == sigpanic {
   408					pcAfterCall-- // sigpanic expects to be called without advancing the pc
   409				}
   410	
   411				// jmpdefer manipulates the return address on the stack so deferreturn gets called repeatedly.
   412				// Model this in WebAssembly with a loop.
   413				if call.To.Sym == deferreturn {
   414					p = appendp(p, ALoop)
   415				}
   416	
   417				// SP -= 8
   418				p = appendp(p, AGet, regAddr(REG_SP))
   419				p = appendp(p, AI32Const, constAddr(8))
   420				p = appendp(p, AI32Sub)
   421				p = appendp(p, ASet, regAddr(REG_SP))
   422	
   423				// write return address to Go stack
   424				p = appendp(p, AGet, regAddr(REG_SP))
   425				p = appendp(p, AI64Const, obj.Addr{
   426					Type:   obj.TYPE_ADDR,
   427					Name:   obj.NAME_EXTERN,
   428					Sym:    s,           // PC_F
   429					Offset: pcAfterCall, // PC_B
   430				})
   431				p = appendp(p, AI64Store, constAddr(0))
   432	
   433				// low-level WebAssembly call to function
   434				switch call.To.Type {
   435				case obj.TYPE_MEM:
   436					if !notUsePC_B[call.To.Sym.Name] {
   437						// Set PC_B parameter to function entry.
   438						p = appendp(p, AI32Const, constAddr(0))
   439					}
   440					p = appendp(p, ACall, call.To)
   441	
   442				case obj.TYPE_NONE:
   443					// (target PC is on stack)
   444					p = appendp(p, AI32WrapI64)
   445					p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
   446					p = appendp(p, AI32ShrU)
   447	
   448					// Set PC_B parameter to function entry.
   449					// We need to push this before pushing the target PC_F,
   450					// so temporarily pop PC_F, using our PC_B as a
   451					// scratch register, and push it back after pushing 0.
   452					p = appendp(p, ASet, regAddr(REG_PC_B))
   453					p = appendp(p, AI32Const, constAddr(0))
   454					p = appendp(p, AGet, regAddr(REG_PC_B))
   455	
   456					p = appendp(p, ACallIndirect)
   457	
   458				default:
   459					panic("bad target for CALL")
   460				}
   461	
   462				// gcWriteBarrier has no return value, it never unwinds the stack
   463				if call.To.Sym == gcWriteBarrier {
   464					break
   465				}
   466	
   467				// jmpdefer removes the frame of deferreturn from the Go stack.
   468				// However, its WebAssembly function still returns normally,
   469				// so we need to return from deferreturn without removing its
   470				// stack frame (no RET), because the frame is already gone.
   471				if call.To.Sym == jmpdefer {
   472					p = appendp(p, AReturn)
   473					break
   474				}
   475	
   476				// return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack
   477				if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes
   478					// trying to unwind WebAssembly stack but call has no resume point, terminate with error
   479					p = appendp(p, AIf)
   480					p = appendp(p, obj.AUNDEF)
   481					p = appendp(p, AEnd)
   482				} else {
   483					// unwinding WebAssembly stack to switch goroutine, return 1
   484					p = appendp(p, ABrIf)
   485					unwindExitBranches = append(unwindExitBranches, p)
   486				}
   487	
   488				// jump to before the call if jmpdefer has reset the return address to the call's PC
   489				if call.To.Sym == deferreturn {
   490					// get PC_B from -8(SP)
   491					p = appendp(p, AGet, regAddr(REG_SP))
   492					p = appendp(p, AI32Const, constAddr(8))
   493					p = appendp(p, AI32Sub)
   494					p = appendp(p, AI32Load16U, constAddr(0))
   495					p = appendp(p, ATee, regAddr(REG_PC_B))
   496	
   497					p = appendp(p, AI32Const, constAddr(call.Pc))
   498					p = appendp(p, AI32Eq)
   499					p = appendp(p, ABrIf, constAddr(0))
   500					p = appendp(p, AEnd) // end of Loop
   501				}
   502	
   503			case obj.ARET, ARETUNWIND:
   504				ret := *p
   505				p.As = obj.ANOP
   506	
   507				if framesize > 0 {
   508					// SP += framesize
   509					p = appendp(p, AGet, regAddr(REG_SP))
   510					p = appendp(p, AI32Const, constAddr(framesize))
   511					p = appendp(p, AI32Add)
   512					p = appendp(p, ASet, regAddr(REG_SP))
   513					// TODO(neelance): This should theoretically set Spadj, but it only works without.
   514					// p.Spadj = int32(-framesize)
   515				}
   516	
   517				if ret.To.Type == obj.TYPE_MEM {
   518					// Set PC_B parameter to function entry.
   519					p = appendp(p, AI32Const, constAddr(0))
   520	
   521					// low-level WebAssembly call to function
   522					p = appendp(p, ACall, ret.To)
   523					p = appendp(p, AReturn)
   524					break
   525				}
   526	
   527				// SP += 8
   528				p = appendp(p, AGet, regAddr(REG_SP))
   529				p = appendp(p, AI32Const, constAddr(8))
   530				p = appendp(p, AI32Add)
   531				p = appendp(p, ASet, regAddr(REG_SP))
   532	
   533				if ret.As == ARETUNWIND {
   534					// function needs to unwind the WebAssembly stack, return 1
   535					p = appendp(p, AI32Const, constAddr(1))
   536					p = appendp(p, AReturn)
   537					break
   538				}
   539	
   540				// not unwinding the WebAssembly stack, return 0
   541				p = appendp(p, AI32Const, constAddr(0))
   542				p = appendp(p, AReturn)
   543			}
   544		}
   545	
   546		for p := s.Func.Text; p != nil; p = p.Link {
   547			switch p.From.Name {
   548			case obj.NAME_AUTO:
   549				p.From.Offset += int64(framesize)
   550			case obj.NAME_PARAM:
   551				p.From.Reg = REG_SP
   552				p.From.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address
   553			}
   554	
   555			switch p.To.Name {
   556			case obj.NAME_AUTO:
   557				p.To.Offset += int64(framesize)
   558			case obj.NAME_PARAM:
   559				p.To.Reg = REG_SP
   560				p.To.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address
   561			}
   562	
   563			switch p.As {
   564			case AGet:
   565				if p.From.Type == obj.TYPE_ADDR {
   566					get := *p
   567					p.As = obj.ANOP
   568	
   569					switch get.From.Name {
   570					case obj.NAME_EXTERN:
   571						p = appendp(p, AI64Const, get.From)
   572					case obj.NAME_AUTO, obj.NAME_PARAM:
   573						p = appendp(p, AGet, regAddr(get.From.Reg))
   574						if get.From.Reg == REG_SP {
   575							p = appendp(p, AI64ExtendI32U)
   576						}
   577						if get.From.Offset != 0 {
   578							p = appendp(p, AI64Const, constAddr(get.From.Offset))
   579							p = appendp(p, AI64Add)
   580						}
   581					default:
   582						panic("bad Get: invalid name")
   583					}
   584				}
   585	
   586			case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
   587				if p.From.Type == obj.TYPE_MEM {
   588					as := p.As
   589					from := p.From
   590	
   591					p.As = AGet
   592					p.From = regAddr(from.Reg)
   593	
   594					if from.Reg != REG_SP {
   595						p = appendp(p, AI32WrapI64)
   596					}
   597	
   598					p = appendp(p, as, constAddr(from.Offset))
   599				}
   600	
   601			case AMOVB, AMOVH, AMOVW, AMOVD:
   602				mov := *p
   603				p.As = obj.ANOP
   604	
   605				var loadAs obj.As
   606				var storeAs obj.As
   607				switch mov.As {
   608				case AMOVB:
   609					loadAs = AI64Load8U
   610					storeAs = AI64Store8
   611				case AMOVH:
   612					loadAs = AI64Load16U
   613					storeAs = AI64Store16
   614				case AMOVW:
   615					loadAs = AI64Load32U
   616					storeAs = AI64Store32
   617				case AMOVD:
   618					loadAs = AI64Load
   619					storeAs = AI64Store
   620				}
   621	
   622				appendValue := func() {
   623					switch mov.From.Type {
   624					case obj.TYPE_CONST:
   625						p = appendp(p, AI64Const, constAddr(mov.From.Offset))
   626	
   627					case obj.TYPE_ADDR:
   628						switch mov.From.Name {
   629						case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO:
   630							p = appendp(p, AGet, regAddr(mov.From.Reg))
   631							if mov.From.Reg == REG_SP {
   632								p = appendp(p, AI64ExtendI32U)
   633							}
   634							p = appendp(p, AI64Const, constAddr(mov.From.Offset))
   635							p = appendp(p, AI64Add)
   636						case obj.NAME_EXTERN:
   637							p = appendp(p, AI64Const, mov.From)
   638						default:
   639							panic("bad name for MOV")
   640						}
   641	
   642					case obj.TYPE_REG:
   643						p = appendp(p, AGet, mov.From)
   644						if mov.From.Reg == REG_SP {
   645							p = appendp(p, AI64ExtendI32U)
   646						}
   647	
   648					case obj.TYPE_MEM:
   649						p = appendp(p, AGet, regAddr(mov.From.Reg))
   650						if mov.From.Reg != REG_SP {
   651							p = appendp(p, AI32WrapI64)
   652						}
   653						p = appendp(p, loadAs, constAddr(mov.From.Offset))
   654	
   655					default:
   656						panic("bad MOV type")
   657					}
   658				}
   659	
   660				switch mov.To.Type {
   661				case obj.TYPE_REG:
   662					appendValue()
   663					if mov.To.Reg == REG_SP {
   664						p = appendp(p, AI32WrapI64)
   665					}
   666					p = appendp(p, ASet, mov.To)
   667	
   668				case obj.TYPE_MEM:
   669					switch mov.To.Name {
   670					case obj.NAME_NONE, obj.NAME_PARAM:
   671						p = appendp(p, AGet, regAddr(mov.To.Reg))
   672						if mov.To.Reg != REG_SP {
   673							p = appendp(p, AI32WrapI64)
   674						}
   675					case obj.NAME_EXTERN:
   676						p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym})
   677					default:
   678						panic("bad MOV name")
   679					}
   680					appendValue()
   681					p = appendp(p, storeAs, constAddr(mov.To.Offset))
   682	
   683				default:
   684					panic("bad MOV type")
   685				}
   686	
   687			case ACallImport:
   688				p.As = obj.ANOP
   689				p = appendp(p, AGet, regAddr(REG_SP))
   690				p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s})
   691				p.Mark = WasmImport
   692			}
   693		}
   694	
   695		{
   696			p := s.Func.Text
   697			if len(unwindExitBranches) > 0 {
   698				p = appendp(p, ABlock) // unwindExit, used to return 1 when unwinding the stack
   699				for _, b := range unwindExitBranches {
   700					b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
   701				}
   702			}
   703			if len(entryPointLoopBranches) > 0 {
   704				p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks
   705				for _, b := range entryPointLoopBranches {
   706					b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
   707				}
   708			}
   709			if numResumePoints > 0 {
   710				// Add Block instructions for resume points and BrTable to jump to selected resume point.
   711				for i := 0; i < numResumePoints+1; i++ {
   712					p = appendp(p, ABlock)
   713				}
   714				p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B
   715				p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs})
   716				p = appendp(p, AEnd) // end of Block
   717			}
   718			for p.Link != nil {
   719				p = p.Link // function instructions
   720			}
   721			if len(entryPointLoopBranches) > 0 {
   722				p = appendp(p, AEnd) // end of entryPointLoop
   723			}
   724			p = appendp(p, obj.AUNDEF)
   725			if len(unwindExitBranches) > 0 {
   726				p = appendp(p, AEnd) // end of unwindExit
   727				p = appendp(p, AI32Const, constAddr(1))
   728			}
   729		}
   730	
   731		currentDepth = 0
   732		blockDepths := make(map[*obj.Prog]int)
   733		for p := s.Func.Text; p != nil; p = p.Link {
   734			switch p.As {
   735			case ABlock, ALoop, AIf:
   736				currentDepth++
   737				blockDepths[p] = currentDepth
   738			case AEnd:
   739				currentDepth--
   740			}
   741	
   742			switch p.As {
   743			case ABr, ABrIf:
   744				if p.To.Type == obj.TYPE_BRANCH {
   745					blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)]
   746					if !ok {
   747						panic("label not at block")
   748					}
   749					p.To = constAddr(int64(currentDepth - blockDepth))
   750				}
   751			}
   752		}
   753	}
   754	
   755	func constAddr(value int64) obj.Addr {
   756		return obj.Addr{Type: obj.TYPE_CONST, Offset: value}
   757	}
   758	
   759	func regAddr(reg int16) obj.Addr {
   760		return obj.Addr{Type: obj.TYPE_REG, Reg: reg}
   761	}
   762	
   763	// countRegisters returns the number of integer and float registers used by s.
   764	// It does so by looking for the maximum I* and R* registers.
   765	func countRegisters(s *obj.LSym) (numI, numF int16) {
   766		for p := s.Func.Text; p != nil; p = p.Link {
   767			var reg int16
   768			switch p.As {
   769			case AGet:
   770				reg = p.From.Reg
   771			case ASet:
   772				reg = p.To.Reg
   773			case ATee:
   774				reg = p.To.Reg
   775			default:
   776				continue
   777			}
   778			if reg >= REG_R0 && reg <= REG_R15 {
   779				if n := reg - REG_R0 + 1; numI < n {
   780					numI = n
   781				}
   782			} else if reg >= REG_F0 && reg <= REG_F15 {
   783				if n := reg - REG_F0 + 1; numF < n {
   784					numF = n
   785				}
   786			}
   787		}
   788		return
   789	}
   790	
   791	// Most of the Go functions has a single parameter (PC_B) in
   792	// Wasm ABI. This is a list of exceptions.
   793	var notUsePC_B = map[string]bool{
   794		"_rt0_wasm_js":           true,
   795		"wasm_export_run":        true,
   796		"wasm_export_resume":     true,
   797		"wasm_export_getsp":      true,
   798		"wasm_pc_f_loop":         true,
   799		"runtime.wasmMove":       true,
   800		"runtime.wasmZero":       true,
   801		"runtime.wasmDiv":        true,
   802		"runtime.wasmTruncS":     true,
   803		"runtime.wasmTruncU":     true,
   804		"runtime.gcWriteBarrier": true,
   805		"cmpbody":                true,
   806		"memeqbody":              true,
   807		"memcmp":                 true,
   808		"memchr":                 true,
   809	}
   810	
   811	func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
   812		w := new(bytes.Buffer)
   813	
   814		hasLocalSP := false
   815		hasPC_B := false
   816		var r0, f0 int16
   817	
   818		// Function starts with declaration of locals: numbers and types.
   819		// Some functions use a special calling convention.
   820		switch s.Name {
   821		case "_rt0_wasm_js", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp", "wasm_pc_f_loop",
   822			"runtime.wasmMove", "runtime.wasmZero", "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody":
   823			writeUleb128(w, 0) // number of sets of locals
   824		case "memchr", "memcmp":
   825			writeUleb128(w, 1) // number of sets of locals
   826			writeUleb128(w, 2) // number of locals
   827			w.WriteByte(0x7F)  // i32
   828		case "cmpbody":
   829			writeUleb128(w, 1) // number of sets of locals
   830			writeUleb128(w, 2) // number of locals
   831			w.WriteByte(0x7E)  // i64
   832		case "runtime.gcWriteBarrier":
   833			writeUleb128(w, 1) // number of sets of locals
   834			writeUleb128(w, 4) // number of locals
   835			w.WriteByte(0x7E)  // i64
   836		default:
   837			// Normal calling convention: No WebAssembly parameters. First local variable is local SP cache.
   838			hasLocalSP = true
   839			hasPC_B = true
   840			numI, numF := countRegisters(s)
   841			r0 = 2
   842			f0 = 2 + numI
   843	
   844			numTypes := 1
   845			if numI > 0 {
   846				numTypes++
   847			}
   848			if numF > 0 {
   849				numTypes++
   850			}
   851	
   852			writeUleb128(w, uint64(numTypes))
   853			writeUleb128(w, 1) // number of locals (SP)
   854			w.WriteByte(0x7F)  // i32
   855			if numI > 0 {
   856				writeUleb128(w, uint64(numI)) // number of locals
   857				w.WriteByte(0x7E)             // i64
   858			}
   859			if numF > 0 {
   860				writeUleb128(w, uint64(numF)) // number of locals
   861				w.WriteByte(0x7C)             // f64
   862			}
   863		}
   864	
   865		if hasLocalSP {
   866			// Copy SP from its global variable into a local variable. Accessing a local variable is more efficient.
   867			updateLocalSP(w)
   868		}
   869	
   870		for p := s.Func.Text; p != nil; p = p.Link {
   871			switch p.As {
   872			case AGet:
   873				if p.From.Type != obj.TYPE_REG {
   874					panic("bad Get: argument is not a register")
   875				}
   876				reg := p.From.Reg
   877				switch {
   878				case reg == REG_SP && hasLocalSP:
   879					w.WriteByte(0x20)  // local.get
   880					writeUleb128(w, 1) // local SP
   881				case reg >= REG_SP && reg <= REG_PAUSE:
   882					w.WriteByte(0x23) // global.get
   883					writeUleb128(w, uint64(reg-REG_SP))
   884				case reg == REG_PC_B:
   885					if !hasPC_B {
   886						panic(fmt.Sprintf("PC_B is not used in %s", s.Name))
   887					}
   888					w.WriteByte(0x20)  // local.get (i32)
   889					writeUleb128(w, 0) // local PC_B
   890				case reg >= REG_R0 && reg <= REG_R15:
   891					w.WriteByte(0x20) // local.get (i64)
   892					writeUleb128(w, uint64(r0+(reg-REG_R0)))
   893				case reg >= REG_F0 && reg <= REG_F15:
   894					w.WriteByte(0x20) // local.get (f64)
   895					writeUleb128(w, uint64(f0+(reg-REG_F0)))
   896				default:
   897					panic("bad Get: invalid register")
   898				}
   899				continue
   900	
   901			case ASet:
   902				if p.To.Type != obj.TYPE_REG {
   903					panic("bad Set: argument is not a register")
   904				}
   905				reg := p.To.Reg
   906				switch {
   907				case reg >= REG_SP && reg <= REG_PAUSE:
   908					if reg == REG_SP && hasLocalSP {
   909						w.WriteByte(0x22)  // local.tee
   910						writeUleb128(w, 1) // local SP
   911					}
   912					w.WriteByte(0x24) // global.set
   913					writeUleb128(w, uint64(reg-REG_SP))
   914				case reg >= REG_R0 && reg <= REG_PC_B:
   915					if p.Link.As == AGet && p.Link.From.Reg == reg {
   916						w.WriteByte(0x22) // local.tee
   917						p = p.Link
   918					} else {
   919						w.WriteByte(0x21) // local.set
   920					}
   921					if reg == REG_PC_B {
   922						if !hasPC_B {
   923							panic(fmt.Sprintf("PC_B is not used in %s", s.Name))
   924						}
   925						writeUleb128(w, 0) // local PC_B
   926					} else if reg <= REG_R15 {
   927						writeUleb128(w, uint64(r0+(reg-REG_R0)))
   928					} else {
   929						writeUleb128(w, uint64(f0+(reg-REG_F0)))
   930					}
   931				default:
   932					panic("bad Set: invalid register")
   933				}
   934				continue
   935	
   936			case ATee:
   937				if p.To.Type != obj.TYPE_REG {
   938					panic("bad Tee: argument is not a register")
   939				}
   940				reg := p.To.Reg
   941				switch {
   942				case reg == REG_PC_B:
   943					if !hasPC_B {
   944						panic(fmt.Sprintf("PC_B is not used in %s", s.Name))
   945					}
   946					w.WriteByte(0x22)  // local.tee (i32)
   947					writeUleb128(w, 0) // local PC_B
   948				case reg >= REG_R0 && reg <= REG_R15:
   949					w.WriteByte(0x22) // local.tee (i64)
   950					writeUleb128(w, uint64(r0+(reg-REG_R0)))
   951				case reg >= REG_F0 && reg <= REG_F15:
   952					w.WriteByte(0x22) // local.tee (f64)
   953					writeUleb128(w, uint64(f0+(reg-REG_F0)))
   954				default:
   955					panic("bad Tee: invalid register")
   956				}
   957				continue
   958	
   959			case ANot:
   960				w.WriteByte(0x45) // i32.eqz
   961				continue
   962	
   963			case obj.AUNDEF:
   964				w.WriteByte(0x00) // unreachable
   965				continue
   966	
   967			case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA:
   968				// ignore
   969				continue
   970			}
   971	
   972			switch {
   973			case p.As < AUnreachable:
   974				panic(fmt.Sprintf("unexpected assembler op: %s", p.As))
   975			case p.As < AEnd:
   976				w.WriteByte(byte(p.As - AUnreachable + 0x00))
   977			case p.As < ADrop:
   978				w.WriteByte(byte(p.As - AEnd + 0x0B))
   979			case p.As < AI32Load:
   980				w.WriteByte(byte(p.As - ADrop + 0x1A))
   981			case p.As < AI32TruncSatF32S:
   982				w.WriteByte(byte(p.As - AI32Load + 0x28))
   983			case p.As < ALast:
   984				w.WriteByte(0xFC)
   985				w.WriteByte(byte(p.As - AI32TruncSatF32S + 0x00))
   986			default:
   987				panic(fmt.Sprintf("unexpected assembler op: %s", p.As))
   988			}
   989	
   990			switch p.As {
   991			case ABlock, ALoop, AIf:
   992				if p.From.Offset != 0 {
   993					// block type, rarely used, e.g. for code compiled with emscripten
   994					w.WriteByte(0x80 - byte(p.From.Offset))
   995					continue
   996				}
   997				w.WriteByte(0x40)
   998	
   999			case ABr, ABrIf:
  1000				if p.To.Type != obj.TYPE_CONST {
  1001					panic("bad Br/BrIf")
  1002				}
  1003				writeUleb128(w, uint64(p.To.Offset))
  1004	
  1005			case ABrTable:
  1006				idxs := p.To.Val.([]uint64)
  1007				writeUleb128(w, uint64(len(idxs)-1))
  1008				for _, idx := range idxs {
  1009					writeUleb128(w, idx)
  1010				}
  1011	
  1012			case ACall:
  1013				switch p.To.Type {
  1014				case obj.TYPE_CONST:
  1015					writeUleb128(w, uint64(p.To.Offset))
  1016	
  1017				case obj.TYPE_MEM:
  1018					if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC {
  1019						fmt.Println(p.To)
  1020						panic("bad name for Call")
  1021					}
  1022					r := obj.Addrel(s)
  1023					r.Off = int32(w.Len())
  1024					r.Type = objabi.R_CALL
  1025					if p.Mark&WasmImport != 0 {
  1026						r.Type = objabi.R_WASMIMPORT
  1027					}
  1028					r.Sym = p.To.Sym
  1029					if hasLocalSP {
  1030						// The stack may have moved, which changes SP. Update the local SP variable.
  1031						updateLocalSP(w)
  1032					}
  1033	
  1034				default:
  1035					panic("bad type for Call")
  1036				}
  1037	
  1038			case ACallIndirect:
  1039				writeUleb128(w, uint64(p.To.Offset))
  1040				w.WriteByte(0x00) // reserved value
  1041				if hasLocalSP {
  1042					// The stack may have moved, which changes SP. Update the local SP variable.
  1043					updateLocalSP(w)
  1044				}
  1045	
  1046			case AI32Const, AI64Const:
  1047				if p.From.Name == obj.NAME_EXTERN {
  1048					r := obj.Addrel(s)
  1049					r.Off = int32(w.Len())
  1050					r.Type = objabi.R_ADDR
  1051					r.Sym = p.From.Sym
  1052					r.Add = p.From.Offset
  1053					break
  1054				}
  1055				writeSleb128(w, p.From.Offset)
  1056	
  1057			case AF64Const:
  1058				b := make([]byte, 8)
  1059				binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64)))
  1060				w.Write(b)
  1061	
  1062			case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
  1063				if p.From.Offset < 0 {
  1064					panic("negative offset for *Load")
  1065				}
  1066				if p.From.Type != obj.TYPE_CONST {
  1067					panic("bad type for *Load")
  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.From.Offset))
  1074	
  1075			case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32:
  1076				if p.To.Offset < 0 {
  1077					panic("negative offset")
  1078				}
  1079				if p.From.Offset > math.MaxUint32 {
  1080					ctxt.Diag("bad offset in %v", p)
  1081				}
  1082				writeUleb128(w, align(p.As))
  1083				writeUleb128(w, uint64(p.To.Offset))
  1084	
  1085			case ACurrentMemory, AGrowMemory:
  1086				w.WriteByte(0x00)
  1087	
  1088			}
  1089		}
  1090	
  1091		w.WriteByte(0x0b) // end
  1092	
  1093		s.P = w.Bytes()
  1094	}
  1095	
  1096	func updateLocalSP(w *bytes.Buffer) {
  1097		w.WriteByte(0x23)  // global.get
  1098		writeUleb128(w, 0) // global SP
  1099		w.WriteByte(0x21)  // local.set
  1100		writeUleb128(w, 1) // local SP
  1101	}
  1102	
  1103	func align(as obj.As) uint64 {
  1104		switch as {
  1105		case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8:
  1106			return 0
  1107		case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16:
  1108			return 1
  1109		case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32:
  1110			return 2
  1111		case AI64Load, AF64Load, AI64Store, AF64Store:
  1112			return 3
  1113		default:
  1114			panic("align: bad op")
  1115		}
  1116	}
  1117	
  1118	func writeUleb128(w io.ByteWriter, v uint64) {
  1119		more := true
  1120		for more {
  1121			c := uint8(v & 0x7f)
  1122			v >>= 7
  1123			more = v != 0
  1124			if more {
  1125				c |= 0x80
  1126			}
  1127			w.WriteByte(c)
  1128		}
  1129	}
  1130	
  1131	func writeSleb128(w io.ByteWriter, v int64) {
  1132		more := true
  1133		for more {
  1134			c := uint8(v & 0x7f)
  1135			s := uint8(v & 0x40)
  1136			v >>= 7
  1137			more = !((v == 0 && s == 0) || (v == -1 && s != 0))
  1138			if more {
  1139				c |= 0x80
  1140			}
  1141			w.WriteByte(c)
  1142		}
  1143	}
  1144	

View as plain text