Initial commit: Go 1.23 release state

This commit is contained in:
Vorapol Rinsatitnon
2024-09-21 23:49:08 +10:00
commit 17cd57a668
13231 changed files with 3114330 additions and 0 deletions

45
misc/cgo/gmp/fib.go Normal file
View File

@@ -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)
}
}

379
misc/cgo/gmp/gmp.go Normal file
View File

@@ -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
}

73
misc/cgo/gmp/pi.go Normal file
View File

@@ -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())
}

View File

@@ -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.

View File

@@ -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>

View File

@@ -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 });
});
}
});

View File

@@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -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"
]
}

View File

@@ -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>

View File

@@ -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;
});

5
misc/editors Normal file
View File

@@ -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

6
misc/go.mod Normal file
View File

@@ -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.22

View File

@@ -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

View File

@@ -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")
}

View File

@@ -0,0 +1,527 @@
// 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` +
`; export HOME="` + deviceRoot + `/home"` +
`; 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
)

57
misc/ios/README Normal file
View File

@@ -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.

23
misc/ios/clangwrap.sh Executable file
View File

@@ -0,0 +1,23 @@
#!/bin/sh
# This script configures clang to target the iOS simulator. If you'd like to
# build for real iOS devices, change SDK to "iphoneos" and PLATFORM to "ios".
# 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
# iphonesimulator with one of them.
SDK=iphonesimulator
PLATFORM=ios-simulator
if [ "$GOARCH" == "arm64" ]; then
CLANGARCH="arm64"
else
CLANGARCH="x86_64"
fi
SDK_PATH=`xcrun --sdk $SDK --show-sdk-path`
# 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 "$@"

133
misc/ios/detect.go Normal file
View File

@@ -0,0 +1,133 @@
// 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
// 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)
}

366
misc/ios/go_ios_exec.go Normal file
View File

@@ -0,0 +1,366 @@
// Copyright 2024 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 the iOS Simulator using the XCode toolchain.
package main
import (
"fmt"
"go/build"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
)
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
}
err = runOnSimulator(appdir)
if err != 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 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 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 runSimulator(appdir, bundleID string, args []string) error {
xcrunArgs := []string{"simctl", "spawn",
"booted",
appdir + "/gotest",
}
xcrunArgs = append(xcrunArgs, args...)
cmd := exec.Command("xcrun", xcrunArgs...)
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("xcrun simctl launch booted %q: %v", bundleID, err)
}
return nil
}
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>
`

191
misc/linkcheck/linkcheck.go Normal file
View File

@@ -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)
}
}

17
misc/wasm/go_js_wasm_exec Executable file
View File

@@ -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" "$@"

23
misc/wasm/go_wasip1_wasm_exec Executable file
View File

@@ -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" -W max-wasm-stack=1048576 ${GOWASIRUNTIMEARGS:-} "$1" "${@:2}"
;;
*)
echo "Unknown Go WASI runtime specified: $GOWASIRUNTIME"
exit 1
;;
esac

49
misc/wasm/wasm_exec.html Normal file
View File

@@ -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>

561
misc/wasm/wasm_exec.js Normal file
View File

@@ -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;
};
}
}
})();

View File

@@ -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);
});