diff options
Diffstat (limited to 'src/cmd/link/internal/wasm/asm.go')
-rw-r--r-- | src/cmd/link/internal/wasm/asm.go | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/src/cmd/link/internal/wasm/asm.go b/src/cmd/link/internal/wasm/asm.go new file mode 100644 index 0000000..99018c8 --- /dev/null +++ b/src/cmd/link/internal/wasm/asm.go @@ -0,0 +1,604 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package wasm + +import ( + "bytes" + "cmd/internal/objabi" + "cmd/link/internal/ld" + "cmd/link/internal/loader" + "cmd/link/internal/sym" + "internal/buildcfg" + "io" + "regexp" +) + +const ( + I32 = 0x7F + I64 = 0x7E + F32 = 0x7D + F64 = 0x7C +) + +const ( + sectionCustom = 0 + sectionType = 1 + sectionImport = 2 + sectionFunction = 3 + sectionTable = 4 + sectionMemory = 5 + sectionGlobal = 6 + sectionExport = 7 + sectionStart = 8 + sectionElement = 9 + sectionCode = 10 + sectionData = 11 +) + +// funcValueOffset is the offset between the PC_F value of a function and the index of the function in WebAssembly +const funcValueOffset = 0x1000 // TODO(neelance): make function addresses play nice with heap addresses + +func gentext(ctxt *ld.Link, ldr *loader.Loader) { +} + +type wasmFunc struct { + Name string + Type uint32 + Code []byte +} + +type wasmFuncType struct { + Params []byte + Results []byte +} + +var wasmFuncTypes = map[string]*wasmFuncType{ + "_rt0_wasm_js": {Params: []byte{}}, // + "wasm_export_run": {Params: []byte{I32, I32}}, // argc, argv + "wasm_export_resume": {Params: []byte{}}, // + "wasm_export_getsp": {Results: []byte{I32}}, // sp + "wasm_pc_f_loop": {Params: []byte{}}, // + "runtime.wasmDiv": {Params: []byte{I64, I64}, Results: []byte{I64}}, // x, y -> x/y + "runtime.wasmTruncS": {Params: []byte{F64}, Results: []byte{I64}}, // x -> int(x) + "runtime.wasmTruncU": {Params: []byte{F64}, Results: []byte{I64}}, // x -> uint(x) + "runtime.gcWriteBarrier": {Params: []byte{I64, I64}}, // ptr, val + "cmpbody": {Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a, alen, b, blen -> -1/0/1 + "memeqbody": {Params: []byte{I64, I64, I64}, Results: []byte{I64}}, // a, b, len -> 0/1 + "memcmp": {Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // a, b, len -> <0/0/>0 + "memchr": {Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // s, c, len -> index +} + +func assignAddress(ldr *loader.Loader, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64) { + // WebAssembly functions do not live in the same address space as the linear memory. + // Instead, WebAssembly automatically assigns indices. Imported functions (section "import") + // have indices 0 to n. They are followed by native functions (sections "function" and "code") + // with indices n+1 and following. + // + // The following rules describe how wasm handles function indices and addresses: + // PC_F = funcValueOffset + WebAssembly function index (not including the imports) + // s.Value = PC = PC_F<<16 + PC_B + // + // The funcValueOffset is necessary to avoid conflicts with expectations + // that the Go runtime has about function addresses. + // The field "s.Value" corresponds to the concept of PC at runtime. + // However, there is no PC register, only PC_F and PC_B. PC_F denotes the function, + // PC_B the resume point inside of that function. The entry of the function has PC_B = 0. + ldr.SetSymSect(s, sect) + ldr.SetSymValue(s, int64(funcValueOffset+va/ld.MINFUNC)<<16) // va starts at zero + va += uint64(ld.MINFUNC) + return sect, n, va +} + +type wasmDataSect struct { + sect *sym.Section + data []byte +} + +var dataSects []wasmDataSect + +func asmb(ctxt *ld.Link, ldr *loader.Loader) { + sections := []*sym.Section{ + ldr.SymSect(ldr.Lookup("runtime.rodata", 0)), + ldr.SymSect(ldr.Lookup("runtime.typelink", 0)), + ldr.SymSect(ldr.Lookup("runtime.itablink", 0)), + ldr.SymSect(ldr.Lookup("runtime.symtab", 0)), + ldr.SymSect(ldr.Lookup("runtime.pclntab", 0)), + ldr.SymSect(ldr.Lookup("runtime.noptrdata", 0)), + ldr.SymSect(ldr.Lookup("runtime.data", 0)), + } + + dataSects = make([]wasmDataSect, len(sections)) + for i, sect := range sections { + data := ld.DatblkBytes(ctxt, int64(sect.Vaddr), int64(sect.Length)) + dataSects[i] = wasmDataSect{sect, data} + } +} + +// asmb writes the final WebAssembly module binary. +// Spec: https://webassembly.github.io/spec/core/binary/modules.html +func asmb2(ctxt *ld.Link, ldr *loader.Loader) { + types := []*wasmFuncType{ + // For normal Go functions, the single parameter is PC_B, + // the return value is + // 0 if the function returned normally or + // 1 if the stack needs to be unwound. + {Params: []byte{I32}, Results: []byte{I32}}, + } + + // collect host imports (functions that get imported from the WebAssembly host, usually JavaScript) + hostImports := []*wasmFunc{ + { + Name: "debug", + Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types), + }, + } + hostImportMap := make(map[loader.Sym]int64) + for _, fn := range ctxt.Textp { + relocs := ldr.Relocs(fn) + for ri := 0; ri < relocs.Count(); ri++ { + r := relocs.At(ri) + if r.Type() == objabi.R_WASMIMPORT { + hostImportMap[r.Sym()] = int64(len(hostImports)) + hostImports = append(hostImports, &wasmFunc{ + Name: ldr.SymName(r.Sym()), + Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types), + }) + } + } + } + + // collect functions with WebAssembly body + var buildid []byte + fns := make([]*wasmFunc, len(ctxt.Textp)) + for i, fn := range ctxt.Textp { + wfn := new(bytes.Buffer) + if ldr.SymName(fn) == "go:buildid" { + writeUleb128(wfn, 0) // number of sets of locals + writeI32Const(wfn, 0) + wfn.WriteByte(0x0b) // end + buildid = ldr.Data(fn) + } else { + // Relocations have variable length, handle them here. + relocs := ldr.Relocs(fn) + P := ldr.Data(fn) + off := int32(0) + for ri := 0; ri < relocs.Count(); ri++ { + r := relocs.At(ri) + if r.Siz() == 0 { + continue // skip marker relocations + } + wfn.Write(P[off:r.Off()]) + off = r.Off() + rs := r.Sym() + switch r.Type() { + case objabi.R_ADDR: + writeSleb128(wfn, ldr.SymValue(rs)+r.Add()) + case objabi.R_CALL: + writeSleb128(wfn, int64(len(hostImports))+ldr.SymValue(rs)>>16-funcValueOffset) + case objabi.R_WASMIMPORT: + writeSleb128(wfn, hostImportMap[rs]) + default: + ldr.Errorf(fn, "bad reloc type %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type())) + continue + } + } + wfn.Write(P[off:]) + } + + typ := uint32(0) + if sig, ok := wasmFuncTypes[ldr.SymName(fn)]; ok { + typ = lookupType(sig, &types) + } + + name := nameRegexp.ReplaceAllString(ldr.SymName(fn), "_") + fns[i] = &wasmFunc{Name: name, Type: typ, Code: wfn.Bytes()} + } + + ctxt.Out.Write([]byte{0x00, 0x61, 0x73, 0x6d}) // magic + ctxt.Out.Write([]byte{0x01, 0x00, 0x00, 0x00}) // version + + // Add any buildid early in the binary: + if len(buildid) != 0 { + writeBuildID(ctxt, buildid) + } + + writeTypeSec(ctxt, types) + writeImportSec(ctxt, hostImports) + writeFunctionSec(ctxt, fns) + writeTableSec(ctxt, fns) + writeMemorySec(ctxt, ldr) + writeGlobalSec(ctxt) + writeExportSec(ctxt, ldr, len(hostImports)) + writeElementSec(ctxt, uint64(len(hostImports)), uint64(len(fns))) + writeCodeSec(ctxt, fns) + writeDataSec(ctxt) + writeProducerSec(ctxt) + if !*ld.FlagS { + writeNameSec(ctxt, len(hostImports), fns) + } +} + +func lookupType(sig *wasmFuncType, types *[]*wasmFuncType) uint32 { + for i, t := range *types { + if bytes.Equal(sig.Params, t.Params) && bytes.Equal(sig.Results, t.Results) { + return uint32(i) + } + } + *types = append(*types, sig) + return uint32(len(*types) - 1) +} + +func writeSecHeader(ctxt *ld.Link, id uint8) int64 { + ctxt.Out.WriteByte(id) + sizeOffset := ctxt.Out.Offset() + ctxt.Out.Write(make([]byte, 5)) // placeholder for length + return sizeOffset +} + +func writeSecSize(ctxt *ld.Link, sizeOffset int64) { + endOffset := ctxt.Out.Offset() + ctxt.Out.SeekSet(sizeOffset) + writeUleb128FixedLength(ctxt.Out, uint64(endOffset-sizeOffset-5), 5) + ctxt.Out.SeekSet(endOffset) +} + +func writeBuildID(ctxt *ld.Link, buildid []byte) { + sizeOffset := writeSecHeader(ctxt, sectionCustom) + writeName(ctxt.Out, "go:buildid") + ctxt.Out.Write(buildid) + writeSecSize(ctxt, sizeOffset) +} + +// writeTypeSec writes the section that declares all function types +// so they can be referenced by index. +func writeTypeSec(ctxt *ld.Link, types []*wasmFuncType) { + sizeOffset := writeSecHeader(ctxt, sectionType) + + writeUleb128(ctxt.Out, uint64(len(types))) + + for _, t := range types { + ctxt.Out.WriteByte(0x60) // functype + writeUleb128(ctxt.Out, uint64(len(t.Params))) + for _, v := range t.Params { + ctxt.Out.WriteByte(byte(v)) + } + writeUleb128(ctxt.Out, uint64(len(t.Results))) + for _, v := range t.Results { + ctxt.Out.WriteByte(byte(v)) + } + } + + writeSecSize(ctxt, sizeOffset) +} + +// writeImportSec writes the section that lists the functions that get +// imported from the WebAssembly host, usually JavaScript. +func writeImportSec(ctxt *ld.Link, hostImports []*wasmFunc) { + sizeOffset := writeSecHeader(ctxt, sectionImport) + + writeUleb128(ctxt.Out, uint64(len(hostImports))) // number of imports + for _, fn := range hostImports { + writeName(ctxt.Out, "go") // provided by the import object in wasm_exec.js + writeName(ctxt.Out, fn.Name) + ctxt.Out.WriteByte(0x00) // func import + writeUleb128(ctxt.Out, uint64(fn.Type)) + } + + writeSecSize(ctxt, sizeOffset) +} + +// writeFunctionSec writes the section that declares the types of functions. +// The bodies of these functions will later be provided in the "code" section. +func writeFunctionSec(ctxt *ld.Link, fns []*wasmFunc) { + sizeOffset := writeSecHeader(ctxt, sectionFunction) + + writeUleb128(ctxt.Out, uint64(len(fns))) + for _, fn := range fns { + writeUleb128(ctxt.Out, uint64(fn.Type)) + } + + writeSecSize(ctxt, sizeOffset) +} + +// writeTableSec writes the section that declares tables. Currently there is only a single table +// that is used by the CallIndirect operation to dynamically call any function. +// The contents of the table get initialized by the "element" section. +func writeTableSec(ctxt *ld.Link, fns []*wasmFunc) { + sizeOffset := writeSecHeader(ctxt, sectionTable) + + numElements := uint64(funcValueOffset + len(fns)) + writeUleb128(ctxt.Out, 1) // number of tables + ctxt.Out.WriteByte(0x70) // type: anyfunc + ctxt.Out.WriteByte(0x00) // no max + writeUleb128(ctxt.Out, numElements) // min + + writeSecSize(ctxt, sizeOffset) +} + +// writeMemorySec writes the section that declares linear memories. Currently one linear memory is being used. +// Linear memory always starts at address zero. More memory can be requested with the GrowMemory instruction. +func writeMemorySec(ctxt *ld.Link, ldr *loader.Loader) { + sizeOffset := writeSecHeader(ctxt, sectionMemory) + + dataSection := ldr.SymSect(ldr.Lookup("runtime.data", 0)) + dataEnd := dataSection.Vaddr + dataSection.Length + var initialSize = dataEnd + 16<<20 // 16MB, enough for runtime init without growing + + const wasmPageSize = 64 << 10 // 64KB + + writeUleb128(ctxt.Out, 1) // number of memories + ctxt.Out.WriteByte(0x00) // no maximum memory size + writeUleb128(ctxt.Out, initialSize/wasmPageSize) // minimum (initial) memory size + + writeSecSize(ctxt, sizeOffset) +} + +// writeGlobalSec writes the section that declares global variables. +func writeGlobalSec(ctxt *ld.Link) { + sizeOffset := writeSecHeader(ctxt, sectionGlobal) + + globalRegs := []byte{ + I32, // 0: SP + I64, // 1: CTXT + I64, // 2: g + I64, // 3: RET0 + I64, // 4: RET1 + I64, // 5: RET2 + I64, // 6: RET3 + I32, // 7: PAUSE + } + + writeUleb128(ctxt.Out, uint64(len(globalRegs))) // number of globals + + for _, typ := range globalRegs { + ctxt.Out.WriteByte(typ) + ctxt.Out.WriteByte(0x01) // var + switch typ { + case I32: + writeI32Const(ctxt.Out, 0) + case I64: + writeI64Const(ctxt.Out, 0) + } + ctxt.Out.WriteByte(0x0b) // end + } + + writeSecSize(ctxt, sizeOffset) +} + +// writeExportSec writes the section that declares exports. +// Exports can be accessed by the WebAssembly host, usually JavaScript. +// The wasm_export_* functions and the linear memory get exported. +func writeExportSec(ctxt *ld.Link, ldr *loader.Loader, lenHostImports int) { + sizeOffset := writeSecHeader(ctxt, sectionExport) + + writeUleb128(ctxt.Out, 4) // number of exports + + for _, name := range []string{"run", "resume", "getsp"} { + s := ldr.Lookup("wasm_export_"+name, 0) + idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset + writeName(ctxt.Out, name) // inst.exports.run/resume/getsp in wasm_exec.js + ctxt.Out.WriteByte(0x00) // func export + writeUleb128(ctxt.Out, uint64(idx)) // funcidx + } + + writeName(ctxt.Out, "mem") // inst.exports.mem in wasm_exec.js + ctxt.Out.WriteByte(0x02) // mem export + writeUleb128(ctxt.Out, 0) // memidx + + writeSecSize(ctxt, sizeOffset) +} + +// writeElementSec writes the section that initializes the tables declared by the "table" section. +// The table for CallIndirect gets initialized in a very simple way so that each table index (PC_F value) +// maps linearly to the function index (numImports + PC_F). +func writeElementSec(ctxt *ld.Link, numImports, numFns uint64) { + sizeOffset := writeSecHeader(ctxt, sectionElement) + + writeUleb128(ctxt.Out, 1) // number of element segments + + writeUleb128(ctxt.Out, 0) // tableidx + writeI32Const(ctxt.Out, funcValueOffset) + ctxt.Out.WriteByte(0x0b) // end + + writeUleb128(ctxt.Out, numFns) // number of entries + for i := uint64(0); i < numFns; i++ { + writeUleb128(ctxt.Out, numImports+i) + } + + writeSecSize(ctxt, sizeOffset) +} + +// writeCodeSec writes the section that provides the function bodies for the functions +// declared by the "func" section. +func writeCodeSec(ctxt *ld.Link, fns []*wasmFunc) { + sizeOffset := writeSecHeader(ctxt, sectionCode) + + writeUleb128(ctxt.Out, uint64(len(fns))) // number of code entries + for _, fn := range fns { + writeUleb128(ctxt.Out, uint64(len(fn.Code))) + ctxt.Out.Write(fn.Code) + } + + writeSecSize(ctxt, sizeOffset) +} + +// writeDataSec writes the section that provides data that will be used to initialize the linear memory. +func writeDataSec(ctxt *ld.Link) { + sizeOffset := writeSecHeader(ctxt, sectionData) + + type dataSegment struct { + offset int32 + data []byte + } + + // Omit blocks of zeroes and instead emit data segments with offsets skipping the zeroes. + // This reduces the size of the WebAssembly binary. We use 8 bytes as an estimate for the + // overhead of adding a new segment (same as wasm-opt's memory-packing optimization uses). + const segmentOverhead = 8 + + // Generate at most this many segments. A higher number of segments gets rejected by some WebAssembly runtimes. + const maxNumSegments = 100000 + + var segments []*dataSegment + for secIndex, ds := range dataSects { + data := ds.data + offset := int32(ds.sect.Vaddr) + + // skip leading zeroes + for len(data) > 0 && data[0] == 0 { + data = data[1:] + offset++ + } + + for len(data) > 0 { + dataLen := int32(len(data)) + var segmentEnd, zeroEnd int32 + if len(segments)+(len(dataSects)-secIndex) == maxNumSegments { + segmentEnd = dataLen + zeroEnd = dataLen + } else { + for { + // look for beginning of zeroes + for segmentEnd < dataLen && data[segmentEnd] != 0 { + segmentEnd++ + } + // look for end of zeroes + zeroEnd = segmentEnd + for zeroEnd < dataLen && data[zeroEnd] == 0 { + zeroEnd++ + } + // emit segment if omitting zeroes reduces the output size + if zeroEnd-segmentEnd >= segmentOverhead || zeroEnd == dataLen { + break + } + segmentEnd = zeroEnd + } + } + + segments = append(segments, &dataSegment{ + offset: offset, + data: data[:segmentEnd], + }) + data = data[zeroEnd:] + offset += zeroEnd + } + } + + writeUleb128(ctxt.Out, uint64(len(segments))) // number of data entries + for _, seg := range segments { + writeUleb128(ctxt.Out, 0) // memidx + writeI32Const(ctxt.Out, seg.offset) + ctxt.Out.WriteByte(0x0b) // end + writeUleb128(ctxt.Out, uint64(len(seg.data))) + ctxt.Out.Write(seg.data) + } + + writeSecSize(ctxt, sizeOffset) +} + +// writeProducerSec writes an optional section that reports the source language and compiler version. +func writeProducerSec(ctxt *ld.Link) { + sizeOffset := writeSecHeader(ctxt, sectionCustom) + writeName(ctxt.Out, "producers") + + writeUleb128(ctxt.Out, 2) // number of fields + + writeName(ctxt.Out, "language") // field name + writeUleb128(ctxt.Out, 1) // number of values + writeName(ctxt.Out, "Go") // value: name + writeName(ctxt.Out, buildcfg.Version) // value: version + + writeName(ctxt.Out, "processed-by") // field name + writeUleb128(ctxt.Out, 1) // number of values + writeName(ctxt.Out, "Go cmd/compile") // value: name + writeName(ctxt.Out, buildcfg.Version) // value: version + + writeSecSize(ctxt, sizeOffset) +} + +var nameRegexp = regexp.MustCompile(`[^\w.]`) + +// writeNameSec writes an optional section that assigns names to the functions declared by the "func" section. +// The names are only used by WebAssembly stack traces, debuggers and decompilers. +// TODO(neelance): add symbol table of DATA symbols +func writeNameSec(ctxt *ld.Link, firstFnIndex int, fns []*wasmFunc) { + sizeOffset := writeSecHeader(ctxt, sectionCustom) + writeName(ctxt.Out, "name") + + sizeOffset2 := writeSecHeader(ctxt, 0x01) // function names + writeUleb128(ctxt.Out, uint64(len(fns))) + for i, fn := range fns { + writeUleb128(ctxt.Out, uint64(firstFnIndex+i)) + writeName(ctxt.Out, fn.Name) + } + writeSecSize(ctxt, sizeOffset2) + + writeSecSize(ctxt, sizeOffset) +} + +type nameWriter interface { + io.ByteWriter + io.Writer +} + +func writeI32Const(w io.ByteWriter, v int32) { + w.WriteByte(0x41) // i32.const + writeSleb128(w, int64(v)) +} + +func writeI64Const(w io.ByteWriter, v int64) { + w.WriteByte(0x42) // i64.const + writeSleb128(w, v) +} + +func writeName(w nameWriter, name string) { + writeUleb128(w, uint64(len(name))) + w.Write([]byte(name)) +} + +func writeUleb128(w io.ByteWriter, v uint64) { + if v < 128 { + w.WriteByte(uint8(v)) + return + } + more := true + for more { + c := uint8(v & 0x7f) + v >>= 7 + more = v != 0 + if more { + c |= 0x80 + } + w.WriteByte(c) + } +} + +func writeUleb128FixedLength(w io.ByteWriter, v uint64, length int) { + for i := 0; i < length; i++ { + c := uint8(v & 0x7f) + v >>= 7 + if i < length-1 { + c |= 0x80 + } + w.WriteByte(c) + } + if v != 0 { + panic("writeUleb128FixedLength: length too small") + } +} + +func writeSleb128(w io.ByteWriter, v int64) { + more := true + for more { + c := uint8(v & 0x7f) + s := uint8(v & 0x40) + v >>= 7 + more = !((v == 0 && s == 0) || (v == -1 && s != 0)) + if more { + c |= 0x80 + } + w.WriteByte(c) + } +} |