2018-03-04 12:59:15 +01:00
|
|
|
// 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"
|
2023-01-22 15:30:59 -08:00
|
|
|
"cmd/internal/obj"
|
|
|
|
|
"cmd/internal/obj/wasm"
|
2018-03-04 12:59:15 +01:00
|
|
|
"cmd/internal/objabi"
|
|
|
|
|
"cmd/link/internal/ld"
|
2020-04-01 13:42:20 -04:00
|
|
|
"cmd/link/internal/loader"
|
2018-03-04 12:59:15 +01:00
|
|
|
"cmd/link/internal/sym"
|
2023-01-22 15:30:59 -08:00
|
|
|
"fmt"
|
2024-01-06 07:12:43 +00:00
|
|
|
"internal/abi"
|
2021-04-15 23:05:49 -04:00
|
|
|
"internal/buildcfg"
|
2018-03-04 12:59:15 +01:00
|
|
|
"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
|
|
|
|
|
|
2020-05-15 18:35:05 -04:00
|
|
|
func gentext(ctxt *ld.Link, ldr *loader.Loader) {
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type wasmFunc struct {
|
2023-01-22 15:30:59 -08:00
|
|
|
Module string
|
|
|
|
|
Name string
|
|
|
|
|
Type uint32
|
|
|
|
|
Code []byte
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type wasmFuncType struct {
|
|
|
|
|
Params []byte
|
|
|
|
|
Results []byte
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-22 15:30:59 -08:00
|
|
|
func readWasmImport(ldr *loader.Loader, s loader.Sym) obj.WasmImport {
|
|
|
|
|
var wi obj.WasmImport
|
2024-08-05 13:40:18 -04:00
|
|
|
wi.Read(ldr.Data(s))
|
2023-01-22 15:30:59 -08:00
|
|
|
return wi
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-04 12:59:15 +01:00
|
|
|
var wasmFuncTypes = map[string]*wasmFuncType{
|
2022-11-01 16:46:43 -07:00
|
|
|
"_rt0_wasm_js": {Params: []byte{}}, //
|
2023-03-24 23:07:58 -07:00
|
|
|
"_rt0_wasm_wasip1": {Params: []byte{}}, //
|
cmd/link, runtime: support library mode on wasip1
This CL adds support of "library", i.e. c-shared, build mode on
wasip1. When -buildmode=c-shared is set, it builds a Wasm module
that is intended to be used as a library, instead of an executable.
It does not have the _start function. Instead, it has an
_initialize function, which initializes the runtime, but not call
the main function.
This is similar to the c-shared build mode on other platforms. One
difference is that unlike cgo callbacks, where Ms are created on-
demand, on Wasm we have only one M, so we just keep the M (and the
G) for callbacks.
For #65199.
Change-Id: Ieb21da96b25c1a9f3989d945cddc964c26f9085b
Reviewed-on: https://go-review.googlesource.com/c/go/+/604316
Reviewed-by: Achille Roussel <achille.roussel@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2024-08-08 18:45:18 -04:00
|
|
|
"_rt0_wasm_wasip1_lib": {Params: []byte{}}, //
|
2023-03-24 23:07:58 -07:00
|
|
|
"wasm_export__start": {}, //
|
2022-11-01 16:46:43 -07:00
|
|
|
"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{}}, //
|
2024-08-07 11:52:39 -04:00
|
|
|
"wasm_pc_f_loop_export": {Params: []byte{I32}}, // pc_f
|
2022-11-01 16:46:43 -07:00
|
|
|
"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)
|
|
|
|
|
"gcWriteBarrier": {Params: []byte{I64}, Results: []byte{I64}}, // #bytes -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier1": {Results: []byte{I64}}, // -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier2": {Results: []byte{I64}}, // -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier3": {Results: []byte{I64}}, // -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier4": {Results: []byte{I64}}, // -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier5": {Results: []byte{I64}}, // -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier6": {Results: []byte{I64}}, // -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier7": {Results: []byte{I64}}, // -> bufptr
|
|
|
|
|
"runtime.gcWriteBarrier8": {Results: []byte{I64}}, // -> bufptr
|
2025-01-16 13:56:15 -05:00
|
|
|
"runtime.notInitialized": {}, //
|
2022-11-01 16:46:43 -07:00
|
|
|
"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
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-06 15:58:21 -04:00
|
|
|
func assignAddress(ldr *loader.Loader, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64) {
|
2018-03-04 12:59:15 +01:00
|
|
|
// 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.
|
2020-04-06 15:58:21 -04:00
|
|
|
ldr.SetSymSect(s, sect)
|
2024-01-06 07:12:43 +00:00
|
|
|
ldr.SetSymValue(s, int64(funcValueOffset+va/abi.MINFUNC)<<16) // va starts at zero
|
|
|
|
|
va += uint64(abi.MINFUNC)
|
2018-03-04 12:59:15 +01:00
|
|
|
return sect, n, va
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-28 09:53:29 -04:00
|
|
|
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.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}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-03 22:41:48 -04:00
|
|
|
|
2018-03-04 12:59:15 +01:00
|
|
|
// asmb writes the final WebAssembly module binary.
|
2018-06-01 17:29:59 -03:00
|
|
|
// Spec: https://webassembly.github.io/spec/core/binary/modules.html
|
2020-05-08 14:09:57 -04:00
|
|
|
func asmb2(ctxt *ld.Link, ldr *loader.Loader) {
|
2018-03-04 12:59:15 +01:00
|
|
|
types := []*wasmFuncType{
|
cmd, runtime: remove PC_F & PC_B globals on Wasm
Following the previous CL, this removes more global variables on
Wasm.
PC_B is used mostly for intra-function jumps, and for a function
telling its callee where to start or resume. This usage can be
served by a parameter. The top level loop (wasm_pc_f_loop) uses
PC_B for resuming a function. This value is either set by gogo,
or loaded from the Go stack at function return. Instead of
loading PC_B at each function return, we could make gogo stores
PC_B at the same stack location, and let the top level loop do
the load. This way, we don't need to use global PC_B to
communicate with the top level loop, and we can replace global
PC_B with a parameter.
PC_F is similar. It is even more so in that the only reader is
the top level loop. Let the top level loop read it from the stack,
and we can get rid of PC_F entirely.
PC_F and PC_B are used less entensively as SP, so this CL has
smaller performance gain.
Running on Chrome 74.0.3729.108 on Linux/AMD64,
name old time/op new time/op delta
BinaryTree17 16.6s ± 0% 16.2s ± 1% -2.59% (p=0.016 n=4+5)
Fannkuch11 11.1s ± 1% 10.8s ± 0% -2.65% (p=0.008 n=5+5)
FmtFprintfEmpty 231ns ± 1% 217ns ± 0% -6.06% (p=0.008 n=5+5)
FmtFprintfString 407ns ± 3% 375ns ± 2% -7.81% (p=0.008 n=5+5)
FmtFprintfInt 466ns ± 2% 430ns ± 0% -7.79% (p=0.016 n=5+4)
FmtFprintfIntInt 719ns ± 2% 673ns ± 2% -6.37% (p=0.008 n=5+5)
FmtFprintfPrefixedInt 706ns ± 1% 676ns ± 3% -4.31% (p=0.008 n=5+5)
FmtFprintfFloat 1.01µs ± 1% 0.97µs ± 1% -4.30% (p=0.008 n=5+5)
FmtManyArgs 2.67µs ± 1% 2.51µs ± 1% -5.95% (p=0.008 n=5+5)
GobDecode 30.7ms ± 9% 31.3ms ±34% ~ (p=0.222 n=5+5)
GobEncode 24.2ms ±23% 20.2ms ± 0% -16.36% (p=0.016 n=5+4)
Gzip 852ms ± 0% 823ms ± 0% -3.38% (p=0.016 n=4+5)
Gunzip 160ms ± 1% 151ms ± 1% -5.37% (p=0.008 n=5+5)
JSONEncode 35.7ms ± 1% 34.3ms ± 1% -3.81% (p=0.008 n=5+5)
JSONDecode 247ms ± 8% 254ms ± 7% ~ (p=0.548 n=5+5)
Mandelbrot200 5.39ms ± 0% 5.41ms ± 0% +0.42% (p=0.008 n=5+5)
GoParse 18.5ms ± 1% 18.3ms ± 2% ~ (p=0.343 n=4+4)
RegexpMatchEasy0_32 424ns ± 2% 397ns ± 0% -6.23% (p=0.008 n=5+5)
RegexpMatchEasy0_1K 2.88µs ± 0% 2.86µs ± 1% ~ (p=0.079 n=5+5)
RegexpMatchEasy1_32 395ns ± 2% 370ns ± 1% -6.23% (p=0.008 n=5+5)
RegexpMatchEasy1_1K 3.26µs ± 0% 3.19µs ± 1% -2.06% (p=0.008 n=5+5)
RegexpMatchMedium_32 564ns ± 1% 532ns ± 0% -5.71% (p=0.008 n=5+5)
RegexpMatchMedium_1K 146µs ± 2% 140µs ± 1% -4.62% (p=0.008 n=5+5)
RegexpMatchHard_32 8.47µs ± 1% 7.91µs ± 1% -6.65% (p=0.008 n=5+5)
RegexpMatchHard_1K 253µs ± 1% 236µs ± 2% -6.66% (p=0.008 n=5+5)
Revcomp 1.78s ± 4% 1.76s ± 5% ~ (p=1.000 n=5+5)
Template 292ms ±29% 269ms ± 5% ~ (p=0.690 n=5+5)
TimeParse 1.61µs ± 4% 1.54µs ± 1% -4.42% (p=0.008 n=5+5)
TimeFormat 1.66µs ± 3% 1.58µs ± 1% -5.22% (p=0.008 n=5+5)
[Geo mean] 232µs 221µs -4.54%
name old speed new speed delta
GobDecode 25.0MB/s ± 8% 25.1MB/s ±27% ~ (p=0.222 n=5+5)
GobEncode 32.8MB/s ±21% 38.0MB/s ± 0% +15.84% (p=0.016 n=5+4)
Gzip 22.8MB/s ± 0% 23.6MB/s ± 0% +3.49% (p=0.016 n=4+5)
Gunzip 121MB/s ± 1% 128MB/s ± 1% +5.68% (p=0.008 n=5+5)
JSONEncode 54.4MB/s ± 1% 56.5MB/s ± 1% +3.97% (p=0.008 n=5+5)
JSONDecode 7.88MB/s ± 8% 7.65MB/s ± 8% ~ (p=0.548 n=5+5)
GoParse 3.07MB/s ± 8% 3.00MB/s ±22% ~ (p=0.579 n=5+5)
RegexpMatchEasy0_32 75.6MB/s ± 2% 80.5MB/s ± 0% +6.58% (p=0.008 n=5+5)
RegexpMatchEasy0_1K 356MB/s ± 0% 358MB/s ± 1% ~ (p=0.095 n=5+5)
RegexpMatchEasy1_32 81.1MB/s ± 2% 86.5MB/s ± 1% +6.69% (p=0.008 n=5+5)
RegexpMatchEasy1_1K 314MB/s ± 0% 320MB/s ± 0% +2.10% (p=0.008 n=5+5)
RegexpMatchMedium_32 1.77MB/s ± 1% 1.88MB/s ± 0% +6.09% (p=0.016 n=5+4)
RegexpMatchMedium_1K 6.99MB/s ± 2% 7.33MB/s ± 1% +4.83% (p=0.008 n=5+5)
RegexpMatchHard_32 3.78MB/s ± 1% 4.04MB/s ± 1% +7.04% (p=0.008 n=5+5)
RegexpMatchHard_1K 4.04MB/s ± 1% 4.33MB/s ± 2% +7.17% (p=0.008 n=5+5)
Revcomp 143MB/s ± 4% 145MB/s ± 5% ~ (p=1.000 n=5+5)
Template 6.77MB/s ±24% 7.22MB/s ± 5% ~ (p=0.690 n=5+5)
[Geo mean] 24.4MB/s 25.4MB/s +4.18%
Change-Id: Ib80716e62992aec28b2c4a96af280c278f83aa49
Reviewed-on: https://go-review.googlesource.com/c/go/+/173980
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Richard Musiol <neelance@gmail.com>
2019-04-25 17:25:54 -04:00
|
|
|
// For normal Go functions, the single parameter is PC_B,
|
|
|
|
|
// the return value is
|
2018-03-04 12:59:15 +01:00
|
|
|
// 0 if the function returned normally or
|
|
|
|
|
// 1 if the stack needs to be unwound.
|
cmd, runtime: remove PC_F & PC_B globals on Wasm
Following the previous CL, this removes more global variables on
Wasm.
PC_B is used mostly for intra-function jumps, and for a function
telling its callee where to start or resume. This usage can be
served by a parameter. The top level loop (wasm_pc_f_loop) uses
PC_B for resuming a function. This value is either set by gogo,
or loaded from the Go stack at function return. Instead of
loading PC_B at each function return, we could make gogo stores
PC_B at the same stack location, and let the top level loop do
the load. This way, we don't need to use global PC_B to
communicate with the top level loop, and we can replace global
PC_B with a parameter.
PC_F is similar. It is even more so in that the only reader is
the top level loop. Let the top level loop read it from the stack,
and we can get rid of PC_F entirely.
PC_F and PC_B are used less entensively as SP, so this CL has
smaller performance gain.
Running on Chrome 74.0.3729.108 on Linux/AMD64,
name old time/op new time/op delta
BinaryTree17 16.6s ± 0% 16.2s ± 1% -2.59% (p=0.016 n=4+5)
Fannkuch11 11.1s ± 1% 10.8s ± 0% -2.65% (p=0.008 n=5+5)
FmtFprintfEmpty 231ns ± 1% 217ns ± 0% -6.06% (p=0.008 n=5+5)
FmtFprintfString 407ns ± 3% 375ns ± 2% -7.81% (p=0.008 n=5+5)
FmtFprintfInt 466ns ± 2% 430ns ± 0% -7.79% (p=0.016 n=5+4)
FmtFprintfIntInt 719ns ± 2% 673ns ± 2% -6.37% (p=0.008 n=5+5)
FmtFprintfPrefixedInt 706ns ± 1% 676ns ± 3% -4.31% (p=0.008 n=5+5)
FmtFprintfFloat 1.01µs ± 1% 0.97µs ± 1% -4.30% (p=0.008 n=5+5)
FmtManyArgs 2.67µs ± 1% 2.51µs ± 1% -5.95% (p=0.008 n=5+5)
GobDecode 30.7ms ± 9% 31.3ms ±34% ~ (p=0.222 n=5+5)
GobEncode 24.2ms ±23% 20.2ms ± 0% -16.36% (p=0.016 n=5+4)
Gzip 852ms ± 0% 823ms ± 0% -3.38% (p=0.016 n=4+5)
Gunzip 160ms ± 1% 151ms ± 1% -5.37% (p=0.008 n=5+5)
JSONEncode 35.7ms ± 1% 34.3ms ± 1% -3.81% (p=0.008 n=5+5)
JSONDecode 247ms ± 8% 254ms ± 7% ~ (p=0.548 n=5+5)
Mandelbrot200 5.39ms ± 0% 5.41ms ± 0% +0.42% (p=0.008 n=5+5)
GoParse 18.5ms ± 1% 18.3ms ± 2% ~ (p=0.343 n=4+4)
RegexpMatchEasy0_32 424ns ± 2% 397ns ± 0% -6.23% (p=0.008 n=5+5)
RegexpMatchEasy0_1K 2.88µs ± 0% 2.86µs ± 1% ~ (p=0.079 n=5+5)
RegexpMatchEasy1_32 395ns ± 2% 370ns ± 1% -6.23% (p=0.008 n=5+5)
RegexpMatchEasy1_1K 3.26µs ± 0% 3.19µs ± 1% -2.06% (p=0.008 n=5+5)
RegexpMatchMedium_32 564ns ± 1% 532ns ± 0% -5.71% (p=0.008 n=5+5)
RegexpMatchMedium_1K 146µs ± 2% 140µs ± 1% -4.62% (p=0.008 n=5+5)
RegexpMatchHard_32 8.47µs ± 1% 7.91µs ± 1% -6.65% (p=0.008 n=5+5)
RegexpMatchHard_1K 253µs ± 1% 236µs ± 2% -6.66% (p=0.008 n=5+5)
Revcomp 1.78s ± 4% 1.76s ± 5% ~ (p=1.000 n=5+5)
Template 292ms ±29% 269ms ± 5% ~ (p=0.690 n=5+5)
TimeParse 1.61µs ± 4% 1.54µs ± 1% -4.42% (p=0.008 n=5+5)
TimeFormat 1.66µs ± 3% 1.58µs ± 1% -5.22% (p=0.008 n=5+5)
[Geo mean] 232µs 221µs -4.54%
name old speed new speed delta
GobDecode 25.0MB/s ± 8% 25.1MB/s ±27% ~ (p=0.222 n=5+5)
GobEncode 32.8MB/s ±21% 38.0MB/s ± 0% +15.84% (p=0.016 n=5+4)
Gzip 22.8MB/s ± 0% 23.6MB/s ± 0% +3.49% (p=0.016 n=4+5)
Gunzip 121MB/s ± 1% 128MB/s ± 1% +5.68% (p=0.008 n=5+5)
JSONEncode 54.4MB/s ± 1% 56.5MB/s ± 1% +3.97% (p=0.008 n=5+5)
JSONDecode 7.88MB/s ± 8% 7.65MB/s ± 8% ~ (p=0.548 n=5+5)
GoParse 3.07MB/s ± 8% 3.00MB/s ±22% ~ (p=0.579 n=5+5)
RegexpMatchEasy0_32 75.6MB/s ± 2% 80.5MB/s ± 0% +6.58% (p=0.008 n=5+5)
RegexpMatchEasy0_1K 356MB/s ± 0% 358MB/s ± 1% ~ (p=0.095 n=5+5)
RegexpMatchEasy1_32 81.1MB/s ± 2% 86.5MB/s ± 1% +6.69% (p=0.008 n=5+5)
RegexpMatchEasy1_1K 314MB/s ± 0% 320MB/s ± 0% +2.10% (p=0.008 n=5+5)
RegexpMatchMedium_32 1.77MB/s ± 1% 1.88MB/s ± 0% +6.09% (p=0.016 n=5+4)
RegexpMatchMedium_1K 6.99MB/s ± 2% 7.33MB/s ± 1% +4.83% (p=0.008 n=5+5)
RegexpMatchHard_32 3.78MB/s ± 1% 4.04MB/s ± 1% +7.04% (p=0.008 n=5+5)
RegexpMatchHard_1K 4.04MB/s ± 1% 4.33MB/s ± 2% +7.17% (p=0.008 n=5+5)
Revcomp 143MB/s ± 4% 145MB/s ± 5% ~ (p=1.000 n=5+5)
Template 6.77MB/s ±24% 7.22MB/s ± 5% ~ (p=0.690 n=5+5)
[Geo mean] 24.4MB/s 25.4MB/s +4.18%
Change-Id: Ib80716e62992aec28b2c4a96af280c278f83aa49
Reviewed-on: https://go-review.googlesource.com/c/go/+/173980
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Richard Musiol <neelance@gmail.com>
2019-04-25 17:25:54 -04:00
|
|
|
{Params: []byte{I32}, Results: []byte{I32}},
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// collect host imports (functions that get imported from the WebAssembly host, usually JavaScript)
|
2023-01-22 15:30:59 -08:00
|
|
|
// we store the import index of each imported function, so the R_WASMIMPORT relocation
|
|
|
|
|
// can write the correct index after a "call" instruction
|
|
|
|
|
// these are added as import statements to the top of the WebAssembly binary
|
|
|
|
|
var hostImports []*wasmFunc
|
2020-05-08 14:09:57 -04:00
|
|
|
hostImportMap := make(map[loader.Sym]int64)
|
2020-05-15 18:35:05 -04:00
|
|
|
for _, fn := range ctxt.Textp {
|
2020-05-08 14:09:57 -04:00
|
|
|
relocs := ldr.Relocs(fn)
|
|
|
|
|
for ri := 0; ri < relocs.Count(); ri++ {
|
2020-07-29 13:26:50 -04:00
|
|
|
r := relocs.At(ri)
|
2020-05-08 14:09:57 -04:00
|
|
|
if r.Type() == objabi.R_WASMIMPORT {
|
2024-08-05 13:40:18 -04:00
|
|
|
if wsym := ldr.WasmImportSym(fn); wsym != 0 {
|
|
|
|
|
wi := readWasmImport(ldr, wsym)
|
2023-01-22 15:30:59 -08:00
|
|
|
hostImportMap[fn] = int64(len(hostImports))
|
|
|
|
|
hostImports = append(hostImports, &wasmFunc{
|
|
|
|
|
Module: wi.Module,
|
|
|
|
|
Name: wi.Name,
|
|
|
|
|
Type: lookupType(&wasmFuncType{
|
|
|
|
|
Params: fieldsToTypes(wi.Params),
|
|
|
|
|
Results: fieldsToTypes(wi.Results),
|
|
|
|
|
}, &types),
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
panic(fmt.Sprintf("missing wasm symbol for %s", ldr.SymName(r.Sym())))
|
|
|
|
|
}
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// collect functions with WebAssembly body
|
2018-06-15 19:13:51 +00:00
|
|
|
var buildid []byte
|
2020-05-15 18:35:05 -04:00
|
|
|
fns := make([]*wasmFunc, len(ctxt.Textp))
|
|
|
|
|
for i, fn := range ctxt.Textp {
|
2018-03-04 12:59:15 +01:00
|
|
|
wfn := new(bytes.Buffer)
|
2021-05-08 00:45:06 +07:00
|
|
|
if ldr.SymName(fn) == "go:buildid" {
|
2018-03-04 12:59:15 +01:00
|
|
|
writeUleb128(wfn, 0) // number of sets of locals
|
|
|
|
|
writeI32Const(wfn, 0)
|
|
|
|
|
wfn.WriteByte(0x0b) // end
|
2020-05-08 14:09:57 -04:00
|
|
|
buildid = ldr.Data(fn)
|
2018-03-04 12:59:15 +01:00
|
|
|
} else {
|
|
|
|
|
// Relocations have variable length, handle them here.
|
2020-05-08 14:09:57 -04:00
|
|
|
relocs := ldr.Relocs(fn)
|
|
|
|
|
P := ldr.Data(fn)
|
2018-03-04 12:59:15 +01:00
|
|
|
off := int32(0)
|
2020-05-08 14:09:57 -04:00
|
|
|
for ri := 0; ri < relocs.Count(); ri++ {
|
2020-07-29 13:26:50 -04:00
|
|
|
r := relocs.At(ri)
|
2020-09-20 23:29:20 -04:00
|
|
|
if r.Siz() == 0 {
|
|
|
|
|
continue // skip marker relocations
|
|
|
|
|
}
|
2020-05-08 14:09:57 -04:00
|
|
|
wfn.Write(P[off:r.Off()])
|
|
|
|
|
off = r.Off()
|
2021-09-21 17:52:19 -04:00
|
|
|
rs := r.Sym()
|
2020-05-08 14:09:57 -04:00
|
|
|
switch r.Type() {
|
2018-03-04 12:59:15 +01:00
|
|
|
case objabi.R_ADDR:
|
2020-05-08 14:09:57 -04:00
|
|
|
writeSleb128(wfn, ldr.SymValue(rs)+r.Add())
|
2018-03-04 12:59:15 +01:00
|
|
|
case objabi.R_CALL:
|
2020-05-08 14:09:57 -04:00
|
|
|
writeSleb128(wfn, int64(len(hostImports))+ldr.SymValue(rs)>>16-funcValueOffset)
|
2018-03-04 12:59:15 +01:00
|
|
|
case objabi.R_WASMIMPORT:
|
2020-05-08 14:09:57 -04:00
|
|
|
writeSleb128(wfn, hostImportMap[rs])
|
2018-03-04 12:59:15 +01:00
|
|
|
default:
|
2020-05-08 14:09:57 -04:00
|
|
|
ldr.Errorf(fn, "bad reloc type %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type()))
|
2018-03-04 12:59:15 +01:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-08 14:09:57 -04:00
|
|
|
wfn.Write(P[off:])
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typ := uint32(0)
|
2020-05-08 14:09:57 -04:00
|
|
|
if sig, ok := wasmFuncTypes[ldr.SymName(fn)]; ok {
|
2018-03-04 12:59:15 +01:00
|
|
|
typ = lookupType(sig, &types)
|
|
|
|
|
}
|
cmd/compile: add basic wasmexport support
This CL adds a compiler directive go:wasmexport, which applies to
a Go function and makes it an exported function of the Wasm module
being built, so it can be called directly from the host. As
proposed in #65199, parameter and result types are limited to
32-bit and 64-bit integers and floats, and there can be at most
one result.
As the Go and Wasm calling conventions are different, for a
wasmexport function we generate a wrapper function does the ABI
conversion at compile time.
Currently this CL only adds basic support. In particular,
- it only supports executable mode, i.e. the Go wasm module calls
into the host via wasmimport, which then calls back to Go via
wasmexport. Library (c-shared) mode is not implemented yet.
- only supports wasip1, not js.
- if the exported function unwinds stacks (goroutine switch, stack
growth, etc.), it probably doesn't work.
TODO: support stack unwinding, c-shared mode, js.
For #65199.
Change-Id: Id1777c2d44f7d51942c1caed3173c0a82f120cc4
Reviewed-on: https://go-review.googlesource.com/c/go/+/603055
Reviewed-by: Than McIntosh <thanm@golang.org>
Reviewed-by: Randy Reddig <randy.reddig@fastly.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2024-08-03 14:20:58 -04:00
|
|
|
if s := ldr.WasmTypeSym(fn); s != 0 {
|
|
|
|
|
var o obj.WasmFuncType
|
|
|
|
|
o.Read(ldr.Data(s))
|
|
|
|
|
t := &wasmFuncType{
|
|
|
|
|
Params: fieldsToTypes(o.Params),
|
|
|
|
|
Results: fieldsToTypes(o.Results),
|
|
|
|
|
}
|
|
|
|
|
typ = lookupType(t, &types)
|
|
|
|
|
}
|
2018-03-04 12:59:15 +01:00
|
|
|
|
2020-05-08 14:09:57 -04:00
|
|
|
name := nameRegexp.ReplaceAllString(ldr.SymName(fn), "_")
|
2018-03-04 12:59:15 +01:00
|
|
|
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
|
|
|
|
|
|
2018-06-15 19:13:51 +00:00
|
|
|
// Add any buildid early in the binary:
|
|
|
|
|
if len(buildid) != 0 {
|
|
|
|
|
writeBuildID(ctxt, buildid)
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-04 12:59:15 +01:00
|
|
|
writeTypeSec(ctxt, types)
|
|
|
|
|
writeImportSec(ctxt, hostImports)
|
|
|
|
|
writeFunctionSec(ctxt, fns)
|
|
|
|
|
writeTableSec(ctxt, fns)
|
2020-05-08 14:09:57 -04:00
|
|
|
writeMemorySec(ctxt, ldr)
|
2018-03-04 12:59:15 +01:00
|
|
|
writeGlobalSec(ctxt)
|
2020-05-08 14:09:57 -04:00
|
|
|
writeExportSec(ctxt, ldr, len(hostImports))
|
2018-03-04 12:59:15 +01:00
|
|
|
writeElementSec(ctxt, uint64(len(hostImports)), uint64(len(fns)))
|
|
|
|
|
writeCodeSec(ctxt, fns)
|
|
|
|
|
writeDataSec(ctxt)
|
2019-09-24 00:48:39 +02:00
|
|
|
writeProducerSec(ctxt)
|
2018-03-04 12:59:15 +01:00
|
|
|
if !*ld.FlagS {
|
2018-06-14 19:22:55 +02:00
|
|
|
writeNameSec(ctxt, len(hostImports), fns)
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-15 19:13:51 +00:00
|
|
|
func writeBuildID(ctxt *ld.Link, buildid []byte) {
|
|
|
|
|
sizeOffset := writeSecHeader(ctxt, sectionCustom)
|
2021-05-08 00:45:06 +07:00
|
|
|
writeName(ctxt.Out, "go:buildid")
|
2018-06-15 19:13:51 +00:00
|
|
|
ctxt.Out.Write(buildid)
|
|
|
|
|
writeSecSize(ctxt, sizeOffset)
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-04 12:59:15 +01:00
|
|
|
// 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 {
|
2025-10-19 19:53:27 +00:00
|
|
|
ctxt.Out.WriteByte(v)
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
writeUleb128(ctxt.Out, uint64(len(t.Results)))
|
|
|
|
|
for _, v := range t.Results {
|
2025-10-19 19:53:27 +00:00
|
|
|
ctxt.Out.WriteByte(v)
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2023-01-22 15:30:59 -08:00
|
|
|
if fn.Module != "" {
|
|
|
|
|
writeName(ctxt.Out, fn.Module)
|
|
|
|
|
} else {
|
|
|
|
|
writeName(ctxt.Out, wasm.GojsModule) // provided by the import object in wasm_exec.js
|
|
|
|
|
}
|
2018-03-04 12:59:15 +01:00
|
|
|
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.
|
2019-04-08 00:36:55 +02:00
|
|
|
// Linear memory always starts at address zero. More memory can be requested with the GrowMemory instruction.
|
2020-05-08 14:09:57 -04:00
|
|
|
func writeMemorySec(ctxt *ld.Link, ldr *loader.Loader) {
|
2018-03-04 12:59:15 +01:00
|
|
|
sizeOffset := writeSecHeader(ctxt, sectionMemory)
|
|
|
|
|
|
cmd/link: reduce Wasm initial memory size
Currently, for Wasm, the linker sets the initial memory size to
the size of global data plus 16 MB. The intention is that it
covers the global data and runtime initialization without growing
the linear memory. However, the code accounts only the data
"section", not the bss "section", therefore the extra 16 MB is
actually used to cover bss variables. Also, as seen on the
previous CL, the runtime actually didn't use the extra space,
which means the program can start without that space.
This CL corrects the global data size calculation, and reduces the
extra to 1 MB. Currently the runtime's allocation pattern at
startup is that it allocates a few pages for the page allocator's
metadata, the an 8 MB reservation for the first 4 MB size, 4 MB
aligned heap arena (it may be possible to reduce that, but we'll
leave that for later). Here we use 1 MB extra space to cover the
small allocations, but let the runtime allocate the heap arena, so
the linker code and the runtime's allocator are not tightly
coupled.
For #69018.
Change-Id: I39fe1172382ecc03f4b537e43ec710af8075eab3
Reviewed-on: https://go-review.googlesource.com/c/go/+/621636
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2024-10-21 13:09:58 -04:00
|
|
|
dataEnd := uint64(ldr.SymValue(ldr.Lookup("runtime.end", 0)))
|
|
|
|
|
var initialSize = dataEnd + 1<<20 // 1 MB, for runtime init allocating a few pages
|
2019-10-06 00:49:52 +02:00
|
|
|
|
|
|
|
|
const wasmPageSize = 64 << 10 // 64KB
|
2019-04-08 00:36:55 +02:00
|
|
|
|
|
|
|
|
writeUleb128(ctxt.Out, 1) // number of memories
|
|
|
|
|
ctxt.Out.WriteByte(0x00) // no maximum memory size
|
|
|
|
|
writeUleb128(ctxt.Out, initialSize/wasmPageSize) // minimum (initial) memory size
|
2018-03-04 12:59:15 +01:00
|
|
|
|
|
|
|
|
writeSecSize(ctxt, sizeOffset)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// writeGlobalSec writes the section that declares global variables.
|
|
|
|
|
func writeGlobalSec(ctxt *ld.Link) {
|
|
|
|
|
sizeOffset := writeSecHeader(ctxt, sectionGlobal)
|
|
|
|
|
|
|
|
|
|
globalRegs := []byte{
|
cmd, runtime: remove PC_F & PC_B globals on Wasm
Following the previous CL, this removes more global variables on
Wasm.
PC_B is used mostly for intra-function jumps, and for a function
telling its callee where to start or resume. This usage can be
served by a parameter. The top level loop (wasm_pc_f_loop) uses
PC_B for resuming a function. This value is either set by gogo,
or loaded from the Go stack at function return. Instead of
loading PC_B at each function return, we could make gogo stores
PC_B at the same stack location, and let the top level loop do
the load. This way, we don't need to use global PC_B to
communicate with the top level loop, and we can replace global
PC_B with a parameter.
PC_F is similar. It is even more so in that the only reader is
the top level loop. Let the top level loop read it from the stack,
and we can get rid of PC_F entirely.
PC_F and PC_B are used less entensively as SP, so this CL has
smaller performance gain.
Running on Chrome 74.0.3729.108 on Linux/AMD64,
name old time/op new time/op delta
BinaryTree17 16.6s ± 0% 16.2s ± 1% -2.59% (p=0.016 n=4+5)
Fannkuch11 11.1s ± 1% 10.8s ± 0% -2.65% (p=0.008 n=5+5)
FmtFprintfEmpty 231ns ± 1% 217ns ± 0% -6.06% (p=0.008 n=5+5)
FmtFprintfString 407ns ± 3% 375ns ± 2% -7.81% (p=0.008 n=5+5)
FmtFprintfInt 466ns ± 2% 430ns ± 0% -7.79% (p=0.016 n=5+4)
FmtFprintfIntInt 719ns ± 2% 673ns ± 2% -6.37% (p=0.008 n=5+5)
FmtFprintfPrefixedInt 706ns ± 1% 676ns ± 3% -4.31% (p=0.008 n=5+5)
FmtFprintfFloat 1.01µs ± 1% 0.97µs ± 1% -4.30% (p=0.008 n=5+5)
FmtManyArgs 2.67µs ± 1% 2.51µs ± 1% -5.95% (p=0.008 n=5+5)
GobDecode 30.7ms ± 9% 31.3ms ±34% ~ (p=0.222 n=5+5)
GobEncode 24.2ms ±23% 20.2ms ± 0% -16.36% (p=0.016 n=5+4)
Gzip 852ms ± 0% 823ms ± 0% -3.38% (p=0.016 n=4+5)
Gunzip 160ms ± 1% 151ms ± 1% -5.37% (p=0.008 n=5+5)
JSONEncode 35.7ms ± 1% 34.3ms ± 1% -3.81% (p=0.008 n=5+5)
JSONDecode 247ms ± 8% 254ms ± 7% ~ (p=0.548 n=5+5)
Mandelbrot200 5.39ms ± 0% 5.41ms ± 0% +0.42% (p=0.008 n=5+5)
GoParse 18.5ms ± 1% 18.3ms ± 2% ~ (p=0.343 n=4+4)
RegexpMatchEasy0_32 424ns ± 2% 397ns ± 0% -6.23% (p=0.008 n=5+5)
RegexpMatchEasy0_1K 2.88µs ± 0% 2.86µs ± 1% ~ (p=0.079 n=5+5)
RegexpMatchEasy1_32 395ns ± 2% 370ns ± 1% -6.23% (p=0.008 n=5+5)
RegexpMatchEasy1_1K 3.26µs ± 0% 3.19µs ± 1% -2.06% (p=0.008 n=5+5)
RegexpMatchMedium_32 564ns ± 1% 532ns ± 0% -5.71% (p=0.008 n=5+5)
RegexpMatchMedium_1K 146µs ± 2% 140µs ± 1% -4.62% (p=0.008 n=5+5)
RegexpMatchHard_32 8.47µs ± 1% 7.91µs ± 1% -6.65% (p=0.008 n=5+5)
RegexpMatchHard_1K 253µs ± 1% 236µs ± 2% -6.66% (p=0.008 n=5+5)
Revcomp 1.78s ± 4% 1.76s ± 5% ~ (p=1.000 n=5+5)
Template 292ms ±29% 269ms ± 5% ~ (p=0.690 n=5+5)
TimeParse 1.61µs ± 4% 1.54µs ± 1% -4.42% (p=0.008 n=5+5)
TimeFormat 1.66µs ± 3% 1.58µs ± 1% -5.22% (p=0.008 n=5+5)
[Geo mean] 232µs 221µs -4.54%
name old speed new speed delta
GobDecode 25.0MB/s ± 8% 25.1MB/s ±27% ~ (p=0.222 n=5+5)
GobEncode 32.8MB/s ±21% 38.0MB/s ± 0% +15.84% (p=0.016 n=5+4)
Gzip 22.8MB/s ± 0% 23.6MB/s ± 0% +3.49% (p=0.016 n=4+5)
Gunzip 121MB/s ± 1% 128MB/s ± 1% +5.68% (p=0.008 n=5+5)
JSONEncode 54.4MB/s ± 1% 56.5MB/s ± 1% +3.97% (p=0.008 n=5+5)
JSONDecode 7.88MB/s ± 8% 7.65MB/s ± 8% ~ (p=0.548 n=5+5)
GoParse 3.07MB/s ± 8% 3.00MB/s ±22% ~ (p=0.579 n=5+5)
RegexpMatchEasy0_32 75.6MB/s ± 2% 80.5MB/s ± 0% +6.58% (p=0.008 n=5+5)
RegexpMatchEasy0_1K 356MB/s ± 0% 358MB/s ± 1% ~ (p=0.095 n=5+5)
RegexpMatchEasy1_32 81.1MB/s ± 2% 86.5MB/s ± 1% +6.69% (p=0.008 n=5+5)
RegexpMatchEasy1_1K 314MB/s ± 0% 320MB/s ± 0% +2.10% (p=0.008 n=5+5)
RegexpMatchMedium_32 1.77MB/s ± 1% 1.88MB/s ± 0% +6.09% (p=0.016 n=5+4)
RegexpMatchMedium_1K 6.99MB/s ± 2% 7.33MB/s ± 1% +4.83% (p=0.008 n=5+5)
RegexpMatchHard_32 3.78MB/s ± 1% 4.04MB/s ± 1% +7.04% (p=0.008 n=5+5)
RegexpMatchHard_1K 4.04MB/s ± 1% 4.33MB/s ± 2% +7.17% (p=0.008 n=5+5)
Revcomp 143MB/s ± 4% 145MB/s ± 5% ~ (p=1.000 n=5+5)
Template 6.77MB/s ±24% 7.22MB/s ± 5% ~ (p=0.690 n=5+5)
[Geo mean] 24.4MB/s 25.4MB/s +4.18%
Change-Id: Ib80716e62992aec28b2c4a96af280c278f83aa49
Reviewed-on: https://go-review.googlesource.com/c/go/+/173980
Run-TryBot: Cherry Zhang <cherryyz@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Richard Musiol <neelance@gmail.com>
2019-04-25 17:25:54 -04:00
|
|
|
I32, // 0: SP
|
|
|
|
|
I64, // 1: CTXT
|
|
|
|
|
I64, // 2: g
|
|
|
|
|
I64, // 3: RET0
|
|
|
|
|
I64, // 4: RET1
|
|
|
|
|
I64, // 5: RET2
|
|
|
|
|
I64, // 6: RET3
|
|
|
|
|
I32, // 7: PAUSE
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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.
|
2018-10-11 12:46:14 +02:00
|
|
|
// The wasm_export_* functions and the linear memory get exported.
|
2020-05-08 14:09:57 -04:00
|
|
|
func writeExportSec(ctxt *ld.Link, ldr *loader.Loader, lenHostImports int) {
|
2018-03-04 12:59:15 +01:00
|
|
|
sizeOffset := writeSecHeader(ctxt, sectionExport)
|
|
|
|
|
|
2023-03-24 23:07:58 -07:00
|
|
|
switch buildcfg.GOOS {
|
|
|
|
|
case "wasip1":
|
cmd/compile: add basic wasmexport support
This CL adds a compiler directive go:wasmexport, which applies to
a Go function and makes it an exported function of the Wasm module
being built, so it can be called directly from the host. As
proposed in #65199, parameter and result types are limited to
32-bit and 64-bit integers and floats, and there can be at most
one result.
As the Go and Wasm calling conventions are different, for a
wasmexport function we generate a wrapper function does the ABI
conversion at compile time.
Currently this CL only adds basic support. In particular,
- it only supports executable mode, i.e. the Go wasm module calls
into the host via wasmimport, which then calls back to Go via
wasmexport. Library (c-shared) mode is not implemented yet.
- only supports wasip1, not js.
- if the exported function unwinds stacks (goroutine switch, stack
growth, etc.), it probably doesn't work.
TODO: support stack unwinding, c-shared mode, js.
For #65199.
Change-Id: Id1777c2d44f7d51942c1caed3173c0a82f120cc4
Reviewed-on: https://go-review.googlesource.com/c/go/+/603055
Reviewed-by: Than McIntosh <thanm@golang.org>
Reviewed-by: Randy Reddig <randy.reddig@fastly.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2024-08-03 14:20:58 -04:00
|
|
|
writeUleb128(ctxt.Out, uint64(2+len(ldr.WasmExports))) // number of exports
|
cmd/link, runtime: support library mode on wasip1
This CL adds support of "library", i.e. c-shared, build mode on
wasip1. When -buildmode=c-shared is set, it builds a Wasm module
that is intended to be used as a library, instead of an executable.
It does not have the _start function. Instead, it has an
_initialize function, which initializes the runtime, but not call
the main function.
This is similar to the c-shared build mode on other platforms. One
difference is that unlike cgo callbacks, where Ms are created on-
demand, on Wasm we have only one M, so we just keep the M (and the
G) for callbacks.
For #65199.
Change-Id: Ieb21da96b25c1a9f3989d945cddc964c26f9085b
Reviewed-on: https://go-review.googlesource.com/c/go/+/604316
Reviewed-by: Achille Roussel <achille.roussel@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2024-08-08 18:45:18 -04:00
|
|
|
var entry, entryExpName string
|
|
|
|
|
switch ctxt.BuildMode {
|
|
|
|
|
case ld.BuildModeExe:
|
|
|
|
|
entry = "_rt0_wasm_wasip1"
|
|
|
|
|
entryExpName = "_start"
|
|
|
|
|
case ld.BuildModeCShared:
|
|
|
|
|
entry = "_rt0_wasm_wasip1_lib"
|
|
|
|
|
entryExpName = "_initialize"
|
|
|
|
|
}
|
|
|
|
|
s := ldr.Lookup(entry, 0)
|
|
|
|
|
if s == 0 {
|
2024-11-01 14:56:25 -04:00
|
|
|
ld.Errorf("export symbol %s not defined", entry)
|
cmd/link, runtime: support library mode on wasip1
This CL adds support of "library", i.e. c-shared, build mode on
wasip1. When -buildmode=c-shared is set, it builds a Wasm module
that is intended to be used as a library, instead of an executable.
It does not have the _start function. Instead, it has an
_initialize function, which initializes the runtime, but not call
the main function.
This is similar to the c-shared build mode on other platforms. One
difference is that unlike cgo callbacks, where Ms are created on-
demand, on Wasm we have only one M, so we just keep the M (and the
G) for callbacks.
For #65199.
Change-Id: Ieb21da96b25c1a9f3989d945cddc964c26f9085b
Reviewed-on: https://go-review.googlesource.com/c/go/+/604316
Reviewed-by: Achille Roussel <achille.roussel@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2024-08-08 18:45:18 -04:00
|
|
|
}
|
2020-05-08 14:09:57 -04:00
|
|
|
idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset
|
cmd/link, runtime: support library mode on wasip1
This CL adds support of "library", i.e. c-shared, build mode on
wasip1. When -buildmode=c-shared is set, it builds a Wasm module
that is intended to be used as a library, instead of an executable.
It does not have the _start function. Instead, it has an
_initialize function, which initializes the runtime, but not call
the main function.
This is similar to the c-shared build mode on other platforms. One
difference is that unlike cgo callbacks, where Ms are created on-
demand, on Wasm we have only one M, so we just keep the M (and the
G) for callbacks.
For #65199.
Change-Id: Ieb21da96b25c1a9f3989d945cddc964c26f9085b
Reviewed-on: https://go-review.googlesource.com/c/go/+/604316
Reviewed-by: Achille Roussel <achille.roussel@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2024-08-08 18:45:18 -04:00
|
|
|
writeName(ctxt.Out, entryExpName) // the wasi entrypoint
|
2018-10-11 12:46:14 +02:00
|
|
|
ctxt.Out.WriteByte(0x00) // func export
|
|
|
|
|
writeUleb128(ctxt.Out, uint64(idx)) // funcidx
|
cmd/compile: add basic wasmexport support
This CL adds a compiler directive go:wasmexport, which applies to
a Go function and makes it an exported function of the Wasm module
being built, so it can be called directly from the host. As
proposed in #65199, parameter and result types are limited to
32-bit and 64-bit integers and floats, and there can be at most
one result.
As the Go and Wasm calling conventions are different, for a
wasmexport function we generate a wrapper function does the ABI
conversion at compile time.
Currently this CL only adds basic support. In particular,
- it only supports executable mode, i.e. the Go wasm module calls
into the host via wasmimport, which then calls back to Go via
wasmexport. Library (c-shared) mode is not implemented yet.
- only supports wasip1, not js.
- if the exported function unwinds stacks (goroutine switch, stack
growth, etc.), it probably doesn't work.
TODO: support stack unwinding, c-shared mode, js.
For #65199.
Change-Id: Id1777c2d44f7d51942c1caed3173c0a82f120cc4
Reviewed-on: https://go-review.googlesource.com/c/go/+/603055
Reviewed-by: Than McIntosh <thanm@golang.org>
Reviewed-by: Randy Reddig <randy.reddig@fastly.com>
Reviewed-by: David Chase <drchase@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
2024-08-03 14:20:58 -04:00
|
|
|
for _, s := range ldr.WasmExports {
|
|
|
|
|
idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset
|
|
|
|
|
writeName(ctxt.Out, ldr.SymName(s))
|
|
|
|
|
ctxt.Out.WriteByte(0x00) // func export
|
|
|
|
|
writeUleb128(ctxt.Out, uint64(idx)) // funcidx
|
|
|
|
|
}
|
|
|
|
|
writeName(ctxt.Out, "memory") // memory in wasi
|
|
|
|
|
ctxt.Out.WriteByte(0x02) // mem export
|
|
|
|
|
writeUleb128(ctxt.Out, 0) // memidx
|
2023-03-24 23:07:58 -07:00
|
|
|
case "js":
|
2024-08-19 15:17:04 -04:00
|
|
|
writeUleb128(ctxt.Out, uint64(4+len(ldr.WasmExports))) // number of exports
|
2023-03-24 23:07:58 -07:00
|
|
|
for _, name := range []string{"run", "resume", "getsp"} {
|
|
|
|
|
s := ldr.Lookup("wasm_export_"+name, 0)
|
cmd/link, runtime: support library mode on wasip1
This CL adds support of "library", i.e. c-shared, build mode on
wasip1. When -buildmode=c-shared is set, it builds a Wasm module
that is intended to be used as a library, instead of an executable.
It does not have the _start function. Instead, it has an
_initialize function, which initializes the runtime, but not call
the main function.
This is similar to the c-shared build mode on other platforms. One
difference is that unlike cgo callbacks, where Ms are created on-
demand, on Wasm we have only one M, so we just keep the M (and the
G) for callbacks.
For #65199.
Change-Id: Ieb21da96b25c1a9f3989d945cddc964c26f9085b
Reviewed-on: https://go-review.googlesource.com/c/go/+/604316
Reviewed-by: Achille Roussel <achille.roussel@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2024-08-08 18:45:18 -04:00
|
|
|
if s == 0 {
|
2024-11-01 14:56:25 -04:00
|
|
|
ld.Errorf("export symbol %s not defined", "wasm_export_"+name)
|
cmd/link, runtime: support library mode on wasip1
This CL adds support of "library", i.e. c-shared, build mode on
wasip1. When -buildmode=c-shared is set, it builds a Wasm module
that is intended to be used as a library, instead of an executable.
It does not have the _start function. Instead, it has an
_initialize function, which initializes the runtime, but not call
the main function.
This is similar to the c-shared build mode on other platforms. One
difference is that unlike cgo callbacks, where Ms are created on-
demand, on Wasm we have only one M, so we just keep the M (and the
G) for callbacks.
For #65199.
Change-Id: Ieb21da96b25c1a9f3989d945cddc964c26f9085b
Reviewed-on: https://go-review.googlesource.com/c/go/+/604316
Reviewed-by: Achille Roussel <achille.roussel@gmail.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
2024-08-08 18:45:18 -04:00
|
|
|
}
|
2023-03-24 23:07:58 -07:00
|
|
|
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
|
|
|
|
|
}
|
2024-08-19 15:17:04 -04:00
|
|
|
for _, s := range ldr.WasmExports {
|
|
|
|
|
idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset
|
|
|
|
|
writeName(ctxt.Out, ldr.SymName(s))
|
|
|
|
|
ctxt.Out.WriteByte(0x00) // func export
|
|
|
|
|
writeUleb128(ctxt.Out, uint64(idx)) // funcidx
|
|
|
|
|
}
|
2023-03-24 23:07:58 -07:00
|
|
|
writeName(ctxt.Out, "mem") // inst.exports.mem in wasm_exec.js
|
|
|
|
|
ctxt.Out.WriteByte(0x02) // mem export
|
|
|
|
|
writeUleb128(ctxt.Out, 0) // memidx
|
|
|
|
|
default:
|
|
|
|
|
ld.Exitf("internal error: writeExportSec: unrecognized GOOS %s", buildcfg.GOOS)
|
2018-10-11 12:46:14 +02:00
|
|
|
}
|
2018-03-04 12:59:15 +01:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-29 12:46:10 +00:00
|
|
|
// writeCodeSec writes the section that provides the function bodies for the functions
|
2018-03-04 12:59:15 +01:00
|
|
|
// 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)
|
|
|
|
|
|
2019-03-17 15:51:30 +01:00
|
|
|
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
|
2018-03-04 12:59:15 +01:00
|
|
|
|
2019-03-24 18:21:11 +01:00
|
|
|
// Generate at most this many segments. A higher number of segments gets rejected by some WebAssembly runtimes.
|
|
|
|
|
const maxNumSegments = 100000
|
|
|
|
|
|
2019-03-17 15:51:30 +01:00
|
|
|
var segments []*dataSegment
|
2020-04-28 09:53:29 -04:00
|
|
|
for secIndex, ds := range dataSects {
|
|
|
|
|
data := ds.data
|
|
|
|
|
offset := int32(ds.sect.Vaddr)
|
2019-03-17 15:51:30 +01:00
|
|
|
|
|
|
|
|
// 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
|
2020-04-28 09:53:29 -04:00
|
|
|
if len(segments)+(len(dataSects)-secIndex) == maxNumSegments {
|
2019-03-24 18:21:11 +01:00
|
|
|
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
|
2019-03-17 15:51:30 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2018-03-04 12:59:15 +01:00
|
|
|
writeUleb128(ctxt.Out, 0) // memidx
|
2019-03-17 15:51:30 +01:00
|
|
|
writeI32Const(ctxt.Out, seg.offset)
|
2018-03-04 12:59:15 +01:00
|
|
|
ctxt.Out.WriteByte(0x0b) // end
|
2019-03-17 15:51:30 +01:00
|
|
|
writeUleb128(ctxt.Out, uint64(len(seg.data)))
|
|
|
|
|
ctxt.Out.Write(seg.data)
|
2018-03-04 12:59:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
writeSecSize(ctxt, sizeOffset)
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-24 00:48:39 +02:00
|
|
|
// 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
|
|
|
|
|
|
2021-04-15 23:05:49 -04:00
|
|
|
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
|
2019-09-24 00:48:39 +02:00
|
|
|
|
|
|
|
|
writeName(ctxt.Out, "processed-by") // field name
|
|
|
|
|
writeUleb128(ctxt.Out, 1) // number of values
|
|
|
|
|
writeName(ctxt.Out, "Go cmd/compile") // value: name
|
2021-04-15 23:05:49 -04:00
|
|
|
writeName(ctxt.Out, buildcfg.Version) // value: version
|
2019-09-24 00:48:39 +02:00
|
|
|
|
|
|
|
|
writeSecSize(ctxt, sizeOffset)
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-27 04:18:15 +00:00
|
|
|
var nameRegexp = regexp.MustCompile(`[^\w.]`)
|
2018-03-04 12:59:15 +01:00
|
|
|
|
|
|
|
|
// 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
|
2018-06-14 19:22:55 +02:00
|
|
|
func writeNameSec(ctxt *ld.Link, firstFnIndex int, fns []*wasmFunc) {
|
2018-03-04 12:59:15 +01:00
|
|
|
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 {
|
2018-06-14 19:22:55 +02:00
|
|
|
writeUleb128(ctxt.Out, uint64(firstFnIndex+i))
|
2018-03-04 12:59:15 +01:00
|
|
|
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) {
|
2019-10-12 21:13:28 +05:30
|
|
|
if v < 128 {
|
|
|
|
|
w.WriteByte(uint8(v))
|
|
|
|
|
return
|
|
|
|
|
}
|
2018-03-04 12:59:15 +01:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-22 15:30:59 -08:00
|
|
|
|
|
|
|
|
func fieldsToTypes(fields []obj.WasmField) []byte {
|
|
|
|
|
b := make([]byte, len(fields))
|
|
|
|
|
for i, f := range fields {
|
|
|
|
|
switch f.Type {
|
2024-11-08 12:43:06 -05:00
|
|
|
case obj.WasmI32, obj.WasmPtr, obj.WasmBool:
|
2023-01-22 15:30:59 -08:00
|
|
|
b[i] = I32
|
|
|
|
|
case obj.WasmI64:
|
|
|
|
|
b[i] = I64
|
|
|
|
|
case obj.WasmF32:
|
|
|
|
|
b[i] = F32
|
|
|
|
|
case obj.WasmF64:
|
|
|
|
|
b[i] = F64
|
|
|
|
|
default:
|
|
|
|
|
panic(fmt.Sprintf("fieldsToTypes: unknown field type: %d", f.Type))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|