Compare commits

...

29 Commits

Author SHA1 Message Date
Ayke van Laethem 2dd06c4378 [DO NOT MERGE] testing Windows CI failures 2024-11-19 11:09:48 +01:00
Ayke van Laethem e12da15f7d cgo: support function-like macros
This is needed for code like this:

    #define __WASI_ERRNO_INVAL (UINT16_C(28))
    #define EINVAL __WASI_ERRNO_INVAL
2024-11-18 18:35:20 +01:00
Ayke van Laethem c4867c8743 cgo: define idents referenced only from macros 2024-11-18 18:35:20 +01:00
Ayke van Laethem 8068419854 runtime: heapptr only needs to be initialized once
There is no need to initialize it twice.
2024-11-18 17:14:55 +01:00
Ayke van Laethem 9b3eb3fe59 ci: run at least some tests on older Go/LLVM versions
These should make sure basic functionality is still working.
Using the `-short` flag to avoid taking too long to run all tests (and
to install all the necessary emulators), and because some targets might
not work in older Go/LLVM versions (such as WASI).

This does _not_ run tests and checks against expected IR, because LLVM
IR changes a lot across versions.
2024-11-18 16:42:14 +01:00
leongross 1dcccf87b5 linux: add runtime.fcntl function
This is needed for the internal/syscall/unix package.

Signed-off-by: leongross <leon.gross@9elements.com>
2024-11-18 14:44:06 +01:00
Ayke van Laethem d51ef253a9 builder: whitelist temporary directory env var for Clang invocation
It looks like this breaks on Windows:
https://github.com/tinygo-org/tinygo/issues/4557
I haven't confirmed this is indeed the problem, but it would make sense.
And passing through the temporary directory seems like a good idea
regardless, there's not much that could break due to that.
2024-11-15 18:27:48 +01:00
Ayke van Laethem 6d4dfcf72f compiler, runtime: move constants into shared package
Use a single package for certain constants that must be the same between
the compiler and the runtime.

While just using the same values in both places works, this is much more
obvious and harder to mess up. It also avoids the need for comments
pointing to the other location the constant is defined. And having it in
code makes it possible for IDEs to analyze the source.

In the future, more such constants and maybe algorithms can be added.
2024-11-15 10:11:57 +01:00
sivchari ac9f72be61 support to parse devl version
Signed-off-by: sivchari <shibuuuu5@gmail.com>
2024-11-15 09:21:47 +01:00
Ayke van Laethem 258dac2324 wasm: tidy up wasm_exec.js a bit 2024-11-14 11:02:54 -08:00
Ayke van Laethem b7d91e2f33 ci: use TinyGo version in artifact files
This avoids needing to rename them ourselves (which is kinda annoying)
and also avoids mistakes in the process.
2024-11-14 10:38:56 +01:00
Ayke van Laethem 860697257b runtime: optimize findHead
This is similar to https://github.com/tinygo-org/tinygo/pull/3899, but
smaller and hopefully just as efficient.

Thanks to @HattoriHanzo031 for starting this work, benchmarking, and for
improving the performance of the code even further.
2024-11-14 09:28:25 +01:00
Damian Gryski 91563cff1f targets/wasm_exec: call process.exit() when go.run() returns 2024-11-14 09:16:25 +01:00
Ben Krieger 4f96a50fd0 reflect: fix Copy of non-pointer array with size > 64bits 2024-11-13 10:28:33 -07:00
Ben Krieger e98fea0bba reflect: add Value.Clear; support anytype->interface{}, Slice->(*)Array in Value.Convert 2024-11-13 10:28:33 -07:00
Ayke van Laethem c728e031df goenv: read git hash embedded in the binary
The git hash that's part of `tinygo version` is now read from the binary
itself instead of embedding it with a `-ldflags` flag. This means it is
also present when building TinyGo using `go build` or `go install`.
2024-11-10 08:59:22 +01:00
Ayke van Laethem ceb7891986 wasm: correctly return from run() in wasm_exec.js
Instead of hanging forever, it should return the exit code from os.Exit.
2024-11-08 11:55:38 +01:00
Ayke van Laethem 04a7baec3e wasm: support //go:wasmexport functions after a call to time.Sleep
This fixes a bug where `//go:wasmexport` functions would not be allowed
anymore after a call to `time.Sleep` (when using `-buildmode=default`).
2024-11-08 11:55:38 +01:00
Ayke van Laethem 8ff97bdedd runtime: remove unnecessary check for negative sleepTicks duration
This is now fixed for every target in the previous commit.

