mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2dd06c4378 | |||
| e12da15f7d | |||
| c4867c8743 | |||
| 8068419854 | |||
| 9b3eb3fe59 | |||
| 1dcccf87b5 | |||
| d51ef253a9 | |||
| 6d4dfcf72f | |||
| ac9f72be61 | |||
| 258dac2324 | |||
| b7d91e2f33 | |||
| 860697257b | |||
| 91563cff1f | |||
| 4f96a50fd0 | |||
| e98fea0bba | |||
| c728e031df | |||
| ceb7891986 | |||
| 04a7baec3e | |||
| 8ff97bdedd | |||
| a6c4287b4d | |||
| f9f439ad49 | |||
| c02a8141c7 | |||
| 5862b481a4 | |||
| 1648fe8ef2 | |||
| 449eefdb19 | |||
| 058f62ac08 | |||
| 4e49ba597d | |||
| 4b706ae25c | |||
| 1ac26d3d2f |
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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: |
|
||||
|
||||
Executable
+4
@@ -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'
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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/"
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Vendored
+16
@@ -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
|
||||
)
|
||||
|
||||
Vendored
+5
@@ -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)
|
||||
|
||||
Vendored
+8
@@ -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
|
||||
)
|
||||
|
||||
Vendored
+4
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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,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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -256,6 +256,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
|
||||
"runtime/": false,
|
||||
"sync/": true,
|
||||
"testing/": true,
|
||||
"tinygo/": false,
|
||||
"unique/": false,
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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"))
|
||||
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -52,6 +52,3 @@ func sleepTicks(d timeUnit) {
|
||||
func ticks() timeUnit {
|
||||
return timeUnit(monotonicclock.Now())
|
||||
}
|
||||
|
||||
func beforeExit() {
|
||||
}
|
||||
|
||||
@@ -32,7 +32,3 @@ func sleepTicks(d timeUnit)
|
||||
|
||||
//go:wasmimport gojs runtime.ticks
|
||||
func ticks() timeUnit
|
||||
|
||||
func beforeExit() {
|
||||
__stdio_exit()
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func run() {
|
||||
go func() {
|
||||
initAll()
|
||||
callMain()
|
||||
schedulerDone = true
|
||||
mainExited = true
|
||||
}()
|
||||
scheduler(false)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build linux || unix
|
||||
|
||||
package syscall
|
||||
|
||||
func Exec(argv0 string, argv []string, envv []string) (err error)
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Vendored
+15
@@ -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:
|
||||
|
||||
Vendored
+4
@@ -23,3 +23,7 @@ recovered: panic
|
||||
panic 1
|
||||
panic 2
|
||||
recovered: panic 2
|
||||
|
||||
# defer panic
|
||||
defer panic
|
||||
recovered from deferred call: deferred panic
|
||||
|
||||
Vendored
+27
@@ -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")
|
||||
}
|
||||
Vendored
+35
@@ -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);
|
||||
});
|
||||
Vendored
+6
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user