gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

wasmobj.go (29868B)


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