Also see: https://github.com/tinygo-org/tinygo/pull/4239
2024-11-07 15:37:18 +01:00
Ayke van Laethem a6c4287b4d runtime: don't call sleepTicks with a negative duration
There are rare cases where this can happen, see for example
https://github.com/tinygo-org/tinygo/issues/4568
2024-11-07 15:37:18 +01:00
leongross f9f439ad49 os: implement StartProcess
Signed-off-by: leongross <leon.gross@9elements.com>
2024-11-07 09:45:47 +01:00
Randy Reddig c02a8141c7 internal/wasm-tools, syscall: update to wasm-tools-go@v0.3.1 (#4577)
* internal/wasm-tools, internal/cm: update wasm-tools-go to v0.3.1 and regenerate bindings

* syscall: use new (cm.Result).Result() method instead of OK() and Err()
2024-11-04 14:32:17 -08:00
sago35 5862b481a4 goenv: update to new v0.35.0 development version 2024-11-01 09:14:09 +01:00
Joonas Bergius 1648fe8ef2 runtime/trace: stub all public methods
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
2024-11-01 09:12:59 +01:00
Ayke van Laethem 449eefdb19 compiler: allow deferred panic
This is rare, but apparently some programs do this:

    defer panic("...")

This is emitted in the IR as a builtin function.
2024-11-01 09:11:38 +01:00
Ayke van Laethem 058f62ac08 main: parse extldflags early so we can report the error message
This avoids some weird behavior when the -extldflags flag cannot be
parsed by TinyGo.
2024-11-01 09:10:49 +01:00
Ayke van Laethem 4e49ba597d interrupt: fix bug in interrupt lowering
The alignment wasn't set, so defaulted to 4 (for a 32-bit int). LLVM saw
this, and therefore assumed that a ptrtoint of the pointer would have
had the lowest bits unset. That's an entirely valid optimization, except
that we are using these globals for arbitrary values (and aren't
actually using these globals).

Fixed by setting alignment to 1. It works, though long-term we should
maybe find a different solution for this.
2024-11-01 09:10:25 +01:00
Ayke van Laethem 4b706ae25c test: show output even when a test binary didn't exit cleanly
This was a problem on wasm, where node would exit with a non-zero exit
code when there was a panic.
2024-11-01 08:49:00 +01:00
Ayke van Laethem 1ac26d3d2f test: run TestWasmExportJS tests in parallel 2024-11-01 08:48:19 +01:00
72 changed files with 1534 additions and 398 deletions
+2
View File
@@ -90,6 +90,8 @@ 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 }}
+10 -3
View File
@@ -26,6 +26,8 @@ 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
@@ -33,6 +35,9 @@ 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:
@@ -104,7 +109,7 @@ jobs:
- name: Test stdlib packages
run: make tinygo-test
- name: Make release artifact
run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.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
@@ -114,8 +119,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
path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
- name: Smoke tests
run: make smoketest TINYGO=$(PWD)/build/tinygo
test-macos-homebrew:
@@ -125,6 +130,8 @@ 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
+26 -14
View File
@@ -19,7 +19,11 @@ 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?)
@@ -32,6 +36,9 @@ 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:
@@ -120,15 +127,15 @@ jobs:
- name: Build TinyGo release
run: |
make release deb -j3 STATIC=1
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
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
- name: Publish release artifact
uses: actions/upload-artifact@v4
with:
name: linux-amd64-double-zipped
name: linux-amd64-double-zipped-${{ steps.version.outputs.version }}
path: |
/tmp/tinygo.linux-amd64.tar.gz
/tmp/tinygo_amd64.deb
/tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
/tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
test-linux-build:
# Test the binaries built in the build-linux job by running the smoke tests.
runs-on: ubuntu-latest
@@ -152,11 +159,11 @@ jobs:
- name: Download release artifact
uses: actions/download-artifact@v4
with:
name: linux-amd64-double-zipped
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
- name: Extract release tarball
run: |
mkdir -p ~/lib
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.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
@@ -166,6 +173,8 @@ jobs:
# potential bugs.
runs-on: ubuntu-latest
steps:
- name: exit early
run: command-does-not-exist
- name: Checkout
uses: actions/checkout@v4
with:
@@ -297,6 +306,9 @@ 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
@@ -381,11 +393,11 @@ jobs:
- name: Download amd64 release
uses: actions/download-artifact@v4
with:
name: linux-amd64-double-zipped
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
@@ -393,12 +405,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.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb
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
- name: Publish release artifact
uses: actions/upload-artifact@v4
with:
name: linux-${{ matrix.goarch }}-double-zipped
name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
path: |
/tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_${{ matrix.libc }}.deb
/tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
/tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb
+2
View File
@@ -15,6 +15,8 @@ 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,6 +15,8 @@ jobs:
permissions:
pull-requests: write
steps:
- name: exit early
run: command-does-not-exist
# Prepare, install tools
- name: Add GOBIN to $PATH
run: |
+4
View File
@@ -0,0 +1,4 @@
#!/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'
+52 -26
View File
@@ -14,6 +14,8 @@ 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
@@ -21,22 +23,30 @@ 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
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- 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
- name: Restore cached LLVM source
uses: actions/cache/restore@v4
id: cache-llvm-source
@@ -85,6 +95,25 @@ 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
@@ -99,16 +128,13 @@ 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 release.zip tinygo
run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo
- name: Publish release artifact
# Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
@@ -118,8 +144,8 @@ jobs:
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4
with:
name: windows-amd64-double-zipped
path: build/release/release.zip
name: windows-amd64-double-zipped-${{ steps.version.outputs.version }}
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
smoke-test-windows:
runs-on: windows-2022
@@ -148,12 +174,12 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
run: 7z x tinygo*.windows-amd64.zip -r
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -178,12 +204,12 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
run: 7z x tinygo*.windows-amd64.zip -r
- name: Test stdlib packages
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -214,11 +240,11 @@ jobs:
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
run: 7z x tinygo*.windows-amd64.zip -r
- name: Test stdlib packages on wasip1
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
+7 -5
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" -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)
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" .
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
TEST_PACKAGES_SLOW = \
@@ -926,6 +926,7 @@ 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
@@ -941,6 +942,7 @@ 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,6 +116,7 @@ var libMusl = Library{
"env/*.c",
"errno/*.c",
"exit/*.c",
"fcntl/*.c",
"internal/defsysinfo.c",
"internal/libc.c",
"internal/syscall_ret.c",
@@ -136,12 +137,20 @@ 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", 4568, 280, 0, 2268},
{"microbit", "examples/serial", 2868, 388, 8, 2272},
{"wioterminal", "examples/pininterrupt", 6104, 1484, 116, 6832},
{"hifive1b", "examples/echo", 4600, 280, 0, 2268},
{"microbit", "examples/serial", 2908, 388, 8, 2272},
{"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6832},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
+18 -2
View File
@@ -32,10 +32,26 @@ 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. But
// removing all environmental variables also works.
// Most importantly, it should not use C_INCLUDE_PATH and the like.
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, iscall)
node := f.getASTDeclNode(name, found)
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, iscall bool) ast.Node {
func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node {
if node, ok := f.defined[name]; ok {
// Declaration was found in the current file, so return it immediately.
return node
+136 -4
View File
@@ -54,8 +54,72 @@ func init() {
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
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]
}
}
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
@@ -96,6 +160,68 @@ 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,
@@ -164,21 +290,25 @@ 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) *tokenizer {
func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *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()
@@ -230,7 +360,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 == '^':
case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
@@ -238,6 +368,8 @@ 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)
expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil)
s := "<invalid>"
if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") {
+59 -39
View File
@@ -63,6 +63,7 @@ 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);
@@ -370,45 +371,8 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_MacroDefinition:
// 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)
tokenPos, value := f.getMacro(c)
expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f)
if scannerError != nil {
f.errors = append(f.errors, *scannerError)
return nil, nil
@@ -488,6 +452,62 @@ 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,3 +84,7 @@ 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,10 +3,26 @@ 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,3 +47,8 @@ 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,6 +26,11 @@ 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.
//
@@ -51,4 +56,7 @@ 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,6 +7,8 @@
// 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)
@@ -17,6 +19,8 @@
// 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
+1 -9
View File
@@ -406,15 +406,7 @@ func (c *Config) LDFlags() []string {
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
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...)
}
ldflags = append(ldflags, c.Options.ExtLDFlags...)
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.
+7 -3
View File
@@ -17,6 +17,7 @@ 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"
@@ -1680,6 +1681,10 @@ 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" {
@@ -1865,10 +1870,9 @@ 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": 1, // panicStrategyPrint
"trap": 2, // panicStrategyTrap
"print": tinygo.PanicStrategyPrint,
"trap": tinygo.PanicStrategyTrap,
}[b.Config.PanicStrategy]
return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil
case name == "runtime/interrupt.New":
+3
View File
@@ -41,6 +41,8 @@ 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)
@@ -48,6 +50,7 @@ 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, "")
+5 -11
View File
@@ -6,17 +6,11 @@ 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) {
@@ -24,20 +18,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 // must match values in src/runtime/hashmap.go
var alg uint64
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmString
alg = uint64(tinygo.HashmapAlgorithmString)
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmBinary
alg = uint64(tinygo.HashmapAlgorithmBinary)
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
alg = hashmapAlgorithmInterface
alg = uint64(tinygo.HashmapAlgorithmInterface)
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
+20 -9
View File
@@ -4,29 +4,37 @@ import (
"errors"
"fmt"
"io"
"runtime/debug"
"strings"
)
// Version of TinyGo.
// Update this value before release of new version of software.
const version = "0.34.0"
var (
// This variable is set at build time using -ldflags parameters.
// See: https://stackoverflow.com/a/11355611
GitSha1 string
)
const version = "0.35.0-dev"
// 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") && GitSha1 != "" {
v += "-" + GitSha1
if strings.HasSuffix(version, "-dev") {
if hash := readGitHash(); hash != "" {
v += "-" + hash
}
}
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) {
@@ -42,6 +50,9 @@ 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,6 +21,7 @@ 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) {
+2 -3
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.0
require github.com/bytecodealliance/wasm-tools-go v0.3.1
require (
github.com/coreos/go-semver v0.3.1 // indirect
@@ -12,8 +12,7 @@ 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 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sys v0.26.0 // indirect
)
+6 -8
View File
@@ -1,5 +1,5 @@
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/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/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,14 +23,12 @@ 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.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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/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 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=
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=
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,6 +256,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"runtime/": false,
"sync/": true,
"testing/": true,
"tinygo/": false,
"unique/": false,
}
+8 -1
View File
@@ -1641,12 +1641,19 @@ 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())
+139 -17
View File
@@ -7,6 +7,7 @@ import (
"bufio"
"bytes"
"context"
"crypto/sha256"
"errors"
"flag"
"io"
@@ -15,7 +16,7 @@ import (
"reflect"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
"sync"
"testing"
@@ -25,9 +26,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"
)
@@ -114,10 +115,15 @@ func TestBuild(t *testing.T) {
return
}
t.Run("Host", func(t *testing.T) {
t.Parallel()
runPlatTests(optionsFromTarget("", sema), tests, t)
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)
})
}
})
return
// Test a few build options.
t.Run("build-options", func(t *testing.T) {
@@ -420,19 +426,67 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
// Build the test binary.
stdout := &bytes.Buffer{}
_, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
return cmd.Run()
})
_, fileExt := config.EmulatorFormat()
tmpdir := t.TempDir()
result, err := builder.Build(pkgName, fileExt, tmpdir, config)
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)
}
t.Fail()
return
t.Fatal("build failed:", err)
}
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.
@@ -517,7 +571,7 @@ func TestWebAssembly(t *testing.T) {
}
}
}
if !slices.Equal(imports, tc.imports) {
if !stringSlicesEqual(imports, tc.imports) {
t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports)
}
}
@@ -525,6 +579,20 @@ 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()
@@ -683,7 +751,14 @@ func TestWasmExport(t *testing.T) {
if tc.command {
// Call _start (the entry point), which calls
// tester.callTestMain, which then runs all the tests.
mustCall(mod.ExportedFunction("_start").Call(ctx))
_, 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)
}
}
} else {
// Run the _initialize call, because this is reactor mode wasm.
mustCall(mod.ExportedFunction("_initialize").Call(ctx))
@@ -728,6 +803,7 @@ 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
@@ -738,7 +814,9 @@ 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)
@@ -766,12 +844,56 @@ 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,6 +71,16 @@ 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
+13 -1
View File
@@ -5,6 +5,12 @@ 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
@@ -47,6 +53,10 @@ 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 {
@@ -57,8 +67,10 @@ 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 nil, &PathError{Op: "fork/exec", Path: name, Err: ErrNotImplemented}
return startProcess(name, argv, attr)
}
func (p *Process) Wait() (*ProcessState, error) {
+103
View File
@@ -0,0 +1,103 @@
// 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
@@ -0,0 +1,78 @@
//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
@@ -0,0 +1,27 @@
//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
@@ -1,35 +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 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
@@ -0,0 +1,58 @@
//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
+15 -2
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 (but not
// pointer because it doesn't have ptrTo).
// All types that have an element type: named, chan, slice, array, map, interface
// (but not pointer because it doesn't have ptrTo).
type elemType struct {
rawType
numMethod uint16
@@ -439,6 +439,12 @@ type ptrType struct {
elem *rawType
}
type interfaceType struct {
rawType
ptrTo *rawType
// TODO: methods
}
type arrayType struct {
rawType
numMethod uint16
@@ -995,6 +1001,10 @@ 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
}
@@ -1060,6 +1070,9 @@ 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.
+63 -11
View File
@@ -689,6 +689,25 @@ 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 {
@@ -888,6 +907,9 @@ 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()
}
@@ -1062,7 +1084,7 @@ func (v Value) Set(x Value) {
v.checkAddressable()
v.checkRO()
if !x.typecode.AssignableTo(v.typecode) {
panic("reflect: cannot set")
panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String())
}
if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface {
@@ -1240,7 +1262,9 @@ func (v Value) OverflowUint(x uint64) bool {
}
func (v Value) CanConvert(t Type) bool {
panic("unimplemented: (reflect.Value).CanConvert()")
// TODO: Optimize this to not actually perform a conversion
_, ok := convertOp(v, t)
return ok
}
func (v Value) Convert(t Type) Value {
@@ -1262,6 +1286,15 @@ 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() {
@@ -1302,14 +1335,33 @@ func convertOp(src Value, typ Type) (Value, bool) {
*/
case Slice:
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
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
}
}
}
@@ -1660,7 +1712,7 @@ func buflen(v Value) (unsafe.Pointer, uintptr) {
buf = hdr.data
len = hdr.len
case Array:
if v.isIndirect() {
if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) {
buf = v.value
} else {
buf = unsafe.Pointer(&v.value)
+211
View File
@@ -1,8 +1,10 @@
package reflect_test
import (
"bytes"
"encoding/base64"
. "reflect"
"slices"
"sort"
"strings"
"testing"
@@ -599,6 +601,29 @@ 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) {
@@ -624,6 +649,192 @@ 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)
+37 -3
View File
@@ -76,6 +76,13 @@ 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 {
@@ -123,7 +130,25 @@ 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 b.state() == blockStateTail {
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
}
b--
}
if gcAsserts {
@@ -146,10 +171,19 @@ 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 {
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, b/blocksPerStateByte))
return blockState(*stateBytePtr>>((b%blocksPerStateByte)*stateBits)) & blockStateMask
return b.stateFromByte(b.stateByte())
}
// setState sets the current block to the given state, which must contain more
+3 -2
View File
@@ -11,7 +11,7 @@ import (
)
// Ever-incrementing pointer: no memory is freed.
var heapptr = heapStart
var heapptr uintptr
// Total amount allocated for runtime.MemStats
var gcTotalAlloc uint64
@@ -93,7 +93,8 @@ func SetFinalizer(obj interface{}, finalizer interface{}) {
}
func initHeap() {
// preinit() may have moved heapStart; reset heapptr
// Initialize this bump-pointer allocator to the start of the heap.
// Needed here because heapStart may not be a compile-time constant.
heapptr = heapStart
}
+11 -18
View File
@@ -7,6 +7,7 @@ package runtime
import (
"reflect"
"tinygo"
"unsafe"
)
@@ -22,14 +23,6 @@ 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
@@ -76,8 +69,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(hashmapAlgorithm(alg))
keyEqual := hashmapKeyEqualAlg(hashmapAlgorithm(alg))
keyHash := hashmapKeyHashAlg(tinygo.HashmapAlgorithm(alg))
keyEqual := hashmapKeyEqualAlg(tinygo.HashmapAlgorithm(alg))
return &hashmap{
buckets: buckets,
@@ -119,13 +112,13 @@ func hashmapClear(m *hashmap) {
}
}
func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
func hashmapKeyEqualAlg(alg tinygo.HashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
switch alg {
case hashmapAlgorithmBinary:
case tinygo.HashmapAlgorithmBinary:
return memequal
case hashmapAlgorithmString:
case tinygo.HashmapAlgorithmString:
return hashmapStringEqual
case hashmapAlgorithmInterface:
case tinygo.HashmapAlgorithmInterface:
return hashmapInterfaceEqual
default:
// compiler bug :(
@@ -133,13 +126,13 @@ func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintpt
}
}
func hashmapKeyHashAlg(alg hashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 {
func hashmapKeyHashAlg(alg tinygo.HashmapAlgorithm) func(key unsafe.Pointer, n, seed uintptr) uint32 {
switch alg {
case hashmapAlgorithmBinary:
case tinygo.HashmapAlgorithmBinary:
return hash32
case hashmapAlgorithmString:
case tinygo.HashmapAlgorithmString:
return hashmapStringPtrHash
case hashmapAlgorithmInterface:
case tinygo.HashmapAlgorithmInterface:
return hashmapInterfacePtrHash
default:
// compiler bug :(
+19 -1
View File
@@ -5,7 +5,9 @@ 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"
@@ -83,6 +85,11 @@ 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)) {
@@ -139,3 +146,14 @@ 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
}
+3 -7
View File
@@ -3,6 +3,7 @@ package runtime
import (
"internal/task"
"runtime/interrupt"
"tinygo"
"unsafe"
)
@@ -22,11 +23,6 @@ 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.
@@ -48,7 +44,7 @@ type deferFrame struct {
// Builtin function panic(msg), used as a compiler intrinsic.
func _panic(message interface{}) {
if panicStrategy() == panicStrategyTrap {
if panicStrategy() == tinygo.PanicStrategyTrap {
trap()
}
// Note: recover is not supported inside interrupts.
@@ -76,7 +72,7 @@ func runtimePanic(msg string) {
}
func runtimePanicAt(addr unsafe.Pointer, msg string) {
if panicStrategy() == panicStrategyTrap {
if panicStrategy() == tinygo.PanicStrategyTrap {
trap()
}
if hasReturnAddr {
+10 -12
View File
@@ -119,19 +119,17 @@ func ticks() timeUnit {
}
func sleepTicks(duration timeUnit) {
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()
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()
}
}
-4
View File
@@ -31,10 +31,6 @@ 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
+8 -3
View File
@@ -80,12 +80,17 @@ func abort() {
//go:linkname syscall_Exit syscall.Exit
func syscall_Exit(code int) {
// 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?
// Flush stdio buffers.
__stdio_exit()
// Exit the program.
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,6 +31,10 @@ 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,6 +60,13 @@ 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.
+4 -1
View File
@@ -5,6 +5,7 @@ package runtime
import (
"math/bits"
"sync/atomic"
"tinygo"
"unsafe"
)
@@ -92,6 +93,8 @@ func main(argc int32, argv *unsafe.Pointer) int {
stackTop = getCurrentStackPointer()
runMain()
sleepTicks(nanosecondsToTicks(1e9))
// For libc compatibility.
return 0
}
@@ -141,7 +144,7 @@ func tinygo_register_fatal_signals()
//
//export tinygo_handle_fatal_signal
func tinygo_handle_fatal_signal(sig int32, addr uintptr) {
if panicStrategy() == panicStrategyTrap {
if panicStrategy() == tinygo.PanicStrategyTrap {
trap()
}
-4
View File
@@ -91,10 +91,6 @@ func ticks() timeUnit {
return timeUnit(nano)
}
func beforeExit() {
__stdio_exit()
}
// Implementations of WASI APIs
//go:wasmimport wasi_snapshot_preview1 args_get
-3
View File
@@ -52,6 +52,3 @@ func sleepTicks(d timeUnit) {
func ticks() timeUnit {
return timeUnit(monotonicclock.Now())
}
func beforeExit() {
}
-4
View File
@@ -32,7 +32,3 @@ func sleepTicks(d timeUnit)
//go:wasmimport gojs runtime.ticks
func ticks() timeUnit
func beforeExit() {
__stdio_exit()
}
+4 -1
View File
@@ -34,5 +34,8 @@ func ticks() timeUnit {
return timeUnit(0)
}
func beforeExit() {
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.
}
+12 -23
View File
@@ -14,12 +14,14 @@ 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()
wasmExportState = wasmExportStateExited
beforeExit()
if mainExited {
// To make sure wasm_exec.js knows that we've exited, exit explicitly.
mainReturnExit()
}
}
// This is the _initialize entry point, when using -buildmode=c-shared.
@@ -27,6 +29,8 @@ 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)
@@ -38,38 +42,23 @@ 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
}
}
// 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
)
// Whether the runtime was initialized by a call to _initialize or _start.
var initializeCalled bool
func wasmExportCheckRun() {
switch wasmExportState {
case wasmExportStateInit:
switch {
case !initializeCalled:
runtimePanic("//go:wasmexport function called before runtime initialization")
case wasmExportStateExited:
case mainExited:
runtimePanic("//go:wasmexport function called after main.main returned")
}
}
+10 -2
View File
@@ -52,7 +52,13 @@ func mainCRTStartup() int {
stackTop = getCurrentStackPointer()
runMain()
// For libc compatibility.
// 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.
return 0
}
@@ -92,7 +98,9 @@ func os_runtime_args() []string {
}
func putchar(c byte) {
libc_putchar(int(c))
if libc_putchar(int(c)) < 0 {
libc_exit(42)
}
}
var heapSize uintptr = 128 * 1024 // small amount to start
+11 -9
View File
@@ -21,7 +21,7 @@ const schedulerDebug = false
// queue a new scheduler invocation using setTimeout.
const asyncScheduler = GOOS == "js"
var schedulerDone bool
var mainExited 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 !schedulerDone {
for !mainExited {
scheduleLog("")
scheduleLog(" schedule")
if sleepQueue != nil || timerQueue != nil {
@@ -230,13 +230,15 @@ func scheduler(returnAtDeadlock bool) {
println("--- timer waiting:", tim, tim.whenTicks())
}
}
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
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
}
}
continue
}
+1 -1
View File
@@ -23,7 +23,7 @@ func run() {
go func() {
initAll()
callMain()
schedulerDone = true
mainExited = true
}()
scheduler(false)
}
+29
View File
@@ -2,6 +2,7 @@
package trace
import (
"context"
"errors"
"io"
)
@@ -11,3 +12,31 @@ 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
}
+72 -75
View File
@@ -162,8 +162,8 @@ func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
}
libcErrno = 0
result := stream.in.BlockingRead(uint64(count))
if err := result.Err(); err != nil {
list, err, isErr := stream.in.BlockingRead(uint64(count)).Result()
if isErr {
if err.Closed() {
libcErrno = 0
return 0
@@ -174,9 +174,7 @@ func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
return -1
}
dst := unsafe.Slice(buf, count)
list := result.OK()
copy(dst, list.Slice())
copy(unsafe.Slice(buf, count), list.Slice())
return int(list.Len())
}
@@ -202,8 +200,8 @@ func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int {
if len > remaining {
len = remaining
}
result := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len]))
if err := result.Err(); err != nil {
_, err, isErr := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])).Result()
if isErr {
if err.Closed() {
libcErrno = 0
return 0
@@ -248,13 +246,13 @@ func pread(fd int32, buf *byte, count uint, offset int64) int {
return -1
}
result := streams.d.Read(types.FileSize(count), types.FileSize(offset))
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
listEOF, err, isErr := streams.d.Read(types.FileSize(count), types.FileSize(offset)).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
list := result.OK().F0
list := listEOF.F0
copy(unsafe.Slice(buf, count), list.Slice())
// TODO(dgryski): EOF bool is ignored?
@@ -285,14 +283,14 @@ func pwrite(fd int32, buf *byte, count uint, offset int64) int {
return -1
}
result := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset))
if err := result.Err(); err != nil {
n, err, isErr := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)).Result()
if isErr {
// TODO(dgryski):
libcErrno = errorCodeToErrno(*err)
libcErrno = errorCodeToErrno(err)
return -1
}
return int(*result.OK())
return int(n)
}
// ssize_t lseek(int fd, off_t offset, int whence);
@@ -321,12 +319,12 @@ func lseek(fd int32, offset int64, whence int) int64 {
case 1: // SEEK_CUR
stream.offset += offset
case 2: // SEEK_END
result := stream.d.Stat()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
stream.offset = int64(result.OK().Size) + offset
stream.offset = int64(stat.Size) + offset
}
return int64(stream.offset)
@@ -439,9 +437,9 @@ func mkdir(pathname *byte, mode uint32) int32 {
return -1
}
result := dir.d.CreateDirectoryAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := dir.d.CreateDirectoryAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
@@ -459,9 +457,9 @@ func rmdir(pathname *byte) int32 {
return -1
}
result := dir.d.RemoveDirectoryAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := dir.d.RemoveDirectoryAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
@@ -486,9 +484,9 @@ func rename(from, to *byte) int32 {
return -1
}
result := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
@@ -520,9 +518,9 @@ func symlink(from, to *byte) int32 {
// TODO(dgryski): check fromDir == toDir?
result := fromDir.d.SymlinkAt(fromRelPath, toRelPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := fromDir.d.SymlinkAt(fromRelPath, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
@@ -554,9 +552,9 @@ func link(from, to *byte) int32 {
// TODO(dgryski): check fromDir == toDir?
result := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
@@ -587,9 +585,9 @@ func fsync(fd int32) int32 {
return -1
}
result := streams.d.SyncData()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := streams.d.SyncData().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
@@ -607,13 +605,12 @@ func readlink(pathname *byte, buf *byte, count uint) int {
return -1
}
result := dir.d.ReadLinkAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
s, err, isErr := dir.d.ReadLinkAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
s := *result.OK()
size := uintptr(count)
if size > uintptr(len(s)) {
size = uintptr(len(s))
@@ -634,9 +631,9 @@ func unlink(pathname *byte) int32 {
return -1
}
result := dir.d.UnlinkFileAt(relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := dir.d.UnlinkFileAt(relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
@@ -661,13 +658,13 @@ func stat(pathname *byte, dst *Stat_t) int32 {
return -1
}
result := dir.d.StatAt(0, relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
stat, err, isErr := dir.d.StatAt(0, relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
setStatFromWASIStat(dst, result.OK())
setStatFromWASIStat(dst, &stat)
return 0
}
@@ -690,13 +687,13 @@ func fstat(fd int32, dst *Stat_t) int32 {
libcErrno = EBADF
return -1
}
result := stream.d.Stat()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
setStatFromWASIStat(dst, result.OK())
setStatFromWASIStat(dst, &stat)
return 0
}
@@ -745,13 +742,13 @@ func lstat(pathname *byte, dst *Stat_t) int32 {
return -1
}
result := dir.d.StatAt(0, relPath)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
stat, err, isErr := dir.d.StatAt(0, relPath).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
setStatFromWASIStat(dst, result.OK())
setStatFromWASIStat(dst, &stat)
return 0
}
@@ -981,25 +978,25 @@ func open(pathname *byte, flags int32, mode uint32) int32 {
pflags &^= types.PathFlagsSymlinkFollow
}
result := dir.d.OpenAt(pflags, relPath, oflags, dflags)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
descriptor, err, isErr := dir.d.OpenAt(pflags, relPath, oflags, dflags).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
stream := wasiFile{
d: *result.OK(),
d: descriptor,
oflag: flags,
refs: 1,
}
if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) {
result := stream.d.Stat()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
stat, err, isErr := stream.d.Stat().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
stream.offset = int64(result.OK().Size)
stream.offset = int64(stat.Size)
}
libcfd := findFreeFD()
@@ -1112,13 +1109,13 @@ func fdopendir(fd int32) unsafe.Pointer {
return nil
}
result := stream.d.ReadDirectory()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
dir, err, isErr := stream.d.ReadDirectory().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return nil
}
return unsafe.Pointer(&libc_DIR{d: *result.OK()})
return unsafe.Pointer(&libc_DIR{d: dir})
}
// int fdclosedir(DIR *);
@@ -1153,13 +1150,13 @@ func readdir(dirp unsafe.Pointer) *Dirent {
return nil
}
result := dir.d.ReadDirectoryEntry()
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
someEntry, err, isErr := dir.d.ReadDirectoryEntry().Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return nil
}
entry := result.OK().Some()
entry := someEntry.Some()
if entry == nil {
libcErrno = 0
return nil
@@ -1311,9 +1308,9 @@ func chdir(name *byte) int {
return -1
}
result := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead)
if err := result.Err(); err != nil {
libcErrno = errorCodeToErrno(*err)
_, err, isErr := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead).Result()
if isErr {
libcErrno = errorCodeToErrno(err)
return -1
}
+5
View File
@@ -233,6 +233,11 @@ 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])
+2
View File
@@ -1,3 +1,5 @@
//go:build linux || unix
package syscall
func Exec(argv0 string, argv []string, envv []string) (err error)
+17
View File
@@ -0,0 +1,17 @@
// 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
)
+36 -15
View File
@@ -132,6 +132,7 @@
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() {
@@ -270,14 +271,11 @@
fd_close: () => 0, // dummy
fd_fdstat_get: () => 0, // dummy
fd_seek: () => 0, // dummy
"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;
}
proc_exit: (code) => {
this.exited = true;
this.exitCode = code;
this._resolveExitPromise();
throw wasmExit;
},
random_get: (bufPtr, bufLen) => {
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
@@ -293,7 +291,14 @@
// func sleepTicks(timeout float64)
"runtime.sleepTicks": (timeout) => {
// Do not sleep, only reactivate scheduler after the given timeout.
setTimeout(this._inst.exports.go_scheduler, timeout);
setTimeout(() => {
if (this.exited) return;
try {
this._inst.exports.go_scheduler();
} catch (e) {
if (e !== wasmExit) throw e;
}
}, timeout);
},
// func finalizeRef(v ref)
@@ -465,12 +470,23 @@
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) {
this._inst.exports._start();
let exitPromise = new Promise((resolve, reject) => {
this._resolveExitPromise = resolve;
});
// TODO: wait until the program exists.
await new Promise(() => {});
// 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;
} else {
this._inst.exports._initialize();
}
@@ -480,7 +496,11 @@
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
try {
this._inst.exports.resume();
} catch (e) {
if (e !== wasmExit) throw e;
}
if (this.exited) {
this._resolveExitPromise();
}
@@ -510,8 +530,9 @@
}
const go = new Go();
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
return go.run(result.instance);
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
let exitCode = await go.run(result.instance);
process.exit(exitCode);
}).catch((err) => {
console.error(err);
process.exit(1);
+15
View File
@@ -19,6 +19,9 @@ func main() {
println("\n# panic replace")
panicReplace()
println("\n# defer panic")
deferPanic()
}
func recoverSimple() {
@@ -89,6 +92,18 @@ 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,3 +23,7 @@ recovered: panic
panic 1
panic 2
recovered: panic 2
# defer panic
defer panic
recovered from deferred call: deferred panic
+27
View File
@@ -0,0 +1,27 @@
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
@@ -0,0 +1,35 @@
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,5 +1,7 @@
package main
import "time"
func init() {
println("called init")
}
@@ -8,6 +10,10 @@ 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()
}