]> Cypherpunks.ru repositories - gostls13.git/blob - src/cmd/link/internal/wasm/asm.go
01c500ed70e6dc3e0baed4176e4dab2073865ede
[gostls13.git] / src / cmd / link / internal / wasm / asm.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/objabi"
10         "cmd/link/internal/ld"
11         "cmd/link/internal/loader"
12         "cmd/link/internal/sym"
13         "internal/buildcfg"
14         "io"
15         "regexp"
16 )
17
18 const (
19         I32 = 0x7F
20         I64 = 0x7E
21         F32 = 0x7D
22         F64 = 0x7C
23 )
24
25 const (
26         sectionCustom   = 0
27         sectionType     = 1
28         sectionImport   = 2
29         sectionFunction = 3
30         sectionTable    = 4
31         sectionMemory   = 5
32         sectionGlobal   = 6
33         sectionExport   = 7
34         sectionStart    = 8
35         sectionElement  = 9
36         sectionCode     = 10
37         sectionData     = 11
38 )
39
40 // funcValueOffset is the offset between the PC_F value of a function and the index of the function in WebAssembly
41 const funcValueOffset = 0x1000 // TODO(neelance): make function addresses play nice with heap addresses
42
43 func gentext(ctxt *ld.Link, ldr *loader.Loader) {
44 }
45
46 type wasmFunc struct {
47         Name string
48         Type uint32
49         Code []byte
50 }
51
52 type wasmFuncType struct {
53         Params  []byte
54         Results []byte
55 }
56
57 var wasmFuncTypes = map[string]*wasmFuncType{
58         "_rt0_wasm_js":           {Params: []byte{}},                                         //
59         "wasm_export_run":        {Params: []byte{I32, I32}},                                 // argc, argv
60         "wasm_export_resume":     {Params: []byte{}},                                         //
61         "wasm_export_getsp":      {Results: []byte{I32}},                                     // sp
62         "wasm_pc_f_loop":         {Params: []byte{}},                                         //
63         "runtime.wasmMove":       {Params: []byte{I32, I32, I32}},                            // dst, src, len
64         "runtime.wasmZero":       {Params: []byte{I32, I32}},                                 // ptr, len
65         "runtime.wasmDiv":        {Params: []byte{I64, I64}, Results: []byte{I64}},           // x, y -> x/y
66         "runtime.wasmTruncS":     {Params: []byte{F64}, Results: []byte{I64}},                // x -> int(x)
67         "runtime.wasmTruncU":     {Params: []byte{F64}, Results: []byte{I64}},                // x -> uint(x)
68         "runtime.gcWriteBarrier": {Params: []byte{I64, I64}},                                 // ptr, val
69         "cmpbody":                {Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a, alen, b, blen -> -1/0/1
70         "memeqbody":              {Params: []byte{I64, I64, I64}, Results: []byte{I64}},      // a, b, len -> 0/1
71         "memcmp":                 {Params: []byte{I32, I32, I32}, Results: []byte{I32}},      // a, b, len -> <0/0/>0
72         "memchr":                 {Params: []byte{I32, I32, I32}, Results: []byte{I32}},      // s, c, len -> index
73 }
74
75 func assignAddress(ldr *loader.Loader, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64) {
76         // WebAssembly functions do not live in the same address space as the linear memory.
77         // Instead, WebAssembly automatically assigns indices. Imported functions (section "import")
78         // have indices 0 to n. They are followed by native functions (sections "function" and "code")
79         // with indices n+1 and following.
80         //
81         // The following rules describe how wasm handles function indices and addresses:
82         //   PC_F = funcValueOffset + WebAssembly function index (not including the imports)
83         //   s.Value = PC = PC_F<<16 + PC_B
84         //
85         // The funcValueOffset is necessary to avoid conflicts with expectations
86         // that the Go runtime has about function addresses.
87         // The field "s.Value" corresponds to the concept of PC at runtime.
88         // However, there is no PC register, only PC_F and PC_B. PC_F denotes the function,
89         // PC_B the resume point inside of that function. The entry of the function has PC_B = 0.
90         ldr.SetSymSect(s, sect)
91         ldr.SetSymValue(s, int64(funcValueOffset+va/ld.MINFUNC)<<16) // va starts at zero
92         va += uint64(ld.MINFUNC)
93         return sect, n, va
94 }
95
96 type wasmDataSect struct {
97         sect *sym.Section
98         data []byte
99 }
100
101 var dataSects []wasmDataSect
102
103 func asmb(ctxt *ld.Link, ldr *loader.Loader) {
104         sections := []*sym.Section{
105                 ldr.SymSect(ldr.Lookup("runtime.rodata", 0)),
106                 ldr.SymSect(ldr.Lookup("runtime.typelink", 0)),
107                 ldr.SymSect(ldr.Lookup("runtime.itablink", 0)),
108                 ldr.SymSect(ldr.Lookup("runtime.symtab", 0)),
109                 ldr.SymSect(ldr.Lookup("runtime.pclntab", 0)),
110                 ldr.SymSect(ldr.Lookup("runtime.noptrdata", 0)),
111                 ldr.SymSect(ldr.Lookup("runtime.data", 0)),
112         }
113
114         dataSects = make([]wasmDataSect, len(sections))
115         for i, sect := range sections {
116                 data := ld.DatblkBytes(ctxt, int64(sect.Vaddr), int64(sect.Length))
117                 dataSects[i] = wasmDataSect{sect, data}
118         }
119 }
120
121 // asmb writes the final WebAssembly module binary.
122 // Spec: https://webassembly.github.io/spec/core/binary/modules.html
123 func asmb2(ctxt *ld.Link, ldr *loader.Loader) {
124         types := []*wasmFuncType{
125                 // For normal Go functions, the single parameter is PC_B,
126                 // the return value is
127                 // 0 if the function returned normally or
128                 // 1 if the stack needs to be unwound.
129                 {Params: []byte{I32}, Results: []byte{I32}},
130         }
131
132         // collect host imports (functions that get imported from the WebAssembly host, usually JavaScript)
133         hostImports := []*wasmFunc{
134                 {
135                         Name: "debug",
136                         Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
137                 },
138         }
139         hostImportMap := make(map[loader.Sym]int64)
140         for _, fn := range ctxt.Textp {
141                 relocs := ldr.Relocs(fn)
142                 for ri := 0; ri < relocs.Count(); ri++ {
143                         r := relocs.At(ri)
144                         if r.Type() == objabi.R_WASMIMPORT {
145                                 hostImportMap[r.Sym()] = int64(len(hostImports))
146                                 hostImports = append(hostImports, &wasmFunc{
147                                         Name: ldr.SymName(r.Sym()),
148                                         Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
149                                 })
150                         }
151                 }
152         }
153
154         // collect functions with WebAssembly body
155         var buildid []byte
156         fns := make([]*wasmFunc, len(ctxt.Textp))
157         for i, fn := range ctxt.Textp {
158                 wfn := new(bytes.Buffer)
159                 if ldr.SymName(fn) == "go.buildid" {
160                         writeUleb128(wfn, 0) // number of sets of locals
161                         writeI32Const(wfn, 0)
162                         wfn.WriteByte(0x0b) // end
163                         buildid = ldr.Data(fn)
164                 } else {
165                         // Relocations have variable length, handle them here.
166                         relocs := ldr.Relocs(fn)
167                         P := ldr.Data(fn)
168                         off := int32(0)
169                         for ri := 0; ri < relocs.Count(); ri++ {
170                                 r := relocs.At(ri)
171                                 if r.Siz() == 0 {
172                                         continue // skip marker relocations
173                                 }
174                                 wfn.Write(P[off:r.Off()])
175                                 off = r.Off()
176                                 rs := r.Sym()
177                                 switch r.Type() {
178                                 case objabi.R_ADDR:
179                                         writeSleb128(wfn, ldr.SymValue(rs)+r.Add())
180                                 case objabi.R_CALL:
181                                         writeSleb128(wfn, int64(len(hostImports))+ldr.SymValue(rs)>>16-funcValueOffset)
182                                 case objabi.R_WASMIMPORT:
183                                         writeSleb128(wfn, hostImportMap[rs])
184                                 default:
185                                         ldr.Errorf(fn, "bad reloc type %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type()))
186                                         continue
187                                 }
188                         }
189                         wfn.Write(P[off:])
190                 }
191
192                 typ := uint32(0)
193                 if sig, ok := wasmFuncTypes[ldr.SymName(fn)]; ok {
194                         typ = lookupType(sig, &types)
195                 }
196
197                 name := nameRegexp.ReplaceAllString(ldr.SymName(fn), "_")
198                 fns[i] = &wasmFunc{Name: name, Type: typ, Code: wfn.Bytes()}
199         }
200
201         ctxt.Out.Write([]byte{0x00, 0x61, 0x73, 0x6d}) // magic
202         ctxt.Out.Write([]byte{0x01, 0x00, 0x00, 0x00}) // version
203
204         // Add any buildid early in the binary:
205         if len(buildid) != 0 {
206                 writeBuildID(ctxt, buildid)
207         }
208
209         writeTypeSec(ctxt, types)
210         writeImportSec(ctxt, hostImports)
211         writeFunctionSec(ctxt, fns)
212         writeTableSec(ctxt, fns)
213         writeMemorySec(ctxt, ldr)
214         writeGlobalSec(ctxt)
215         writeExportSec(ctxt, ldr, len(hostImports))
216         writeElementSec(ctxt, uint64(len(hostImports)), uint64(len(fns)))
217         writeCodeSec(ctxt, fns)
218         writeDataSec(ctxt)
219         writeProducerSec(ctxt)
220         if !*ld.FlagS {
221                 writeNameSec(ctxt, len(hostImports), fns)
222         }
223 }
224
225 func lookupType(sig *wasmFuncType, types *[]*wasmFuncType) uint32 {
226         for i, t := range *types {
227                 if bytes.Equal(sig.Params, t.Params) && bytes.Equal(sig.Results, t.Results) {
228                         return uint32(i)
229                 }
230         }
231         *types = append(*types, sig)
232         return uint32(len(*types) - 1)
233 }
234
235 func writeSecHeader(ctxt *ld.Link, id uint8) int64 {
236         ctxt.Out.WriteByte(id)
237         sizeOffset := ctxt.Out.Offset()
238         ctxt.Out.Write(make([]byte, 5)) // placeholder for length
239         return sizeOffset
240 }
241
242 func writeSecSize(ctxt *ld.Link, sizeOffset int64) {
243         endOffset := ctxt.Out.Offset()
244         ctxt.Out.SeekSet(sizeOffset)
245         writeUleb128FixedLength(ctxt.Out, uint64(endOffset-sizeOffset-5), 5)
246         ctxt.Out.SeekSet(endOffset)
247 }
248
249 func writeBuildID(ctxt *ld.Link, buildid []byte) {
250         sizeOffset := writeSecHeader(ctxt, sectionCustom)
251         writeName(ctxt.Out, "go.buildid")
252         ctxt.Out.Write(buildid)
253         writeSecSize(ctxt, sizeOffset)
254 }
255
256 // writeTypeSec writes the section that declares all function types
257 // so they can be referenced by index.
258 func writeTypeSec(ctxt *ld.Link, types []*wasmFuncType) {
259         sizeOffset := writeSecHeader(ctxt, sectionType)
260
261         writeUleb128(ctxt.Out, uint64(len(types)))
262
263         for _, t := range types {
264                 ctxt.Out.WriteByte(0x60) // functype
265                 writeUleb128(ctxt.Out, uint64(len(t.Params)))
266                 for _, v := range t.Params {
267                         ctxt.Out.WriteByte(byte(v))
268                 }
269                 writeUleb128(ctxt.Out, uint64(len(t.Results)))
270                 for _, v := range t.Results {
271                         ctxt.Out.WriteByte(byte(v))
272                 }
273         }
274
275         writeSecSize(ctxt, sizeOffset)
276 }
277
278 // writeImportSec writes the section that lists the functions that get
279 // imported from the WebAssembly host, usually JavaScript.
280 func writeImportSec(ctxt *ld.Link, hostImports []*wasmFunc) {
281         sizeOffset := writeSecHeader(ctxt, sectionImport)
282
283         writeUleb128(ctxt.Out, uint64(len(hostImports))) // number of imports
284         for _, fn := range hostImports {
285                 writeName(ctxt.Out, "go") // provided by the import object in wasm_exec.js
286                 writeName(ctxt.Out, fn.Name)
287                 ctxt.Out.WriteByte(0x00) // func import
288                 writeUleb128(ctxt.Out, uint64(fn.Type))
289         }
290
291         writeSecSize(ctxt, sizeOffset)
292 }
293
294 // writeFunctionSec writes the section that declares the types of functions.
295 // The bodies of these functions will later be provided in the "code" section.
296 func writeFunctionSec(ctxt *ld.Link, fns []*wasmFunc) {
297         sizeOffset := writeSecHeader(ctxt, sectionFunction)
298
299         writeUleb128(ctxt.Out, uint64(len(fns)))
300         for _, fn := range fns {
301                 writeUleb128(ctxt.Out, uint64(fn.Type))
302         }
303
304         writeSecSize(ctxt, sizeOffset)
305 }
306
307 // writeTableSec writes the section that declares tables. Currently there is only a single table
308 // that is used by the CallIndirect operation to dynamically call any function.
309 // The contents of the table get initialized by the "element" section.
310 func writeTableSec(ctxt *ld.Link, fns []*wasmFunc) {
311         sizeOffset := writeSecHeader(ctxt, sectionTable)
312
313         numElements := uint64(funcValueOffset + len(fns))
314         writeUleb128(ctxt.Out, 1)           // number of tables
315         ctxt.Out.WriteByte(0x70)            // type: anyfunc
316         ctxt.Out.WriteByte(0x00)            // no max
317         writeUleb128(ctxt.Out, numElements) // min
318
319         writeSecSize(ctxt, sizeOffset)
320 }
321
322 // writeMemorySec writes the section that declares linear memories. Currently one linear memory is being used.
323 // Linear memory always starts at address zero. More memory can be requested with the GrowMemory instruction.
324 func writeMemorySec(ctxt *ld.Link, ldr *loader.Loader) {
325         sizeOffset := writeSecHeader(ctxt, sectionMemory)
326
327         dataSection := ldr.SymSect(ldr.Lookup("runtime.data", 0))
328         dataEnd := dataSection.Vaddr + dataSection.Length
329         var initialSize = dataEnd + 16<<20 // 16MB, enough for runtime init without growing
330
331         const wasmPageSize = 64 << 10 // 64KB
332
333         writeUleb128(ctxt.Out, 1)                        // number of memories
334         ctxt.Out.WriteByte(0x00)                         // no maximum memory size
335         writeUleb128(ctxt.Out, initialSize/wasmPageSize) // minimum (initial) memory size
336
337         writeSecSize(ctxt, sizeOffset)
338 }
339
340 // writeGlobalSec writes the section that declares global variables.
341 func writeGlobalSec(ctxt *ld.Link) {
342         sizeOffset := writeSecHeader(ctxt, sectionGlobal)
343
344         globalRegs := []byte{
345                 I32, // 0: SP
346                 I64, // 1: CTXT
347                 I64, // 2: g
348                 I64, // 3: RET0
349                 I64, // 4: RET1
350                 I64, // 5: RET2
351                 I64, // 6: RET3
352                 I32, // 7: PAUSE
353         }
354
355         writeUleb128(ctxt.Out, uint64(len(globalRegs))) // number of globals
356
357         for _, typ := range globalRegs {
358                 ctxt.Out.WriteByte(typ)
359                 ctxt.Out.WriteByte(0x01) // var
360                 switch typ {
361                 case I32:
362                         writeI32Const(ctxt.Out, 0)
363                 case I64:
364                         writeI64Const(ctxt.Out, 0)
365                 }
366                 ctxt.Out.WriteByte(0x0b) // end
367         }
368
369         writeSecSize(ctxt, sizeOffset)
370 }
371
372 // writeExportSec writes the section that declares exports.
373 // Exports can be accessed by the WebAssembly host, usually JavaScript.
374 // The wasm_export_* functions and the linear memory get exported.
375 func writeExportSec(ctxt *ld.Link, ldr *loader.Loader, lenHostImports int) {
376         sizeOffset := writeSecHeader(ctxt, sectionExport)
377
378         writeUleb128(ctxt.Out, 4) // number of exports
379
380         for _, name := range []string{"run", "resume", "getsp"} {
381                 s := ldr.Lookup("wasm_export_"+name, 0)
382                 idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset
383                 writeName(ctxt.Out, name)           // inst.exports.run/resume/getsp in wasm_exec.js
384                 ctxt.Out.WriteByte(0x00)            // func export
385                 writeUleb128(ctxt.Out, uint64(idx)) // funcidx
386         }
387
388         writeName(ctxt.Out, "mem") // inst.exports.mem in wasm_exec.js
389         ctxt.Out.WriteByte(0x02)   // mem export
390         writeUleb128(ctxt.Out, 0)  // memidx
391
392         writeSecSize(ctxt, sizeOffset)
393 }
394
395 // writeElementSec writes the section that initializes the tables declared by the "table" section.
396 // The table for CallIndirect gets initialized in a very simple way so that each table index (PC_F value)
397 // maps linearly to the function index (numImports + PC_F).
398 func writeElementSec(ctxt *ld.Link, numImports, numFns uint64) {
399         sizeOffset := writeSecHeader(ctxt, sectionElement)
400
401         writeUleb128(ctxt.Out, 1) // number of element segments
402
403         writeUleb128(ctxt.Out, 0) // tableidx
404         writeI32Const(ctxt.Out, funcValueOffset)
405         ctxt.Out.WriteByte(0x0b) // end
406
407         writeUleb128(ctxt.Out, numFns) // number of entries
408         for i := uint64(0); i < numFns; i++ {
409                 writeUleb128(ctxt.Out, numImports+i)
410         }
411
412         writeSecSize(ctxt, sizeOffset)
413 }
414
415 // writeElementSec writes the section that provides the function bodies for the functions
416 // declared by the "func" section.
417 func writeCodeSec(ctxt *ld.Link, fns []*wasmFunc) {
418         sizeOffset := writeSecHeader(ctxt, sectionCode)
419
420         writeUleb128(ctxt.Out, uint64(len(fns))) // number of code entries
421         for _, fn := range fns {
422                 writeUleb128(ctxt.Out, uint64(len(fn.Code)))
423                 ctxt.Out.Write(fn.Code)
424         }
425
426         writeSecSize(ctxt, sizeOffset)
427 }
428
429 // writeDataSec writes the section that provides data that will be used to initialize the linear memory.
430 func writeDataSec(ctxt *ld.Link) {
431         sizeOffset := writeSecHeader(ctxt, sectionData)
432
433         type dataSegment struct {
434                 offset int32
435                 data   []byte
436         }
437
438         // Omit blocks of zeroes and instead emit data segments with offsets skipping the zeroes.
439         // This reduces the size of the WebAssembly binary. We use 8 bytes as an estimate for the
440         // overhead of adding a new segment (same as wasm-opt's memory-packing optimization uses).
441         const segmentOverhead = 8
442
443         // Generate at most this many segments. A higher number of segments gets rejected by some WebAssembly runtimes.
444         const maxNumSegments = 100000
445
446         var segments []*dataSegment
447         for secIndex, ds := range dataSects {
448                 data := ds.data
449                 offset := int32(ds.sect.Vaddr)
450
451                 // skip leading zeroes
452                 for len(data) > 0 && data[0] == 0 {
453                         data = data[1:]
454                         offset++
455                 }
456
457                 for len(data) > 0 {
458                         dataLen := int32(len(data))
459                         var segmentEnd, zeroEnd int32
460                         if len(segments)+(len(dataSects)-secIndex) == maxNumSegments {
461                                 segmentEnd = dataLen
462                                 zeroEnd = dataLen
463                         } else {
464                                 for {
465                                         // look for beginning of zeroes
466                                         for segmentEnd < dataLen && data[segmentEnd] != 0 {
467                                                 segmentEnd++
468                                         }
469                                         // look for end of zeroes
470                                         zeroEnd = segmentEnd
471                                         for zeroEnd < dataLen && data[zeroEnd] == 0 {
472                                                 zeroEnd++
473                                         }
474                                         // emit segment if omitting zeroes reduces the output size
475                                         if zeroEnd-segmentEnd >= segmentOverhead || zeroEnd == dataLen {
476                                                 break
477                                         }
478                                         segmentEnd = zeroEnd
479                                 }
480                         }
481
482                         segments = append(segments, &dataSegment{
483                                 offset: offset,
484                                 data:   data[:segmentEnd],
485                         })
486                         data = data[zeroEnd:]
487                         offset += zeroEnd
488                 }
489         }
490
491         writeUleb128(ctxt.Out, uint64(len(segments))) // number of data entries
492         for _, seg := range segments {
493                 writeUleb128(ctxt.Out, 0) // memidx
494                 writeI32Const(ctxt.Out, seg.offset)
495                 ctxt.Out.WriteByte(0x0b) // end
496                 writeUleb128(ctxt.Out, uint64(len(seg.data)))
497                 ctxt.Out.Write(seg.data)
498         }
499
500         writeSecSize(ctxt, sizeOffset)
501 }
502
503 // writeProducerSec writes an optional section that reports the source language and compiler version.
504 func writeProducerSec(ctxt *ld.Link) {
505         sizeOffset := writeSecHeader(ctxt, sectionCustom)
506         writeName(ctxt.Out, "producers")
507
508         writeUleb128(ctxt.Out, 2) // number of fields
509
510         writeName(ctxt.Out, "language")       // field name
511         writeUleb128(ctxt.Out, 1)             // number of values
512         writeName(ctxt.Out, "Go")             // value: name
513         writeName(ctxt.Out, buildcfg.Version) // value: version
514
515         writeName(ctxt.Out, "processed-by")   // field name
516         writeUleb128(ctxt.Out, 1)             // number of values
517         writeName(ctxt.Out, "Go cmd/compile") // value: name
518         writeName(ctxt.Out, buildcfg.Version) // value: version
519
520         writeSecSize(ctxt, sizeOffset)
521 }
522
523 var nameRegexp = regexp.MustCompile(`[^\w\.]`)
524
525 // writeNameSec writes an optional section that assigns names to the functions declared by the "func" section.
526 // The names are only used by WebAssembly stack traces, debuggers and decompilers.
527 // TODO(neelance): add symbol table of DATA symbols
528 func writeNameSec(ctxt *ld.Link, firstFnIndex int, fns []*wasmFunc) {
529         sizeOffset := writeSecHeader(ctxt, sectionCustom)
530         writeName(ctxt.Out, "name")
531
532         sizeOffset2 := writeSecHeader(ctxt, 0x01) // function names
533         writeUleb128(ctxt.Out, uint64(len(fns)))
534         for i, fn := range fns {
535                 writeUleb128(ctxt.Out, uint64(firstFnIndex+i))
536                 writeName(ctxt.Out, fn.Name)
537         }
538         writeSecSize(ctxt, sizeOffset2)
539
540         writeSecSize(ctxt, sizeOffset)
541 }
542
543 type nameWriter interface {
544         io.ByteWriter
545         io.Writer
546 }
547
548 func writeI32Const(w io.ByteWriter, v int32) {
549         w.WriteByte(0x41) // i32.const
550         writeSleb128(w, int64(v))
551 }
552
553 func writeI64Const(w io.ByteWriter, v int64) {
554         w.WriteByte(0x42) // i64.const
555         writeSleb128(w, v)
556 }
557
558 func writeName(w nameWriter, name string) {
559         writeUleb128(w, uint64(len(name)))
560         w.Write([]byte(name))
561 }
562
563 func writeUleb128(w io.ByteWriter, v uint64) {
564         if v < 128 {
565                 w.WriteByte(uint8(v))
566                 return
567         }
568         more := true
569         for more {
570                 c := uint8(v & 0x7f)
571                 v >>= 7
572                 more = v != 0
573                 if more {
574                         c |= 0x80
575                 }
576                 w.WriteByte(c)
577         }
578 }
579
580 func writeUleb128FixedLength(w io.ByteWriter, v uint64, length int) {
581         for i := 0; i < length; i++ {
582                 c := uint8(v & 0x7f)
583                 v >>= 7
584                 if i < length-1 {
585                         c |= 0x80
586                 }
587                 w.WriteByte(c)
588         }
589         if v != 0 {
590                 panic("writeUleb128FixedLength: length too small")
591         }
592 }
593
594 func writeSleb128(w io.ByteWriter, v int64) {
595         more := true
596         for more {
597                 c := uint8(v & 0x7f)
598                 s := uint8(v & 0x40)
599                 v >>= 7
600                 more = !((v == 0 && s == 0) || (v == -1 && s != 0))
601                 if more {
602                         c |= 0x80
603                 }
604                 w.WriteByte(c)
605         }
606 }