| // 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/sym" |
| "io" |
| "regexp" |
| "runtime" |
| ) |
| |
| 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) { |
| } |
| |
| type wasmFunc struct { |
| Name string |
| Type uint32 |
| Code []byte |
| } |
| |
| type wasmFuncType struct { |
| Params []byte |
| Results []byte |
| } |
| |
| var wasmFuncTypes = map[string]*wasmFuncType{ |
| "_rt0_wasm_js": &wasmFuncType{Params: []byte{I32, I32}}, // argc, argv |
| "runtime.wasmMove": &wasmFuncType{Params: []byte{I32, I32, I32}}, // dst, src, len |
| "runtime.wasmZero": &wasmFuncType{Params: []byte{I32, I32}}, // ptr, len |
| "runtime.wasmDiv": &wasmFuncType{Params: []byte{I64, I64}, Results: []byte{I64}}, // x, y -> x/y |
| "runtime.wasmTruncS": &wasmFuncType{Params: []byte{F64}, Results: []byte{I64}}, // x -> int(x) |
| "runtime.wasmTruncU": &wasmFuncType{Params: []byte{F64}, Results: []byte{I64}}, // x -> uint(x) |
| "runtime.gcWriteBarrier": &wasmFuncType{Params: []byte{I64, I64}}, // ptr, val |
| "cmpbody": &wasmFuncType{Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a, alen, b, blen -> -1/0/1 |
| "memeqbody": &wasmFuncType{Params: []byte{I64, I64, I64}, Results: []byte{I64}}, // a, b, len -> 0/1 |
| "memcmp": &wasmFuncType{Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // a, b, len -> <0/0/>0 |
| "memchr": &wasmFuncType{Params: []byte{I32, I32, I32}, Results: []byte{I32}}, // s, c, len -> index |
| } |
| |
| func assignAddress(ctxt *ld.Link, sect *sym.Section, n int, s *sym.Symbol, 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. |
| s.Sect = sect |
| s.Value = int64(funcValueOffset+va/ld.MINFUNC) << 16 // va starts at zero |
| va += uint64(ld.MINFUNC) |
| return sect, n, va |
| } |
| |
| // asmb writes the final WebAssembly module binary. |
| // Spec: https://webassembly.github.io/spec/core/binary/modules.html |
| func asmb(ctxt *ld.Link) { |
| if ctxt.Debugvlog != 0 { |
| ctxt.Logf("%5.2f asmb\n", ld.Cputime()) |
| } |
| |
| types := []*wasmFuncType{ |
| // For normal Go functions the return value is |
| // 0 if the function returned normally or |
| // 1 if the stack needs to be unwound. |
| &wasmFuncType{Results: []byte{I32}}, |
| } |
| |
| // collect host imports (functions that get imported from the WebAssembly host, usually JavaScript) |
| hostImports := []*wasmFunc{ |
| &wasmFunc{ |
| Name: "debug", |
| Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types), |
| }, |
| } |
| hostImportMap := make(map[*sym.Symbol]int64) |
| for _, fn := range ctxt.Textp { |
| for _, r := range fn.R { |
| if r.Type == objabi.R_WASMIMPORT { |
| hostImportMap[r.Sym] = int64(len(hostImports)) |
| hostImports = append(hostImports, &wasmFunc{ |
| Name: r.Sym.Name, |
| 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 fn.Name == "go.buildid" { |
| writeUleb128(wfn, 0) // number of sets of locals |
| writeI32Const(wfn, 0) |
| wfn.WriteByte(0x0b) // end |
| buildid = fn.P |
| } else { |
| // Relocations have variable length, handle them here. |
| off := int32(0) |
| for _, r := range fn.R { |
| wfn.Write(fn.P[off:r.Off]) |
| off = r.Off |
| switch r.Type { |
| case objabi.R_ADDR: |
| writeSleb128(wfn, r.Sym.Value+r.Add) |
| case objabi.R_CALL: |
| writeSleb128(wfn, int64(len(hostImports))+r.Sym.Value>>16-funcValueOffset) |
| case objabi.R_WASMIMPORT: |
| writeSleb128(wfn, hostImportMap[r.Sym]) |
| default: |
| ld.Errorf(fn, "bad reloc type %d (%s)", r.Type, sym.RelocName(ctxt.Arch, r.Type)) |
| continue |
| } |
| } |
| wfn.Write(fn.P[off:]) |
| } |
| |
| typ := uint32(0) |
| if sig, ok := wasmFuncTypes[fn.Name]; ok { |
| typ = lookupType(sig, &types) |
| } |
| |
| name := nameRegexp.ReplaceAllString(fn.Name, "_") |
| fns[i] = &wasmFunc{Name: name, Type: typ, Code: wfn.Bytes()} |
| } |
| |
| // look up program entry point |
| rt0 := uint32(len(hostImports)) + uint32(ctxt.Syms.ROLookup("_rt0_wasm_js", 0).Value>>16) - funcValueOffset |
| |
| 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) |
| } |
| |
| writeGoVersion(ctxt) |
| writeTypeSec(ctxt, types) |
| writeImportSec(ctxt, hostImports) |
| writeFunctionSec(ctxt, fns) |
| writeTableSec(ctxt, fns) |
| writeMemorySec(ctxt) |
| writeGlobalSec(ctxt) |
| writeExportSec(ctxt, rt0) |
| writeElementSec(ctxt, uint64(len(hostImports)), uint64(len(fns))) |
| writeCodeSec(ctxt, fns) |
| writeDataSec(ctxt) |
| if !*ld.FlagS { |
| writeNameSec(ctxt, len(hostImports), fns) |
| } |
| |
| ctxt.Out.Flush() |
| } |
| |
| 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) |
| } |
| |
| func writeGoVersion(ctxt *ld.Link) { |
| sizeOffset := writeSecHeader(ctxt, sectionCustom) |
| writeName(ctxt.Out, "go.version") |
| ctxt.Out.Write([]byte(runtime.Version())) |
| 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. |
| func writeMemorySec(ctxt *ld.Link) { |
| sizeOffset := writeSecHeader(ctxt, sectionMemory) |
| |
| // Linear memory always starts at address zero. |
| // The unit of the sizes is "WebAssembly page size", which is 64Ki. |
| // The minimum is currently set to 1GB, which is a lot. |
| // More memory can be requested with the grow_memory instruction, |
| // but this operation currently is rather slow, so we avoid it for now. |
| // TODO(neelance): Use lower initial memory size. |
| writeUleb128(ctxt.Out, 1) // number of memories |
| ctxt.Out.WriteByte(0x00) // no maximum memory size |
| writeUleb128(ctxt.Out, 1024*16) // 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: PC_F |
| I32, // 1: PC_B |
| I32, // 2: SP |
| I64, // 3: CTXT |
| I64, // 4: g |
| I64, // 5: RET0 |
| I64, // 6: RET1 |
| I64, // 7: RET2 |
| I64, // 8: RET3 |
| I32, // 9: RUN |
| } |
| |
| 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. |
| // Currently _rt0_wasm_js (program entry point) and the linear memory get exported. |
| func writeExportSec(ctxt *ld.Link, rt0 uint32) { |
| sizeOffset := writeSecHeader(ctxt, sectionExport) |
| |
| writeUleb128(ctxt.Out, 2) // number of exports |
| |
| writeName(ctxt.Out, "run") // inst.exports.run in wasm_exec.js |
| ctxt.Out.WriteByte(0x00) // func export |
| writeUleb128(ctxt.Out, uint64(rt0)) // 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) |
| } |
| |
| // writeElementSec 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) |
| |
| sections := []*sym.Section{ |
| ctxt.Syms.Lookup("runtime.rodata", 0).Sect, |
| ctxt.Syms.Lookup("runtime.typelink", 0).Sect, |
| ctxt.Syms.Lookup("runtime.itablink", 0).Sect, |
| ctxt.Syms.Lookup("runtime.symtab", 0).Sect, |
| ctxt.Syms.Lookup("runtime.pclntab", 0).Sect, |
| ctxt.Syms.Lookup("runtime.noptrdata", 0).Sect, |
| ctxt.Syms.Lookup("runtime.data", 0).Sect, |
| } |
| |
| writeUleb128(ctxt.Out, uint64(len(sections))) // number of data entries |
| |
| for _, sec := range sections { |
| writeUleb128(ctxt.Out, 0) // memidx |
| writeI32Const(ctxt.Out, int32(sec.Vaddr)) |
| ctxt.Out.WriteByte(0x0b) // end |
| writeUleb128(ctxt.Out, uint64(sec.Length)) |
| ld.Datblk(ctxt, int64(sec.Vaddr), int64(sec.Length)) |
| } |
| |
| 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) { |
| 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) |
| } |
| } |