]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/internal/obj/wasm/wasmobj.go
all: implement wasmimport directive
[gostls13.git] / src / cmd / internal / obj / wasm / wasmobj.go
1 // Copyright 2018 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package wasm
6
7 import (
8         "bytes"
9         "cmd/internal/obj"
10         "cmd/internal/objabi"
11         "cmd/internal/sys"
12         "encoding/binary"
13         "fmt"
14         "io"
15         "math"
16 )
17
18 var Register = map[string]int16{
19         "SP":    REG_SP,
20         "CTXT":  REG_CTXT,
21         "g":     REG_g,
22         "RET0":  REG_RET0,
23         "RET1":  REG_RET1,
24         "RET2":  REG_RET2,
25         "RET3":  REG_RET3,
26         "PAUSE": REG_PAUSE,
27
28         "R0":  REG_R0,
29         "R1":  REG_R1,
30         "R2":  REG_R2,
31         "R3":  REG_R3,
32         "R4":  REG_R4,
33         "R5":  REG_R5,
34         "R6":  REG_R6,
35         "R7":  REG_R7,
36         "R8":  REG_R8,
37         "R9":  REG_R9,
38         "R10": REG_R10,
39         "R11": REG_R11,
40         "R12": REG_R12,
41         "R13": REG_R13,
42         "R14": REG_R14,
43         "R15": REG_R15,
44
45         "F0":  REG_F0,
46         "F1":  REG_F1,
47         "F2":  REG_F2,
48         "F3":  REG_F3,
49         "F4":  REG_F4,
50         "F5":  REG_F5,
51         "F6":  REG_F6,
52         "F7":  REG_F7,
53         "F8":  REG_F8,
54         "F9":  REG_F9,
55         "F10": REG_F10,
56         "F11": REG_F11,
57         "F12": REG_F12,
58         "F13": REG_F13,
59         "F14": REG_F14,
60         "F15": REG_F15,
61
62         "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         ABr:           true,
104         ABrIf:         true,
105         ABrTable:      true,
106         AI32Store:     true,
107         AI64Store:     true,
108         AF32Store:     true,
109         AF64Store:     true,
110         AI32Store8:    true,
111         AI32Store16:   true,
112         AI64Store8:    true,
113         AI64Store16:   true,
114         AI64Store32:   true,
115         ACALLNORESUME: true,
116 }
117
118 var Linkwasm = obj.LinkArch{
119         Arch:       sys.ArchWasm,
120         Init:       instinit,
121         Preprocess: preprocess,
122         Assemble:   assemble,
123         UnaryDst:   unaryDst,
124 }
125
126 var (
127         morestack       *obj.LSym
128         morestackNoCtxt *obj.LSym
129         sigpanic        *obj.LSym
130 )
131
132 const (
133         /* mark flags */
134         WasmImport = 1 << 0
135 )
136
137 const (
138         // This is a special wasm module name that when used as the module name
139         // in //go:wasmimport will cause the generated code to pass the stack pointer
140         // directly to the imported function. In other words, any function that
141         // uses the gojs module understands the internal Go WASM ABI directly.
142         GojsModule = "gojs"
143 )
144
145 func instinit(ctxt *obj.Link) {
146         morestack = ctxt.Lookup("runtime.morestack")
147         morestackNoCtxt = ctxt.Lookup("runtime.morestack_noctxt")
148         sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
149 }
150
151 func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
152         appendp := func(p *obj.Prog, as obj.As, args ...obj.Addr) *obj.Prog {
153                 if p.As != obj.ANOP {
154                         p2 := obj.Appendp(p, newprog)
155                         p2.Pc = p.Pc
156                         p = p2
157                 }
158                 p.As = as
159                 switch len(args) {
160                 case 0:
161                         p.From = obj.Addr{}
162                         p.To = obj.Addr{}
163                 case 1:
164                         if unaryDst[as] {
165                                 p.From = obj.Addr{}
166                                 p.To = args[0]
167                         } else {
168                                 p.From = args[0]
169                                 p.To = obj.Addr{}
170                         }
171                 case 2:
172                         p.From = args[0]
173                         p.To = args[1]
174                 default:
175                         panic("bad args")
176                 }
177                 return p
178         }
179
180         framesize := s.Func().Text.To.Offset
181         if framesize < 0 {
182                 panic("bad framesize")
183         }
184         s.Func().Args = s.Func().Text.To.Val.(int32)
185         s.Func().Locals = int32(framesize)
186
187         // If the function exits just to call out to a wasmimport, then
188         // generate the code to translate from our internal Go-stack
189         // based call convention to the native webassembly call convention.
190         if wi := s.Func().WasmImport; wi != nil {
191                 s.Func().WasmImportSym = wi.CreateSym(ctxt)
192                 p := s.Func().Text
193                 if p.Link != nil {
194                         panic("wrapper functions for WASM imports should not have a body")
195                 }
196                 to := obj.Addr{
197                         Type: obj.TYPE_MEM,
198                         Name: obj.NAME_EXTERN,
199                         Sym:  s,
200                 }
201
202                 // If the module that the import is for is our magic "gojs" module, then this
203                 // indicates that the called function understands the Go stack-based call convention
204                 // so we just pass the stack pointer to it, knowing it will read the params directly
205                 // off the stack and push the results into memory based on the stack pointer.
206                 if wi.Module == GojsModule {
207                         // The called function has a signature of 'func(sp int)'. It has access to the memory
208                         // value somewhere to be able to address the memory based on the "sp" value.
209
210                         p = appendp(p, AGet, regAddr(REG_SP))
211                         p = appendp(p, ACall, to)
212
213                         p.Mark = WasmImport
214                 } else {
215                         if len(wi.Results) > 1 {
216                                 // TODO(evanphx) implement support for the multi-value proposal:
217                                 // https://github.com/WebAssembly/multi-value/blob/master/proposals/multi-value/Overview.md
218                                 panic("invalid results type") // impossible until multi-value proposal has landed
219                         }
220                         if len(wi.Results) == 1 {
221                                 // If we have a result (rather than returning nothing at all), then
222                                 // we'll write the result to the Go stack relative to the current stack pointer.
223                                 // We cache the current stack pointer value on the wasm stack here and then use
224                                 // it after the Call instruction to store the result.
225                                 p = appendp(p, AGet, regAddr(REG_SP))
226                         }
227                         for _, f := range wi.Params {
228                                 // Each load instructions will consume the value of sp on the stack, so
229                                 // we need to read sp for each param. WASM appears to not have a stack dup instruction
230                                 // (a strange ommission for a stack-based VM), if it did, we'd be using the dup here.
231                                 p = appendp(p, AGet, regAddr(REG_SP))
232
233                                 // Offset is the location of the param on the Go stack (ie relative to sp).
234                                 // Because of our call convention, the parameters are located an additional 8 bytes
235                                 // from sp because we store the return address as a int64 at the bottom of the stack.
236                                 // Ie the stack looks like [return_addr, param3, param2, param1, etc]
237
238                                 // Ergo, we add 8 to the true byte offset of the param to skip the return address.
239                                 loadOffset := f.Offset + 8
240
241                                 // We're reading the value from the Go stack onto the WASM stack and leaving it there
242                                 // for CALL to pick them up.
243                                 switch f.Type {
244                                 case obj.WasmI32:
245                                         p = appendp(p, AI32Load, constAddr(loadOffset))
246                                 case obj.WasmI64:
247                                         p = appendp(p, AI64Load, constAddr(loadOffset))
248                                 case obj.WasmF32:
249                                         p = appendp(p, AF32Load, constAddr(loadOffset))
250                                 case obj.WasmF64:
251                                         p = appendp(p, AF64Load, constAddr(loadOffset))
252                                 case obj.WasmPtr:
253                                         p = appendp(p, AI64Load, constAddr(loadOffset))
254                                         p = appendp(p, AI32WrapI64)
255                                 default:
256                                         panic("bad param type")
257                                 }
258                         }
259
260                         // The call instruction is marked as being for a wasm import so that a later phase
261                         // will generate relocation information that allows us to patch this with then
262                         // offset of the imported function in the wasm imports.
263                         p = appendp(p, ACall, to)
264                         p.Mark = WasmImport
265
266                         if len(wi.Results) == 1 {
267                                 f := wi.Results[0]
268
269                                 // Much like with the params, we need to adjust the offset we store the result value
270                                 // to by 8 bytes to account for the return address on the Go stack.
271                                 storeOffset := f.Offset + 8
272
273                                 // This code is paired the code above that reads the stack pointer onto the wasm
274                                 // stack. We've done this so we have a consistent view of the sp value as it might
275                                 // be manipulated by the call and we want to ignore that manipulation here.
276                                 switch f.Type {
277                                 case obj.WasmI32:
278                                         p = appendp(p, AI32Store, constAddr(storeOffset))
279                                 case obj.WasmI64:
280                                         p = appendp(p, AI64Store, constAddr(storeOffset))
281                                 case obj.WasmF32:
282                                         p = appendp(p, AF32Store, constAddr(storeOffset))
283                                 case obj.WasmF64:
284                                         p = appendp(p, AF64Store, constAddr(storeOffset))
285                                 case obj.WasmPtr:
286                                         p = appendp(p, AI64ExtendI32U)
287                                         p = appendp(p, AI64Store, constAddr(storeOffset))
288                                 default:
289                                         panic("bad result type")
290                                 }
291                         }
292                 }
293
294                 p = appendp(p, obj.ARET)
295
296                 // It should be 0 already, but we'll set it to 0 anyway just to be sure
297                 // that the code below which adds frame expansion code to the function body
298                 // isn't run. We don't want the frame expansion code because our function
299                 // body is just the code to translate and call the imported function.
300                 framesize = 0
301         } else if s.Func().Text.From.Sym.Wrapper() {
302                 // if g._panic != nil && g._panic.argp == FP {
303                 //   g._panic.argp = bottom-of-frame
304                 // }
305                 //
306                 // MOVD g_panic(g), R0
307                 // Get R0
308                 // I64Eqz
309                 // Not
310                 // If
311                 //   Get SP
312                 //   I64ExtendI32U
313                 //   I64Const $framesize+8
314                 //   I64Add
315                 //   I64Load panic_argp(R0)
316                 //   I64Eq
317                 //   If
318                 //     MOVD SP, panic_argp(R0)
319                 //   End
320                 // End
321
322                 gpanic := obj.Addr{
323                         Type:   obj.TYPE_MEM,
324                         Reg:    REGG,
325                         Offset: 4 * 8, // g_panic
326                 }
327
328                 panicargp := obj.Addr{
329                         Type:   obj.TYPE_MEM,
330                         Reg:    REG_R0,
331                         Offset: 0, // panic.argp
332                 }
333
334                 p := s.Func().Text
335                 p = appendp(p, AMOVD, gpanic, regAddr(REG_R0))
336
337                 p = appendp(p, AGet, regAddr(REG_R0))
338                 p = appendp(p, AI64Eqz)
339                 p = appendp(p, ANot)
340                 p = appendp(p, AIf)
341
342                 p = appendp(p, AGet, regAddr(REG_SP))
343                 p = appendp(p, AI64ExtendI32U)
344                 p = appendp(p, AI64Const, constAddr(framesize+8))
345                 p = appendp(p, AI64Add)
346                 p = appendp(p, AI64Load, panicargp)
347
348                 p = appendp(p, AI64Eq)
349                 p = appendp(p, AIf)
350                 p = appendp(p, AMOVD, regAddr(REG_SP), panicargp)
351                 p = appendp(p, AEnd)
352
353                 p = appendp(p, AEnd)
354         }
355
356         if framesize > 0 {
357                 p := s.Func().Text
358                 p = appendp(p, AGet, regAddr(REG_SP))
359                 p = appendp(p, AI32Const, constAddr(framesize))
360                 p = appendp(p, AI32Sub)
361                 p = appendp(p, ASet, regAddr(REG_SP))
362                 p.Spadj = int32(framesize)
363         }
364
365         // If the framesize is 0, then imply nosplit because it's a specially
366         // generated function.
367         needMoreStack := framesize > 0 && !s.Func().Text.From.Sym.NoSplit()
368
369         // If the maymorestack debug option is enabled, insert the
370         // call to maymorestack *before* processing resume points so
371         // we can construct a resume point after maymorestack for
372         // morestack to resume at.
373         var pMorestack = s.Func().Text
374         if needMoreStack && ctxt.Flag_maymorestack != "" {
375                 p := pMorestack
376
377                 // Save REGCTXT on the stack.
378                 const tempFrame = 8
379                 p = appendp(p, AGet, regAddr(REG_SP))
380                 p = appendp(p, AI32Const, constAddr(tempFrame))
381                 p = appendp(p, AI32Sub)
382                 p = appendp(p, ASet, regAddr(REG_SP))
383                 p.Spadj = tempFrame
384                 ctxtp := obj.Addr{
385                         Type:   obj.TYPE_MEM,
386                         Reg:    REG_SP,
387                         Offset: 0,
388                 }
389                 p = appendp(p, AMOVD, regAddr(REGCTXT), ctxtp)
390
391                 // maymorestack must not itself preempt because we
392                 // don't have full stack information, so this can be
393                 // ACALLNORESUME.
394                 p = appendp(p, ACALLNORESUME, constAddr(0))
395                 // See ../x86/obj6.go
396                 sym := ctxt.LookupABI(ctxt.Flag_maymorestack, s.ABI())
397                 p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: sym}
398
399                 // Restore REGCTXT.
400                 p = appendp(p, AMOVD, ctxtp, regAddr(REGCTXT))
401                 p = appendp(p, AGet, regAddr(REG_SP))
402                 p = appendp(p, AI32Const, constAddr(tempFrame))
403                 p = appendp(p, AI32Add)
404                 p = appendp(p, ASet, regAddr(REG_SP))
405                 p.Spadj = -tempFrame
406
407                 // Add an explicit ARESUMEPOINT after maymorestack for
408                 // morestack to resume at.
409                 pMorestack = appendp(p, ARESUMEPOINT)
410         }
411
412         // Introduce resume points for CALL instructions
413         // and collect other explicit resume points.
414         numResumePoints := 0
415         explicitBlockDepth := 0
416         pc := int64(0) // pc is only incremented when necessary, this avoids bloat of the BrTable instruction
417         var tableIdxs []uint64
418         tablePC := int64(0)
419         base := ctxt.PosTable.Pos(s.Func().Text.Pos).Base()
420         for p := s.Func().Text; p != nil; p = p.Link {
421                 prevBase := base
422                 base = ctxt.PosTable.Pos(p.Pos).Base()
423                 switch p.As {
424                 case ABlock, ALoop, AIf:
425                         explicitBlockDepth++
426
427                 case AEnd:
428                         if explicitBlockDepth == 0 {
429                                 panic("End without block")
430                         }
431                         explicitBlockDepth--
432
433                 case ARESUMEPOINT:
434                         if explicitBlockDepth != 0 {
435                                 panic("RESUME can only be used on toplevel")
436                         }
437                         p.As = AEnd
438                         for tablePC <= pc {
439                                 tableIdxs = append(tableIdxs, uint64(numResumePoints))
440                                 tablePC++
441                         }
442                         numResumePoints++
443                         pc++
444
445                 case obj.ACALL:
446                         if explicitBlockDepth != 0 {
447                                 panic("CALL can only be used on toplevel, try CALLNORESUME instead")
448                         }
449                         appendp(p, ARESUMEPOINT)
450                 }
451
452                 p.Pc = pc
453
454                 // Increase pc whenever some pc-value table needs a new entry. Don't increase it
455                 // more often to avoid bloat of the BrTable instruction.
456                 // The "base != prevBase" condition detects inlined instructions. They are an
457                 // implicit call, so entering and leaving this section affects the stack trace.
458                 if p.As == ACALLNORESUME || p.As == obj.ANOP || p.As == ANop || p.Spadj != 0 || base != prevBase {
459                         pc++
460                         if p.To.Sym == sigpanic {
461                                 // The panic stack trace expects the PC at the call of sigpanic,
462                                 // not the next one. However, runtime.Caller subtracts 1 from the
463                                 // PC. To make both PC and PC-1 work (have the same line number),
464                                 // we advance the PC by 2 at sigpanic.
465                                 pc++
466                         }
467                 }
468         }
469         tableIdxs = append(tableIdxs, uint64(numResumePoints))
470         s.Size = pc + 1
471
472         if needMoreStack {
473                 p := pMorestack
474
475                 if framesize <= objabi.StackSmall {
476                         // small stack: SP <= stackguard
477                         // Get SP
478                         // Get g
479                         // I32WrapI64
480                         // I32Load $stackguard0
481                         // I32GtU
482
483                         p = appendp(p, AGet, regAddr(REG_SP))
484                         p = appendp(p, AGet, regAddr(REGG))
485                         p = appendp(p, AI32WrapI64)
486                         p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
487                         p = appendp(p, AI32LeU)
488                 } else {
489                         // large stack: SP-framesize <= stackguard-StackSmall
490                         //              SP <= stackguard+(framesize-StackSmall)
491                         // Get SP
492                         // Get g
493                         // I32WrapI64
494                         // I32Load $stackguard0
495                         // I32Const $(framesize-StackSmall)
496                         // I32Add
497                         // I32GtU
498
499                         p = appendp(p, AGet, regAddr(REG_SP))
500                         p = appendp(p, AGet, regAddr(REGG))
501                         p = appendp(p, AI32WrapI64)
502                         p = appendp(p, AI32Load, constAddr(2*int64(ctxt.Arch.PtrSize))) // G.stackguard0
503                         p = appendp(p, AI32Const, constAddr(framesize-objabi.StackSmall))
504                         p = appendp(p, AI32Add)
505                         p = appendp(p, AI32LeU)
506                 }
507                 // TODO(neelance): handle wraparound case
508
509                 p = appendp(p, AIf)
510                 // This CALL does *not* have a resume point after it
511                 // (we already inserted all of the resume points). As
512                 // a result, morestack will resume at the *previous*
513                 // resume point (typically, the beginning of the
514                 // function) and perform the morestack check again.
515                 // This is why we don't need an explicit loop like
516                 // other architectures.
517                 p = appendp(p, obj.ACALL, constAddr(0))
518                 if s.Func().Text.From.Sym.NeedCtxt() {
519                         p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestack}
520                 } else {
521                         p.To = obj.Addr{Type: obj.TYPE_MEM, Name: obj.NAME_EXTERN, Sym: morestackNoCtxt}
522                 }
523                 p = appendp(p, AEnd)
524         }
525
526         // record the branches targeting the entry loop and the unwind exit,
527         // their targets with be filled in later
528         var entryPointLoopBranches []*obj.Prog
529         var unwindExitBranches []*obj.Prog
530         currentDepth := 0
531         for p := s.Func().Text; p != nil; p = p.Link {
532                 switch p.As {
533                 case ABlock, ALoop, AIf:
534                         currentDepth++
535                 case AEnd:
536                         currentDepth--
537                 }
538
539                 switch p.As {
540                 case obj.AJMP:
541                         jmp := *p
542                         p.As = obj.ANOP
543
544                         if jmp.To.Type == obj.TYPE_BRANCH {
545                                 // jump to basic block
546                                 p = appendp(p, AI32Const, constAddr(jmp.To.Val.(*obj.Prog).Pc))
547                                 p = appendp(p, ASet, regAddr(REG_PC_B)) // write next basic block to PC_B
548                                 p = appendp(p, ABr)                     // jump to beginning of entryPointLoop
549                                 entryPointLoopBranches = append(entryPointLoopBranches, p)
550                                 break
551                         }
552
553                         // low-level WebAssembly call to function
554                         switch jmp.To.Type {
555                         case obj.TYPE_MEM:
556                                 if !notUsePC_B[jmp.To.Sym.Name] {
557                                         // Set PC_B parameter to function entry.
558                                         p = appendp(p, AI32Const, constAddr(0))
559                                 }
560                                 p = appendp(p, ACall, jmp.To)
561
562                         case obj.TYPE_NONE:
563                                 // (target PC is on stack)
564                                 p = appendp(p, AI32WrapI64)
565                                 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
566                                 p = appendp(p, AI32ShrU)
567
568                                 // Set PC_B parameter to function entry.
569                                 // We need to push this before pushing the target PC_F,
570                                 // so temporarily pop PC_F, using our REG_PC_B as a
571                                 // scratch register, and push it back after pushing 0.
572                                 p = appendp(p, ASet, regAddr(REG_PC_B))
573                                 p = appendp(p, AI32Const, constAddr(0))
574                                 p = appendp(p, AGet, regAddr(REG_PC_B))
575
576                                 p = appendp(p, ACallIndirect)
577
578                         default:
579                                 panic("bad target for JMP")
580                         }
581
582                         p = appendp(p, AReturn)
583
584                 case obj.ACALL, ACALLNORESUME:
585                         call := *p
586                         p.As = obj.ANOP
587
588                         pcAfterCall := call.Link.Pc
589                         if call.To.Sym == sigpanic {
590                                 pcAfterCall-- // sigpanic expects to be called without advancing the pc
591                         }
592
593                         // SP -= 8
594                         p = appendp(p, AGet, regAddr(REG_SP))
595                         p = appendp(p, AI32Const, constAddr(8))
596                         p = appendp(p, AI32Sub)
597                         p = appendp(p, ASet, regAddr(REG_SP))
598
599                         // write return address to Go stack
600                         p = appendp(p, AGet, regAddr(REG_SP))
601                         p = appendp(p, AI64Const, obj.Addr{
602                                 Type:   obj.TYPE_ADDR,
603                                 Name:   obj.NAME_EXTERN,
604                                 Sym:    s,           // PC_F
605                                 Offset: pcAfterCall, // PC_B
606                         })
607                         p = appendp(p, AI64Store, constAddr(0))
608
609                         // low-level WebAssembly call to function
610                         switch call.To.Type {
611                         case obj.TYPE_MEM:
612                                 if !notUsePC_B[call.To.Sym.Name] {
613                                         // Set PC_B parameter to function entry.
614                                         p = appendp(p, AI32Const, constAddr(0))
615                                 }
616                                 p = appendp(p, ACall, call.To)
617
618                         case obj.TYPE_NONE:
619                                 // (target PC is on stack)
620                                 p = appendp(p, AI32WrapI64)
621                                 p = appendp(p, AI32Const, constAddr(16)) // only needs PC_F bits (16-31), PC_B bits (0-15) are zero
622                                 p = appendp(p, AI32ShrU)
623
624                                 // Set PC_B parameter to function entry.
625                                 // We need to push this before pushing the target PC_F,
626                                 // so temporarily pop PC_F, using our PC_B as a
627                                 // scratch register, and push it back after pushing 0.
628                                 p = appendp(p, ASet, regAddr(REG_PC_B))
629                                 p = appendp(p, AI32Const, constAddr(0))
630                                 p = appendp(p, AGet, regAddr(REG_PC_B))
631
632                                 p = appendp(p, ACallIndirect)
633
634                         default:
635                                 panic("bad target for CALL")
636                         }
637
638                         // return value of call is on the top of the stack, indicating whether to unwind the WebAssembly stack
639                         if call.As == ACALLNORESUME && call.To.Sym != sigpanic { // sigpanic unwinds the stack, but it never resumes
640                                 // trying to unwind WebAssembly stack but call has no resume point, terminate with error
641                                 p = appendp(p, AIf)
642                                 p = appendp(p, obj.AUNDEF)
643                                 p = appendp(p, AEnd)
644                         } else {
645                                 // unwinding WebAssembly stack to switch goroutine, return 1
646                                 p = appendp(p, ABrIf)
647                                 unwindExitBranches = append(unwindExitBranches, p)
648                         }
649
650                 case obj.ARET, ARETUNWIND:
651                         ret := *p
652                         p.As = obj.ANOP
653
654                         if framesize > 0 {
655                                 // SP += framesize
656                                 p = appendp(p, AGet, regAddr(REG_SP))
657                                 p = appendp(p, AI32Const, constAddr(framesize))
658                                 p = appendp(p, AI32Add)
659                                 p = appendp(p, ASet, regAddr(REG_SP))
660                                 // TODO(neelance): This should theoretically set Spadj, but it only works without.
661                                 // p.Spadj = int32(-framesize)
662                         }
663
664                         if ret.To.Type == obj.TYPE_MEM {
665                                 // Set PC_B parameter to function entry.
666                                 p = appendp(p, AI32Const, constAddr(0))
667
668                                 // low-level WebAssembly call to function
669                                 p = appendp(p, ACall, ret.To)
670                                 p = appendp(p, AReturn)
671                                 break
672                         }
673
674                         // SP += 8
675                         p = appendp(p, AGet, regAddr(REG_SP))
676                         p = appendp(p, AI32Const, constAddr(8))
677                         p = appendp(p, AI32Add)
678                         p = appendp(p, ASet, regAddr(REG_SP))
679
680                         if ret.As == ARETUNWIND {
681                                 // function needs to unwind the WebAssembly stack, return 1
682                                 p = appendp(p, AI32Const, constAddr(1))
683                                 p = appendp(p, AReturn)
684                                 break
685                         }
686
687                         // not unwinding the WebAssembly stack, return 0
688                         p = appendp(p, AI32Const, constAddr(0))
689                         p = appendp(p, AReturn)
690                 }
691         }
692
693         for p := s.Func().Text; p != nil; p = p.Link {
694                 switch p.From.Name {
695                 case obj.NAME_AUTO:
696                         p.From.Offset += framesize
697                 case obj.NAME_PARAM:
698                         p.From.Reg = REG_SP
699                         p.From.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address
700                 }
701
702                 switch p.To.Name {
703                 case obj.NAME_AUTO:
704                         p.To.Offset += framesize
705                 case obj.NAME_PARAM:
706                         p.To.Reg = REG_SP
707                         p.To.Offset += framesize + 8 // parameters are after the frame and the 8-byte return address
708                 }
709
710                 switch p.As {
711                 case AGet:
712                         if p.From.Type == obj.TYPE_ADDR {
713                                 get := *p
714                                 p.As = obj.ANOP
715
716                                 switch get.From.Name {
717                                 case obj.NAME_EXTERN:
718                                         p = appendp(p, AI64Const, get.From)
719                                 case obj.NAME_AUTO, obj.NAME_PARAM:
720                                         p = appendp(p, AGet, regAddr(get.From.Reg))
721                                         if get.From.Reg == REG_SP {
722                                                 p = appendp(p, AI64ExtendI32U)
723                                         }
724                                         if get.From.Offset != 0 {
725                                                 p = appendp(p, AI64Const, constAddr(get.From.Offset))
726                                                 p = appendp(p, AI64Add)
727                                         }
728                                 default:
729                                         panic("bad Get: invalid name")
730                                 }
731                         }
732
733                 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
734                         if p.From.Type == obj.TYPE_MEM {
735                                 as := p.As
736                                 from := p.From
737
738                                 p.As = AGet
739                                 p.From = regAddr(from.Reg)
740
741                                 if from.Reg != REG_SP {
742                                         p = appendp(p, AI32WrapI64)
743                                 }
744
745                                 p = appendp(p, as, constAddr(from.Offset))
746                         }
747
748                 case AMOVB, AMOVH, AMOVW, AMOVD:
749                         mov := *p
750                         p.As = obj.ANOP
751
752                         var loadAs obj.As
753                         var storeAs obj.As
754                         switch mov.As {
755                         case AMOVB:
756                                 loadAs = AI64Load8U
757                                 storeAs = AI64Store8
758                         case AMOVH:
759                                 loadAs = AI64Load16U
760                                 storeAs = AI64Store16
761                         case AMOVW:
762                                 loadAs = AI64Load32U
763                                 storeAs = AI64Store32
764                         case AMOVD:
765                                 loadAs = AI64Load
766                                 storeAs = AI64Store
767                         }
768
769                         appendValue := func() {
770                                 switch mov.From.Type {
771                                 case obj.TYPE_CONST:
772                                         p = appendp(p, AI64Const, constAddr(mov.From.Offset))
773
774                                 case obj.TYPE_ADDR:
775                                         switch mov.From.Name {
776                                         case obj.NAME_NONE, obj.NAME_PARAM, obj.NAME_AUTO:
777                                                 p = appendp(p, AGet, regAddr(mov.From.Reg))
778                                                 if mov.From.Reg == REG_SP {
779                                                         p = appendp(p, AI64ExtendI32U)
780                                                 }
781                                                 p = appendp(p, AI64Const, constAddr(mov.From.Offset))
782                                                 p = appendp(p, AI64Add)
783                                         case obj.NAME_EXTERN:
784                                                 p = appendp(p, AI64Const, mov.From)
785                                         default:
786                                                 panic("bad name for MOV")
787                                         }
788
789                                 case obj.TYPE_REG:
790                                         p = appendp(p, AGet, mov.From)
791                                         if mov.From.Reg == REG_SP {
792                                                 p = appendp(p, AI64ExtendI32U)
793                                         }
794
795                                 case obj.TYPE_MEM:
796                                         p = appendp(p, AGet, regAddr(mov.From.Reg))
797                                         if mov.From.Reg != REG_SP {
798                                                 p = appendp(p, AI32WrapI64)
799                                         }
800                                         p = appendp(p, loadAs, constAddr(mov.From.Offset))
801
802                                 default:
803                                         panic("bad MOV type")
804                                 }
805                         }
806
807                         switch mov.To.Type {
808                         case obj.TYPE_REG:
809                                 appendValue()
810                                 if mov.To.Reg == REG_SP {
811                                         p = appendp(p, AI32WrapI64)
812                                 }
813                                 p = appendp(p, ASet, mov.To)
814
815                         case obj.TYPE_MEM:
816                                 switch mov.To.Name {
817                                 case obj.NAME_NONE, obj.NAME_PARAM:
818                                         p = appendp(p, AGet, regAddr(mov.To.Reg))
819                                         if mov.To.Reg != REG_SP {
820                                                 p = appendp(p, AI32WrapI64)
821                                         }
822                                 case obj.NAME_EXTERN:
823                                         p = appendp(p, AI32Const, obj.Addr{Type: obj.TYPE_ADDR, Name: obj.NAME_EXTERN, Sym: mov.To.Sym})
824                                 default:
825                                         panic("bad MOV name")
826                                 }
827                                 appendValue()
828                                 p = appendp(p, storeAs, constAddr(mov.To.Offset))
829
830                         default:
831                                 panic("bad MOV type")
832                         }
833                 }
834         }
835
836         {
837                 p := s.Func().Text
838                 if len(unwindExitBranches) > 0 {
839                         p = appendp(p, ABlock) // unwindExit, used to return 1 when unwinding the stack
840                         for _, b := range unwindExitBranches {
841                                 b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
842                         }
843                 }
844                 if len(entryPointLoopBranches) > 0 {
845                         p = appendp(p, ALoop) // entryPointLoop, used to jump between basic blocks
846                         for _, b := range entryPointLoopBranches {
847                                 b.To = obj.Addr{Type: obj.TYPE_BRANCH, Val: p}
848                         }
849                 }
850                 if numResumePoints > 0 {
851                         // Add Block instructions for resume points and BrTable to jump to selected resume point.
852                         for i := 0; i < numResumePoints+1; i++ {
853                                 p = appendp(p, ABlock)
854                         }
855                         p = appendp(p, AGet, regAddr(REG_PC_B)) // read next basic block from PC_B
856                         p = appendp(p, ABrTable, obj.Addr{Val: tableIdxs})
857                         p = appendp(p, AEnd) // end of Block
858                 }
859                 for p.Link != nil {
860                         p = p.Link // function instructions
861                 }
862                 if len(entryPointLoopBranches) > 0 {
863                         p = appendp(p, AEnd) // end of entryPointLoop
864                 }
865                 p = appendp(p, obj.AUNDEF)
866                 if len(unwindExitBranches) > 0 {
867                         p = appendp(p, AEnd) // end of unwindExit
868                         p = appendp(p, AI32Const, constAddr(1))
869                 }
870         }
871
872         currentDepth = 0
873         blockDepths := make(map[*obj.Prog]int)
874         for p := s.Func().Text; p != nil; p = p.Link {
875                 switch p.As {
876                 case ABlock, ALoop, AIf:
877                         currentDepth++
878                         blockDepths[p] = currentDepth
879                 case AEnd:
880                         currentDepth--
881                 }
882
883                 switch p.As {
884                 case ABr, ABrIf:
885                         if p.To.Type == obj.TYPE_BRANCH {
886                                 blockDepth, ok := blockDepths[p.To.Val.(*obj.Prog)]
887                                 if !ok {
888                                         panic("label not at block")
889                                 }
890                                 p.To = constAddr(int64(currentDepth - blockDepth))
891                         }
892                 }
893         }
894 }
895
896 func constAddr(value int64) obj.Addr {
897         return obj.Addr{Type: obj.TYPE_CONST, Offset: value}
898 }
899
900 func regAddr(reg int16) obj.Addr {
901         return obj.Addr{Type: obj.TYPE_REG, Reg: reg}
902 }
903
904 // Most of the Go functions has a single parameter (PC_B) in
905 // Wasm ABI. This is a list of exceptions.
906 var notUsePC_B = map[string]bool{
907         "_rt0_wasm_js":            true,
908         "wasm_export_run":         true,
909         "wasm_export_resume":      true,
910         "wasm_export_getsp":       true,
911         "wasm_pc_f_loop":          true,
912         "gcWriteBarrier":          true,
913         "runtime.gcWriteBarrier1": true,
914         "runtime.gcWriteBarrier2": true,
915         "runtime.gcWriteBarrier3": true,
916         "runtime.gcWriteBarrier4": true,
917         "runtime.gcWriteBarrier5": true,
918         "runtime.gcWriteBarrier6": true,
919         "runtime.gcWriteBarrier7": true,
920         "runtime.gcWriteBarrier8": true,
921         "runtime.wasmDiv":         true,
922         "runtime.wasmTruncS":      true,
923         "runtime.wasmTruncU":      true,
924         "cmpbody":                 true,
925         "memeqbody":               true,
926         "memcmp":                  true,
927         "memchr":                  true,
928 }
929
930 func assemble(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {
931         type regVar struct {
932                 global bool
933                 index  uint64
934         }
935
936         type varDecl struct {
937                 count uint64
938                 typ   valueType
939         }
940
941         hasLocalSP := false
942         regVars := [MAXREG - MINREG]*regVar{
943                 REG_SP - MINREG:    {true, 0},
944                 REG_CTXT - MINREG:  {true, 1},
945                 REG_g - MINREG:     {true, 2},
946                 REG_RET0 - MINREG:  {true, 3},
947                 REG_RET1 - MINREG:  {true, 4},
948                 REG_RET2 - MINREG:  {true, 5},
949                 REG_RET3 - MINREG:  {true, 6},
950                 REG_PAUSE - MINREG: {true, 7},
951         }
952         var varDecls []*varDecl
953         useAssemblyRegMap := func() {
954                 for i := int16(0); i < 16; i++ {
955                         regVars[REG_R0+i-MINREG] = &regVar{false, uint64(i)}
956                 }
957         }
958
959         // Function starts with declaration of locals: numbers and types.
960         // Some functions use a special calling convention.
961         switch s.Name {
962         case "_rt0_wasm_js", "wasm_export_run", "wasm_export_resume", "wasm_export_getsp", "wasm_pc_f_loop",
963                 "runtime.wasmDiv", "runtime.wasmTruncS", "runtime.wasmTruncU", "memeqbody":
964                 varDecls = []*varDecl{}
965                 useAssemblyRegMap()
966         case "memchr", "memcmp":
967                 varDecls = []*varDecl{{count: 2, typ: i32}}
968                 useAssemblyRegMap()
969         case "cmpbody":
970                 varDecls = []*varDecl{{count: 2, typ: i64}}
971                 useAssemblyRegMap()
972         case "gcWriteBarrier":
973                 varDecls = []*varDecl{{count: 5, typ: i64}}
974                 useAssemblyRegMap()
975         case "runtime.gcWriteBarrier1",
976                 "runtime.gcWriteBarrier2",
977                 "runtime.gcWriteBarrier3",
978                 "runtime.gcWriteBarrier4",
979                 "runtime.gcWriteBarrier5",
980                 "runtime.gcWriteBarrier6",
981                 "runtime.gcWriteBarrier7",
982                 "runtime.gcWriteBarrier8":
983                 // no locals
984                 useAssemblyRegMap()
985         default:
986                 // Normal calling convention: PC_B as WebAssembly parameter. First local variable is local SP cache.
987                 regVars[REG_PC_B-MINREG] = &regVar{false, 0}
988                 hasLocalSP = true
989
990                 var regUsed [MAXREG - MINREG]bool
991                 for p := s.Func().Text; p != nil; p = p.Link {
992                         if p.From.Reg != 0 {
993                                 regUsed[p.From.Reg-MINREG] = true
994                         }
995                         if p.To.Reg != 0 {
996                                 regUsed[p.To.Reg-MINREG] = true
997                         }
998                 }
999
1000                 regs := []int16{REG_SP}
1001                 for reg := int16(REG_R0); reg <= REG_F31; reg++ {
1002                         if regUsed[reg-MINREG] {
1003                                 regs = append(regs, reg)
1004                         }
1005                 }
1006
1007                 var lastDecl *varDecl
1008                 for i, reg := range regs {
1009                         t := regType(reg)
1010                         if lastDecl == nil || lastDecl.typ != t {
1011                                 lastDecl = &varDecl{
1012                                         count: 0,
1013                                         typ:   t,
1014                                 }
1015                                 varDecls = append(varDecls, lastDecl)
1016                         }
1017                         lastDecl.count++
1018                         if reg != REG_SP {
1019                                 regVars[reg-MINREG] = &regVar{false, 1 + uint64(i)}
1020                         }
1021                 }
1022         }
1023
1024         w := new(bytes.Buffer)
1025
1026         writeUleb128(w, uint64(len(varDecls)))
1027         for _, decl := range varDecls {
1028                 writeUleb128(w, decl.count)
1029                 w.WriteByte(byte(decl.typ))
1030         }
1031
1032         if hasLocalSP {
1033                 // Copy SP from its global variable into a local variable. Accessing a local variable is more efficient.
1034                 updateLocalSP(w)
1035         }
1036
1037         for p := s.Func().Text; p != nil; p = p.Link {
1038                 switch p.As {
1039                 case AGet:
1040                         if p.From.Type != obj.TYPE_REG {
1041                                 panic("bad Get: argument is not a register")
1042                         }
1043                         reg := p.From.Reg
1044                         v := regVars[reg-MINREG]
1045                         if v == nil {
1046                                 panic("bad Get: invalid register")
1047                         }
1048                         if reg == REG_SP && hasLocalSP {
1049                                 writeOpcode(w, ALocalGet)
1050                                 writeUleb128(w, 1) // local SP
1051                                 continue
1052                         }
1053                         if v.global {
1054                                 writeOpcode(w, AGlobalGet)
1055                         } else {
1056                                 writeOpcode(w, ALocalGet)
1057                         }
1058                         writeUleb128(w, v.index)
1059                         continue
1060
1061                 case ASet:
1062                         if p.To.Type != obj.TYPE_REG {
1063                                 panic("bad Set: argument is not a register")
1064                         }
1065                         reg := p.To.Reg
1066                         v := regVars[reg-MINREG]
1067                         if v == nil {
1068                                 panic("bad Set: invalid register")
1069                         }
1070                         if reg == REG_SP && hasLocalSP {
1071                                 writeOpcode(w, ALocalTee)
1072                                 writeUleb128(w, 1) // local SP
1073                         }
1074                         if v.global {
1075                                 writeOpcode(w, AGlobalSet)
1076                         } else {
1077                                 if p.Link.As == AGet && p.Link.From.Reg == reg {
1078                                         writeOpcode(w, ALocalTee)
1079                                         p = p.Link
1080                                 } else {
1081                                         writeOpcode(w, ALocalSet)
1082                                 }
1083                         }
1084                         writeUleb128(w, v.index)
1085                         continue
1086
1087                 case ATee:
1088                         if p.To.Type != obj.TYPE_REG {
1089                                 panic("bad Tee: argument is not a register")
1090                         }
1091                         reg := p.To.Reg
1092                         v := regVars[reg-MINREG]
1093                         if v == nil {
1094                                 panic("bad Tee: invalid register")
1095                         }
1096                         writeOpcode(w, ALocalTee)
1097                         writeUleb128(w, v.index)
1098                         continue
1099
1100                 case ANot:
1101                         writeOpcode(w, AI32Eqz)
1102                         continue
1103
1104                 case obj.AUNDEF:
1105                         writeOpcode(w, AUnreachable)
1106                         continue
1107
1108                 case obj.ANOP, obj.ATEXT, obj.AFUNCDATA, obj.APCDATA:
1109                         // ignore
1110                         continue
1111                 }
1112
1113                 writeOpcode(w, p.As)
1114
1115                 switch p.As {
1116                 case ABlock, ALoop, AIf:
1117                         if p.From.Offset != 0 {
1118                                 // block type, rarely used, e.g. for code compiled with emscripten
1119                                 w.WriteByte(0x80 - byte(p.From.Offset))
1120                                 continue
1121                         }
1122                         w.WriteByte(0x40)
1123
1124                 case ABr, ABrIf:
1125                         if p.To.Type != obj.TYPE_CONST {
1126                                 panic("bad Br/BrIf")
1127                         }
1128                         writeUleb128(w, uint64(p.To.Offset))
1129
1130                 case ABrTable:
1131                         idxs := p.To.Val.([]uint64)
1132                         writeUleb128(w, uint64(len(idxs)-1))
1133                         for _, idx := range idxs {
1134                                 writeUleb128(w, idx)
1135                         }
1136
1137                 case ACall:
1138                         switch p.To.Type {
1139                         case obj.TYPE_CONST:
1140                                 writeUleb128(w, uint64(p.To.Offset))
1141
1142                         case obj.TYPE_MEM:
1143                                 if p.To.Name != obj.NAME_EXTERN && p.To.Name != obj.NAME_STATIC {
1144                                         fmt.Println(p.To)
1145                                         panic("bad name for Call")
1146                                 }
1147                                 r := obj.Addrel(s)
1148                                 r.Siz = 1 // actually variable sized
1149                                 r.Off = int32(w.Len())
1150                                 r.Type = objabi.R_CALL
1151                                 if p.Mark&WasmImport != 0 {
1152                                         r.Type = objabi.R_WASMIMPORT
1153                                 }
1154                                 r.Sym = p.To.Sym
1155                                 if hasLocalSP {
1156                                         // The stack may have moved, which changes SP. Update the local SP variable.
1157                                         updateLocalSP(w)
1158                                 }
1159
1160                         default:
1161                                 panic("bad type for Call")
1162                         }
1163
1164                 case ACallIndirect:
1165                         writeUleb128(w, uint64(p.To.Offset))
1166                         w.WriteByte(0x00) // reserved value
1167                         if hasLocalSP {
1168                                 // The stack may have moved, which changes SP. Update the local SP variable.
1169                                 updateLocalSP(w)
1170                         }
1171
1172                 case AI32Const, AI64Const:
1173                         if p.From.Name == obj.NAME_EXTERN {
1174                                 r := obj.Addrel(s)
1175                                 r.Siz = 1 // actually variable sized
1176                                 r.Off = int32(w.Len())
1177                                 r.Type = objabi.R_ADDR
1178                                 r.Sym = p.From.Sym
1179                                 r.Add = p.From.Offset
1180                                 break
1181                         }
1182                         writeSleb128(w, p.From.Offset)
1183
1184                 case AF32Const:
1185                         b := make([]byte, 4)
1186                         binary.LittleEndian.PutUint32(b, math.Float32bits(float32(p.From.Val.(float64))))
1187                         w.Write(b)
1188
1189                 case AF64Const:
1190                         b := make([]byte, 8)
1191                         binary.LittleEndian.PutUint64(b, math.Float64bits(p.From.Val.(float64)))
1192                         w.Write(b)
1193
1194                 case AI32Load, AI64Load, AF32Load, AF64Load, AI32Load8S, AI32Load8U, AI32Load16S, AI32Load16U, AI64Load8S, AI64Load8U, AI64Load16S, AI64Load16U, AI64Load32S, AI64Load32U:
1195                         if p.From.Offset < 0 {
1196                                 panic("negative offset for *Load")
1197                         }
1198                         if p.From.Type != obj.TYPE_CONST {
1199                                 panic("bad type for *Load")
1200                         }
1201                         if p.From.Offset > math.MaxUint32 {
1202                                 ctxt.Diag("bad offset in %v", p)
1203                         }
1204                         writeUleb128(w, align(p.As))
1205                         writeUleb128(w, uint64(p.From.Offset))
1206
1207                 case AI32Store, AI64Store, AF32Store, AF64Store, AI32Store8, AI32Store16, AI64Store8, AI64Store16, AI64Store32:
1208                         if p.To.Offset < 0 {
1209                                 panic("negative offset")
1210                         }
1211                         if p.From.Offset > math.MaxUint32 {
1212                                 ctxt.Diag("bad offset in %v", p)
1213                         }
1214                         writeUleb128(w, align(p.As))
1215                         writeUleb128(w, uint64(p.To.Offset))
1216
1217                 case ACurrentMemory, AGrowMemory, AMemoryFill:
1218                         w.WriteByte(0x00)
1219
1220                 case AMemoryCopy:
1221                         w.WriteByte(0x00)
1222                         w.WriteByte(0x00)
1223
1224                 }
1225         }
1226
1227         w.WriteByte(0x0b) // end
1228
1229         s.P = w.Bytes()
1230 }
1231
1232 func updateLocalSP(w *bytes.Buffer) {
1233         writeOpcode(w, AGlobalGet)
1234         writeUleb128(w, 0) // global SP
1235         writeOpcode(w, ALocalSet)
1236         writeUleb128(w, 1) // local SP
1237 }
1238
1239 func writeOpcode(w *bytes.Buffer, as obj.As) {
1240         switch {
1241         case as < AUnreachable:
1242                 panic(fmt.Sprintf("unexpected assembler op: %s", as))
1243         case as < AEnd:
1244                 w.WriteByte(byte(as - AUnreachable + 0x00))
1245         case as < ADrop:
1246                 w.WriteByte(byte(as - AEnd + 0x0B))
1247         case as < ALocalGet:
1248                 w.WriteByte(byte(as - ADrop + 0x1A))
1249         case as < AI32Load:
1250                 w.WriteByte(byte(as - ALocalGet + 0x20))
1251         case as < AI32TruncSatF32S:
1252                 w.WriteByte(byte(as - AI32Load + 0x28))
1253         case as < ALast:
1254                 w.WriteByte(0xFC)
1255                 w.WriteByte(byte(as - AI32TruncSatF32S + 0x00))
1256         default:
1257                 panic(fmt.Sprintf("unexpected assembler op: %s", as))
1258         }
1259 }
1260
1261 type valueType byte
1262
1263 const (
1264         i32 valueType = 0x7F
1265         i64 valueType = 0x7E
1266         f32 valueType = 0x7D
1267         f64 valueType = 0x7C
1268 )
1269
1270 func regType(reg int16) valueType {
1271         switch {
1272         case reg == REG_SP:
1273                 return i32
1274         case reg >= REG_R0 && reg <= REG_R15:
1275                 return i64
1276         case reg >= REG_F0 && reg <= REG_F15:
1277                 return f32
1278         case reg >= REG_F16 && reg <= REG_F31:
1279                 return f64
1280         default:
1281                 panic("invalid register")
1282         }
1283 }
1284
1285 func align(as obj.As) uint64 {
1286         switch as {
1287         case AI32Load8S, AI32Load8U, AI64Load8S, AI64Load8U, AI32Store8, AI64Store8:
1288                 return 0
1289         case AI32Load16S, AI32Load16U, AI64Load16S, AI64Load16U, AI32Store16, AI64Store16:
1290                 return 1
1291         case AI32Load, AF32Load, AI64Load32S, AI64Load32U, AI32Store, AF32Store, AI64Store32:
1292                 return 2
1293         case AI64Load, AF64Load, AI64Store, AF64Store:
1294                 return 3
1295         default:
1296                 panic("align: bad op")
1297         }
1298 }
1299
1300 func writeUleb128(w io.ByteWriter, v uint64) {
1301         if v < 128 {
1302                 w.WriteByte(uint8(v))
1303                 return
1304         }
1305         more := true
1306         for more {
1307                 c := uint8(v & 0x7f)
1308                 v >>= 7
1309                 more = v != 0
1310                 if more {
1311                         c |= 0x80
1312                 }
1313                 w.WriteByte(c)
1314         }
1315 }
1316
1317 func writeSleb128(w io.ByteWriter, v int64) {
1318         more := true
1319         for more {
1320                 c := uint8(v & 0x7f)
1321                 s := uint8(v & 0x40)
1322                 v >>= 7
1323                 more = !((v == 0 && s == 0) || (v == -1 && s != 0))
1324                 if more {
1325                         c |= 0x80
1326                 }
1327                 w.WriteByte(c)
1328         }
1329 }