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