summaryrefslogtreecommitdiffstats
path: root/misc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /misc
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'misc')
-rw-r--r--misc/cgo/gmp/fib.go45
-rw-r--r--misc/cgo/gmp/gmp.go379
-rw-r--r--misc/cgo/gmp/pi.go73
-rw-r--r--misc/chrome/gophertool/README.txt8
-rw-r--r--misc/chrome/gophertool/background.html12
-rw-r--r--misc/chrome/gophertool/background.js9
-rw-r--r--misc/chrome/gophertool/gopher.js41
-rw-r--r--misc/chrome/gophertool/gopher.pngbin0 -> 5588 bytes
-rw-r--r--misc/chrome/gophertool/manifest.json20
-rw-r--r--misc/chrome/gophertool/popup.html21
-rw-r--r--misc/chrome/gophertool/popup.js46
-rw-r--r--misc/editors5
-rw-r--r--misc/go.mod6
-rw-r--r--misc/go_android_exec/README25
-rw-r--r--misc/go_android_exec/exitcode_test.go76
-rw-r--r--misc/go_android_exec/main.go526
-rw-r--r--misc/ios/README57
-rwxr-xr-xmisc/ios/clangwrap.sh20
-rw-r--r--misc/ios/detect.go134
-rw-r--r--misc/ios/go_ios_exec.go911
-rw-r--r--misc/linkcheck/linkcheck.go191
-rwxr-xr-xmisc/wasm/go_js_wasm_exec17
-rwxr-xr-xmisc/wasm/go_wasip1_wasm_exec23
-rw-r--r--misc/wasm/wasm_exec.html49
-rw-r--r--misc/wasm/wasm_exec.js561
-rw-r--r--misc/wasm/wasm_exec_node.js39
26 files changed, 3294 insertions, 0 deletions
diff --git a/misc/cgo/gmp/fib.go b/misc/cgo/gmp/fib.go
new file mode 100644
index 0000000..48b0700
--- /dev/null
+++ b/misc/cgo/gmp/fib.go
@@ -0,0 +1,45 @@
+// Copyright 2009 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.
+
+//go:build ignore
+
+// Compute Fibonacci numbers with two goroutines
+// that pass integers back and forth. No actual
+// concurrency, just threads and synchronization
+// and foreign code on multiple pthreads.
+
+package main
+
+import (
+ big "."
+ "runtime"
+)
+
+func fibber(c chan *big.Int, out chan string, n int64) {
+ // Keep the fibbers in dedicated operating system
+ // threads, so that this program tests coordination
+ // between pthreads and not just goroutines.
+ runtime.LockOSThread()
+
+ i := big.NewInt(n)
+ if n == 0 {
+ c <- i
+ }
+ for {
+ j := <-c
+ out <- j.String()
+ i.Add(i, j)
+ c <- i
+ }
+}
+
+func main() {
+ c := make(chan *big.Int)
+ out := make(chan string)
+ go fibber(c, out, 0)
+ go fibber(c, out, 1)
+ for i := 0; i < 200; i++ {
+ println(<-out)
+ }
+}
diff --git a/misc/cgo/gmp/gmp.go b/misc/cgo/gmp/gmp.go
new file mode 100644
index 0000000..0835fdc
--- /dev/null
+++ b/misc/cgo/gmp/gmp.go
@@ -0,0 +1,379 @@
+// Copyright 2009 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.
+
+/*
+An example of wrapping a C library in Go. This is the GNU
+multiprecision library gmp's integer type mpz_t wrapped to look like
+the Go package big's integer type Int.
+
+This is a syntactically valid Go program—it can be parsed with the Go
+parser and processed by godoc—but it is not compiled directly by gc.
+Instead, a separate tool, cgo, processes it to produce three output
+files. The first two, 6g.go and 6c.c, are a Go source file for 6g and
+a C source file for 6c; both compile as part of the named package
+(gmp, in this example). The third, gcc.c, is a C source file for gcc;
+it compiles into a shared object (.so) that is dynamically linked into
+any 6.out that imports the first two files.
+
+The stanza
+
+ // #include <gmp.h>
+ import "C"
+
+is a signal to cgo. The doc comment on the import of "C" provides
+additional context for the C file. Here it is just a single #include
+but it could contain arbitrary C definitions to be imported and used.
+
+Cgo recognizes any use of a qualified identifier C.xxx and uses gcc to
+find the definition of xxx. If xxx is a type, cgo replaces C.xxx with
+a Go translation. C arithmetic types translate to precisely-sized Go
+arithmetic types. A C struct translates to a Go struct, field by
+field; unrepresentable fields are replaced with opaque byte arrays. A
+C union translates into a struct containing the first union member and
+perhaps additional padding. C arrays become Go arrays. C pointers
+become Go pointers. C function pointers become Go's uintptr.
+C void pointers become Go's unsafe.Pointer.
+
+For example, mpz_t is defined in <gmp.h> as:
+
+ typedef unsigned long int mp_limb_t;
+
+ typedef struct
+ {
+ int _mp_alloc;
+ int _mp_size;
+ mp_limb_t *_mp_d;
+ } __mpz_struct;
+
+ typedef __mpz_struct mpz_t[1];
+
+Cgo generates:
+
+ type _C_int int32
+ type _C_mp_limb_t uint64
+ type _C___mpz_struct struct {
+ _mp_alloc _C_int;
+ _mp_size _C_int;
+ _mp_d *_C_mp_limb_t;
+ }
+ type _C_mpz_t [1]_C___mpz_struct
+
+and then replaces each occurrence of a type C.xxx with _C_xxx.
+
+If xxx is data, cgo arranges for C.xxx to refer to the C variable,
+with the type translated as described above. To do this, cgo must
+introduce a Go variable that points at the C variable (the linker can
+be told to initialize this pointer). For example, if the gmp library
+provided
+
+ mpz_t zero;
+
+then cgo would rewrite a reference to C.zero by introducing
+
+ var _C_zero *C.mpz_t
+
+and then replacing all instances of C.zero with (*_C_zero).
+
+Cgo's most interesting translation is for functions. If xxx is a C
+function, then cgo rewrites C.xxx into a new function _C_xxx that
+calls the C xxx in a standard pthread. The new function translates
+its arguments, calls xxx, and translates the return value.
+
+Translation of parameters and the return value follows the type
+translation above except that arrays passed as parameters translate
+explicitly in Go to pointers to arrays, as they do (implicitly) in C.
+
+Garbage collection is the big problem. It is fine for the Go world to
+have pointers into the C world and to free those pointers when they
+are no longer needed. To help, the Go code can define Go objects
+holding the C pointers and use runtime.SetFinalizer on those Go objects.
+
+It is much more difficult for the C world to have pointers into the Go
+world, because the Go garbage collector is unaware of the memory
+allocated by C. The most important consideration is not to
+constrain future implementations, so the rule is that Go code can
+hand a Go pointer to C code but must separately arrange for
+Go to hang on to a reference to the pointer until C is done with it.
+*/
+package gmp
+
+/*
+#cgo LDFLAGS: -lgmp
+#include <gmp.h>
+#include <stdlib.h>
+
+// gmp 5.0.0+ changed the type of the 3rd argument to mp_bitcnt_t,
+// so, to support older versions, we wrap these two functions.
+void _mpz_mul_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) {
+ mpz_mul_2exp(a, b, n);
+}
+void _mpz_div_2exp(mpz_ptr a, mpz_ptr b, unsigned long n) {
+ mpz_div_2exp(a, b, n);
+}
+*/
+import "C"
+
+import (
+ "os"
+ "unsafe"
+)
+
+/*
+ * one of a kind
+ */
+
+// An Int represents a signed multi-precision integer.
+// The zero value for an Int represents the value 0.
+type Int struct {
+ i C.mpz_t
+ init bool
+}
+
+// NewInt returns a new Int initialized to x.
+func NewInt(x int64) *Int { return new(Int).SetInt64(x) }
+
+// Int promises that the zero value is a 0, but in gmp
+// the zero value is a crash. To bridge the gap, the
+// init bool says whether this is a valid gmp value.
+// doinit initializes z.i if it needs it. This is not inherent
+// to FFI, just a mismatch between Go's convention of
+// making zero values useful and gmp's decision not to.
+func (z *Int) doinit() {
+ if z.init {
+ return
+ }
+ z.init = true
+ C.mpz_init(&z.i[0])
+}
+
+// Bytes returns z's representation as a big-endian byte array.
+func (z *Int) Bytes() []byte {
+ b := make([]byte, (z.Len()+7)/8)
+ n := C.size_t(len(b))
+ C.mpz_export(unsafe.Pointer(&b[0]), &n, 1, 1, 1, 0, &z.i[0])
+ return b[0:n]
+}
+
+// Len returns the length of z in bits. 0 is considered to have length 1.
+func (z *Int) Len() int {
+ z.doinit()
+ return int(C.mpz_sizeinbase(&z.i[0], 2))
+}
+
+// Set sets z = x and returns z.
+func (z *Int) Set(x *Int) *Int {
+ z.doinit()
+ C.mpz_set(&z.i[0], &x.i[0])
+ return z
+}
+
+// SetBytes interprets b as the bytes of a big-endian integer
+// and sets z to that value.
+func (z *Int) SetBytes(b []byte) *Int {
+ z.doinit()
+ if len(b) == 0 {
+ z.SetInt64(0)
+ } else {
+ C.mpz_import(&z.i[0], C.size_t(len(b)), 1, 1, 1, 0, unsafe.Pointer(&b[0]))
+ }
+ return z
+}
+
+// SetInt64 sets z = x and returns z.
+func (z *Int) SetInt64(x int64) *Int {
+ z.doinit()
+ // TODO(rsc): more work on 32-bit platforms
+ C.mpz_set_si(&z.i[0], C.long(x))
+ return z
+}
+
+// SetString interprets s as a number in the given base
+// and sets z to that value. The base must be in the range [2,36].
+// SetString returns an error if s cannot be parsed or the base is invalid.
+func (z *Int) SetString(s string, base int) error {
+ z.doinit()
+ if base < 2 || base > 36 {
+ return os.ErrInvalid
+ }
+ p := C.CString(s)
+ defer C.free(unsafe.Pointer(p))
+ if C.mpz_set_str(&z.i[0], p, C.int(base)) < 0 {
+ return os.ErrInvalid
+ }
+ return nil
+}
+
+// String returns the decimal representation of z.
+func (z *Int) String() string {
+ if z == nil {
+ return "nil"
+ }
+ z.doinit()
+ p := C.mpz_get_str(nil, 10, &z.i[0])
+ s := C.GoString(p)
+ C.free(unsafe.Pointer(p))
+ return s
+}
+
+func (z *Int) destroy() {
+ if z.init {
+ C.mpz_clear(&z.i[0])
+ }
+ z.init = false
+}
+
+/*
+ * arithmetic
+ */
+
+// Add sets z = x + y and returns z.
+func (z *Int) Add(x, y *Int) *Int {
+ x.doinit()
+ y.doinit()
+ z.doinit()
+ C.mpz_add(&z.i[0], &x.i[0], &y.i[0])
+ return z
+}
+
+// Sub sets z = x - y and returns z.
+func (z *Int) Sub(x, y *Int) *Int {
+ x.doinit()
+ y.doinit()
+ z.doinit()
+ C.mpz_sub(&z.i[0], &x.i[0], &y.i[0])
+ return z
+}
+
+// Mul sets z = x * y and returns z.
+func (z *Int) Mul(x, y *Int) *Int {
+ x.doinit()
+ y.doinit()
+ z.doinit()
+ C.mpz_mul(&z.i[0], &x.i[0], &y.i[0])
+ return z
+}
+
+// Div sets z = x / y, rounding toward zero, and returns z.
+func (z *Int) Div(x, y *Int) *Int {
+ x.doinit()
+ y.doinit()
+ z.doinit()
+ C.mpz_tdiv_q(&z.i[0], &x.i[0], &y.i[0])
+ return z
+}
+
+// Mod sets z = x % y and returns z.
+// Like the result of the Go % operator, z has the same sign as x.
+func (z *Int) Mod(x, y *Int) *Int {
+ x.doinit()
+ y.doinit()
+ z.doinit()
+ C.mpz_tdiv_r(&z.i[0], &x.i[0], &y.i[0])
+ return z
+}
+
+// Lsh sets z = x << s and returns z.
+func (z *Int) Lsh(x *Int, s uint) *Int {
+ x.doinit()
+ z.doinit()
+ C._mpz_mul_2exp(&z.i[0], &x.i[0], C.ulong(s))
+ return z
+}
+
+// Rsh sets z = x >> s and returns z.
+func (z *Int) Rsh(x *Int, s uint) *Int {
+ x.doinit()
+ z.doinit()
+ C._mpz_div_2exp(&z.i[0], &x.i[0], C.ulong(s))
+ return z
+}
+
+// Exp sets z = x^y % m and returns z.
+// If m == nil, Exp sets z = x^y.
+func (z *Int) Exp(x, y, m *Int) *Int {
+ m.doinit()
+ x.doinit()
+ y.doinit()
+ z.doinit()
+ if m == nil {
+ C.mpz_pow_ui(&z.i[0], &x.i[0], C.mpz_get_ui(&y.i[0]))
+ } else {
+ C.mpz_powm(&z.i[0], &x.i[0], &y.i[0], &m.i[0])
+ }
+ return z
+}
+
+func (z *Int) Int64() int64 {
+ if !z.init {
+ return 0
+ }
+ return int64(C.mpz_get_si(&z.i[0]))
+}
+
+// Neg sets z = -x and returns z.
+func (z *Int) Neg(x *Int) *Int {
+ x.doinit()
+ z.doinit()
+ C.mpz_neg(&z.i[0], &x.i[0])
+ return z
+}
+
+// Abs sets z to the absolute value of x and returns z.
+func (z *Int) Abs(x *Int) *Int {
+ x.doinit()
+ z.doinit()
+ C.mpz_abs(&z.i[0], &x.i[0])
+ return z
+}
+
+/*
+ * functions without a clear receiver
+ */
+
+// CmpInt compares x and y. The result is
+//
+// -1 if x < y
+// 0 if x == y
+// +1 if x > y
+func CmpInt(x, y *Int) int {
+ x.doinit()
+ y.doinit()
+ switch cmp := C.mpz_cmp(&x.i[0], &y.i[0]); {
+ case cmp < 0:
+ return -1
+ case cmp == 0:
+ return 0
+ }
+ return +1
+}
+
+// DivModInt sets q = x / y and r = x % y.
+func DivModInt(q, r, x, y *Int) {
+ q.doinit()
+ r.doinit()
+ x.doinit()
+ y.doinit()
+ C.mpz_tdiv_qr(&q.i[0], &r.i[0], &x.i[0], &y.i[0])
+}
+
+// GcdInt sets d to the greatest common divisor of a and b,
+// which must be positive numbers.
+// If x and y are not nil, GcdInt sets x and y such that d = a*x + b*y.
+// If either a or b is not positive, GcdInt sets d = x = y = 0.
+func GcdInt(d, x, y, a, b *Int) {
+ d.doinit()
+ x.doinit()
+ y.doinit()
+ a.doinit()
+ b.doinit()
+ C.mpz_gcdext(&d.i[0], &x.i[0], &y.i[0], &a.i[0], &b.i[0])
+}
+
+// ProbablyPrime performs n Miller-Rabin tests to check whether z is prime.
+// If it returns true, z is prime with probability 1 - 1/4^n.
+// If it returns false, z is not prime.
+func (z *Int) ProbablyPrime(n int) bool {
+ z.doinit()
+ return int(C.mpz_probab_prime_p(&z.i[0], C.int(n))) > 0
+}
diff --git a/misc/cgo/gmp/pi.go b/misc/cgo/gmp/pi.go
new file mode 100644
index 0000000..537a426
--- /dev/null
+++ b/misc/cgo/gmp/pi.go
@@ -0,0 +1,73 @@
+// Copyright 2009 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.
+
+//go:build ignore
+
+package main
+
+import (
+ big "."
+ "fmt"
+ "runtime"
+)
+
+var (
+ tmp1 = big.NewInt(0)
+ tmp2 = big.NewInt(0)
+ numer = big.NewInt(1)
+ accum = big.NewInt(0)
+ denom = big.NewInt(1)
+ ten = big.NewInt(10)
+)
+
+func extractDigit() int64 {
+ if big.CmpInt(numer, accum) > 0 {
+ return -1
+ }
+ tmp1.Lsh(numer, 1).Add(tmp1, numer).Add(tmp1, accum)
+ big.DivModInt(tmp1, tmp2, tmp1, denom)
+ tmp2.Add(tmp2, numer)
+ if big.CmpInt(tmp2, denom) >= 0 {
+ return -1
+ }
+ return tmp1.Int64()
+}
+
+func nextTerm(k int64) {
+ y2 := k*2 + 1
+ accum.Add(accum, tmp1.Lsh(numer, 1))
+ accum.Mul(accum, tmp1.SetInt64(y2))
+ numer.Mul(numer, tmp1.SetInt64(k))
+ denom.Mul(denom, tmp1.SetInt64(y2))
+}
+
+func eliminateDigit(d int64) {
+ accum.Sub(accum, tmp1.Mul(denom, tmp1.SetInt64(d)))
+ accum.Mul(accum, ten)
+ numer.Mul(numer, ten)
+}
+
+func main() {
+ i := 0
+ k := int64(0)
+ for {
+ d := int64(-1)
+ for d < 0 {
+ k++
+ nextTerm(k)
+ d = extractDigit()
+ }
+ eliminateDigit(d)
+ fmt.Printf("%c", d+'0')
+
+ if i++; i%50 == 0 {
+ fmt.Printf("\n")
+ if i >= 1000 {
+ break
+ }
+ }
+ }
+
+ fmt.Printf("\n%d calls; bit sizes: %d %d %d\n", runtime.NumCgoCall(), numer.Len(), accum.Len(), denom.Len())
+}
diff --git a/misc/chrome/gophertool/README.txt b/misc/chrome/gophertool/README.txt
new file mode 100644
index 0000000..a7c0b4b
--- /dev/null
+++ b/misc/chrome/gophertool/README.txt
@@ -0,0 +1,8 @@
+To install:
+
+1) chrome://extensions/
+2) click "[+] Developer Mode" in top right
+3) "Load unpacked extension..."
+4) pick $GOROOT/misc/chrome/gophertool
+
+Done. It'll now auto-reload from source.
diff --git a/misc/chrome/gophertool/background.html b/misc/chrome/gophertool/background.html
new file mode 100644
index 0000000..06daa98
--- /dev/null
+++ b/misc/chrome/gophertool/background.html
@@ -0,0 +1,12 @@
+<html>
+<!--
+ Copyright 2011 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.
+-->
+<head>
+<script src="gopher.js"></script>
+<script src="background.js"></script>
+</head>
+</html>
+
diff --git a/misc/chrome/gophertool/background.js b/misc/chrome/gophertool/background.js
new file mode 100644
index 0000000..79ae05d
--- /dev/null
+++ b/misc/chrome/gophertool/background.js
@@ -0,0 +1,9 @@
+chrome.omnibox.onInputEntered.addListener(function(t) {
+ var url = urlForInput(t);
+ if (url) {
+ chrome.tabs.query({ "active": true, "currentWindow": true }, function(tab) {
+ if (!tab) return;
+ chrome.tabs.update(tab.id, { "url": url, "selected": true });
+ });
+ }
+});
diff --git a/misc/chrome/gophertool/gopher.js b/misc/chrome/gophertool/gopher.js
new file mode 100644
index 0000000..09edb29
--- /dev/null
+++ b/misc/chrome/gophertool/gopher.js
@@ -0,0 +1,41 @@
+// Copyright 2011 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.
+
+var numericRE = /^\d+$/;
+var commitRE = /^(?:\d+:)?([0-9a-f]{6,40})$/; // e.g "8486:ab29d2698a47" or "ab29d2698a47"
+var gerritChangeIdRE = /^I[0-9a-f]{4,40}$/; // e.g. Id69c00d908d18151486007ec03da5495b34b05f5
+var pkgRE = /^[a-z0-9_\/]+$/;
+
+function urlForInput(t) {
+ if (!t) {
+ return null;
+ }
+
+ if (numericRE.test(t)) {
+ if (t < 150000) {
+ // We could use the golang.org/cl/ handler here, but
+ // avoid some redirect latency and go right there, since
+ // one is easy. (no server-side mapping)
+ return "https://github.com/golang/go/issues/" + t;
+ }
+ return "https://golang.org/cl/" + t;
+ }
+
+ if (gerritChangeIdRE.test(t)) {
+ return "https://golang.org/cl/" + t;
+ }
+
+ var match = commitRE.exec(t);
+ if (match) {
+ return "https://golang.org/change/" + match[1];
+ }
+
+ if (pkgRE.test(t)) {
+ // TODO: make this smarter, using a list of packages + substring matches.
+ // Get the list from godoc itself in JSON format?
+ return "https://golang.org/pkg/" + t;
+ }
+
+ return null;
+}
diff --git a/misc/chrome/gophertool/gopher.png b/misc/chrome/gophertool/gopher.png
new file mode 100644
index 0000000..0d1abb7
--- /dev/null
+++ b/misc/chrome/gophertool/gopher.png
Binary files differ
diff --git a/misc/chrome/gophertool/manifest.json b/misc/chrome/gophertool/manifest.json
new file mode 100644
index 0000000..0438659
--- /dev/null
+++ b/misc/chrome/gophertool/manifest.json
@@ -0,0 +1,20 @@
+{
+ "name": "Hacking Gopher",
+ "version": "1.0",
+ "manifest_version": 2,
+ "description": "Go Hacking utility",
+ "background": {
+ "page": "background.html"
+ },
+ "browser_action": {
+ "default_icon": "gopher.png",
+ "default_popup": "popup.html"
+ },
+ "omnibox": { "keyword": "golang" },
+ "icons": {
+ "16": "gopher.png"
+ },
+ "permissions": [
+ "tabs"
+ ]
+}
diff --git a/misc/chrome/gophertool/popup.html b/misc/chrome/gophertool/popup.html
new file mode 100644
index 0000000..ad42a38
--- /dev/null
+++ b/misc/chrome/gophertool/popup.html
@@ -0,0 +1,21 @@
+<html>
+<!--
+ Copyright 2011 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.
+-->
+<head>
+<script src="gopher.js"></script>
+<script src="popup.js"></script>
+</head>
+<body style='margin: 0.5em; font-family: sans;'>
+<small><a href="#" url="https://golang.org/issue">issue</a>,
+<a href="#" url="https://golang.org/cl">codereview</a>,
+<a href="#" url="https://golang.org/change">commit</a>, or
+<a href="#" url="https://golang.org/pkg/">pkg</a> id/name:</small>
+<form style='margin: 0' id='navform'><nobr><input id="inputbox" size=10 tabindex=1 /><input type="submit" value="go" /></nobr></form>
+<small>Also: <a href="#" url="https://build.golang.org">buildbots</a>
+<a href="#" url="https://github.com/golang/go">GitHub</a>
+</small>
+</body>
+</html>
diff --git a/misc/chrome/gophertool/popup.js b/misc/chrome/gophertool/popup.js
new file mode 100644
index 0000000..410d651
--- /dev/null
+++ b/misc/chrome/gophertool/popup.js
@@ -0,0 +1,46 @@
+function openURL(url) {
+ chrome.tabs.create({ "url": url })
+}
+
+function addLinks() {
+ var links = document.getElementsByTagName("a");
+ for (var i = 0; i < links.length; i++) {
+ var url = links[i].getAttribute("url");
+ if (url)
+ links[i].addEventListener("click", function () {
+ openURL(this.getAttribute("url"));
+ });
+ }
+}
+
+window.addEventListener("load", function () {
+ addLinks();
+ console.log("hacking gopher pop-up loaded.");
+ document.getElementById("inputbox").focus();
+});
+
+window.addEventListener("submit", function () {
+ console.log("submitting form");
+ var box = document.getElementById("inputbox");
+ box.focus();
+
+ var t = box.value;
+ if (t == "") {
+ return false;
+ }
+
+ var success = function(url) {
+ console.log("matched " + t + " to: " + url)
+ box.value = "";
+ openURL(url);
+ return false; // cancel form submission
+ };
+
+ var url = urlForInput(t);
+ if (url) {
+ return success(url);
+ }
+
+ console.log("no match for text: " + t)
+ return false;
+});
diff --git a/misc/editors b/misc/editors
new file mode 100644
index 0000000..3a0f73f
--- /dev/null
+++ b/misc/editors
@@ -0,0 +1,5 @@
+For information about plugins and other support for Go in editors and shells,
+see this page on the Go Wiki:
+
+https://golang.org/wiki/IDEsAndTextEditorPlugins
+
diff --git a/misc/go.mod b/misc/go.mod
new file mode 100644
index 0000000..d5494b1
--- /dev/null
+++ b/misc/go.mod
@@ -0,0 +1,6 @@
+// Module misc contains binaries that pertain to specific platforms
+// (Android, iOS, and WebAssembly), as well as some miscellaneous
+// tests and tools.
+module misc
+
+go 1.21
diff --git a/misc/go_android_exec/README b/misc/go_android_exec/README
new file mode 100644
index 0000000..13b59d9
--- /dev/null
+++ b/misc/go_android_exec/README
@@ -0,0 +1,25 @@
+Android
+=======
+
+For details on developing Go for Android, see the documentation in the
+mobile subrepository:
+
+ https://github.com/golang/mobile
+
+To run the standard library tests, enable Cgo and use an appropriate
+C compiler from the Android NDK. For example,
+
+ CGO_ENABLED=1 \
+ GOOS=android \
+ GOARCH=arm64 \
+ CC_FOR_TARGET=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \
+ ./all.bash
+
+To run tests on the Android device, add the bin directory to PATH so the
+go tool can find the go_android_$GOARCH_exec wrapper generated by
+make.bash. For example, to run the go1 benchmarks
+
+ export PATH=$GOROOT/bin:$PATH
+ cd $GOROOT/test/bench/go1/
+ GOOS=android GOARCH=arm64 go test -bench=. -count=N -timeout=T
+
diff --git a/misc/go_android_exec/exitcode_test.go b/misc/go_android_exec/exitcode_test.go
new file mode 100644
index 0000000..4ad2f60
--- /dev/null
+++ b/misc/go_android_exec/exitcode_test.go
@@ -0,0 +1,76 @@
+// Copyright 2023 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.
+
+//go:build !(windows || js || wasip1)
+
+package main
+
+import (
+ "regexp"
+ "strings"
+ "testing"
+)
+
+func TestExitCodeFilter(t *testing.T) {
+ // Write text to the filter one character at a time.
+ var out strings.Builder
+ f, exitStr := newExitCodeFilter(&out)
+ // Embed a "fake" exit code in the middle to check that we don't get caught on it.
+ pre := "abc" + exitStr + "123def"
+ text := pre + exitStr + `1`
+ for i := 0; i < len(text); i++ {
+ _, err := f.Write([]byte{text[i]})
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ // The "pre" output should all have been flushed already.
+ if want, got := pre, out.String(); want != got {
+ t.Errorf("filter should have already flushed %q, but flushed %q", want, got)
+ }
+
+ code, err := f.Finish()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Nothing more should have been written to out.
+ if want, got := pre, out.String(); want != got {
+ t.Errorf("want output %q, got %q", want, got)
+ }
+ if want := 1; want != code {
+ t.Errorf("want exit code %d, got %d", want, code)
+ }
+}
+
+func TestExitCodeMissing(t *testing.T) {
+ var wantErr *regexp.Regexp
+ check := func(text string) {
+ t.Helper()
+ var out strings.Builder
+ f, exitStr := newExitCodeFilter(&out)
+ if want := "exitcode="; want != exitStr {
+ t.Fatalf("test assumes exitStr will be %q, but got %q", want, exitStr)
+ }
+ f.Write([]byte(text))
+ _, err := f.Finish()
+ // We should get a no exit code error
+ if err == nil || !wantErr.MatchString(err.Error()) {
+ t.Errorf("want error matching %s, got %s", wantErr, err)
+ }
+ // And it should flush all output (even if it looks
+ // like we may be getting an exit code)
+ if got := out.String(); text != got {
+ t.Errorf("want full output %q, got %q", text, got)
+ }
+ }
+ wantErr = regexp.MustCompile("^no exit code")
+ check("abc")
+ check("exitcode")
+ check("exitcode=")
+ check("exitcode=123\n")
+ wantErr = regexp.MustCompile("^bad exit code: .* value out of range")
+ check("exitcode=999999999999999999999999")
+}
diff --git a/misc/go_android_exec/main.go b/misc/go_android_exec/main.go
new file mode 100644
index 0000000..554810c
--- /dev/null
+++ b/misc/go_android_exec/main.go
@@ -0,0 +1,526 @@
+// Copyright 2014 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.
+
+// This wrapper uses syscall.Flock to prevent concurrent adb commands,
+// so for now it only builds on platforms that support that system call.
+// TODO(#33974): use a more portable library for file locking.
+
+//go:build darwin || dragonfly || freebsd || illumos || linux || netbsd || openbsd
+
+// This program can be used as go_android_GOARCH_exec by the Go tool.
+// It executes binaries on an android device using adb.
+package main
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "os/signal"
+ "path"
+ "path/filepath"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+)
+
+func adbRun(args string) (int, error) {
+ // The exit code of adb is often wrong. In theory it was fixed in 2016
+ // (https://code.google.com/p/android/issues/detail?id=3254), but it's
+ // still broken on our builders in 2023. Instead, append the exitcode to
+ // the output and parse it from there.
+ filter, exitStr := newExitCodeFilter(os.Stdout)
+ args += "; echo -n " + exitStr + "$?"
+
+ cmd := adbCmd("exec-out", args)
+ cmd.Stdout = filter
+ // If the adb subprocess somehow hangs, go test will kill this wrapper
+ // and wait for our os.Stderr (and os.Stdout) to close as a result.
+ // However, if the os.Stderr (or os.Stdout) file descriptors are
+ // passed on, the hanging adb subprocess will hold them open and
+ // go test will hang forever.
+ //
+ // Avoid that by wrapping stderr, breaking the short circuit and
+ // forcing cmd.Run to use another pipe and goroutine to pass
+ // along stderr from adb.
+ cmd.Stderr = struct{ io.Writer }{os.Stderr}
+ err := cmd.Run()
+
+ // Before we process err, flush any further output and get the exit code.
+ exitCode, err2 := filter.Finish()
+
+ if err != nil {
+ return 0, fmt.Errorf("adb exec-out %s: %v", args, err)
+ }
+ return exitCode, err2
+}
+
+func adb(args ...string) error {
+ if out, err := adbCmd(args...).CombinedOutput(); err != nil {
+ fmt.Fprintf(os.Stderr, "adb %s\n%s", strings.Join(args, " "), out)
+ return err
+ }
+ return nil
+}
+
+func adbCmd(args ...string) *exec.Cmd {
+ if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
+ args = append(strings.Split(flags, " "), args...)
+ }
+ return exec.Command("adb", args...)
+}
+
+const (
+ deviceRoot = "/data/local/tmp/go_android_exec"
+ deviceGoroot = deviceRoot + "/goroot"
+)
+
+func main() {
+ log.SetFlags(0)
+ log.SetPrefix("go_android_exec: ")
+ exitCode, err := runMain()
+ if err != nil {
+ log.Fatal(err)
+ }
+ os.Exit(exitCode)
+}
+
+func runMain() (int, error) {
+ // Concurrent use of adb is flaky, so serialize adb commands.
+ // See https://github.com/golang/go/issues/23795 or
+ // https://issuetracker.google.com/issues/73230216.
+ lockPath := filepath.Join(os.TempDir(), "go_android_exec-adb-lock")
+ lock, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0666)
+ if err != nil {
+ return 0, err
+ }
+ defer lock.Close()
+ if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
+ return 0, err
+ }
+
+ // In case we're booting a device or emulator alongside all.bash, wait for
+ // it to be ready. adb wait-for-device is not enough, we have to
+ // wait for sys.boot_completed.
+ if err := adb("wait-for-device", "exec-out", "while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"); err != nil {
+ return 0, err
+ }
+
+ // Done once per make.bash.
+ if err := adbCopyGoroot(); err != nil {
+ return 0, err
+ }
+
+ // Prepare a temporary directory that will be cleaned up at the end.
+ // Binary names can conflict.
+ // E.g. template.test from the {html,text}/template packages.
+ binName := filepath.Base(os.Args[1])
+ deviceGotmp := fmt.Sprintf(deviceRoot+"/%s-%d", binName, os.Getpid())
+ deviceGopath := deviceGotmp + "/gopath"
+ defer adb("exec-out", "rm", "-rf", deviceGotmp) // Clean up.
+
+ // Determine the package by examining the current working
+ // directory, which will look something like
+ // "$GOROOT/src/mime/multipart" or "$GOPATH/src/golang.org/x/mobile".
+ // We extract everything after the $GOROOT or $GOPATH to run on the
+ // same relative directory on the target device.
+ importPath, isStd, modPath, modDir, err := pkgPath()
+ if err != nil {
+ return 0, err
+ }
+ var deviceCwd string
+ if isStd {
+ // Note that we use path.Join here instead of filepath.Join:
+ // The device paths should be slash-separated even if the go_android_exec
+ // wrapper itself is compiled for Windows.
+ deviceCwd = path.Join(deviceGoroot, "src", importPath)
+ } else {
+ deviceCwd = path.Join(deviceGopath, "src", importPath)
+ if modDir != "" {
+ // In module mode, the user may reasonably expect the entire module
+ // to be present. Copy it over.
+ deviceModDir := path.Join(deviceGopath, "src", modPath)
+ if err := adb("exec-out", "mkdir", "-p", path.Dir(deviceModDir)); err != nil {
+ return 0, err
+ }
+ // We use a single recursive 'adb push' of the module root instead of
+ // walking the tree and copying it piecewise. If the directory tree
+ // contains nested modules this could push a lot of unnecessary contents,
+ // but for the golang.org/x repos it seems to be significantly (~2x)
+ // faster than copying one file at a time (via filepath.WalkDir),
+ // apparently due to high latency in 'adb' commands.
+ if err := adb("push", modDir, deviceModDir); err != nil {
+ return 0, err
+ }
+ } else {
+ if err := adb("exec-out", "mkdir", "-p", deviceCwd); err != nil {
+ return 0, err
+ }
+ if err := adbCopyTree(deviceCwd, importPath); err != nil {
+ return 0, err
+ }
+
+ // Copy .go files from the package.
+ goFiles, err := filepath.Glob("*.go")
+ if err != nil {
+ return 0, err
+ }
+ if len(goFiles) > 0 {
+ args := append(append([]string{"push"}, goFiles...), deviceCwd)
+ if err := adb(args...); err != nil {
+ return 0, err
+ }
+ }
+ }
+ }
+
+ deviceBin := fmt.Sprintf("%s/%s", deviceGotmp, binName)
+ if err := adb("push", os.Args[1], deviceBin); err != nil {
+ return 0, err
+ }
+
+ // Forward SIGQUIT from the go command to show backtraces from
+ // the binary instead of from this wrapper.
+ quit := make(chan os.Signal, 1)
+ signal.Notify(quit, syscall.SIGQUIT)
+ go func() {
+ for range quit {
+ // We don't have the PID of the running process; use the
+ // binary name instead.
+ adb("exec-out", "killall -QUIT "+binName)
+ }
+ }()
+ cmd := `export TMPDIR="` + deviceGotmp + `"` +
+ `; export GOROOT="` + deviceGoroot + `"` +
+ `; export GOPATH="` + deviceGopath + `"` +
+ `; export CGO_ENABLED=0` +
+ `; export GOPROXY=` + os.Getenv("GOPROXY") +
+ `; export GOCACHE="` + deviceRoot + `/gocache"` +
+ `; export PATH="` + deviceGoroot + `/bin":$PATH` +
+ `; cd "` + deviceCwd + `"` +
+ "; '" + deviceBin + "' " + strings.Join(os.Args[2:], " ")
+ code, err := adbRun(cmd)
+ signal.Reset(syscall.SIGQUIT)
+ close(quit)
+ return code, err
+}
+
+type exitCodeFilter struct {
+ w io.Writer // Pass through to w
+ exitRe *regexp.Regexp
+ buf bytes.Buffer
+}
+
+func newExitCodeFilter(w io.Writer) (*exitCodeFilter, string) {
+ const exitStr = "exitcode="
+
+ // Build a regexp that matches any prefix of the exit string at the end of
+ // the input. We do it this way to avoid assuming anything about the
+ // subcommand output (e.g., it might not be \n-terminated).
+ var exitReStr strings.Builder
+ for i := 1; i <= len(exitStr); i++ {
+ fmt.Fprintf(&exitReStr, "%s$|", exitStr[:i])
+ }
+ // Finally, match the exit string along with an exit code.
+ // This is the only case we use a group, and we'll use this
+ // group to extract the numeric code.
+ fmt.Fprintf(&exitReStr, "%s([0-9]+)$", exitStr)
+ exitRe := regexp.MustCompile(exitReStr.String())
+
+ return &exitCodeFilter{w: w, exitRe: exitRe}, exitStr
+}
+
+func (f *exitCodeFilter) Write(data []byte) (int, error) {
+ n := len(data)
+ f.buf.Write(data)
+ // Flush to w until a potential match of exitRe
+ b := f.buf.Bytes()
+ match := f.exitRe.FindIndex(b)
+ if match == nil {
+ // Flush all of the buffer.
+ _, err := f.w.Write(b)
+ f.buf.Reset()
+ if err != nil {
+ return n, err
+ }
+ } else {
+ // Flush up to the beginning of the (potential) match.
+ _, err := f.w.Write(b[:match[0]])
+ f.buf.Next(match[0])
+ if err != nil {
+ return n, err
+ }
+ }
+ return n, nil
+}
+
+func (f *exitCodeFilter) Finish() (int, error) {
+ // f.buf could be empty, contain a partial match of exitRe, or
+ // contain a full match.
+ b := f.buf.Bytes()
+ defer f.buf.Reset()
+ match := f.exitRe.FindSubmatch(b)
+ if len(match) < 2 || match[1] == nil {
+ // Not a full match. Flush.
+ if _, err := f.w.Write(b); err != nil {
+ return 0, err
+ }
+ return 0, fmt.Errorf("no exit code (in %q)", string(b))
+ }
+
+ // Parse the exit code.
+ code, err := strconv.Atoi(string(match[1]))
+ if err != nil {
+ // Something is malformed. Flush.
+ if _, err := f.w.Write(b); err != nil {
+ return 0, err
+ }
+ return 0, fmt.Errorf("bad exit code: %v (in %q)", err, string(b))
+ }
+ return code, nil
+}
+
+// pkgPath determines the package import path of the current working directory,
+// and indicates whether it is
+// and returns the path to the package source relative to $GOROOT (or $GOPATH).
+func pkgPath() (importPath string, isStd bool, modPath, modDir string, err error) {
+ errorf := func(format string, args ...any) (string, bool, string, string, error) {
+ return "", false, "", "", fmt.Errorf(format, args...)
+ }
+ goTool, err := goTool()
+ if err != nil {
+ return errorf("%w", err)
+ }
+ cmd := exec.Command(goTool, "list", "-e", "-f", "{{.ImportPath}}:{{.Standard}}{{with .Module}}:{{.Path}}:{{.Dir}}{{end}}", ".")
+ out, err := cmd.Output()
+ if err != nil {
+ if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
+ return errorf("%v: %s", cmd, ee.Stderr)
+ }
+ return errorf("%v: %w", cmd, err)
+ }
+
+ parts := strings.SplitN(string(bytes.TrimSpace(out)), ":", 4)
+ if len(parts) < 2 {
+ return errorf("%v: missing ':' in output: %q", cmd, out)
+ }
+ importPath = parts[0]
+ if importPath == "" || importPath == "." {
+ return errorf("current directory does not have a Go import path")
+ }
+ isStd, err = strconv.ParseBool(parts[1])
+ if err != nil {
+ return errorf("%v: non-boolean .Standard in output: %q", cmd, out)
+ }
+ if len(parts) >= 4 {
+ modPath = parts[2]
+ modDir = parts[3]
+ }
+
+ return importPath, isStd, modPath, modDir, nil
+}
+
+// adbCopyTree copies testdata, go.mod, go.sum files from subdir
+// and from parent directories all the way up to the root of subdir.
+// go.mod and go.sum files are needed for the go tool modules queries,
+// and the testdata directories for tests. It is common for tests to
+// reach out into testdata from parent packages.
+func adbCopyTree(deviceCwd, subdir string) error {
+ dir := ""
+ for {
+ for _, name := range []string{"testdata", "go.mod", "go.sum"} {
+ hostPath := filepath.Join(dir, name)
+ if _, err := os.Stat(hostPath); err != nil {
+ continue
+ }
+ devicePath := path.Join(deviceCwd, dir)
+ if err := adb("exec-out", "mkdir", "-p", devicePath); err != nil {
+ return err
+ }
+ if err := adb("push", hostPath, devicePath); err != nil {
+ return err
+ }
+ }
+ if subdir == "." {
+ break
+ }
+ subdir = filepath.Dir(subdir)
+ dir = path.Join(dir, "..")
+ }
+ return nil
+}
+
+// adbCopyGoroot clears deviceRoot for previous versions of GOROOT, GOPATH
+// and temporary data. Then, it copies relevant parts of GOROOT to the device,
+// including the go tool built for android.
+// A lock file ensures this only happens once, even with concurrent exec
+// wrappers.
+func adbCopyGoroot() error {
+ goTool, err := goTool()
+ if err != nil {
+ return err
+ }
+ cmd := exec.Command(goTool, "version")
+ cmd.Stderr = os.Stderr
+ out, err := cmd.Output()
+ if err != nil {
+ return fmt.Errorf("%v: %w", cmd, err)
+ }
+ goVersion := string(out)
+
+ // Also known by cmd/dist. The bootstrap command deletes the file.
+ statPath := filepath.Join(os.TempDir(), "go_android_exec-adb-sync-status")
+ stat, err := os.OpenFile(statPath, os.O_CREATE|os.O_RDWR, 0666)
+ if err != nil {
+ return err
+ }
+ defer stat.Close()
+ // Serialize check and copying.
+ if err := syscall.Flock(int(stat.Fd()), syscall.LOCK_EX); err != nil {
+ return err
+ }
+ s, err := io.ReadAll(stat)
+ if err != nil {
+ return err
+ }
+ if string(s) == goVersion {
+ return nil
+ }
+
+ goroot, err := findGoroot()
+ if err != nil {
+ return err
+ }
+
+ // Delete the device's GOROOT, GOPATH and any leftover test data,
+ // and recreate GOROOT.
+ if err := adb("exec-out", "rm", "-rf", deviceRoot); err != nil {
+ return err
+ }
+
+ // Build Go for Android.
+ cmd = exec.Command(goTool, "install", "cmd")
+ out, err = cmd.CombinedOutput()
+ if err != nil {
+ if len(bytes.TrimSpace(out)) > 0 {
+ log.Printf("\n%s", out)
+ }
+ return fmt.Errorf("%v: %w", cmd, err)
+ }
+ if err := adb("exec-out", "mkdir", "-p", deviceGoroot); err != nil {
+ return err
+ }
+
+ // Copy the Android tools from the relevant bin subdirectory to GOROOT/bin.
+ cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/go")
+ cmd.Stderr = os.Stderr
+ out, err = cmd.Output()
+ if err != nil {
+ return fmt.Errorf("%v: %w", cmd, err)
+ }
+ platformBin := filepath.Dir(string(bytes.TrimSpace(out)))
+ if platformBin == "." {
+ return errors.New("failed to locate cmd/go for target platform")
+ }
+ if err := adb("push", platformBin, path.Join(deviceGoroot, "bin")); err != nil {
+ return err
+ }
+
+ // Copy only the relevant subdirectories from pkg: pkg/include and the
+ // platform-native binaries in pkg/tool.
+ if err := adb("exec-out", "mkdir", "-p", path.Join(deviceGoroot, "pkg", "tool")); err != nil {
+ return err
+ }
+ if err := adb("push", filepath.Join(goroot, "pkg", "include"), path.Join(deviceGoroot, "pkg", "include")); err != nil {
+ return err
+ }
+
+ cmd = exec.Command(goTool, "list", "-f", "{{.Target}}", "cmd/compile")
+ cmd.Stderr = os.Stderr
+ out, err = cmd.Output()
+ if err != nil {
+ return fmt.Errorf("%v: %w", cmd, err)
+ }
+ platformToolDir := filepath.Dir(string(bytes.TrimSpace(out)))
+ if platformToolDir == "." {
+ return errors.New("failed to locate cmd/compile for target platform")
+ }
+ relToolDir, err := filepath.Rel(filepath.Join(goroot), platformToolDir)
+ if err != nil {
+ return err
+ }
+ if err := adb("push", platformToolDir, path.Join(deviceGoroot, relToolDir)); err != nil {
+ return err
+ }
+
+ // Copy all other files from GOROOT.
+ dirents, err := os.ReadDir(goroot)
+ if err != nil {
+ return err
+ }
+ for _, de := range dirents {
+ switch de.Name() {
+ case "bin", "pkg":
+ // We already created GOROOT/bin and GOROOT/pkg above; skip those.
+ continue
+ }
+ if err := adb("push", filepath.Join(goroot, de.Name()), path.Join(deviceGoroot, de.Name())); err != nil {
+ return err
+ }
+ }
+
+ if _, err := stat.WriteString(goVersion); err != nil {
+ return err
+ }
+ return nil
+}
+
+func findGoroot() (string, error) {
+ gorootOnce.Do(func() {
+ // If runtime.GOROOT reports a non-empty path, assume that it is valid.
+ // (It may be empty if this binary was built with -trimpath.)
+ gorootPath = runtime.GOROOT()
+ if gorootPath != "" {
+ return
+ }
+
+ // runtime.GOROOT is empty — perhaps go_android_exec was built with
+ // -trimpath and GOROOT is unset. Try 'go env GOROOT' as a fallback,
+ // assuming that the 'go' command in $PATH is the correct one.
+
+ cmd := exec.Command("go", "env", "GOROOT")
+ cmd.Stderr = os.Stderr
+ out, err := cmd.Output()
+ if err != nil {
+ gorootErr = fmt.Errorf("%v: %w", cmd, err)
+ }
+
+ gorootPath = string(bytes.TrimSpace(out))
+ if gorootPath == "" {
+ gorootErr = errors.New("GOROOT not found")
+ }
+ })
+
+ return gorootPath, gorootErr
+}
+
+func goTool() (string, error) {
+ goroot, err := findGoroot()
+ if err != nil {
+ return "", err
+ }
+ return filepath.Join(goroot, "bin", "go"), nil
+}
+
+var (
+ gorootOnce sync.Once
+ gorootPath string
+ gorootErr error
+)
diff --git a/misc/ios/README b/misc/ios/README
new file mode 100644
index 0000000..0f5e9e3
--- /dev/null
+++ b/misc/ios/README
@@ -0,0 +1,57 @@
+Go on iOS
+=========
+
+To run the standard library tests, run all.bash as usual, but with the compiler
+set to the clang wrapper that invokes clang for iOS. For example, this command runs
+ all.bash on the iOS emulator:
+
+ GOOS=ios GOARCH=amd64 CGO_ENABLED=1 CC_FOR_TARGET=$(pwd)/../misc/ios/clangwrap.sh ./all.bash
+
+If CC_FOR_TARGET is not set when the toolchain is built (make.bash or all.bash), CC
+can be set on the command line. For example,
+
+ GOOS=ios GOARCH=amd64 CGO_ENABLED=1 CC=$(go env GOROOT)/misc/ios/clangwrap.sh go build
+
+Setting CC is not necessary if the toolchain is built with CC_FOR_TARGET set.
+
+To use the go tool to run individual programs and tests, put $GOROOT/bin into PATH to ensure
+the go_ios_$GOARCH_exec wrapper is found. For example, to run the archive/tar tests:
+
+ export PATH=$GOROOT/bin:$PATH
+ GOOS=ios GOARCH=amd64 CGO_ENABLED=1 go test archive/tar
+
+The go_ios_exec wrapper uses GOARCH to select the emulator (amd64) or the device (arm64).
+However, further setup is required to run tests or programs directly on a device.
+
+First make sure you have a valid developer certificate and have setup your device properly
+to run apps signed by your developer certificate. Then install the libimobiledevice and
+ideviceinstaller tools from https://www.libimobiledevice.org/. Use the HEAD versions from
+source; the stable versions have bugs that prevents the Go exec wrapper to install and run
+apps.
+
+Second, the Go exec wrapper must be told the developer account signing identity, the team
+id and a provisioned bundle id to use. They're specified with the environment variables
+GOIOS_DEV_ID, GOIOS_TEAM_ID and GOIOS_APP_ID. The detect.go program in this directory will
+attempt to auto-detect suitable values. Run it as
+
+ go run detect.go
+
+which will output something similar to
+
+ export GOIOS_DEV_ID="iPhone Developer: xxx@yyy.zzz (XXXXXXXX)"
+ export GOIOS_APP_ID=YYYYYYYY.some.bundle.id
+ export GOIOS_TEAM_ID=ZZZZZZZZ
+
+If you have multiple devices connected, specify the device UDID with the GOIOS_DEVICE_ID
+variable. Use `idevice_id -l` to list all available UDIDs. Then, setting GOARCH to arm64
+will select the device:
+
+ GOOS=ios GOARCH=arm64 CGO_ENABLED=1 CC_FOR_TARGET=$(pwd)/../misc/ios/clangwrap.sh ./all.bash
+
+Note that the go_darwin_$GOARCH_exec wrapper uninstalls any existing app identified by
+the bundle id before installing a new app. If the uninstalled app is the last app by
+the developer identity, the device might also remove the permission to run apps from
+that developer, and the exec wrapper will fail to install the new app. To avoid that,
+install another app with the same developer identity but with a different bundle id.
+That way, the permission to install apps is held on to while the primary app is
+uninstalled.
diff --git a/misc/ios/clangwrap.sh b/misc/ios/clangwrap.sh
new file mode 100755
index 0000000..8f7b439
--- /dev/null
+++ b/misc/ios/clangwrap.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+# This uses the latest available iOS SDK, which is recommended.
+# To select a specific SDK, run 'xcodebuild -showsdks'
+# to see the available SDKs and replace iphoneos with one of them.
+if [ "$GOARCH" == "arm64" ]; then
+ SDK=iphoneos
+ PLATFORM=ios
+ CLANGARCH="arm64"
+else
+ SDK=iphonesimulator
+ PLATFORM=ios-simulator
+ CLANGARCH="x86_64"
+fi
+
+SDK_PATH=`xcrun --sdk $SDK --show-sdk-path`
+export IPHONEOS_DEPLOYMENT_TARGET=5.1
+# cmd/cgo doesn't support llvm-gcc-4.2, so we have to use clang.
+CLANG=`xcrun --sdk $SDK --find clang`
+
+exec "$CLANG" -arch $CLANGARCH -isysroot "$SDK_PATH" -m${PLATFORM}-version-min=12.0 "$@"
diff --git a/misc/ios/detect.go b/misc/ios/detect.go
new file mode 100644
index 0000000..1cb8ae5
--- /dev/null
+++ b/misc/ios/detect.go
@@ -0,0 +1,134 @@
+// Copyright 2015 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.
+
+//go:build ignore
+// +build ignore
+
+// detect attempts to autodetect the correct
+// values of the environment variables
+// used by go_ios_exec.
+// detect shells out to ideviceinfo, a third party program that can
+// be obtained by following the instructions at
+// https://github.com/libimobiledevice/libimobiledevice.
+package main
+
+import (
+ "bytes"
+ "crypto/x509"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+func main() {
+ udids := getLines(exec.Command("idevice_id", "-l"))
+ if len(udids) == 0 {
+ fail("no udid found; is a device connected?")
+ }
+
+ mps := detectMobileProvisionFiles(udids)
+ if len(mps) == 0 {
+ fail("did not find mobile provision matching device udids %q", udids)
+ }
+
+ fmt.Println("# Available provisioning profiles below.")
+ fmt.Println("# NOTE: Any existing app on the device with the app id specified by GOIOS_APP_ID")
+ fmt.Println("# will be overwritten when running Go programs.")
+ for _, mp := range mps {
+ fmt.Println()
+ f, err := os.CreateTemp("", "go_ios_detect_")
+ check(err)
+ fname := f.Name()
+ defer os.Remove(fname)
+
+ out := output(parseMobileProvision(mp))
+ _, err = f.Write(out)
+ check(err)
+ check(f.Close())
+
+ cert, err := plistExtract(fname, "DeveloperCertificates:0")
+ check(err)
+ pcert, err := x509.ParseCertificate(cert)
+ check(err)
+ fmt.Printf("export GOIOS_DEV_ID=\"%s\"\n", pcert.Subject.CommonName)
+
+ appID, err := plistExtract(fname, "Entitlements:application-identifier")
+ check(err)
+ fmt.Printf("export GOIOS_APP_ID=%s\n", appID)
+
+ teamID, err := plistExtract(fname, "Entitlements:com.apple.developer.team-identifier")
+ check(err)
+ fmt.Printf("export GOIOS_TEAM_ID=%s\n", teamID)
+ }
+}
+
+func detectMobileProvisionFiles(udids [][]byte) []string {
+ cmd := exec.Command("mdfind", "-name", ".mobileprovision")
+ lines := getLines(cmd)
+
+ var files []string
+ for _, line := range lines {
+ if len(line) == 0 {
+ continue
+ }
+ xmlLines := getLines(parseMobileProvision(string(line)))
+ matches := 0
+ for _, udid := range udids {
+ for _, xmlLine := range xmlLines {
+ if bytes.Contains(xmlLine, udid) {
+ matches++
+ }
+ }
+ }
+ if matches == len(udids) {
+ files = append(files, string(line))
+ }
+ }
+ return files
+}
+
+func parseMobileProvision(fname string) *exec.Cmd {
+ return exec.Command("security", "cms", "-D", "-i", string(fname))
+}
+
+func plistExtract(fname string, path string) ([]byte, error) {
+ out, err := exec.Command("/usr/libexec/PlistBuddy", "-c", "Print "+path, fname).CombinedOutput()
+ if err != nil {
+ return nil, err
+ }
+ return bytes.TrimSpace(out), nil
+}
+
+func getLines(cmd *exec.Cmd) [][]byte {
+ out := output(cmd)
+ lines := bytes.Split(out, []byte("\n"))
+ // Skip the empty line at the end.
+ if len(lines[len(lines)-1]) == 0 {
+ lines = lines[:len(lines)-1]
+ }
+ return lines
+}
+
+func output(cmd *exec.Cmd) []byte {
+ out, err := cmd.Output()
+ if err != nil {
+ fmt.Println(strings.Join(cmd.Args, "\n"))
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ return out
+}
+
+func check(err error) {
+ if err != nil {
+ fail(err.Error())
+ }
+}
+
+func fail(msg string, v ...interface{}) {
+ fmt.Fprintf(os.Stderr, msg, v...)
+ fmt.Fprintln(os.Stderr)
+ os.Exit(1)
+}
diff --git a/misc/ios/go_ios_exec.go b/misc/ios/go_ios_exec.go
new file mode 100644
index 0000000..c275dd3
--- /dev/null
+++ b/misc/ios/go_ios_exec.go
@@ -0,0 +1,911 @@
+// Copyright 2015 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.
+
+// This program can be used as go_ios_$GOARCH_exec by the Go tool.
+// It executes binaries on an iOS device using the XCode toolchain
+// and the ios-deploy program: https://github.com/phonegap/ios-deploy
+//
+// This script supports an extra flag, -lldb, that pauses execution
+// just before the main program begins and allows the user to control
+// the remote lldb session. This flag is appended to the end of the
+// script's arguments and is not passed through to the underlying
+// binary.
+//
+// This script requires that three environment variables be set:
+//
+// GOIOS_DEV_ID: The codesigning developer id or certificate identifier
+// GOIOS_APP_ID: The provisioning app id prefix. Must support wildcard app ids.
+// GOIOS_TEAM_ID: The team id that owns the app id prefix.
+//
+// $GOROOT/misc/ios contains a script, detect.go, that attempts to autodetect these.
+package main
+
+import (
+ "bytes"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "go/build"
+ "io"
+ "log"
+ "net"
+ "os"
+ "os/exec"
+ "os/signal"
+ "path/filepath"
+ "runtime"
+ "strconv"
+ "strings"
+ "syscall"
+ "time"
+)
+
+const debug = false
+
+var tmpdir string
+
+var (
+ devID string
+ appID string
+ teamID string
+ bundleID string
+ deviceID string
+)
+
+// lock is a file lock to serialize iOS runs. It is global to avoid the
+// garbage collector finalizing it, closing the file and releasing the
+// lock prematurely.
+var lock *os.File
+
+func main() {
+ log.SetFlags(0)
+ log.SetPrefix("go_ios_exec: ")
+ if debug {
+ log.Println(strings.Join(os.Args, " "))
+ }
+ if len(os.Args) < 2 {
+ log.Fatal("usage: go_ios_exec a.out")
+ }
+
+ // For compatibility with the old builders, use a fallback bundle ID
+ bundleID = "golang.gotest"
+
+ exitCode, err := runMain()
+ if err != nil {
+ log.Fatalf("%v\n", err)
+ }
+ os.Exit(exitCode)
+}
+
+func runMain() (int, error) {
+ var err error
+ tmpdir, err = os.MkdirTemp("", "go_ios_exec_")
+ if err != nil {
+ return 1, err
+ }
+ if !debug {
+ defer os.RemoveAll(tmpdir)
+ }
+
+ appdir := filepath.Join(tmpdir, "gotest.app")
+ os.RemoveAll(appdir)
+
+ if err := assembleApp(appdir, os.Args[1]); err != nil {
+ return 1, err
+ }
+
+ // This wrapper uses complicated machinery to run iOS binaries. It
+ // works, but only when running one binary at a time.
+ // Use a file lock to make sure only one wrapper is running at a time.
+ //
+ // The lock file is never deleted, to avoid concurrent locks on distinct
+ // files with the same path.
+ lockName := filepath.Join(os.TempDir(), "go_ios_exec-"+deviceID+".lock")
+ lock, err = os.OpenFile(lockName, os.O_CREATE|os.O_RDONLY, 0666)
+ if err != nil {
+ return 1, err
+ }
+ if err := syscall.Flock(int(lock.Fd()), syscall.LOCK_EX); err != nil {
+ return 1, err
+ }
+
+ if goarch := os.Getenv("GOARCH"); goarch == "arm64" {
+ err = runOnDevice(appdir)
+ } else {
+ err = runOnSimulator(appdir)
+ }
+ if err != nil {
+ // If the lldb driver completed with an exit code, use that.
+ if err, ok := err.(*exec.ExitError); ok {
+ if ws, ok := err.Sys().(interface{ ExitStatus() int }); ok {
+ return ws.ExitStatus(), nil
+ }
+ }
+ return 1, err
+ }
+ return 0, nil
+}
+
+func runOnSimulator(appdir string) error {
+ if err := installSimulator(appdir); err != nil {
+ return err
+ }
+
+ return runSimulator(appdir, bundleID, os.Args[2:])
+}
+
+func runOnDevice(appdir string) error {
+ // e.g. B393DDEB490947F5A463FD074299B6C0AXXXXXXX
+ devID = getenv("GOIOS_DEV_ID")
+
+ // e.g. Z8B3JBXXXX.org.golang.sample, Z8B3JBXXXX prefix is available at
+ // https://developer.apple.com/membercenter/index.action#accountSummary as Team ID.
+ appID = getenv("GOIOS_APP_ID")
+
+ // e.g. Z8B3JBXXXX, available at
+ // https://developer.apple.com/membercenter/index.action#accountSummary as Team ID.
+ teamID = getenv("GOIOS_TEAM_ID")
+
+ // Device IDs as listed with ios-deploy -c.
+ deviceID = os.Getenv("GOIOS_DEVICE_ID")
+
+ if _, id, ok := strings.Cut(appID, "."); ok {
+ bundleID = id
+ }
+
+ if err := signApp(appdir); err != nil {
+ return err
+ }
+
+ if err := uninstallDevice(bundleID); err != nil {
+ return err
+ }
+
+ if err := installDevice(appdir); err != nil {
+ return err
+ }
+
+ if err := mountDevImage(); err != nil {
+ return err
+ }
+
+ // Kill any hanging debug bridges that might take up port 3222.
+ exec.Command("killall", "idevicedebugserverproxy").Run()
+
+ closer, err := startDebugBridge()
+ if err != nil {
+ return err
+ }
+ defer closer()
+
+ return runDevice(appdir, bundleID, os.Args[2:])
+}
+
+func getenv(envvar string) string {
+ s := os.Getenv(envvar)
+ if s == "" {
+ log.Fatalf("%s not set\nrun $GOROOT/misc/ios/detect.go to attempt to autodetect", envvar)
+ }
+ return s
+}
+
+func assembleApp(appdir, bin string) error {
+ if err := os.MkdirAll(appdir, 0755); err != nil {
+ return err
+ }
+
+ if err := cp(filepath.Join(appdir, "gotest"), bin); err != nil {
+ return err
+ }
+
+ pkgpath, err := copyLocalData(appdir)
+ if err != nil {
+ return err
+ }
+
+ entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")
+ if err := os.WriteFile(entitlementsPath, []byte(entitlementsPlist()), 0744); err != nil {
+ return err
+ }
+ if err := os.WriteFile(filepath.Join(appdir, "Info.plist"), []byte(infoPlist(pkgpath)), 0744); err != nil {
+ return err
+ }
+ if err := os.WriteFile(filepath.Join(appdir, "ResourceRules.plist"), []byte(resourceRules), 0744); err != nil {
+ return err
+ }
+ return nil
+}
+
+func signApp(appdir string) error {
+ entitlementsPath := filepath.Join(tmpdir, "Entitlements.plist")
+ cmd := exec.Command(
+ "codesign",
+ "-f",
+ "-s", devID,
+ "--entitlements", entitlementsPath,
+ appdir,
+ )
+ if debug {
+ log.Println(strings.Join(cmd.Args, " "))
+ }
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("codesign: %v", err)
+ }
+ return nil
+}
+
+// mountDevImage ensures a developer image is mounted on the device.
+// The image contains the device lldb server for idevicedebugserverproxy
+// to connect to.
+func mountDevImage() error {
+ // Check for existing mount.
+ cmd := idevCmd(exec.Command("ideviceimagemounter", "-l", "-x"))
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ os.Stderr.Write(out)
+ return fmt.Errorf("ideviceimagemounter: %v", err)
+ }
+ var info struct {
+ Dict struct {
+ Data []byte `xml:",innerxml"`
+ } `xml:"dict"`
+ }
+ if err := xml.Unmarshal(out, &info); err != nil {
+ return fmt.Errorf("mountDevImage: failed to decode mount information: %v", err)
+ }
+ dict, err := parsePlistDict(info.Dict.Data)
+ if err != nil {
+ return fmt.Errorf("mountDevImage: failed to parse mount information: %v", err)
+ }
+ if dict["ImagePresent"] == "true" && dict["Status"] == "Complete" {
+ return nil
+ }
+ // Some devices only give us an ImageSignature key.
+ if _, exists := dict["ImageSignature"]; exists {
+ return nil
+ }
+ // No image is mounted. Find a suitable image.
+ imgPath, err := findDevImage()
+ if err != nil {
+ return err
+ }
+ sigPath := imgPath + ".signature"
+ cmd = idevCmd(exec.Command("ideviceimagemounter", imgPath, sigPath))
+ if out, err := cmd.CombinedOutput(); err != nil {
+ os.Stderr.Write(out)
+ return fmt.Errorf("ideviceimagemounter: %v", err)
+ }
+ return nil
+}
+
+// findDevImage use the device iOS version and build to locate a suitable
+// developer image.
+func findDevImage() (string, error) {
+ cmd := idevCmd(exec.Command("ideviceinfo"))
+ out, err := cmd.Output()
+ if err != nil {
+ return "", fmt.Errorf("ideviceinfo: %v", err)
+ }
+ var iosVer, buildVer string
+ lines := bytes.Split(out, []byte("\n"))
+ for _, line := range lines {
+ key, val, ok := strings.Cut(string(line), ": ")
+ if !ok {
+ continue
+ }
+ switch key {
+ case "ProductVersion":
+ iosVer = val
+ case "BuildVersion":
+ buildVer = val
+ }
+ }
+ if iosVer == "" || buildVer == "" {
+ return "", errors.New("failed to parse ideviceinfo output")
+ }
+ verSplit := strings.Split(iosVer, ".")
+ if len(verSplit) > 2 {
+ // Developer images are specific to major.minor ios version.
+ // Cut off the patch version.
+ iosVer = strings.Join(verSplit[:2], ".")
+ }
+ sdkBase := "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport"
+ patterns := []string{fmt.Sprintf("%s (%s)", iosVer, buildVer), fmt.Sprintf("%s (*)", iosVer), fmt.Sprintf("%s*", iosVer)}
+ for _, pattern := range patterns {
+ matches, err := filepath.Glob(filepath.Join(sdkBase, pattern, "DeveloperDiskImage.dmg"))
+ if err != nil {
+ return "", fmt.Errorf("findDevImage: %v", err)
+ }
+ if len(matches) > 0 {
+ return matches[0], nil
+ }
+ }
+ return "", fmt.Errorf("failed to find matching developer image for iOS version %s build %s", iosVer, buildVer)
+}
+
+// startDebugBridge ensures that the idevicedebugserverproxy runs on
+// port 3222.
+func startDebugBridge() (func(), error) {
+ errChan := make(chan error, 1)
+ cmd := idevCmd(exec.Command("idevicedebugserverproxy", "3222"))
+ var stderr bytes.Buffer
+ cmd.Stderr = &stderr
+ if err := cmd.Start(); err != nil {
+ return nil, fmt.Errorf("idevicedebugserverproxy: %v", err)
+ }
+ go func() {
+ if err := cmd.Wait(); err != nil {
+ if _, ok := err.(*exec.ExitError); ok {
+ errChan <- fmt.Errorf("idevicedebugserverproxy: %s", stderr.Bytes())
+ } else {
+ errChan <- fmt.Errorf("idevicedebugserverproxy: %v", err)
+ }
+ }
+ errChan <- nil
+ }()
+ closer := func() {
+ cmd.Process.Kill()
+ <-errChan
+ }
+ // Dial localhost:3222 to ensure the proxy is ready.
+ delay := time.Second / 4
+ for attempt := 0; attempt < 5; attempt++ {
+ conn, err := net.DialTimeout("tcp", "localhost:3222", 5*time.Second)
+ if err == nil {
+ conn.Close()
+ return closer, nil
+ }
+ select {
+ case <-time.After(delay):
+ delay *= 2
+ case err := <-errChan:
+ return nil, err
+ }
+ }
+ closer()
+ return nil, errors.New("failed to set up idevicedebugserverproxy")
+}
+
+// findDeviceAppPath returns the device path to the app with the
+// given bundle ID. It parses the output of ideviceinstaller -l -o xml,
+// looking for the bundle ID and the corresponding Path value.
+func findDeviceAppPath(bundleID string) (string, error) {
+ cmd := idevCmd(exec.Command("ideviceinstaller", "-l", "-o", "xml"))
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ os.Stderr.Write(out)
+ return "", fmt.Errorf("ideviceinstaller: -l -o xml %v", err)
+ }
+ var list struct {
+ Apps []struct {
+ Data []byte `xml:",innerxml"`
+ } `xml:"array>dict"`
+ }
+ if err := xml.Unmarshal(out, &list); err != nil {
+ return "", fmt.Errorf("failed to parse ideviceinstaller output: %v", err)
+ }
+ for _, app := range list.Apps {
+ values, err := parsePlistDict(app.Data)
+ if err != nil {
+ return "", fmt.Errorf("findDeviceAppPath: failed to parse app dict: %v", err)
+ }
+ if values["CFBundleIdentifier"] == bundleID {
+ if path, ok := values["Path"]; ok {
+ return path, nil
+ }
+ }
+ }
+ return "", fmt.Errorf("failed to find device path for bundle: %s", bundleID)
+}
+
+// Parse an xml encoded plist. Plist values are mapped to string.
+func parsePlistDict(dict []byte) (map[string]string, error) {
+ d := xml.NewDecoder(bytes.NewReader(dict))
+ values := make(map[string]string)
+ var key string
+ var hasKey bool
+ for {
+ tok, err := d.Token()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, err
+ }
+ if tok, ok := tok.(xml.StartElement); ok {
+ if tok.Name.Local == "key" {
+ if err := d.DecodeElement(&key, &tok); err != nil {
+ return nil, err
+ }
+ hasKey = true
+ } else if hasKey {
+ var val string
+ var err error
+ switch n := tok.Name.Local; n {
+ case "true", "false":
+ // Bools are represented as <true/> and <false/>.
+ val = n
+ err = d.Skip()
+ default:
+ err = d.DecodeElement(&val, &tok)
+ }
+ if err != nil {
+ return nil, err
+ }
+ values[key] = val
+ hasKey = false
+ } else {
+ if err := d.Skip(); err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+ return values, nil
+}
+
+func installSimulator(appdir string) error {
+ cmd := exec.Command(
+ "xcrun", "simctl", "install",
+ "booted", // Install to the booted simulator.
+ appdir,
+ )
+ if out, err := cmd.CombinedOutput(); err != nil {
+ os.Stderr.Write(out)
+ return fmt.Errorf("xcrun simctl install booted %q: %v", appdir, err)
+ }
+ return nil
+}
+
+func uninstallDevice(bundleID string) error {
+ cmd := idevCmd(exec.Command(
+ "ideviceinstaller",
+ "-U", bundleID,
+ ))
+ if out, err := cmd.CombinedOutput(); err != nil {
+ os.Stderr.Write(out)
+ return fmt.Errorf("ideviceinstaller -U %q: %s", bundleID, err)
+ }
+ return nil
+}
+
+func installDevice(appdir string) error {
+ attempt := 0
+ for {
+ cmd := idevCmd(exec.Command(
+ "ideviceinstaller",
+ "-i", appdir,
+ ))
+ if out, err := cmd.CombinedOutput(); err != nil {
+ // Sometimes, installing the app fails for some reason.
+ // Give the device a few seconds and try again.
+ if attempt < 5 {
+ time.Sleep(5 * time.Second)
+ attempt++
+ continue
+ }
+ os.Stderr.Write(out)
+ return fmt.Errorf("ideviceinstaller -i %q: %v (%d attempts)", appdir, err, attempt)
+ }
+ return nil
+ }
+}
+
+func idevCmd(cmd *exec.Cmd) *exec.Cmd {
+ if deviceID != "" {
+ // Inject -u device_id after the executable, but before the arguments.
+ args := []string{cmd.Args[0], "-u", deviceID}
+ cmd.Args = append(args, cmd.Args[1:]...)
+ }
+ return cmd
+}
+
+func runSimulator(appdir, bundleID string, args []string) error {
+ cmd := exec.Command(
+ "xcrun", "simctl", "launch",
+ "--wait-for-debugger",
+ "booted",
+ bundleID,
+ )
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ os.Stderr.Write(out)
+ return fmt.Errorf("xcrun simctl launch booted %q: %v", bundleID, err)
+ }
+ var processID int
+ var ignore string
+ if _, err := fmt.Sscanf(string(out), "%s %d", &ignore, &processID); err != nil {
+ return fmt.Errorf("runSimulator: couldn't find processID from `simctl launch`: %v (%q)", err, out)
+ }
+ _, err = runLLDB("ios-simulator", appdir, strconv.Itoa(processID), args)
+ return err
+}
+
+func runDevice(appdir, bundleID string, args []string) error {
+ attempt := 0
+ for {
+ // The device app path reported by the device might be stale, so retry
+ // the lookup of the device path along with the lldb launching below.
+ deviceapp, err := findDeviceAppPath(bundleID)
+ if err != nil {
+ // The device app path might not yet exist for a newly installed app.
+ if attempt == 5 {
+ return err
+ }
+ attempt++
+ time.Sleep(5 * time.Second)
+ continue
+ }
+ out, err := runLLDB("remote-ios", appdir, deviceapp, args)
+ // If the program was not started it can be retried without papering over
+ // real test failures.
+ started := bytes.HasPrefix(out, []byte("lldb: running program"))
+ if started || err == nil || attempt == 5 {
+ return err
+ }
+ // Sometimes, the app was not yet ready to launch or the device path was
+ // stale. Retry.
+ attempt++
+ time.Sleep(5 * time.Second)
+ }
+}
+
+func runLLDB(target, appdir, deviceapp string, args []string) ([]byte, error) {
+ var env []string
+ for _, e := range os.Environ() {
+ // Don't override TMPDIR, HOME, GOCACHE on the device.
+ if strings.HasPrefix(e, "TMPDIR=") || strings.HasPrefix(e, "HOME=") || strings.HasPrefix(e, "GOCACHE=") {
+ continue
+ }
+ env = append(env, e)
+ }
+ lldb := exec.Command(
+ "python",
+ "-", // Read script from stdin.
+ target,
+ appdir,
+ deviceapp,
+ )
+ lldb.Args = append(lldb.Args, args...)
+ lldb.Env = env
+ lldb.Stdin = strings.NewReader(lldbDriver)
+ lldb.Stdout = os.Stdout
+ var out bytes.Buffer
+ lldb.Stderr = io.MultiWriter(&out, os.Stderr)
+ err := lldb.Start()
+ if err == nil {
+ // Forward SIGQUIT to the lldb driver which in turn will forward
+ // to the running program.
+ sigs := make(chan os.Signal, 1)
+ signal.Notify(sigs, syscall.SIGQUIT)
+ proc := lldb.Process
+ go func() {
+ for sig := range sigs {
+ proc.Signal(sig)
+ }
+ }()
+ err = lldb.Wait()
+ signal.Stop(sigs)
+ close(sigs)
+ }
+ return out.Bytes(), err
+}
+
+func copyLocalDir(dst, src string) error {
+ if err := os.Mkdir(dst, 0755); err != nil {
+ return err
+ }
+
+ d, err := os.Open(src)
+ if err != nil {
+ return err
+ }
+ defer d.Close()
+ fi, err := d.Readdir(-1)
+ if err != nil {
+ return err
+ }
+
+ for _, f := range fi {
+ if f.IsDir() {
+ if f.Name() == "testdata" {
+ if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
+ return err
+ }
+ }
+ continue
+ }
+ if err := cp(dst, filepath.Join(src, f.Name())); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func cp(dst, src string) error {
+ out, err := exec.Command("cp", "-a", src, dst).CombinedOutput()
+ if err != nil {
+ os.Stderr.Write(out)
+ }
+ return err
+}
+
+func copyLocalData(dstbase string) (pkgpath string, err error) {
+ cwd, err := os.Getwd()
+ if err != nil {
+ return "", err
+ }
+
+ finalPkgpath, underGoRoot, err := subdir()
+ if err != nil {
+ return "", err
+ }
+ cwd = strings.TrimSuffix(cwd, finalPkgpath)
+
+ // Copy all immediate files and testdata directories between
+ // the package being tested and the source root.
+ pkgpath = ""
+ for _, element := range strings.Split(finalPkgpath, string(filepath.Separator)) {
+ if debug {
+ log.Printf("copying %s", pkgpath)
+ }
+ pkgpath = filepath.Join(pkgpath, element)
+ dst := filepath.Join(dstbase, pkgpath)
+ src := filepath.Join(cwd, pkgpath)
+ if err := copyLocalDir(dst, src); err != nil {
+ return "", err
+ }
+ }
+
+ if underGoRoot {
+ // Copy timezone file.
+ //
+ // Typical apps have the zoneinfo.zip in the root of their app bundle,
+ // read by the time package as the working directory at initialization.
+ // As we move the working directory to the GOROOT pkg directory, we
+ // install the zoneinfo.zip file in the pkgpath.
+ err := cp(
+ filepath.Join(dstbase, pkgpath),
+ filepath.Join(cwd, "lib", "time", "zoneinfo.zip"),
+ )
+ if err != nil {
+ return "", err
+ }
+ // Copy src/runtime/textflag.h for (at least) Test386EndToEnd in
+ // cmd/asm/internal/asm.
+ runtimePath := filepath.Join(dstbase, "src", "runtime")
+ if err := os.MkdirAll(runtimePath, 0755); err != nil {
+ return "", err
+ }
+ err = cp(
+ filepath.Join(runtimePath, "textflag.h"),
+ filepath.Join(cwd, "src", "runtime", "textflag.h"),
+ )
+ if err != nil {
+ return "", err
+ }
+ }
+
+ return finalPkgpath, nil
+}
+
+// subdir determines the package based on the current working directory,
+// and returns the path to the package source relative to $GOROOT (or $GOPATH).
+func subdir() (pkgpath string, underGoRoot bool, err error) {
+ cwd, err := os.Getwd()
+ if err != nil {
+ return "", false, err
+ }
+ cwd, err = filepath.EvalSymlinks(cwd)
+ if err != nil {
+ log.Fatal(err)
+ }
+ goroot, err := filepath.EvalSymlinks(runtime.GOROOT())
+ if err != nil {
+ return "", false, err
+ }
+ if strings.HasPrefix(cwd, goroot) {
+ subdir, err := filepath.Rel(goroot, cwd)
+ if err != nil {
+ return "", false, err
+ }
+ return subdir, true, nil
+ }
+
+ for _, p := range filepath.SplitList(build.Default.GOPATH) {
+ pabs, err := filepath.EvalSymlinks(p)
+ if err != nil {
+ return "", false, err
+ }
+ if !strings.HasPrefix(cwd, pabs) {
+ continue
+ }
+ subdir, err := filepath.Rel(pabs, cwd)
+ if err == nil {
+ return subdir, false, nil
+ }
+ }
+ return "", false, fmt.Errorf(
+ "working directory %q is not in either GOROOT(%q) or GOPATH(%q)",
+ cwd,
+ runtime.GOROOT(),
+ build.Default.GOPATH,
+ )
+}
+
+func infoPlist(pkgpath string) string {
+ return `<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+<key>CFBundleName</key><string>golang.gotest</string>
+<key>CFBundleSupportedPlatforms</key><array><string>iPhoneOS</string></array>
+<key>CFBundleExecutable</key><string>gotest</string>
+<key>CFBundleVersion</key><string>1.0</string>
+<key>CFBundleShortVersionString</key><string>1.0</string>
+<key>CFBundleIdentifier</key><string>` + bundleID + `</string>
+<key>CFBundleResourceSpecification</key><string>ResourceRules.plist</string>
+<key>LSRequiresIPhoneOS</key><true/>
+<key>CFBundleDisplayName</key><string>gotest</string>
+<key>GoExecWrapperWorkingDirectory</key><string>` + pkgpath + `</string>
+</dict>
+</plist>
+`
+}
+
+func entitlementsPlist() string {
+ return `<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>keychain-access-groups</key>
+ <array><string>` + appID + `</string></array>
+ <key>get-task-allow</key>
+ <true/>
+ <key>application-identifier</key>
+ <string>` + appID + `</string>
+ <key>com.apple.developer.team-identifier</key>
+ <string>` + teamID + `</string>
+</dict>
+</plist>
+`
+}
+
+const resourceRules = `<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>rules</key>
+ <dict>
+ <key>.*</key>
+ <true/>
+ <key>Info.plist</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <integer>10</integer>
+ </dict>
+ <key>ResourceRules.plist</key>
+ <dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <integer>100</integer>
+ </dict>
+ </dict>
+</dict>
+</plist>
+`
+
+const lldbDriver = `
+import sys
+import os
+import signal
+
+platform, exe, device_exe_or_pid, args = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4:]
+
+env = []
+for k, v in os.environ.items():
+ env.append(k + "=" + v)
+
+sys.path.append('/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python')
+
+import lldb
+
+debugger = lldb.SBDebugger.Create()
+debugger.SetAsync(True)
+debugger.SkipLLDBInitFiles(True)
+
+err = lldb.SBError()
+target = debugger.CreateTarget(exe, None, platform, True, err)
+if not target.IsValid() or not err.Success():
+ sys.stderr.write("lldb: failed to setup up target: %s\n" % (err))
+ sys.exit(1)
+
+listener = debugger.GetListener()
+
+if platform == 'remote-ios':
+ target.modules[0].SetPlatformFileSpec(lldb.SBFileSpec(device_exe_or_pid))
+ process = target.ConnectRemote(listener, 'connect://localhost:3222', None, err)
+else:
+ process = target.AttachToProcessWithID(listener, int(device_exe_or_pid), err)
+
+if not err.Success():
+ sys.stderr.write("lldb: failed to connect to remote target %s: %s\n" % (device_exe_or_pid, err))
+ sys.exit(1)
+
+# Don't stop on signals.
+sigs = process.GetUnixSignals()
+for i in range(0, sigs.GetNumSignals()):
+ sig = sigs.GetSignalAtIndex(i)
+ sigs.SetShouldStop(sig, False)
+ sigs.SetShouldNotify(sig, False)
+
+event = lldb.SBEvent()
+running = False
+prev_handler = None
+
+def signal_handler(signal, frame):
+ process.Signal(signal)
+
+def run_program():
+ # Forward SIGQUIT to the program.
+ prev_handler = signal.signal(signal.SIGQUIT, signal_handler)
+ # Tell the Go driver that the program is running and should not be retried.
+ sys.stderr.write("lldb: running program\n")
+ running = True
+ # Process is stopped at attach/launch. Let it run.
+ process.Continue()
+
+if platform != 'remote-ios':
+ # For the local emulator the program is ready to run.
+ # For remote device runs, we need to wait for eStateConnected,
+ # below.
+ run_program()
+
+while True:
+ if not listener.WaitForEvent(1, event):
+ continue
+ if not lldb.SBProcess.EventIsProcessEvent(event):
+ continue
+ if running:
+ # Pass through stdout and stderr.
+ while True:
+ out = process.GetSTDOUT(8192)
+ if not out:
+ break
+ sys.stdout.write(out)
+ while True:
+ out = process.GetSTDERR(8192)
+ if not out:
+ break
+ sys.stderr.write(out)
+ state = process.GetStateFromEvent(event)
+ if state in [lldb.eStateCrashed, lldb.eStateDetached, lldb.eStateUnloaded, lldb.eStateExited]:
+ if running:
+ signal.signal(signal.SIGQUIT, prev_handler)
+ break
+ elif state == lldb.eStateConnected:
+ if platform == 'remote-ios':
+ process.RemoteLaunch(args, env, None, None, None, None, 0, False, err)
+ if not err.Success():
+ sys.stderr.write("lldb: failed to launch remote process: %s\n" % (err))
+ process.Kill()
+ debugger.Terminate()
+ sys.exit(1)
+ run_program()
+
+exitStatus = process.GetExitStatus()
+exitDesc = process.GetExitDescription()
+process.Kill()
+debugger.Terminate()
+if exitStatus == 0 and exitDesc is not None:
+ # Ensure tests fail when killed by a signal.
+ exitStatus = 123
+
+sys.exit(exitStatus)
+`
diff --git a/misc/linkcheck/linkcheck.go b/misc/linkcheck/linkcheck.go
new file mode 100644
index 0000000..efe4009
--- /dev/null
+++ b/misc/linkcheck/linkcheck.go
@@ -0,0 +1,191 @@
+// Copyright 2013 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.
+
+// The linkcheck command finds missing links in the godoc website.
+// It crawls a URL recursively and notes URLs and URL fragments
+// that it's seen and prints a report of missing links at the end.
+package main
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "regexp"
+ "strings"
+ "sync"
+)
+
+var (
+ root = flag.String("root", "http://localhost:6060", "Root to crawl")
+ verbose = flag.Bool("verbose", false, "verbose")
+)
+
+var wg sync.WaitGroup // outstanding fetches
+var urlq = make(chan string) // URLs to crawl
+
+// urlFrag is a URL and its optional #fragment (without the #)
+type urlFrag struct {
+ url, frag string
+}
+
+var (
+ mu sync.Mutex
+ crawled = make(map[string]bool) // URL without fragment -> true
+ neededFrags = make(map[urlFrag][]string) // URL#frag -> who needs it
+)
+
+var aRx = regexp.MustCompile(`<a href=['"]?(/[^\s'">]+)`)
+
+// Owned by crawlLoop goroutine:
+var (
+ linkSources = make(map[string][]string) // url no fragment -> sources
+ fragExists = make(map[urlFrag]bool)
+ problems []string
+)
+
+func localLinks(body string) (links []string) {
+ seen := map[string]bool{}
+ mv := aRx.FindAllStringSubmatch(body, -1)
+ for _, m := range mv {
+ ref := m[1]
+ if strings.HasPrefix(ref, "/src/") {
+ continue
+ }
+ if !seen[ref] {
+ seen[ref] = true
+ links = append(links, m[1])
+ }
+ }
+ return
+}
+
+var idRx = regexp.MustCompile(`\bid=['"]?([^\s'">]+)`)
+
+func pageIDs(body string) (ids []string) {
+ mv := idRx.FindAllStringSubmatch(body, -1)
+ for _, m := range mv {
+ ids = append(ids, m[1])
+ }
+ return
+}
+
+// url may contain a #fragment, and the fragment is then noted as needing to exist.
+func crawl(url string, sourceURL string) {
+ if strings.Contains(url, "/devel/release") {
+ return
+ }
+ mu.Lock()
+ defer mu.Unlock()
+ if u, frag, ok := strings.Cut(url, "#"); ok {
+ url = u
+ if frag != "" {
+ uf := urlFrag{url, frag}
+ neededFrags[uf] = append(neededFrags[uf], sourceURL)
+ }
+ }
+ if crawled[url] {
+ return
+ }
+ crawled[url] = true
+
+ wg.Add(1)
+ go func() {
+ urlq <- url
+ }()
+}
+
+func addProblem(url, errmsg string) {
+ msg := fmt.Sprintf("Error on %s: %s (from %s)", url, errmsg, linkSources[url])
+ if *verbose {
+ log.Print(msg)
+ }
+ problems = append(problems, msg)
+}
+
+func crawlLoop() {
+ for url := range urlq {
+ if err := doCrawl(url); err != nil {
+ addProblem(url, err.Error())
+ }
+ }
+}
+
+func doCrawl(url string) error {
+ defer wg.Done()
+
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return err
+ }
+ res, err := http.DefaultTransport.RoundTrip(req)
+ if err != nil {
+ return err
+ }
+ // Handle redirects.
+ if res.StatusCode/100 == 3 {
+ newURL, err := res.Location()
+ if err != nil {
+ return fmt.Errorf("resolving redirect: %v", err)
+ }
+ if !strings.HasPrefix(newURL.String(), *root) {
+ // Skip off-site redirects.
+ return nil
+ }
+ crawl(newURL.String(), url)
+ return nil
+ }
+ if res.StatusCode != 200 {
+ return errors.New(res.Status)
+ }
+ slurp, err := io.ReadAll(res.Body)
+ res.Body.Close()
+ if err != nil {
+ log.Fatalf("Error reading %s body: %v", url, err)
+ }
+ if *verbose {
+ log.Printf("Len of %s: %d", url, len(slurp))
+ }
+ body := string(slurp)
+ for _, ref := range localLinks(body) {
+ if *verbose {
+ log.Printf(" links to %s", ref)
+ }
+ dest := *root + ref
+ linkSources[dest] = append(linkSources[dest], url)
+ crawl(dest, url)
+ }
+ for _, id := range pageIDs(body) {
+ if *verbose {
+ log.Printf(" url %s has #%s", url, id)
+ }
+ fragExists[urlFrag{url, id}] = true
+ }
+ return nil
+}
+
+func main() {
+ flag.Parse()
+
+ go crawlLoop()
+ crawl(*root, "")
+
+ wg.Wait()
+ close(urlq)
+ for uf, needers := range neededFrags {
+ if !fragExists[uf] {
+ problems = append(problems, fmt.Sprintf("Missing fragment for %+v from %v", uf, needers))
+ }
+ }
+
+ for _, s := range problems {
+ fmt.Println(s)
+ }
+ if len(problems) > 0 {
+ os.Exit(1)
+ }
+}
diff --git a/misc/wasm/go_js_wasm_exec b/misc/wasm/go_js_wasm_exec
new file mode 100755
index 0000000..ff59257
--- /dev/null
+++ b/misc/wasm/go_js_wasm_exec
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+# 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.
+
+SOURCE="${BASH_SOURCE[0]}"
+while [ -h "$SOURCE" ]; do
+ DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+ SOURCE="$(readlink "$SOURCE")"
+ [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
+done
+DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )"
+
+# Increase the V8 stack size from the default of 984K
+# to 8192K to ensure all tests can pass without hitting
+# stack size limits.
+exec node --stack-size=8192 "$DIR/wasm_exec_node.js" "$@"
diff --git a/misc/wasm/go_wasip1_wasm_exec b/misc/wasm/go_wasip1_wasm_exec
new file mode 100755
index 0000000..0351994
--- /dev/null
+++ b/misc/wasm/go_wasip1_wasm_exec
@@ -0,0 +1,23 @@
+#!/usr/bin/env bash
+# Copyright 2023 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.
+
+case "$GOWASIRUNTIME" in
+ "wasmedge")
+ exec wasmedge --dir=/ --env PWD="$PWD" --env PATH="$PATH" ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
+ ;;
+ "wasmer")
+ exec wasmer run --dir=/ --env PWD="$PWD" --env PATH="$PATH" ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
+ ;;
+ "wazero")
+ exec wazero run -mount /:/ -env-inherit -cachedir "${TMPDIR:-/tmp}"/wazero ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
+ ;;
+ "wasmtime" | "")
+ exec wasmtime run --dir=/ --env PWD="$PWD" --env PATH="$PATH" --max-wasm-stack 1048576 ${GOWASIRUNTIMEARGS:-} "$1" -- "${@:2}"
+ ;;
+ *)
+ echo "Unknown Go WASI runtime specified: $GOWASIRUNTIME"
+ exit 1
+ ;;
+esac
diff --git a/misc/wasm/wasm_exec.html b/misc/wasm/wasm_exec.html
new file mode 100644
index 0000000..72e6447
--- /dev/null
+++ b/misc/wasm/wasm_exec.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<!--
+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.
+-->
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <title>Go wasm</title>
+</head>
+
+<body>
+ <!--
+ Add the following polyfill for Microsoft Edge 17/18 support:
+ <script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
+ (see https://caniuse.com/#feat=textencoder)
+ -->
+ <script src="wasm_exec.js"></script>
+ <script>
+ if (!WebAssembly.instantiateStreaming) { // polyfill
+ WebAssembly.instantiateStreaming = async (resp, importObject) => {
+ const source = await (await resp).arrayBuffer();
+ return await WebAssembly.instantiate(source, importObject);
+ };
+ }
+
+ const go = new Go();
+ let mod, inst;
+ WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
+ mod = result.module;
+ inst = result.instance;
+ document.getElementById("runButton").disabled = false;
+ }).catch((err) => {
+ console.error(err);
+ });
+
+ async function run() {
+ console.clear();
+ await go.run(inst);
+ inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
+ }
+ </script>
+
+ <button onClick="run();" id="runButton" disabled>Run</button>
+</body>
+
+</html> \ No newline at end of file
diff --git a/misc/wasm/wasm_exec.js b/misc/wasm/wasm_exec.js
new file mode 100644
index 0000000..bc6f210
--- /dev/null
+++ b/misc/wasm/wasm_exec.js
@@ -0,0 +1,561 @@
+// 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.
+
+"use strict";
+
+(() => {
+ const enosys = () => {
+ const err = new Error("not implemented");
+ err.code = "ENOSYS";
+ return err;
+ };
+
+ if (!globalThis.fs) {
+ let outputBuf = "";
+ globalThis.fs = {
+ constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
+ writeSync(fd, buf) {
+ outputBuf += decoder.decode(buf);
+ const nl = outputBuf.lastIndexOf("\n");
+ if (nl != -1) {
+ console.log(outputBuf.substring(0, nl));
+ outputBuf = outputBuf.substring(nl + 1);
+ }
+ return buf.length;
+ },
+ write(fd, buf, offset, length, position, callback) {
+ if (offset !== 0 || length !== buf.length || position !== null) {
+ callback(enosys());
+ return;
+ }
+ const n = this.writeSync(fd, buf);
+ callback(null, n);
+ },
+ chmod(path, mode, callback) { callback(enosys()); },
+ chown(path, uid, gid, callback) { callback(enosys()); },
+ close(fd, callback) { callback(enosys()); },
+ fchmod(fd, mode, callback) { callback(enosys()); },
+ fchown(fd, uid, gid, callback) { callback(enosys()); },
+ fstat(fd, callback) { callback(enosys()); },
+ fsync(fd, callback) { callback(null); },
+ ftruncate(fd, length, callback) { callback(enosys()); },
+ lchown(path, uid, gid, callback) { callback(enosys()); },
+ link(path, link, callback) { callback(enosys()); },
+ lstat(path, callback) { callback(enosys()); },
+ mkdir(path, perm, callback) { callback(enosys()); },
+ open(path, flags, mode, callback) { callback(enosys()); },
+ read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
+ readdir(path, callback) { callback(enosys()); },
+ readlink(path, callback) { callback(enosys()); },
+ rename(from, to, callback) { callback(enosys()); },
+ rmdir(path, callback) { callback(enosys()); },
+ stat(path, callback) { callback(enosys()); },
+ symlink(path, link, callback) { callback(enosys()); },
+ truncate(path, length, callback) { callback(enosys()); },
+ unlink(path, callback) { callback(enosys()); },
+ utimes(path, atime, mtime, callback) { callback(enosys()); },
+ };
+ }
+
+ if (!globalThis.process) {
+ globalThis.process = {
+ getuid() { return -1; },
+ getgid() { return -1; },
+ geteuid() { return -1; },
+ getegid() { return -1; },
+ getgroups() { throw enosys(); },
+ pid: -1,
+ ppid: -1,
+ umask() { throw enosys(); },
+ cwd() { throw enosys(); },
+ chdir() { throw enosys(); },
+ }
+ }
+
+ if (!globalThis.crypto) {
+ throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
+ }
+
+ if (!globalThis.performance) {
+ throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
+ }
+
+ if (!globalThis.TextEncoder) {
+ throw new Error("globalThis.TextEncoder is not available, polyfill required");
+ }
+
+ if (!globalThis.TextDecoder) {
+ throw new Error("globalThis.TextDecoder is not available, polyfill required");
+ }
+
+ const encoder = new TextEncoder("utf-8");
+ const decoder = new TextDecoder("utf-8");
+
+ globalThis.Go = class {
+ constructor() {
+ this.argv = ["js"];
+ this.env = {};
+ this.exit = (code) => {
+ if (code !== 0) {
+ console.warn("exit code:", code);
+ }
+ };
+ this._exitPromise = new Promise((resolve) => {
+ this._resolveExitPromise = resolve;
+ });
+ this._pendingEvent = null;
+ this._scheduledTimeouts = new Map();
+ this._nextCallbackTimeoutID = 1;
+
+ const setInt64 = (addr, v) => {
+ this.mem.setUint32(addr + 0, v, true);
+ this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
+ }
+
+ const setInt32 = (addr, v) => {
+ this.mem.setUint32(addr + 0, v, true);
+ }
+
+ const getInt64 = (addr) => {
+ const low = this.mem.getUint32(addr + 0, true);
+ const high = this.mem.getInt32(addr + 4, true);
+ return low + high * 4294967296;
+ }
+
+ const loadValue = (addr) => {
+ const f = this.mem.getFloat64(addr, true);
+ if (f === 0) {
+ return undefined;
+ }
+ if (!isNaN(f)) {
+ return f;
+ }
+
+ const id = this.mem.getUint32(addr, true);
+ return this._values[id];
+ }
+
+ const storeValue = (addr, v) => {
+ const nanHead = 0x7FF80000;
+
+ if (typeof v === "number" && v !== 0) {
+ if (isNaN(v)) {
+ this.mem.setUint32(addr + 4, nanHead, true);
+ this.mem.setUint32(addr, 0, true);
+ return;
+ }
+ this.mem.setFloat64(addr, v, true);
+ return;
+ }
+
+ if (v === undefined) {
+ this.mem.setFloat64(addr, 0, true);
+ return;
+ }
+
+ let id = this._ids.get(v);
+ if (id === undefined) {
+ id = this._idPool.pop();
+ if (id === undefined) {
+ id = this._values.length;
+ }
+ this._values[id] = v;
+ this._goRefCounts[id] = 0;
+ this._ids.set(v, id);
+ }
+ this._goRefCounts[id]++;
+ let typeFlag = 0;
+ switch (typeof v) {
+ case "object":
+ if (v !== null) {
+ typeFlag = 1;
+ }
+ break;
+ case "string":
+ typeFlag = 2;
+ break;
+ case "symbol":
+ typeFlag = 3;
+ break;
+ case "function":
+ typeFlag = 4;
+ break;
+ }
+ this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
+ this.mem.setUint32(addr, id, true);
+ }
+
+ const loadSlice = (addr) => {
+ const array = getInt64(addr + 0);
+ const len = getInt64(addr + 8);
+ return new Uint8Array(this._inst.exports.mem.buffer, array, len);
+ }
+
+ const loadSliceOfValues = (addr) => {
+ const array = getInt64(addr + 0);
+ const len = getInt64(addr + 8);
+ const a = new Array(len);
+ for (let i = 0; i < len; i++) {
+ a[i] = loadValue(array + i * 8);
+ }
+ return a;
+ }
+
+ const loadString = (addr) => {
+ const saddr = getInt64(addr + 0);
+ const len = getInt64(addr + 8);
+ return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
+ }
+
+ const timeOrigin = Date.now() - performance.now();
+ this.importObject = {
+ _gotest: {
+ add: (a, b) => a + b,
+ },
+ gojs: {
+ // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
+ // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
+ // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
+ // This changes the SP, thus we have to update the SP used by the imported function.
+
+ // func wasmExit(code int32)
+ "runtime.wasmExit": (sp) => {
+ sp >>>= 0;
+ const code = this.mem.getInt32(sp + 8, true);
+ this.exited = true;
+ delete this._inst;
+ delete this._values;
+ delete this._goRefCounts;
+ delete this._ids;
+ delete this._idPool;
+ this.exit(code);
+ },
+
+ // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
+ "runtime.wasmWrite": (sp) => {
+ sp >>>= 0;
+ const fd = getInt64(sp + 8);
+ const p = getInt64(sp + 16);
+ const n = this.mem.getInt32(sp + 24, true);
+ fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
+ },
+
+ // func resetMemoryDataView()
+ "runtime.resetMemoryDataView": (sp) => {
+ sp >>>= 0;
+ this.mem = new DataView(this._inst.exports.mem.buffer);
+ },
+
+ // func nanotime1() int64
+ "runtime.nanotime1": (sp) => {
+ sp >>>= 0;
+ setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
+ },
+
+ // func walltime() (sec int64, nsec int32)
+ "runtime.walltime": (sp) => {
+ sp >>>= 0;
+ const msec = (new Date).getTime();
+ setInt64(sp + 8, msec / 1000);
+ this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
+ },
+
+ // func scheduleTimeoutEvent(delay int64) int32
+ "runtime.scheduleTimeoutEvent": (sp) => {
+ sp >>>= 0;
+ const id = this._nextCallbackTimeoutID;
+ this._nextCallbackTimeoutID++;
+ this._scheduledTimeouts.set(id, setTimeout(
+ () => {
+ this._resume();
+ while (this._scheduledTimeouts.has(id)) {
+ // for some reason Go failed to register the timeout event, log and try again
+ // (temporary workaround for https://github.com/golang/go/issues/28975)
+ console.warn("scheduleTimeoutEvent: missed timeout event");
+ this._resume();
+ }
+ },
+ getInt64(sp + 8),
+ ));
+ this.mem.setInt32(sp + 16, id, true);
+ },
+
+ // func clearTimeoutEvent(id int32)
+ "runtime.clearTimeoutEvent": (sp) => {
+ sp >>>= 0;
+ const id = this.mem.getInt32(sp + 8, true);
+ clearTimeout(this._scheduledTimeouts.get(id));
+ this._scheduledTimeouts.delete(id);
+ },
+
+ // func getRandomData(r []byte)
+ "runtime.getRandomData": (sp) => {
+ sp >>>= 0;
+ crypto.getRandomValues(loadSlice(sp + 8));
+ },
+
+ // func finalizeRef(v ref)
+ "syscall/js.finalizeRef": (sp) => {
+ sp >>>= 0;
+ const id = this.mem.getUint32(sp + 8, true);
+ this._goRefCounts[id]--;
+ if (this._goRefCounts[id] === 0) {
+ const v = this._values[id];
+ this._values[id] = null;
+ this._ids.delete(v);
+ this._idPool.push(id);
+ }
+ },
+
+ // func stringVal(value string) ref
+ "syscall/js.stringVal": (sp) => {
+ sp >>>= 0;
+ storeValue(sp + 24, loadString(sp + 8));
+ },
+
+ // func valueGet(v ref, p string) ref
+ "syscall/js.valueGet": (sp) => {
+ sp >>>= 0;
+ const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 32, result);
+ },
+
+ // func valueSet(v ref, p string, x ref)
+ "syscall/js.valueSet": (sp) => {
+ sp >>>= 0;
+ Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
+ },
+
+ // func valueDelete(v ref, p string)
+ "syscall/js.valueDelete": (sp) => {
+ sp >>>= 0;
+ Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
+ },
+
+ // func valueIndex(v ref, i int) ref
+ "syscall/js.valueIndex": (sp) => {
+ sp >>>= 0;
+ storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
+ },
+
+ // valueSetIndex(v ref, i int, x ref)
+ "syscall/js.valueSetIndex": (sp) => {
+ sp >>>= 0;
+ Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
+ },
+
+ // func valueCall(v ref, m string, args []ref) (ref, bool)
+ "syscall/js.valueCall": (sp) => {
+ sp >>>= 0;
+ try {
+ const v = loadValue(sp + 8);
+ const m = Reflect.get(v, loadString(sp + 16));
+ const args = loadSliceOfValues(sp + 32);
+ const result = Reflect.apply(m, v, args);
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 56, result);
+ this.mem.setUint8(sp + 64, 1);
+ } catch (err) {
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 56, err);
+ this.mem.setUint8(sp + 64, 0);
+ }
+ },
+
+ // func valueInvoke(v ref, args []ref) (ref, bool)
+ "syscall/js.valueInvoke": (sp) => {
+ sp >>>= 0;
+ try {
+ const v = loadValue(sp + 8);
+ const args = loadSliceOfValues(sp + 16);
+ const result = Reflect.apply(v, undefined, args);
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, result);
+ this.mem.setUint8(sp + 48, 1);
+ } catch (err) {
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, err);
+ this.mem.setUint8(sp + 48, 0);
+ }
+ },
+
+ // func valueNew(v ref, args []ref) (ref, bool)
+ "syscall/js.valueNew": (sp) => {
+ sp >>>= 0;
+ try {
+ const v = loadValue(sp + 8);
+ const args = loadSliceOfValues(sp + 16);
+ const result = Reflect.construct(v, args);
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, result);
+ this.mem.setUint8(sp + 48, 1);
+ } catch (err) {
+ sp = this._inst.exports.getsp() >>> 0; // see comment above
+ storeValue(sp + 40, err);
+ this.mem.setUint8(sp + 48, 0);
+ }
+ },
+
+ // func valueLength(v ref) int
+ "syscall/js.valueLength": (sp) => {
+ sp >>>= 0;
+ setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
+ },
+
+ // valuePrepareString(v ref) (ref, int)
+ "syscall/js.valuePrepareString": (sp) => {
+ sp >>>= 0;
+ const str = encoder.encode(String(loadValue(sp + 8)));
+ storeValue(sp + 16, str);
+ setInt64(sp + 24, str.length);
+ },
+
+ // valueLoadString(v ref, b []byte)
+ "syscall/js.valueLoadString": (sp) => {
+ sp >>>= 0;
+ const str = loadValue(sp + 8);
+ loadSlice(sp + 16).set(str);
+ },
+
+ // func valueInstanceOf(v ref, t ref) bool
+ "syscall/js.valueInstanceOf": (sp) => {
+ sp >>>= 0;
+ this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
+ },
+
+ // func copyBytesToGo(dst []byte, src ref) (int, bool)
+ "syscall/js.copyBytesToGo": (sp) => {
+ sp >>>= 0;
+ const dst = loadSlice(sp + 8);
+ const src = loadValue(sp + 32);
+ if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
+ this.mem.setUint8(sp + 48, 0);
+ return;
+ }
+ const toCopy = src.subarray(0, dst.length);
+ dst.set(toCopy);
+ setInt64(sp + 40, toCopy.length);
+ this.mem.setUint8(sp + 48, 1);
+ },
+
+ // func copyBytesToJS(dst ref, src []byte) (int, bool)
+ "syscall/js.copyBytesToJS": (sp) => {
+ sp >>>= 0;
+ const dst = loadValue(sp + 8);
+ const src = loadSlice(sp + 16);
+ if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
+ this.mem.setUint8(sp + 48, 0);
+ return;
+ }
+ const toCopy = src.subarray(0, dst.length);
+ dst.set(toCopy);
+ setInt64(sp + 40, toCopy.length);
+ this.mem.setUint8(sp + 48, 1);
+ },
+
+ "debug": (value) => {
+ console.log(value);
+ },
+ }
+ };
+ }
+
+ async run(instance) {
+ if (!(instance instanceof WebAssembly.Instance)) {
+ throw new Error("Go.run: WebAssembly.Instance expected");
+ }
+ this._inst = instance;
+ this.mem = new DataView(this._inst.exports.mem.buffer);
+ this._values = [ // JS values that Go currently has references to, indexed by reference id
+ NaN,
+ 0,
+ null,
+ true,
+ false,
+ globalThis,
+ this,
+ ];
+ this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
+ this._ids = new Map([ // mapping from JS values to reference ids
+ [0, 1],
+ [null, 2],
+ [true, 3],
+ [false, 4],
+ [globalThis, 5],
+ [this, 6],
+ ]);
+ this._idPool = []; // unused ids that have been garbage collected
+ this.exited = false; // whether the Go program has exited
+
+ // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
+ let offset = 4096;
+
+ const strPtr = (str) => {
+ const ptr = offset;
+ const bytes = encoder.encode(str + "\0");
+ new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
+ offset += bytes.length;
+ if (offset % 8 !== 0) {
+ offset += 8 - (offset % 8);
+ }
+ return ptr;
+ };
+
+ const argc = this.argv.length;
+
+ const argvPtrs = [];
+ this.argv.forEach((arg) => {
+ argvPtrs.push(strPtr(arg));
+ });
+ argvPtrs.push(0);
+
+ const keys = Object.keys(this.env).sort();
+ keys.forEach((key) => {
+ argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
+ });
+ argvPtrs.push(0);
+
+ const argv = offset;
+ argvPtrs.forEach((ptr) => {
+ this.mem.setUint32(offset, ptr, true);
+ this.mem.setUint32(offset + 4, 0, true);
+ offset += 8;
+ });
+
+ // The linker guarantees global data starts from at least wasmMinDataAddr.
+ // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
+ const wasmMinDataAddr = 4096 + 8192;
+ if (offset >= wasmMinDataAddr) {
+ throw new Error("total length of command line and environment variables exceeds limit");
+ }
+
+ this._inst.exports.run(argc, argv);
+ if (this.exited) {
+ this._resolveExitPromise();
+ }
+ await this._exitPromise;
+ }
+
+ _resume() {
+ if (this.exited) {
+ throw new Error("Go program has already exited");
+ }
+ this._inst.exports.resume();
+ if (this.exited) {
+ this._resolveExitPromise();
+ }
+ }
+
+ _makeFuncWrapper(id) {
+ const go = this;
+ return function () {
+ const event = { id: id, this: this, args: arguments };
+ go._pendingEvent = event;
+ go._resume();
+ return event.result;
+ };
+ }
+ }
+})();
diff --git a/misc/wasm/wasm_exec_node.js b/misc/wasm/wasm_exec_node.js
new file mode 100644
index 0000000..9860690
--- /dev/null
+++ b/misc/wasm/wasm_exec_node.js
@@ -0,0 +1,39 @@
+// Copyright 2021 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.
+
+"use strict";
+
+if (process.argv.length < 3) {
+ console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
+ process.exit(1);
+}
+
+globalThis.require = require;
+globalThis.fs = require("fs");
+globalThis.TextEncoder = require("util").TextEncoder;
+globalThis.TextDecoder = require("util").TextDecoder;
+
+globalThis.performance ??= require("performance");
+
+globalThis.crypto ??= require("crypto");
+
+require("./wasm_exec");
+
+const go = new Go();
+go.argv = process.argv.slice(2);
+go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
+go.exit = process.exit;
+WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
+ process.on("exit", (code) => { // Node.js exits if no event handler is pending
+ if (code === 0 && !go.exited) {
+ // deadlock, make Go print error and stack traces
+ go._pendingEvent = { id: 0 };
+ go._resume();
+ }
+ });
+ return go.run(result.instance);
+}).catch((err) => {
+ console.error(err);
+ process.exit(1);
+});