Compare commits

..

1 Commits

Author SHA1 Message Date
Ayke van Laethem 8e7a288bb4 wasm: use wasip1 API for parameter/env passing
Instead of hardcoding the command line parameters and the environment
variables in the binary, pass them at runtime to Node.js and use the
WASIp1 API to retrieve them in wasm_exec.js.

The only real benefit right now is that it becomes possible to change
`go.argv` and `go.env` before running a wasm binary.

This also changes the syscall package for GOOS=js: it now becomes more
like a libc (using wasi-libc), which means error values like
`syscall.EEXIST` will actually match the one returned by the relevant
libc function.

Wasm binary size for packages that import the os package will be
increased somewhat.
2024-10-30 11:14:18 +01:00
79 changed files with 503 additions and 1591 deletions
-2
View File
@@ -90,8 +90,6 @@ commands:
name: Check Go code formatting
command: make fmt-check lint
- run: make gen-device -j4
# TODO: change this to -skip='TestErrors|TestWasm' with Go 1.20
- run: go test -tags=llvm<<parameters.llvm>> -short -run='TestBuild|TestTest|TestGetList|TestTraceback'
- run: make smoketest XTENSA=0
- save_cache:
key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
+3 -10
View File
@@ -26,8 +26,6 @@ jobs:
goarch: arm64
runs-on: ${{ matrix.os }}
steps:
- name: exit early
run: command-does-not-exist
- name: Install Dependencies
run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
@@ -35,9 +33,6 @@ jobs:
uses: actions/checkout@v4
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v5
with:
@@ -109,7 +104,7 @@ jobs:
- name: Test stdlib packages
run: make tinygo-test
- name: Make release artifact
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
@@ -119,8 +114,8 @@ jobs:
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4
with:
name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
name: darwin-${{ matrix.goarch }}-double-zipped
path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
- name: Smoke tests
run: make smoketest TINYGO=$(PWD)/build/tinygo
test-macos-homebrew:
@@ -130,8 +125,6 @@ jobs:
matrix:
version: [16, 17, 18]
steps:
- name: exit early
run: command-does-not-exist
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- name: Fix Python symlinks
+14 -26
View File
@@ -19,11 +19,7 @@ jobs:
runs-on: ubuntu-latest
container:
image: golang:1.23-alpine
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: exit early
run: command-does-not-exist
- name: Install apk dependencies
# tar: needed for actions/cache@v4
# git+openssh: needed for checkout (I think?)
@@ -36,9 +32,6 @@ jobs:
uses: actions/checkout@v4
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Cache Go
uses: actions/cache@v4
with:
@@ -127,15 +120,15 @@ jobs:
- name: Build TinyGo release
run: |
make release deb -j3 STATIC=1
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v4
with:
name: linux-amd64-double-zipped-${{ steps.version.outputs.version }}
name: linux-amd64-double-zipped
path: |
/tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
/tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
/tmp/tinygo.linux-amd64.tar.gz
/tmp/tinygo_amd64.deb
test-linux-build:
# Test the binaries built in the build-linux job by running the smoke tests.
runs-on: ubuntu-latest
@@ -159,11 +152,11 @@ jobs:
- name: Download release artifact
uses: actions/download-artifact@v4
with:
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
name: linux-amd64-double-zipped
- name: Extract release tarball
run: |
mkdir -p ~/lib
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
- run: make tinygo-test-wasip1-fast
- run: make tinygo-test-wasip2-fast
@@ -173,8 +166,6 @@ jobs:
# potential bugs.
runs-on: ubuntu-latest
steps:
- name: exit early
run: command-does-not-exist
- name: Checkout
uses: actions/checkout@v4
with:
@@ -306,9 +297,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install apt dependencies
run: |
sudo apt-get update
@@ -393,11 +381,11 @@ jobs:
- name: Download amd64 release
uses: actions/download-artifact@v4
with:
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
name: linux-amd64-double-zipped
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
@@ -405,12 +393,12 @@ jobs:
- name: Create ${{ matrix.goarch }} release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }}
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb
cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb
- name: Publish release artifact
uses: actions/upload-artifact@v4
with:
name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
name: linux-${{ matrix.goarch }}-double-zipped
path: |
/tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb
/tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_${{ matrix.libc }}.deb
-2
View File
@@ -15,8 +15,6 @@ jobs:
nix-test:
runs-on: ubuntu-latest
steps:
- name: exit early
run: command-does-not-exist
- name: Uninstall system LLVM
# Hack to work around issue where we still include system headers for
# some reason.
-2
View File
@@ -15,8 +15,6 @@ jobs:
permissions:
pull-requests: write
steps:
- name: exit early
run: command-does-not-exist
# Prepare, install tools
- name: Add GOBIN to $PATH
run: |
@@ -1,4 +0,0 @@
#!/bin/sh
# Extract the version string from the source code, to be stored in a variable.
grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g'
+26 -52
View File
@@ -14,8 +14,6 @@ concurrency:
jobs:
build-windows:
runs-on: windows-2022
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
@@ -23,30 +21,22 @@ jobs:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
#- uses: brechtm/setup-scoop@v2
# with:
# scoop_update: 'false'
#- name: Install Dependencies
# shell: bash
# run: |
# scoop install ninja binaryen
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies
shell: bash
run: |
scoop install ninja binaryen
- name: Checkout
uses: actions/checkout@v4
- name: submodules
shell: bash
run: git submodule update --init lib/mingw-w64
- name: Extract TinyGo version
id: version
shell: bash
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: command
shell: bash
run: go env
#- name: Install Go
# uses: actions/setup-go@v5
# with:
# go-version: '1.23'
# cache: true
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Restore cached LLVM source
uses: actions/cache/restore@v4
id: cache-llvm-source
@@ -95,25 +85,6 @@ jobs:
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Restore Go cache
uses: actions/cache/restore@v4
with:
key: go-cache-v2
path: |
C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-short -run=TestBuild -v"
- name: Save Go cache
uses: actions/cache/save@v4
with:
key: go-cache-v2
path: |
C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod
- name: exit
run: command-does-not-exist
- name: Cache wasi-libc sysroot
uses: actions/cache@v4
id: cache-wasi-libc
@@ -128,13 +99,16 @@ jobs:
scoop install wasmtime@14.0.4
- name: make gen-device
run: make -j3 gen-device
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-short"
- name: Build TinyGo release tarball
shell: bash
run: make build/release -j4
- name: Make release artifact
shell: bash
working-directory: build/release
run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo
run: 7z -tzip a release.zip tinygo
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
@@ -144,8 +118,8 @@ jobs:
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4
with:
name: windows-amd64-double-zipped-${{ steps.version.outputs.version }}
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
name: windows-amd64-double-zipped
path: build/release/release.zip
smoke-test-windows:
runs-on: windows-2022
@@ -174,12 +148,12 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
name: windows-amd64-double-zipped
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x tinygo*.windows-amd64.zip -r
run: 7z x release.zip -r
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -204,12 +178,12 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
name: windows-amd64-double-zipped
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x tinygo*.windows-amd64.zip -r
run: 7z x release.zip -r
- name: Test stdlib packages
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -240,11 +214,11 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
name: windows-amd64-double-zipped
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x tinygo*.windows-amd64.zip -r
run: 7z x release.zip -r
- name: Test stdlib packages on wasip1
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
+5 -7
View File
@@ -191,7 +191,7 @@ gen-device: gen-device-stm32
endif
gen-device-avr:
#@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
@@ -263,7 +263,7 @@ endif
.PHONY: wasi-libc
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
#@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM)
# Generate WASI syscall bindings
@@ -291,9 +291,9 @@ endif
tinygo: ## Build the TinyGo compiler
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
test: #wasi-libc check-nodejs-version
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -race -buildmode exe -tags "byollvm osusergo" .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc check-nodejs-version
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
TEST_PACKAGES_SLOW = \
@@ -926,7 +926,6 @@ endif
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/fcntl build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src
@@ -942,7 +941,6 @@ endif
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
-9
View File
@@ -116,7 +116,6 @@ var libMusl = Library{
"env/*.c",
"errno/*.c",
"exit/*.c",
"fcntl/*.c",
"internal/defsysinfo.c",
"internal/libc.c",
"internal/syscall_ret.c",
@@ -137,20 +136,12 @@ var libMusl = Library{
"thread/*.c",
"time/*.c",
"unistd/*.c",
"process/*.c",
}
if arch == "arm" {
// These files need to be added to the start for some reason.
globs = append([]string{"thread/arm/*.c"}, globs...)
}
if arch != "aarch64" && arch != "mips" {
//aarch64 and mips have no architecture specific code, either they
// are not supported or don't need any?
globs = append([]string{"process/" + arch + "/*.s"}, globs...)
}
var sources []string
seenSources := map[string]struct{}{}
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
+3 -3
View File
@@ -41,9 +41,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4600, 280, 0, 2268},
{"microbit", "examples/serial", 2908, 388, 8, 2272},
{"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6832},
{"hifive1b", "examples/echo", 4568, 280, 0, 2268},
{"microbit", "examples/serial", 2868, 388, 8, 2272},
{"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
+2 -18
View File
@@ -32,26 +32,10 @@ func runCCompiler(flags ...string) error {
cmd.Stderr = os.Stderr
// Make sure the command doesn't use any environmental variables.
// Most importantly, it should not use C_INCLUDE_PATH and the like.
// Most importantly, it should not use C_INCLUDE_PATH and the like. But
// removing all environmental variables also works.
cmd.Env = []string{}
// Let some environment variables through. One important one is the
// temporary directory, especially on Windows it looks like Clang breaks if
// the temporary directory has not been set.
// See: https://github.com/tinygo-org/tinygo/issues/4557
// Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435
for _, env := range os.Environ() {
// We could parse the key and look it up in a map, but since there are
// only a few keys iterating through them is easier and maybe even
// faster.
for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} {
if strings.HasPrefix(env, prefix) {
cmd.Env = append(cmd.Env, env)
break
}
}
}
return cmd.Run()
}
+2 -2
View File
@@ -1148,7 +1148,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
if alias := cgoAliases["C."+name]; alias != "" {
return alias
}
node := f.getASTDeclNode(name, found)
node := f.getASTDeclNode(name, found, iscall)
if node, ok := node.(*ast.FuncDecl); ok {
if !iscall {
return node.Name.Name + "$funcaddr"
@@ -1160,7 +1160,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
// getASTDeclNode will declare the given C AST node (if not already defined) and
// returns it.
func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node {
func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) ast.Node {
if node, ok := f.defined[name]; ok {
// Declaration was found in the current file, so return it immediately.
return node
+4 -136
View File
@@ -54,72 +54,8 @@ func init() {
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value, f)
// If params is non-nil (could be a zero length slice), this const is
// actually a function-call like expression from another macro.
// This means we have to parse a string like "(a, b) (a+b)".
// We do this by parsing the parameters at the start and then treating the
// following like a normal constant expression.
if params != nil {
// Parse opening paren.
if t.curToken != token.LPAREN {
return nil, unexpectedToken(t, token.LPAREN)
}
t.Next()
// Parse parameters (identifiers) and closing paren.
var paramIdents []string
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
// No parameters, break early.
t.Next()
break
}
// Read the parameter name.
if t.curToken != token.IDENT {
return nil, unexpectedToken(t, token.IDENT)
}
paramIdents = append(paramIdents, t.curValue)
t.Next()
// Read the next token: either a continuation (comma) or end of list
// (rparen).
if t.curToken == token.RPAREN {
// End of parameter list.
t.Next()
break
} else if t.curToken == token.COMMA {
// Comma, so there will be another parameter name.
t.Next()
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'",
}
}
}
// Report an error if there is a mismatch in parameter length.
// The error is reported at the location of the closing paren from the
// caller location.
if len(params) != len(paramIdents) {
return nil, &scanner.Error{
Pos: t.fset.Position(callerPos),
Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)),
}
}
// Assign values to the parameters.
// These parameter names are closer in 'scope' than other identifiers so
// will be used first when parsing an identifier.
for i, name := range paramIdents {
t.params[name] = params[i]
}
}
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
@@ -160,68 +96,6 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
// If the identifier is one of the parameters of this function-like macro,
// use the parameter value.
if val, ok := t.params[t.curValue]; ok {
return val, nil
}
if t.f != nil {
// Check whether this identifier is actually a macro "call" with
// parameters. In that case, we should parse the parameters and pass it
// on to a new invocation of parseConst.
if t.peekToken == token.LPAREN {
if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) {
// We know the current and peek tokens (the peek one is the '('
// token). So skip ahead until the current token is the first
// unknown token.
t.Next()
t.Next()
// Parse the list of parameters until ')' (rparen) is found.
params := []ast.Expr{}
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
break
}
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
params = append(params, x)
t.Next()
if t.curToken == token.COMMA {
t.Next()
} else if t.curToken == token.RPAREN {
break
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'",
}
}
}
// Evaluate the macro value and use it as the identifier value.
rparen := t.curPos
pos, text := t.f.getMacro(cursor)
return parseConst(pos, t.fset, text, params, rparen, t.f)
}
}
// Normally the name is something defined in the file (like another
// macro) which we get the declaration from using getASTDeclName.
// This ensures that names that are only referenced inside a macro are
// still getting defined.
if cursor, ok := t.f.names[t.curValue]; ok {
return &ast.Ident{
NamePos: t.curPos,
Name: t.f.getASTDeclName(t.curValue, cursor, false),
}, nil
}
}
// t.f is nil during testing. This is a fallback.
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
@@ -290,25 +164,21 @@ func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
f *cgoFile
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
params map[string]ast.Expr
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer {
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
f: f,
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
params: make(map[string]ast.Expr),
}
// Parse the first two tokens (cur and peek).
t.Next()
@@ -360,7 +230,7 @@ func (t *tokenizer) Next() {
t.peekValue = t.buf[:2]
t.buf = t.buf[2:]
return
case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
@@ -368,8 +238,6 @@ func (t *tokenizer) Next() {
t.peekToken = token.LPAREN
case ')':
t.peekToken = token.RPAREN
case ',':
t.peekToken = token.COMMA
case '+':
t.peekToken = token.ADD
case '-':
+1 -1
View File
@@ -59,7 +59,7 @@ func TestParseConst(t *testing.T) {
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil)
expr, err := parseConst(startPos, fset, tc.C)
s := "<invalid>"
if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") {
+39 -59
View File
@@ -63,7 +63,6 @@ long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
@@ -371,8 +370,45 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_MacroDefinition:
tokenPos, value := f.getMacro(c)
expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f)
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset) + len(name)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
C.clang_disposeTokens(tu, rawTokens, numTokens)
value := sourceBuf.String()
// Try to convert this #define into a Go constant expression.
tokenPos := token.NoPos
if pos != token.NoPos {
tokenPos = pos + token.Pos(len(name))
}
expr, scannerError := parseConst(tokenPos, f.fset, value)
if scannerError != nil {
f.errors = append(f.errors, *scannerError)
return nil, nil
@@ -452,62 +488,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}
}
// Return whether this is a macro that's also function-like, like this:
//
// #define add(a, b) (a+b)
func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool {
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition {
return false
}
return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0
}
// Get the macro value: the position in the source file and the string value of
// the macro.
func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) {
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
defer C.clang_disposeTokens(tu, rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
value = sourceBuf.String()
// Obtain the position of this token. This is the position of the first
// character in the 'value' string and is used to report errors at the
// correct location in the source file.
pos = f.getCursorPosition(c)
return
}
func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString)
s = C.GoString(rawString)
-4
View File
@@ -84,7 +84,3 @@ unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) {
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) {
return clang_Cursor_isMacroFunctionLike(c);
}
-16
View File
@@ -3,26 +3,10 @@ package main
/*
#define foo 3
#define bar foo
#define unreferenced 4
#define referenced unreferenced
#define fnlike() 5
#define fnlike_val fnlike()
#define square(n) (n*n)
#define square_val square(20)
#define add(a, b) (a + b)
#define add_val add(3, 5)
*/
import "C"
const (
Foo = C.foo
Bar = C.bar
Baz = C.referenced
fnlike = C.fnlike_val
square = C.square_val
add = C.add_val
)
-5
View File
@@ -47,8 +47,3 @@ type (
const C.foo = 3
const C.bar = C.foo
const C.unreferenced = 4
const C.referenced = C.unreferenced
const C.fnlike_val = 5
const C.square_val = (20 * 20)
const C.add_val = (3 + 5)
-8
View File
@@ -26,11 +26,6 @@ import "C"
// #warning another warning
import "C"
// #define add(a, b) (a+b)
// #define add_toomuch add(1, 2, 3)
// #define add_toolittle add(1)
import "C"
// Make sure that errors for the following lines won't change with future
// additions to the CGo preamble.
//
@@ -56,7 +51,4 @@ var (
// constants passed by a command line parameter
_ = C.SOME_PARAM_CONST_invalid
_ = C.SOME_PARAM_CONST_valid
_ = C.add_toomuch
_ = C.add_toolittle
)
-4
View File
@@ -7,8 +7,6 @@
// testdata/errors.go:16:33: unexpected token ), expected end of expression
// testdata/errors.go:17:34: unexpected token ), expected end of expression
// -: unexpected token INT, expected end of expression
// testdata/errors.go:30:35: unexpected number of parameters: expected 2, got 3
// testdata/errors.go:31:31: unexpected number of parameters: expected 2, got 1
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
@@ -19,8 +17,6 @@
// testdata/errors.go:114: undefined: C.SOME_CONST_b
// testdata/errors.go:116: undefined: C.SOME_CONST_startspace
// testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid
// testdata/errors.go:122: undefined: C.add_toomuch
// testdata/errors.go:123: undefined: C.add_toolittle
package main
+9 -1
View File
@@ -406,7 +406,15 @@ func (c *Config) LDFlags() []string {
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
ldflags = append(ldflags, c.Options.ExtLDFlags...)
if c.Options.ExtLDFlags != "" {
ext, err := shlex.Split(c.Options.ExtLDFlags)
if err != nil {
// if shlex can't split it, pass it as-is and let the external linker complain
ext = []string{c.Options.ExtLDFlags}
}
ldflags = append(ldflags, ext...)
}
return ldflags
}
+1 -1
View File
@@ -58,7 +58,7 @@ type Options struct {
Timeout time.Duration
WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option
ExtLDFlags []string
ExtLDFlags string
}
// Verify performs a validation on the given options, raising an error if options are not valid.
+3 -7
View File
@@ -17,7 +17,6 @@ import (
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/loader"
"github.com/tinygo-org/tinygo/src/tinygo"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/types/typeutil"
"tinygo.org/x/go-llvm"
@@ -1681,10 +1680,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
result = b.CreateSelect(cmp, result, arg, "")
}
return result, nil
case "panic":
// This is rare, but happens in "defer panic()".
b.createRuntimeInvoke("_panic", argValues, "")
return llvm.Value{}, nil
case "print", "println":
for i, value := range argValues {
if i >= 1 && callName == "println" {
@@ -1870,9 +1865,10 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
}
return llvm.ConstInt(b.ctx.Int1Type(), supportsRecover, false), nil
case name == "runtime.panicStrategy":
// These constants are defined in src/runtime/panic.go.
panicStrategy := map[string]uint64{
"print": tinygo.PanicStrategyPrint,
"trap": tinygo.PanicStrategyTrap,
"print": 1, // panicStrategyPrint
"trap": 2, // panicStrategyTrap
}[b.Config.PanicStrategy]
return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil
case name == "runtime/interrupt.New":
-3
View File
@@ -41,8 +41,6 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Create a new global of type runtime/interrupt.handle. Globals of this
// type are lowered in the interrupt lowering pass.
// It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the
// global has the lower bits unset.
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := b.getLLVMType(globalType)
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
@@ -50,7 +48,6 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetAlignment(1)
initializer := llvm.ConstNull(globalLLVMType)
initializer = b.CreateInsertValue(initializer, funcContext, 0, "")
initializer = b.CreateInsertValue(initializer, funcPtr, 1, "")
+11 -5
View File
@@ -6,11 +6,17 @@ import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/src/tinygo"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// constants for hashmap algorithms; must match src/runtime/hashmap.go
const (
hashmapAlgorithmBinary = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
@@ -18,20 +24,20 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
var alg uint64
var alg uint64 // must match values in src/runtime/hashmap.go
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
alg = uint64(tinygo.HashmapAlgorithmString)
alg = hashmapAlgorithmString
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
alg = uint64(tinygo.HashmapAlgorithmBinary)
alg = hashmapAlgorithmBinary
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
alg = uint64(tinygo.HashmapAlgorithmInterface)
alg = hashmapAlgorithmInterface
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
+9 -20
View File
@@ -4,37 +4,29 @@ import (
"errors"
"fmt"
"io"
"runtime/debug"
"strings"
)
// Version of TinyGo.
// Update this value before release of new version of software.
const version = "0.35.0-dev"
const version = "0.34.0"
var (
// This variable is set at build time using -ldflags parameters.
// See: https://stackoverflow.com/a/11355611
GitSha1 string
)
// Return TinyGo version, either in the form 0.30.0 or as a development version
// (like 0.30.0-dev-abcd012).
func Version() string {
v := version
if strings.HasSuffix(version, "-dev") {
if hash := readGitHash(); hash != "" {
v += "-" + hash
}
if strings.HasSuffix(version, "-dev") && GitSha1 != "" {
v += "-" + GitSha1
}
return v
}
func readGitHash() string {
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
return setting.Value[:8]
}
}
}
return ""
}
// GetGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func GetGorootVersion() (major, minor int, err error) {
@@ -50,9 +42,6 @@ func GetGorootVersion() (major, minor int, err error) {
// major, minor, and patch version: 1, 3, and 2 in this example.
// If there is an error, (0, 0, 0) and an error will be returned.
func Parse(version string) (major, minor, patch int, err error) {
if strings.HasPrefix(version, "devel ") {
version = strings.Split(strings.TrimPrefix(version, "devel "), version)[0]
}
if version == "" || version[:2] != "go" {
return 0, 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
-1
View File
@@ -21,7 +21,6 @@ func TestParse(t *testing.T) {
{"go1.23.5-rc6", 1, 23, 5, false},
{"go2.0", 2, 0, 0, false},
{"go2.0.15", 2, 0, 15, false},
{"devel go1.24-f99f5da18f Thu Nov 14 22:29:26 2024 +0000 darwin/arm64", 1, 24, 0, false},
}
for _, tt := range tests {
t.Run(tt.v, func(t *testing.T) {
+3 -2
View File
@@ -2,7 +2,7 @@ module github.com/tinygo-org/tinygo/internal/tools
go 1.22.4
require github.com/bytecodealliance/wasm-tools-go v0.3.1
require github.com/bytecodealliance/wasm-tools-go v0.3.0
require (
github.com/coreos/go-semver v0.3.1 // indirect
@@ -12,7 +12,8 @@ require (
github.com/regclient/regclient v0.7.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect
github.com/urfave/cli/v3 v3.0.0-alpha9 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sys v0.26.0 // indirect
)
+8 -6
View File
@@ -1,5 +1,5 @@
github.com/bytecodealliance/wasm-tools-go v0.3.1 h1:9Q9PjSzkbiVmkUvZ7nYCfJ02mcQDBalxycA3s8g7kR4=
github.com/bytecodealliance/wasm-tools-go v0.3.1/go.mod h1:vNAQ8DAEp6xvvk+TUHah5DslLEa76f4H6e737OeaxuY=
github.com/bytecodealliance/wasm-tools-go v0.3.0 h1:9aeDFYpbi3gtIW/nJCH+P+LhFMqezGoOfzqbUZLadho=
github.com/bytecodealliance/wasm-tools-go v0.3.0/go.mod h1:VY+9FlpLi6jnhCrZLkyJjF9rjU4aEekgaRTk28MS2JE=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -23,12 +23,14 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgLKD+qV+Y=
github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=
github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-1
View File
@@ -256,7 +256,6 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"runtime/": false,
"sync/": true,
"testing/": true,
"tinygo/": false,
"unique/": false,
}
+6 -14
View File
@@ -770,12 +770,11 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error {
// for the given emulator.
func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) {
// Determine whether we're on a system that supports environment variables
// and command line parameters (operating systems, WASI) or not (baremetal,
// WebAssembly in the browser). If we're on a system without an environment,
// we need to pass command line arguments and environment variables through
// global variables (built into the binary directly) instead of the
// conventional way.
needsEnvInVars := config.GOOS() == "js"
// and command line parameters (operating systems, WASI) or not (baremetal).
// If we're on a system without an environment, we need to pass command line
// arguments and environment variables through global variables (built into
// the binary directly) instead of the conventional way.
needsEnvInVars := false
for _, tag := range config.BuildTags() {
if tag == "baremetal" {
needsEnvInVars = true
@@ -1641,19 +1640,12 @@ func main() {
Timeout: *timeout,
WITPackage: witPackage,
WITWorld: witWorld,
ExtLDFlags: extLDFlags,
}
if *printCommands {
options.PrintCommands = printCommand
}
if extLDFlags != "" {
options.ExtLDFlags, err = shlex.Split(extLDFlags)
if err != nil {
fmt.Fprintln(os.Stderr, "could not parse -extldflags:", err)
os.Exit(1)
}
}
err = options.Verify()
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
+17 -139
View File
@@ -7,7 +7,6 @@ import (
"bufio"
"bytes"
"context"
"crypto/sha256"
"errors"
"flag"
"io"
@@ -16,7 +15,7 @@ import (
"reflect"
"regexp"
"runtime"
"strconv"
"slices"
"strings"
"sync"
"testing"
@@ -26,9 +25,9 @@ import (
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/tetratelabs/wazero/sys"
"github.com/tinygo-org/tinygo/builder"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/diagnostics"
"github.com/tinygo-org/tinygo/goenv"
)
@@ -115,15 +114,10 @@ func TestBuild(t *testing.T) {
return
}
t.Run("Debugging", func(t *testing.T) {
for i := 0; i < 5; i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
options := optionsFromTarget("", sema)
runTest("alias.go", options, t, nil, nil)
})
}
t.Run("Host", func(t *testing.T) {
t.Parallel()
runPlatTests(optionsFromTarget("", sema), tests, t)
})
return
// Test a few build options.
t.Run("build-options", func(t *testing.T) {
@@ -426,67 +420,19 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
// Build the test binary.
stdout := &bytes.Buffer{}
_, fileExt := config.EmulatorFormat()
tmpdir := t.TempDir()
result, err := builder.Build(pkgName, fileExt, tmpdir, config)
_, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
return cmd.Run()
})
if err != nil {
t.Fatal("build failed:", err)
w := &bytes.Buffer{}
diagnostics.CreateDiagnostics(err).WriteTo(w, "")
for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
t.Log(line)
}
t.Fail()
return
}
data, err := os.ReadFile(result.Executable)
if err != nil {
t.Fatal("failed to read executable:", err)
}
hash := sha256.Sum256(data)
t.Logf("executable hash and size: %d %x", len(data), hash)
for i := 0; i < 100; i++ {
i := i
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
stdout := &bytes.Buffer{}
cmd := exec.Command(result.Executable)
cmd.Stdout = stdout
cmd.Stderr = stdout
err := cmd.Run()
if err != nil {
t.Log("run error:", err)
}
checkOutput(t, expectedOutputPath, stdout.Bytes())
})
}
return
//_, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
// data, err := os.ReadFile(result.Executable)
// if err != nil {
// t.Fatal("failed to read executable:", err)
// }
// hash := sha256.Sum256(data)
// t.Logf("executable hash and size: %d %x", len(data), hash)
// t.Log("command:", cmd)
// err = cmd.Run()
// if err == nil {
// t.Log(" error is nil!")
// } else {
// t.Log(" error:", err)
// }
// return err
//})
//if err != nil {
// w := &bytes.Buffer{}
// diagnostics.CreateDiagnostics(err).WriteTo(w, "")
// for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
// t.Log(line)
// }
// if stdout.Len() != 0 {
// t.Logf("output:\n%s", stdout.String())
// }
// t.Fail()
// return
//}
actual := stdout.Bytes()
if config.EmulatorName() == "simavr" {
// Strip simavr log formatting.
@@ -571,7 +517,7 @@ func TestWebAssembly(t *testing.T) {
}
}
}
if !stringSlicesEqual(imports, tc.imports) {
if !slices.Equal(imports, tc.imports) {
t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports)
}
}
@@ -579,20 +525,6 @@ func TestWebAssembly(t *testing.T) {
}
}
func stringSlicesEqual(s1, s2 []string) bool {
// We can use slices.Equal once we drop support for Go 1.20 (it was added in
// Go 1.21).
if len(s1) != len(s2) {
return false
}
for i, s := range s1 {
if s != s2[i] {
return false
}
}
return true
}
func TestWasmExport(t *testing.T) {
t.Parallel()
@@ -751,14 +683,7 @@ func TestWasmExport(t *testing.T) {
if tc.command {
// Call _start (the entry point), which calls
// tester.callTestMain, which then runs all the tests.
_, err := mod.ExportedFunction("_start").Call(ctx)
if err != nil {
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() == 0 {
// Exited with code 0. Nothing to worry about.
} else {
t.Error("failed to run _start:", err)
}
}
mustCall(mod.ExportedFunction("_start").Call(ctx))
} else {
// Run the _initialize call, because this is reactor mode wasm.
mustCall(mod.ExportedFunction("_initialize").Call(ctx))
@@ -803,7 +728,6 @@ func TestWasmFuncOf(t *testing.T) {
// Test //go:wasmexport in JavaScript (using NodeJS).
func TestWasmExportJS(t *testing.T) {
t.Parallel()
type testCase struct {
name string
buildMode string
@@ -814,9 +738,7 @@ func TestWasmExportJS(t *testing.T) {
{name: "c-shared", buildMode: "c-shared"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
// Build the wasm binary.
tmpdir := t.TempDir()
options := optionsFromTarget("wasm", sema)
@@ -844,56 +766,12 @@ func TestWasmExportJS(t *testing.T) {
}
}
// Test whether Go.run() (in wasm_exec.js) normally returns and returns the
// right exit code.
func TestWasmExit(t *testing.T) {
t.Parallel()
type testCase struct {
name string
output string
}
tests := []testCase{
{name: "normal", output: "exit code: 0\n"},
{name: "exit-0", output: "exit code: 0\n"},
{name: "exit-0-sleep", output: "slept\nexit code: 0\n"},
{name: "exit-1", output: "exit code: 1\n"},
{name: "exit-1-sleep", output: "slept\nexit code: 1\n"},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
options := optionsFromTarget("wasm", sema)
buildConfig, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
buildConfig.Target.Emulator = "node testdata/wasmexit.js {}"
output := &bytes.Buffer{}
_, err = buildAndRun("testdata/wasmexit.go", buildConfig, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
return cmd.Run()
})
if err != nil {
t.Error(err)
}
expected := "wasmexit test: " + tc.name + "\n" + tc.output
checkOutputData(t, []byte(expected), output.Bytes())
})
}
}
// Check whether the output of a test equals the expected output.
func checkOutput(t *testing.T, filename string, actual []byte) {
expectedOutput, err := os.ReadFile(filename)
if err != nil {
t.Fatal("could not read output file:", err)
}
checkOutputData(t, expectedOutput, actual)
}
func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n"))
actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n"))
-10
View File
@@ -71,16 +71,6 @@ func (r *result[Shape, OK, Err]) Err() *Err {
return (*Err)(unsafe.Pointer(&r.data))
}
// Result returns (OK, zero value of Err, false) if r represents the OK case,
// or (zero value of OK, Err, true) if r represents the error case.
// This does not have a pointer receiver, so it can be chained.
func (r result[Shape, OK, Err]) Result() (ok OK, err Err, isErr bool) {
if r.isErr {
return ok, *(*Err)(unsafe.Pointer(&r.data)), true
}
return *(*OK)(unsafe.Pointer(&r.data)), err, false
}
// This function is sized so it can be inlined and optimized away.
func (r *result[Shape, OK, Err]) validate() {
var shape Shape
+1 -13
View File
@@ -5,12 +5,6 @@ import (
"syscall"
)
var (
ErrNotImplementedDir = errors.New("directory setting not implemented")
ErrNotImplementedSys = errors.New("sys setting not implemented")
ErrNotImplementedFiles = errors.New("files setting not implemented")
)
type Signal interface {
String() string
Signal() // to distinguish from other Stringers
@@ -53,10 +47,6 @@ func (p *ProcessState) Sys() interface{} {
return nil // TODO
}
func (p *ProcessState) Exited() bool {
return false // TODO
}
// ExitCode returns the exit code of the exited process, or -1
// if the process hasn't exited or was terminated by a signal.
func (p *ProcessState) ExitCode() int {
@@ -67,10 +57,8 @@ type Process struct {
Pid int
}
// StartProcess starts a new process with the program, arguments and attributes specified by name, argv and attr.
// Arguments to the process (os.Args) are passed via argv.
func StartProcess(name string, argv []string, attr *ProcAttr) (*Process, error) {
return startProcess(name, argv, attr)
return nil, &PathError{Op: "fork/exec", Path: name, Err: ErrNotImplemented}
}
func (p *Process) Wait() (*ProcessState, error) {
-103
View File
@@ -1,103 +0,0 @@
// 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 linux && !baremetal && !tinygo.wasm
package os
import (
"errors"
"runtime"
"syscall"
)
// The only signal values guaranteed to be present in the os package on all
// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
// the process to exit). On Windows, sending os.Interrupt to a process with
// os.Process.Signal is not implemented; it will return an error instead of
// sending a signal.
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL
)
// Keep compatible with golang and always succeed and return new proc with pid on Linux.
func findProcess(pid int) (*Process, error) {
return &Process{Pid: pid}, nil
}
func (p *Process) release() error {
// NOOP for unix.
p.Pid = -1
// no need for a finalizer anymore
runtime.SetFinalizer(p, nil)
return nil
}
// This function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls.
// The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process.
// It thereby replaces the newly created process with the specified command and arguments.
// Differences to upstream golang implementation (https://cs.opensource.google/go/go/+/master:src/syscall/exec_unix.go;l=143):
// * No setting of Process Attributes
// * Ignoring Ctty
// * No ForkLocking (might be introduced by #4273)
// * No parent-child communication via pipes (TODO)
// * No waiting for crashes child processes to prohibit zombie process accumulation / Wait status checking (TODO)
func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error) {
if argv == nil {
return 0, errors.New("exec: no argv")
}
if len(argv) == 0 {
return 0, errors.New("exec: no argv")
}
if attr == nil {
attr = new(ProcAttr)
}
p, err := fork()
pid = int(p)
if err != nil {
return 0, err
}
// else code runs in child, which then should exec the new process
err = execve(argv0, argv, attr.Env)
if err != nil {
// exec failed
return 0, err
}
// 3. TODO: use pipes to communicate back child status
return pid, nil
}
// In Golang, the idiomatic way to create a new process is to use the StartProcess function.
// Since the Model of operating system processes in tinygo differs from the one in Golang, we need to implement the StartProcess function differently.
// The startProcess function is a wrapper around the forkExec function, which is a wrapper around the fork and execve system calls.
// The StartProcess function creates a new process by forking the current process and then calling execve to replace the current process with the new process.
// It thereby replaces the newly created process with the specified command and arguments.
func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err error) {
if attr != nil {
if attr.Dir != "" {
return nil, ErrNotImplementedDir
}
if attr.Sys != nil {
return nil, ErrNotImplementedSys
}
if len(attr.Files) != 0 {
return nil, ErrNotImplementedFiles
}
}
pid, err := forkExec(name, argv, attr)
if err != nil {
return nil, err
}
return findProcess(pid)
}
-78
View File
@@ -1,78 +0,0 @@
//go:build linux && !baremetal && !tinygo.wasm
package os_test
import (
"errors"
. "os"
"runtime"
"syscall"
"testing"
)
// Test the functionality of the forkExec function, which is used to fork and exec a new process.
// This test is not run on Windows, as forkExec is not supported on Windows.
// This test is not run on Plan 9, as forkExec is not supported on Plan 9.
func TestForkExec(t *testing.T) {
if runtime.GOOS != "linux" {
t.Logf("skipping test on %s", runtime.GOOS)
return
}
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{})
if !errors.Is(err, nil) {
t.Fatalf("forkExec failed: %v", err)
}
if proc == nil {
t.Fatalf("proc is nil")
}
if proc.Pid == 0 {
t.Fatalf("forkExec failed: new process has pid 0")
}
}
func TestForkExecErrNotExist(t *testing.T) {
proc, err := StartProcess("invalid", []string{"invalid"}, &ProcAttr{})
if !errors.Is(err, ErrNotExist) {
t.Fatalf("wanted ErrNotExist, got %s\n", err)
}
if proc != nil {
t.Fatalf("wanted nil, got %v\n", proc)
}
}
func TestForkExecProcDir(t *testing.T) {
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Dir: "dir"})
if !errors.Is(err, ErrNotImplementedDir) {
t.Fatalf("wanted ErrNotImplementedDir, got %v\n", err)
}
if proc != nil {
t.Fatalf("wanted nil, got %v\n", proc)
}
}
func TestForkExecProcSys(t *testing.T) {
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Sys: &syscall.SysProcAttr{}})
if !errors.Is(err, ErrNotImplementedSys) {
t.Fatalf("wanted ErrNotImplementedSys, got %v\n", err)
}
if proc != nil {
t.Fatalf("wanted nil, got %v\n", proc)
}
}
func TestForkExecProcFiles(t *testing.T) {
proc, err := StartProcess("/bin/echo", []string{"hello", "world"}, &ProcAttr{Files: []*File{}})
if !errors.Is(err, ErrNotImplementedFiles) {
t.Fatalf("wanted ErrNotImplementedFiles, got %v\n", err)
}
if proc != nil {
t.Fatalf("wanted nil, got %v\n", proc)
}
}
-27
View File
@@ -1,27 +0,0 @@
//go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm
package os
import "syscall"
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL
)
func findProcess(pid int) (*Process, error) {
return &Process{Pid: pid}, nil
}
func (p *Process) release() error {
p.Pid = -1
return nil
}
func forkExec(_ string, _ []string, _ *ProcAttr) (pid int, err error) {
return 0, ErrNotImplemented
}
func startProcess(_ string, _ []string, _ *ProcAttr) (proc *Process, err error) {
return &Process{Pid: 0}, ErrNotImplemented
}
+35
View File
@@ -0,0 +1,35 @@
// 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 aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || wasip1 || wasip2 || windows
package os
import (
"runtime"
"syscall"
)
// The only signal values guaranteed to be present in the os package on all
// systems are os.Interrupt (send the process an interrupt) and os.Kill (force
// the process to exit). On Windows, sending os.Interrupt to a process with
// os.Process.Signal is not implemented; it will return an error instead of
// sending a signal.
var (
Interrupt Signal = syscall.SIGINT
Kill Signal = syscall.SIGKILL
)
// Keep compatible with golang and always succeed and return new proc with pid on Linux.
func findProcess(pid int) (*Process, error) {
return &Process{Pid: pid}, nil
}
func (p *Process) release() error {
// NOOP for unix.
p.Pid = -1
// no need for a finalizer anymore
runtime.SetFinalizer(p, nil)
return nil
}
-58
View File
@@ -1,58 +0,0 @@
//go:build linux && !baremetal && !tinygo.wasm
package os
import (
"syscall"
"unsafe"
)
func fork() (pid int32, err error) {
pid = libc_fork()
if pid != 0 {
if errno := *libc_errno(); errno != 0 {
err = syscall.Errno(*libc_errno())
}
}
return
}
// the golang standard library does not expose interfaces for execve and fork, so we define them here the same way via the libc wrapper
func execve(pathname string, argv []string, envv []string) error {
argv0 := cstring(pathname)
// transform argv and envv into the format expected by execve
argv1 := make([]*byte, len(argv)+1)
for i, arg := range argv {
argv1[i] = &cstring(arg)[0]
}
argv1[len(argv)] = nil
env1 := make([]*byte, len(envv)+1)
for i, env := range envv {
env1[i] = &cstring(env)[0]
}
env1[len(envv)] = nil
ret, _, err := syscall.Syscall(syscall.SYS_EXECVE, uintptr(unsafe.Pointer(&argv0[0])), uintptr(unsafe.Pointer(&argv1[0])), uintptr(unsafe.Pointer(&env1[0])))
if int(ret) != 0 {
return err
}
return nil
}
func cstring(s string) []byte {
data := make([]byte, len(s)+1)
copy(data, s)
// final byte should be zero from the initial allocation
return data
}
//export fork
func libc_fork() int32
// Internal musl function to get the C errno pointer.
//
//export __errno_location
func libc_errno() *int32
+2 -15
View File
@@ -424,8 +424,8 @@ type rawType struct {
meta uint8 // metadata byte, contains kind and flags (see constants above)
}
// All types that have an element type: named, chan, slice, array, map, interface
// (but not pointer because it doesn't have ptrTo).
// All types that have an element type: named, chan, slice, array, map (but not
// pointer because it doesn't have ptrTo).
type elemType struct {
rawType
numMethod uint16
@@ -439,12 +439,6 @@ type ptrType struct {
elem *rawType
}
type interfaceType struct {
rawType
ptrTo *rawType
// TODO: methods
}
type arrayType struct {
rawType
numMethod uint16
@@ -1001,10 +995,6 @@ func (t *rawType) AssignableTo(u Type) bool {
return true
}
if t.underlying() == u.(*rawType).underlying() && (!t.isNamed() || !u.(*rawType).isNamed()) {
return true
}
if u.Kind() == Interface && u.NumMethod() == 0 {
return true
}
@@ -1070,9 +1060,6 @@ func (t *rawType) NumMethod() int {
return int((*ptrType)(unsafe.Pointer(t)).numMethod)
case Struct:
return int((*structType)(unsafe.Pointer(t)).numMethod)
case Interface:
//FIXME: Use len(methods)
return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod()
}
// Other types have no methods attached. Note we don't panic here.
+11 -63
View File
@@ -689,25 +689,6 @@ func (v Value) Cap() int {
}
}
//go:linkname mapclear runtime.hashmapClear
func mapclear(p unsafe.Pointer)
// Clear clears the contents of a map or zeros the contents of a slice
//
// It panics if v's Kind is not Map or Slice.
func (v Value) Clear() {
switch v.typecode.Kind() {
case Map:
mapclear(v.pointer())
case Slice:
hdr := (*sliceHeader)(v.value)
elemSize := v.typecode.underlying().elem().Size()
memzero(hdr.data, elemSize*hdr.len)
default:
panic(&ValueError{Method: "Clear", Kind: v.Kind()})
}
}
// NumField returns the number of fields of this struct. It panics for other
// value types.
func (v Value) NumField() int {
@@ -907,9 +888,6 @@ func (v Value) Index(i int) Value {
}
func (v Value) NumMethod() int {
if v.typecode == nil {
panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid})
}
return v.typecode.NumMethod()
}
@@ -1084,7 +1062,7 @@ func (v Value) Set(x Value) {
v.checkAddressable()
v.checkRO()
if !x.typecode.AssignableTo(v.typecode) {
panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String())
panic("reflect: cannot set")
}
if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface {
@@ -1262,9 +1240,7 @@ func (v Value) OverflowUint(x uint64) bool {
}
func (v Value) CanConvert(t Type) bool {
// TODO: Optimize this to not actually perform a conversion
_, ok := convertOp(v, t)
return ok
panic("unimplemented: (reflect.Value).CanConvert()")
}
func (v Value) Convert(t Type) Value {
@@ -1286,15 +1262,6 @@ func convertOp(src Value, typ Type) (Value, bool) {
}, true
}
if rtype := typ.(*rawType); rtype.Kind() == Interface && rtype.NumMethod() == 0 {
iface := composeInterface(unsafe.Pointer(src.typecode), src.value)
return Value{
typecode: rtype,
value: unsafe.Pointer(&iface),
flags: valueFlagExported,
}, true
}
switch src.Kind() {
case Int, Int8, Int16, Int32, Int64:
switch rtype := typ.(*rawType); rtype.Kind() {
@@ -1335,33 +1302,14 @@ func convertOp(src Value, typ Type) (Value, bool) {
*/
case Slice:
switch rtype := typ.(*rawType); rtype.Kind() {
case Array:
if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() {
return Value{
typecode: rtype,
value: (*sliceHeader)(src.value).data,
flags: src.flags | valueFlagIndirect,
}, true
}
case Pointer:
if rtype.Elem().Kind() == Array {
if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() {
return Value{
typecode: rtype,
value: (*sliceHeader)(src.value).data,
flags: src.flags & (valueFlagExported | valueFlagRO),
}, true
}
}
case String:
if !src.typecode.elem().isNamed() {
switch src.Type().Elem().Kind() {
case Uint8:
return cvtBytesString(src, rtype), true
case Int32:
return cvtRunesString(src, rtype), true
}
if typ.Kind() == String && !src.typecode.elem().isNamed() {
rtype := typ.(*rawType)
switch src.Type().Elem().Kind() {
case Uint8:
return cvtBytesString(src, rtype), true
case Int32:
return cvtRunesString(src, rtype), true
}
}
@@ -1712,7 +1660,7 @@ func buflen(v Value) (unsafe.Pointer, uintptr) {
buf = hdr.data
len = hdr.len
case Array:
if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() {
buf = v.value
} else {
buf = unsafe.Pointer(&v.value)
-211
View File
@@ -1,10 +1,8 @@
package reflect_test
import (
"bytes"
"encoding/base64"
. "reflect"
"slices"
"sort"
"strings"
"testing"
@@ -601,29 +599,6 @@ func TestAssignableTo(t *testing.T) {
if got, want := refa.Interface().(int), 4; got != want {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}
b := []byte{0x01, 0x02}
refb := ValueOf(&b).Elem()
refb.Set(ValueOf([]byte{0x02, 0x03}))
if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}
type bstr []byte
c := bstr{0x01, 0x02}
refc := ValueOf(&c).Elem()
refc.Set(ValueOf([]byte{0x02, 0x03}))
if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}
d := []byte{0x01, 0x02}
refd := ValueOf(&d).Elem()
refd.Set(ValueOf(bstr{0x02, 0x03}))
if got, want := refb.Interface().([]byte), []byte{0x02, 0x03}; !bytes.Equal(got, want) {
t.Errorf("AssignableTo / Set failed, got %v, want %v", got, want)
}
}
func TestConvert(t *testing.T) {
@@ -649,192 +624,6 @@ func TestConvert(t *testing.T) {
}
}
func TestConvertSliceToArrayOrArrayPointer(t *testing.T) {
s := make([]byte, 2, 4)
// a0 := [0]byte(s)
// a1 := [1]byte(s[1:]) // a1[0] == s[1]
// a2 := [2]byte(s) // a2[0] == s[0]
// a4 := [4]byte(s) // panics: len([4]byte) > len(s)
v := ValueOf(s).Convert(TypeFor[[0]byte]())
if v.Kind() != Array || v.Type().Len() != 0 {
t.Error("Convert([]byte -> [0]byte)")
}
v = ValueOf(s[1:]).Convert(TypeFor[[1]byte]())
if v.Kind() != Array || v.Type().Len() != 1 {
t.Error("Convert([]byte -> [1]byte)")
}
v = ValueOf(s).Convert(TypeFor[[2]byte]())
if v.Kind() != Array || v.Type().Len() != 2 {
t.Error("Convert([]byte -> [2]byte)")
}
if ValueOf(s).CanConvert(TypeFor[[4]byte]()) {
t.Error("Converting a slice with len smaller than array to array should fail")
}
// s0 := (*[0]byte)(s) // s0 != nil
// s1 := (*[1]byte)(s[1:]) // &s1[0] == &s[1]
// s2 := (*[2]byte)(s) // &s2[0] == &s[0]
// s4 := (*[4]byte)(s) // panics: len([4]byte) > len(s)
v = ValueOf(s).Convert(TypeFor[*[0]byte]())
if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 0 {
t.Error("Convert([]byte -> *[0]byte)")
}
v = ValueOf(s[1:]).Convert(TypeFor[*[1]byte]())
if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 1 {
t.Error("Convert([]byte -> *[1]byte)")
}
v = ValueOf(s).Convert(TypeFor[*[2]byte]())
if v.Kind() != Pointer || v.Elem().Kind() != Array || v.Elem().Type().Len() != 2 {
t.Error("Convert([]byte -> *[2]byte)")
}
if ValueOf(s).CanConvert(TypeFor[*[4]byte]()) {
t.Error("Converting a slice with len smaller than array to array pointer should fail")
}
// Test converting slices with backing arrays <= and >64bits
slice64 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
array64 := ValueOf(slice64).Convert(TypeFor[[8]byte]()).Interface().([8]byte)
if !bytes.Equal(slice64, array64[:]) {
t.Errorf("converted array %x does not match backing array of slice %x", array64, slice64)
}
slice72 := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}
array72 := ValueOf(slice72).Convert(TypeFor[[9]byte]()).Interface().([9]byte)
if !bytes.Equal(slice72, array72[:]) {
t.Errorf("converted array %x does not match backing array of slice %x", array72, slice72)
}
}
func TestConvertToEmptyInterface(t *testing.T) {
anyType := TypeFor[interface{}]()
v := ValueOf(false).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(bool -> interface{})")
}
_ = v.Interface().(interface{}).(bool)
v = ValueOf(int64(3)).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(int64 -> interface{})")
}
_ = v.Interface().(interface{}).(int64)
v = ValueOf(struct{}{}).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(struct -> interface{})")
}
_ = v.Interface().(interface{}).(struct{})
v = ValueOf([]struct{}{}).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(slice -> interface{})")
}
_ = v.Interface().(interface{}).([]struct{})
v = ValueOf(map[string]string{"A": "B"}).Convert(anyType)
if v.Kind() != Interface || v.NumMethod() > 0 {
t.Error("Convert(map -> interface{})")
}
_ = v.Interface().(interface{}).(map[string]string)
}
func TestClearSlice(t *testing.T) {
type stringSlice []string
for _, test := range []struct {
slice any
expect any
}{
{
slice: []bool{true, false, true},
expect: []bool{false, false, false},
},
{
slice: []byte{0x00, 0x01, 0x02, 0x03},
expect: []byte{0x00, 0x00, 0x00, 0x00},
},
{
slice: [][]int{[]int{2, 1}, []int{3}, []int{}},
expect: [][]int{nil, nil, nil},
},
{
slice: []stringSlice{stringSlice{"hello", "world"}, stringSlice{}, stringSlice{"goodbye"}},
expect: []stringSlice{nil, nil, nil},
},
} {
v := ValueOf(test.slice)
expectLen, expectCap := v.Len(), v.Cap()
v.Clear()
if len := v.Len(); len != expectLen {
t.Errorf("Clear(slice) altered len, got %d, expected %d", len, expectLen)
}
if cap := v.Cap(); cap != expectCap {
t.Errorf("Clear(slice) altered cap, got %d, expected %d", cap, expectCap)
}
if !DeepEqual(test.slice, test.expect) {
t.Errorf("Clear(slice) got %v, expected %v", test.slice, test.expect)
}
}
}
func TestClearMap(t *testing.T) {
for _, test := range []struct {
m any
expect any
}{
{
m: map[int]bool{1: true, 2: false, 3: true},
expect: map[int]bool{},
},
{
m: map[string][]byte{"hello": []byte("world")},
expect: map[string][]byte{},
},
} {
v := ValueOf(test.m)
v.Clear()
if len := v.Len(); len != 0 {
t.Errorf("Clear(map) should set len to 0, got %d", len)
}
if !DeepEqual(test.m, test.expect) {
t.Errorf("Clear(map) got %v, expected %v", test.m, test.expect)
}
}
}
func TestCopyArrayToSlice(t *testing.T) {
// Test copying array <=64 bits and >64bits
// See issue #4554
a1 := [1]int64{1}
s1 := make([]int64, 1)
a2 := [2]int64{1, 2}
s2 := make([]int64, 2)
a8 := [8]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
s8 := make([]byte, 8)
a9 := [9]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}
s9 := make([]byte, 9)
Copy(ValueOf(s1), ValueOf(a1))
if !slices.Equal(s1, a1[:]) {
t.Errorf("copied slice %x does not match input array %x", s1, a1[:])
}
Copy(ValueOf(s2), ValueOf(a2))
if !slices.Equal(s2, a2[:]) {
t.Errorf("copied slice %x does not match input array %x", s2, a2[:])
}
Copy(ValueOf(s8), ValueOf(a8))
if !bytes.Equal(s8, a8[:]) {
t.Errorf("copied slice %x does not match input array %x", s8, a8[:])
}
Copy(ValueOf(s9), ValueOf(a9))
if !bytes.Equal(s9, a9[:]) {
t.Errorf("copied slice %x does not match input array %x", s9, a9[:])
}
}
func TestIssue4040(t *testing.T) {
var value interface{} = uint16(0)
+3 -37
View File
@@ -76,13 +76,6 @@ const (
blockStateMask blockState = 3 // 11
)
// The byte value of a block where every block is a 'tail' block.
const blockStateByteAllTails = 0 |
uint8(blockStateTail<<(stateBits*3)) |
uint8(blockStateTail<<(stateBits*2)) |
uint8(blockStateTail<<(stateBits*1)) |
uint8(blockStateTail<<(stateBits*0))
// String returns a human-readable version of the block state, for debugging.
func (s blockState) String() string {
switch s {
@@ -130,25 +123,7 @@ func (b gcBlock) address() uintptr {
// points to an allocated object. It returns the same block if this block
// already points to the head.
func (b gcBlock) findHead() gcBlock {
for {
// Optimization: check whether the current block state byte (which
// contains the state of multiple blocks) is composed entirely of tail
// blocks. If so, we can skip back to the last block in the previous
// state byte.
// This optimization speeds up findHead for pointers that point into a
// large allocation.
stateByte := b.stateByte()
if stateByte == blockStateByteAllTails {
b -= (b % blocksPerStateByte) + 1
continue
}
// Check whether we've found a non-tail block, which means we found the
// head.
state := b.stateFromByte(stateByte)
if state != blockStateTail {
break
}
for b.state() == blockStateTail {
b--
}
if gcAsserts {
@@ -171,19 +146,10 @@ func (b gcBlock) findNext() gcBlock {
return b
}
func (b gcBlock) stateByte() byte {
return *(*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte))
}
// Return the block state given a state byte. The state byte must have been
// obtained using b.stateByte(), otherwise the result is incorrect.
func (b gcBlock) stateFromByte(stateByte byte) blockState {
return blockState(stateByte>>((b%blocksPerStateByte)*stateBits)) & blockStateMask
}
// State returns the current block state.
func (b gcBlock) state() blockState {
return b.stateFromByte(b.stateByte())
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte))
return blockState(*stateBytePtr>>((b%blocksPerStateByte)*stateBits)) & blockStateMask
}
// setState sets the current block to the given state, which must contain more
+2 -3
View File
@@ -11,7 +11,7 @@ import (
)
// Ever-incrementing pointer: no memory is freed.
var heapptr uintptr
var heapptr = heapStart
// Total amount allocated for runtime.MemStats
var gcTotalAlloc uint64
@@ -93,8 +93,7 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
}
func initHeap() {
// Initialize this bump-pointer allocator to the start of the heap.
// Needed here because heapStart may not be a compile-time constant.
// preinit() may have moved heapStart; reset heapptr
heapptr = heapStart
}
+18 -11
View File
@@ -7,7 +7,6 @@ package runtime
import (
"reflect"
"tinygo"
"unsafe"
)
@@ -23,6 +22,14 @@ type hashmap struct {
keyHash func(key unsafe.Pointer, size, seed uintptr) uint32
}
type hashmapAlgorithm uint8
const (
hashmapAlgorithmBinary hashmapAlgorithm = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
// A hashmap bucket. A bucket is a container of 8 key/value pairs: first the
// following two entries, then the 8 keys, then the 8 values. This somewhat odd
// ordering is to make sure the keys and values are well aligned when one of
@@ -69,8 +76,8 @@ func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) *hashm
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + keySize*8 + valueSize*8
buckets := alloc(bucketBufSize*(1<<bucketBits), nil)
keyHash := hashmapKeyHashAlg(tinygo.HashmapAlgorithm(alg))
keyEqual := hashmapKeyEqualAlg(tinygo.HashmapAlgorithm(alg))
keyHash := hashmapKeyHashAlg(hashmapAlgorithm(alg))
keyEqual := hashmapKeyEqualAlg(hashmapAlgorithm(alg))
return &hashmap{
buckets: buckets,
@@ -112,13 +119,13 @@ func hashmapClear(m *hashmap) {
}
}
func hashmapKeyEqualAlg(alg tinygo.HashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
switch alg {
case tinygo.HashmapAlgorithmBinary:
case hashmapAlgorithmBinary:
return memequal
case tinygo.HashmapAlgorithmString:
case hashmapAlgorithmString:
return hashmapStringEqual
case tinygo.HashmapAlgorithmInterface:
case hashmapAlgorithmInterface:
return hashmapInterfaceEqual
default:
// compiler bug :(
@@ -126,13 +133,13 @@ func hashmapKeyEqualAlg(alg tinygo.HashmapAlgorithm) func(x, y unsafe.Pointer, n
}
}
func hashmapKeyHashAlg(alg tinygo.HashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 {
func hashmapKeyHashAlg(alg hashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 {
switch alg {
case tinygo.HashmapAlgorithmBinary:
case hashmapAlgorithmBinary:
return hash32
case tinygo.HashmapAlgorithmString:
case hashmapAlgorithmString:
return hashmapStringPtrHash
case tinygo.HashmapAlgorithmInterface:
case hashmapAlgorithmInterface:
return hashmapInterfacePtrHash
default:
// compiler bug :(
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal || js || wasm_unknown
//go:build baremetal || wasm_unknown
package runtime
+1 -19
View File
@@ -5,9 +5,7 @@ package runtime
// This file is for systems that are _actually_ Linux (not systems that pretend
// to be Linux, like baremetal systems).
import (
"unsafe"
)
import "unsafe"
const GOOS = "linux"
@@ -85,11 +83,6 @@ type elfProgramHeader32 struct {
//go:extern __ehdr_start
var ehdr_start elfHeader
// int *__errno_location(void);
//
//export __errno_location
func libc_errno_location() *int32
// findGlobals finds globals in the .data/.bss sections.
// It parses the ELF program header to find writable segments.
func findGlobals(found func(start, end uintptr)) {
@@ -146,14 +139,3 @@ func hardwareRand() (n uint64, ok bool) {
//
//export getrandom
func libc_getrandom(buf unsafe.Pointer, buflen uintptr, flags uint32) uint32
// int fcntl(int fd, int cmd, int arg);
//
//export fcntl
func libc_fcntl(fd int, cmd int, arg int) (ret int)
func fcntl(fd int32, cmd int32, arg int32) (ret int32, errno int32) {
ret = int32(libc_fcntl(int(fd), int(cmd), int(arg)))
errno = *libc_errno_location()
return
}
+7 -3
View File
@@ -3,7 +3,6 @@ package runtime
import (
"internal/task"
"runtime/interrupt"
"tinygo"
"unsafe"
)
@@ -23,6 +22,11 @@ func tinygo_longjmp(frame *deferFrame)
// Returns whether recover is supported on the current architecture.
func supportsRecover() bool
const (
panicStrategyPrint = 1
panicStrategyTrap = 2
)
// Compile intrinsic.
// Returns which strategy is used. This is usually "print" but can be changed
// using the -panic= compiler flag.
@@ -44,7 +48,7 @@ type deferFrame struct {
// Builtin function panic(msg), used as a compiler intrinsic.
func _panic(message interface{}) {
if panicStrategy() == tinygo.PanicStrategyTrap {
if panicStrategy() == panicStrategyTrap {
trap()
}
// Note: recover is not supported inside interrupts.
@@ -72,7 +76,7 @@ func runtimePanic(msg string) {
}
func runtimePanicAt(addr unsafe.Pointer, msg string) {
if panicStrategy() == tinygo.PanicStrategyTrap {
if panicStrategy() == panicStrategyTrap {
trap()
}
if hasReturnAddr {
+12 -10
View File
@@ -119,17 +119,19 @@ func ticks() timeUnit {
}
func sleepTicks(duration timeUnit) {
curr := ticks()
last := curr + duration // 64-bit overflow unlikely
for curr < last {
cycles := timeUnit((last - curr) / pitCyclesPerMicro)
if cycles > 0xFFFFFFFF {
cycles = 0xFFFFFFFF
if duration >= 0 {
curr := ticks()
last := curr + duration // 64-bit overflow unlikely
for curr < last {
cycles := timeUnit((last - curr) / pitCyclesPerMicro)
if cycles > 0xFFFFFFFF {
cycles = 0xFFFFFFFF
}
if !timerSleep(uint32(cycles)) {
return // return early due to interrupt
}
curr = ticks()
}
if !timerSleep(uint32(cycles)) {
return // return early due to interrupt
}
curr = ticks()
}
}
+4
View File
@@ -31,6 +31,10 @@ func nanosecondsToTicks(ns int64) timeUnit {
}
func sleepTicks(d timeUnit) {
if d <= 0 {
return
}
if hasScheduler {
// With scheduler, sleepTicks may return early if an interrupt or
// event fires - so scheduler can schedule any go routines now
+41 -8
View File
@@ -29,6 +29,44 @@ func proc_exit(exitcode uint32)
//export __stdio_exit
func __stdio_exit()
var args []string
//go:linkname os_runtime_args os.runtime_args
func os_runtime_args() []string {
if args == nil {
// Read the number of args (argc) and the buffer size required to store
// all these args (argv).
var argc, argv_buf_size uint32
args_sizes_get(&argc, &argv_buf_size)
if argc == 0 {
return nil
}
// Obtain the command line arguments
argsSlice := make([]unsafe.Pointer, argc)
buf := make([]byte, argv_buf_size)
args_get(&argsSlice[0], unsafe.Pointer(&buf[0]))
// Convert the array of C strings to an array of Go strings.
args = make([]string, argc)
for i, cstr := range argsSlice {
length := strlen(cstr)
argString := _string{
length: length,
ptr: (*byte)(cstr),
}
args[i] = *(*string)(unsafe.Pointer(&argString))
}
}
return args
}
//go:wasmimport wasi_snapshot_preview1 args_get
func args_get(argv *unsafe.Pointer, argv_buf unsafe.Pointer) (errno uint16)
//go:wasmimport wasi_snapshot_preview1 args_sizes_get
func args_sizes_get(argc *uint32, argv_buf_size *uint32) (errno uint16)
const (
putcharBufferSize = 120
stdout = 1
@@ -80,17 +118,12 @@ func abort() {
//go:linkname syscall_Exit syscall.Exit
func syscall_Exit(code int) {
// Flush stdio buffers.
__stdio_exit()
// Exit the program.
// TODO: should we call __stdio_exit here?
// It's a low-level exit (syscall.Exit) so doing any libc stuff seems
// unexpected, but then where else should stdio buffers be flushed?
proc_exit(uint32(code))
}
func mainReturnExit() {
syscall_Exit(0)
}
// TinyGo does not yet support any form of parallelism on WebAssembly, so these
// can be left empty.
@@ -31,10 +31,6 @@ func abort() {
//go:linkname syscall_Exit syscall.Exit
func syscall_Exit(code int) {
// Because this is the "unknown" target we can't call an exit function.
// But we also can't just return since the program will likely expect this
// function to never return. So we panic instead.
runtimePanic("unsupported: syscall.Exit")
}
// There is not yet any support for any form of parallelism on WebAssembly, so these
-7
View File
@@ -60,13 +60,6 @@ func syscall_Exit(code int) {
exit.Exit(code != 0)
}
func mainReturnExit() {
// WASIp2 does not use _start, instead it uses _initialize and a custom
// WASIp2-specific main function. So this should never be called in
// practice.
runtimePanic("unreachable: _start was called")
}
// TinyGo does not yet support any form of parallelism on WebAssembly, so these
// can be left empty.
+1 -4
View File
@@ -5,7 +5,6 @@ package runtime
import (
"math/bits"
"sync/atomic"
"tinygo"
"unsafe"
)
@@ -93,8 +92,6 @@ func main(argc int32, argv *unsafe.Pointer) int {
stackTop = getCurrentStackPointer()
runMain()
sleepTicks(nanosecondsToTicks(1e9))
// For libc compatibility.
return 0
}
@@ -144,7 +141,7 @@ func tinygo_register_fatal_signals()
//
//export tinygo_handle_fatal_signal
func tinygo_handle_fatal_signal(sig int32, addr uintptr) {
if panicStrategy() == tinygo.PanicStrategyTrap {
if panicStrategy() == panicStrategyTrap {
trap()
}
+4 -42
View File
@@ -2,10 +2,6 @@
package runtime
import (
"unsafe"
)
type timeUnit int64
// libc constructors
@@ -21,38 +17,6 @@ func init() {
__wasm_call_ctors()
}
var args []string
//go:linkname os_runtime_args os.runtime_args
func os_runtime_args() []string {
if args == nil {
// Read the number of args (argc) and the buffer size required to store
// all these args (argv).
var argc, argv_buf_size uint32
args_sizes_get(&argc, &argv_buf_size)
if argc == 0 {
return nil
}
// Obtain the command line arguments
argsSlice := make([]unsafe.Pointer, argc)
buf := make([]byte, argv_buf_size)
args_get(&argsSlice[0], unsafe.Pointer(&buf[0]))
// Convert the array of C strings to an array of Go strings.
args = make([]string, argc)
for i, cstr := range argsSlice {
length := strlen(cstr)
argString := _string{
length: length,
ptr: (*byte)(cstr),
}
args[i] = *(*string)(unsafe.Pointer(&argString))
}
}
return args
}
func ticksToNanoseconds(ticks timeUnit) int64 {
return int64(ticks)
}
@@ -91,14 +55,12 @@ func ticks() timeUnit {
return timeUnit(nano)
}
func beforeExit() {
__stdio_exit()
}
// Implementations of WASI APIs
//go:wasmimport wasi_snapshot_preview1 args_get
func args_get(argv *unsafe.Pointer, argv_buf unsafe.Pointer) (errno uint16)
//go:wasmimport wasi_snapshot_preview1 args_sizes_get
func args_sizes_get(argc *uint32, argv_buf_size *uint32) (errno uint16)
//go:wasmimport wasi_snapshot_preview1 clock_time_get
func clock_time_get(clockid uint32, precision uint64, time *uint64) (errno uint16)
+3
View File
@@ -52,3 +52,6 @@ func sleepTicks(d timeUnit) {
func ticks() timeUnit {
return timeUnit(monotonicclock.Now())
}
func beforeExit() {
}
+4
View File
@@ -32,3 +32,7 @@ func sleepTicks(d timeUnit)
//go:wasmimport gojs runtime.ticks
func ticks() timeUnit
func beforeExit() {
__stdio_exit()
}
+1 -4
View File
@@ -34,8 +34,5 @@ func ticks() timeUnit {
return timeUnit(0)
}
func mainReturnExit() {
// Don't exit explicitly here. We can't (there is no environment with an
// exit call) but also it's not needed. We can just let _start and main.main
// return to the caller.
func beforeExit() {
}
+23 -12
View File
@@ -14,14 +14,12 @@ import (
// This is the _start entry point, when using -buildmode=default.
func wasmEntryCommand() {
// These need to be initialized early so that the heap can be initialized.
initializeCalled = true
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
wasmExportState = wasmExportStateInMain
run()
if mainExited {
// To make sure wasm_exec.js knows that we've exited, exit explicitly.
mainReturnExit()
}
wasmExportState = wasmExportStateExited
beforeExit()
}
// This is the _initialize entry point, when using -buildmode=c-shared.
@@ -29,8 +27,6 @@ func wasmEntryReactor() {
// This function is called before any //go:wasmexport functions are called
// to initialize everything. It must not block.
initializeCalled = true
// Initialize the heap.
heapStart = uintptr(unsafe.Pointer(&heapStartSymbol))
heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize)
@@ -42,23 +38,38 @@ func wasmEntryReactor() {
// goroutine.
go func() {
initAll()
wasmExportState = wasmExportStateReactor
}()
scheduler(true)
if wasmExportState != wasmExportStateReactor {
// Unlikely, but if package initializers do something blocking (like
// time.Sleep()), that's a bug.
runtimePanic("package initializer blocks")
}
} else {
// There are no goroutines (except for the main one, if you can call it
// that), so we can just run all the package initializers.
initAll()
wasmExportState = wasmExportStateReactor
}
}
// Whether the runtime was initialized by a call to _initialize or _start.
var initializeCalled bool
// Track which state we're in: before (or during) init, running inside
// main.main, after main.main returned, or reactor mode (after init).
var wasmExportState uint8
const (
wasmExportStateInit = iota
wasmExportStateInMain
wasmExportStateExited
wasmExportStateReactor
)
func wasmExportCheckRun() {
switch {
case !initializeCalled:
switch wasmExportState {
case wasmExportStateInit:
runtimePanic("//go:wasmexport function called before runtime initialization")
case mainExited:
case wasmExportStateExited:
runtimePanic("//go:wasmexport function called after main.main returned")
}
}
+2 -10
View File
@@ -52,13 +52,7 @@ func mainCRTStartup() int {
stackTop = getCurrentStackPointer()
runMain()
// Exit via exit(0) instead of returning.
// This matches mingw-w64-crt/crt/crtexe.c, which exits using exit(0)
// instead of returning the return value.
libc_exit(0)
// Unreachable, since we're already exited. But we need to return something
// to make this valid Go code.
// For libc compatibility.
return 0
}
@@ -98,9 +92,7 @@ func os_runtime_args() []string {
}
func putchar(c byte) {
if libc_putchar(int(c)) < 0 {
libc_exit(42)
}
libc_putchar(int(c))
}
var heapSize uintptr = 128 * 1024 // small amount to start
+9 -11
View File
@@ -21,7 +21,7 @@ const schedulerDebug = false
// queue a new scheduler invocation using setTimeout.
const asyncScheduler = GOOS == "js"
var mainExited bool
var schedulerDone bool
// Queues used by the scheduler.
var (
@@ -166,7 +166,7 @@ func removeTimer(tim *timer) bool {
func scheduler(returnAtDeadlock bool) {
// Main scheduler loop.
var now timeUnit
for !mainExited {
for !schedulerDone {
scheduleLog("")
scheduleLog(" schedule")
if sleepQueue != nil || timerQueue != nil {
@@ -230,15 +230,13 @@ func scheduler(returnAtDeadlock bool) {
println("--- timer waiting:", tim, tim.whenTicks())
}
}
if timeLeft > 0 {
sleepTicks(timeLeft)
if asyncScheduler {
// The sleepTicks function above only sets a timeout at
// which point the scheduler will be called again. It does
// not really sleep. So instead of sleeping, we return and
// expect to be called again.
break
}
sleepTicks(timeLeft)
if asyncScheduler {
// The sleepTicks function above only sets a timeout at which
// point the scheduler will be called again. It does not really
// sleep. So instead of sleeping, we return and expect to be
// called again.
break
}
continue
}
+1 -1
View File
@@ -23,7 +23,7 @@ func run() {
go func() {
initAll()
callMain()
mainExited = true
schedulerDone = true
}()
scheduler(false)
}
-29
View File
@@ -2,7 +2,6 @@
package trace
import (
"context"
"errors"
"io"
)
@@ -12,31 +11,3 @@ func Start(w io.Writer) error {
}
func Stop() {}
func NewTask(pctx context.Context, taskType string) (ctx context.Context, task *Task) {
return context.TODO(), nil
}
type Task struct{}
func (t *Task) End() {}
func Log(ctx context.Context, category, message string) {}
func Logf(ctx context.Context, category, format string, args ...any) {}
func WithRegion(ctx context.Context, regionType string, fn func()) {
fn()
}
func StartRegion(ctx context.Context, regionType string) *Region {
return nil
}
type Region struct{}
func (r *Region) End() {}
func IsEnabled() bool {
return false
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build nintendoswitch || wasip1
//go:build js || nintendoswitch || wasip1
package syscall
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !wasip1 && !wasip2
//go:build !js && !wasip1 && !wasip2
package syscall
@@ -1,4 +1,4 @@
//go:build wasip1
//go:build js || wasip1
package syscall
+75 -72
View File
@@ -162,8 +162,8 @@ func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
}
libcErrno = 0
list, err, isErr := stream.in.BlockingRead(uint64(count)).Result()
if isErr {
result := stream.in.BlockingRead(uint64(count))
if err := result.Err(); err != nil {
if err.Closed() {
libcErrno = 0
return 0
@@ -174,7 +174,9 @@ func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
return -1
}
copy(unsafe.Slice(buf, count), list.Slice())
dst := unsafe.Slice(buf, count)
list := result.OK()
copy(dst, list.Slice())
return int(list.Len())
}
@@ -200,8 +202,8 @@ func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
if len > remaining {
len = remaining
}
_, err, isErr := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])).Result()
if isErr {
result := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len]))
if err := result.Err(); err != nil {
if err.Closed() {
libcErrno = 0
return 0
@@ -246,13 +248,13 @@ func pread(fd int32, buf *byte, count uint, offset int64) int {
return -1
}
listEOF, err, isErr := streams.d.Read(types.FileSize(count), types.FileSize(offset)).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := streams.d.Read(types.FileSize(count), types.FileSize(offset))
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
list := listEOF.F0
list := result.OK().F0
copy(unsafe.Slice(buf, count), list.Slice())
// TODO(dgryski): EOF bool is ignored?
@@ -283,14 +285,14 @@ func pwrite(fd int32, buf *byte, count uint, offset int64) int {
return -1
}
n, err, isErr := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)).Result()
if isErr {
result := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset))
if err := result.Err(); err != nil {
// TODO(dgryski):
libcErrno = errorCodeToErrno(err)
libcErrno = errorCodeToErrno(*err)
return -1
}
return int(n)
return int(*result.OK())
}
// ssize_t lseek(int fd, off_t offset, int whence);
@@ -319,12 +321,12 @@ func lseek(fd int32, offset int64, whence int) int64 {
case 1: // SEEK_CUR
stream.offset += offset
case 2: // SEEK_END
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := stream.d.Stat()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
stream.offset = int64(stat.Size) + offset
stream.offset = int64(result.OK().Size) + offset
}
return int64(stream.offset)
@@ -437,9 +439,9 @@ func mkdir(pathname *byte, mode uint32) int32 {
return -1
}
_, err, isErr := dir.d.CreateDirectoryAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.CreateDirectoryAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
@@ -457,9 +459,9 @@ func rmdir(pathname *byte) int32 {
return -1
}
_, err, isErr := dir.d.RemoveDirectoryAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.RemoveDirectoryAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
@@ -484,9 +486,9 @@ func rename(from, to *byte) int32 {
return -1
}
_, err, isErr := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
@@ -518,9 +520,9 @@ func symlink(from, to *byte) int32 {
// TODO(dgryski): check fromDir == toDir?
_, err, isErr := fromDir.d.SymlinkAt(fromRelPath, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := fromDir.d.SymlinkAt(fromRelPath, toRelPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
@@ -552,9 +554,9 @@ func link(from, to *byte) int32 {
// TODO(dgryski): check fromDir == toDir?
_, err, isErr := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
@@ -585,9 +587,9 @@ func fsync(fd int32) int32 {
return -1
}
_, err, isErr := streams.d.SyncData().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := streams.d.SyncData()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
@@ -605,12 +607,13 @@ func readlink(pathname *byte, buf *byte, count uint) int {
return -1
}
s, err, isErr := dir.d.ReadLinkAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.ReadLinkAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
s := *result.OK()
size := uintptr(count)
if size > uintptr(len(s)) {
size = uintptr(len(s))
@@ -631,9 +634,9 @@ func unlink(pathname *byte) int32 {
return -1
}
_, err, isErr := dir.d.UnlinkFileAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.UnlinkFileAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
@@ -658,13 +661,13 @@ func stat(pathname *byte, dst *Stat_t) int32 {
return -1
}
stat, err, isErr := dir.d.StatAt(0, relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.StatAt(0, relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
setStatFromWASIStat(dst, &stat)
setStatFromWASIStat(dst, result.OK())
return 0
}
@@ -687,13 +690,13 @@ func fstat(fd int32, dst *Stat_t) int32 {
libcErrno = EBADF
return -1
}
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := stream.d.Stat()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
setStatFromWASIStat(dst, &stat)
setStatFromWASIStat(dst, result.OK())
return 0
}
@@ -742,13 +745,13 @@ func lstat(pathname *byte, dst *Stat_t) int32 {
return -1
}
stat, err, isErr := dir.d.StatAt(0, relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.StatAt(0, relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
setStatFromWASIStat(dst, &stat)
setStatFromWASIStat(dst, result.OK())
return 0
}
@@ -978,25 +981,25 @@ func open(pathname *byte, flags int32, mode uint32) int32 {
pflags &^= types.PathFlagsSymlinkFollow
}
descriptor, err, isErr := dir.d.OpenAt(pflags, relPath, oflags, dflags).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.OpenAt(pflags, relPath, oflags, dflags)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
stream := wasiFile{
d: descriptor,
d: *result.OK(),
oflag: flags,
refs: 1,
}
if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) {
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := stream.d.Stat()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
stream.offset = int64(stat.Size)
stream.offset = int64(result.OK().Size)
}
libcfd := findFreeFD()
@@ -1109,13 +1112,13 @@ func fdopendir(fd int32) unsafe.Pointer {
return nil
}
dir, err, isErr := stream.d.ReadDirectory().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := stream.d.ReadDirectory()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return nil
}
return unsafe.Pointer(&libc_DIR{d: dir})
return unsafe.Pointer(&libc_DIR{d: *result.OK()})
}
// int fdclosedir(DIR *);
@@ -1150,13 +1153,13 @@ func readdir(dirp unsafe.Pointer) *Dirent {
return nil
}
someEntry, err, isErr := dir.d.ReadDirectoryEntry().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.ReadDirectoryEntry()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return nil
}
entry := someEntry.Some()
entry := result.OK().Some()
if entry == nil {
libcErrno = 0
return nil
@@ -1308,9 +1311,9 @@ func chdir(name *byte) int {
return -1
}
_, err, isErr := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
result := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
return -1
}
+1 -6
View File
@@ -1,4 +1,4 @@
//go:build nintendoswitch || wasip1 || wasip2
//go:build js || nintendoswitch || wasip1 || wasip2
package syscall
@@ -233,11 +233,6 @@ func (w WaitStatus) Continued() bool { return false }
func (w WaitStatus) StopSignal() Signal { return 0 }
func (w WaitStatus) TrapCause() int { return 0 }
// since rusage is quite a big struct and we stub it out anyway no need to define it here
func Wait4(pid int, wstatus *WaitStatus, options int, rusage uintptr) (wpid int, err error) {
return 0, ENOSYS // TODO
}
func Getenv(key string) (value string, found bool) {
data := cstring(key)
raw := libc_getenv(&data[0])
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build wasip1 || wasip2
//go:build js || wasip1 || wasip2
package syscall
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal || js || wasm_unknown
//go:build baremetal || wasm_unknown
package syscall
-2
View File
@@ -1,5 +1,3 @@
//go:build linux || unix
package syscall
func Exec(argv0 string, argv []string, envv []string) (err error)
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build baremetal || nintendoswitch || js || wasm_unknown
//go:build baremetal || nintendoswitch || wasm_unknown
package syscall
-17
View File
@@ -1,17 +0,0 @@
// Package tinygo contains constants used between the TinyGo compiler and
// runtime.
package tinygo
const (
PanicStrategyPrint = iota + 1
PanicStrategyTrap
)
type HashmapAlgorithm uint8
// Constants for hashmap algorithms.
const (
HashmapAlgorithmBinary HashmapAlgorithm = iota
HashmapAlgorithmString
HashmapAlgorithmInterface
)
+69 -37
View File
@@ -132,10 +132,11 @@
const decoder = new TextDecoder("utf-8");
let reinterpretBuf = new DataView(new ArrayBuffer(8));
var logLine = [];
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
global.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this._callbackTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
@@ -236,9 +237,58 @@
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
}
const storeStringArraySizes = (array, num_ptr, buf_size_ptr) => {
let buf_size = 0;
for (let s of array) {
buf_size += s.length + 1;
}
mem().setUint32(num_ptr, array.length, true);
mem().setUint32(buf_size_ptr, buf_size, true);
}
const storeStringArray = (array, ptrs_ptr, buf_ptr) => {
for (let s of array) {
// Put string data in buffer.
let data = encoder.encode(s);
let dest = new Uint8Array(this._inst.exports.memory.buffer, buf_ptr, data.length);
dest.set(data);
mem().setUint8(buf_ptr+data.length, 0);
// Put pointer to buffer in pointers array.
mem().setUint32(ptrs_ptr, buf_ptr, true);
// Advance to the next element in the array.
ptrs_ptr += 4;
buf_ptr += data.length + 1;
}
}
const envArray = () => {
let array = [];
for (let [key, value] of Object.entries(this.env)) {
array.push(`${key}=${value}`);
}
return array;
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
wasi_snapshot_preview1: {
args_sizes_get: (argc_ptr, argv_buf_size_ptr) => {
storeStringArraySizes(this.argv, argc_ptr, argv_buf_size_ptr);
return 0;
},
args_get: (argv_ptr, argv_buf_ptr) => {
storeStringArray(this.argv, argv_ptr, argv_buf_ptr);
return 0;
},
environ_get: (env_ptr, env_buf_ptr) => {
storeStringArray(envArray(), env_ptr, env_buf_ptr);
},
environ_sizes_get: (env_ptr, env_buf_size_ptr) => {
storeStringArraySizes(envArray(), env_ptr, env_buf_size_ptr);
return 0;
},
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
let nwritten = 0;
@@ -271,11 +321,14 @@
fd_close: () => 0, // dummy
fd_fdstat_get: () => 0, // dummy
fd_seek: () => 0, // dummy
proc_exit: (code) => {
this.exited = true;
this.exitCode = code;
this._resolveExitPromise();
throw wasmExit;
"proc_exit": (code) => {
if (global.process) {
// Node.js
process.exit(code);
} else {
// Can't exit in a browser.
throw 'trying to exit with code ' + code;
}
},
random_get: (bufPtr, bufLen) => {
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
@@ -291,14 +344,7 @@
// func sleepTicks(timeout float64)
"runtime.sleepTicks": (timeout) => {
// Do not sleep, only reactivate scheduler after the given timeout.
setTimeout(() => {
if (this.exited) return;
try {
this._inst.exports.go_scheduler();
} catch (e) {
if (e !== wasmExit) throw e;
}
}, timeout);
setTimeout(this._inst.exports.go_scheduler, timeout);
},
// func finalizeRef(v ref)
@@ -470,23 +516,12 @@
this._ids = new Map(); // mapping from JS values to reference ids
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
this.exitCode = 0;
if (this._inst.exports._start) {
let exitPromise = new Promise((resolve, reject) => {
this._resolveExitPromise = resolve;
});
this._inst.exports._start();
// Run program, but catch the wasmExit exception that's thrown
// to return back here.
try {
this._inst.exports._start();
} catch (e) {
if (e !== wasmExit) throw e;
}
await exitPromise;
return this.exitCode;
// TODO: wait until the program exists.
await new Promise(() => {});
} else {
this._inst.exports._initialize();
}
@@ -496,11 +531,7 @@
if (this.exited) {
throw new Error("Go program has already exited");
}
try {
this._inst.exports.resume();
} catch (e) {
if (e !== wasmExit) throw e;
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
@@ -524,15 +555,16 @@
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length != 3) {
if (process.argv.length < 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
let exitCode = await go.run(result.instance);
process.exit(exitCode);
go.argv = process.argv.slice(2);
go.env = process.env;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
return go.run(result.instance);
}).catch((err) => {
console.error(err);
process.exit(1);
-15
View File
@@ -19,9 +19,6 @@ func main() {
println("\n# panic replace")
panicReplace()
println("\n# defer panic")
deferPanic()
}
func recoverSimple() {
@@ -92,18 +89,6 @@ func panicReplace() {
panic("panic 1")
}
func deferPanic() {
defer func() {
printitf("recovered from deferred call:", recover())
}()
// This recover should not do anything.
defer recover()
defer panic("deferred panic")
println("defer panic")
}
func printitf(msg string, itf interface{}) {
switch itf := itf.(type) {
case string:
-4
View File
@@ -23,7 +23,3 @@ recovered: panic
panic 1
panic 2
recovered: panic 2
# defer panic
defer panic
recovered from deferred call: deferred panic
-27
View File
@@ -1,27 +0,0 @@
package main
import (
"os"
"time"
)
func main() {
println("wasmexit test:", os.Args[1])
switch os.Args[1] {
case "normal":
return
case "exit-0":
os.Exit(0)
case "exit-0-sleep":
time.Sleep(time.Millisecond)
println("slept")
os.Exit(0)
case "exit-1":
os.Exit(1)
case "exit-1-sleep":
time.Sleep(time.Millisecond)
println("slept")
os.Exit(1)
}
println("unknown wasmexit test")
}
-35
View File
@@ -1,35 +0,0 @@
require('../targets/wasm_exec.js');
function runTests() {
let testCall = (name, params, expected) => {
let result = go._inst.exports[name].apply(null, params);
if (result !== expected) {
console.error(`${name}(...${params}): expected result ${expected}, got ${result}`);
}
}
// These are the same tests as in TestWasmExport.
testCall('hello', [], undefined);
testCall('add', [3, 5], 8);
testCall('add', [7, 9], 16);
testCall('add', [6, 1], 7);
testCall('reentrantCall', [2, 3], 5);
testCall('reentrantCall', [1, 8], 9);
}
let go = new Go();
go.importObject.tester = {
callOutside: (a, b) => {
return go._inst.exports.add(a, b);
},
callTestMain: () => {
runTests();
},
};
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
let value = await go.run(result.instance);
console.log('exit code:', value);
}).catch((err) => {
console.error(err);
process.exit(1);
});
-6
View File
@@ -1,7 +1,5 @@
package main
import "time"
func init() {
println("called init")
}
@@ -10,10 +8,6 @@ func init() {
func callTestMain()
func main() {
// Check that exported functions can still be called after calling
// time.Sleep.
time.Sleep(time.Millisecond)
// main.main is not used when using -buildmode=c-shared.
callTestMain()
}