123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185 |
- // Copyright 2018 The Go Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package wasm
- import (
- "bytes"
- "github.com/twitchyliquid64/golang-asm/obj"
- "github.com/twitchyliquid64/golang-asm/objabi"
- "github.com/twitchyliquid64/golang-asm/sys"
- "encoding/binary"
- "fmt"
- "io"
- "math"
- )
- var Register = map[string]int16{
- "SP": REG_SP,
- "CTXT": REG_CTXT,
- "g": REG_g,
- "RET0": REG_RET0,
- "RET1": REG_RET1,
- "RET2": REG_RET2,
- "RET3": REG_RET3,
- "PAUSE": REG_PAUSE,
- "R0": REG_R0,
- "R1": REG_R1,
- "R2": REG_R2,
- "R3": REG_R3,
- "R4": REG_R4,
- "R5": REG_R5,
- "R6": REG_R6,
- "R7": REG_R7,
- "R8": REG_R8,
- "R9": REG_R9,
- "R10": REG_R10,
- "R11": REG_R11,
- "R12": REG_R12,
- "R13": REG_R13,
- "R14": REG_R14,
- "R15": REG_R15,
- "F0": REG_F0,
- "F1": REG_F1,
- "F2": REG_F2,
- "F3": REG_F3,
- "F4": REG_F4,
- "F5": REG_F5,
- "F6": REG_F6,
- "F7": REG_F7,
- "F8": REG_F8,
- "F9": REG_F9,
- "F10": REG_F10,
- "F11": REG_F11,
- "F12": REG_F12,
- "F13": REG_F13,
- "F14": REG_F14,
- "F15": REG_F15,
- "F16": REG_F16,
- "F17": REG_F17,
- "F18": REG_F18,
- "F19": REG_F19,
- "F20": REG_F20,
- "F21": REG_F21,
- "F22": REG_F22,
- "F23": REG_F23,
- "F24": REG_F24,
- "F25": REG_F25,
- "F26": REG_F26,
- "F27": REG_F27,
- "F28": REG_F28,
- "F29": REG_F29,
- "F30": REG_F30,
- "F31": REG_F31,
- "PC_B": REG_PC_B,
- }
- var registerNames []string
- func init() {
- obj.RegisterRegister(MINREG, MAXREG, rconv)
- obj.RegisterOpcode(obj.ABaseWasm, Anames)
- registerNames = make([]string, MAXREG-MINREG)
- for name, reg := range Register {
- registerNames[reg-MINREG] = name
- }
- }
- func rconv(r int) string {
- return registerNames[r-MINREG]
- }
- var unaryDst = map[obj.As]bool{
- ASet: true,
- ATee: true,
- ACall: true,
- ACallIndirect: true,
- ACallImport: true,
- ABr: true,
- ABrIf: true,
- ABrTable: true,
- AI32Store: true,
- AI64Store: true,
- AF32Store: true,
- AF64Store: true,
- AI32Store8: true,
- AI32Store16: true,
- AI64Store8: true,
- AI64Store16: true,
- AI64Store32: true,
- ACALLNORESUME: true,
- }
- var Linkwasm = obj.LinkArch{
- Arch: sys.ArchWasm,
- Init: instinit,
- Preprocess: preprocess,
- Assemble: assemble,
- UnaryDst: unaryDst,
- }
- var (
- morestack *obj.LSym
- morestackNoCtxt *obj.LSym
- gcWriteBarrier *obj.LSym
- sigpanic *obj.LSym
- sigpanic0 *obj.LSym
- deferreturn *obj.LSym
- jmpdefer *obj.LSym
- )
- const (
- /* mark flags */
- WasmImport = 1 << 0
- )
- func instinit(ctxt *obj.Link) {
- morestack = ctxt.Lookup("runtime.morestack")
- morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
- gcWriteBarrier = ctxt.Lookup("runtime.gcWriteBarrier")
- sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
- sigpanic0 = ctxt.LookupABI("runtime.sigpanic", 0) // sigpanic called from assembly, which has ABI0
- deferreturn = ctxt.LookupABI("runtime.deferreturn", obj.ABIInternal)
- // jmpdefer is defined in assembly as ABI0, but what we're
- // looking for is the *call* to jmpdefer from the Go function
- // deferreturn, so we're looking for the ABIInternal version
- // of jmpdefer that's called by Go.
- jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABIInternal)
- }
- func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
- appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog {
- if p.As != obj.ANOP {
- p2 := obj.Appendp(p, newprog)
- p2.Pc = p.Pc
- p = p2
- }
- p.As = as
- switch len(args) {
- case 0:
- p.From = obj.Addr{}
- p.To = obj.Addr{}
- case 1:
- if unaryDst[as] {
- p.From = obj.Addr{}
- p.To = args[0]
- } else {
- p.From = args[0]
- p.To = obj.Addr{}
- }
- case 2:
- p.From = args[0]
- p.To = args[1]
- default:
- panic("bad args")
- }
- return p
- }
- framesize := s.Func.Text.To.Offset
- if framesize < 0 {
- panic("bad framesize")
- }
- s.Func.Args = s.Func.Text.To.Val.(int32)
- s.Func.Locals = int32(framesize)
- if s.Func.Text.From.Sym.Wrapper() {
- // if g._panic != nil && g._panic.argp == FP {
- // g._panic.argp = bottom-of-frame
- // }
- //
- // MOVD g_panic(g), R0
- // Get R0
- // I64Eqz
- // Not
- // If
- // Get SP
- // I64ExtendI32U
- // I64Const $framesize+8
- // I64Add
- // I64Load panic_argp(R0)
- // I64Eq
- // If
- // MOVD SP, panic_argp(R0)
- // End
- // End
- gpanic := obj.Addr{
- Type: obj.TYPE_MEM,
- Reg: REGG,
- Offset: 4 * 8, // g_panic
- }
- panicargp := obj.Addr{
- Type: obj.TYPE_MEM,
- Reg: REG_R0,
- Offset: 0, // panic.argp
- }
- p := s.Func.Text
- p = appendp(p, AMOVD, gpanic, regAddr(REG_R0))
- p = appendp(p, AGet, regAddr(REG_R0))
- p = appendp(p, AI64Eqz)
- p = appendp(p, ANot)
- p = appendp(p, AIf)
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AI64ExtendI32U)
- p = appendp(p, AI64Const, constAddr(framesize+8))
- p = appendp(p, AI64Add)
- p = appendp(p, AI64Load, panicargp)
- p = appendp(p, AI64Eq)
- p = appendp(p, AIf)
- p = appendp(p, AMOVD, regAddr(REG_SP), panicargp)
- p = appendp(p, AEnd)
- p = appendp(p, AEnd)
- }
- if framesize > 0 {
- p := s.Func.Text
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AI32Const, constAddr(framesize))
- p = appendp(p, AI32Sub)
- p = appendp(p, ASet, regAddr(REG_SP))
- p.Spadj = int32(framesize)
- }
- // Introduce resume points for CALL instructions
- // and collect other explicit resume points.
- numResumePoints := 0
- explicitBlockDepth := 0
- pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction
- var tableIdxs []uint64
- tablePC := int64(0)
- base := ctxt.PosTable.Pos(s.Func.Text.Pos).Base()
- for p := s.Func.Text; p != nil; p = p.Link {
- prevBase := base
- base = ctxt.PosTable.Pos(p.Pos).Base()
- switch p.As {
- case ABlock, ALoop, AIf:
- explicitBlockDepth++
- case AEnd:
- if explicitBlockDepth == 0 {
- panic("End without block")
- }
- explicitBlockDepth--
- case ARESUMEPOINT:
- if explicitBlockDepth != 0 {
- panic("RESUME can only be used on toplevel")
- }
- p.As = AEnd
- for tablePC <= pc {
- tableIdxs = append(tableIdxs, uint64(numResumePoints))
- tablePC++
- }
- numResumePoints++
- pc++
- case obj.ACALL:
- if explicitBlockDepth != 0 {
- panic("CALL can only be used on toplevel, try CALLNORESUME instead")
- }
- appendp(p, ARESUMEPOINT)
- }
- p.Pc = pc
- // Increase pc whenever some pc-value table needs a new entry. Don't increase it
- // more often to avoid bloat of the BrTable instruction.
- // The "base != prevBase" condition detects inlined instructions. They are an
- // implicit call, so entering and leaving this section affects the stack trace.
- if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase {
- pc++
- if p.To.Sym == sigpanic {
- // The panic stack trace expects the PC at the call of sigpanic,
- // not the next one. However, runtime.Caller subtracts 1 from the
- // PC. To make both PC and PC-1 work (have the same line number),
- // we advance the PC by 2 at sigpanic.
- pc++
- }
- }
- }
- tableIdxs = append(tableIdxs, uint64(numResumePoints))
- s.Size = pc + 1
- if !s.Func.Text.From.Sym.NoSplit() {
- p := s.Func.Text
- if framesize <= objabi.StackSmall {
- // small stack: SP <= stackguard
- // Get SP
- // Get g
- // I32WrapI64
- // I32Load $stackguard0
- // I32GtU
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AGet, regAddr(REGG))
- p = appendp(p, AI32WrapI64)
- p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
- p = appendp(p, AI32LeU)
- } else {
- // large stack: SP-framesize <= stackguard-StackSmall
- // SP <= stackguard+(framesize-StackSmall)
- // Get SP
- // Get g
- // I32WrapI64
- // I32Load $stackguard0
- // I32Const $(framesize-StackSmall)
- // I32Add
- // I32GtU
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AGet, regAddr(REGG))
- p = appendp(p, AI32WrapI64)
- p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
- p = appendp(p, AI32Const, constAddr(int64(framesize)-objabi.StackSmall))
- p = appendp(p, AI32Add)
- p = appendp(p, AI32LeU)
- }
- // TODO(neelance): handle wraparound case
- p = appendp(p, AIf)
- p = appendp(p, obj.ACALL, constAddr(0))
- if s.Func.Text.From.Sym.NeedCtxt() {
- p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack}
- } else {
- p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt}
- }
- p = appendp(p, AEnd)
- }
- // record the branches targeting the entry loop and the unwind exit,
- // their targets with be filled in later
- var entryPointLoopBranches []*obj.Prog
- var unwindExitBranches []*obj.Prog
- currentDepth := 0
- for p := s.Func.Text; p != nil; p = p.Link {
- switch p.As {
- case ABlock, ALoop, AIf:
- currentDepth++
- case AEnd:
- currentDepth--
- }
- switch p.As {
- case obj.AJMP:
- jmp := *p
- p.As = obj.ANOP
- if jmp.To.Type == obj.TYPE_BRANCH {
- // jump to basic block
- p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc))
- p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B
- p = appendp(p, ABr) // jump to beginning of entryPointLoop
- entryPointLoopBranches = append(entryPointLoopBranches, p)
- break
- }
- // low-level WebAssembly call to function
- switch jmp.To.Type {
- case obj.TYPE_MEM:
- if !notUsePC_B[jmp.To.Sym.Name] {
- // Set PC_B parameter to function entry.
- p = appendp(p, AI32Const, constAddr(0))
- }
- p = appendp(p, ACall, jmp.To)
- case obj.TYPE_NONE:
- // (target PC is on stack)
- p = appendp(p, AI32WrapI64)
- p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
- p = appendp(p, AI32ShrU)
- // Set PC_B parameter to function entry.
- // We need to push this before pushing the target PC_F,
- // so temporarily pop PC_F, using our REG_PC_B as a
- // scratch register, and push it back after pushing 0.
- p = appendp(p, ASet, regAddr(REG_PC_B))
- p = appendp(p, AI32Const, constAddr(0))
- p = appendp(p, AGet, regAddr(REG_PC_B))
- p = appendp(p, ACallIndirect)
- default:
- panic("bad target for JMP")
- }
- p = appendp(p, AReturn)
- case obj.ACALL, ACALLNORESUME:
- call := *p
- p.As = obj.ANOP
- pcAfterCall := call.Link.Pc
- if call.To.Sym == sigpanic {
- pcAfterCall-- // sigpanic expects to be called without advancing the pc
- }
- // jmpdefer manipulates the return address on the stack so deferreturn gets called repeatedly.
- // Model this in WebAssembly with a loop.
- if call.To.Sym == deferreturn {
- p = appendp(p, ALoop)
- }
- // SP -= 8
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AI32Const, constAddr(8))
- p = appendp(p, AI32Sub)
- p = appendp(p, ASet, regAddr(REG_SP))
- // write return address to Go stack
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AI64Const, obj.Addr{
- Type: obj.TYPE_ADDR,
- Name: obj.NAME_EXTERN,
- Sym: s, // PC_F
- Offset: pcAfterCall, // PC_B
- })
- p = appendp(p, AI64Store, constAddr(0))
- // low-level WebAssembly call to function
- switch call.To.Type {
- case obj.TYPE_MEM:
- if !notUsePC_B[call.To.Sym.Name] {
- // Set PC_B parameter to function entry.
- p = appendp(p, AI32Const, constAddr(0))
- }
- p = appendp(p, ACall, call.To)
- case obj.TYPE_NONE:
- // (target PC is on stack)
- p = appendp(p, AI32WrapI64)
- p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
- p = appendp(p, AI32ShrU)
- // Set PC_B parameter to function entry.
- // We need to push this before pushing the target PC_F,
- // so temporarily pop PC_F, using our PC_B as a
- // scratch register, and push it back after pushing 0.
- p = appendp(p, ASet, regAddr(REG_PC_B))
- p = appendp(p, AI32Const, constAddr(0))
- p = appendp(p, AGet, regAddr(REG_PC_B))
- p = appendp(p, ACallIndirect)
- default:
- panic("bad target for CALL")
- }
- // gcWriteBarrier has no return value, it never unwinds the stack
- if call.To.Sym == gcWriteBarrier {
- break
- }
- // jmpdefer removes the frame of deferreturn from the Go stack.
- // However, its WebAssembly function still returns normally,
- // so we need to return from deferreturn without removing its
- // stack frame (no RET), because the frame is already gone.
- if call.To.Sym == jmpdefer {
- p = appendp(p, AReturn)
- break
- }
- // return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack
- if call.As == ACALLNORESUME && call.To.Sym != sigpanic && call.To.Sym != sigpanic0 { // sigpanic unwinds the stack, but it never resumes
- // trying to unwind WebAssembly stack but call has no resume point, terminate with error
- p = appendp(p, AIf)
- p = appendp(p, obj.AUNDEF)
- p = appendp(p, AEnd)
- } else {
- // unwinding WebAssembly stack to switch goroutine, return 1
- p = appendp(p, ABrIf)
- unwindExitBranches = append(unwindExitBranches, p)
- }
- // jump to before the call if jmpdefer has reset the return address to the call's PC
- if call.To.Sym == deferreturn {
- // get PC_B from -8(SP)
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AI32Const, constAddr(8))
- p = appendp(p, AI32Sub)
- p = appendp(p, AI32Load16U, constAddr(0))
- p = appendp(p, ATee, regAddr(REG_PC_B))
- p = appendp(p, AI32Const, constAddr(call.Pc))
- p = appendp(p, AI32Eq)
- p = appendp(p, ABrIf, constAddr(0))
- p = appendp(p, AEnd) // end of Loop
- }
- case obj.ARET, ARETUNWIND:
- ret := *p
- p.As = obj.ANOP
- if framesize > 0 {
- // SP += framesize
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AI32Const, constAddr(framesize))
- p = appendp(p, AI32Add)
- p = appendp(p, ASet, regAddr(REG_SP))
- // TODO(neelance): This should theoretically set Spadj, but it only works without.
- // p.Spadj = int32(-framesize)
- }
- if ret.To.Type == obj.TYPE_MEM {
- // Set PC_B parameter to function entry.
- p = appendp(p, AI32Const, constAddr(0))
- // low-level WebAssembly call to function
- p = appendp(p, ACall, ret.To)
- p = appendp(p, AReturn)
- break
- }
- // SP += 8
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, AI32Const, constAddr(8))
- p = appendp(p, AI32Add)
- p = appendp(p, ASet, regAddr(REG_SP))
- if ret.As == ARETUNWIND {
- // function needs to unwind the WebAssembly stack, return 1
- p = appendp(p, AI32Const, constAddr(1))
- p = appendp(p, AReturn)
- break
- }
- // not unwinding the WebAssembly stack, return 0
- p = appendp(p, AI32Const, constAddr(0))
- p = appendp(p, AReturn)
- }
- }
- for p := s.Func.Text; p != nil; p = p.Link {
- switch p.From.Name {
- case obj.NAME_AUTO:
- p.From.Offset += int64(framesize)
- case obj.NAME_PARAM:
- p.From.Reg = REG_SP
- p.From.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address
- }
- switch p.To.Name {
- case obj.NAME_AUTO:
- p.To.Offset += int64(framesize)
- case obj.NAME_PARAM:
- p.To.Reg = REG_SP
- p.To.Offset += int64(framesize) + 8 // parameters are after the frame and the 8-byte return address
- }
- switch p.As {
- case AGet:
- if p.From.Type == obj.TYPE_ADDR {
- get := *p
- p.As = obj.ANOP
- switch get.From.Name {
- case obj.NAME_EXTERN:
- p = appendp(p, AI64Const, get.From)
- case obj.NAME_AUTO, obj.NAME_PARAM:
- p = appendp(p, AGet, regAddr(get.From.Reg))
- if get.From.Reg == REG_SP {
- p = appendp(p, AI64ExtendI32U)
- }
- if get.From.Offset != 0 {
- p = appendp(p, AI64Const, constAddr(get.From.Offset))
- p = appendp(p, AI64Add)
- }
- default:
- panic("bad Get: invalid name")
- }
- }
- case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
- if p.From.Type == obj.TYPE_MEM {
- as := p.As
- from := p.From
- p.As = AGet
- p.From = regAddr(from.Reg)
- if from.Reg != REG_SP {
- p = appendp(p, AI32WrapI64)
- }
- p = appendp(p, as, constAddr(from.Offset))
- }
- case AMOVB, AMOVH, AMOVW, AMOVD:
- mov := *p
- p.As = obj.ANOP
- var loadAs obj.As
- var storeAs obj.As
- switch mov.As {
- case AMOVB:
- loadAs = AI64Load8U
- storeAs = AI64Store8
- case AMOVH:
- loadAs = AI64Load16U
- storeAs = AI64Store16
- case AMOVW:
- loadAs = AI64Load32U
- storeAs = AI64Store32
- case AMOVD:
- loadAs = AI64Load
- storeAs = AI64Store
- }
- appendValue := func() {
- switch mov.From.Type {
- case obj.TYPE_CONST:
- p = appendp(p, AI64Const, constAddr(mov.From.Offset))
- case obj.TYPE_ADDR:
- switch mov.From.Name {
- case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO:
- p = appendp(p, AGet, regAddr(mov.From.Reg))
- if mov.From.Reg == REG_SP {
- p = appendp(p, AI64ExtendI32U)
- }
- p = appendp(p, AI64Const, constAddr(mov.From.Offset))
- p = appendp(p, AI64Add)
- case obj.NAME_EXTERN:
- p = appendp(p, AI64Const, mov.From)
- default:
- panic("bad name for MOV")
- }
- case obj.TYPE_REG:
- p = appendp(p, AGet, mov.From)
- if mov.From.Reg == REG_SP {
- p = appendp(p, AI64ExtendI32U)
- }
- case obj.TYPE_MEM:
- p = appendp(p, AGet, regAddr(mov.From.Reg))
- if mov.From.Reg != REG_SP {
- p = appendp(p, AI32WrapI64)
- }
- p = appendp(p, loadAs, constAddr(mov.From.Offset))
- default:
- panic("bad MOV type")
- }
- }
- switch mov.To.Type {
- case obj.TYPE_REG:
- appendValue()
- if mov.To.Reg == REG_SP {
- p = appendp(p, AI32WrapI64)
- }
- p = appendp(p, ASet, mov.To)
- case obj.TYPE_MEM:
- switch mov.To.Name {
- case obj.NAME_NONE, obj.NAME_PARAM:
- p = appendp(p, AGet, regAddr(mov.To.Reg))
- if mov.To.Reg != REG_SP {
- p = appendp(p, AI32WrapI64)
- }
- case obj.NAME_EXTERN:
- p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym})
- default:
- panic("bad MOV name")
- }
- appendValue()
- p = appendp(p, storeAs, constAddr(mov.To.Offset))
- default:
- panic("bad MOV type")
- }
- case ACallImport:
- p.As = obj.ANOP
- p = appendp(p, AGet, regAddr(REG_SP))
- p = appendp(p, ACall, obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: s})
- p.Mark = WasmImport
- }
- }
- {
- p := s.Func.Text
- if len(unwindExitBranches) > 0 {
- p = appendp(p, ABlock) // unwindExit, used to return 1 when unwinding the stack
- for _, b := range unwindExitBranches {
- b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
- }
- }
- if len(entryPointLoopBranches) > 0 {
- p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks
- for _, b := range entryPointLoopBranches {
- b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
- }
- }
- if numResumePoints > 0 {
- // Add Block instructions for resume points and BrTable to jump to selected resume point.
- for i := 0; i < numResumePoints+1; i++ {
- p = appendp(p, ABlock)
- }
- p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B
- p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs})
- p = appendp(p, AEnd) // end of Block
- }
- for p.Link != nil {
- p = p.Link // function instructions
- }
- if len(entryPointLoopBranches) > 0 {
- p = appendp(p, AEnd) // end of entryPointLoop
- }
- p = appendp(p, obj.AUNDEF)
- if len(unwindExitBranches) > 0 {
- p = appendp(p, AEnd) // end of unwindExit
- p = appendp(p, AI32Const, constAddr(1))
- }
- }
- currentDepth = 0
- blockDepths := make(map[*obj.Prog]int)
- for p := s.Func.Text; p != nil; p = p.Link {
- switch p.As {
- case ABlock, ALoop, AIf:
- currentDepth++
- blockDepths[p] = currentDepth
- case AEnd:
- currentDepth--
- }
- switch p.As {
- case ABr, ABrIf:
- if p.To.Type == obj.TYPE_BRANCH {
- blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)]
- if !ok {
- panic("label not at block")
- }
- p.To = constAddr(int64(currentDepth - blockDepth))
- }
- }
- }
- }
- func constAddr(value int64) obj.Addr {
- return obj.Addr{Type: obj.TYPE_CONST, Offset: value}
- }
- func regAddr(reg int16) obj.Addr {
- return obj.Addr{Type: obj.TYPE_REG, Reg: reg}
- }
- // Most of the Go functions has a single parameter (PC_B) in
- // Wasm ABI. This is a list of exceptions.
- var notUsePC_B = map[string]bool{
- "_rt0_wasm_js": true,
- "wasm_export_run": true,
- "wasm_export_resume": true,
- "wasm_export_getsp": true,
- "wasm_pc_f_loop": true,
- "runtime.wasmMove": true,
- "runtime.wasmZero": true,
- "runtime.wasmDiv": true,
- "runtime.wasmTruncS": true,
- "runtime.wasmTruncU": true,
- "runtime.gcWriteBarrier": true,
- "cmpbody": true,
- "memeqbody": true,
- "memcmp": true,
- "memchr": true,
- }
- func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
- type regVar struct {
- global bool
- index uint64
- }
- type varDecl struct {
- count uint64
- typ valueType
- }
- hasLocalSP := false
- regVars := [MAXREG - MINREG]*regVar{
- REG_SP - MINREG: {true, 0},
- REG_CTXT - MINREG: {true, 1},
- REG_g - MINREG: {true, 2},
- REG_RET0 - MINREG: {true, 3},
- REG_RET1 - MINREG: {true, 4},
- REG_RET2 - MINREG: {true, 5},
- REG_RET3 - MINREG: {true, 6},
- REG_PAUSE - MINREG: {true, 7},
- }
- var varDecls []*varDecl
- useAssemblyRegMap := func() {
- for i := int16(0); i < 16; i++ {
- regVars[REG_R0+i-MINREG] = ®Var{false, uint64(i)}
- }
- }
- // Function starts with declaration of locals: numbers and types.
- // Some functions use a special calling convention.
- switch s.Name {
- case "_rt0_wasm_js", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp", "wasm_pc_f_loop",
- "runtime.wasmMove", "runtime.wasmZero", "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody":
- varDecls = []*varDecl{}
- useAssemblyRegMap()
- case "memchr", "memcmp":
- varDecls = []*varDecl{{count: 2, typ: i32}}
- useAssemblyRegMap()
- case "cmpbody":
- varDecls = []*varDecl{{count: 2, typ: i64}}
- useAssemblyRegMap()
- case "runtime.gcWriteBarrier":
- varDecls = []*varDecl{{count: 4, typ: i64}}
- useAssemblyRegMap()
- default:
- // Normal calling convention: PC_B as WebAssembly parameter. First local variable is local SP cache.
- regVars[REG_PC_B-MINREG] = ®Var{false, 0}
- hasLocalSP = true
- var regUsed [MAXREG - MINREG]bool
- for p := s.Func.Text; p != nil; p = p.Link {
- if p.From.Reg != 0 {
- regUsed[p.From.Reg-MINREG] = true
- }
- if p.To.Reg != 0 {
- regUsed[p.To.Reg-MINREG] = true
- }
- }
- regs := []int16{REG_SP}
- for reg := int16(REG_R0); reg <= REG_F31; reg++ {
- if regUsed[reg-MINREG] {
- regs = append(regs, reg)
- }
- }
- var lastDecl *varDecl
- for i, reg := range regs {
- t := regType(reg)
- if lastDecl == nil || lastDecl.typ != t {
- lastDecl = &varDecl{
- count: 0,
- typ: t,
- }
- varDecls = append(varDecls, lastDecl)
- }
- lastDecl.count++
- if reg != REG_SP {
- regVars[reg-MINREG] = ®Var{false, 1 + uint64(i)}
- }
- }
- }
- w := new(bytes.Buffer)
- writeUleb128(w, uint64(len(varDecls)))
- for _, decl := range varDecls {
- writeUleb128(w, decl.count)
- w.WriteByte(byte(decl.typ))
- }
- if hasLocalSP {
- // Copy SP from its global variable into a local variable. Accessing a local variable is more efficient.
- updateLocalSP(w)
- }
- for p := s.Func.Text; p != nil; p = p.Link {
- switch p.As {
- case AGet:
- if p.From.Type != obj.TYPE_REG {
- panic("bad Get: argument is not a register")
- }
- reg := p.From.Reg
- v := regVars[reg-MINREG]
- if v == nil {
- panic("bad Get: invalid register")
- }
- if reg == REG_SP && hasLocalSP {
- writeOpcode(w, ALocalGet)
- writeUleb128(w, 1) // local SP
- continue
- }
- if v.global {
- writeOpcode(w, AGlobalGet)
- } else {
- writeOpcode(w, ALocalGet)
- }
- writeUleb128(w, v.index)
- continue
- case ASet:
- if p.To.Type != obj.TYPE_REG {
- panic("bad Set: argument is not a register")
- }
- reg := p.To.Reg
- v := regVars[reg-MINREG]
- if v == nil {
- panic("bad Set: invalid register")
- }
- if reg == REG_SP && hasLocalSP {
- writeOpcode(w, ALocalTee)
- writeUleb128(w, 1) // local SP
- }
- if v.global {
- writeOpcode(w, AGlobalSet)
- } else {
- if p.Link.As == AGet && p.Link.From.Reg == reg {
- writeOpcode(w, ALocalTee)
- p = p.Link
- } else {
- writeOpcode(w, ALocalSet)
- }
- }
- writeUleb128(w, v.index)
- continue
- case ATee:
- if p.To.Type != obj.TYPE_REG {
- panic("bad Tee: argument is not a register")
- }
- reg := p.To.Reg
- v := regVars[reg-MINREG]
- if v == nil {
- panic("bad Tee: invalid register")
- }
- writeOpcode(w, ALocalTee)
- writeUleb128(w, v.index)
- continue
- case ANot:
- writeOpcode(w, AI32Eqz)
- continue
- case obj.AUNDEF:
- writeOpcode(w, AUnreachable)
- continue
- case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA:
- // ignore
- continue
- }
- writeOpcode(w, p.As)
- switch p.As {
- case ABlock, ALoop, AIf:
- if p.From.Offset != 0 {
- // block type, rarely used, e.g. for code compiled with emscripten
- w.WriteByte(0x80 - byte(p.From.Offset))
- continue
- }
- w.WriteByte(0x40)
- case ABr, ABrIf:
- if p.To.Type != obj.TYPE_CONST {
- panic("bad Br/BrIf")
- }
- writeUleb128(w, uint64(p.To.Offset))
- case ABrTable:
- idxs := p.To.Val.([]uint64)
- writeUleb128(w, uint64(len(idxs)-1))
- for _, idx := range idxs {
- writeUleb128(w, idx)
- }
- case ACall:
- switch p.To.Type {
- case obj.TYPE_CONST:
- writeUleb128(w, uint64(p.To.Offset))
- case obj.TYPE_MEM:
- if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC {
- fmt.Println(p.To)
- panic("bad name for Call")
- }
- r := obj.Addrel(s)
- r.Off = int32(w.Len())
- r.Type = objabi.R_CALL
- if p.Mark&WasmImport != 0 {
- r.Type = objabi.R_WASMIMPORT
- }
- r.Sym = p.To.Sym
- if hasLocalSP {
- // The stack may have moved, which changes SP. Update the local SP variable.
- updateLocalSP(w)
- }
- default:
- panic("bad type for Call")
- }
- case ACallIndirect:
- writeUleb128(w, uint64(p.To.Offset))
- w.WriteByte(0x00) // reserved value
- if hasLocalSP {
- // The stack may have moved, which changes SP. Update the local SP variable.
- updateLocalSP(w)
- }
- case AI32Const, AI64Const:
- if p.From.Name == obj.NAME_EXTERN {
- r := obj.Addrel(s)
- r.Off = int32(w.Len())
- r.Type = objabi.R_ADDR
- r.Sym = p.From.Sym
- r.Add = p.From.Offset
- break
- }
- writeSleb128(w, p.From.Offset)
- case AF32Const:
- b := make([]byte, 4)
- binary.LittleEndian.PutUint32(b, math.Float32bits(float32(p.From.Val.(float64))))
- w.Write(b)
- case AF64Const:
- b := make([]byte, 8)
- binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64)))
- w.Write(b)
- case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
- if p.From.Offset < 0 {
- panic("negative offset for *Load")
- }
- if p.From.Type != obj.TYPE_CONST {
- panic("bad type for *Load")
- }
- if p.From.Offset > math.MaxUint32 {
- ctxt.Diag("bad offset in %v", p)
- }
- writeUleb128(w, align(p.As))
- writeUleb128(w, uint64(p.From.Offset))
- case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32:
- if p.To.Offset < 0 {
- panic("negative offset")
- }
- if p.From.Offset > math.MaxUint32 {
- ctxt.Diag("bad offset in %v", p)
- }
- writeUleb128(w, align(p.As))
- writeUleb128(w, uint64(p.To.Offset))
- case ACurrentMemory, AGrowMemory:
- w.WriteByte(0x00)
- }
- }
- w.WriteByte(0x0b) // end
- s.P = w.Bytes()
- }
- func updateLocalSP(w *bytes.Buffer) {
- writeOpcode(w, AGlobalGet)
- writeUleb128(w, 0) // global SP
- writeOpcode(w, ALocalSet)
- writeUleb128(w, 1) // local SP
- }
- func writeOpcode(w *bytes.Buffer, as obj.As) {
- switch {
- case as < AUnreachable:
- panic(fmt.Sprintf("unexpected assembler op: %s", as))
- case as < AEnd:
- w.WriteByte(byte(as - AUnreachable + 0x00))
- case as < ADrop:
- w.WriteByte(byte(as - AEnd + 0x0B))
- case as < ALocalGet:
- w.WriteByte(byte(as - ADrop + 0x1A))
- case as < AI32Load:
- w.WriteByte(byte(as - ALocalGet + 0x20))
- case as < AI32TruncSatF32S:
- w.WriteByte(byte(as - AI32Load + 0x28))
- case as < ALast:
- w.WriteByte(0xFC)
- w.WriteByte(byte(as - AI32TruncSatF32S + 0x00))
- default:
- panic(fmt.Sprintf("unexpected assembler op: %s", as))
- }
- }
- type valueType byte
- const (
- i32 valueType = 0x7F
- i64 valueType = 0x7E
- f32 valueType = 0x7D
- f64 valueType = 0x7C
- )
- func regType(reg int16) valueType {
- switch {
- case reg == REG_SP:
- return i32
- case reg >= REG_R0 && reg <= REG_R15:
- return i64
- case reg >= REG_F0 && reg <= REG_F15:
- return f32
- case reg >= REG_F16 && reg <= REG_F31:
- return f64
- default:
- panic("invalid register")
- }
- }
- func align(as obj.As) uint64 {
- switch as {
- case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8:
- return 0
- case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16:
- return 1
- case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32:
- return 2
- case AI64Load, AF64Load, AI64Store, AF64Store:
- return 3
- default:
- panic("align: bad op")
- }
- }
- func writeUleb128(w io.ByteWriter, v uint64) {
- if v < 128 {
- w.WriteByte(uint8(v))
- return
- }
- more := true
- for more {
- c := uint8(v & 0x7f)
- v >>= 7
- more = v != 0
- if more {
- c |= 0x80
- }
- w.WriteByte(c)
- }
- }
- func writeSleb128(w io.ByteWriter, v int64) {
- more := true
- for more {
- c := uint8(v & 0x7f)
- s := uint8(v & 0x40)
- v >>= 7
- more = !((v == 0 && s == 0) || (v == -1 && s != 0))
- if more {
- c |= 0x80
- }
- w.WriteByte(c)
- }
- }
|