diff --git a/.circleci/config.yml b/.circleci/config.yml index 11e58108f1..a3a052c28c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,17 +5,17 @@ commands: steps: - run: name: "Pull submodules" - command: git submodule update --init --recursive + command: git submodule update --init llvm-source-linux: steps: - restore_cache: keys: - - llvm-source-17-v1 + - llvm-source-19-v1 - run: name: "Fetch LLVM source" command: make llvm-source - save_cache: - key: llvm-source-17-v1 + key: llvm-source-19-v1 paths: - llvm-project/clang/lib/Headers - llvm-project/clang/include @@ -33,13 +33,13 @@ commands: steps: - restore_cache: keys: - - binaryen-linux-v2 + - binaryen-linux-v3 - run: name: "Build Binaryen" command: | make binaryen - save_cache: - key: binaryen-linux-v2 + key: binaryen-linux-v3 paths: - build/wasm-opt test-linux: @@ -88,8 +88,10 @@ commands: # Do this before gen-device so that it doesn't check the # formatting of generated files. name: Check Go code formatting - command: make fmt-check + 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<> -short -run='TestBuild|TestTest|TestGetList|TestTraceback' - run: make smoketest XTENSA=0 - save_cache: key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} @@ -98,19 +100,21 @@ commands: - /go/pkg/mod jobs: - test-llvm15-go118: + test-llvm15-go119: docker: - - image: golang:1.18-bullseye + - image: golang:1.19-bullseye steps: - test-linux: llvm: "15" + # "make lint" fails before go 1.21 because internal/tools/go.mod specifies packages that require go 1.21 + fmt-check: false resource_class: large - test-llvm17-go122: + test-llvm19-go124: docker: - - image: golang:1.22-bullseye + - image: golang:1.24-bullseye steps: - test-linux: - llvm: "17" + llvm: "19" resource_class: large workflows: @@ -118,6 +122,6 @@ workflows: jobs: # This tests our lowest supported versions of Go and LLVM, to make sure at # least the smoke tests still pass. - - test-llvm15-go118 - # This tests LLVM 17 support when linking against system libraries. - - test-llvm17-go122 + - test-llvm15-go119 + # This tests LLVM 19 support when linking against system libraries. + - test-llvm19-go124 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..869e436c3c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +open_collective: tinygo diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 29e11284bd..25b5971783 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -16,34 +16,36 @@ jobs: name: build-macos strategy: matrix: - # macos-12: amd64 (oldest supported version as of 05-02-2024) + # macos-13: amd64 (oldest supported version as of 18-10-2024) # macos-14: arm64 (oldest arm64 version) - os: [macos-12, macos-14] + os: [macos-13, macos-14] include: - - os: macos-12 + - os: macos-13 goarch: amd64 - os: macos-14 goarch: arm64 runs-on: ${{ matrix.os }} steps: - name: Install Dependencies - shell: bash run: | HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen - name: Checkout 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: - go-version: '1.22' + go-version: '1.24' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-${{ matrix.os }}-v1 + key: llvm-source-19-${{ matrix.os }}-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -68,11 +70,10 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-${{ matrix.os }}-v1 + key: llvm-build-19-${{ matrix.os }}-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' - shell: bash run: | # fetch LLVM source rm -rf llvm-project @@ -100,15 +101,13 @@ jobs: - name: make gen-device run: make -j3 gen-device - name: Test TinyGo - shell: bash run: make test GOTESTFLAGS="-short" - name: Build TinyGo release tarball run: make release -j3 - name: Test stdlib packages run: make tinygo-test - name: Make release artifact - shell: bash - 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 @@ -118,17 +117,16 @@ 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 - shell: bash run: make smoketest TINYGO=$(PWD)/build/tinygo test-macos-homebrew: name: homebrew-install runs-on: macos-latest strategy: matrix: - version: [16, 17] + version: [16, 17, 18, 19] steps: - name: Set up Homebrew uses: Homebrew/actions/setup-homebrew@master @@ -145,15 +143,15 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - name: Build TinyGo (LLVM ${{ matrix.version }}) run: go install -tags=llvm${{ matrix.version }} - name: Check binary run: tinygo version - name: Build TinyGo (default LLVM) - if: matrix.version == 17 + if: matrix.version == 19 run: go install - name: Check binary - if: matrix.version == 17 + if: matrix.version == 19 run: tinygo version diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 9b6c8d895f..0a8fabf1f2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -18,13 +18,15 @@ jobs: # statically linked binary. runs-on: ubuntu-latest container: - image: golang:1.22-alpine + image: golang:1.24-alpine + outputs: + version: ${{ steps.version.outputs.version }} steps: - name: Install apk dependencies # tar: needed for actions/cache@v4 # git+openssh: needed for checkout (I think?) # ruby: needed to install fpm - run: apk add tar git openssh make g++ ruby + run: apk add tar git openssh make g++ ruby-dev - name: Work around CVE-2022-24765 # We're not on a multi-user machine, so this is safe. run: git config --global --add safe.directory "$GITHUB_WORKSPACE" @@ -32,6 +34,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: @@ -43,7 +48,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-alpine-v1 + key: llvm-source-19-linux-alpine-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -68,7 +73,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-alpine-v1 + key: llvm-build-19-linux-alpine-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -113,18 +118,22 @@ jobs: gem install --version 4.0.7 public_suffix gem install --version 2.7.6 dotenv gem install --no-document fpm + - name: Run linter + run: make lint + - name: Run spellcheck + run: make spell - 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 @@ -132,28 +141,31 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + submodules: true - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - name: Install wasmtime - run: | - mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin - curl https://github.com/bytecodealliance/wasmtime/releases/download/v14.0.4/wasmtime-v14.0.4-x86_64-linux.tar.xz -o wasmtime-v14.0.4-x86_64-linux.tar.xz -SfL - tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v14.0.4-x86_64-linux.tar.xz --strip-components=1 wasmtime-v14.0.4-x86_64-linux/* - echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + uses: bytecodealliance/actions/wasmtime/setup@v1 + with: + version: "29.0.1" + - name: Install wasm-tools + uses: bytecodealliance/actions/wasm-tools/setup@v1 - 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-wasi-fast - run: make tinygo-test-wasip1-fast + - run: make tinygo-test-wasip2-fast + - run: make tinygo-test-wasm - run: make smoketest assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch @@ -178,23 +190,23 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - name: Install Node.js uses: actions/setup-node@v4 with: node-version: '18' - name: Install wasmtime - run: | - mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin - curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v14.0.4/wasmtime-v14.0.4-x86_64-linux.tar.xz -o wasmtime-v14.0.4-x86_64-linux.tar.xz -SfL - tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v14.0.4-x86_64-linux.tar.xz --strip-components=1 wasmtime-v14.0.4-x86_64-linux/* - echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH + uses: bytecodealliance/actions/wasmtime/setup@v1 + with: + version: "29.0.1" + - name: Setup `wasm-tools` + uses: bytecodealliance/actions/wasm-tools/setup@v1 - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-asserts-v1 + key: llvm-source-19-linux-asserts-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -219,7 +231,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-asserts-v1 + key: llvm-build-19-linux-asserts-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -266,7 +278,7 @@ jobs: run: make tinygo-test - run: make smoketest - run: make wasmtest - - run: make tinygo-baremetal + - run: make tinygo-test-baremetal build-linux-cross: # Build ARM Linux binaries, ready for release. # This intentionally uses an older Linux image, so that we compile against @@ -286,11 +298,14 @@ jobs: - goarch: arm toolchain: arm-linux-gnueabihf libc: armhf - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 # note: use the oldest image available! (see above) needs: build-linux 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 @@ -301,13 +316,13 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-v1 + key: llvm-source-19-linux-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -332,7 +347,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-linux-${{ matrix.goarch }}-v1 + key: llvm-build-19-linux-${{ matrix.goarch }}-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -356,13 +371,13 @@ jobs: uses: actions/cache@v4 id: cache-binaryen with: - key: binaryen-linux-${{ matrix.goarch }}-v3 + key: binaryen-linux-${{ matrix.goarch }}-v4 path: build/wasm-opt - name: Build Binaryen if: steps.cache-binaryen.outputs.cache-hit != 'true' run: | sudo apt-get install --no-install-recommends ninja-build - git submodule update --init --recursive lib/binaryen + git submodule update --init lib/binaryen make CROSS=${{ matrix.toolchain }} binaryen - name: Install fpm run: | @@ -375,11 +390,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 @@ -387,12 +402,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 diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index d7fd574dfe..d1ba745b2e 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -35,8 +35,8 @@ jobs: uses: docker/metadata-action@v5 with: images: | - tinygo/llvm-17 - ghcr.io/${{ github.repository_owner }}/llvm-17 + tinygo/llvm-19 + ghcr.io/${{ github.repository_owner }}/llvm-19 tags: | type=sha,format=long type=raw,value=latest diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 90695cebc6..b19538c4d9 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -15,16 +15,21 @@ jobs: nix-test: runs-on: ubuntu-latest steps: + - name: Uninstall system LLVM + # Hack to work around issue where we still include system headers for + # some reason. + # See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668 + run: sudo apt-get remove llvm-18 - name: Checkout uses: actions/checkout@v4 - - name: Pull musl + - name: Pull musl, bdwgc run: | - git submodule update --init lib/musl + git submodule update --init lib/musl lib/bdwgc - name: Restore LLVM source cache uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-linux-nix-v1 + key: llvm-source-19-linux-nix-v1 path: | llvm-project/compiler-rt - name: Download LLVM source diff --git a/.github/workflows/sizediff-install-pkgs.sh b/.github/workflows/sizediff-install-pkgs.sh index ce51d4d36f..e81c994ea8 100755 --- a/.github/workflows/sizediff-install-pkgs.sh +++ b/.github/workflows/sizediff-install-pkgs.sh @@ -2,11 +2,11 @@ # still works after checking out the dev branch (that is, when going from LLVM # 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed). -echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main' | sudo tee /etc/apt/sources.list.d/llvm.list +echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-19 main' | sudo tee /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo apt-get update sudo apt-get install --no-install-recommends -y \ - llvm-17-dev \ - clang-17 \ - libclang-17-dev \ - lld-17 + llvm-19-dev \ + clang-19 \ + libclang-19-dev \ + lld-19 diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index c9c027bf0f..9c2b49e283 100644 --- a/.github/workflows/sizediff.yml +++ b/.github/workflows/sizediff.yml @@ -9,7 +9,9 @@ concurrency: jobs: sizediff: - runs-on: ubuntu-22.04 + # Note: when updating the Ubuntu version, also update the Ubuntu version in + # sizediff-install-pkgs.sh + runs-on: ubuntu-24.04 permissions: pull-requests: write steps: @@ -28,7 +30,7 @@ jobs: uses: actions/cache@v4 id: cache-llvm-source with: - key: llvm-source-17-sizediff-v1 + key: llvm-source-19-sizediff-v1 path: | llvm-project/compiler-rt - name: Download LLVM source @@ -37,7 +39,7 @@ jobs: - name: Cache Go uses: actions/cache@v4 with: - key: go-cache-linux-sizediff-v1-${{ hashFiles('go.mod') }} + key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }} path: | ~/.cache/go-build ~/go/pkg/mod diff --git a/.github/workflows/tinygo-extract-version.sh b/.github/workflows/tinygo-extract-version.sh new file mode 100755 index 0000000000..2bc1c85e35 --- /dev/null +++ b/.github/workflows/tinygo-extract-version.sh @@ -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' diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d03d2b5073..42365f59b7 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -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 @@ -32,16 +34,20 @@ jobs: uses: actions/checkout@v4 with: submodules: true + - name: Extract TinyGo version + id: version + shell: bash + run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT" - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-17-windows-v1 + key: llvm-source-19-windows-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -66,7 +72,7 @@ jobs: uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-17-windows-v1 + key: llvm-build-19-windows-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -94,9 +100,16 @@ jobs: - name: Build wasi-libc if: steps.cache-wasi-libc.outputs.cache-hit != 'true' run: make wasi-libc + - name: Cache Go cache + uses: actions/cache@v4 + with: + key: go-cache-windows-v1-${{ hashFiles('go.mod') }} + path: | + C:/Users/runneradmin/AppData/Local/go-build + C:/Users/runneradmin/go/pkg/mod - name: Install wasmtime run: | - scoop install wasmtime@14.0.4 + scoop install wasmtime@29.0.1 - name: make gen-device run: make -j3 gen-device - name: Test TinyGo @@ -108,7 +121,7 @@ jobs: - 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 +131,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 @@ -143,17 +156,17 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - 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 @@ -173,17 +186,17 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - 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 @@ -203,22 +216,22 @@ jobs: - name: Install Dependencies shell: bash run: | - scoop install binaryen && scoop install wasmtime@14.0.4 + scoop install binaryen && scoop install wasmtime@29.0.1 - name: Checkout uses: actions/checkout@v4 - name: Install Go uses: actions/setup-go@v5 with: - go-version: '1.22' + go-version: '1.24' cache: true - 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 - - name: Test stdlib packages on wasi - run: make tinygo-test-wasi-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo + 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 diff --git a/.gitignore b/.gitignore index 9d4f702f8c..2761d1fcc7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +.DS_Store +.vscode +go.work +go.work.sum + docs/_build src/device/avr/*.go src/device/avr/*.ld @@ -15,9 +20,11 @@ src/device/stm32/*.go src/device/stm32/*.s src/device/kendryte/*.go src/device/kendryte/*.s +src/device/renesas/*.go +src/device/renesas/*.s src/device/rp/*.go src/device/rp/*.s -vendor +./vendor llvm-build llvm-project build/* @@ -30,5 +37,9 @@ test.exe test.gba test.hex test.nro +test.uf2 test.wasm wasm.wasm + +*.uf2 +*.elf \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index c3e7e47bb8..97689eff82 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,10 +32,13 @@ [submodule "lib/macos-minimal-sdk"] path = lib/macos-minimal-sdk url = https://github.com/aykevl/macos-minimal-sdk.git -[submodule "lib/renesas-svd"] - path = lib/renesas-svd - url = https://github.com/tinygo-org/renesas-svd.git [submodule "src/net"] path = src/net url = https://github.com/tinygo-org/net.git branch = dev +[submodule "lib/wasi-cli"] + path = lib/wasi-cli + url = https://github.com/WebAssembly/wasi-cli +[submodule "lib/bdwgc"] + path = lib/bdwgc + url = https://github.com/ivmai/bdwgc.git diff --git a/BUILDING.md b/BUILDING.md index efb49c7259..6f90d9badd 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -18,7 +18,7 @@ tarball. If you want to help with development of TinyGo itself, you should follo LLVM, Clang and LLD are quite light on dependencies, requiring only standard build tools to be built. Go is of course necessary to build TinyGo itself. - * Go (1.18+) + * Go (1.19+) * GNU Make * Standard build tools (gcc/clang) * git @@ -28,6 +28,21 @@ build tools to be built. Go is of course necessary to build TinyGo itself. The rest of this guide assumes you're running Linux, but it should be equivalent on a different system like Mac. +## Using GNU Make + +The static build of TinyGo is driven by GNUmakefile, which provides a help target for quick reference: + + % make help + clean Remove build directory + fmt Reformat source + fmt-check Warn if any source needs reformatting + gen-device Generate microcontroller-specific sources + llvm-source Get LLVM sources + llvm-build Build LLVM + tinygo Build the TinyGo compiler + lint Lint source tree + spell Spellcheck source tree + ## Download the source The first step is to download the TinyGo sources (use `--recursive` if you clone @@ -85,7 +100,7 @@ Now that we have a working static build, it's time to make a release tarball: If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules: - git submodule update --init --recursive + git submodule update --init The release tarball is stored in build/release.tar.gz, and can be extracted with the following command (for example in ~/lib): diff --git a/CHANGELOG.md b/CHANGELOG.md index 704e1dee9e..1c1d488934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,358 @@ +0.37.0 +--- +* **general** + - add the Boehm-Demers-Weiser GC on Linux +* **ci** + - add more tests for wasm and baremetal +* **compiler** + - crypto/internal/sysrand is allowed to use unsafe signatures +* **examples** + - add goroutine benchmark to examples +* **fixes** + - ensure use of pointers for SPI interface on atsam21/atsam51 and other machines/boards that were missing implementation (#4798) + - replace loop counter with hw timer for USB SetAddressReq on rp2040 (#4796) +* **internal** + - update to go.bytecodealliance.org@v0.6.2 in GNUmakefile and internal/wasm-tools + - exclude certain files when copying package in internal/cm + - update to go.bytecodealliance.org/cm@v0.2.2 in internal/cm + - remove old reflect.go in internal/reflectlite +* **loader** + - use build tags for package iter and iter methods on reflect.Value in loader, iter, reflect + - add shim for go1.22 and earlier in loader, iter +* **machine** + - bump rp2040 to 200MHz (#4768) + - correct register address for Pin.SetInterrupt for rp2350 (#4782) + - don't block the rp2xxx UART interrupt handler + - fix RP2040 Pico board on the playground + - add flash support for rp2350 (#4803) +* **os** + - add stub Symlink for wasm +* **refactor** + - use *SPI everywhere to make consistent for implementations. Fixes #4663 "in reverse" by making SPI a pointer everywhere, as discussed in the comments. +* **reflect** + - add Value.SetIter{Key,Value} and MapIter.Reset in reflect, internal/reflectlite + - embed reflectlite types into reflect types in reflect, internal/reflectlite + - add Go 1.24 iter.Seq[2] methods + - copy reflect iter tests from upstream Go + - panic on Type.CanSeq[2] instead of returning false + - remove strconv.go + - remove unused go:linkname functions +* **riscv-qemu** + - add VirtIO RNG device + - increase stack size +* **runtime** + - only allocate heap memory when needed + - remove unused file func.go + - use package reflectlite +* **transform** + - cherry-pick from #4774 + + +0.36.0 +--- +* **general** + - add initial Go 1.24 support + - add support for LLVM 19 + - update license for 2025 + - make small corrections for README regarding wasm + - use GOOS and GOARCH for building wasm simulated boards + - only infer target for wasm when GOOS and GOARCH are set correctly, not just based on file extension + - add test-corpus-wasip2 + - use older image for cross-compiling builds + - update Linux builds to run on ubuntu-latest since 20.04 is being retired + - ensure build output directory is created + - add NoSandbox flag to chrome headless that is run during WASM tests, since this is now required for Ubuntu 23+ and we are using Ubuntu 24+ when running Github Actions + - update wasmtime used for CI to 29.0.1 to fix issue with install during CI tests + - update to use `Get-CimInstance` as `wmic` is being deprecated on WIndows + - remove unnecessary executable permissions + - `goenv`: update to new v0.36.0 development version +* **compiler** + - `builder`: fix parsing of external ld.lld error messages + - `cgo`: mangle identifier names + - `interp`: correctly mark functions as modifying memory + - add buildmode=wasi-legacy to support existing base of users who expected the older behavior for wasi modules to not return an exit code as if they were reactors +* **standard library** + - `crypto/tls`: add Dialer.DialContext() to fix websocket client + - `crypto/tls`: add VersionTLS constants and VersionName(version uint16) method that turns it into a string, copied from big go + - `internal/syscall/unix`: use our own version of this package + - `machine`: replace hard-coded cpu frequencies on rp2xxx + - `machine`: bump rp2350 CPUFrequency to 150 MHz + - `machine`: compute rp2 clock dividers from crystal and target frequency + - `machine`: remove bytes package dependency in flash code + - `machine/usb/descriptor`: avoid bytes package + - `net`: update to latest submodule with httptest subpackage and ResolveIPAddress implementation + - `os`: add File.Chdir support + - `os`: implement stub Chdir for non-OS systems + - `os/file`: add file.Chmod + - `reflect`: implement Value.Equal + - `runtime`: add FIPS helper functions + - `runtime`: manually initialize xorshift state + - `sync`: move Mutex to internal/task + - `syscall`: add wasip1 RandomGet + - `testing`: add Chdir + - `wasip2`: add stubs to get internal/syscall/unix to work +* **fixes** + - correctly handle calls for GetRNG() when being made from nrf devices with SoftDevice enabled + - fix stm32f103 ADC + - `wasm`: correctly handle id lookup for finalizeRef call + - `wasm`: avoid total failure on wasm finalizer call + - `wasm`: convert offset as signed int into unsigned int in syscall/js.stringVal in wasm_exec.js +* **targets** + - rp2350: add pll generalized solution; fix ADC handles; pwm period fix + - rp2350: extending support to include the rp2350b + - rp2350: cleanup: unexport internal USB and clock package variable, consts and types + - nrf: make ADC resolution changeable + - turn on GC for TKey1 device, since it does in fact work + - match Pico2 stack size to Pico +* **boards** + - add support for Pimoroni Pico Plus2 + - add target for pico2-w board + - add comboat_fw tag for elecrow W5 boards with Combo-AT Wifi firmware + - add support for Elecrow Pico rp2350 W5 boards + - add support for Elecrow Pico rp2040 W5 boards + - add support for NRF51 HW-651 + - add support for esp32c3-supermini + - add support for waveshare-rp2040-tiny +* **examples** + - add naive debouncing for pininterrupt example + + +0.35.0 +--- +* **general** + - update cmsis-svd library + - use default UART settings in the echo example + - `goenv`: also show git hash with custom build of TinyGo + - `goenv`: support parsing development versions of Go + - `main`: parse extldflags early so we can report the error message +* **compiler** + - `builder`: whitelist temporary directory env var for Clang invocation to fix Windows bug + - `builder`: fix cache paths in `-size=full` output + - `builder`: work around incorrectly escaped DWARF paths on Windows (Clang bug) + - `builder`: fix wasi-libc path names on Windows with `-size=full` + - `builder`: write HTML size report + - `cgo`: support C identifiers only referred to from within macros + - `cgo`: support function-like macros + - `cgo`: support errno value as second return parameter + - `cgo`: add support for `#cgo noescape` lines + - `compiler`: fix bug in interrupt lowering + - `compiler`: allow panic directly in `defer` + - `compiler`: fix wasmimport -> wasmexport in error message + - `compiler`: support `//go:noescape` pragma + - `compiler`: report error instead of crashing when instantiating a generic function without body + - `interp`: align created globals +* **standard library** + - `machine`: modify i2s interface/implementation to better match specification + - `os`: implement `StartProcess` + - `reflect`: add `Value.Clear` + - `reflect`: add interface support to `NumMethods` + - `reflect`: fix `AssignableTo` for named + non-named types + - `reflect`: implement `CanConvert` + - `reflect`: handle more cases in `Convert` + - `reflect`: fix Copy of non-pointer array with size > 64bits + - `runtime`: don't call sleepTicks with a negative duration + - `runtime`: optimize GC scanning (findHead) + - `runtime`: move constants into shared package + - `runtime`: add `runtime.fcntl` function for internal/syscall/unix + - `runtime`: heapptr only needs to be initialized once + - `runtime`: refactor scheduler (this fixes a few bugs with `-scheduler=none`) + - `runtime`: rewrite channel implementation to be smaller and more flexible + - `runtime`: use `SA_RESTART` when registering a signal for os/signal + - `runtime`: implement race-free signals using futexes + - `runtime`: run deferred functions in `Goexit` + - `runtime`: remove `Cond` which seems to be unused + - `runtime`: properly handle unix read on directory + - `runtime/trace`: stub all public methods + - `sync`: don't use volatile in `Mutex` + - `sync`: implement `WaitGroup` using a (pseudo)futex + - `sync`: make `Cond` parallelism-safe + - `syscall`: use wasi-libc tables for wasm/js target +* **targets** + - `mips`: fix a bug when scanning the stack + - `nintendoswitch`: get this target to compile again + - `rp2350`: add support for the new RP2350 + - `rp2040/rp2350` : make I2C implementation shared for rp2040/rp2350 + - `rp2040/rp2350` : make SPI implementation shared for rp2040/rp2350 + - `rp2040/rp2350` : make RNG implementation shared for rp2040/rp2350 + - `wasm`: revise and simplify wasmtime argument handling + - `wasm`: support `//go:wasmexport` functions after a call to `time.Sleep` + - `wasm`: correctly return from run() in wasm_exec.js + - `wasm`: call process.exit() when go.run() returns + - `windows`: don't return, exit via exit(0) instead to flush stdout buffer +* **boards** + - add support for the Tillitis TKey + - add support for the Raspberry Pi Pico2 (based on the RP2040) + - add support for Pimoroni Tiny2350 + + +0.34.0 +--- +* **general** + - fix `GOOS=wasip1` for `tinygo test` + - add `-C DIR` flag + - add initial documentation for project governance + - add `-ldflags='-extldflags=...'` support + - improve usage message with `tinygo help` and when passing invalid parameters +* **compiler** + - `builder`: remove environment variables when invoking Clang, to avoid the environment changing the behavior + - `builder`: check for the Go toolchain version used to compile TinyGo + - `cgo`: add `C.CBytes` implementation + - `compiler`: fix passing weirdly-padded structs as parameters to new goroutines + - `compiler`: support pragmas on generic functions + - `compiler`: do not let the slice buffer escape when casting a `[]byte` or `[]rune` to a string, to help escape analysis + - `compiler`: conform to the latest iteration of the wasm types proposal + - `loader`: don't panic when main package is not named 'main' + - `loader`: make sure we always return type checker errors even without type errors + - `transform`: optimize range over `[]byte(string)` +* **standard library** + - `crypto/x509`: add package stub to build crypto/x509 on macOS + - `machine/usb/adc/midi`: fix `PitchBend` + - `os`: add `Truncate` stub for baremetal + - `os`: add stubs for `os.File` deadlines + - `os`: add internal `net.newUnixFile` for the net package + - `runtime`: stub runtime_{Before,After}Exec for linkage + - `runtime`: randomize map accesses + - `runtime`: support `maps.Clone` + - `runtime`: add more fields to `MemStats` + - `runtime`: implement newcoro, coroswitch to support package iter + - `runtime`: disallow defer in interrupts + - `runtime`: add support for os/signal on Linux and MacOS + - `runtime`: add gc layout info for some basic types to help the precise GC + - `runtime`: bump GC mark stack size to avoid excessive heap rescans +* **targets** + - `darwin`: use Go standard library syscall package instead of a custom one + - `fe310`: support GPIO `PinInput` + - `mips`: fix compiler crash with GOMIPS=softfloat and defer + - `mips`: add big-endian (GOARCH=mips) support + - `mips`: use MIPS32 (instead of MIPS32R2) as the instruction set for wider compatibility + - `wasi`: add relative and absolute --dir options to wasmtime args + - `wasip2`: add wasmtime -S args to support network interfaces + - `wasm`: add `//go:wasmexport` support (for all WebAssembly targets) + - `wasm`: use precise instead of conservative GC for WebAssembly (including WASI) + - `wasm-unknown`: add bulk memory flags since basically every runtime has it now +* **boards** + - add RAKwireless RAK4631 + - add WaveShare ESP-C3-32S-Kit + + +0.33.0 +--- + +* **general** + - use latest version of x/tools + - add chromeos 9p support for flashing + - sort compiler error messages by source position in a package + - don't include prebuilt libraries in the release to simplify packaging and reduce the release tarball size + - show runtime panic addresses for `tinygo run` + - support Go 1.23 (including all new language features) + - `test`: support GOOS/GOARCH pairs in the `-target` flag + - `test`: remove message after test binary built +* **compiler** + - remove unused registers for x86_64 linux syscalls + - remove old atomics workaround for AVR (not necessary in modern LLVM versions) + - support `golang.org/x/sys/unix` syscalls + - `builder`: remove workaround for generics race condition + - `builder`: add package ID to compiler and optimization error messages + - `builder`: show better error messages for some common linker errors + - `cgo`: support preprocessor macros passed on the command line + - `cgo`: use absolute paths for error messages + - `cgo`: add support for printf + - `loader`: handle `go list` errors inside TinyGo (for better error messages) + - `transform`: fix incorrect alignment of heap-to-stack transform + - `transform`: use thinlto-pre-link passes (instead of the full pipeline) to speed up compilation speed slightly +* **standard library** + - `crypto/tls`: add CipherSuiteName and some extra fields to ConnectionSTate + - `internal/abi`: implement initial version of this package + - `machine`: use new `internal/binary` package + - `machine`: rewrite Reply() to fix sending long replies in I2C Target Mode + - `machine/usb/descriptor`: Reset joystick physical + - `machine/usb/descriptor`: Drop second joystick hat + - `machine/usb/descriptor`: Add more HID... functions + - `machine/usb/descriptor`: Fix encoding of values + - `machine/usb/hid/joystick`: Allow more hat switches + - `os`: add `Chown`, `Truncate` + - `os/user`: use stdlib version of this package + - `reflect`: return correct name for the `unsafe.Pointer` type + - `reflect`: implement `Type.Overflow*` functions + - `runtime`: implement dummy `getAuxv` to satisfy golang.org/x/sys/ + - `runtime`: don't zero out new allocations for `-gc=leaking` when they are already zeroed + - `runtime`: simplify slice growing/appending code + - `runtime`: print a message when a fatal signal like SIGSEGV happens + - `runtime/debug`: add `GoVersion` to `debug.BuildInfo` + - `sync`: add `Map.Clear()` + - `sync/atomic`: add And* and Or* compiler intrinsics needed for Go 1.23 + - `syscall`: add `Fork` and `Execve` + - `syscall`: add all MacOS errno values + - `testing`: stub out `T.Deadline` + - `unique`: implement custom (naive) version of the unique package +* **targets** + - `arm`: support `GOARM=*,softfloat` (softfloat support for ARM v5, v6, and v7) + - `mips`: add linux/mipsle (and experimental linux/mips) support + - `mips`: add `GOMIPS=softfloat` support + - `wasip2`: add WASI preview 2 support + - `wasm/js`: add `node:` prefix in `require()` call of wasm_exec.js + - `wasm-unknown`: make sure the `os` package can be imported + - `wasm-unknown`: remove import-memory flag + + +0.32.0 +--- + +* **general** + - fix wasi-libc include headers on Nix + - apply OpenOCD commands after target configuration + - fix a minor race condition when determining the build tags + - support UF2 drives with a space in their name on Linux + - add LLVM 18 support + - drop support for Go 1.18 to be able to stay up to date + +* **compiler** + - move `-panic=trap` support to the compiler/runtime + - fix symbol table index for WebAssembly archives + - fix ed25519 build errors by adjusting the alias names + - add aliases to generic AES functions + - fix race condition by temporarily applying a proposed patch + - `builder`: keep un-wasm-opt'd .wasm if -work was passed + - `builder`: make sure wasm-opt command line is printed if asked + - `cgo`: implement shift operations in preprocessor macros + - `interp`: checking for methodset existence + +* **standard library** + - `machine`: add `__tinygo_spi_tx` function to simulator + - `machine`: fix simulator I2C support + - `machine`: add GetRNG support to simulator + - `machine`: add `TxFifoFreeLevel` for CAN + - `os`: add `Link` + - `os`: add `FindProcess` for posix + - `os`: add `Process.Release` for unix + - `os`: add `SetReadDeadline` stub + - `os`, `os/signal`: add signal stubs + - `os/user`: add stubs for `Lookup{,Group}` and `Group` + - `reflect`: use int in `StringHeader` and `SliceHeader` on non-AVR platforms + - `reflect`: fix `NumMethods` for Interface type + - `runtime`: skip negative sleep durations in sleepTicks + +* **targets** + - `esp32`: add I2C support + - `rp2040`: move UART0 and UART1 to common file + - `rp2040`: make all RP2040 boards available for simulation + - `rp2040`: fix timeUnit type + - `stm32`: add i2c `Frequency` and `SetBaudRate` function for chips that were missing implementation + - `wasm-unknown`: add math and memory builtins that LLVM needs + - `wasip1`: replace existing `-target=wasi` support with wasip1 as supported in Go 1.21+ + +* **boards** + - `adafruit-esp32-feather-v2`: add the Adafruit ESP32 Feather V2 + - `badger2040-w`: add support for the Badger2040 W + - `feather-nrf52840-sense`: fix lack of LXFO + - `m5paper`: add support for the M5 Paper + - `mksnanov3`: limit programming speed to 1800 kHz + - `nucleol476rg`: add stm32 nucleol476rg support + - `pico-w`: add the Pico W (which is near-idential to the pico target) + - `thingplus-rp2040`, `waveshare-rp2040-zero`: add WS2812 definition + - `pca10059-s140v7`: add this variant to the PCA10059 board + + 0.31.2 --- @@ -159,7 +514,7 @@ - `reflect`: add SetZero - `reflect`: fix iterating over maps with interface{} keys - `reflect`: implement Value.Grow - - `reflect`: remove unecessary heap allocations + - `reflect`: remove unnecessary heap allocations - `reflect`: use .key() instead of a type assert - `sync`: add implementation from upstream Go for OnceFunc, OnceValue, and OnceValues * **targets** @@ -1838,7 +2193,7 @@ - allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it - remove `//go:volatile` support It has been replaced with the runtime/volatile package. - - allow poiners in map keys + - allow pointers in map keys - support non-constant syscall numbers - implement non-blocking selects - add support for the `-tags` flag diff --git a/Dockerfile b/Dockerfile index d54719e250..520ad7c9b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # tinygo-llvm stage obtains the llvm source for TinyGo -FROM golang:1.22 AS tinygo-llvm +FROM golang:1.24 AS tinygo-llvm RUN apt-get update && \ apt-get install -y apt-utils make cmake clang-15 ninja-build && \ @@ -27,13 +27,13 @@ COPY . /tinygo # build the compiler and tools RUN cd /tinygo/ && \ - git submodule update --init --recursive && \ + git submodule update --init && \ make gen-device -j4 && \ make build/release # tinygo-compiler copies the compiler build over to a base Go container (without # all the build tools etc). -FROM golang:1.22 AS tinygo-compiler +FROM golang:1.24 AS tinygo-compiler # Copy tinygo build. COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo diff --git a/GNUmakefile b/GNUmakefile index cc3367060f..1a97269e9b 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -10,7 +10,7 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld # Try to autodetect LLVM build tools. # Versions are listed here in descending priority order. -LLVM_VERSIONS = 17 16 15 +LLVM_VERSIONS = 19 18 17 16 15 errifempty = $(if $(1),$(1),$(error $(2))) detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) toolSearchPathsVersion = $(1)-$(2) @@ -31,6 +31,7 @@ export GOROOT = $(shell $(GO) env GOROOT) # Flags to pass to go test. GOTESTFLAGS ?= +GOTESTPKGS ?= ./builder ./cgo ./compileopts ./compiler ./interp ./transform . # tinygo binary for tests TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo) @@ -110,7 +111,7 @@ endif .PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp -LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest +LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest ifeq ($(OS),Windows_NT) EXE = .exe @@ -146,7 +147,7 @@ endif MD5SUM ?= md5sum # Libraries that should be linked in for the statically linked Clang. -CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions +CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangInstallAPI clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ # Libraries that should be linked in for the statically linked LLD. @@ -165,7 +166,7 @@ LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES) # library path (for ninja). # This list also includes a few tools that are necessary as part of the full # TinyGo build. -NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES))) +NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES))) # For static linking. ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") @@ -174,23 +175,23 @@ ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA) endif -clean: +clean: ## Remove build directory @rm -rf build FMT_PATHS = ./*.go builder cgo/*.go compiler interp loader src transform -fmt: +fmt: ## Reformat source @gofmt -l -w $(FMT_PATHS) -fmt-check: +fmt-check: ## Warn if any source needs reformatting @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 -gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp +gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp gen-device-renesas ## Generate microcontroller-specific sources ifneq ($(STM32), 0) 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 --recursive"; 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/ @@ -233,21 +234,19 @@ gen-device-rp: build/gen-device-svd GO111MODULE=off $(GO) fmt ./src/device/rp gen-device-renesas: build/gen-device-svd - ./build/gen-device-svd -source=https://github.com/tinygo-org/renesas-svd lib/renesas-svd/ src/device/renesas/ + ./build/gen-device-svd -source=https://github.com/cmsis-svd/cmsis-svd-data/tree/master/data/Renesas lib/cmsis-svd/data/Renesas/ src/device/renesas/ GO111MODULE=off $(GO) fmt ./src/device/renesas -# Get LLVM sources. $(LLVM_PROJECTDIR)/llvm: - git clone -b xtensa_release_17.0.1 --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) -llvm-source: $(LLVM_PROJECTDIR)/llvm + git clone -b xtensa_release_19.1.2 --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) +llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources # Configure LLVM. TINYGO_SOURCE_DIR=$(shell pwd) $(LLVM_BUILDDIR)/build.ninja: - mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) + mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;AVR;Mips;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) -# Build LLVM. -$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja +$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja ## Build LLVM cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS) ifneq ($(USE_SYSTEM_BINARYEN),1) @@ -256,7 +255,7 @@ ifneq ($(USE_SYSTEM_BINARYEN),1) binaryen: build/wasm-opt$(EXE) build/wasm-opt$(EXE): mkdir -p build - cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE) + cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF -DENABLE_WERROR=OFF $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE) cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE) endif @@ -264,9 +263,22 @@ 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 --recursive"; 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 +WASM_TOOLS_MODULE=go.bytecodealliance.org +.PHONY: wasi-syscall +wasi-syscall: wasi-cm + rm -rf ./src/internal/wasi/* + go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit + +# Copy package cm into src/internal/cm +.PHONY: wasi-cm +wasi-cm: + rm -rf ./src/internal/cm/* + rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm + # Check for Node.js used during WASM tests. NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) MIN_NODEJS_VERSION=18 @@ -278,12 +290,11 @@ ifeq (, $(shell which node)) endif @if [ $(NODEJS_VERSION) -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version 18+ to run tests."; exit 1; fi -# Build the Go compiler. -tinygo: +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`" . + 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=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform . + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi TEST_PACKAGES_SLOW = \ @@ -293,26 +304,32 @@ TEST_PACKAGES_SLOW = \ # Standard library packages that pass tests quickly on darwin, linux, wasi, and windows TEST_PACKAGES_FAST = \ + cmp \ compress/lzw \ compress/zlib \ container/heap \ container/list \ container/ring \ - crypto/des \ + crypto/ecdsa \ + crypto/elliptic \ crypto/md5 \ - crypto/rc4 \ crypto/sha1 \ crypto/sha256 \ crypto/sha512 \ + database/sql/driver \ debug/macho \ embed/internal/embedtest \ encoding \ encoding/ascii85 \ + encoding/asn1 \ encoding/base32 \ encoding/base64 \ encoding/csv \ encoding/hex \ + go/ast \ + go/format \ go/scanner \ + go/version \ hash \ hash/adler32 \ hash/crc64 \ @@ -334,54 +351,93 @@ TEST_PACKAGES_FAST = \ unicode \ unicode/utf16 \ unicode/utf8 \ + unique \ $(nil) -# Assume this will go away before Go2, so only check minor version. -ifeq ($(filter $(shell $(GO) env GOVERSION | cut -f 2 -d.), 16 17 18), ) -TEST_PACKAGES_FAST += crypto/internal/nistec/fiat -else -TEST_PACKAGES_FAST += crypto/elliptic/internal/fiat -endif - # archive/zip requires os.ReadAt, which is not yet supported on windows # bytes requires mmap # compress/flate appears to hang on wasi +# crypto/aes fails on wasi, needs panic()/recover() +# crypto/des fails on wasi, needs panic()/recover() # crypto/hmac fails on wasi, it exits with a "slice out of range" panic # debug/plan9obj requires os.ReadAt, which is not yet supported on windows -# image requires recover(), which is not yet supported on wasi +# image requires recover(), which is not yet supported on wasi # io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi +# mime: fail on wasi; neds panic()/recover() +# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK # mime/quotedprintable requires syscall.Faccessat +# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK +# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK +# regexp/syntax: fails on wasip1; needs panic()/recover() # strconv requires recover() which is not yet supported on wasi -# text/tabwriter requries recover(), which is not yet supported on wasi +# text/tabwriter requires recover(), which is not yet supported on wasi # text/template/parse requires recover(), which is not yet supported on wasi # testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi # Additional standard library packages that pass tests on individual platforms TEST_PACKAGES_LINUX := \ archive/zip \ - bytes \ compress/flate \ + crypto/aes \ + crypto/des \ crypto/hmac \ debug/dwarf \ debug/plan9obj \ image \ io/ioutil \ + mime \ + mime/multipart \ mime/quotedprintable \ net \ + net/mail \ + net/textproto \ + os/user \ + regexp/syntax \ strconv \ - testing/fstest \ text/tabwriter \ text/template/parse TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX) +# os/user requires t.Skip() support TEST_PACKAGES_WINDOWS := \ compress/flate \ + crypto/des \ crypto/hmac \ strconv \ text/template/parse \ $(nil) + +# These packages cannot be tested on wasm, mostly because these tests assume a +# working filesystem. This could perhaps be fixed, by supporting filesystem +# access when running inside Node.js. +TEST_PACKAGES_WASM = $(filter-out $(TEST_PACKAGES_NONWASM), $(TEST_PACKAGES_FAST)) +TEST_PACKAGES_NONWASM = \ + compress/lzw \ + compress/zlib \ + crypto/ecdsa \ + debug/macho \ + embed/internal/embedtest \ + go/format \ + os \ + testing \ + $(nil) + +# These packages cannot be tested on baremetal. +# +# Some reasons why the tests don't pass on baremetal: +# +# * No filesystem is available, so packages like compress/zlib can't be tested +# (just like wasm). +# * picolibc math functions apparently are less precise, the math package +# fails on baremetal. +TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST)) +TEST_PACKAGES_NONBAREMETAL = \ + $(TEST_PACKAGES_NONWASM) \ + math \ + $(nil) + # Report platforms on which each standard library package is known to pass tests jointmp := $(shell echo /tmp/join.$$$$) report-stdlib-tests-pass: @@ -425,18 +481,45 @@ tinygo-bench-fast: $(TINYGO) test -bench . $(TEST_PACKAGES_HOST) # Same thing, except for wasi rather than the current platform. +tinygo-test-wasm: + $(TINYGO) test -target wasm $(TEST_PACKAGES_WASM) tinygo-test-wasi: - $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi + $(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi tinygo-test-wasip1: GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi -tinygo-test-wasi-fast: - $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) ./tests/runtime_wasi tinygo-test-wasip1-fast: - GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) ./tests/runtime_wasi -tinygo-bench-wasi: - $(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) -tinygo-bench-wasi-fast: - $(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST) + $(TINYGO) test -target=wasip1 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi + +tinygo-test-wasip2-slow: + $(TINYGO) test -target=wasip2 $(TEST_PACKAGES_SLOW) +tinygo-test-wasip2-fast: + $(TINYGO) test -target=wasip2 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi + +tinygo-test-wasip2-sum-slow: + TINYGO=$(TINYGO) \ + TARGET=wasip2 \ + TESTOPTS="-x -work" \ + PACKAGES="$(TEST_PACKAGES_SLOW)" \ + gotestsum --raw-command -- ./tools/tgtestjson.sh +tinygo-test-wasip2-sum-fast: + TINYGO=$(TINYGO) \ + TARGET=wasip2 \ + TESTOPTS="-x -work" \ + PACKAGES="$(TEST_PACKAGES_FAST)" \ + gotestsum --raw-command -- ./tools/tgtestjson.sh +tinygo-bench-wasip1: + $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) +tinygo-bench-wasip1-fast: + $(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) + +tinygo-bench-wasip2: + $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) +tinygo-bench-wasip2-fast: + $(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) + +# Run tests on riscv-qemu since that one provides a large amount of memory. +tinygo-test-baremetal: + $(TINYGO) test -target riscv-qemu $(TEST_PACKAGES_BAREMETAL) # Test external packages in a large corpus. test-corpus: @@ -444,15 +527,21 @@ test-corpus: test-corpus-fast: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml test-corpus-wasi: wasi-libc - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasi - -tinygo-baremetal: - # Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest. - # regression test for #2666: e.g. encoding/hex must pass on baremetal - $(TINYGO) test -target cortex-m-qemu encoding/hex + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1 +test-corpus-wasip2: wasi-libc + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2 + +.PHONY: testchdir +testchdir: + # test 'build' command with{,out} -C argument + $(TINYGO) build -C tests/testing/chdir chdir.go && rm tests/testing/chdir/chdir + $(TINYGO) build ./tests/testing/chdir/chdir.go && rm chdir + # test 'run' command with{,out} -C argument + EXPECT_DIR=$(PWD)/tests/testing/chdir $(TINYGO) run -C tests/testing/chdir chdir.go + EXPECT_DIR=$(PWD) $(TINYGO) run ./tests/testing/chdir/chdir.go .PHONY: smoketest -smoketest: +smoketest: testchdir $(TINYGO) version $(TINYGO) targets > /dev/null # regression test for #2892 @@ -462,6 +551,8 @@ smoketest: # regression test for #2563 cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test # test all examples (except pwm) + $(TINYGO) build -size short -o test.hex -target=pga2350 examples/echo + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc @@ -490,7 +581,7 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial + $(TINYGO) build -size short -o test.hex -target=pca10040 examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick @$(MD5SUM) test.hex @@ -510,19 +601,23 @@ smoketest: @$(MD5SUM) test.hex # test simulated boards on play.tinygo.org ifneq ($(WASM), 0) - $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 + @$(MD5SUM) test.wasm + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 + @$(MD5SUM) test.wasm + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 @$(MD5SUM) test.wasm - $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/serial + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=pico examples/blinky1 @$(MD5SUM) test.wasm endif # test all targets/boards @@ -598,6 +693,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pca10059-s140v7 examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1 @@ -606,6 +703,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pyportal examples/dac @@ -616,7 +715,7 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1 @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=qtpy examples/serial + $(TINYGO) build -size short -o test.hex -target=qtpy examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=teensy41 examples/blinky1 @$(MD5SUM) test.hex @@ -654,6 +753,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=badger2040 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=badger2040-w examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=tufty2040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=thingplus-rp2040 examples/blinky1 @@ -672,6 +773,14 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=thumby examples/echo @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pico-plus2 examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex @@ -686,7 +795,7 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/serial + $(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/machinetest @$(MD5SUM) test.hex ifneq ($(STM32), 0) $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 @@ -703,6 +812,8 @@ ifneq ($(STM32), 0) @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=nucleo-l476rg examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nucleo-wl55jc examples/blinky1 @@ -726,7 +837,7 @@ ifneq ($(STM32), 0) endif $(TINYGO) build -size short -o test.hex -target=atmega328pb examples/blinkm @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial + $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/machinetest @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 @$(MD5SUM) test.hex @@ -751,27 +862,49 @@ endif ifneq ($(XTENSA), 0) $(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1 @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 + @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial + $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target m5stack examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target m5stack examples/serial + $(TINYGO) build -size short -o test.bin -target m5stick-c examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target m5stick-c examples/serial + $(TINYGO) build -size short -o test.bin -target m5paper examples/machinetest @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target mch2022 examples/serial + $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest @$(MD5SUM) test.bin endif - $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/serial + $(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/machinetest + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/machinetest + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32-c3-devkit-rust-1 examples/blinky1 @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/serial + $(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/blinky1 @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/serial + $(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=tkey examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=elecrow-rp2040 examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=elecrow-rp2350 examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=hw-651 examples/machinetest + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=hw-651-s110v8 examples/machinetest + @$(MD5SUM) test.hex ifneq ($(WASM), 0) $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main @@ -786,11 +919,12 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo @$(MD5SUM) test.hex - $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial + $(TINYGO) build -o test.nro -target=nintendoswitch examples/echo2 @$(MD5SUM) test.nro $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go @$(MD5SUM) test.hex GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo + GOOS=linux GOARCH=mips $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=windows GOARCH=arm64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo @@ -807,10 +941,12 @@ wasmtest: build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen) @mkdir -p build/release/tinygo/bin + @mkdir -p build/release/tinygo/lib/bdwgc @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/macos-minimal-sdk @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common + @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @mkdir -p build/release/tinygo/lib/musl/arch @mkdir -p build/release/tinygo/lib/musl/crt @@ -818,15 +954,16 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN @mkdir -p build/release/tinygo/lib/nrfx @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm - @mkdir -p build/release/tinygo/lib/wasi-libc - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0 - @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus - @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4 + @mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers + @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch + @mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @mkdir -p build/release/tinygo/lib/wasi-cli/ @echo copying source files @cp -p build/tinygo$(EXE) build/release/tinygo/bin ifneq ($(USE_SYSTEM_BINARYEN),1) @cp -p build/wasm-opt$(EXE) build/release/tinygo/bin endif + @cp -rp lib/bdwgc/* build/release/tinygo/lib/bdwgc @cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include @cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS @cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS @@ -835,29 +972,39 @@ endif @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch + @cp -rp lib/musl/arch/mips build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl @cp -rp lib/musl/include build/release/tinygo/lib/musl + @cp -rp lib/musl/src/ctype build/release/tinygo/lib/musl/src @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 + @cp -rp lib/musl/src/locale build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/linux build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/sched build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src + @cp -rp lib/musl/src/stdlib build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src @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 + @cp -rp lib/mingw-w64/mingw-w64-crt/stdio/ucrt_* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio @cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx @@ -869,17 +1016,20 @@ endif @cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib - @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot + @cp -rp lib/wasi-libc/libc-bottom-half/headers/public build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers + @cp -rp lib/wasi-libc/libc-top-half/musl/arch/generic build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch + @cp -rp lib/wasi-libc/libc-top-half/musl/arch/wasm32 build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch + @cp -rp lib/wasi-libc/libc-top-half/musl/src/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/src/internal build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/src/math build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/src/string build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src + @cp -rp lib/wasi-libc/libc-top-half/musl/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl + @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot + @cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp src build/release/tinygo/src @cp -rp targets build/release/tinygo/targets - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc - ./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc release: tar -czf build/release.tar.gz -C build/release tinygo @@ -896,3 +1046,34 @@ ifneq ($(RELEASEONLY), 1) release: build/release deb: build/release endif + +.PHONY: tools +tools: + cd internal/tools && go generate -tags tools ./ + +.PHONY: lint +lint: tools ## Lint source tree + revive -version + # TODO: lint more directories! + # revive.toml isn't flexible enough to filter out just one kind of error from a checker, so do it with grep here. + # Can't use grep with friendly formatter. Plain output isn't too bad, though. + # Use 'grep .' to get rid of stray blank line + revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}' + +SPELLDIRSCMD=find . -depth 1 -type d | egrep -wv '.git|lib|llvm|src'; find src -depth 1 | egrep -wv 'device|internal|net|vendor'; find src/internal -depth 1 -type d | egrep -wv src/internal/wasi +.PHONY: spell +spell: tools ## Spellcheck source tree + misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md + +.PHONY: spellfix +spellfix: tools ## Same as spell, but fixes what it finds + misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md + +# https://www.client9.com/self-documenting-makefiles/ +.PHONY: help +help: + @awk -F ':|##' '/^[^\t].+?:.*?##/ {\ + gsub(/\$$\(LLVM_BUILDDIR\)/, "$(LLVM_BUILDDIR)"); \ + printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF \ + }' $(MAKEFILE_LIST) +#.DEFAULT_GOAL=help diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000000..7650b404b4 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,32 @@ +TinyGo Team Members +=================== + +The team of humans who maintain TinyGo. + +* **Purpose**: To maintain the community, code, documentation, and tools for the TinyGo compiler. +* **Board**: The group of people who share responsibility for key decisions for the TinyGo organization. + * **Majority Voting**: The board makes decisions by majority vote. + * **Membership**: The board elects its own members. +* **Do-ocracy**: Those who step forward to do a given task propose how it should be done. Then other interested people can make comments. +* **Proof of Work**: Power in decision-making is slightly weighted based on a participant's labor for the community. +* **Initiation**: We need to establish a procedure for how people join the team of maintainers. +* **Transparency**: Important information should be made publicly available, ideally in a way that allows for public comment. +* **Code of Conduct**: Participants agree to abide by the current project Code of Conduct. + +## Members + +* Ayke van Laethem (@aykevl) +* Daniel Esteban (@conejoninja) +* Ron Evans (@deadprogram) +* Damian Gryski (@dgryski) +* Masaaki Takasago (@sago35) +* Patricio Whittingslow (@soypat) +* Yurii Soldak (@ysoldak) + +## Experimental + +* **Monthly Meeting**: A monthly meeting for the team and any other interested participants. + Duration: 1 hour + Facilitation: @deadprogram + Schedule: See https://github.com/tinygo-org/tinygo/wiki/Meetings for more information + diff --git a/LICENSE b/LICENSE index 92f68666c5..4d0fde7595 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ -Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved. +Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved. TinyGo includes portions of the Go standard library. -Copyright (c) 2009-2023 The Go Authors. All rights reserved. +Copyright (c) 2009-2024 The Go Authors. All rights reserved. TinyGo includes portions of LLVM, which is under the Apache License v2.0 with LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information. diff --git a/README.md b/README.md index d470c47394..518dcdad18 100644 --- a/README.md +++ b/README.md @@ -41,27 +41,29 @@ tinygo flash -target arduino examples/blinky1 TinyGo is very useful for compiling programs both for use in browsers (WASM) as well as for use on servers and other edge devices (WASI). -TinyGo programs can run in Fastly Compute@Edge (https://developer.fastly.com/learning/compute/go/), Fermyon Spin (https://developer.fermyon.com/spin/go-components), wazero (https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes. +TinyGo programs can run in [Fastly Compute](https://www.fastly.com/documentation/guides/compute/go/), [Fermyon Spin](https://developer.fermyon.com/spin/go-components), [wazero](https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes. Here is a small TinyGo program for use by a WASI host application: ```go package main -//go:wasm-module yourmodulename -//export add +//go:wasmexport add func add(x, y uint32) uint32 { return x + y } +``` + +This compiles the above TinyGo program for use on any WASI Preview 1 runtime: -// main is required for the `wasi` target, even if it isn't used. -func main() {} +```shell +tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go ``` -This compiles the above TinyGo program for use on any WASI runtime: +You can also use the same syntax as Go 1.24+: ```shell -tinygo build -o main.wasm -target=wasi main.go +GOARCH=wasip1 GOOS=wasm tinygo build -buildmode=c-shared -o add.wasm add.go ``` ## Installation diff --git a/builder/ar.go b/builder/ar.go index 3f1c8c213f..245f08ffb3 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -16,7 +16,7 @@ import ( "github.com/blakesmith/ar" ) -// makeArchive creates an arcive for static linking from a list of object files +// makeArchive creates an archive for static linking from a list of object files // given as a parameter. It is equivalent to the following command: // // ar -rcs @@ -78,17 +78,27 @@ func makeArchive(arfile *os.File, objs []string) error { } else if dbg, err := wasm.Parse(objfile); err == nil { for _, s := range dbg.Sections { switch section := s.(type) { - case *wasm.SectionImport: - for _, ln := range section.Entries { - - if ln.Kind != wasm.ExtKindFunction { - // Not a function + case *wasm.SectionLinking: + for _, symbol := range section.Symbols { + if symbol.Flags&wasm.LinkingSymbolFlagUndefined != 0 { + // Don't list undefined functions. + continue + } + if symbol.Flags&wasm.LinkingSymbolFlagBindingLocal != 0 { + // Don't include local symbols. + continue + } + if symbol.Kind != wasm.LinkingSymbolKindFunction && symbol.Kind != wasm.LinkingSymbolKindData { + // Link functions and data symbols. + // Some data symbols need to be included, such as + // __log_data. continue } + // Include in the archive. symbolTable = append(symbolTable, struct { name string fileIndex int - }{ln.Field, i}) + }{symbol.Name, i}) } } } @@ -140,7 +150,7 @@ func makeArchive(arfile *os.File, objs []string) error { } // Keep track of the start of the symbol table. - symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR) + symbolTableStart, err := arfile.Seek(0, io.SeekCurrent) if err != nil { return err } @@ -162,7 +172,7 @@ func makeArchive(arfile *os.File, objs []string) error { // Store the start index, for when we'll update the symbol table with // the correct file start indices. - offset, err := arfile.Seek(0, os.SEEK_CUR) + offset, err := arfile.Seek(0, io.SeekCurrent) if err != nil { return err } diff --git a/builder/bdwgc.go b/builder/bdwgc.go new file mode 100644 index 0000000000..c7c797636e --- /dev/null +++ b/builder/bdwgc.go @@ -0,0 +1,71 @@ +package builder + +// The well-known conservative Boehm-Demers-Weiser GC. +// This file provides a way to compile this GC for use with TinyGo. + +import ( + "path/filepath" + + "github.com/tinygo-org/tinygo/goenv" +) + +var BoehmGC = Library{ + name: "bdwgc", + cflags: func(target, headerPath string) []string { + libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") + return []string{ + // use a modern environment + "-DUSE_MMAP", // mmap is available + "-DUSE_MUNMAP", // return memory to the OS using munmap + "-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations + "-DNO_EXECUTE_PERMISSION", // don't make the heap executable + + // specific flags for TinyGo + "-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go) + "-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment + "-DNO_GETCONTEXT", // musl doesn't support getcontext() + + // Special flag to work around the lack of __data_start in ld.lld. + // TODO: try to fix this in LLVM/lld directly so we don't have to + // work around it anymore. + "-DGC_DONT_REGISTER_MAIN_STATIC_DATA", + + // Do not scan the stack. We have our own mechanism to do this. + "-DSTACK_NOT_SCANNED", + + // Assertions can be enabled while debugging GC issues. + //"-DGC_ASSERTIONS", + + // Threading is not yet supported, so these are disabled. + //"-DGC_THREADS", + //"-DTHREAD_LOCAL_ALLOC", + + "-I" + libdir + "/include", + } + }, + sourceDir: func() string { + return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc") + }, + librarySources: func(target string) ([]string, error) { + return []string{ + "allchblk.c", + "alloc.c", + "blacklst.c", + "dbg_mlc.c", + "dyn_load.c", + "finalize.c", + "headers.c", + "mach_dep.c", + "malloc.c", + "mark.c", + "mark_rts.c", + "misc.c", + "new_hblk.c", + "obj_map.c", + "os_dep.c", + "pthread_stop_world.c", + "pthread_support.c", + "reclaim.c", + }, nil + }, +} diff --git a/builder/build.go b/builder/build.go index b9965c1e85..9d73a03664 100644 --- a/builder/build.go +++ b/builder/build.go @@ -61,6 +61,10 @@ type BuildResult struct { // correctly printing test results: the import path isn't always the same as // the path listed on the command line. ImportPath string + + // Map from path to package name. It is needed to attribute binary size to + // the right Go package. + PackagePathMap map[string]string } // packageAction is the struct that is serialized to JSON and hashed, to work as @@ -143,20 +147,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // the libc needs them. root := goenv.Get("TINYGOROOT") var libcDependencies []*compileJob + var libcJob *compileJob switch config.Target.Libc { case "darwin-libSystem": job := makeDarwinLibSystemJob(config, tmpdir) libcDependencies = append(libcDependencies, job) case "musl": - job, unlock, err := Musl.load(config, tmpdir) + var unlock func() + libcJob, unlock, err = libMusl.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } defer unlock() - libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) - libcDependencies = append(libcDependencies, job) + libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o"))) + libcDependencies = append(libcDependencies, libcJob) case "picolibc": - libcJob, unlock, err := Picolibc.load(config, tmpdir) + libcJob, unlock, err := libPicolibc.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } @@ -168,12 +174,20 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return BuildResult{}, errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?") } libcDependencies = append(libcDependencies, dummyCompileJob(path)) + case "wasmbuiltins": + libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir, nil) + if err != nil { + return BuildResult{}, err + } + defer unlock() + libcDependencies = append(libcDependencies, libcJob) case "mingw-w64": - _, unlock, err := MinGW.load(config, tmpdir) + job, unlock, err := libMinGW.load(config, tmpdir, nil) if err != nil { return BuildResult{}, err } - unlock() + defer unlock() + libcDependencies = append(libcDependencies, job) libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...) case "": // no library specified, so nothing to do @@ -189,6 +203,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ABI: config.ABI(), GOOS: config.GOOS(), GOARCH: config.GOARCH(), + BuildMode: config.BuildMode(), CodeModel: config.CodeModel(), RelocationModel: config.RelocationModel(), SizeLevel: sizeLevel, @@ -200,6 +215,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe MaxStackAlloc: config.MaxStackAlloc(), NeedsStackObjects: config.NeedsStackObjects(), Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed + PanicStrategy: config.PanicStrategy(), } // Load the target machine, which is the LLVM object that contains all @@ -232,6 +248,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return result, err } + // Store which filesystem paths map to which package name. + result.PackagePathMap = make(map[string]string, len(lprogram.Packages)) + for _, pkg := range lprogram.Sorted() { + result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() + } + // Create the *ssa.Program. This does not yet build the entire SSA of the // program so it's pretty fast and doesn't need to be parallelized. program := lprogram.LoadSSA() @@ -345,10 +367,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } packageActionIDJobs[pkg.ImportPath] = packageActionIDJob - // Build the SSA for the given package. - ssaPkg := program.Package(pkg.Pkg) - ssaPkg.Build() - // Now create the job to actually build the package. It will exit early // if the package is already compiled. job := &compileJob{ @@ -371,7 +389,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe defer mod.Context().Dispose() defer mod.Dispose() if errs != nil { - return newMultiError(errs) + return newMultiError(errs, pkg.ImportPath) } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification error after compiling package " + pkg.ImportPath) @@ -433,8 +451,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if global.IsNil() { return errors.New("global not found: " + globalName) } + globalType := global.GlobalValueType() + if globalType.TypeKind() != llvm.StructTypeKind || globalType.StructName() != "runtime._string" { + // Verify this is indeed a string. This is needed so + // that makeGlobalsModule can just create the right + // globals of string type without checking. + return fmt.Errorf("%s: not a string", globalName) + } name := global.Name() - newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp") + newGlobal := llvm.AddGlobal(mod, globalType, name+".tmp") global.ReplaceAllUsesWith(newGlobal) global.EraseFromParentAsGlobal() newGlobal.SetName(name) @@ -522,6 +547,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } } + // Insert values from -ldflags="-X ..." into the IR. + // This is a separate module, so that the "runtime._string" type + // doesn't need to match precisely. LLVM tends to rename that type + // sometimes, leading to errors. But linking in a separate module + // works fine. See: + // https://github.com/tinygo-org/tinygo/issues/4810 + globalsMod := makeGlobalsModule(ctx, globalValues, machine) + llvm.LinkModules(mod, globalsMod) + // Create runtime.initAll function that calls the runtime // initializer of each package. llvmInitFn := mod.NamedFunction("runtime.initAll") @@ -574,7 +608,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Run all optimization passes, which are much more effective now // that the optimizer can see the whole program at once. - err := optimizeProgram(mod, config, globalValues) + err := optimizeProgram(mod, config) if err != nil { return err } @@ -588,6 +622,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe }, } + // Create the output directory, if needed + if err := os.MkdirAll(filepath.Dir(outpath), 0777); err != nil { + return result, err + } + // Check whether we only need to create an object file. // If so, we don't need to link anything and will be finished quickly. outext := filepath.Ext(outpath) @@ -644,10 +683,27 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe result.Binary = result.Executable // final file ldflags := append(config.LDFlags(), "-o", result.Executable) + if config.Options.BuildMode == "c-shared" { + if !strings.HasPrefix(config.Triple(), "wasm32-") { + return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment") + } + ldflags = append(ldflags, "--no-entry") + } + + if config.Options.BuildMode == "wasi-legacy" { + if !strings.HasPrefix(config.Triple(), "wasm32-") { + return result, fmt.Errorf("buildmode wasi-legacy is only supported on wasm") + } + + if config.Options.Scheduler != "none" { + return result, fmt.Errorf("buildmode wasi-legacy only supports scheduler=none") + } + } + // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { - job, unlock, err := CompilerRT.load(config, tmpdir) + job, unlock, err := libCompilerRT.load(config, tmpdir, nil) if err != nil { return result, err } @@ -655,6 +711,19 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe linkerDependencies = append(linkerDependencies, job) } + // The Boehm collector is stored in a separate C library. + if config.GC() == "boehm" { + if libcJob == nil { + return BuildResult{}, fmt.Errorf("boehm GC isn't supported with libc %s", config.Target.Libc) + } + job, unlock, err := BoehmGC.load(config, tmpdir, libcJob) + if err != nil { + return BuildResult{}, err + } + defer unlock() + linkerDependencies = append(linkerDependencies, job) + } + // Add jobs to compile extra files. These files are in C or assembly and // contain things like the interrupt vector table and low level operations // such as stack switching. @@ -677,7 +746,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe for _, pkg := range lprogram.Sorted() { pkg := pkg for _, filename := range pkg.CFiles { - abspath := filepath.Join(pkg.Dir, filename) + abspath := filepath.Join(pkg.OriginalDir(), filename) job := &compileJob{ description: "compile CGo file " + abspath, run: func(job *compileJob) error { @@ -741,6 +810,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ldflags = append(ldflags, dependency.result) } ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) + ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat if config.GOOS() == "windows" { // Options for the MinGW wrapper for the lld COFF linker. ldflags = append(ldflags, @@ -765,7 +835,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if sizeLevel >= 2 { // Workaround with roughly the same effect as // https://reviews.llvm.org/D119342. - // Can hopefully be removed in LLVM 18. + // Can hopefully be removed in LLVM 19. ldflags = append(ldflags, "-mllvm", "--rotation-max-header-size=0") } @@ -774,7 +844,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } err = link(config.Target.Linker, ldflags...) if err != nil { - return &commandError{"failed to link", result.Executable, err} + return err } var calculatedStacks []string @@ -798,6 +868,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return fmt.Errorf("could not modify stack sizes: %w", err) } } + + // Apply patches of bootloader in the order they appear. + if len(config.Target.BootPatches) > 0 { + err = applyPatches(result.Executable, config.Target.BootPatches) + } + if config.RP2040BootPatch() { // Patch the second stage bootloader CRC into the .boot2 section err = patchRP2040BootCRC(result.Executable) @@ -817,14 +893,20 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe args = append(args, "--asyncify") } + inputFile := result.Binary + result.Binary = result.Executable + ".wasmopt" args = append(args, opt, "-g", - result.Executable, - "--output", result.Executable, + inputFile, + "--output", result.Binary, ) - cmd := exec.Command(goenv.Get("WASMOPT"), args...) + wasmopt := goenv.Get("WASMOPT") + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(wasmopt, args...) + } + cmd := exec.Command(wasmopt, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -834,20 +916,77 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } } - // Print code size if requested. - if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { - packagePathMap := make(map[string]string, len(lprogram.Packages)) - for _, pkg := range lprogram.Sorted() { - packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() + // Run wasm-tools for component-model binaries + witPackage := strings.ReplaceAll(config.Target.WITPackage, "{root}", goenv.Get("TINYGOROOT")) + if config.Options.WITPackage != "" { + witPackage = config.Options.WITPackage + } + witWorld := config.Target.WITWorld + if config.Options.WITWorld != "" { + witWorld = config.Options.WITWorld + } + if witPackage != "" && witWorld != "" { + + // wasm-tools component embed -w wasi:cli/command + // $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm + componentEmbedInputFile := result.Binary + result.Binary = result.Executable + ".wasm-component-embed" + args := []string{ + "component", + "embed", + "-w", witWorld, + witPackage, + componentEmbedInputFile, + "-o", result.Binary, } - sizes, err := loadProgramSize(result.Executable, packagePathMap) + + wasmtools := goenv.Get("WASMTOOLS") + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(wasmtools, args...) + } + cmd := exec.Command(wasmtools, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err := cmd.Run() + if err != nil { + return fmt.Errorf("`wasm-tools component embed` failed: %w", err) + } + + // wasm-tools component new embedded.wasm -o component.wasm + componentNewInputFile := result.Binary + result.Binary = result.Executable + ".wasm-component-new" + args = []string{ + "component", + "new", + componentNewInputFile, + "-o", result.Binary, + } + + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(wasmtools, args...) + } + cmd = exec.Command(wasmtools, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() + if err != nil { + return fmt.Errorf("`wasm-tools component new` failed: %w", err) + } + } + + // Print code size if requested. + if config.Options.PrintSizes != "" { + sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) if err != nil { return err } - if config.Options.PrintSizes == "short" { + switch config.Options.PrintSizes { + case "short": fmt.Printf(" code data bss | flash ram\n") fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM()) - } else { + case "full": if !config.Debug() { fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail") } @@ -859,6 +998,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } fmt.Printf("------------------------------- | --------------- | -------\n") fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS) + case "html": + const filename = "size-report.html" + err := writeSizeReport(sizes, filename, pkgName) + if err != nil { + return err + } + fmt.Println("Wrote size report to", filename) } } @@ -1030,7 +1176,7 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c // optimizeProgram runs a series of optimizations and transformations that are // needed to convert a program to its final form. Some transformations are not // optional and must be run as the compiler expects them to run. -func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error { +func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) if err != nil { return err @@ -1048,17 +1194,11 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m } } - // Insert values from -ldflags="-X ..." into the IR. - err = setGlobalValues(mod, globalValues) - if err != nil { - return err - } - // Run most of the whole-program optimizations (including the whole // O0/O1/O2/Os/Oz optimization pipeline). errs := transform.Optimize(mod, config) if len(errs) > 0 { - return newMultiError(errs) + return newMultiError(errs, "") } if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { return errors.New("verification failure after LLVM optimization passes") @@ -1067,10 +1207,19 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m return nil } -// setGlobalValues sets the global values from the -ldflags="-X ..." compiler -// option in the given module. An error may be returned if the global is not of -// the expected type. -func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error { +func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, machine llvm.TargetMachine) llvm.Module { + mod := ctx.NewModule("cmdline-globals") + targetData := machine.CreateTargetData() + defer targetData.Dispose() + mod.SetDataLayout(targetData.String()) + + stringType := ctx.StructCreateNamed("runtime._string") + uintptrType := ctx.IntType(targetData.PointerSize() * 8) + stringType.StructSetBody([]llvm.Type{ + llvm.PointerType(ctx.Int8Type(), 0), + uintptrType, + }, false) + var pkgPaths []string for pkgPath := range globals { pkgPaths = append(pkgPaths, pkgPath) @@ -1086,24 +1235,6 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro for _, name := range names { value := pkg[name] globalName := pkgPath + "." + name - global := mod.NamedGlobal(globalName) - if global.IsNil() || !global.Initializer().IsNil() { - // The global either does not exist (optimized away?) or has - // some value, in which case it has already been initialized at - // package init time. - continue - } - - // A strin is a {ptr, len} pair. We need these types to build the - // initializer. - initializerType := global.GlobalValueType() - if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" { - return fmt.Errorf("%s: not a string", globalName) - } - elementTypes := initializerType.StructElementTypes() - if len(elementTypes) != 2 { - return fmt.Errorf("%s: not a string", globalName) - } // Create a buffer for the string contents. bufInitializer := mod.Context().ConstString(value, false) @@ -1114,22 +1245,20 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro buf.SetLinkage(llvm.PrivateLinkage) // Create the string value, which is a {ptr, len} pair. - zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false) - ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero}) - if ptr.Type() != elementTypes[0] { - return fmt.Errorf("%s: not a string", globalName) - } - length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false) - initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{ - ptr, + length := llvm.ConstInt(uintptrType, uint64(len(value)), false) + initializer := llvm.ConstNamedStruct(stringType, []llvm.Value{ + buf, length, }) - // Set the initializer. No initializer should be set at this point. + // Create the string global. + global := llvm.AddGlobal(mod, stringType, globalName) global.SetInitializer(initializer) + global.SetAlignment(targetData.PrefTypeAlignment(stringType)) } } - return nil + + return mod } // functionStackSizes keeps stack size information about a single function @@ -1185,7 +1314,7 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri } // Goroutines need to be started and finished and take up some stack space - // that way. This can be measured by measuing the stack size of + // that way. This can be measured by measuring the stack size of // tinygo_startTask. if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 { return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs) @@ -1348,6 +1477,23 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS } } +func applyPatches(executable string, bootPatches []string) (err error) { + for _, patch := range bootPatches { + switch patch { + case "rp2040": + err = patchRP2040BootCRC(executable) + // case "rp2350": + // err = patchRP2350BootIMAGE_DEF(executable) + default: + err = errors.New("undefined boot patch name") + } + if err != nil { + return fmt.Errorf("apply boot patch %q: %w", patch, err) + } + } + return nil +} + // RP2040 second stage bootloader CRC32 calculation // // Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf @@ -1359,7 +1505,7 @@ func patchRP2040BootCRC(executable string) error { } if len(bytes) != 256 { - return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes") + return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes)) } // From the 'official' RP2040 checksum script: @@ -1398,3 +1544,10 @@ func lock(path string) func() { return func() { flock.Close() } } + +func b2u8(b bool) uint8 { + if b { + return 1 + } + return 0 +} diff --git a/builder/builder_test.go b/builder/builder_test.go index 0ed632da3b..ccccef30ba 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -33,7 +33,9 @@ func TestClangAttributes(t *testing.T) { "k210", "nintendoswitch", "riscv-qemu", - "wasi", + "tkey", + "wasip1", + "wasip2", "wasm", "wasm-unknown", } @@ -52,20 +54,29 @@ func TestClangAttributes(t *testing.T) { for _, options := range []*compileopts.Options{ {GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "amd64"}, - {GOOS: "linux", GOARCH: "arm", GOARM: "5"}, - {GOOS: "linux", GOARCH: "arm", GOARM: "6"}, - {GOOS: "linux", GOARCH: "arm", GOARM: "7"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "5,softfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "6,softfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "7,softfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "5,hardfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "6,hardfloat"}, + {GOOS: "linux", GOARCH: "arm", GOARM: "7,hardfloat"}, {GOOS: "linux", GOARCH: "arm64"}, + {GOOS: "linux", GOARCH: "mips", GOMIPS: "hardfloat"}, + {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "hardfloat"}, + {GOOS: "linux", GOARCH: "mips", GOMIPS: "softfloat"}, + {GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"}, {GOOS: "darwin", GOARCH: "amd64"}, {GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "arm64"}, - {GOOS: "wasip1", GOARCH: "wasm"}, } { name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH if options.GOARCH == "arm" { name += ",GOARM=" + options.GOARM } + if options.GOARCH == "mips" || options.GOARCH == "mipsle" { + name += ",GOMIPS=" + options.GOMIPS + } t.Run(name, func(t *testing.T) { testClangAttributes(t, options) }) diff --git a/builder/builtins.go b/builder/builtins.go index a1066b6714..b493b6680a 100644 --- a/builder/builtins.go +++ b/builder/builtins.go @@ -3,19 +3,19 @@ package builder import ( "os" "path/filepath" - "strings" + "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" ) -// These are the GENERIC_SOURCES according to CMakeList.txt. +// These are the GENERIC_SOURCES according to CMakeList.txt except for +// divmodsi4.c and udivmodsi4.c. var genericBuiltins = []string{ "absvdi2.c", "absvsi2.c", "absvti2.c", "adddf3.c", "addsf3.c", - "addtf3.c", "addvdi3.c", "addvsi3.c", "addvti3.c", @@ -40,12 +40,12 @@ var genericBuiltins = []string{ "divdf3.c", "divdi3.c", "divmoddi4.c", + //"divmodsi4.c", + "divmodti4.c", "divsc3.c", "divsf3.c", "divsi3.c", - "divtc3.c", "divti3.c", - "divtf3.c", "extendsfdf2.c", "extendhfsf2.c", "ffsdi2.c", @@ -91,7 +91,6 @@ var genericBuiltins = []string{ "mulsc3.c", "mulsf3.c", "multi3.c", - "multf3.c", "mulvdi3.c", "mulvsi3.c", "mulvti3.c", @@ -111,13 +110,11 @@ var genericBuiltins = []string{ "popcountti2.c", "powidf2.c", "powisf2.c", - "powitf2.c", "subdf3.c", "subsf3.c", "subvdi3.c", "subvsi3.c", "subvti3.c", - "subtf3.c", "trampoline_setup.c", "truncdfhf2.c", "truncdfsf2.c", @@ -126,6 +123,7 @@ var genericBuiltins = []string{ "ucmpti2.c", "udivdi3.c", "udivmoddi4.c", + //"udivmodsi4.c", "udivmodti4.c", "udivsi3.c", "udivti3.c", @@ -134,6 +132,38 @@ var genericBuiltins = []string{ "umodti3.c", } +// These are the GENERIC_TF_SOURCES as of LLVM 18. +// They are not needed on all platforms (32-bit platforms usually don't need +// these) but they seem to compile fine so it's easier to include them. +var genericBuiltins128 = []string{ + "addtf3.c", + "comparetf2.c", + "divtc3.c", + "divtf3.c", + "extenddftf2.c", + "extendhftf2.c", + "extendsftf2.c", + "fixtfdi.c", + "fixtfsi.c", + "fixtfti.c", + "fixunstfdi.c", + "fixunstfsi.c", + "fixunstfti.c", + "floatditf.c", + "floatsitf.c", + "floattitf.c", + "floatunditf.c", + "floatunsitf.c", + "floatuntitf.c", + "multc3.c", + "multf3.c", + "powitf2.c", + "subtf3.c", + "trunctfdf2.c", + "trunctfhf2.c", + "trunctfsf2.c", +} + var aeabiBuiltins = []string{ "arm/aeabi_cdcmp.S", "arm/aeabi_cdcmpeq_check_nan.c", @@ -171,12 +201,12 @@ var avrBuiltins = []string{ "avr/udivmodqi4.S", } -// CompilerRT is a library with symbols required by programs compiled with LLVM. -// These symbols are for operations that cannot be emitted with a single +// libCompilerRT is a library with symbols required by programs compiled with +// LLVM. These symbols are for operations that cannot be emitted with a single // instruction or a short sequence of instructions for that target. // // For more information, see: https://compiler-rt.llvm.org/ -var CompilerRT = Library{ +var libCompilerRT = Library{ name: "compiler-rt", cflags: func(target, headerPath string) []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} @@ -192,11 +222,13 @@ var CompilerRT = Library{ }, librarySources: func(target string) ([]string, error) { builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins - if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { + switch compileopts.CanonicalArchName(target) { + case "arm": builtins = append(builtins, aeabiBuiltins...) - } - if strings.HasPrefix(target, "avr") { + case "avr": builtins = append(builtins, avrBuiltins...) + case "x86_64", "aarch64", "riscv64": // any 64-bit arch + builtins = append(builtins, genericBuiltins128...) } return builtins, nil }, diff --git a/builder/cc1as.cpp b/builder/cc1as.cpp index 9f9e377fac..2cbb7b9f68 100644 --- a/builder/cc1as.cpp +++ b/builder/cc1as.cpp @@ -82,10 +82,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, // Parse the arguments. const OptTable &OptTbl = getDriverOptTable(); - const unsigned IncludedFlagsBitmask = options::CC1AsOption; + llvm::opt::Visibility VisibilityMask(options::CC1AsOption); unsigned MissingArgIndex, MissingArgCount; - InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, - IncludedFlagsBitmask); + InputArgList Args = + OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask); // Check for missing argument error. if (MissingArgCount) { @@ -98,7 +98,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, for (const Arg *A : Args.filtered(OPT_UNKNOWN)) { auto ArgString = A->getAsString(Args); std::string Nearest; - if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1) + if (OptTbl.findNearest(ArgString, Nearest, VisibilityMask) > 1) Diags.Report(diag::err_drv_unknown_argument) << ArgString; else Diags.Report(diag::err_drv_unknown_argument_with_suggestion) @@ -145,6 +145,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, } Opts.RelaxELFRelocations = !Args.hasArg(OPT_mrelax_relocations_no); + Opts.SSE2AVX = Args.hasArg(OPT_msse2avx); if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32)) Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64); Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags); @@ -234,6 +235,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, Opts.EmitCompactUnwindNonCanonical = Args.hasArg(OPT_femit_compact_unwind_non_canonical); + Opts.Crel = Args.hasArg(OPT_crel); Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file); @@ -287,8 +289,14 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, assert(MRI && "Unable to create target register info!"); MCTargetOptions MCOptions; + MCOptions.MCRelaxAll = Opts.RelaxAll; MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind; MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical; + MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels; + MCOptions.Crel = Opts.Crel; + MCOptions.X86RelaxRelocations = Opts.RelaxELFRelocations; + MCOptions.X86Sse2Avx = Opts.SSE2AVX; + MCOptions.CompressDebugSections = Opts.CompressDebugSections; MCOptions.AsSecureLogFile = Opts.AsSecureLogFile; std::unique_ptr MAI( @@ -297,9 +305,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, // Ensure MCAsmInfo initialization occurs before any use, otherwise sections // may be created with a combination of default and explicit settings. - MAI->setCompressDebugSections(Opts.CompressDebugSections); - MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations); bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj; if (Opts.OutputPath.empty()) @@ -343,8 +349,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, MOFI->setDarwinTargetVariantSDKVersion(Opts.DarwinTargetVariantSDKVersion); Ctx.setObjectFileInfo(MOFI.get()); - if (Opts.SaveTemporaryLabels) - Ctx.setAllowTemporaryLabels(false); if (Opts.GenDwarfForAssembly) Ctx.setGenDwarfForAssembly(true); if (!Opts.DwarfDebugFlags.empty()) @@ -381,6 +385,9 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, MCOptions.MCNoWarn = Opts.NoWarn; MCOptions.MCFatalWarnings = Opts.FatalWarnings; MCOptions.MCNoTypeCheck = Opts.NoTypeCheck; + MCOptions.ShowMCInst = Opts.ShowInst; + MCOptions.AsmVerbose = true; + MCOptions.MCUseDwarfDirectory = MCTargetOptions::EnableDwarfDirectory; MCOptions.ABIName = Opts.TargetABI; // FIXME: There is a bit of code duplication with addPassesToEmitFile. @@ -395,10 +402,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); auto FOut = std::make_unique(*Out); - Str.reset(TheTarget->createAsmStreamer( - Ctx, std::move(FOut), /*asmverbose*/ true, - /*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB), - Opts.ShowInst)); + Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), IP, + std::move(CE), std::move(MAB))); } else if (Opts.OutputType == AssemblerInvocation::FT_Null) { Str.reset(createNullStreamer(Ctx)); } else { @@ -421,9 +426,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, Triple T(Opts.Triple); Str.reset(TheTarget->createMCObjectStreamer( - T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI, - Opts.RelaxAll, Opts.IncrementalLinkerCompatible, - /*DWARFMustBeAtTheEnd*/ true)); + T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI)); Str.get()->initSections(Opts.NoExecStack, *STI); } @@ -436,9 +439,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, Str.get()->emitZeros(1); } - // Assembly to object compilation should leverage assembly info. - Str->setUseAssemblerInfoForParsing(true); - bool Failed = false; std::unique_ptr Parser( @@ -521,9 +521,10 @@ int cc1as_main(ArrayRef Argv, const char *Argv0, void *MainAddr) { if (Asm.ShowHelp) { getDriverOptTable().printHelp( llvm::outs(), "clang -cc1as [options] file...", - "Clang Integrated Assembler", - /*Include=*/driver::options::CC1AsOption, /*Exclude=*/0, - /*ShowAllAliases=*/false); + "Clang Integrated Assembler", /*ShowHidden=*/false, + /*ShowAllAliases=*/false, + llvm::opt::Visibility(driver::options::CC1AsOption)); + return 0; } diff --git a/builder/cc1as.h b/builder/cc1as.h index 4b22fc3e81..cc973fd88a 100644 --- a/builder/cc1as.h +++ b/builder/cc1as.h @@ -38,10 +38,17 @@ struct AssemblerInvocation { /// @{ std::vector IncludePaths; + LLVM_PREFERRED_TYPE(bool) unsigned NoInitialTextSection : 1; + LLVM_PREFERRED_TYPE(bool) unsigned SaveTemporaryLabels : 1; + LLVM_PREFERRED_TYPE(bool) unsigned GenDwarfForAssembly : 1; + LLVM_PREFERRED_TYPE(bool) unsigned RelaxELFRelocations : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned SSE2AVX : 1; + LLVM_PREFERRED_TYPE(bool) unsigned Dwarf64 : 1; unsigned DwarfVersion; std::string DwarfDebugFlags; @@ -66,7 +73,9 @@ struct AssemblerInvocation { FT_Obj ///< Object file output. }; FileType OutputType; + LLVM_PREFERRED_TYPE(bool) unsigned ShowHelp : 1; + LLVM_PREFERRED_TYPE(bool) unsigned ShowVersion : 1; /// @} @@ -74,19 +83,28 @@ struct AssemblerInvocation { /// @{ unsigned OutputAsmVariant; + LLVM_PREFERRED_TYPE(bool) unsigned ShowEncoding : 1; + LLVM_PREFERRED_TYPE(bool) unsigned ShowInst : 1; /// @} /// @name Assembler Options /// @{ + LLVM_PREFERRED_TYPE(bool) unsigned RelaxAll : 1; + LLVM_PREFERRED_TYPE(bool) unsigned NoExecStack : 1; + LLVM_PREFERRED_TYPE(bool) unsigned FatalWarnings : 1; + LLVM_PREFERRED_TYPE(bool) unsigned NoWarn : 1; + LLVM_PREFERRED_TYPE(bool) unsigned NoTypeCheck : 1; + LLVM_PREFERRED_TYPE(bool) unsigned IncrementalLinkerCompatible : 1; + LLVM_PREFERRED_TYPE(bool) unsigned EmbedBitcode : 1; /// Whether to emit DWARF unwind info. @@ -94,8 +112,12 @@ struct AssemblerInvocation { // Whether to emit compact-unwind for non-canonical entries. // Note: maybe overriden by other constraints. + LLVM_PREFERRED_TYPE(bool) unsigned EmitCompactUnwindNonCanonical : 1; + LLVM_PREFERRED_TYPE(bool) + unsigned Crel : 1; + /// The name of the relocation model to use. std::string RelocationModel; @@ -126,6 +148,7 @@ struct AssemblerInvocation { ShowInst = 0; ShowEncoding = 0; RelaxAll = 0; + SSE2AVX = 0; NoExecStack = 0; FatalWarnings = 0; NoWarn = 0; @@ -136,6 +159,7 @@ struct AssemblerInvocation { EmbedBitcode = 0; EmitDwarfUnwind = EmitDwarfUnwindType::Default; EmitCompactUnwindNonCanonical = false; + Crel = false; } static bool CreateFromArgs(AssemblerInvocation &Res, diff --git a/builder/commands.go b/builder/commands.go index 932e9aceaf..d804ee1476 100644 --- a/builder/commands.go +++ b/builder/commands.go @@ -3,7 +3,6 @@ package builder import ( "errors" "fmt" - "os" "os/exec" "runtime" "strings" @@ -76,14 +75,3 @@ func LookupCommand(name string) (string, error) { } return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " ")) } - -func execCommand(name string, args ...string) error { - name, err := LookupCommand(name) - if err != nil { - return err - } - cmd := exec.Command(name, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} diff --git a/builder/config.go b/builder/config.go index d6d1a38387..b36b9333f3 100644 --- a/builder/config.go +++ b/builder/config.go @@ -2,6 +2,7 @@ package builder import ( "fmt" + "runtime" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" @@ -23,20 +24,37 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { spec.OpenOCDCommands = options.OpenOCDCommands } - major, minor, err := goenv.GetGorootVersion() + // Version range supported by TinyGo. + const minorMin = 19 + const minorMax = 24 + + // Check that we support this Go toolchain version. + gorootMajor, gorootMinor, err := goenv.GetGorootVersion() if err != nil { return nil, err } - if major != 1 || minor < 18 || minor > 22 { + if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax { // Note: when this gets updated, also update the Go compatibility matrix: // https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md - return nil, fmt.Errorf("requires go version 1.18 through 1.22, got go%d.%d", major, minor) + return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor) + } + + // Check that the Go toolchain version isn't too new, if we haven't been + // compiled with the latest Go version. + // This may be a bit too aggressive: if the newer version doesn't change the + // Go language we will most likely be able to compile it. + buildMajor, buildMinor, _, err := goenv.Parse(runtime.Version()) + if err != nil { + return nil, err + } + if buildMajor != 1 || buildMinor < gorootMinor { + return nil, fmt.Errorf("cannot compile with Go toolchain version go%d.%d (TinyGo was built using toolchain version %s)", gorootMajor, gorootMinor, runtime.Version()) } return &compileopts.Config{ Options: options, Target: spec, - GoMinorVersion: minor, + GoMinorVersion: gorootMinor, TestConfig: options.TestConfig, }, nil } diff --git a/builder/error.go b/builder/error.go index 0f920a0315..fe1a2d422e 100644 --- a/builder/error.go +++ b/builder/error.go @@ -3,7 +3,8 @@ package builder // MultiError is a list of multiple errors (actually: diagnostics) returned // during LLVM IR generation. type MultiError struct { - Errs []error + ImportPath string + Errs []error } func (e *MultiError) Error() string { @@ -14,15 +15,16 @@ func (e *MultiError) Error() string { // newMultiError returns a *MultiError if there is more than one error, or // returns that error directly when there is only one. Passing an empty slice -// will lead to a panic. -func newMultiError(errs []error) error { +// will return nil (because there is no error). +// The importPath may be passed if this error is for a single package. +func newMultiError(errs []error, importPath string) error { switch len(errs) { case 0: - panic("attempted to create empty MultiError") + return nil case 1: return errs[0] default: - return &MultiError{errs} + return &MultiError{importPath, errs} } } diff --git a/builder/jobs.go b/builder/jobs.go index a23d07534d..116887461e 100644 --- a/builder/jobs.go +++ b/builder/jobs.go @@ -17,14 +17,6 @@ import ( // concurrency or performance issues. const jobRunnerDebug = false -type jobState uint8 - -const ( - jobStateQueued jobState = iota // not yet running - jobStateRunning // running - jobStateFinished // finished running -) - // compileJob is a single compiler job, comparable to a single Makefile target. // It is used to orchestrate various compiler tasks that can be run in parallel // but that have dependencies and thus have limitations in how they can be run. @@ -55,12 +47,11 @@ func dummyCompileJob(result string) *compileJob { // ordered as such in the job dependencies. func runJobs(job *compileJob, sema chan struct{}) error { if sema == nil { - // Have a default, if the semaphore isn't set. This is useful for - // tests. + // Have a default, if the semaphore isn't set. This is useful for tests. sema = make(chan struct{}, runtime.NumCPU()) } if cap(sema) == 0 { - return errors.New("cannot 0 jobs at a time") + return errors.New("cannot run 0 jobs at a time") } // Create a slice of jobs to run, where all dependencies are run in order. @@ -81,10 +72,10 @@ func runJobs(job *compileJob, sema chan struct{}) error { waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs)) dependents := make(map[*compileJob][]*compileJob, len(jobs)) - jidx := make(map[*compileJob]int) + compileJobs := make(map[*compileJob]int) var ready intHeap for i, job := range jobs { - jidx[job] = i + compileJobs[job] = i if len(job.dependencies) == 0 { // This job is ready to run. ready.Push(i) @@ -105,8 +96,7 @@ func runJobs(job *compileJob, sema chan struct{}) error { // Create a channel to accept notifications of completion. doneChan := make(chan *compileJob) - // Send each job in the jobs slice to a worker, taking care of job - // dependencies. + // Send each job in the jobs slice to a worker, taking care of job dependencies. numRunningJobs := 0 var totalTime time.Duration start := time.Now() @@ -156,7 +146,7 @@ func runJobs(job *compileJob, sema chan struct{}) error { delete(wait, completed) if len(wait) == 0 { // This job is now ready to run. - ready.Push(jidx[j]) + ready.Push(compileJobs[j]) delete(waiting, j) } } diff --git a/builder/library.go b/builder/library.go index e0ac31a747..1b6afe2fcd 100644 --- a/builder/library.go +++ b/builder/library.go @@ -35,19 +35,6 @@ type Library struct { crt1Source string } -// Load the library archive, possibly generating and caching it if needed. -// The resulting directory may be stored in the provided tmpdir, which is -// expected to be removed after the Load call. -func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) { - job, unlock, err := l.load(config, tmpdir) - if err != nil { - return "", err - } - defer unlock() - err = runJobs(job, config.Options.Semaphore) - return filepath.Dir(job.result), err -} - // load returns a compile job to build this library file for the given target // and CPU. It may return a dummy compileJob if the library build is already // cached. The path is stored as job.result but is only valid after the job has @@ -56,7 +43,11 @@ func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, e // output archive file, it is expected to be removed after use. // As a side effect, this call creates the library header files if they didn't // exist yet. -func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) { +// The provided libc job (if not null) will cause this libc to be added as a +// dependency for all C compiler jobs, and adds libc headers for the given +// target config. In other words, pass this libc if the library needs a libc to +// compile. +func (l *Library) load(config *compileopts.Config, tmpdir string, libc *compileJob) (job *compileJob, abortLock func(), err error) { outdir, precompiled := config.LibcPath(l.name) archiveFilePath := filepath.Join(outdir, "lib.a") if precompiled { @@ -162,25 +153,40 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ if config.ABI() != "" { args = append(args, "-mabi="+config.ABI()) } - if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { + switch compileopts.CanonicalArchName(target) { + case "arm": if strings.Split(target, "-")[2] == "linux" { args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") } else { args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") } - } - if strings.HasPrefix(target, "avr") { + case "avr": // AVR defaults to C float and double both being 32-bit. This deviates // from what most code (and certainly compiler-rt) expects. So we need // to force the compiler to use 64-bit floating point numbers for // double. args = append(args, "-mdouble=64") - } - if strings.HasPrefix(target, "riscv32-") { + case "riscv32": args = append(args, "-march=rv32imac", "-fforce-enable-int128") - } - if strings.HasPrefix(target, "riscv64-") { + case "riscv64": args = append(args, "-march=rv64gc") + case "mips": + args = append(args, "-fno-pic") + } + if config.Target.SoftFloat { + // Use softfloat instead of floating point instructions. This is + // supported on many architectures. + args = append(args, "-msoft-float") + } else { + if strings.HasPrefix(target, "armv5") { + // On ARMv5 we need to explicitly enable hardware floating point + // instructions: Clang appears to assume the hardware doesn't have a + // FPU otherwise. + args = append(args, "-mfpu=vfpv2") + } + } + if libc != nil { + args = append(args, config.LibcCFlags()...) } var once sync.Once @@ -234,7 +240,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ objpath := filepath.Join(dir, cleanpath+".o") os.MkdirAll(filepath.Dir(objpath), 0o777) objs = append(objs, objpath) - job.dependencies = append(job.dependencies, &compileJob{ + objfile := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string @@ -249,7 +255,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ } return nil }, - }) + } + if libc != nil { + objfile.dependencies = append(objfile.dependencies, libc) + } + job.dependencies = append(job.dependencies, objfile) } // Create crt1.o job, if needed. @@ -258,7 +268,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // won't make much of a difference in speed). if l.crt1Source != "" { srcpath := filepath.Join(sourceDir, l.crt1Source) - job.dependencies = append(job.dependencies, &compileJob{ + crt1Job := &compileJob{ description: "compile " + srcpath, run: func(*compileJob) error { var compileArgs []string @@ -278,7 +288,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ } return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o")) }, - }) + } + if libc != nil { + crt1Job.dependencies = append(crt1Job.dependencies, libc) + } + job.dependencies = append(job.dependencies, crt1Job) } ok = true diff --git a/builder/mingw-w64.go b/builder/mingw-w64.go index 1e7701d476..32cf58f531 100644 --- a/builder/mingw-w64.go +++ b/builder/mingw-w64.go @@ -10,7 +10,7 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -var MinGW = Library{ +var libMinGW = Library{ name: "mingw-w64", makeHeaders: func(target, includeDir string) error { // copy _mingw.h @@ -27,14 +27,30 @@ var MinGW = Library{ _, err = io.Copy(outf, inf) return err }, - sourceDir: func() string { return "" }, // unused + sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") }, cflags: func(target, headerPath string) []string { - // No flags necessary because there are no files to compile. - return nil + mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") + return []string{ + "-nostdlibinc", + "-isystem", mingwDir + "/mingw-w64-headers/crt", + "-I", mingwDir + "/mingw-w64-headers/defaults/include", + "-I" + headerPath, + } }, librarySources: func(target string) ([]string, error) { - // We only use the UCRT DLL file. No source files necessary. - return nil, nil + // These files are needed so that printf and the like are supported. + sources := []string{ + "mingw-w64-crt/stdio/ucrt_fprintf.c", + "mingw-w64-crt/stdio/ucrt_fwprintf.c", + "mingw-w64-crt/stdio/ucrt_printf.c", + "mingw-w64-crt/stdio/ucrt_snprintf.c", + "mingw-w64-crt/stdio/ucrt_sprintf.c", + "mingw-w64-crt/stdio/ucrt_vfprintf.c", + "mingw-w64-crt/stdio/ucrt_vprintf.c", + "mingw-w64-crt/stdio/ucrt_vsnprintf.c", + "mingw-w64-crt/stdio/ucrt_vsprintf.c", + } + return sources, nil }, } diff --git a/builder/musl.go b/builder/musl.go index 9b5c52704e..dc03be46f7 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -12,7 +12,7 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -var Musl = Library{ +var libMusl = Library{ name: "musl", makeHeaders: func(target, includeDir string) error { bits := filepath.Join(includeDir, "bits") @@ -92,6 +92,8 @@ var Musl = Library{ "-Wno-ignored-pragmas", "-Wno-tautological-constant-out-of-range-compare", "-Wno-deprecated-non-prototype", + "-Wno-format", + "-Wno-parentheses", "-Qunused-arguments", // Select include dirs. Don't include standard library includes // (that would introduce host dependencies and other complications), @@ -111,32 +113,51 @@ var Musl = Library{ librarySources: func(target string) ([]string, error) { arch := compileopts.MuslArchitecture(target) globs := []string{ + "ctype/*.c", "env/*.c", "errno/*.c", "exit/*.c", + "fcntl/*.c", "internal/defsysinfo.c", + "internal/intscan.c", "internal/libc.c", + "internal/shgetc.c", "internal/syscall_ret.c", "internal/vdso.c", "legacy/*.c", + "locale/*.c", "linux/*.c", + "locale/*.c", "malloc/*.c", "malloc/mallocng/*.c", "mman/*.c", "math/*.c", + "misc/*.c", + "multibyte/*.c", + "sched/*.c", + "signal/" + arch + "/*.s", "signal/*.c", "stdio/*.c", + "stdlib/*.c", "string/*.c", "thread/" + arch + "/*.s", "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/" diff --git a/builder/picolibc.go b/builder/picolibc.go index ab49ba5ad1..9026b99ee4 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -8,9 +8,9 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) -// Picolibc is a C library for bare metal embedded devices. It was originally +// libPicolibc is a C library for bare metal embedded devices. It was originally // based on newlib. -var Picolibc = Library{ +var libPicolibc = Library{ name: "picolibc", makeHeaders: func(target, includeDir string) error { f, err := os.Create(filepath.Join(includeDir, "picolibc.h")) @@ -29,10 +29,12 @@ var Picolibc = Library{ "-D_HAVE_ALIAS_ATTRIBUTE", "-DTINY_STDIO", "-DPOSIX_IO", + "-DFORMAT_DEFAULT_INTEGER", // use __i_vfprintf and __i_vfscanf by default "-D_IEEE_LIBM", "-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU "-D__OBSOLETE_MATH_DOUBLE=0", "-D_WANT_IO_C99_FORMATS", + "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", "-nostdlibinc", "-isystem", newlibDir + "/libc/include", "-I" + newlibDir + "/libc/tinystdio", diff --git a/builder/size-report.go b/builder/size-report.go new file mode 100644 index 0000000000..d826f30274 --- /dev/null +++ b/builder/size-report.go @@ -0,0 +1,56 @@ +package builder + +import ( + _ "embed" + "fmt" + "html/template" + "os" +) + +//go:embed size-report.html +var sizeReportBase string + +func writeSizeReport(sizes *programSize, filename, pkgName string) error { + tmpl, err := template.New("report").Parse(sizeReportBase) + if err != nil { + return err + } + + f, err := os.Create(filename) + if err != nil { + return fmt.Errorf("could not open report file: %w", err) + } + defer f.Close() + + // Prepare data for the report. + type sizeLine struct { + Name string + Size *packageSize + } + programData := []sizeLine{} + for _, name := range sizes.sortedPackageNames() { + pkgSize := sizes.Packages[name] + programData = append(programData, sizeLine{ + Name: name, + Size: pkgSize, + }) + } + sizeTotal := map[string]uint64{ + "code": sizes.Code, + "rodata": sizes.ROData, + "data": sizes.Data, + "bss": sizes.BSS, + "flash": sizes.Flash(), + } + + // Write the report. + err = tmpl.Execute(f, map[string]any{ + "pkgName": pkgName, + "sizes": programData, + "sizeTotal": sizeTotal, + }) + if err != nil { + return fmt.Errorf("could not create report file: %w", err) + } + return nil +} diff --git a/builder/size-report.html b/builder/size-report.html new file mode 100644 index 0000000000..2afb5c43d1 --- /dev/null +++ b/builder/size-report.html @@ -0,0 +1,109 @@ + + + + Codestin Search App + + + + + + +
+

Size Report for {{.pkgName}}

+ +

How much space is used by Go packages, C libraries, and other bits to set up the program environment.

+ +
    +
  • Code is the actual program code (machine code instructions).
  • +
  • Read-only data are read-only global variables. On most microcontrollers, these are stored in flash and do not take up any RAM.
  • +
  • Data are writable global variables with a non-zero initializer. On microcontrollers, they are copied from flash to RAM on reset.
  • +
  • BSS are writable global variables that are zero initialized. They do not take up any space in the binary, but do take up RAM. On microcontrollers, this area is zeroed on reset.
  • +
+ +

The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using -no-debug strips most of those.

+ +

Program breakdown

+ +

You can click on the rows below to see which files contribute to the binary size.

+ +
+ + + + + + + + + + + + + {{range $i, $pkg := .sizes}} + + + + + + + + + {{range $filename, $sizes := .Size.Sub}} + + + + + + + + + {{end}} + {{end}} + + + + + + + + + + + +
PackageCodeRead-only dataDataBSSBinary size
{{.Name}}{{.Size.Code}}{{.Size.ROData}}{{.Size.Data}}{{.Size.BSS}} + {{.Size.Flash}} +
+ {{if eq $filename ""}} + (unknown file) + {{else}} + {{$filename}} + {{end}} + {{$sizes.Code}}{{$sizes.ROData}}{{$sizes.Data}}{{$sizes.BSS}} + {{$sizes.Flash}} +
Total{{.sizeTotal.code}}{{.sizeTotal.rodata}}{{.sizeTotal.data}}{{.sizeTotal.bss}}{{.sizeTotal.flash}}
+
+
+ + + diff --git a/builder/sizes.go b/builder/sizes.go index caa3ca33f4..485a652d97 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -12,6 +12,7 @@ import ( "os" "path/filepath" "regexp" + "runtime" "sort" "strings" @@ -24,7 +25,7 @@ const sizesDebug = false // programSize contains size statistics per package of a compiled program. type programSize struct { - Packages map[string]packageSize + Packages map[string]*packageSize Code uint64 ROData uint64 Data uint64 @@ -52,13 +53,29 @@ func (ps *programSize) RAM() uint64 { return ps.Data + ps.BSS } +// Return the package size information for a given package path, creating it if +// it doesn't exist yet. +func (ps *programSize) getPackage(path string) *packageSize { + if field, ok := ps.Packages[path]; ok { + return field + } + field := &packageSize{ + Program: ps, + Sub: map[string]*packageSize{}, + } + ps.Packages[path] = field + return field +} + // packageSize contains the size of a package, calculated from the linked object // file. type packageSize struct { - Code uint64 - ROData uint64 - Data uint64 - BSS uint64 + Program *programSize + Code uint64 + ROData uint64 + Data uint64 + BSS uint64 + Sub map[string]*packageSize } // Flash usage in regular microcontrollers. @@ -71,6 +88,31 @@ func (ps *packageSize) RAM() uint64 { return ps.Data + ps.BSS } +// Flash usage in regular microcontrollers, as a percentage of the total flash +// usage of the program. +func (ps *packageSize) FlashPercent() float64 { + return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100 +} + +// Add a single size data point to this package. +// This must only be called while calculating package size, not afterwards. +func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) { + if size == 0 { + return + } + + // Add size for the package. + *getField(ps, isVariable) += size + + // Add size for file inside package. + sub, ok := ps.Sub[filename] + if !ok { + sub = &packageSize{Program: ps.Program} + ps.Sub[filename] = sub + } + *getField(sub, isVariable) += size +} + // A mapping of a single chunk of code or data to a file path. type addressLine struct { Address uint64 @@ -194,11 +236,22 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64 if !prevLineEntry.EndSequence { // The chunk describes the code from prevLineEntry to // lineEntry. + path := prevLineEntry.File.Name + if runtime.GOOS == "windows" { + // Work around a Clang bug on Windows: + // https://github.com/llvm/llvm-project/issues/117317 + path = strings.ReplaceAll(path, "\\\\", "\\") + + // wasi-libc likes to use forward slashes, but we + // canonicalize everything to use backwards slashes as + // is common on Windows. + path = strings.ReplaceAll(path, "/", "\\") + } line := addressLine{ Address: prevLineEntry.Address + codeOffset, Length: lineEntry.Address - prevLineEntry.Address, Align: codeAlignment, - File: prevLineEntry.File.Name, + File: path, } if line.Length != 0 { addresses = append(addresses, line) @@ -773,49 +826,40 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz // Now finally determine the binary/RAM size usage per package by going // through each allocated section. - sizes := make(map[string]packageSize) + sizes := make(map[string]*packageSize) + program := &programSize{ + Packages: sizes, + } for _, section := range sections { switch section.Type { case memoryCode: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { if isVariable { - field.ROData += size - } else { - field.Code += size + return &ps.ROData } - sizes[path] = field + return &ps.Code }, packagePathMap) case memoryROData: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] - field.ROData += size - sizes[path] = field + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { + return &ps.ROData }, packagePathMap) case memoryData: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] - field.Data += size - sizes[path] = field + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { + return &ps.Data }, packagePathMap) case memoryBSS: - readSection(section, addresses, func(path string, size uint64, isVariable bool) { - field := sizes[path] - field.BSS += size - sizes[path] = field + readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { + return &ps.BSS }, packagePathMap) case memoryStack: // We store the C stack as a pseudo-package. - sizes["C stack"] = packageSize{ - BSS: section.Size, - } + program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 { + return &ps.BSS + }, "", section.Size, false) } } // ...and summarize the results. - program := &programSize{ - Packages: sizes, - } for _, pkg := range sizes { program.Code += pkg.Code program.ROData += pkg.ROData @@ -826,8 +870,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz } // readSection determines for each byte in this section to which package it -// belongs. It reports this usage through the addSize callback. -func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) { +// belongs. +func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) { // The addr variable tracks at which address we are while going through this // section. We start at the beginning. addr := section.Address @@ -849,9 +893,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st addrAligned := (addr + line.Align - 1) &^ (line.Align - 1) if line.Align > 1 && addrAligned >= line.Address { // It is, assume that's what causes the gap. - addSize("(padding)", line.Address-addr, true) + program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true) } else { - addSize("(unknown)", line.Address-addr, false) + program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false) if sizesDebug { fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align) } @@ -873,7 +917,8 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st length = line.Length - (addr - line.Address) } // Finally, mark this chunk of memory as used by the given package. - addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable) + packagePath, filename := findPackagePath(line.File, packagePathMap) + program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable) addr = line.Address + line.Length } if addr < sectionEnd { @@ -882,9 +927,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st if section.Align > 1 && addrAligned >= sectionEnd { // The gap is caused by the section alignment. // For example, if a .rodata section ends with a non-aligned string. - addSize("(padding)", sectionEnd-addr, true) + program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true) } else { - addSize("(unknown)", sectionEnd-addr, false) + program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false) if sizesDebug { fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align) } @@ -894,17 +939,25 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st // findPackagePath returns the Go package (or a pseudo package) for the given // path. It uses some heuristics, for example for some C libraries. -func findPackagePath(path string, packagePathMap map[string]string) string { +func findPackagePath(path string, packagePathMap map[string]string) (packagePath, filename string) { // Check whether this path is part of one of the compiled packages. packagePath, ok := packagePathMap[filepath.Dir(path)] - if !ok { + if ok { + // Directory is known as a Go package. + // Add the file itself as well. + filename = filepath.Base(path) + } else { if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) { // Emit C libraries (in the lib subdirectory of TinyGo) as a single - // package, with a "C" prefix. For example: "C compiler-rt" for the - // compiler runtime library from LLVM. - packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1] - } else if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project")) { + // package, with a "C" prefix. For example: "C picolibc" for the + // baremetal libc. + libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator)) + parts := strings.SplitN(libPath, string(os.PathSeparator), 2) + packagePath = "C " + parts[0] + filename = parts[1] + } else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) { packagePath = "C compiler-rt" + filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator)) } else if packageSymbolRegexp.MatchString(path) { // Parse symbol names like main$alloc or runtime$string. packagePath = path[:strings.LastIndex(path, "$")] @@ -927,9 +980,11 @@ func findPackagePath(path string, packagePathMap map[string]string) string { // fixed in the compiler. packagePath = "-" } else { - // This is some other path. Not sure what it is, so just emit its directory. - packagePath = filepath.Dir(path) // fallback + // This is some other path. Not sure what it is, so just emit its + // directory as a fallback. + packagePath = filepath.Dir(path) + filename = filepath.Base(path) } } - return packagePath + return } diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 429e3f3e98..a96ce9e6f6 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -1,6 +1,7 @@ package builder import ( + "regexp" "runtime" "testing" "time" @@ -41,9 +42,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", 4476, 280, 0, 2252}, - {"microbit", "examples/serial", 2724, 388, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 5996, 1484, 116, 6816}, + {"hifive1b", "examples/echo", 4560, 280, 0, 2268}, + {"microbit", "examples/serial", 2916, 388, 8, 2272}, + {"wioterminal", "examples/pininterrupt", 7359, 1489, 116, 6912}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the @@ -55,26 +56,7 @@ func TestBinarySize(t *testing.T) { t.Parallel() // Build the binary. - options := compileopts.Options{ - Target: tc.target, - Opt: "z", - Semaphore: sema, - InterpTimeout: 60 * time.Second, - Debug: true, - VerifyIR: true, - } - target, err := compileopts.LoadTarget(&options) - if err != nil { - t.Fatal("could not load target:", err) - } - config := &compileopts.Config{ - Options: &options, - Target: target, - } - result, err := Build(tc.path, "", t.TempDir(), config) - if err != nil { - t.Fatal("could not build:", err) - } + result := buildBinary(t, tc.target, tc.path) // Check whether the size of the binary matches the expected size. sizes, err := loadProgramSize(result.Executable, nil) @@ -90,3 +72,69 @@ func TestBinarySize(t *testing.T) { }) } } + +// Check that the -size=full flag attributes binary size to the correct package +// without filesystem paths and things like that. +func TestSizeFull(t *testing.T) { + tests := []string{ + "microbit", + "wasip1", + } + + libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector" + pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task" + + for _, target := range tests { + target := target + t.Run(target, func(t *testing.T) { + t.Parallel() + + // Build the binary. + result := buildBinary(t, target, "examples/serial") + + // Check whether the binary doesn't contain any unexpected package + // names. + sizes, err := loadProgramSize(result.Executable, result.PackagePathMap) + if err != nil { + t.Fatal("could not read program size:", err) + } + for _, pkg := range sizes.sortedPackageNames() { + if pkg == "(padding)" || pkg == "(unknown)" { + // TODO: correctly attribute all unknown binary size. + continue + } + if libMatch.MatchString(pkg) { + continue + } + if pkgMatch.MatchString(pkg) { + continue + } + t.Error("unexpected package name in size output:", pkg) + } + }) + } +} + +func buildBinary(t *testing.T, targetString, pkgName string) BuildResult { + options := compileopts.Options{ + Target: targetString, + Opt: "z", + Semaphore: sema, + InterpTimeout: 60 * time.Second, + Debug: true, + VerifyIR: true, + } + target, err := compileopts.LoadTarget(&options) + if err != nil { + t.Fatal("could not load target:", err) + } + config := &compileopts.Config{ + Options: &options, + Target: target, + } + result, err := Build(pkgName, "", t.TempDir(), config) + if err != nil { + t.Fatal("could not build:", err) + } + return result +} diff --git a/builder/tools.go b/builder/tools.go index a23714d604..9d15e4ccaa 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -1,44 +1,191 @@ package builder import ( + "bytes" + "fmt" + "go/scanner" + "go/token" "os" "os/exec" - - "github.com/tinygo-org/tinygo/goenv" + "regexp" + "strconv" + "strings" ) // runCCompiler invokes a C compiler with the given arguments. func runCCompiler(flags ...string) error { + // Find the right command to run Clang. + var cmd *exec.Cmd if hasBuiltinTools { // Compile this with the internal Clang compiler. - cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + cmd = exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) + } else { + // Compile this with an external invocation of the Clang compiler. + name, err := LookupCommand("clang") + if err != nil { + return err + } + cmd = exec.Command(name, flags...) + } + + cmd.Stdout = os.Stdout + 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. + 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 + } + } } - // Compile this with an external invocation of the Clang compiler. - return execCommand("clang", flags...) + return cmd.Run() } // link invokes a linker with the given name and flags. func link(linker string, flags ...string) error { - if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") { - // Run command with internal linker. - cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() + // We only support LLD. + if linker != "ld.lld" && linker != "wasm-ld" { + return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", linker) } - // Fall back to external command. - if _, ok := commands[linker]; ok { - return execCommand(linker, flags...) + var cmd *exec.Cmd + if hasBuiltinTools { + cmd = exec.Command(os.Args[0], append([]string{linker}, flags...)...) + } else { + name, err := LookupCommand(linker) + if err != nil { + return err + } + cmd = exec.Command(name, flags...) } - - cmd := exec.Command(linker, flags...) + var buf bytes.Buffer cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Dir = goenv.Get("TINYGOROOT") - return cmd.Run() + cmd.Stderr = &buf + err := cmd.Run() + if err != nil { + if buf.Len() == 0 { + // The linker failed but there was no output. + // Therefore, show some output anyway. + return fmt.Errorf("failed to run linker: %w", err) + } + return parseLLDErrors(buf.String()) + } + return nil +} + +// Split LLD errors into individual erros (including errors that continue on the +// next line, using a ">>>" prefix). If possible, replace the raw errors with a +// more user-friendly version (and one that's more in a Go style). +func parseLLDErrors(text string) error { + // Split linker output in separate error messages. + lines := strings.Split(text, "\n") + var errorLines []string // one or more line (belonging to a single error) per line + for _, line := range lines { + line = strings.TrimRight(line, "\r") // needed for Windows + if len(errorLines) != 0 && strings.HasPrefix(line, ">>> ") { + errorLines[len(errorLines)-1] += "\n" + line + continue + } + if line == "" { + continue + } + errorLines = append(errorLines, line) + } + + // Parse error messages. + var linkErrors []error + var flashOverflow, ramOverflow uint64 + for _, message := range errorLines { + parsedError := false + + // Check for undefined symbols. + // This can happen in some cases like with CGo and //go:linkname tricker. + if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil { + symbolName := matches[2] + for _, line := range strings.Split(message, "\n") { + matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line) + if matches != nil { + parsedError = true + line, _ := strconv.Atoi(matches[3]) + // TODO: detect common mistakes like -gc=none? + linkErrors = append(linkErrors, scanner.Error{ + Pos: token.Position{ + Filename: matches[2], + Line: line, + }, + Msg: "linker could not find symbol " + symbolName, + }) + } + } + } + + // Check for flash/RAM overflow. + if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil { + region := matches[3] + n, err := strconv.ParseUint(matches[4], 10, 64) + if err != nil { + // Should not happen at all (unless it overflows an uint64 for some reason). + continue + } + + // Check which area overflowed. + // Some chips use differently named memory areas, but these are by + // far the most common. + switch region { + case "FLASH_TEXT": + if n > flashOverflow { + flashOverflow = n + } + parsedError = true + case "RAM": + if n > ramOverflow { + ramOverflow = n + } + parsedError = true + } + } + + // If we couldn't parse the linker error: show the error as-is to + // the user. + if !parsedError { + linkErrors = append(linkErrors, LinkerError{message}) + } + } + + if flashOverflow > 0 { + linkErrors = append(linkErrors, LinkerError{ + Msg: fmt.Sprintf("program too large for this chip (flash overflowed by %d bytes)\n\toptimization guide: https://tinygo.org/docs/guides/optimizing-binaries/", flashOverflow), + }) + } + if ramOverflow > 0 { + linkErrors = append(linkErrors, LinkerError{ + Msg: fmt.Sprintf("program uses too much static RAM on this chip (RAM overflowed by %d bytes)", ramOverflow), + }) + } + + return newMultiError(linkErrors, "") +} + +// LLD linker error that could not be parsed or doesn't refer to a source +// location. +type LinkerError struct { + Msg string +} + +func (e LinkerError) Error() string { + return e.Msg } diff --git a/builder/wasmbuiltins.go b/builder/wasmbuiltins.go new file mode 100644 index 0000000000..4c158f2337 --- /dev/null +++ b/builder/wasmbuiltins.go @@ -0,0 +1,81 @@ +package builder + +import ( + "os" + "path/filepath" + + "github.com/tinygo-org/tinygo/goenv" +) + +var libWasmBuiltins = Library{ + name: "wasmbuiltins", + makeHeaders: func(target, includeDir string) error { + if err := os.Mkdir(includeDir+"/bits", 0o777); err != nil { + return err + } + f, err := os.Create(includeDir + "/bits/alltypes.h") + if err != nil { + return err + } + if _, err := f.Write([]byte(wasmAllTypes)); err != nil { + return err + } + return f.Close() + }, + cflags: func(target, headerPath string) []string { + libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") + return []string{ + "-Werror", + "-Wall", + "-std=gnu11", + "-nostdlibinc", + "-isystem", libcDir + "/libc-top-half/musl/arch/wasm32", + "-isystem", libcDir + "/libc-top-half/musl/arch/generic", + "-isystem", libcDir + "/libc-top-half/musl/src/internal", + "-isystem", libcDir + "/libc-top-half/musl/src/include", + "-isystem", libcDir + "/libc-top-half/musl/include", + "-isystem", libcDir + "/libc-bottom-half/headers/public", + "-I" + headerPath, + } + }, + sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") }, + librarySources: func(target string) ([]string, error) { + return []string{ + // memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and + // llvm.memset.* LLVM intrinsics. + "libc-top-half/musl/src/string/memcpy.c", + "libc-top-half/musl/src/string/memmove.c", + "libc-top-half/musl/src/string/memset.c", + + // exp, exp2, and log are needed for LLVM math builtin functions + // like llvm.exp.*. + "libc-top-half/musl/src/math/__math_divzero.c", + "libc-top-half/musl/src/math/__math_invalid.c", + "libc-top-half/musl/src/math/__math_oflow.c", + "libc-top-half/musl/src/math/__math_uflow.c", + "libc-top-half/musl/src/math/__math_xflow.c", + "libc-top-half/musl/src/math/exp.c", + "libc-top-half/musl/src/math/exp_data.c", + "libc-top-half/musl/src/math/exp2.c", + "libc-top-half/musl/src/math/log.c", + "libc-top-half/musl/src/math/log_data.c", + }, nil + }, +} + +// alltypes.h for wasm-libc, using the types as defined inside Clang. +const wasmAllTypes = ` +typedef __SIZE_TYPE__ size_t; +typedef __INT8_TYPE__ int8_t; +typedef __INT16_TYPE__ int16_t; +typedef __INT32_TYPE__ int32_t; +typedef __INT64_TYPE__ int64_t; +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; +typedef __UINTPTR_TYPE__ uintptr_t; + +// This type is used internally in wasi-libc. +typedef double double_t; +` diff --git a/cgo/cgo.go b/cgo/cgo.go index d5f1221200..6b8ea4373d 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -18,6 +18,7 @@ import ( "go/scanner" "go/token" "path/filepath" + "sort" "strconv" "strings" @@ -42,6 +43,7 @@ type cgoPackage struct { fset *token.FileSet tokenFiles map[string]*token.File definedGlobally map[string]ast.Node + noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines anonDecls map[interface{}]string cflags []string // CFlags from #cgo lines ldflags []string // LDFlags from #cgo lines @@ -80,21 +82,28 @@ type bitfieldInfo struct { endBit int64 // may be 0 meaning "until the end of the field" } +// Information about a #cgo noescape line in the source code. +type noescapingFunc struct { + name string + pos token.Pos + used bool // true if used somewhere in the source (for proper error reporting) +} + // cgoAliases list type aliases between Go and C, for types that are equivalent // in both languages. See addTypeAliases. var cgoAliases = map[string]string{ - "C.int8_t": "int8", - "C.int16_t": "int16", - "C.int32_t": "int32", - "C.int64_t": "int64", - "C.uint8_t": "uint8", - "C.uint16_t": "uint16", - "C.uint32_t": "uint32", - "C.uint64_t": "uint64", - "C.uintptr_t": "uintptr", - "C.float": "float32", - "C.double": "float64", - "C._Bool": "bool", + "_Cgo_int8_t": "int8", + "_Cgo_int16_t": "int16", + "_Cgo_int32_t": "int32", + "_Cgo_int64_t": "int64", + "_Cgo_uint8_t": "uint8", + "_Cgo_uint16_t": "uint16", + "_Cgo_uint32_t": "uint32", + "_Cgo_uint64_t": "uint64", + "_Cgo_uintptr_t": "uintptr", + "_Cgo_float": "float32", + "_Cgo_double": "float64", + "_Cgo__Bool": "bool", } // builtinAliases are handled specially because they only exist on the Go side @@ -136,31 +145,105 @@ typedef unsigned long long _Cgo_ulonglong; // The string/bytes functions below implement C.CString etc. To make sure the // runtime doesn't need to know the C int type, lengths are converted to uintptr // first. -// These functions will be modified to get a "C." prefix, so the source below -// doesn't reflect the final AST. -const generatedGoFilePrefix = ` +const generatedGoFilePrefixBase = ` +import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char + +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string + +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string + +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) +} + +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -//go:linkname C.GoString runtime.cgo_GoString -func GoString(*C.char) string +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func __GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -func GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func __GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr +` -func GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +const generatedGoFilePrefixOther = generatedGoFilePrefixBase + ` +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) +} +` + +// Windows uses fake errno values in the syscall package. +// See for example: https://github.com/golang/go/issues/23468 +// TinyGo uses mingw-w64 though, which does have defined errno values. Since the +// syscall package is the standard library one we can't change it, but we can +// map the errno values to match the values in the syscall package. +// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h +const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + ` +var _Cgo___errno_mapping = [...]syscall.Errno{ + 1: syscall.EPERM, + 2: syscall.ENOENT, + 3: syscall.ESRCH, + 4: syscall.EINTR, + 5: syscall.EIO, + 6: syscall.ENXIO, + 7: syscall.E2BIG, + 8: syscall.ENOEXEC, + 9: syscall.EBADF, + 10: syscall.ECHILD, + 11: syscall.EAGAIN, + 12: syscall.ENOMEM, + 13: syscall.EACCES, + 14: syscall.EFAULT, + 16: syscall.EBUSY, + 17: syscall.EEXIST, + 18: syscall.EXDEV, + 19: syscall.ENODEV, + 20: syscall.ENOTDIR, + 21: syscall.EISDIR, + 22: syscall.EINVAL, + 23: syscall.ENFILE, + 24: syscall.EMFILE, + 25: syscall.ENOTTY, + 27: syscall.EFBIG, + 28: syscall.ENOSPC, + 29: syscall.ESPIPE, + 30: syscall.EROFS, + 31: syscall.EMLINK, + 32: syscall.EPIPE, + 33: syscall.EDOM, + 34: syscall.ERANGE, + 36: syscall.EDEADLK, + 38: syscall.ENAMETOOLONG, + 39: syscall.ENOLCK, + 40: syscall.ENOSYS, + 41: syscall.ENOTEMPTY, + 42: syscall.EILSEQ, +} + +func _Cgo___get_errno() error { + num := _Cgo___get_errno_num() + if num < uintptr(len(_Cgo___errno_mapping)) { + if mapped := _Cgo___errno_mapping[num]; mapped != 0 { + return mapped + } + } + return syscall.Errno(num) } ` @@ -171,7 +254,7 @@ func GoBytes(ptr unsafe.Pointer, length C.int) []byte { // functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file // hashes of the accessed C header files. If there is one or more error, it // returns these in the []error slice but still modifies the AST. -func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) { +func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) { p := &cgoPackage{ packageName: files[0].Name.Name, currentDir: dir, @@ -179,6 +262,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl fset: fset, tokenFiles: map[string]*token.File{}, definedGlobally: map[string]ast.Node{}, + noescapingFuncs: map[string]*noescapingFunc{}, anonDecls: map[interface{}]string{}, visitedFiles: map[string][]byte{}, } @@ -203,7 +287,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl // Construct a new in-memory AST for CGo declarations of this package. // The first part is written as Go code that is then parsed, but more code // is added later to the AST to declare functions, globals, etc. - goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix + goCode := "package " + files[0].Name.Name + "\n\n" + if goos == "windows" { + goCode += generatedGoFilePrefixWindows + } else { + goCode += generatedGoFilePrefixOther + } p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments) if err != nil { // This is always a bug in the cgo package. @@ -213,23 +302,6 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl // If the Comments field is not set to nil, the go/format package will get // confused about where comments should go. p.generated.Comments = nil - // Adjust some of the functions in there. - for _, decl := range p.generated.Decls { - switch decl := decl.(type) { - case *ast.FuncDecl: - switch decl.Name.Name { - case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes": - // Adjust the name to have a "C." prefix so it is correctly - // resolved. - decl.Name.Name = "C." + decl.Name.Name - } - } - } - // Patch some types, for example *C.char in C.CString. - cf := p.newCGoFile(nil, -1) // dummy *cgoFile for the walker - astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool { - return cf.walker(cursor, nil) - }, nil) // Find `import "C"` C fragments in the file. p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file @@ -308,7 +380,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl Tok: token.TYPE, } for _, name := range builtinAliases { - typeSpec := p.getIntegerType("C."+name, names["_Cgo_"+name]) + typeSpec := p.getIntegerType("_Cgo_"+name, names["_Cgo_"+name]) gen.Specs = append(gen.Specs, typeSpec) } p.generated.Decls = append(p.generated.Decls, gen) @@ -337,6 +409,22 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl }) } + // Show an error when a #cgo noescape line isn't used in practice. + // This matches upstream Go. I think the goal is to avoid issues with + // misspelled function names, which seems very useful. + var unusedNoescapeLines []*noescapingFunc + for _, value := range p.noescapingFuncs { + if !value.used { + unusedNoescapeLines = append(unusedNoescapeLines, value) + } + } + sort.SliceStable(unusedNoescapeLines, func(i, j int) bool { + return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos + }) + for _, value := range unusedNoescapeLines { + p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name)) + } + // Print the newly generated in-memory AST, for debugging. //ast.Print(fset, p.generated) @@ -402,6 +490,33 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin } text = text[:lineStart] + string(spaces) + text[lineEnd:] + allFields := strings.Fields(line[4:]) + switch allFields[0] { + case "noescape": + // The code indicates that pointer parameters will not be captured + // by the called C function. + if len(allFields) < 2 { + p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line") + continue + } + if len(allFields) > 2 { + p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line") + continue + } + name := allFields[1] + p.noescapingFuncs[name] = &noescapingFunc{ + name: name, + pos: pos, + used: false, + } + continue + case "nocallback": + // We don't do anything special when calling a C function, so there + // appears to be no optimization that we can do here. + // Accept, but ignore the parameter for compatibility. + continue + } + // Get the text before the colon in the #cgo directive. colon := strings.IndexByte(line, ':') if colon < 0 { @@ -1138,22 +1253,22 @@ func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string { func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string { // Some types are defined in stdint.h and map directly to a particular Go // type. - if alias := cgoAliases["C."+name]; alias != "" { + if alias := cgoAliases["_Cgo_"+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" } return node.Name.Name } - return "C." + name + return "_Cgo_" + name } // 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 @@ -1248,8 +1363,8 @@ extern __typeof(%s) %s __attribute__((alias(%#v))); case *elaboratedTypeInfo: // Add struct bitfields. for _, bitfield := range elaboratedType.bitfields { - f.createBitfieldGetter(bitfield, "C."+name) - f.createBitfieldSetter(bitfield, "C."+name) + f.createBitfieldGetter(bitfield, "_Cgo_"+name) + f.createBitfieldSetter(bitfield, "_Cgo_"+name) } if elaboratedType.unionSize != 0 { // Create union getters/setters. @@ -1258,7 +1373,7 @@ extern __typeof(%s) %s __attribute__((alias(%#v))); f.addError(elaboratedType.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names))) continue } - f.createUnionAccessor(field, "C."+name) + f.createUnionAccessor(field, "_Cgo_"+name) } } } @@ -1272,6 +1387,45 @@ extern __typeof(%s) %s __attribute__((alias(%#v))); // separate namespace (no _Cgo_ hacks like in gc). func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool { switch node := cursor.Node().(type) { + case *ast.AssignStmt: + // An assign statement could be something like this: + // + // val, errno := C.some_func() + // + // Check whether it looks like that, and if so, read the errno value and + // return it as the second return value. The call will be transformed + // into something like this: + // + // val, errno := C.some_func(), C.__get_errno() + if len(node.Lhs) != 2 || len(node.Rhs) != 1 { + return true + } + rhs, ok := node.Rhs[0].(*ast.CallExpr) + if !ok { + return true + } + fun, ok := rhs.Fun.(*ast.SelectorExpr) + if !ok { + return true + } + x, ok := fun.X.(*ast.Ident) + if !ok { + return true + } + if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" { + // Replace "C"."some_func" into "C.somefunc". + rhs.Fun = &ast.Ident{ + NamePos: x.NamePos, + Name: f.getASTDeclName(fun.Sel.Name, found, true), + } + // Add the errno value as the second value in the statement. + node.Rhs = append(node.Rhs, &ast.CallExpr{ + Fun: &ast.Ident{ + NamePos: node.Lhs[1].End(), + Name: "_Cgo___get_errno", + }, + }) + } case *ast.CallExpr: fun, ok := node.Fun.(*ast.SelectorExpr) if !ok { @@ -1293,7 +1447,7 @@ func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) b return true } if x.Name == "C" { - name := "C." + node.Sel.Name + name := "_Cgo_" + node.Sel.Name if found, ok := names[node.Sel.Name]; ok { name = f.getASTDeclName(node.Sel.Name, found, false) } diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index 5425d2779b..f852c7f5f9 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -7,6 +7,7 @@ import ( "go/ast" "go/format" "go/parser" + "go/scanner" "go/token" "go/types" "os" @@ -55,7 +56,7 @@ func TestCGo(t *testing.T) { } // Process the AST with CGo. - cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags) + cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux") // Check the AST for type errors. var typecheckErrors []error @@ -63,7 +64,7 @@ func TestCGo(t *testing.T) { Error: func(err error) { typecheckErrors = append(typecheckErrors, err) }, - Importer: simpleImporter{}, + Importer: newSimpleImporter(), Sizes: types.SizesFor("gccgo", "arm"), } _, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil) @@ -201,14 +202,33 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) { } // simpleImporter implements the types.Importer interface, but only allows -// importing the unsafe package. +// importing the syscall and unsafe packages. type simpleImporter struct { + syscallPkg *types.Package +} + +func newSimpleImporter() *simpleImporter { + i := &simpleImporter{} + + // Implement a dummy syscall package with the Errno type. + i.syscallPkg = types.NewPackage("syscall", "syscall") + obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil) + named := types.NewNamed(obj, nil, nil) + i.syscallPkg.Scope().Insert(obj) + named.SetUnderlying(types.Typ[types.Uintptr]) + sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false) + named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig)) + i.syscallPkg.MarkComplete() + + return i } // Import implements the Importer interface. For testing usage only: it only // supports importing the unsafe package. -func (i simpleImporter) Import(path string) (*types.Package, error) { +func (i *simpleImporter) Import(path string) (*types.Package, error) { switch path { + case "syscall": + return i.syscallPkg, nil case "unsafe": return types.Unsafe, nil default: @@ -216,10 +236,16 @@ func (i simpleImporter) Import(path string) (*types.Package, error) { } } -// formatDiagnostics formats the error message to be an indented comment. It +// formatDiagnostic formats the error message to be an indented comment. It // also fixes Windows path name issues (backward slashes). func formatDiagnostic(err error) string { - msg := err.Error() + var msg string + switch err := err.(type) { + case scanner.Error: + msg = err.Pos.String() + ": " + err.Msg + default: + msg = err.Error() + } if runtime.GOOS == "windows" { // Fix Windows path slashes. msg = strings.ReplaceAll(msg, "testdata\\", "testdata/") diff --git a/cgo/const.go b/cgo/const.go index 6420af4b92..9e7b06b4de 100644 --- a/cgo/const.go +++ b/cgo/const.go @@ -17,6 +17,8 @@ var ( token.OR: precedenceOr, token.XOR: precedenceXor, token.AND: precedenceAnd, + token.SHL: precedenceShift, + token.SHR: precedenceShift, token.ADD: precedenceAdd, token.SUB: precedenceAdd, token.MUL: precedenceMul, @@ -25,11 +27,13 @@ var ( } ) +// See: https://en.cppreference.com/w/c/language/operator_precedence const ( precedenceLowest = iota + 1 precedenceOr precedenceXor precedenceAnd + precedenceShift precedenceAdd precedenceMul precedencePrefix @@ -50,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 { @@ -82,7 +150,7 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) { for t.peekToken != token.EOF && precedence < precedences[t.peekToken] { switch t.peekToken { - case token.OR, token.XOR, token.AND, token.ADD, token.SUB, token.MUL, token.QUO, token.REM: + case token.OR, token.XOR, token.AND, token.SHL, token.SHR, token.ADD, token.SUB, token.MUL, token.QUO, token.REM: t.Next() leftExpr, err = parseBinaryExpr(t, leftExpr) } @@ -92,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, @@ -160,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() @@ -191,7 +325,9 @@ func (t *tokenizer) Next() { t.curValue = t.peekValue // Parse the next peek token. - t.peekPos += token.Pos(len(t.curValue)) + if t.peekPos != token.NoPos { + t.peekPos += token.Pos(len(t.curValue)) + } for { if len(t.buf) == 0 { t.peekToken = token.EOF @@ -203,20 +339,28 @@ func (t *tokenizer) Next() { // Skip whitespace. // Based on this source, not sure whether it represents C whitespace: // https://en.cppreference.com/w/cpp/string/byte/isspace - t.peekPos++ + if t.peekPos != token.NoPos { + t.peekPos++ + } t.buf = t.buf[1:] - case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&"): + case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&" || string(t.buf[:2]) == "<<" || string(t.buf[:2]) == ">>"): // Two-character tokens. switch c { case '&': t.peekToken = token.LAND case '|': t.peekToken = token.LOR + case '<': + t.peekToken = token.SHL + case '>': + t.peekToken = token.SHR + default: + panic("unreachable") } 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 { @@ -224,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 '-': diff --git a/cgo/const_test.go b/cgo/const_test.go index 305416e84d..b87f8063a4 100644 --- a/cgo/const_test.go +++ b/cgo/const_test.go @@ -40,6 +40,10 @@ func TestParseConst(t *testing.T) { {`5&5`, `5 & 5`}, {`5|5`, `5 | 5`}, {`5^5`, `5 ^ 5`}, + {`5<<5`, `5 << 5`}, + {`5>>5`, `5 >> 5`}, + {`5>>5 + 3`, `5 >> (5 + 3)`}, + {`5>>5 ^ 3`, `5>>5 ^ 3`}, {`5||5`, `error: 1:2: unexpected token ||, expected end of expression`}, // logical binops aren't supported yet {`(5/5)`, `(5 / 5)`}, {`1 - 2`, `1 - 2`}, @@ -55,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 := "" if err != nil { if !strings.HasPrefix(tc.Go, "error: ") { diff --git a/cgo/libclang.go b/cgo/libclang.go index 0860c6af4d..759417a6ea 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -4,6 +4,7 @@ package cgo // modification. It does not touch the AST itself. import ( + "bytes" "crypto/sha256" "crypto/sha512" "encoding/hex" @@ -62,10 +63,24 @@ 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); +// Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this: +// In file included from _cgo_export.c:4: +// cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration] +// libclang.go:68:5: note: previous declaration is here +// See: https://github.com/golang/go/issues/49721 +#if defined(_WIN32) +#define CGO_DECL // __declspec(dllexport) +#else +#define CGO_DECL +#endif + +CGO_DECL int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); +CGO_DECL int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); -int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); +CGO_DECL void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data); */ import "C" @@ -204,7 +219,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) obj := &ast.Object{ Kind: ast.Fun, - Name: "C." + name, + Name: "_Cgo_" + name, } exportName := name localName := name @@ -242,7 +257,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { }, Name: &ast.Ident{ NamePos: pos, - Name: "C." + localName, + Name: "_Cgo_" + localName, Obj: obj, }, Type: &ast.FuncType{ @@ -254,10 +269,18 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { }, }, } + var doc []string if C.clang_isFunctionTypeVariadic(cursorType) != 0 { + doc = append(doc, "//go:variadic") + } + if _, ok := f.noescapingFuncs[name]; ok { + doc = append(doc, "//go:noescape") + f.noescapingFuncs[name].used = true + } + if len(doc) != 0 { decl.Doc.List = append(decl.Doc.List, &ast.Comment{ Slash: pos - 1, - Text: "//go:variadic", + Text: strings.Join(doc, "\n"), }) } for i := 0; i < numArgs; i++ { @@ -296,7 +319,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { return decl, stringSignature case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: typ := f.makeASTRecordType(c, pos) - typeName := "C." + name + typeName := "_Cgo_" + name typeExpr := typ.typeExpr if typ.unionSize != 0 { // Convert to a single-field struct type. @@ -317,7 +340,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { obj.Decl = typeSpec return typeSpec, typ case C.CXCursor_TypedefDecl: - typeName := "C." + name + typeName := "_Cgo_" + name underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) obj := &ast.Object{ Kind: ast.Typ, @@ -355,12 +378,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { } obj := &ast.Object{ Kind: ast.Var, - Name: "C." + name, + Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }}, Type: typeExpr, @@ -369,42 +392,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: - sourceRange := C.tinygo_clang_getCursorExtent(c) - start := C.clang_getRangeStart(sourceRange) - end := C.clang_getRangeEnd(sourceRange) - var file, endFile C.CXFile - var startOffset, endOffset C.unsigned - C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset) - if file == nil { - f.addError(pos, "internal error: could not find file where macro is defined") - return nil, nil - } - C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset) - if file != endFile { - f.addError(pos, "internal error: expected start and end location of a macro to be in the same file") - return nil, nil - } - if startOffset > endOffset { - f.addError(pos, "internal error: start offset of macro is after end offset") - return nil, nil - } - - // read file contents and extract the relevant byte range - tu := C.tinygo_clang_Cursor_getTranslationUnit(c) - var size C.size_t - sourcePtr := C.clang_getFileContents(tu, file, &size) - if endOffset >= C.uint(size) { - f.addError(pos, "internal error: end offset of macro lies after end of file") - return nil, nil - } - source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset]) - if !strings.HasPrefix(source, name) { - f.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source)) - return nil, nil - } - value := source[len(name):] - // Try to convert this #define into a Go constant expression. - expr, scannerError := parseConst(pos+token.Pos(len(name)), 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 @@ -418,12 +407,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { } obj := &ast.Object{ Kind: ast.Con, - Name: "C." + name, + Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }}, Values: []ast.Expr{expr}, @@ -434,7 +423,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { case C.CXCursor_EnumDecl: obj := &ast.Object{ Kind: ast.Typ, - Name: "C." + name, + Name: "_Cgo_" + name, } underlying := C.tinygo_clang_getEnumDeclIntegerType(c) // TODO: gc's CGo implementation uses types such as `uint32` for enums @@ -442,7 +431,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { typeSpec := &ast.TypeSpec{ Name: &ast.Ident{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }, Assign: pos, @@ -465,12 +454,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { } obj := &ast.Object{ Kind: ast.Con, - Name: "C." + name, + Name: "_Cgo_" + name, } valueSpec := &ast.ValueSpec{ Names: []*ast.Ident{{ NamePos: pos, - Name: "C." + name, + Name: "_Cgo_" + name, Obj: obj, }}, Values: []ast.Expr{expr}, @@ -484,6 +473,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) @@ -642,13 +687,6 @@ func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) { // addErrorAt is a utility function to add an error to the list of errors. func (p *cgoPackage) addErrorAt(position token.Position, msg string) { - if filepath.IsAbs(position.Filename) { - // Relative paths for readability, like other Go parser errors. - relpath, err := filepath.Rel(p.currentDir, position.Filename) - if err == nil { - position.Filename = relpath - } - } p.errors = append(p.errors, scanner.Error{ Pos: position, Msg: msg, @@ -707,27 +745,27 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { var typeName string switch typ.kind { case C.CXType_Char_S, C.CXType_Char_U: - typeName = "C.char" + typeName = "_Cgo_char" case C.CXType_SChar: - typeName = "C.schar" + typeName = "_Cgo_schar" case C.CXType_UChar: - typeName = "C.uchar" + typeName = "_Cgo_uchar" case C.CXType_Short: - typeName = "C.short" + typeName = "_Cgo_short" case C.CXType_UShort: - typeName = "C.ushort" + typeName = "_Cgo_ushort" case C.CXType_Int: - typeName = "C.int" + typeName = "_Cgo_int" case C.CXType_UInt: - typeName = "C.uint" + typeName = "_Cgo_uint" case C.CXType_Long: - typeName = "C.long" + typeName = "_Cgo_long" case C.CXType_ULong: - typeName = "C.ulong" + typeName = "_Cgo_ulong" case C.CXType_LongLong: - typeName = "C.longlong" + typeName = "_Cgo_longlong" case C.CXType_ULongLong: - typeName = "C.ulonglong" + typeName = "_Cgo_ulonglong" case C.CXType_Bool: typeName = "bool" case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble: @@ -858,7 +896,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr { typeSpelling := getString(C.clang_getTypeSpelling(typ)) typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind)) f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling)) - typeName = "C." + typeName = "_Cgo_" } return &ast.Ident{ NamePos: pos, @@ -875,7 +913,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp var goName string typeSize := C.clang_Type_getSizeOf(underlyingType) switch name { - case "C.char": + case "_Cgo_char": if typeSize != 1 { // This happens for some very special purpose architectures // (DSPs etc.) that are not currently targeted. @@ -888,7 +926,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp case C.CXType_Char_U: goName = "uint8" } - case "C.schar", "C.short", "C.int", "C.long", "C.longlong": + case "_Cgo_schar", "_Cgo_short", "_Cgo_int", "_Cgo_long", "_Cgo_longlong": switch typeSize { case 1: goName = "int8" @@ -899,7 +937,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp case 8: goName = "int64" } - case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong": + case "_Cgo_uchar", "_Cgo_ushort", "_Cgo_uint", "_Cgo_ulong", "_Cgo_ulonglong": switch typeSize { case 1: goName = "uint8" diff --git a/cgo/libclang_config_llvm17.go b/cgo/libclang_config_llvm17.go index c17cca09b8..6395d8a3af 100644 --- a/cgo/libclang_config_llvm17.go +++ b/cgo/libclang_config_llvm17.go @@ -1,4 +1,4 @@ -//go:build !byollvm && !llvm15 && !llvm16 +//go:build !byollvm && llvm17 package cgo diff --git a/cgo/libclang_config_llvm18.go b/cgo/libclang_config_llvm18.go new file mode 100644 index 0000000000..da181291c4 --- /dev/null +++ b/cgo/libclang_config_llvm18.go @@ -0,0 +1,15 @@ +//go:build !byollvm && llvm18 + +package cgo + +/* +#cgo linux CFLAGS: -I/usr/include/llvm-18 -I/usr/include/llvm-c-18 -I/usr/lib/llvm-18/include +#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@18/include +#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@18/include +#cgo freebsd CFLAGS: -I/usr/local/llvm18/include +#cgo linux LDFLAGS: -L/usr/lib/llvm-18/lib -lclang +#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@18/lib -lclang +#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@18/lib -lclang +#cgo freebsd LDFLAGS: -L/usr/local/llvm18/lib -lclang +*/ +import "C" diff --git a/cgo/libclang_config_llvm19.go b/cgo/libclang_config_llvm19.go new file mode 100644 index 0000000000..867d23f24b --- /dev/null +++ b/cgo/libclang_config_llvm19.go @@ -0,0 +1,15 @@ +//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 + +package cgo + +/* +#cgo linux CFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -I/usr/lib/llvm-19/include +#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@19/include +#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@19/include +#cgo freebsd CFLAGS: -I/usr/local/llvm19/include +#cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -lclang +#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@19/lib -lclang +#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@19/lib -lclang +#cgo freebsd LDFLAGS: -L/usr/local/llvm19/lib -lclang +*/ +import "C" diff --git a/cgo/libclang_stubs.c b/cgo/libclang_stubs.c index 1b157d0aa7..e8098fac09 100644 --- a/cgo/libclang_stubs.c +++ b/cgo/libclang_stubs.c @@ -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); +} diff --git a/cgo/testdata/basic.out.go b/cgo/testdata/basic.out.go index f2daa78dac..191cfba38f 100644 --- a/cgo/testdata/basic.out.go +++ b/cgo/testdata/basic.out.go @@ -1,39 +1,54 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} + +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer + +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) +} + +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr + +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) diff --git a/cgo/testdata/const.go b/cgo/testdata/const.go index 43645d3ac4..d5a7dfd396 100644 --- a/cgo/testdata/const.go +++ b/cgo/testdata/const.go @@ -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 ) diff --git a/cgo/testdata/const.out.go b/cgo/testdata/const.out.go index 4a5f5fd5b0..0329ba5985 100644 --- a/cgo/testdata/const.out.go +++ b/cgo/testdata/const.out.go @@ -1,42 +1,62 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} + +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer + +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) +} + +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr + +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -const C.foo = 3 -const C.bar = C.foo +const _Cgo_foo = 3 +const _Cgo_bar = _Cgo_foo +const _Cgo_unreferenced = 4 +const _Cgo_referenced = _Cgo_unreferenced +const _Cgo_fnlike_val = 5 +const _Cgo_square_val = (20 * 20) +const _Cgo_add_val = (3 + 5) diff --git a/cgo/testdata/errors.go b/cgo/testdata/errors.go index 7ca5b79600..8813f83cf4 100644 --- a/cgo/testdata/errors.go +++ b/cgo/testdata/errors.go @@ -10,20 +10,35 @@ typedef struct { typedef someType noType; // undefined type +// Some invalid noescape lines +#cgo noescape +#cgo noescape foo bar +#cgo noescape unusedFunction + #define SOME_CONST_1 5) // invalid const syntax #define SOME_CONST_2 6) // const not used (so no error) #define SOME_CONST_3 1234 // const too large for byte +#define SOME_CONST_b 3 ) // const with lots of weird whitespace (to test error locations) +# define SOME_CONST_startspace 3) */ // // // #define SOME_CONST_4 8) // after some empty lines +// #cgo CFLAGS: -DSOME_PARAM_CONST_invalid=3/+3 +// #cgo CFLAGS: -DSOME_PARAM_CONST_valid=3+4 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. +// //line errors.go:100 var ( // constant too large @@ -38,4 +53,15 @@ var ( _ byte = C.SOME_CONST_3 _ = C.SOME_CONST_4 + + _ = C.SOME_CONST_b + + _ = C.SOME_CONST_startspace + + // constants passed by a command line parameter + _ = C.SOME_PARAM_CONST_invalid + _ = C.SOME_PARAM_CONST_valid + + _ = C.add_toomuch + _ = C.add_toolittle ) diff --git a/cgo/testdata/errors.out.go b/cgo/testdata/errors.out.go index b1646a2e0d..0ae794087f 100644 --- a/cgo/testdata/errors.out.go +++ b/cgo/testdata/errors.out.go @@ -1,60 +1,89 @@ // CGo errors: +// testdata/errors.go:14:1: missing function name in #cgo noescape line +// testdata/errors.go:15:1: multiple function names in #cgo noescape line // testdata/errors.go:4:2: warning: some warning // testdata/errors.go:11:9: error: unknown type name 'someType' -// testdata/errors.go:22:5: warning: another warning -// testdata/errors.go:13:23: unexpected token ), expected end of expression -// testdata/errors.go:19:26: unexpected token ), expected end of expression +// testdata/errors.go:31:5: warning: another warning +// testdata/errors.go:18:23: unexpected token ), expected end of expression +// testdata/errors.go:26:26: unexpected token ), expected end of expression +// testdata/errors.go:21:33: unexpected token ), expected end of expression +// testdata/errors.go:22:34: unexpected token ), expected end of expression +// -: unexpected token INT, expected end of expression +// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3 +// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1 +// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used // 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) +// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as _Cgo_char value in variable declaration (overflows) // testdata/errors.go:105: unknown field z in struct literal -// testdata/errors.go:108: undefined: C.SOME_CONST_1 -// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) -// testdata/errors.go:112: undefined: C.SOME_CONST_4 +// testdata/errors.go:108: undefined: _Cgo_SOME_CONST_1 +// testdata/errors.go:110: cannot use _Cgo_SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) +// testdata/errors.go:112: undefined: _Cgo_SOME_CONST_4 +// testdata/errors.go:114: undefined: _Cgo_SOME_CONST_b +// testdata/errors.go:116: undefined: _Cgo_SOME_CONST_startspace +// testdata/errors.go:119: undefined: _Cgo_SOME_PARAM_CONST_invalid +// testdata/errors.go:122: undefined: _Cgo_add_toomuch +// testdata/errors.go:123: undefined: _Cgo_add_toolittle package main +import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} + +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer + +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) +} + +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr + +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -type C.struct_point_t struct { - x C.int - y C.int +type _Cgo_struct_point_t struct { + x _Cgo_int + y _Cgo_int } -type C.point_t = C.struct_point_t +type _Cgo_point_t = _Cgo_struct_point_t -const C.SOME_CONST_3 = 1234 +const _Cgo_SOME_CONST_3 = 1234 +const _Cgo_SOME_PARAM_CONST_valid = 3 + 4 diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go index 72520ab6d9..ac5cf546db 100644 --- a/cgo/testdata/flags.out.go +++ b/cgo/testdata/flags.out.go @@ -5,43 +5,58 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} + +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer + +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) +} + +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr + +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -const C.BAR = 3 -const C.FOO_H = 1 +const _Cgo_BAR = 3 +const _Cgo_FOO_H = 1 diff --git a/cgo/testdata/symbols.go b/cgo/testdata/symbols.go index fb585c2f87..c8029a1481 100644 --- a/cgo/testdata/symbols.go +++ b/cgo/testdata/symbols.go @@ -9,6 +9,10 @@ static void staticfunc(int x); // Global variable signatures. extern int someValue; + +void notEscapingFunction(int *a); + +#cgo noescape notEscapingFunction */ import "C" @@ -18,6 +22,7 @@ func accessFunctions() { C.variadic0() C.variadic2(3, 5) C.staticfunc(3) + C.notEscapingFunction(nil) } func accessGlobals() { diff --git a/cgo/testdata/symbols.out.go b/cgo/testdata/symbols.out.go index f2826ae2e9..8a603cfd7e 100644 --- a/cgo/testdata/symbols.out.go +++ b/cgo/testdata/symbols.out.go @@ -1,64 +1,84 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} + +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer + +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) +} + +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr + +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) //export foo -func C.foo(a C.int, b C.int) C.int +func _Cgo_foo(a _Cgo_int, b _Cgo_int) _Cgo_int -var C.foo$funcaddr unsafe.Pointer +var _Cgo_foo$funcaddr unsafe.Pointer //export variadic0 //go:variadic -func C.variadic0() +func _Cgo_variadic0() -var C.variadic0$funcaddr unsafe.Pointer +var _Cgo_variadic0$funcaddr unsafe.Pointer //export variadic2 //go:variadic -func C.variadic2(x C.int, y C.int) +func _Cgo_variadic2(x _Cgo_int, y _Cgo_int) -var C.variadic2$funcaddr unsafe.Pointer +var _Cgo_variadic2$funcaddr unsafe.Pointer //export _Cgo_static_173c95a79b6df1980521_staticfunc -func C.staticfunc!symbols.go(x C.int) +func _Cgo_staticfunc!symbols.go(x _Cgo_int) + +var _Cgo_staticfunc!symbols.go$funcaddr unsafe.Pointer -var C.staticfunc!symbols.go$funcaddr unsafe.Pointer +//export notEscapingFunction +//go:noescape +func _Cgo_notEscapingFunction(a *_Cgo_int) +var _Cgo_notEscapingFunction$funcaddr unsafe.Pointer //go:extern someValue -var C.someValue C.int +var _Cgo_someValue _Cgo_int diff --git a/cgo/testdata/types.out.go b/cgo/testdata/types.out.go index a35d3733c4..3eaa53f1fb 100644 --- a/cgo/testdata/types.out.go +++ b/cgo/testdata/types.out.go @@ -1,147 +1,166 @@ package main +import "syscall" import "unsafe" var _ unsafe.Pointer -//go:linkname C.CString runtime.cgo_CString -func C.CString(string) *C.char +//go:linkname _Cgo_CString runtime.cgo_CString +func _Cgo_CString(string) *_Cgo_char -//go:linkname C.GoString runtime.cgo_GoString -func C.GoString(*C.char) string +//go:linkname _Cgo_GoString runtime.cgo_GoString +func _Cgo_GoString(*_Cgo_char) string -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func C.__GoStringN(*C.char, uintptr) string +//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN +func _Cgo___GoStringN(*_Cgo_char, uintptr) string -func C.GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { + return _Cgo___GoStringN(cstr, uintptr(length)) } -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func C.__GoBytes(unsafe.Pointer, uintptr) []byte +//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes +func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte -func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} + +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer + +func _Cgo_CBytes(b []byte) unsafe.Pointer { + return _Cgo___CBytes(b) +} + +//go:linkname _Cgo___get_errno_num runtime.cgo_errno +func _Cgo___get_errno_num() uintptr + +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } type ( - C.char uint8 - C.schar int8 - C.uchar uint8 - C.short int16 - C.ushort uint16 - C.int int32 - C.uint uint32 - C.long int32 - C.ulong uint32 - C.longlong int64 - C.ulonglong uint64 + _Cgo_char uint8 + _Cgo_schar int8 + _Cgo_uchar uint8 + _Cgo_short int16 + _Cgo_ushort uint16 + _Cgo_int int32 + _Cgo_uint uint32 + _Cgo_long int32 + _Cgo_ulong uint32 + _Cgo_longlong int64 + _Cgo_ulonglong uint64 ) -type C.myint = C.int -type C.struct_point2d_t struct { - x C.int - y C.int -} -type C.point2d_t = C.struct_point2d_t -type C.struct_point3d struct { - x C.int - y C.int - z C.int -} -type C.point3d_t = C.struct_point3d -type C.struct_type1 struct { - _type C.int - __type C.int - ___type C.int -} -type C.struct_type2 struct{ _type C.int } -type C.union_union1_t struct{ i C.int } -type C.union1_t = C.union_union1_t -type C.union_union3_t struct{ $union uint64 } - -func (union *C.union_union3_t) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union3_t) unionfield_d() *float64 { +type _Cgo_myint = _Cgo_int +type _Cgo_struct_point2d_t struct { + x _Cgo_int + y _Cgo_int +} +type _Cgo_point2d_t = _Cgo_struct_point2d_t +type _Cgo_struct_point3d struct { + x _Cgo_int + y _Cgo_int + z _Cgo_int +} +type _Cgo_point3d_t = _Cgo_struct_point3d +type _Cgo_struct_type1 struct { + _type _Cgo_int + __type _Cgo_int + ___type _Cgo_int +} +type _Cgo_struct_type2 struct{ _type _Cgo_int } +type _Cgo_union_union1_t struct{ i _Cgo_int } +type _Cgo_union1_t = _Cgo_union_union1_t +type _Cgo_union_union3_t struct{ $union uint64 } + +func (union *_Cgo_union_union3_t) unionfield_i() *_Cgo_int { + return (*_Cgo_int)(unsafe.Pointer(&union.$union)) +} +func (union *_Cgo_union_union3_t) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union3_t) unionfield_s() *C.short { - return (*C.short)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union3_t) unionfield_s() *_Cgo_short { + return (*_Cgo_short)(unsafe.Pointer(&union.$union)) } -type C.union3_t = C.union_union3_t -type C.union_union2d struct{ $union [2]uint64 } +type _Cgo_union3_t = _Cgo_union_union3_t +type _Cgo_union_union2d struct{ $union [2]uint64 } -func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union2d) unionfield_d() *[2]float64 { +func (union *_Cgo_union_union2d) unionfield_i() *_Cgo_int { + return (*_Cgo_int)(unsafe.Pointer(&union.$union)) +} +func (union *_Cgo_union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) } -type C.union2d_t = C.union_union2d -type C.union_unionarray_t struct{ arr [10]C.uchar } -type C.unionarray_t = C.union_unionarray_t -type C._Ctype_union___0 struct{ $union [3]uint32 } +type _Cgo_union2d_t = _Cgo_union_union2d +type _Cgo_union_unionarray_t struct{ arr [10]_Cgo_uchar } +type _Cgo_unionarray_t = _Cgo_union_unionarray_t +type _Cgo__Ctype_union___0 struct{ $union [3]uint32 } -func (union *C._Ctype_union___0) unionfield_area() *C.point2d_t { - return (*C.point2d_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo__Ctype_union___0) unionfield_area() *_Cgo_point2d_t { + return (*_Cgo_point2d_t)(unsafe.Pointer(&union.$union)) } -func (union *C._Ctype_union___0) unionfield_solid() *C.point3d_t { - return (*C.point3d_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo__Ctype_union___0) unionfield_solid() *_Cgo_point3d_t { + return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) } -type C.struct_struct_nested_t struct { - begin C.point2d_t - end C.point2d_t - tag C.int +type _Cgo_struct_struct_nested_t struct { + begin _Cgo_point2d_t + end _Cgo_point2d_t + tag _Cgo_int - coord C._Ctype_union___0 + coord _Cgo__Ctype_union___0 } -type C.struct_nested_t = C.struct_struct_nested_t -type C.union_union_nested_t struct{ $union [2]uint64 } +type _Cgo_struct_nested_t = _Cgo_struct_struct_nested_t +type _Cgo_union_union_nested_t struct{ $union [2]uint64 } -func (union *C.union_union_nested_t) unionfield_point() *C.point3d_t { - return (*C.point3d_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union_nested_t) unionfield_point() *_Cgo_point3d_t { + return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union_nested_t) unionfield_array() *C.unionarray_t { - return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union_nested_t) unionfield_array() *_Cgo_unionarray_t { + return (*_Cgo_unionarray_t)(unsafe.Pointer(&union.$union)) } -func (union *C.union_union_nested_t) unionfield_thing() *C.union3_t { - return (*C.union3_t)(unsafe.Pointer(&union.$union)) +func (union *_Cgo_union_union_nested_t) unionfield_thing() *_Cgo_union3_t { + return (*_Cgo_union3_t)(unsafe.Pointer(&union.$union)) } -type C.union_nested_t = C.union_union_nested_t -type C.enum_option = C.int -type C.option_t = C.enum_option -type C.enum_option2_t = C.uint -type C.option2_t = C.enum_option2_t -type C.struct_types_t struct { +type _Cgo_union_nested_t = _Cgo_union_union_nested_t +type _Cgo_enum_option = _Cgo_int +type _Cgo_option_t = _Cgo_enum_option +type _Cgo_enum_option2_t = _Cgo_uint +type _Cgo_option2_t = _Cgo_enum_option2_t +type _Cgo_struct_types_t struct { f float32 d float64 - ptr *C.int + ptr *_Cgo_int } -type C.types_t = C.struct_types_t -type C.myIntArray = [10]C.int -type C.struct_bitfield_t struct { - start C.uchar - __bitfield_1 C.uchar +type _Cgo_types_t = _Cgo_struct_types_t +type _Cgo_myIntArray = [10]_Cgo_int +type _Cgo_struct_bitfield_t struct { + start _Cgo_uchar + __bitfield_1 _Cgo_uchar - d C.uchar - e C.uchar + d _Cgo_uchar + e _Cgo_uchar } -func (s *C.struct_bitfield_t) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } -func (s *C.struct_bitfield_t) set_bitfield_a(value C.uchar) { +func (s *_Cgo_struct_bitfield_t) bitfield_a() _Cgo_uchar { return s.__bitfield_1 & 0x1f } +func (s *_Cgo_struct_bitfield_t) set_bitfield_a(value _Cgo_uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 } -func (s *C.struct_bitfield_t) bitfield_b() C.uchar { +func (s *_Cgo_struct_bitfield_t) bitfield_b() _Cgo_uchar { return s.__bitfield_1 >> 5 & 0x1 } -func (s *C.struct_bitfield_t) set_bitfield_b(value C.uchar) { +func (s *_Cgo_struct_bitfield_t) set_bitfield_b(value _Cgo_uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 } -func (s *C.struct_bitfield_t) bitfield_c() C.uchar { +func (s *_Cgo_struct_bitfield_t) bitfield_c() _Cgo_uchar { return s.__bitfield_1 >> 6 } -func (s *C.struct_bitfield_t) set_bitfield_c(value C.uchar, +func (s *_Cgo_struct_bitfield_t) set_bitfield_c(value _Cgo_uchar, ) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 } -type C.bitfield_t = C.struct_bitfield_t +type _Cgo_bitfield_t = _Cgo_struct_bitfield_t diff --git a/compileopts/config.go b/compileopts/config.go index 9349738a4a..a9eb235ad1 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -33,6 +33,17 @@ func (c *Config) CPU() string { return c.Target.CPU } +// The current build mode (like the `-buildmode` command line flag). +func (c *Config) BuildMode() string { + if c.Options.BuildMode != "" { + return c.Options.BuildMode + } + if c.Target.BuildMode != "" { + return c.Target.BuildMode + } + return "default" +} + // Features returns a list of features this CPU supports. For example, for a // RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list // will be returned. @@ -60,7 +71,7 @@ func (c *Config) GOOS() string { } // GOARCH returns the GOARCH of the target. This might not always be the actual -// archtecture: for example, the AVR target is not supported by the Go standard +// architecture: for example, the AVR target is not supported by the Go standard // library so such targets will usually pretend to be linux/arm. func (c *Config) GOARCH() string { return c.Target.GOARCH @@ -72,11 +83,19 @@ func (c *Config) GOARM() string { return c.Options.GOARM } +// GOMIPS will return the GOMIPS environment variable given to the compiler when +// building a program. +func (c *Config) GOMIPS() string { + return c.Options.GOMIPS +} + // BuildTags returns the complete list of build tags used during this build. func (c *Config) BuildTags() []string { - tags := append(c.Target.BuildTags, []string{ + tags := append([]string(nil), c.Target.BuildTags...) // copy slice (avoid a race) + tags = append(tags, []string{ "tinygo", // that's the compiler "purego", // to get various crypto packages to work + "osusergo", // to get os/user to work "math_big_pure_go", // to get math/big to work "gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package "serial." + c.Serial()}...) // used inside the machine package @@ -206,16 +225,28 @@ func (c *Config) RP2040BootPatch() bool { return false } -// MuslArchitecture returns the architecture name as used in musl libc. It is -// usually the same as the first part of the LLVM triple, but not always. -func MuslArchitecture(triple string) string { +// Return a canonicalized architecture name, so we don't have to deal with arm* +// vs thumb* vs arm64. +func CanonicalArchName(triple string) string { arch := strings.Split(triple, "-")[0] + if arch == "arm64" { + return "aarch64" + } if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { - arch = "arm" + return "arm" + } + if arch == "mipsel" { + return "mips" } return arch } +// MuslArchitecture returns the architecture name as used in musl libc. It is +// usually the same as the first part of the LLVM triple, but not always. +func MuslArchitecture(triple string) string { + return CanonicalArchName(triple) +} + // LibcPath returns the path to the libc directory. The libc path will be either // a precompiled libc shipped with a TinyGo build, or a libc path in the cache // directory (which might not yet be built). @@ -227,6 +258,13 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) { if c.ABI() != "" { archname += "-" + c.ABI() } + if c.Target.SoftFloat { + archname += "-softfloat" + } + if name == "bdwgc" { + // Boehm GC is compiled against a particular libc. + archname += "-" + c.Target.Libc + } // Try to load a precompiled library. precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name) @@ -281,77 +319,93 @@ func (c *Config) CFlags(libclang bool) []string { "-resource-dir="+resourceDir, ) } + cflags = append(cflags, c.LibcCFlags()...) + // Always emit debug information. It is optionally stripped at link time. + cflags = append(cflags, "-gdwarf-4") + // Use the same optimization level as TinyGo. + cflags = append(cflags, "-O"+c.Options.Opt) + // Set the LLVM target triple. + cflags = append(cflags, "--target="+c.Triple()) + // Set the -mcpu (or similar) flag. + if c.Target.CPU != "" { + if c.GOARCH() == "amd64" || c.GOARCH() == "386" { + // x86 prefers the -march flag (-mcpu is deprecated there). + cflags = append(cflags, "-march="+c.Target.CPU) + } else if strings.HasPrefix(c.Triple(), "avr") { + // AVR MCUs use -mmcu instead of -mcpu. + cflags = append(cflags, "-mmcu="+c.Target.CPU) + } else { + // The rest just uses -mcpu. + cflags = append(cflags, "-mcpu="+c.Target.CPU) + } + } + // Set the -mabi flag, if needed. + if c.ABI() != "" { + cflags = append(cflags, "-mabi="+c.ABI()) + } + return cflags +} + +// LibcCFlags returns the C compiler flags for the configured libc. +// It only uses flags that are part of the libc path (triple, cpu, abi, libc +// name) so it can safely be used to compile another C library. +func (c *Config) LibcCFlags() []string { switch c.Target.Libc { case "darwin-libSystem": root := goenv.Get("TINYGOROOT") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"), - ) + } case "picolibc": root := goenv.Get("TINYGOROOT") picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") path, _ := c.LibcPath("picolibc") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(picolibcDir, "include"), "-isystem", filepath.Join(picolibcDir, "tinystdio"), - ) + "-D__PICOLIBC_ERRNO_FUNCTION=__errno_location", + } case "musl": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("musl") arch := MuslArchitecture(c.Triple()) - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "musl", "arch", arch), + "-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"), "-isystem", filepath.Join(root, "lib", "musl", "include"), - ) + } case "wasi-libc": root := goenv.Get("TINYGOROOT") - cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot") + return []string{ + "-nostdlibinc", + "-isystem", root + "/lib/wasi-libc/sysroot/include", + } + case "wasmbuiltins": + // nothing to add (library is purely for builtins) + return nil case "mingw-w64": root := goenv.Get("TINYGOROOT") path, _ := c.LibcPath("mingw-w64") - cflags = append(cflags, + return []string{ "-nostdlibinc", "-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), "-D_UCRT", - ) + } case "": // No libc specified, nothing to add. + return nil default: // Incorrect configuration. This could be handled in a better way, but // usually this will be found by developers (not by TinyGo users). panic("unknown libc: " + c.Target.Libc) } - // Always emit debug information. It is optionally stripped at link time. - cflags = append(cflags, "-gdwarf-4") - // Use the same optimization level as TinyGo. - cflags = append(cflags, "-O"+c.Options.Opt) - // Set the LLVM target triple. - cflags = append(cflags, "--target="+c.Triple()) - // Set the -mcpu (or similar) flag. - if c.Target.CPU != "" { - if c.GOARCH() == "amd64" || c.GOARCH() == "386" { - // x86 prefers the -march flag (-mcpu is deprecated there). - cflags = append(cflags, "-march="+c.Target.CPU) - } else if strings.HasPrefix(c.Triple(), "avr") { - // AVR MCUs use -mmcu instead of -mcpu. - cflags = append(cflags, "-mmcu="+c.Target.CPU) - } else { - // The rest just uses -mcpu. - cflags = append(cflags, "-mcpu="+c.Target.CPU) - } - } - // Set the -mabi flag, if needed. - if c.ABI() != "" { - cflags = append(cflags, "-mabi="+c.ABI()) - } - return cflags } // LDFlags returns the flags to pass to the linker. A few more flags are needed @@ -368,6 +422,8 @@ func (c *Config) LDFlags() []string { if c.Target.LinkerScript != "" { ldflags = append(ldflags, "-T", c.Target.LinkerScript) } + ldflags = append(ldflags, c.Options.ExtLDFlags...) + return ldflags } @@ -435,7 +491,7 @@ func (c *Config) BinaryFormat(ext string) string { // Programmer returns the flash method and OpenOCD interface name given a // particular configuration. It may either be all configured in the target JSON -// file or be modified using the -programmmer command-line option. +// file or be modified using the -programmer command-line option. func (c *Config) Programmer() (method, openocdInterface string) { switch c.Options.Programmer { case "": @@ -475,9 +531,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) { return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport) } args = []string{"-f", "interface/" + openocdInterface + ".cfg"} - for _, cmd := range c.Target.OpenOCDCommands { - args = append(args, "-c", cmd) - } if c.Target.OpenOCDTransport != "" { transport := c.Target.OpenOCDTransport if transport == "swd" { @@ -489,6 +542,9 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) { args = append(args, "-c", "transport select "+transport) } args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg") + for _, cmd := range c.Target.OpenOCDCommands { + args = append(args, "-c", cmd) + } return args, nil } diff --git a/compileopts/options.go b/compileopts/options.go index debdaf08cd..ed248c8020 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -8,10 +8,11 @@ import ( ) var ( - validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} + validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"} + validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"} validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSerialOptions = []string{"none", "uart", "usb", "rtt"} - validPrintSizeOptions = []string{"none", "short", "full"} + validPrintSizeOptions = []string{"none", "short", "full", "html"} validPanicStrategyOptions = []string{"print", "trap"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"} ) @@ -23,8 +24,10 @@ type Options struct { GOOS string // environment variable GOARCH string // environment variable GOARM string // environment variable (only used with GOARCH=arm) + GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle) Directory string // working dir, leave it unset to use the current working dir Target string + BuildMode string // -buildmode flag Opt string GC string PanicStrategy string @@ -53,10 +56,21 @@ type Options struct { Monitor bool BaudRate int 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 } // Verify performs a validation on the given options, raising an error if options are not valid. func (o *Options) Verify() error { + if o.BuildMode != "" { + valid := isInArray(validBuildModeOptions, o.BuildMode) + if !valid { + return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`, + o.BuildMode, + strings.Join(validBuildModeOptions, ", ")) + } + } if o.GC != "" { valid := isInArray(validGCOptions, o.GC) if !valid { diff --git a/compileopts/options_test.go b/compileopts/options_test.go index 23ffec465f..280ff9b467 100644 --- a/compileopts/options_test.go +++ b/compileopts/options_test.go @@ -9,9 +9,9 @@ import ( func TestVerifyOptions(t *testing.T) { - expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`) + expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise, boehm`) expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`) - expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`) + expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`) expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) testCases := []struct { diff --git a/compileopts/target.go b/compileopts/target.go index 66f44b7ccf..6917a944bd 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -26,11 +26,13 @@ type TargetSpec struct { Inherits []string `json:"inherits,omitempty"` Triple string `json:"llvm-target,omitempty"` CPU string `json:"cpu,omitempty"` - ABI string `json:"target-abi,omitempty"` // rougly equivalent to -mabi= flag + ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag Features string `json:"features,omitempty"` GOOS string `json:"goos,omitempty"` GOARCH string `json:"goarch,omitempty"` + SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc) BuildTags []string `json:"build-tags,omitempty"` + BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified) GC string `json:"gc,omitempty"` Scheduler string `json:"scheduler,omitempty"` Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) @@ -44,6 +46,7 @@ type TargetSpec struct { LinkerScript string `json:"linkerscript,omitempty"` ExtraFiles []string `json:"extra-files,omitempty"` RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum + BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear. Emulator string `json:"emulator,omitempty"` FlashCommand string `json:"flash-command,omitempty"` GDB []string `json:"gdb,omitempty"` @@ -62,6 +65,8 @@ type TargetSpec struct { JLinkDevice string `json:"jlink-device,omitempty"` CodeModel string `json:"code-model,omitempty"` RelocationModel string `json:"relocation-model,omitempty"` + WITPackage string `json:"wit-package,omitempty"` + WITWorld string `json:"wit-world,omitempty"` } // overrideProperties overrides all properties that are set in child into itself using reflection. @@ -84,6 +89,10 @@ func (spec *TargetSpec) overrideProperties(child *TargetSpec) error { if src.Uint() != 0 { dst.Set(src) } + case reflect.Bool: + if src.Bool() { + dst.Set(src) + } case reflect.Ptr: // for pointers, copy if not nil if !src.IsNil() { dst.Set(src) @@ -170,59 +179,7 @@ func (spec *TargetSpec) resolveInherits() error { // Load a target specification. func LoadTarget(options *Options) (*TargetSpec, error) { if options.Target == "" { - // Configure based on GOOS/GOARCH environment variables (falling back to - // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. - var llvmarch string - switch options.GOARCH { - case "386": - llvmarch = "i386" - case "amd64": - llvmarch = "x86_64" - case "arm64": - llvmarch = "aarch64" - case "arm": - switch options.GOARM { - case "5": - llvmarch = "armv5" - case "6": - llvmarch = "armv6" - case "7": - llvmarch = "armv7" - default: - return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM) - } - case "wasm": - llvmarch = "wasm32" - default: - llvmarch = options.GOARCH - } - llvmvendor := "unknown" - llvmos := options.GOOS - switch llvmos { - case "darwin": - // Use macosx* instead of darwin, otherwise darwin/arm64 will refer - // to iOS! - llvmos = "macosx10.12.0" - if llvmarch == "aarch64" { - // Looks like Apple prefers to call this architecture ARM64 - // instead of AArch64. - llvmarch = "arm64" - llvmos = "macosx11.0.0" - } - llvmvendor = "apple" - case "wasip1": - llvmos = "wasi" - } - // Target triples (which actually have four components, but are called - // triples for historical reasons) have the form: - // arch-vendor-os-environment - target := llvmarch + "-" + llvmvendor + "-" + llvmos - if options.GOOS == "windows" { - target += "-gnu" - } else if options.GOARCH == "arm" { - target += "-gnueabihf" - } - return defaultTarget(options.GOOS, options.GOARCH, target) + return defaultTarget(options) } // See whether there is a target specification for this target (e.g. @@ -283,75 +240,160 @@ func GetTargetSpecs() (map[string]*TargetSpec, error) { return maps, nil } -func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { - // No target spec available. Use the default one, useful on most systems - // with a regular OS. +// Load a target from environment variables (which default to +// runtime.GOOS/runtime.GOARCH). +func defaultTarget(options *Options) (*TargetSpec, error) { spec := TargetSpec{ - Triple: triple, - GOOS: goos, - GOARCH: goarch, - BuildTags: []string{goos, goarch}, - GC: "precise", + GOOS: options.GOOS, + GOARCH: options.GOARCH, + BuildTags: []string{options.GOOS, options.GOARCH}, Scheduler: "tasks", Linker: "cc", DefaultStackSize: 1024 * 64, // 64kB GDB: []string{"gdb"}, PortReset: "false", } - switch goarch { + + // Configure target based on GOARCH. + var llvmarch string + switch options.GOARCH { case "386": + llvmarch = "i386" spec.CPU = "pentium4" spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "amd64": + llvmarch = "x86_64" spec.CPU = "x86-64" spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "arm": spec.CPU = "generic" spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") - switch strings.Split(triple, "-")[0] { - case "armv5": - spec.Features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" - case "armv6": - spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" - case "armv7": - spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + subarch := strings.Split(options.GOARM, ",") + if len(subarch) > 2 { + return nil, fmt.Errorf("invalid GOARM=%s, must be of form ,[hardfloat|softfloat]", options.GOARM) + } + archLevel := subarch[0] + var fpu string + if len(subarch) >= 2 { + fpu = subarch[1] + } else { + // Pick the default fpu value: softfloat for armv5 and hardfloat + // above that. + if archLevel == "5" { + fpu = "softfloat" + } else { + fpu = "hardfloat" + } + } + switch fpu { + case "softfloat": + spec.CFlags = append(spec.CFlags, "-msoft-float") + spec.SoftFloat = true + case "hardfloat": + // Hardware floating point support is the default everywhere except + // on ARMv5 where it needs to be enabled explicitly. + if archLevel == "5" { + spec.CFlags = append(spec.CFlags, "-mfpu=vfpv2") + } + default: + return nil, fmt.Errorf("invalid extension GOARM=%s, must be softfloat or hardfloat", options.GOARM) + } + switch archLevel { + case "5": + llvmarch = "armv5" + if spec.SoftFloat { + spec.Features = "+armv5t,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } else { + spec.Features = "+armv5t,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } + case "6": + llvmarch = "armv6" + if spec.SoftFloat { + spec.Features = "+armv6,+dsp,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } else { + spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } + case "7": + llvmarch = "armv7" + if spec.SoftFloat { + spec.Features = "+armv7-a,+dsp,+soft-float,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } else { + spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } + default: + return nil, fmt.Errorf("invalid GOARM=%s, must be of form ,[hardfloat|softfloat] where num is 5, 6, or 7", options.GOARM) } case "arm64": spec.CPU = "generic" - if goos == "darwin" { - spec.Features = "+neon" - } else if goos == "windows" { - spec.Features = "+neon,-fmv" + llvmarch = "aarch64" + if options.GOOS == "darwin" { + spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a" + // Looks like Apple prefers to call this architecture ARM64 + // instead of AArch64. + llvmarch = "arm64" + } else if options.GOOS == "windows" { + spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv" } else { // linux - spec.Features = "+neon,-fmv,-outline-atomics" + spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv,-outline-atomics" + } + case "mips", "mipsle": + spec.CPU = "mips32" + spec.CFlags = append(spec.CFlags, "-fno-pic") + if options.GOARCH == "mips" { + llvmarch = "mips" // big endian + } else { + llvmarch = "mipsel" // little endian + } + switch options.GOMIPS { + case "hardfloat": + spec.Features = "+fpxx,+mips32,+nooddspreg,-noabicalls" + case "softfloat": + spec.SoftFloat = true + spec.Features = "+mips32,+soft-float,-noabicalls" + spec.CFlags = append(spec.CFlags, "-msoft-float") + default: + return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS) } case "wasm": - spec.CPU = "generic" - spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" - spec.BuildTags = append(spec.BuildTags, "tinygo.wasm") - spec.CFlags = append(spec.CFlags, - "-mbulk-memory", - "-mnontrapping-fptoint", - "-msign-ext", - ) + return nil, fmt.Errorf("GOARCH=wasm but GOOS is unset. Please set GOOS to wasm, wasip1, or wasip2.") + default: + return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH) } - if goos == "darwin" { + + // Configure target based on GOOS. + llvmos := options.GOOS + llvmvendor := "unknown" + switch options.GOOS { + case "darwin": + spec.GC = "precise" + platformVersion := "10.12.0" + if options.GOARCH == "arm64" { + platformVersion = "11.0.0" // first macosx platform with arm64 support + } + llvmvendor = "apple" spec.Linker = "ld.lld" spec.Libc = "darwin-libSystem" - arch := strings.Split(triple, "-")[0] - platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx") + // Use macosx* instead of darwin, otherwise darwin/arm64 will refer to + // iOS! + llvmos = "macosx" + platformVersion spec.LDFlags = append(spec.LDFlags, "-flavor", "darwin", "-dead_strip", - "-arch", arch, + "-arch", llvmarch, "-platform_version", "macos", platformVersion, platformVersion, ) - } else if goos == "linux" { + spec.ExtraFiles = append(spec.ExtraFiles, + "src/internal/futex/futex_darwin.c", + "src/runtime/os_darwin.c", + "src/runtime/runtime_unix.c", + "src/runtime/signal.c") + case "linux": + spec.GC = "boehm" spec.Linker = "ld.lld" spec.RTLib = "compiler-rt" spec.Libc = "musl" spec.LDFlags = append(spec.LDFlags, "--gc-sections") - if goarch == "arm64" { + if options.GOARCH == "arm64" { // Disable outline atomics. For details, see: // https://cpufun.substack.com/p/atomics-in-aarch64 // A better way would be to fully support outline atomics, which @@ -365,7 +407,12 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // proper threading. spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") } - } else if goos == "windows" { + spec.ExtraFiles = append(spec.ExtraFiles, + "src/internal/futex/futex_linux.c", + "src/runtime/runtime_unix.c", + "src/runtime/signal.c") + case "windows": + spec.GC = "precise" spec.Linker = "ld.lld" spec.Libc = "mingw-w64" // Note: using a medium code model, low image base and no ASLR @@ -374,7 +421,7 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { // normally present in Go (without explicitly opting in). // For more discussion: // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 - switch goarch { + switch options.GOARCH { case "amd64": spec.LDFlags = append(spec.LDFlags, "-m", "i386pep", @@ -391,40 +438,51 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { "--no-insert-timestamp", "--no-dynamicbase", ) - } else if goos == "wasip1" { - spec.GC = "" // use default GC - spec.Scheduler = "asyncify" - spec.Linker = "wasm-ld" - spec.RTLib = "compiler-rt" - spec.Libc = "wasi-libc" - spec.DefaultStackSize = 1024 * 64 // 64kB - spec.LDFlags = append(spec.LDFlags, - "--stack-first", - "--no-demangle", - ) - spec.Emulator = "wasmtime --dir={tmpDir}::/tmp {}" - spec.ExtraFiles = append(spec.ExtraFiles, - "src/runtime/asm_tinygowasm.S", - "src/internal/task/task_asyncify_wasm.S", - ) - } else { - spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie + case "wasm", "wasip1", "wasip2": + return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS) + default: + return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS) + } + + // Target triples (which actually have four components, but are called + // triples for historical reasons) have the form: + // arch-vendor-os-environment + spec.Triple = llvmarch + "-" + llvmvendor + "-" + llvmos + if options.GOOS == "windows" { + spec.Triple += "-gnu" + } else if options.GOOS == "linux" { + // We use musl on Linux (not glibc) so we should use -musleabi* instead + // of -gnueabi*. + // The *hf suffix selects between soft/hard floating point ABI. + if spec.SoftFloat { + spec.Triple += "-musleabi" + } else { + spec.Triple += "-musleabihf" + } } - if goarch != "wasm" { + + // Add extra assembly files (needed for the scheduler etc). + if options.GOARCH != "wasm" { suffix := "" - if goos == "windows" && goarch == "amd64" { + if options.GOOS == "windows" && options.GOARCH == "amd64" { // Windows uses a different calling convention on amd64 from other // operating systems so we need separate assembly files. suffix = "_windows" } - spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S") - spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S") + asmGoarch := options.GOARCH + if options.GOARCH == "mips" || options.GOARCH == "mipsle" { + asmGoarch = "mipsx" + } + spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S") + spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S") } - if goarch != runtime.GOARCH { + + // Configure the emulator. + if options.GOARCH != runtime.GOARCH { // Some educated guesses as to how to invoke helper programs. spec.GDB = []string{"gdb-multiarch"} - if goos == "linux" { - switch goarch { + if options.GOOS == "linux" { + switch options.GOARCH { case "386": // amd64 can _usually_ run 32-bit programs, so skip the emulator in that case. if runtime.GOARCH != "amd64" { @@ -436,14 +494,19 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { spec.Emulator = "qemu-arm {}" case "arm64": spec.Emulator = "qemu-aarch64 {}" + case "mips": + spec.Emulator = "qemu-mips {}" + case "mipsle": + spec.Emulator = "qemu-mipsel {}" } } } - if goos != runtime.GOOS { - if goos == "windows" { + if options.GOOS != runtime.GOOS { + if options.GOOS == "windows" { spec.Emulator = "wine {}" } } + return &spec, nil } diff --git a/compiler/alias.go b/compiler/alias.go index b16cbce863..9d57a587e7 100644 --- a/compiler/alias.go +++ b/compiler/alias.go @@ -16,13 +16,18 @@ import "tinygo.org/x/go-llvm" var stdlibAliases = map[string]string{ // crypto packages - "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", - "crypto/ed25519/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", - "crypto/md5.block": "crypto/md5.blockGeneric", - "crypto/sha1.block": "crypto/sha1.blockGeneric", - "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", - "crypto/sha256.block": "crypto/sha256.blockGeneric", - "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", + "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", + "crypto/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", + "crypto/md5.block": "crypto/md5.blockGeneric", + "crypto/sha1.block": "crypto/sha1.blockGeneric", + "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", + "crypto/sha256.block": "crypto/sha256.blockGeneric", + "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", + "internal/chacha8rand.block": "internal/chacha8rand.block_generic", + + // AES + "crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock", + "crypto/aes.encryptBlockAsm": "crypto/aes.encryptBlock", // math package "math.archHypot": "math.hypot", diff --git a/compiler/asserts.go b/compiler/asserts.go index 0fb112e0bc..f07b73bc26 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -99,7 +99,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, // However, in practice, it is also necessary to check that the length is // not too big that a GEP wouldn't be possible without wrapping the pointer. // These two checks (non-negative and not too big) can be merged into one - // using an unsiged greater than. + // using an unsigned greater than. // Make sure the len value is at least as big as a uintptr. len = b.extendInteger(len, lenType, b.uintptrType) @@ -135,7 +135,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, // Calculate (^uintptr(0)) >> 1, which is the max value that fits in an // uintptr if uintptrs were signed. - maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false)) + maxBufSize := b.CreateLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false), "") if elementSize > maxBufSize.ZExtValue() { b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize)) return @@ -150,7 +150,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, // Make sure maxBufSize has the same type as bufSize. if maxBufSize.Type() != bufSize.Type() { - maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type()) + maxBufSize = b.CreateZExt(maxBufSize, bufSize.Type(), "") } // Do the check for a too large (or negative) buffer size. diff --git a/compiler/atomic.go b/compiler/atomic.go index 006da5ef8b..496e3a2c9f 100644 --- a/compiler/atomic.go +++ b/compiler/atomic.go @@ -1,9 +1,6 @@ package compiler import ( - "fmt" - "strings" - "tinygo.org/x/go-llvm" ) @@ -15,23 +12,19 @@ func (b *builder) createAtomicOp(name string) llvm.Value { case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) - if strings.HasPrefix(b.Triple, "avr") { - // AtomicRMW does not work on AVR as intended: - // - There are some register allocation issues (fixed by https://reviews.llvm.org/D97127 which is not yet in a usable LLVM release) - // - The result is the new value instead of the old value - vType := val.Type() - name := fmt.Sprintf("__sync_fetch_and_add_%d", vType.IntTypeWidth()/8) - fn := b.mod.NamedFunction(name) - if fn.IsNil() { - fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType}, false)) - } - oldVal := b.createCall(fn.GlobalValueType(), fn, []llvm.Value{ptr, val}, "") - // Return the new value, not the original value returned. - return b.CreateAdd(oldVal, val, "") - } oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) // Return the new value, not the original value returned by atomicrmw. return b.CreateAdd(oldVal, val, "") + case "AndInt32", "AndInt64", "AndUint32", "AndUint64", "AndUintptr": + ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) + val := b.getValue(b.fn.Params[1], getPos(b.fn)) + oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAnd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + return oldVal + case "OrInt32", "OrInt64", "OrUint32", "OrUint64", "OrUintptr": + ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) + val := b.getValue(b.fn.Params[1], getPos(b.fn)) + oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpOr, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + return oldVal case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) val := b.getValue(b.fn.Params[1], getPos(b.fn)) diff --git a/compiler/calls.go b/compiler/calls.go index f4b76a5135..6400e634bd 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -19,8 +19,9 @@ const maxFieldsPerParam = 3 // useful while declaring or defining a function. type paramInfo struct { llvmType llvm.Type - name string // name, possibly with suffixes for e.g. struct fields - elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer + name string // name, possibly with suffixes for e.g. struct fields + elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer + flags paramFlags // extra flags for this parameter } // paramFlags identifies parameter attributes for flags. Most importantly, it @@ -28,9 +29,9 @@ type paramInfo struct { type paramFlags uint8 const ( - // Parameter may have the deferenceable_or_null attribute. This attribute - // cannot be applied to unsafe.Pointer and to the data pointer of slices. - paramIsDeferenceableOrNull = 1 << iota + // Whether this is a full or partial Go parameter (int, slice, etc). + // The extra context parameter is not a Go parameter. + paramIsGoParam = 1 << iota ) // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or @@ -195,6 +196,7 @@ func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Ty info := paramInfo{ llvmType: t, name: name, + flags: paramIsGoParam, } if goType != nil { switch underlying := goType.Underlying().(type) { diff --git a/compiler/channel.go b/compiler/channel.go index 9969835e84..0ff2ab7f32 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -4,7 +4,9 @@ package compiler // or pseudo-operations that are lowered during goroutine lowering. import ( + "fmt" "go/types" + "math" "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" @@ -41,17 +43,17 @@ func (b *builder) createChanSend(instr *ssa.Send) { b.CreateStore(chanValue, valueAlloca) } - // Allocate blockedlist buffer. - channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") - channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") + // Allocate buffer for the channel operation. + channelOp := b.getLLVMRuntimeType("channelOp") + channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the send. - b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") + b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") // End the lifetime of the allocas. // This also works around a bug in CoroSplit, at least in LLVM 8: // https://bugs.llvm.org/show_bug.cgi?id=41742 - b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) + b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if !isZeroSize { b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } @@ -72,12 +74,12 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") } - // Allocate blockedlist buffer. - channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") - channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") + // Allocate buffer for the channel operation. + channelOp := b.getLLVMRuntimeType("channelOp") + channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") // Do the receive. - commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") var received llvm.Value if isZeroSize { received = llvm.ConstNull(valueType) @@ -85,7 +87,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { received = b.CreateLoad(valueType, valueAlloca, "chan.received") b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } - b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) + b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if unop.CommaOk { tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) @@ -124,6 +126,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { } } + const maxSelectStates = math.MaxUint32 >> 2 + if len(expr.States) > maxSelectStates { + // The runtime code assumes that the number of state must fit in 30 bits + // (so the select index can be stored in a uint32 with two bits reserved + // for other purposes). It seems unlikely that a real program would have + // that many states, but we check for this case anyway to be sure. + // We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic + // operations which aren't available everywhere. + b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates)) + + // Continue as usual (we'll generate broken code but the error will + // prevent the compilation to complete). + } + // This code create a (stack-allocated) slice containing all the select // cases and then calls runtime.chanSelect to perform the actual select // statement. @@ -198,10 +214,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { if expr.Blocking { // Stack-allocate operation structures. // If these were simply created as a slice, they would heap-allocate. - chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) - chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") - chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) - chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{ + opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates)) + opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca") + opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) + opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false), }, "select.block") @@ -209,15 +225,18 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState - chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList + opsPtr, opsLen, opsLen, // []channelOp }, "select.result") // Terminate the lifetime of the operation structures. - b.emitLifetimeEnd(chBlockAlloca, chBlockSize) + b.emitLifetimeEnd(opsAlloca, opsSize) } else { - results = b.createRuntimeCall("tryChanSelect", []llvm.Value{ + opsPtr := llvm.ConstNull(b.dataPtrType) + opsLen := llvm.ConstInt(b.uintptrType, 0, false) + results = b.createRuntimeCall("chanSelect", []llvm.Value{ recvbuf, statesPtr, statesLen, statesLen, // []chanSelectState + opsPtr, opsLen, opsLen, // []channelOp (nil slice) }, "select.result") } diff --git a/compiler/compiler.go b/compiler/compiler.go index 2ebd5a9f22..28ec312dd0 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -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" @@ -44,6 +45,7 @@ type Config struct { ABI string GOOS string GOARCH string + BuildMode string CodeModel string RelocationModel string SizeLevel int @@ -56,6 +58,7 @@ type Config struct { MaxStackAlloc uint64 NeedsStackObjects bool Debug bool // Whether to emit debug information in the LLVM module. + PanicStrategy string } // compilerContext contains function-independent data that should still be @@ -241,7 +244,7 @@ func NewTargetMachine(config *Config) (llvm.TargetMachine, error) { } // Sizes returns a types.Sizes appropriate for the given target machine. It -// includes the correct int size and aligment as is necessary for the Go +// includes the correct int size and alignment as is necessary for the Go // typechecker. func Sizes(machine llvm.TargetMachine) types.Sizes { targetData := machine.CreateTargetData() @@ -1383,6 +1386,11 @@ func (b *builder) createFunction() { b.llvmFn.SetLinkage(llvm.InternalLinkage) b.createFunction() } + + // Create wrapper function that can be called externally. + if b.info.wasmExport != "" { + b.createWasmExport() + } } // posser is an interface that's implemented by both ssa.Value and @@ -1673,7 +1681,12 @@ 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": + b.createRuntimeCall("printlock", nil, "") for i, value := range argValues { if i >= 1 && callName == "println" { b.createRuntimeCall("printspace", nil, "") @@ -1734,6 +1747,7 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c if callName == "println" { b.createRuntimeCall("printnl", nil, "") } + b.createRuntimeCall("printunlock", nil, "") return llvm.Value{}, nil // print() or println() returns void case "real": cplx := argValues[0] @@ -1845,9 +1859,11 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.emitSV64Call(instr.Args, getPos(instr)) case strings.HasPrefix(name, "(device/riscv.CSR)."): return b.emitCSROperation(instr) - case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall"): - return b.createSyscall(instr) - case strings.HasPrefix(name, "syscall.rawSyscallNoError"): + case strings.HasPrefix(name, "syscall.Syscall") || strings.HasPrefix(name, "syscall.RawSyscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.Syscall") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscall"): + if b.GOOS != "darwin" { + return b.createSyscall(instr) + } + case strings.HasPrefix(name, "syscall.rawSyscallNoError") || strings.HasPrefix(name, "golang.org/x/sys/unix.RawSyscallNoError"): return b.createRawSyscallNoError(instr) case name == "runtime.supportsRecover": supportsRecover := uint64(0) @@ -1855,8 +1871,19 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) supportsRecover = 1 } return llvm.ConstInt(b.ctx.Int1Type(), supportsRecover, false), nil + case name == "runtime.panicStrategy": + panicStrategy := map[string]uint64{ + "print": tinygo.PanicStrategyPrint, + "trap": tinygo.PanicStrategyTrap, + }[b.Config.PanicStrategy] + return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil case name == "runtime/interrupt.New": return b.createInterruptGlobal(instr) + case name == "internal/abi.FuncPCABI0": + retval := b.createDarwinFuncPCABI0Call(instr) + if !retval.IsNil() { + return retval, nil + } } calleeType, callee = b.getFunction(fn) @@ -1955,7 +1982,7 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value { return value } else { // indicates a compiler bug - panic("local has not been parsed: " + expr.String()) + panic("SSA value not previously found in function: " + expr.String()) } } } @@ -2009,6 +2036,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { sizeValue := llvm.ConstInt(b.uintptrType, size, false) layoutValue := b.createObjectLayout(typ, expr.Pos()) buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment) + align := b.targetData.ABITypeAlignment(typ) + buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align))) return buf, nil } else { buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment) @@ -2173,7 +2202,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+ptrTyp.String()) } - // Make sure index is at least the size of uintptr becuase getelementptr + // Make sure index is at least the size of uintptr because getelementptr // assumes index is a signed integer. index = b.extendInteger(index, expr.Index.Type(), b.uintptrType) @@ -2215,6 +2244,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { sliceType := expr.Type().Underlying().(*types.Slice) llvmElemType := b.getLLVMType(sliceType.Elem()) elemSize := b.targetData.TypeAllocSize(llvmElemType) + elemAlign := b.targetData.ABITypeAlignment(llvmElemType) elemSizeValue := llvm.ConstInt(b.uintptrType, elemSize, false) maxSize := b.maxSliceSize(llvmElemType) @@ -2238,6 +2268,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf") + slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign))) // Extend or truncate if necessary. This is safe as we've already done // the bounds check. @@ -2549,7 +2580,7 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va sizeY := b.targetData.TypeAllocSize(y.Type()) // Check if the shift is bigger than the bit-width of the shifted value. - // This is UB in LLVM, so it needs to be handled seperately. + // This is UB in LLVM, so it needs to be handled separately. // The Go spec indirectly defines the result as 0. // Negative shifts are handled earlier, so we can treat y as unsigned. overshifted := b.CreateICmp(llvm.IntUGE, y, llvm.ConstInt(y.Type(), 8*sizeX, false), "shift.overflow") diff --git a/compiler/defer.go b/compiler/defer.go index e1ff2f58ed..2ca76a8325 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -16,6 +16,7 @@ package compiler import ( "go/types" "strconv" + "strings" "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" @@ -160,7 +161,7 @@ str x2, [x1, #8] mov x0, #0 1: ` - constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}" + constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{memory}" if b.GOOS != "darwin" && b.GOOS != "windows" { // These registers cause the following warning when compiling for // MacOS and Windows: @@ -187,6 +188,24 @@ std z+5, r29 ldi r24, 0 1:` constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}" + case "mips": + // $4 flag (zero or non-zero) + // $5 defer frame + asmString = ` +.set noat +move $$4, $$zero +jal 1f +1: +addiu $$ra, 8 +sw $$ra, 4($$5) +.set at` + constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{memory}" + if !strings.Contains(b.Features, "+soft-float") { + // Using floating point registers together with GOMIPS=softfloat + // results in a crash: "This value type is not natively supported!" + // So only add them when using hardfloat. + constraints += ",~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31}" + } case "riscv32": asmString = ` la a2, 1f diff --git a/compiler/gc.go b/compiler/gc.go index 9d568a174b..fc0e6e687f 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -78,7 +78,7 @@ func (b *builder) trackValue(value llvm.Value) { } } -// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner +// trackPointer creates a call to runtime.trackPointer, bitcasting the pointer // first if needed. The input value must be of LLVM pointer type. func (b *builder) trackPointer(value llvm.Value) { b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "") diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 95abc77ff9..701797152c 100644 --- a/compiler/goroutine.go +++ b/compiler/goroutine.go @@ -7,43 +7,14 @@ import ( "go/token" "go/types" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // createGo emits code to start a new goroutine. func (b *builder) createGo(instr *ssa.Go) { - // Get all function parameters to pass to the goroutine. - var params []llvm.Value - for _, param := range instr.Call.Args { - params = append(params, b.getValue(param, getPos(instr))) - } - - var prefix string - var funcPtr llvm.Value - var funcType llvm.Type - hasContext := false - if callee := instr.Call.StaticCallee(); callee != nil { - // Static callee is known. This makes it easier to start a new - // goroutine. - var context llvm.Value - switch value := instr.Call.Value.(type) { - case *ssa.Function: - // Goroutine call is regular function call. No context is necessary. - case *ssa.MakeClosure: - // A goroutine call on a func value, but the callee is trivial to find. For - // example: immediately applied functions. - funcValue := b.getValue(value, getPos(instr)) - context = b.extractFuncContext(funcValue) - default: - panic("StaticCallee returned an unexpected value") - } - if !context.IsNil() { - params = append(params, context) // context parameter - hasContext = true - } - funcType, funcPtr = b.getFunction(callee) - } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { + if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { // We cheat. None of the builtins do any long or blocking operation, so // we might as well run these builtins right away without the program // noticing the difference. @@ -74,6 +45,38 @@ func (b *builder) createGo(instr *ssa.Go) { } b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos()) return + } + + // Get all function parameters to pass to the goroutine. + var params []llvm.Value + for _, param := range instr.Call.Args { + params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...) + } + + var prefix string + var funcPtr llvm.Value + var funcType llvm.Type + hasContext := false + if callee := instr.Call.StaticCallee(); callee != nil { + // Static callee is known. This makes it easier to start a new + // goroutine. + var context llvm.Value + switch value := instr.Call.Value.(type) { + case *ssa.Function: + // Goroutine call is regular function call. No context is necessary. + case *ssa.MakeClosure: + // A goroutine call on a func value, but the callee is trivial to find. For + // example: immediately applied functions. + funcValue := b.getValue(value, getPos(instr)) + context = b.extractFuncContext(funcValue) + default: + panic("StaticCallee returned an unexpected value") + } + if !context.IsNil() { + params = append(params, context) // context parameter + hasContext = true + } + funcType, funcPtr = b.getFunction(callee) } else if instr.Call.IsInvoke() { // This is a method call on an interface value. itf := b.getValue(instr.Call.Value, getPos(instr)) @@ -99,7 +102,7 @@ func (b *builder) createGo(instr *ssa.Go) { paramBundle := b.emitPointerPack(params) var stackSize llvm.Value - callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos()) + callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos()) if b.AutomaticStackSize { // The stack size is not known until after linking. Call a dummy // function that will be replaced with a load from a special ELF @@ -119,6 +122,147 @@ func (b *builder) createGo(instr *ssa.Go) { b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "") } +// Create an exported wrapper function for functions with the //go:wasmexport +// pragma. This wrapper function is quite complex when the scheduler is enabled: +// it needs to start a new goroutine each time the exported function is called. +func (b *builder) createWasmExport() { + pos := b.info.wasmExportPos + if b.info.exported { + // //export really shouldn't be used anymore when //go:wasmexport is + // available, because //go:wasmexport is much better defined. + b.addError(pos, "cannot use //export and //go:wasmexport at the same time") + return + } + + const suffix = "#wasmexport" + + // Declare the exported function. + paramTypes := b.llvmFnType.ParamTypes() + exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false) + exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType) + b.addStandardAttributes(exportedFn) + llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn) + exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport)) + + // Create a builder for this wrapper function. + builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn) + defer builder.Dispose() + + // Define this function as a separate function in DWARF + if b.Debug { + if b.fn.Syntax() != nil { + // Create debug info file if needed. + pos := b.program.Fset.Position(pos) + builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line) + } + builder.setDebugLocation(pos) + } + + // Create a single basic block inside of it. + bb := llvm.AddBasicBlock(exportedFn, "entry") + builder.SetInsertPointAtEnd(bb) + + // Insert an assertion to make sure this //go:wasmexport function is not + // called at a time when it is not allowed (for example, before the runtime + // is initialized). + builder.createRuntimeCall("wasmExportCheckRun", nil, "") + + if b.Scheduler == "none" { + // When the scheduler has been disabled, this is really trivial: just + // call the function. + params := exportedFn.Params() + params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter + retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "") + if b.fn.Signature.Results() == nil { + builder.CreateRetVoid() + } else { + builder.CreateRet(retval) + } + + } else { + // The scheduler is enabled, so we need to start a new goroutine, wait + // for it to complete, and read the result value. + + // Build a function that looks like this: + // + // func foo#wasmexport(param0, param1, ..., paramN) { + // var state *stateStruct + // + // // 'done' must be explicitly initialized ('state' is not zeroed) + // state.done = false + // + // // store the parameters in the state object + // state.param0 = param0 + // state.param1 = param1 + // ... + // state.paramN = paramN + // + // // create a goroutine and push it to the runqueue + // task.start(uintptr(gowrapper), &state) + // + // // run the scheduler + // runtime.wasmExportRun(&state.done) + // + // // if there is a return value, load it and return + // return state.result + // } + + hasReturn := b.fn.Signature.Results() != nil + + // Build the state struct type. + // It stores the function parameters, the 'done' flag, and reserves + // space for a return value if needed. + stateFields := exportedFnType.ParamTypes() + numParams := len(stateFields) + stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field + if hasReturn { + stateFields = append(stateFields, b.llvmFnType.ReturnType()) + } + stateStruct := b.ctx.StructType(stateFields, false) + + // Allocate the state struct on the stack. + statePtr := builder.CreateAlloca(stateStruct, "status") + + // Initialize the 'done' field. + doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false), + }, "done.gep") + builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP) + + // Store all parameters in the state object. + for i, param := range exportedFn.Params() { + gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + }, "") + builder.CreateStore(param, gep) + } + + // Create a new goroutine and add it to the runqueue. + wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos) + stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) + taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) + builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "") + + // Run the scheduler. + builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "") + + // Read the return value (if any) and return to the caller of the + // //go:wasmexport function. + if hasReturn { + gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false), + }, "") + retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval") + builder.CreateRet(retval) + } else { + builder.CreateRetVoid() + } + } +} + // createGoroutineStartWrapper creates a wrapper for the task-based // implementation of goroutines. For example, to call a function like this: // @@ -142,7 +286,7 @@ func (b *builder) createGo(instr *ssa.Go) { // to last parameter of the function) is used for this wrapper. If hasContext is // false, the parameter bundle is assumed to have no context parameter and undef // is passed instead. -func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value { +func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value { var wrapper llvm.Value b := &builder{ @@ -160,14 +304,18 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. if !fn.IsAFunction().IsNil() { // See whether this wrapper has already been created. If so, return it. name := fn.Name() - wrapper = c.mod.NamedFunction(name + "$gowrapper") + wrapperName := name + "$gowrapper" + if isWasmExport { + wrapperName += "-wasmexport" + } + wrapper = c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { return llvm.ConstPtrToInt(wrapper, c.uintptrType) } // Create the wrapper. wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) - wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) + wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) @@ -197,23 +345,110 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } - // Create the list of params for the call. - paramTypes := fnType.ParamTypes() - if !hasContext { - paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter - } - params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) - if !hasContext { - params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter - } + if !isWasmExport { + // Regular 'go' instruction. - // Create the call. - b.CreateCall(fnType, fn, params, "") + // Create the list of params for the call. + paramTypes := fnType.ParamTypes() + if !hasContext { + paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter + } - if c.Scheduler == "asyncify" { - b.CreateCall(deadlockType, deadlock, []llvm.Value{ - llvm.Undef(c.dataPtrType), - }, "") + params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) + if !hasContext { + params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter + } + + // Create the call. + b.CreateCall(fnType, fn, params, "") + + if c.Scheduler == "asyncify" { + b.CreateCall(deadlockType, deadlock, []llvm.Value{ + llvm.Undef(c.dataPtrType), + }, "") + } + } else { + // Goroutine started from a //go:wasmexport pragma. + // The function looks like this: + // + // func foo$gowrapper-wasmexport(state *stateStruct) { + // // load values + // param0 := state.params[0] + // param1 := state.params[1] + // + // // call wrapped functions + // result := foo(param0, param1, ...) + // + // // store result value (if there is any) + // state.result = result + // + // // finish exported function + // state.done = true + // runtime.wasmExportExit() + // } + // + // The state object here looks like: + // + // struct state { + // param0 + // param1 + // param* // etc + // done bool + // result returnType + // } + + returnType := fnType.ReturnType() + hasReturn := returnType != b.ctx.VoidType() + statePtr := wrapper.Param(0) + + // Create the state struct (it must match the type in createWasmExport). + stateFields := fnType.ParamTypes() + numParams := len(stateFields) - 1 + stateFields = stateFields[:numParams:numParams] // strip 'context' parameter + stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool + if hasReturn { + stateFields = append(stateFields, returnType) + } + stateStruct := b.ctx.StructType(stateFields, false) + + // Extract parameters from the state object, and call the function + // that's being wrapped. + var callParams []llvm.Value + for i := 0; i < numParams; i++ { + gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(b.ctx.Int32Type(), 0, false), + llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), + }, "") + param := b.CreateLoad(stateFields[i], gep, "") + callParams = append(callParams, param) + } + callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter + result := b.CreateCall(fnType, fn, callParams, "") + + // Store the return value back into the shared state. + // Unlike regular goroutines, these special //go:wasmexport + // goroutines can return a value. + if hasReturn { + gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false), + }, "result.ptr") + b.CreateStore(result, gep) + } + + // Mark this function as having finished executing. + // This is important so the runtime knows the exported function + // didn't block. + doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false), + }, "done.gep") + b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP) + + // Call back into the runtime. This will exit the goroutine, switch + // back to the scheduler, which will in turn return from the + // //go:wasmexport function. + b.createRuntimeCall("wasmExportExit", nil, "") } } else { @@ -295,5 +530,5 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. } // Return a ptrtoint of the wrapper, not the function itself. - return b.CreatePtrToInt(wrapper, c.uintptrType, "") + return llvm.ConstPtrToInt(wrapper, c.uintptrType) } diff --git a/compiler/interface.go b/compiler/interface.go index 564e3a4146..dffaeec0ad 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -86,7 +86,7 @@ func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token. // extractValueFromInterface extract the value from an interface value // (runtime._interface) under the assumption that it is of the type given in -// llvmType. The behavior is undefied if the interface is nil or llvmType +// llvmType. The behavior is undefined if the interface is nil or llvmType // doesn't match the underlying type of the interface. func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value { valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") @@ -124,16 +124,19 @@ func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value { func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { ms := c.program.MethodSets.MethodSet(typ) hasMethodSet := ms.Len() != 0 - if _, ok := typ.Underlying().(*types.Interface); ok { + _, isInterface := typ.Underlying().(*types.Interface) + if isInterface { hasMethodSet = false } + // As defined in https://pkg.go.dev/reflect#Type: + // NumMethod returns the number of methods accessible using Method. + // For a non-interface type, it returns the number of exported methods. + // For an interface type, it returns the number of exported and unexported methods. var numMethods int - if hasMethodSet { - for i := 0; i < ms.Len(); i++ { - if ms.At(i).Obj().Exported() { - numMethods++ - } + for i := 0; i < ms.Len(); i++ { + if isInterface || ms.At(i).Obj().Exported() { + numMethods++ } } @@ -511,8 +514,7 @@ var basicTypeNames = [...]string{ func getTypeCodeName(t types.Type) (string, bool) { switch t := t.(type) { case *types.Named: - // Note: check for `t.Obj().Pkg() != nil` for Go 1.18 only. - if t.Obj().Pkg() != nil && t.Obj().Parent() != t.Obj().Pkg().Scope() { + if t.Obj().Parent() != t.Obj().Pkg().Scope() { return "named:" + t.String() + "$local", true } return "named:" + t.String(), false diff --git a/compiler/interrupt.go b/compiler/interrupt.go index 6ba031819a..68c8a36058 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -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, "") diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index c1d05348b5..3c7edd7c95 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -23,6 +23,8 @@ func (b *builder) defineIntrinsicFunction() { b.createMemoryCopyImpl() case name == "runtime.memzero": b.createMemoryZeroImpl() + case name == "runtime.stacksave": + b.createStackSaveImpl() case name == "runtime.KeepAlive": b.createKeepAliveImpl() case strings.HasPrefix(name, "runtime/volatile.Load"): @@ -77,6 +79,14 @@ func (b *builder) createMemoryZeroImpl() { b.CreateRetVoid() } +// createStackSaveImpl creates a call to llvm.stacksave.p0 to read the current +// stack pointer. +func (b *builder) createStackSaveImpl() { + b.createFunctionStart(true) + sp := b.readStackPointer() + b.CreateRet(sp) +} + // Return the llvm.memset.p0.i8 function declaration. func (c *compilerContext) getMemsetFunc() llvm.Value { fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) diff --git a/compiler/llvm.go b/compiler/llvm.go index 968d28b88b..139c5a1cd8 100644 --- a/compiler/llvm.go +++ b/compiler/llvm.go @@ -7,6 +7,7 @@ import ( "math/big" "strings" + "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -127,12 +128,14 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { // Packed data is bigger than a pointer, so allocate it on the heap. sizeValue := llvm.ConstInt(b.uintptrType, size, false) + align := b.targetData.ABITypeAlignment(packedType) alloc := b.mod.NamedFunction("runtime.alloc") packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ sizeValue, llvm.ConstNull(b.dataPtrType), llvm.Undef(b.dataPtrType), // unused context parameter }, "") + packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align))) if b.NeedsStackObjects { b.trackPointer(packedAlloc) } @@ -368,13 +371,6 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In return big.NewInt(1) case llvm.StructTypeKind: ptrs := big.NewInt(0) - if typ.StructName() == "runtime.funcValue" { - // Hack: the type runtime.funcValue contains an 'id' field which is - // of type uintptr, but before the LowerFuncValues pass it actually - // contains a pointer (ptrtoint) to a global. This trips up the - // interp package. Therefore, make the id field a pointer for now. - typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false) - } for i, subtyp := range typ.StructElementTypes() { subptrs := c.getPointerBitmap(subtyp, pos) if subptrs.BitLen() == 0 { @@ -416,18 +412,11 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In } } -// archFamily returns the archtecture from the LLVM triple but with some +// archFamily returns the architecture from the LLVM triple but with some // architecture names ("armv6", "thumbv7m", etc) merged into a single // architecture name ("arm"). func (c *compilerContext) archFamily() string { - arch := strings.Split(c.Triple, "-")[0] - if strings.HasPrefix(arch, "arm64") { - return "aarch64" - } - if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { - return "arm" - } - return arch + return compileopts.CanonicalArchName(c.Triple) } // isThumb returns whether we're in ARM or in Thumb mode. It panics if the @@ -451,10 +440,14 @@ func (c *compilerContext) isThumb() bool { // readStackPointer emits a LLVM intrinsic call that returns the current stack // pointer as an *i8. func (b *builder) readStackPointer() llvm.Value { - stacksave := b.mod.NamedFunction("llvm.stacksave") + name := "llvm.stacksave.p0" + if llvmutil.Version() < 18 { + name = "llvm.stacksave" // backwards compatibility with LLVM 17 and below + } + stacksave := b.mod.NamedFunction(name) if stacksave.IsNil() { fnType := llvm.FunctionType(b.dataPtrType, nil, false) - stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType) + stacksave = llvm.AddFunction(b.mod, name, fnType) } return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "") } diff --git a/compiler/llvmutil/llvm.go b/compiler/llvmutil/llvm.go index c07cd0560b..061bee6c9a 100644 --- a/compiler/llvmutil/llvm.go +++ b/compiler/llvmutil/llvm.go @@ -1,5 +1,5 @@ // Package llvmutil contains utility functions used across multiple compiler -// packages. For example, they may be used by both the compiler pacakge and +// packages. For example, they may be used by both the compiler package and // transformation packages. // // Normally, utility packages are avoided. However, in this case, the utility @@ -8,6 +8,10 @@ package llvmutil import ( + "encoding/binary" + "strconv" + "strings" + "tinygo.org/x/go-llvm" ) @@ -28,7 +32,7 @@ func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm } // CreateTemporaryAlloca creates a new alloca in the entry block and adds -// lifetime start infromation in the IR signalling that the alloca won't be used +// lifetime start information in the IR signalling that the alloca won't be used // before this point. // // This is useful for creating temporary allocas for intrinsics. Don't forget to @@ -173,7 +177,7 @@ func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llv return newBlock } -// Append the given values to a global array like llvm.used. The global might +// AppendToGlobal appends the given values to a global array like llvm.used. The global might // not exist yet. The values can be any pointer type, they will be cast to i8*. func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { // Read the existing values in the llvm.used array (if it exists). @@ -203,3 +207,23 @@ func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { used.SetInitializer(usedInitializer) used.SetLinkage(llvm.AppendingLinkage) } + +// Version returns the LLVM major version. +func Version() int { + majorStr := strings.Split(llvm.Version, ".")[0] + major, err := strconv.Atoi(majorStr) + if err != nil { + panic("unexpected error while parsing LLVM version: " + err.Error()) // should not happen + } + return major +} + +// Return the byte order for the given target triple. Most targets are little +// endian, but for example MIPS can be big-endian. +func ByteOrder(target string) binary.ByteOrder { + if strings.HasPrefix(target, "mips-") { + return binary.BigEndian + } else { + return binary.LittleEndian + } +} diff --git a/compiler/map.go b/compiler/map.go index 1c124c2b20..71fb3f0da8 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -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) @@ -326,7 +320,7 @@ func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error { if i < numFields-1 { nextOffset = b.targetData.ElementOffset(llvmStructType, i+1) } else { - // Last field? Next offset is the total size of the allcoate struct. + // Last field? Next offset is the total size of the allocate struct. nextOffset = b.targetData.TypeAllocSize(llvmStructType) } diff --git a/compiler/symbol.go b/compiler/symbol.go index bf5ac5f1b7..1226683d57 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/tinygo-org/tinygo/compiler/llvmutil" + "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -23,15 +24,18 @@ import ( // The linkName value contains a valid link name, even if //go:linkname is not // present. type functionInfo struct { - wasmModule string // go:wasm-module - wasmName string // wasm-export-name or wasm-import-name in the IR - linkName string // go:linkname, go:export - the IR function name - section string // go:section - object file section name - exported bool // go:export, CGo - interrupt bool // go:interrupt - nobounds bool // go:nobounds - variadic bool // go:variadic (CGo only) - inline inlineType // go:inline + wasmModule string // go:wasm-module + wasmName string // wasm-export-name or wasm-import-name in the IR + wasmExport string // go:wasmexport is defined (export is unset, this adds an exported wrapper) + wasmExportPos token.Pos // position of //go:wasmexport comment + linkName string // go:linkname, go:export - the IR function name + section string // go:section - object file section name + exported bool // go:export, CGo + interrupt bool // go:interrupt + nobounds bool // go:nobounds + noescape bool // go:noescape + variadic bool // go:variadic (CGo only) + inline inlineType // go:inline } type inlineType int @@ -124,11 +128,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) c.addStandardDeclaredAttributes(llvmFn) dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") - for i, info := range paramInfos { - if info.elemSize != 0 { - dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize) + for i, paramInfo := range paramInfos { + if paramInfo.elemSize != 0 { + dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize) llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) } + if info.noescape && paramInfo.flags¶mIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind { + // Parameters to functions with a //go:noescape parameter should get + // the nocapture attribute. However, the context parameter should + // not. + // (It may be safe to add the nocapture parameter to the context + // parameter, but I'd like to stay on the safe side here). + nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0) + llvmFn.AddAttributeAtIndex(i+1, nocapture) + } } // Set a number of function or parameter attributes, depending on the @@ -139,6 +152,8 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // On *nix systems, the "abort" functuion in libc is used to handle fatal panics. // Mark it as noreturn so LLVM can optimize away code. llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0)) + case "internal/abi.NoEscape": + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) case "runtime.alloc": // Tell the optimizer that runtime.alloc is an allocator, meaning that it // returns values that are never null and never alias to an existing value. @@ -168,6 +183,12 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) + case "runtime.stringFromBytes": + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) + case "runtime.stringFromRunes": + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) + llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) case "runtime.trackPointer": // This function is necessary for tracking pointers on the stack in a // portable way (see gc_stack_portable.go). Indicate to the optimizer @@ -210,13 +231,26 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) } } + // Build the function if needed. + c.maybeCreateSyntheticFunction(fn, llvmFn) + + return fnType, llvmFn +} + +// If this is a synthetic function (such as a generic function or a wrapper), +// create it now. +func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn llvm.Value) { // Synthetic functions are functions that do not appear in the source code, // they are artificially constructed. Usually they are wrapper functions // that are not referenced anywhere except in a SSA call instruction so // should be created right away. // The exception is the package initializer, which does appear in the // *ssa.Package members and so shouldn't be created here. - if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" { + if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" { + if len(fn.Blocks) == 0 { + c.addError(fn.Pos(), "missing function body") + return + } irbuilder := c.ctx.NewBuilder() b := newBuilder(c, irbuilder, fn) b.createFunction() @@ -224,8 +258,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) llvmFn.SetLinkage(llvm.LinkOnceODRLinkage) llvmFn.SetUnnamedAddr(true) } - - return fnType, llvmFn } // getFunctionInfo returns information about a function that is not directly @@ -239,8 +271,27 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { // Pick the default linkName. linkName: f.RelString(nil), } + + // Check for a few runtime functions that are treated specially. + if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" { + info.linkName = "_initialize" + info.wasmName = "_initialize" + info.exported = true + } + if info.linkName == "runtime.wasmEntryCommand" && c.BuildMode == "default" { + info.linkName = "_start" + info.wasmName = "_start" + info.exported = true + } + if info.linkName == "runtime.wasmEntryLegacy" && c.BuildMode == "wasi-legacy" { + info.linkName = "_start" + info.wasmName = "_start" + info.exported = true + } + // Check for //go: pragmas, which may change the link name (among others). c.parsePragmas(&info, f) + c.functionInfos[f] = info return info } @@ -248,150 +299,242 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { // parsePragmas is used by getFunctionInfo to parse function pragmas such as // //export or //go:noinline. func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { - if f.Syntax() == nil { + syntax := f.Syntax() + if f.Origin() != nil { + syntax = f.Origin().Syntax() + } + if syntax == nil { return } - if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { + + // Read all pragmas of this function. + var pragmas []*ast.Comment + hasWasmExport := false + if decl, ok := syntax.(*ast.FuncDecl); ok && decl.Doc != nil { for _, comment := range decl.Doc.List { text := comment.Text - if strings.HasPrefix(text, "//export ") { - // Rewrite '//export' to '//go:export' for compatibility with - // gc. - text = "//go:" + text[2:] + if strings.HasPrefix(text, "//go:") || strings.HasPrefix(text, "//export ") { + pragmas = append(pragmas, comment) + if strings.HasPrefix(comment.Text, "//go:wasmexport ") { + hasWasmExport = true + } + } + } + } + + // Parse each pragma. + for _, comment := range pragmas { + parts := strings.Fields(comment.Text) + switch parts[0] { + case "//export", "//go:export": + if len(parts) != 2 { + continue } - if !strings.HasPrefix(text, "//go:") { + if hasWasmExport { + // //go:wasmexport overrides //export. continue } - parts := strings.Fields(text) - switch parts[0] { - case "//go:export": - if len(parts) != 2 { - continue - } - info.linkName = parts[1] - info.wasmName = info.linkName - info.exported = true - case "//go:interrupt": - if hasUnsafeImport(f.Pkg.Pkg) { - info.interrupt = true - } - case "//go:wasm-module": - // Alternative comment for setting the import module. - // This is deprecated, use //go:wasmimport instead. - if len(parts) != 2 { - continue - } - info.wasmModule = parts[1] - case "//go:wasmimport": - // Import a WebAssembly function, for example a WASI function. - // Original proposal: https://github.com/golang/go/issues/38248 - // Allow globally: https://github.com/golang/go/issues/59149 - if len(parts) != 3 { - continue - } - c.checkWasmImport(f, comment.Text) - info.exported = true - info.wasmModule = parts[1] - info.wasmName = parts[2] - case "//go:inline": - info.inline = inlineHint - case "//go:noinline": + info.linkName = parts[1] + info.wasmName = info.linkName + info.exported = true + case "//go:interrupt": + if hasUnsafeImport(f.Pkg.Pkg) { + info.interrupt = true + } + case "//go:wasm-module": + // Alternative comment for setting the import module. + // This is deprecated, use //go:wasmimport instead. + if len(parts) != 2 { + continue + } + info.wasmModule = parts[1] + case "//go:wasmimport": + // Import a WebAssembly function, for example a WASI function. + // Original proposal: https://github.com/golang/go/issues/38248 + // Allow globally: https://github.com/golang/go/issues/59149 + if len(parts) != 3 { + continue + } + if f.Blocks != nil { + // Defined functions cannot be exported. + c.addError(f.Pos(), "can only use //go:wasmimport on declarations") + continue + } + c.checkWasmImportExport(f, comment.Text) + info.exported = true + info.wasmModule = parts[1] + info.wasmName = parts[2] + case "//go:wasmexport": + if f.Blocks == nil { + c.addError(f.Pos(), "can only use //go:wasmexport on definitions") + continue + } + if len(parts) != 2 { + c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmexport, not %d", len(parts)-1)) + continue + } + name := parts[1] + if name == "_start" || name == "_initialize" { + c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name)) + continue + } + if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" { + c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode)) + continue + } + if c.archFamily() != "wasm32" { + c.addError(f.Pos(), "//go:wasmexport is only supported on wasm") + } + c.checkWasmImportExport(f, comment.Text) + info.wasmExport = name + info.wasmExportPos = comment.Slash + case "//go:inline": + info.inline = inlineHint + case "//go:noinline": + info.inline = inlineNone + case "//go:linkname": + if len(parts) != 3 || parts[1] != f.Name() { + continue + } + // Only enable go:linkname when the package imports "unsafe". + // This is a slightly looser requirement than what gc uses: gc + // requires the file to import "unsafe", not the package as a + // whole. + if hasUnsafeImport(f.Pkg.Pkg) { + info.linkName = parts[2] + } + case "//go:section": + // Only enable go:section when the package imports "unsafe". + // go:section also implies go:noinline since inlining could + // move the code to a different section than that requested. + if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) { + info.section = parts[1] info.inline = inlineNone - case "//go:linkname": - if len(parts) != 3 || parts[1] != f.Name() { - continue - } - // Only enable go:linkname when the package imports "unsafe". - // This is a slightly looser requirement than what gc uses: gc - // requires the file to import "unsafe", not the package as a - // whole. - if hasUnsafeImport(f.Pkg.Pkg) { - info.linkName = parts[2] - } - case "//go:section": - // Only enable go:section when the package imports "unsafe". - // go:section also implies go:noinline since inlining could - // move the code to a different section than that requested. - if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) { - info.section = parts[1] - info.inline = inlineNone - } - case "//go:nobounds": - // Skip bounds checking in this function. Useful for some - // runtime functions. - // This is somewhat dangerous and thus only imported in packages - // that import unsafe. - if hasUnsafeImport(f.Pkg.Pkg) { - info.nobounds = true - } - case "//go:variadic": - // The //go:variadic pragma is emitted by the CGo preprocessing - // pass for C variadic functions. This includes both explicit - // (with ...) and implicit (no parameters in signature) - // functions. - if strings.HasPrefix(f.Name(), "C.") { - // This prefix cannot naturally be created, it must have - // been created as a result of CGo preprocessing. - info.variadic = true - } + } + case "//go:nobounds": + // Skip bounds checking in this function. Useful for some + // runtime functions. + // This is somewhat dangerous and thus only imported in packages + // that import unsafe. + if hasUnsafeImport(f.Pkg.Pkg) { + info.nobounds = true + } + case "//go:noescape": + // Don't let pointer parameters escape. + // Following the upstream Go implementation, we only do this for + // declarations, not definitions. + if len(f.Blocks) == 0 { + info.noescape = true + } + case "//go:variadic": + // The //go:variadic pragma is emitted by the CGo preprocessing + // pass for C variadic functions. This includes both explicit + // (with ...) and implicit (no parameters in signature) + // functions. + if strings.HasPrefix(f.Name(), "_Cgo_") { + // This prefix was created as a result of CGo preprocessing. + info.variadic = true } } } } -// Check whether this function cannot be used in //go:wasmimport. It will add an -// error if this is the case. +// Check whether this function can be used in //go:wasmimport or +// //go:wasmexport. It will add an error if this is not the case. // // The list of allowed types is based on this proposal: // https://github.com/golang/go/issues/59149 -func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) { - if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" { +func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) { + if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" || c.pkg.Path() == "crypto/internal/sysrand" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return } - if f.Blocks != nil { - // Defined functions cannot be exported. - c.addError(f.Pos(), fmt.Sprintf("can only use //go:wasmimport on declarations")) - return - } if f.Signature.Results().Len() > 1 { c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma)) } else if f.Signature.Results().Len() == 1 { result := f.Signature.Results().At(0) - if !isValidWasmType(result.Type(), true) { + if !c.isValidWasmType(result.Type(), siteResult) { c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String())) } } for _, param := range f.Params { // Check whether the type is allowed. // Only a very limited number of types can be mapped to WebAssembly. - if !isValidWasmType(param.Type(), false) { + if !c.isValidWasmType(param.Type(), siteParam) { c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String())) } } } -// Check whether the type maps directly to a WebAssembly type, according to: +// Check whether the type maps directly to a WebAssembly type. +// +// This reflects the relaxed type restrictions proposed here (except for structs.HostLayout): +// https://github.com/golang/go/issues/66984 +// +// This previously reflected the additional restrictions documented here: // https://github.com/golang/go/issues/59149 -func isValidWasmType(typ types.Type, isReturn bool) bool { +func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool { switch typ := typ.Underlying().(type) { case *types.Basic: switch typ.Kind() { + case types.Bool: + return true + case types.Int8, types.Uint8, types.Int16, types.Uint16: + return site == siteIndirect case types.Int32, types.Uint32, types.Int64, types.Uint64: return true case types.Float32, types.Float64: return true - case types.UnsafePointer: - if !isReturn { - return true + case types.Uintptr, types.UnsafePointer: + return true + case types.String: + // string flattens to two values, so disallowed as a result + return site == siteParam || site == siteIndirect + } + case *types.Array: + return site == siteIndirect && c.isValidWasmType(typ.Elem(), siteIndirect) + case *types.Struct: + if site != siteIndirect { + return false + } + // Structs with no fields do not need structs.HostLayout + if typ.NumFields() == 0 { + return true + } + hasHostLayout := true // default to true before detecting Go version + // (*types.Package).GoVersion added in go1.21 + if gv, ok := any(c.pkg).(interface{ GoVersion() string }); ok { + if goenv.Compare(gv.GoVersion(), "go1.23") >= 0 { + hasHostLayout = false // package structs added in go1.23 + } + } + for i := 0; i < typ.NumFields(); i++ { + ftyp := typ.Field(i).Type() + if ftyp.String() == "structs.HostLayout" { + hasHostLayout = true + continue + } + if !c.isValidWasmType(ftyp, siteIndirect) { + return false } } + return hasHostLayout + case *types.Pointer: + return c.isValidWasmType(typ.Elem(), siteIndirect) } return false } +type wasmSite int + +const ( + siteParam wasmSite = iota + siteResult + siteIndirect // pointer or field +) + // getParams returns the function parameters, including the receiver at the // start. This is an alternative to the Params member of *ssa.Function, which is // not yet populated when the package has not yet been built. diff --git a/compiler/syscall.go b/compiler/syscall.go index 2623ea94cf..aa40ad1a55 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -5,6 +5,7 @@ package compiler import ( "strconv" + "strings" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" @@ -12,7 +13,8 @@ import ( // createRawSyscall creates a system call with the provided system call number // and returns the result as a single integer (the system call result). The -// result is not further interpreted. +// result is not further interpreted (with the exception of MIPS to use the same +// return value everywhere). func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { num := b.getValue(call.Args[0], getPos(call)) switch { @@ -33,18 +35,17 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { "{r10}", "{r8}", "{r9}", - "{r11}", - "{r12}", - "{r13}", }[i] llvmValue := b.getValue(arg, getPos(call)) args = append(args, llvmValue) argTypes = append(argTypes, llvmValue.Type()) } + // rcx and r11 are clobbered by the syscall, so make sure they are not used constraints += ",~{rcx},~{r11}" fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false) return b.CreateCall(fnType, target, args, ""), nil + case b.GOARCH == "386" && b.GOOS == "linux": // Sources: // syscall(2) man page @@ -71,6 +72,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false) return b.CreateCall(fnType, target, args, ""), nil + case b.GOARCH == "arm" && b.GOOS == "linux": // Implement the EABI system call convention for Linux. // Source: syscall(2) man page. @@ -103,6 +105,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil + case b.GOARCH == "arm64" && b.GOOS == "linux": // Source: syscall(2) man page. args := []llvm.Value{} @@ -135,6 +138,98 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) return b.CreateCall(fnType, target, args, ""), nil + + case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux": + // Implement the system call convention for Linux. + // Source: syscall(2) man page and musl: + // https://git.musl-libc.org/cgit/musl/tree/arch/mips/syscall_arch.h + // Also useful: + // https://web.archive.org/web/20220529105937/https://www.linux-mips.org/wiki/Syscall + // The syscall number goes in r2, the result also in r2. + // Register r7 is both an input parameter and an output parameter: if it + // is non-zero, the system call failed and r2 is the error code. + // The code below implements the O32 syscall ABI, not the N32 ABI. It + // could implement both at the same time if needed (like what appears to + // be done in musl) by forcing arg5-arg7 into the right registers but + // letting the compiler decide the registers should result in _slightly_ + // faster and smaller code. + args := []llvm.Value{num} + argTypes := []llvm.Type{b.uintptrType} + constraints := "={$2},={$7},0" + syscallParams := call.Args[1:] + if len(syscallParams) > 7 { + // There is one syscall that uses 7 parameters: sync_file_range. + // But only 7, not more. Go however only has Syscall6 and Syscall9. + // Therefore, we can ignore the remaining parameters. + syscallParams = syscallParams[:7] + } + for i, arg := range syscallParams { + constraints += "," + [...]string{ + "{$4}", // arg1 + "{$5}", // arg2 + "{$6}", // arg3 + "1", // arg4, error return + "r", // arg5 on the stack + "r", // arg6 on the stack + "r", // arg7 on the stack + }[i] + llvmValue := b.getValue(arg, getPos(call)) + args = append(args, llvmValue) + argTypes = append(argTypes, llvmValue.Type()) + } + // Create assembly code. + // Parameters beyond the first 4 are passed on the stack instead of in + // registers in the O32 syscall ABI. + // We need ".set noat" because LLVM might pick register $1 ($at) as the + // register for a parameter and apparently this is not allowed on MIPS + // unless you use this specific pragma. + asm := "syscall" + switch len(syscallParams) { + case 5: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + case 6: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "sw $8, 20($$sp)\n" + // arg6 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + case 7: + asm = "" + + ".set noat\n" + + "subu $$sp, $$sp, 32\n" + + "sw $7, 16($$sp)\n" + // arg5 + "sw $8, 20($$sp)\n" + // arg6 + "sw $9, 24($$sp)\n" + // arg7 + "syscall\n" + + "addu $$sp, $$sp, 32\n" + + ".set at\n" + } + constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}" + returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false) + fnType := llvm.FunctionType(returnType, argTypes, false) + target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false) + call := b.CreateCall(fnType, target, args, "") + resultCode := b.CreateExtractValue(call, 0, "") // r2 + errorFlag := b.CreateExtractValue(call, 1, "") // r7 + // Pseudocode to return the result with the same convention as other + // archs: + // return (errorFlag != 0) ? -resultCode : resultCode; + // At least on QEMU with the O32 ABI, the error code is always positive. + zero := llvm.ConstInt(b.uintptrType, 0, false) + isError := b.CreateICmp(llvm.IntNE, errorFlag, zero, "") + negativeResult := b.CreateSub(zero, resultCode, "") + result := b.CreateSelect(isError, negativeResult, resultCode, "") + return result, nil + default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } @@ -217,6 +312,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { retval = b.CreateInsertValue(retval, syscallResult, 0, "") retval = b.CreateInsertValue(retval, errResult, 2, "") return retval, nil + default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } @@ -234,3 +330,64 @@ func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, err retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "") return retval, nil } + +// Lower a call to internal/abi.FuncPCABI0 on MacOS. +// This function is called like this: +// +// syscall(abi.FuncPCABI0(libc_mkdir_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0) +// +// So we'll want to return a function pointer (as uintptr) that points to the +// libc function. Specifically, we _don't_ want to point to the trampoline +// function (which is implemented in Go assembly which we can't read), but +// rather to the actually intended function. For this we're going to assume that +// all the functions follow a specific pattern: libc__trampoline. +// +// The return value is the function pointer as an uintptr, or a nil value if +// this isn't possible (and a regular call should be made as fallback). +func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value { + if b.GOOS != "darwin" { + // This has only been tested on MacOS (and only seems to be used there). + return llvm.Value{} + } + + // Check that it uses a function call like syscall.libc_*_trampoline + itf := instr.Args[0].(*ssa.MakeInterface) + calledFn := itf.X.(*ssa.Function) + if pkgName := calledFn.Pkg.Pkg.Path(); pkgName != "syscall" && pkgName != "internal/syscall/unix" { + return llvm.Value{} + } + if !strings.HasPrefix(calledFn.Name(), "libc_") || !strings.HasSuffix(calledFn.Name(), "_trampoline") { + + return llvm.Value{} + } + + // Extract the libc function name. + name := strings.TrimPrefix(strings.TrimSuffix(calledFn.Name(), "_trampoline"), "libc_") + if name == "open" { + // Special case: open() is a variadic function and can't be called like + // a regular function. Therefore, we need to use a wrapper implemented + // in C. + name = "syscall_libc_open" + } + if b.GOARCH == "amd64" { + if name == "fdopendir" || name == "readdir_r" { + // Hack to support amd64, which needs the $INODE64 suffix. + // This is also done in upstream Go: + // https://github.com/golang/go/commit/096ab3c21b88ccc7d411379d09fe6274e3159467 + name += "$INODE64" + } + } + + // Obtain the C function. + // Use a simple function (no parameters or return value) because all we need + // is the address of the function. + llvmFn := b.mod.NamedFunction(name) + if llvmFn.IsNil() { + llvmFnType := llvm.FunctionType(b.ctx.VoidType(), nil, false) + llvmFn = llvm.AddFunction(b.mod, name, llvmFnType) + } + + // Cast the function pointer to a uintptr (because that's what + // abi.FuncPCABI0 returns). + return b.CreatePtrToInt(llvmFn, b.uintptrType, "") +} diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll index e81d5f2a6a..2c2797504b 100644 --- a/compiler/testdata/basic.ll +++ b/compiler/testdata/basic.ll @@ -206,7 +206,7 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/channel.ll b/compiler/testdata/channel.ll index be769e8597..b99c428665 100644 --- a/compiler/testdata/channel.ll +++ b/compiler/testdata/channel.ll @@ -3,7 +3,7 @@ source_filename = "channel.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" -%runtime.channelBlockedList = type { ptr, ptr, ptr, { ptr, i32, i32 } } +%runtime.channelOp = type { ptr, ptr, i32, ptr } %runtime.chanSelectState = type { ptr, ptr } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) @@ -18,15 +18,15 @@ entry: } ; Function Attrs: nounwind -define hidden void @main.chanIntSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 + %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) store i32 3, ptr %chan.value, align 4 - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4 + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) ret void } @@ -34,61 +34,61 @@ entry: ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3 -declare void @runtime.chanSend(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1 +declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1 ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3 ; Function Attrs: nounwind -define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 + %chan.op = alloca %runtime.channelOp, align 8 %chan.value = alloca i32, align 4 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4 + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } -declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1 +declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1 ; Function Attrs: nounwind -define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + %chan.op = alloca %runtime.channelOp, align 8 + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4 + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } ; Function Attrs: nounwind -define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: - %chan.blockedList = alloca %runtime.channelBlockedList, align 8 - call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) - %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4 - call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) + %chan.op = alloca %runtime.channelOp, align 8 + call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) + %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4 + call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) ret void } ; Function Attrs: nounwind -define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(32) %ch1, ptr dereferenceable_or_null(32) %ch2, ptr %context) unnamed_addr #2 { +define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(36) %ch1, ptr dereferenceable_or_null(36) %ch2, ptr %context) unnamed_addr #2 { entry: %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.send.value = alloca i32, align 4 store i32 1, ptr %select.send.value, align 4 call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) - store ptr %ch1, ptr %select.states.alloca, align 8 - %select.states.alloca.repack1 = getelementptr inbounds %runtime.chanSelectState, ptr %select.states.alloca, i32 0, i32 1 + store ptr %ch1, ptr %select.states.alloca, align 4 + %select.states.alloca.repack1 = getelementptr inbounds i8, ptr %select.states.alloca, i32 4 store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 - %0 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1 - store ptr %ch2, ptr %0, align 8 - %.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1 + %0 = getelementptr inbounds i8, ptr %select.states.alloca, i32 8 + store ptr %ch2, ptr %0, align 4 + %.repack3 = getelementptr inbounds i8, ptr %select.states.alloca, i32 12 store ptr null, ptr %.repack3, align 4 - %select.result = call { i32, i1 } @runtime.tryChanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr undef) #4 + %select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #4 call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) %1 = extractvalue { i32, i1 } %select.result, 0 %2 = icmp eq i32 %1, 0 @@ -105,10 +105,10 @@ select.body: ; preds = %select.next br label %select.done } -declare { i32, i1 } @runtime.tryChanSelect(ptr, ptr, i32, i32, ptr) #1 +declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1 -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #4 = { nounwind } diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index 32697ccd5b..7f5c3d6800 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -3,9 +3,8 @@ source_filename = "defer.go" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" -%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface } +%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface } %runtime._interface = type { ptr, ptr } -%runtime._defer = type { i32, ptr } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 @@ -25,10 +24,10 @@ entry: %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 - %0 = call ptr @llvm.stacksave() + %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 - %defer.alloca.repack15 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 + %defer.alloca.repack15 = getelementptr inbounds i8, ptr %defer.alloca, i32 4 store ptr null, ptr %defer.alloca.repack15, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 @@ -52,7 +51,7 @@ rundefers.loophead: ; preds = %3, %rundefers.block br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead - %stack.next.gep = getelementptr inbounds %runtime._defer, ptr %2, i32 0, i32 1 + %stack.next.gep = getelementptr inbounds i8, ptr %2, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %2, align 4 @@ -88,7 +87,7 @@ rundefers.loophead6: ; preds = %5, %lpad br i1 %stackIsNil7, label %rundefers.end3, label %rundefers.loop5 rundefers.loop5: ; preds = %rundefers.loophead6 - %stack.next.gep8 = getelementptr inbounds %runtime._defer, ptr %4, i32 0, i32 1 + %stack.next.gep8 = getelementptr inbounds i8, ptr %4, i32 4 %stack.next9 = load ptr, ptr %stack.next.gep8, align 4 store ptr %stack.next9, ptr %deferPtr, align 4 %callback11 = load i32, ptr %4, align 4 @@ -113,7 +112,7 @@ rundefers.end3: ; preds = %rundefers.loophead6 } ; Function Attrs: nocallback nofree nosync nounwind willreturn -declare ptr @llvm.stacksave() #3 +declare ptr @llvm.stacksave.p0() #3 declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #2 @@ -122,12 +121,18 @@ declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2 ; Function Attrs: nounwind define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 { entry: + call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 3, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 ret void } +declare void @runtime.printlock(ptr) #2 + declare void @runtime.printint32(i32, ptr) #2 +declare void @runtime.printunlock(ptr) #2 + ; Function Attrs: nounwind define hidden void @main.deferMultiple(ptr %context) unnamed_addr #1 { entry: @@ -136,14 +141,14 @@ entry: %deferPtr = alloca ptr, align 4 store ptr null, ptr %deferPtr, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4 - %0 = call ptr @llvm.stacksave() + %0 = call ptr @llvm.stacksave.p0() call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 store i32 0, ptr %defer.alloca, align 4 - %defer.alloca.repack22 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 + %defer.alloca.repack22 = getelementptr inbounds i8, ptr %defer.alloca, i32 4 store ptr null, ptr %defer.alloca.repack22, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 store i32 1, ptr %defer.alloca2, align 4 - %defer.alloca2.repack23 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca2, i32 0, i32 1 + %defer.alloca2.repack23 = getelementptr inbounds i8, ptr %defer.alloca2, i32 4 store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4 store ptr %defer.alloca2, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 @@ -167,7 +172,7 @@ rundefers.loophead: ; preds = %4, %3, %rundefers.b br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead - %stack.next.gep = getelementptr inbounds %runtime._defer, ptr %2, i32 0, i32 1 + %stack.next.gep = getelementptr inbounds i8, ptr %2, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 %callback = load i32, ptr %2, align 4 @@ -213,7 +218,7 @@ rundefers.loophead10: ; preds = %7, %6, %lpad br i1 %stackIsNil11, label %rundefers.end7, label %rundefers.loop9 rundefers.loop9: ; preds = %rundefers.loophead10 - %stack.next.gep12 = getelementptr inbounds %runtime._defer, ptr %5, i32 0, i32 1 + %stack.next.gep12 = getelementptr inbounds i8, ptr %5, i32 4 %stack.next13 = load ptr, ptr %stack.next.gep12, align 4 store ptr %stack.next13, ptr %deferPtr, align 4 %callback15 = load i32, ptr %5, align 4 @@ -250,20 +255,24 @@ rundefers.end7: ; preds = %rundefers.loophead1 ; Function Attrs: nounwind define internal void @"main.deferMultiple$1"(ptr %context) unnamed_addr #1 { entry: + call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 3, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 ret void } ; Function Attrs: nounwind define internal void @"main.deferMultiple$2"(ptr %context) unnamed_addr #1 { entry: + call void @runtime.printlock(ptr undef) #4 call void @runtime.printint32(i32 5, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #3 = { nocallback nofree nosync nounwind willreturn } attributes #4 = { nounwind } attributes #5 = { nounwind returns_twice } diff --git a/compiler/testdata/errors.go b/compiler/testdata/errors.go index 5778a931e1..ae95b75234 100644 --- a/compiler/testdata/errors.go +++ b/compiler/testdata/errors.go @@ -1,6 +1,9 @@ package main -import "unsafe" +import ( + "structs" + "unsafe" +) //go:wasmimport modulename empty func empty() @@ -13,31 +16,92 @@ func implementation() { type Uint uint32 +type S struct { + _ structs.HostLayout + a [4]uint32 + b uintptr + d float32 + e float64 +} + //go:wasmimport modulename validparam -func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint) +func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S, j *struct{}, k *[8]uint8) -// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int -// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type string +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [4]uint32 // ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte -// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type *int32 +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type struct{a int} +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type chan struct{} +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type func() +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type uint +// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [8]int // //go:wasmimport modulename invalidparam -func invalidparam(a int, b string, c []byte, d *int32) +func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func(), f int, g uint, h [8]int) + +// ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{int} +// ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{string} +// +//go:wasmimport modulename invalidparam_no_hostlayout +func invalidparam_no_hostlayout(a *struct{ int }, b *struct{ string }) + +//go:wasmimport modulename validreturn_int32 +func validreturn_int32() int32 + +//go:wasmimport modulename validreturn_ptr_int32 +func validreturn_ptr_int32() *int32 + +//go:wasmimport modulename validreturn_ptr_string +func validreturn_ptr_string() *string + +//go:wasmimport modulename validreturn_ptr_struct +func validreturn_ptr_struct() *S + +//go:wasmimport modulename validreturn_ptr_struct +func validreturn_ptr_empty_struct() *struct{} -//go:wasmimport modulename validreturn -func validreturn() int32 +//go:wasmimport modulename validreturn_ptr_array +func validreturn_ptr_array() *[8]uint8 + +//go:wasmimport modulename validreturn_unsafe_pointer +func validreturn_unsafe_pointer() unsafe.Pointer // ERROR: //go:wasmimport modulename manyreturns: too many return values // //go:wasmimport modulename manyreturns func manyreturns() (int32, int32) -// ERROR: //go:wasmimport modulename invalidreturn: unsupported result type int +// ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type int +// +//go:wasmimport modulename invalidreturn_int +func invalidreturn_int() int + +// ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type uint +// +//go:wasmimport modulename invalidreturn_int +func invalidreturn_uint() uint + +// ERROR: //go:wasmimport modulename invalidreturn_func: unsupported result type func() +// +//go:wasmimport modulename invalidreturn_func +func invalidreturn_func() func() + +// ERROR: //go:wasmimport modulename invalidreturn_pointer_array_int: unsupported result type *[8]int +// +//go:wasmimport modulename invalidreturn_pointer_array_int +func invalidreturn_pointer_array_int() *[8]int + +// ERROR: //go:wasmimport modulename invalidreturn_slice_byte: unsupported result type []byte +// +//go:wasmimport modulename invalidreturn_slice_byte +func invalidreturn_slice_byte() []byte + +// ERROR: //go:wasmimport modulename invalidreturn_chan_int: unsupported result type chan int // -//go:wasmimport modulename invalidreturn -func invalidreturn() int +//go:wasmimport modulename invalidreturn_chan_int +func invalidreturn_chan_int() chan int -// ERROR: //go:wasmimport modulename invalidUnsafePointerReturn: unsupported result type unsafe.Pointer +// ERROR: //go:wasmimport modulename invalidreturn_string: unsupported result type string // -//go:wasmimport modulename invalidUnsafePointerReturn -func invalidUnsafePointerReturn() unsafe.Pointer +//go:wasmimport modulename invalidreturn_string +func invalidreturn_string() string diff --git a/compiler/testdata/float.ll b/compiler/testdata/float.ll index 735ab19768..e894e941b0 100644 --- a/compiler/testdata/float.ll +++ b/compiler/testdata/float.ll @@ -93,6 +93,6 @@ entry: ret i8 %0 } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } diff --git a/compiler/testdata/func.ll b/compiler/testdata/func.ll index bec79bffc5..a4ad26c3dd 100644 --- a/compiler/testdata/func.ll +++ b/compiler/testdata/func.ll @@ -44,7 +44,7 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index de638dad2c..314c6cd4e7 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -16,9 +16,9 @@ target triple = "wasm32-unknown-wasi" @main.struct2 = hidden global ptr null, align 4 @main.struct3 = hidden global ptr null, align 4 @main.struct4 = hidden global ptr null, align 4 -@main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 8 -@main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 8 -@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 8 +@main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 +@main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 +@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } @"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } @"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 @@ -39,16 +39,16 @@ entry: define hidden void @main.newScalar(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call dereferenceable(1) ptr @runtime.alloc(i32 1, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new = call align 1 dereferenceable(1) ptr @runtime.alloc(i32 1, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.scalar1, align 4 - %new1 = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new1 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.scalar2, align 4 - %new2 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new2 = call align 8 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.scalar3, align 4 - %new3 = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new3 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.scalar4, align 4 ret void @@ -58,13 +58,13 @@ entry: define hidden void @main.newArray(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call dereferenceable(3) ptr @runtime.alloc(i32 3, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new = call align 1 dereferenceable(3) ptr @runtime.alloc(i32 3, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.array1, align 4 - %new1 = call dereferenceable(71) ptr @runtime.alloc(i32 71, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new1 = call align 1 dereferenceable(71) ptr @runtime.alloc(i32 71, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.array2, align 4 - %new2 = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 + %new2 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.array3, align 4 ret void @@ -74,16 +74,16 @@ entry: define hidden void @main.newStruct(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new, ptr @main.struct1, align 4 - %new1 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new1, ptr @main.struct2, align 4 - %new2 = call dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 + %new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new2, ptr @main.struct3, align 4 - %new3 = call dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 + %new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store ptr %new3, ptr @main.struct4, align 4 ret void @@ -93,7 +93,7 @@ entry: define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %new = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3 + %new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 ret ptr %new } @@ -102,21 +102,21 @@ entry: define hidden void @main.makeSlice(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %makeslice = call dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice = call align 1 dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 - store ptr %makeslice, ptr @main.slice1, align 8 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 2), align 8 - %makeslice1 = call dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 + store ptr %makeslice, ptr @main.slice1, align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice1, i32 4), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice1, i32 8), align 4 + %makeslice1 = call align 4 dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice1, ptr nonnull %stackalloc, ptr undef) #3 - store ptr %makeslice1, ptr @main.slice2, align 8 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 2), align 8 - %makeslice3 = call dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 + store ptr %makeslice1, ptr @main.slice2, align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice2, i32 4), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice2, i32 8), align 4 + %makeslice3 = call align 4 dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice3, ptr nonnull %stackalloc, ptr undef) #3 - store ptr %makeslice3, ptr @main.slice3, align 8 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 1), align 4 - store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 2), align 8 + store ptr %makeslice3, ptr @main.slice3, align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice3, i32 4), align 4 + store i32 5, ptr getelementptr inbounds (i8, ptr @main.slice3, i32 8), align 4 ret void } @@ -124,10 +124,10 @@ entry: define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 + %0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 store double %v.r, ptr %0, align 8 - %.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1 + %.repack1 = getelementptr inbounds i8, ptr %0, i32 8 store double %v.i, ptr %.repack1, align 8 %1 = insertvalue %runtime._interface { ptr @"reflect/types.type:basic:complex128", ptr undef }, ptr %0, 1 call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:complex128", ptr nonnull %stackalloc, ptr undef) #3 @@ -135,7 +135,7 @@ entry: ret %runtime._interface %1 } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/go1.20.ll b/compiler/testdata/go1.20.ll index 6ef13fb4a8..9d4f9e1dc6 100644 --- a/compiler/testdata/go1.20.ll +++ b/compiler/testdata/go1.20.ll @@ -36,7 +36,7 @@ entry: br i1 %4, label %unsafe.String.throw, label %unsafe.String.next unsafe.String.next: ; preds = %entry - %5 = zext i16 %len to i32 + %5 = zext nneg i16 %len to i32 %6 = insertvalue %runtime._string undef, ptr %ptr, 0 %7 = insertvalue %runtime._string %6, i32 %5, 1 call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3 @@ -57,7 +57,7 @@ entry: ret ptr %s.data } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index d76ec6212c..982e4fda1a 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -171,9 +171,9 @@ declare i32 @llvm.smax.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) declare i32 @llvm.umax.i32(i32, i32) #4 -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nocallback nofree nounwind willreturn memory(argmem: write) } attributes #4 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } attributes #5 = { nounwind } diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index 6918e7be66..3f21d30a76 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -3,8 +3,6 @@ source_filename = "goroutine.go" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" -%runtime._string = type { ptr, i32 } - @"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) @@ -63,16 +61,18 @@ entry: ; Function Attrs: nounwind define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #1 { entry: - %n = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 store i32 3, ptr %n, align 4 - %0 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 %2 = load i32, ptr %n, align 4 + call void @runtime.printlock(ptr undef) #9 call void @runtime.printint32(i32 %2, ptr undef) #9 + call void @runtime.printunlock(ptr undef) #9 ret void } @@ -87,22 +87,26 @@ entry: define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #5 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) ret void } +declare void @runtime.printlock(ptr) #2 + declare void @runtime.printint32(i32, ptr) #2 +declare void @runtime.printunlock(ptr) #2 + ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { entry: - %0 = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %2 = getelementptr inbounds i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 @@ -113,9 +117,9 @@ entry: define linkonce_odr void @main.funcGoroutine.gowrapper(ptr %0) unnamed_addr #6 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 call void %5(i32 %1, ptr %3) #9 ret void @@ -137,25 +141,25 @@ entry: declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2 ; Function Attrs: nounwind -define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #1 { +define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { entry: call void @runtime.chanClose(ptr %ch, ptr undef) #9 ret void } -declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #2 +declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: - %0 = call dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 - %.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1 - store i32 4, ptr %.repack1, align 4 - %2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2 - store ptr %itf.typecode, ptr %2, align 4 + %2 = getelementptr inbounds i8, ptr %0, i32 8 + store i32 4, ptr %2, align 4 + %3 = getelementptr inbounds i8, ptr %0, i32 12 + store ptr %itf.typecode, ptr %3, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #9 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9 ret void @@ -167,23 +171,23 @@ declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { entry: %1 = load ptr, ptr %0, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load i32, ptr %4, align 4 - %6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %6 = getelementptr inbounds i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } -attributes #3 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.regularFunction" } -attributes #4 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } -attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } -attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } -attributes #7 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } -attributes #8 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } +attributes #3 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.regularFunction" } +attributes #4 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } +attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } +attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" } +attributes #7 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #8 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #9 = { nounwind } diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index db7ce187f7..3c0db4b941 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -3,8 +3,6 @@ source_filename = "goroutine.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" -%runtime._string = type { ptr, i32 } - @"main$string" = internal unnamed_addr constant [4 x i8] c"test", align 1 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) @@ -66,19 +64,21 @@ entry: define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %n = call dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 + %n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 store i32 3, ptr %n, align 4 call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #9 - %0 = call dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %n, ptr %1, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 %2 = load i32, ptr %n, align 4 + call void @runtime.printlock(ptr undef) #9 call void @runtime.printint32(i32 %2, ptr undef) #9 + call void @runtime.printunlock(ptr undef) #9 ret void } @@ -93,25 +93,29 @@ entry: define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(ptr %0) unnamed_addr #5 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 call void @"main.closureFunctionGoroutine$1"(i32 %1, ptr %3) call void @runtime.deadlock(ptr undef) #9 unreachable } +declare void @runtime.printlock(ptr) #1 + declare void @runtime.printint32(i32, ptr) #1 +declare void @runtime.printunlock(ptr) #1 + ; Function Attrs: nounwind define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store i32 5, ptr %0, align 4 - %1 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr %fn.context, ptr %1, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %2 = getelementptr inbounds i8, ptr %0, i32 8 store ptr %fn.funcptr, ptr %2, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @main.funcGoroutine.gowrapper to i32), ptr nonnull %0, i32 65536, ptr undef) #9 ret void @@ -121,9 +125,9 @@ entry: define linkonce_odr void @main.funcGoroutine.gowrapper(ptr %0) unnamed_addr #6 { entry: %1 = load i32, ptr %0, align 4 - %2 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { i32, ptr, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load ptr, ptr %4, align 4 call void %5(i32 %1, ptr %3) #9 call void @runtime.deadlock(ptr undef) #9 @@ -146,27 +150,27 @@ entry: declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1 ; Function Attrs: nounwind -define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { +define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 { entry: call void @runtime.chanClose(ptr %ch, ptr undef) #9 ret void } -declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #1 +declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1 ; Function Attrs: nounwind define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %0 = call dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 + %0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #9 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #9 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds i8, ptr %0, i32 4 store ptr @"main$string", ptr %1, align 4 - %.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1 - store i32 4, ptr %.repack1, align 4 - %2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2 - store ptr %itf.typecode, ptr %2, align 4 + %2 = getelementptr inbounds i8, ptr %0, i32 8 + store i32 4, ptr %2, align 4 + %3 = getelementptr inbounds i8, ptr %0, i32 12 + store ptr %itf.typecode, ptr %3, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9 ret void } @@ -177,24 +181,24 @@ declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #8 { entry: %1 = load ptr, ptr %0, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds i8, ptr %0, i32 4 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds i8, ptr %0, i32 8 %5 = load i32, ptr %4, align 4 - %6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %6 = getelementptr inbounds i8, ptr %0, i32 12 %7 = load ptr, ptr %6, align 4 call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #9 call void @runtime.deadlock(ptr undef) #9 unreachable } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="main.regularFunction" } -attributes #4 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } -attributes #5 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } -attributes #6 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper" } -attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } -attributes #8 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.regularFunction" } +attributes #4 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" } +attributes #5 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.closureFunctionGoroutine$1" } +attributes #6 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" } +attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" } +attributes #8 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" } attributes #9 = { nounwind } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index ff3a04d912..49b501da2a 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -9,7 +9,7 @@ target triple = "wasm32-unknown-wasi" @"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4 @"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4 @"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4 -@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 +@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 @"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1 @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4 @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 @@ -130,11 +130,11 @@ entry: declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, ptr, ptr) #6 -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-methods"="reflect/methods.Error() string" } -attributes #4 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-methods"="reflect/methods.String() string" } -attributes #5 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" } -attributes #6 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error() string" } +attributes #4 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String() string" } +attributes #5 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" } +attributes #6 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } attributes #7 = { nounwind } diff --git a/compiler/testdata/pointer.ll b/compiler/testdata/pointer.ll index a659b21744..eb551c6872 100644 --- a/compiler/testdata/pointer.ll +++ b/compiler/testdata/pointer.ll @@ -44,7 +44,7 @@ entry: ret ptr %x } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go index fa1d4b0e96..1e6e967f53 100644 --- a/compiler/testdata/pragma.go +++ b/compiler/testdata/pragma.go @@ -48,6 +48,22 @@ func inlineFunc() { func noinlineFunc() { } +type Int interface { + int8 | int16 +} + +// Same for generic functions (but the compiler may miss the pragma due to it +// being generic). +// +//go:noinline +func noinlineGenericFunc[T Int]() { +} + +func useGeneric() { + // Make sure the generic function above is instantiated. + noinlineGenericFunc[int8]() +} + // This function should have the specified section. // //go:section .special_function_section @@ -90,3 +106,12 @@ var undefinedGlobalNotInSection uint32 //go:align 1024 //go:section .global_section var multipleGlobalPragmas uint32 + +//go:noescape +func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte) + +// The //go:noescape pragma only works on declarations, not definitions. +// +//go:noescape +func stillEscapes(a *int, b []int, c chan int, d *[0]byte) { +} diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index 3ee4078f1a..a3cbb72c1d 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -48,6 +48,19 @@ entry: ret void } +; Function Attrs: nounwind +define hidden void @main.useGeneric(ptr %context) unnamed_addr #2 { +entry: + call void @"main.noinlineGenericFunc[int8]"(ptr undef) + ret void +} + +; Function Attrs: noinline nounwind +define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #5 { +entry: + ret void +} + ; Function Attrs: noinline nounwind define hidden void @main.functionInSection(ptr %context) unnamed_addr #5 section ".special_function_section" { entry: @@ -72,13 +85,21 @@ entry: declare void @main.undefinedFunctionNotInSection(ptr) #1 -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" } -attributes #4 = { inlinehint nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" } -attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="modulename" "wasm-import-name"="import1" } -attributes #8 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="foobar" "wasm-import-name"="imported" } -attributes #9 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exported" } +declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(36), ptr nocapture, ptr) #1 + +; Function Attrs: nounwind +define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(36) %c, ptr %d, ptr %context) unnamed_addr #2 { +entry: + ret void +} + +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" } +attributes #4 = { inlinehint nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="exportedFunctionInSection" } +attributes #7 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="modulename" "wasm-import-name"="import1" } +attributes #8 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="foobar" "wasm-import-name"="imported" } +attributes #9 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="exported" } diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index bc01987419..24623ccc68 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -48,12 +48,12 @@ declare void @runtime.lookupPanic(ptr) #1 define hidden { ptr, i32, i32 } @main.sliceAppendValues(ptr %ints.data, i32 %ints.len, i32 %ints.cap, ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %varargs = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %varargs = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %varargs, ptr nonnull %stackalloc, ptr undef) #3 store i32 1, ptr %varargs, align 4 - %0 = getelementptr inbounds [3 x i32], ptr %varargs, i32 0, i32 1 + %0 = getelementptr inbounds i8, ptr %varargs, i32 4 store i32 2, ptr %0, align 4 - %1 = getelementptr inbounds [3 x i32], ptr %varargs, i32 0, i32 2 + %1 = getelementptr inbounds i8, ptr %varargs, i32 8 store i32 3, ptr %1, align 4 %append.new = call { ptr, i32, i32 } @runtime.sliceAppend(ptr %ints.data, ptr nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, ptr undef) #3 %append.newPtr = extractvalue { ptr, i32, i32 } %append.new, 0 @@ -100,7 +100,7 @@ entry: br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry - %makeslice.buf = call ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %len, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -122,8 +122,8 @@ entry: br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry - %makeslice.cap = shl i32 %len, 1 - %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.cap = shl nuw i32 %len, 1 + %makeslice.buf = call align 2 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -144,7 +144,7 @@ entry: slice.next: ; preds = %entry %makeslice.cap = mul i32 %len, 3 - %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.buf = call align 1 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -164,8 +164,8 @@ entry: br i1 %slice.maxcap, label %slice.throw, label %slice.next slice.next: ; preds = %entry - %makeslice.cap = shl i32 %len, 2 - %makeslice.buf = call ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice.cap = shl nuw i32 %len, 2 + %makeslice.buf = call align 4 ptr @runtime.alloc(i32 %makeslice.cap, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %0 = insertvalue { ptr, i32, i32 } undef, ptr %makeslice.buf, 0 %1 = insertvalue { ptr, i32, i32 } %0, i32 %len, 1 %2 = insertvalue { ptr, i32, i32 } %1, i32 %len, 2 @@ -216,7 +216,7 @@ declare void @runtime.sliceToArrayPointerPanic(ptr) #1 define hidden ptr @main.SliceToArrayConst(ptr %context) unnamed_addr #2 { entry: %stackalloc = alloca i8, align 1 - %makeslice = call dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 + %makeslice = call align 4 dereferenceable(24) ptr @runtime.alloc(i32 24, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 br i1 false, label %slicetoarray.throw, label %slicetoarray.next @@ -286,7 +286,7 @@ entry: br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry - %5 = trunc i64 %len to i32 + %5 = trunc nuw i64 %len to i32 %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 @@ -310,7 +310,7 @@ entry: br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next unsafe.Slice.next: ; preds = %entry - %5 = trunc i64 %len to i32 + %5 = trunc nuw i64 %len to i32 %6 = insertvalue { ptr, i32, i32 } undef, ptr %ptr, 0 %7 = insertvalue { ptr, i32, i32 } %6, i32 %5, 1 %8 = insertvalue { ptr, i32, i32 } %7, i32 %5, 2 @@ -322,7 +322,7 @@ unsafe.Slice.throw: ; preds = %entry unreachable } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/string.ll b/compiler/testdata/string.ll index 64582dd961..2a00955fe7 100644 --- a/compiler/testdata/string.ll +++ b/compiler/testdata/string.ll @@ -97,7 +97,7 @@ lookup.throw: ; preds = %entry unreachable } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #3 = { nounwind } diff --git a/compiler/testdata/zeromap.ll b/compiler/testdata/zeromap.ll index 510010dbea..928809187f 100644 --- a/compiler/testdata/zeromap.ll +++ b/compiler/testdata/zeromap.ll @@ -26,7 +26,7 @@ entry: %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) - store %main.hasPadding %2, ptr %hashmap.key, align 8 + store %main.hasPadding %2, ptr %hashmap.key, align 4 %3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 %4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9 @@ -59,7 +59,7 @@ entry: call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) store i32 5, ptr %hashmap.value, align 4 call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) - store %main.hasPadding %2, ptr %hashmap.key, align 8 + store %main.hasPadding %2, ptr %hashmap.key, align 4 %3 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 call void @runtime.memzero(ptr nonnull %3, i32 3, ptr undef) #5 %4 = getelementptr inbounds i8, ptr %hashmap.key, i32 9 @@ -80,8 +80,8 @@ entry: call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 - store %main.hasPadding %s.elt, ptr %hashmap.key, align 8 - %hashmap.key.repack1 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1 + store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 + %hashmap.key.repack1 = getelementptr inbounds i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 %0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 @@ -108,8 +108,8 @@ entry: store i32 5, ptr %hashmap.value, align 4 call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) %s.elt = extractvalue [2 x %main.hasPadding] %s, 0 - store %main.hasPadding %s.elt, ptr %hashmap.key, align 8 - %hashmap.key.repack1 = getelementptr inbounds [2 x %main.hasPadding], ptr %hashmap.key, i32 0, i32 1 + store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 + %hashmap.key.repack1 = getelementptr inbounds i8, ptr %hashmap.key, i32 12 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 %0 = getelementptr inbounds i8, ptr %hashmap.key, i32 1 @@ -132,9 +132,9 @@ entry: ret void } -attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } +attributes #3 = { noinline nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #4 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #5 = { nounwind } diff --git a/corpus_test.go b/corpus_test.go index 1b27d6f6b0..f17a9b9f50 100644 --- a/corpus_test.go +++ b/corpus_test.go @@ -51,6 +51,7 @@ func TestCorpus(t *testing.T) { if *testTarget != "" { target = *testTarget } + isWASI := strings.HasPrefix(target, "wasi") repos, err := loadRepos(*corpus) if err != nil { @@ -69,7 +70,7 @@ func TestCorpus(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - if target == "wasi" && repo.SkipWASI { + if isWASI && repo.SkipWASI { t.Skip("skip wasi") } if repo.Slow && testing.Short() { @@ -135,7 +136,7 @@ func TestCorpus(t *testing.T) { t.Run(dir.Pkg, func(t *testing.T) { t.Parallel() - if target == "wasi" && dir.SkipWASI { + if isWASI && dir.SkipWASI { t.Skip("skip wasi") } if dir.Slow && testing.Short() { diff --git a/diagnostics/diagnostics.go b/diagnostics/diagnostics.go new file mode 100644 index 0000000000..1e363580a9 --- /dev/null +++ b/diagnostics/diagnostics.go @@ -0,0 +1,212 @@ +// Package diagnostics formats compiler errors and prints them in a consistent +// way. +package diagnostics + +import ( + "bytes" + "fmt" + "go/scanner" + "go/token" + "go/types" + "io" + "path/filepath" + "sort" + "strings" + + "github.com/tinygo-org/tinygo/builder" + "github.com/tinygo-org/tinygo/goenv" + "github.com/tinygo-org/tinygo/interp" + "github.com/tinygo-org/tinygo/loader" +) + +// A single diagnostic. +type Diagnostic struct { + Pos token.Position + Msg string +} + +// One or multiple errors of a particular package. +// It can also represent whole-program errors (like linker errors) that can't +// easily be connected to a single package. +type PackageDiagnostic struct { + ImportPath string // the same ImportPath as in `go list -json` + Diagnostics []Diagnostic +} + +// Diagnostics of a whole program. This can include errors belonging to multiple +// packages, or just a single package. +type ProgramDiagnostic []PackageDiagnostic + +// CreateDiagnostics reads the underlying errors in the error object and creates +// a set of diagnostics that's sorted and can be readily printed. +func CreateDiagnostics(err error) ProgramDiagnostic { + if err == nil { + return nil + } + // Right now, the compiler will only show errors for the first package that + // fails to build. This is likely to change in the future. + return ProgramDiagnostic{ + createPackageDiagnostic(err), + } +} + +// Create diagnostics for a single package (though, in practice, it may also be +// used for whole-program diagnostics in some cases). +func createPackageDiagnostic(err error) PackageDiagnostic { + // Extract diagnostics for this package. + var pkgDiag PackageDiagnostic + switch err := err.(type) { + case *builder.MultiError: + if err.ImportPath != "" { + pkgDiag.ImportPath = err.ImportPath + } + for _, err := range err.Errs { + diags := createDiagnostics(err) + pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...) + } + case loader.Errors: + if err.Pkg != nil { + pkgDiag.ImportPath = err.Pkg.ImportPath + } + for _, err := range err.Errs { + diags := createDiagnostics(err) + pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, diags...) + } + case *interp.Error: + pkgDiag.ImportPath = err.ImportPath + w := &bytes.Buffer{} + fmt.Fprintln(w, err.Error()) + if len(err.Inst) != 0 { + fmt.Fprintln(w, err.Inst) + } + if len(err.Traceback) > 0 { + fmt.Fprintln(w, "\ntraceback:") + for _, line := range err.Traceback { + fmt.Fprintln(w, line.Pos.String()+":") + fmt.Fprintln(w, line.Inst) + } + } + pkgDiag.Diagnostics = append(pkgDiag.Diagnostics, Diagnostic{ + Msg: w.String(), + }) + default: + pkgDiag.Diagnostics = createDiagnostics(err) + } + + // Sort these diagnostics by file/line/column. + sort.SliceStable(pkgDiag.Diagnostics, func(i, j int) bool { + posI := pkgDiag.Diagnostics[i].Pos + posJ := pkgDiag.Diagnostics[j].Pos + if posI.Filename != posJ.Filename { + return posI.Filename < posJ.Filename + } + if posI.Line != posJ.Line { + return posI.Line < posJ.Line + } + return posI.Column < posJ.Column + }) + + return pkgDiag +} + +// Extract diagnostics from the given error message and return them as a slice +// of errors (which in many cases will just be a single diagnostic). +func createDiagnostics(err error) []Diagnostic { + switch err := err.(type) { + case types.Error: + return []Diagnostic{ + { + Pos: err.Fset.Position(err.Pos), + Msg: err.Msg, + }, + } + case scanner.Error: + return []Diagnostic{ + { + Pos: err.Pos, + Msg: err.Msg, + }, + } + case scanner.ErrorList: + var diags []Diagnostic + for _, err := range err { + diags = append(diags, createDiagnostics(*err)...) + } + return diags + case loader.Error: + if err.Err.Pos.Filename != "" { + // Probably a syntax error in a dependency. + return createDiagnostics(err.Err) + } else { + // Probably an "import cycle not allowed" error. + buf := &bytes.Buffer{} + fmt.Fprintln(buf, "package", err.ImportStack[0]) + for i := 1; i < len(err.ImportStack); i++ { + pkgPath := err.ImportStack[i] + if i == len(err.ImportStack)-1 { + // last package + fmt.Fprintln(buf, "\timports", pkgPath+": "+err.Err.Error()) + } else { + // not the last package + fmt.Fprintln(buf, "\timports", pkgPath) + } + } + return []Diagnostic{ + {Msg: buf.String()}, + } + } + default: + return []Diagnostic{ + {Msg: err.Error()}, + } + } +} + +// Write program diagnostics to the given writer with 'wd' as the relative +// working directory. +func (progDiag ProgramDiagnostic) WriteTo(w io.Writer, wd string) { + for _, pkgDiag := range progDiag { + pkgDiag.WriteTo(w, wd) + } +} + +// Write package diagnostics to the given writer with 'wd' as the relative +// working directory. +func (pkgDiag PackageDiagnostic) WriteTo(w io.Writer, wd string) { + if pkgDiag.ImportPath != "" { + fmt.Fprintln(w, "#", pkgDiag.ImportPath) + } + for _, diag := range pkgDiag.Diagnostics { + diag.WriteTo(w, wd) + } +} + +// Write this diagnostic to the given writer with 'wd' as the relative working +// directory. +func (diag Diagnostic) WriteTo(w io.Writer, wd string) { + if diag.Pos == (token.Position{}) { + fmt.Fprintln(w, diag.Msg) + return + } + pos := diag.Pos // make a copy + if !strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { + // This file is not from the standard library (either the GOROOT or the + // TINYGOROOT). Make the path relative, for easier reading. Ignore any + // errors in the process (falling back to the absolute path). + pos.Filename = tryToMakePathRelative(pos.Filename, wd) + } + fmt.Fprintf(w, "%s: %s\n", pos, diag.Msg) +} + +// try to make the path relative to the current working directory. If any error +// occurs, this error is ignored and the absolute path is returned instead. +func tryToMakePathRelative(dir, wd string) string { + if wd == "" { + return dir // working directory not found + } + relpath, err := filepath.Rel(wd, dir) + if err != nil { + return dir + } + return relpath +} diff --git a/diff.go b/diff.go new file mode 100644 index 0000000000..87ce86d0e5 --- /dev/null +++ b/diff.go @@ -0,0 +1,261 @@ +// Copyright 2022 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. + +package main + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +// A pair is a pair of values tracked for both the x and y side of a diff. +// It is typically a pair of line indexes. +type pair struct{ x, y int } + +// Diff returns an anchored diff of the two texts old and new +// in the “unified diff” format. If old and new are identical, +// Diff returns a nil slice (no output). +// +// Unix diff implementations typically look for a diff with +// the smallest number of lines inserted and removed, +// which can in the worst case take time quadratic in the +// number of lines in the texts. As a result, many implementations +// either can be made to run for a long time or cut off the search +// after a predetermined amount of work. +// +// In contrast, this implementation looks for a diff with the +// smallest number of “unique” lines inserted and removed, +// where unique means a line that appears just once in both old and new. +// We call this an “anchored diff” because the unique lines anchor +// the chosen matching regions. An anchored diff is usually clearer +// than a standard diff, because the algorithm does not try to +// reuse unrelated blank lines or closing braces. +// The algorithm also guarantees to run in O(n log n) time +// instead of the standard O(n²) time. +// +// Some systems call this approach a “patience diff,” named for +// the “patience sorting” algorithm, itself named for a solitaire card game. +// We avoid that name for two reasons. First, the name has been used +// for a few different variants of the algorithm, so it is imprecise. +// Second, the name is frequently interpreted as meaning that you have +// to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, +// when in fact the algorithm is faster than the standard one. +func Diff(oldName string, old []byte, newName string, new []byte) []byte { + if bytes.Equal(old, new) { + return nil + } + x := lines(old) + y := lines(new) + + // Print diff header. + var out bytes.Buffer + fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) + fmt.Fprintf(&out, "--- %s\n", oldName) + fmt.Fprintf(&out, "+++ %s\n", newName) + + // Loop over matches to consider, + // expanding each match to include surrounding lines, + // and then printing diff chunks. + // To avoid setup/teardown cases outside the loop, + // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair + // in the sequence of matches. + var ( + done pair // printed up to x[:done.x] and y[:done.y] + chunk pair // start lines of current chunk + count pair // number of lines from each side in current chunk + ctext []string // lines for current chunk + ) + for _, m := range tgs(x, y) { + if m.x < done.x { + // Already handled scanning forward from earlier match. + continue + } + + // Expand matching lines as far as possible, + // establishing that x[start.x:end.x] == y[start.y:end.y]. + // Note that on the first (or last) iteration we may (or definitely do) + // have an empty match: start.x==end.x and start.y==end.y. + start := m + for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { + start.x-- + start.y-- + } + end := m + for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { + end.x++ + end.y++ + } + + // Emit the mismatched lines before start into this chunk. + // (No effect on first sentinel iteration, when start = {0,0}.) + for _, s := range x[done.x:start.x] { + ctext = append(ctext, "-"+s) + count.x++ + } + for _, s := range y[done.y:start.y] { + ctext = append(ctext, "+"+s) + count.y++ + } + + // If we're not at EOF and have too few common lines, + // the chunk includes all the common lines and continues. + const C = 3 // number of context lines + if (end.x < len(x) || end.y < len(y)) && + (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { + for _, s := range x[start.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + continue + } + + // End chunk with common lines for context. + if len(ctext) > 0 { + n := end.x - start.x + if n > C { + n = C + } + for _, s := range x[start.x : start.x+n] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = pair{start.x + n, start.y + n} + + // Format and emit chunk. + // Convert line numbers to 1-indexed. + // Special case: empty file shows up as 0,0 not 1,0. + if count.x > 0 { + chunk.x++ + } + if count.y > 0 { + chunk.y++ + } + fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) + for _, s := range ctext { + out.WriteString(s) + } + count.x = 0 + count.y = 0 + ctext = ctext[:0] + } + + // If we reached EOF, we're done. + if end.x >= len(x) && end.y >= len(y) { + break + } + + // Otherwise start a new chunk. + chunk = pair{end.x - C, end.y - C} + for _, s := range x[chunk.x:end.x] { + ctext = append(ctext, " "+s) + count.x++ + count.y++ + } + done = end + } + + return out.Bytes() +} + +// lines returns the lines in the file x, including newlines. +// If the file does not end in a newline, one is supplied +// along with a warning about the missing newline. +func lines(x []byte) []string { + l := strings.SplitAfter(string(x), "\n") + if l[len(l)-1] == "" { + l = l[:len(l)-1] + } else { + // Treat last line as having a message about the missing newline attached, + // using the same text as BSD/GNU diff (including the leading backslash). + l[len(l)-1] += "\n\\ No newline at end of file\n" + } + return l +} + +// tgs returns the pairs of indexes of the longest common subsequence +// of unique lines in x and y, where a unique line is one that appears +// once in x and once in y. +// +// The longest common subsequence algorithm is as described in +// Thomas G. Szymanski, “A Special Case of the Maximal Common +// Subsequence Problem,” Princeton TR #170 (January 1975), +// available at https://research.swtch.com/tgs170.pdf. +func tgs(x, y []string) []pair { + // Count the number of times each string appears in a and b. + // We only care about 0, 1, many, counted as 0, -1, -2 + // for the x side and 0, -4, -8 for the y side. + // Using negative numbers now lets us distinguish positive line numbers later. + m := make(map[string]int) + for _, s := range x { + if c := m[s]; c > -2 { + m[s] = c - 1 + } + } + for _, s := range y { + if c := m[s]; c > -8 { + m[s] = c - 4 + } + } + + // Now unique strings can be identified by m[s] = -1+-4. + // + // Gather the indexes of those strings in x and y, building: + // xi[i] = increasing indexes of unique strings in x. + // yi[i] = increasing indexes of unique strings in y. + // inv[i] = index j such that x[xi[i]] = y[yi[j]]. + var xi, yi, inv []int + for i, s := range y { + if m[s] == -1+-4 { + m[s] = len(yi) + yi = append(yi, i) + } + } + for i, s := range x { + if j, ok := m[s]; ok && j >= 0 { + xi = append(xi, i) + inv = append(inv, j) + } + } + + // Apply Algorithm A from Szymanski's paper. + // In those terms, A = J = inv and B = [0, n). + // We add sentinel pairs {0,0}, and {len(x),len(y)} + // to the returned sequence, to help the processing loop. + J := inv + n := len(xi) + T := make([]int, n) + L := make([]int, n) + for i := range T { + T[i] = n + 1 + } + for i := 0; i < n; i++ { + k := sort.Search(n, func(k int) bool { + return T[k] >= J[i] + }) + T[k] = J[i] + L[i] = k + 1 + } + k := 0 + for _, v := range L { + if k < v { + k = v + } + } + seq := make([]pair, 2+k) + seq[1+k] = pair{len(x), len(y)} // sentinel at end + lastj := n + for i := n - 1; i >= 0; i-- { + if L[i] == k && J[i] < lastj { + seq[k] = pair{xi[i], yi[J[i]]} + k-- + } + } + seq[0] = pair{0, 0} // sentinel at start + return seq +} diff --git a/errors_test.go b/errors_test.go new file mode 100644 index 0000000000..fe118616e3 --- /dev/null +++ b/errors_test.go @@ -0,0 +1,139 @@ +package main + +import ( + "bytes" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/diagnostics" +) + +// Test the error messages of the TinyGo compiler. +func TestErrors(t *testing.T) { + // TODO: nicely formatted error messages for: + // - duplicate symbols in ld.lld (currently only prints bitcode file) + type errorTest struct { + name string + target string + } + for _, tc := range []errorTest{ + {name: "cgo"}, + {name: "compiler"}, + {name: "interp"}, + {name: "invalidmain"}, + {name: "invalidname"}, + {name: "linker-flashoverflow", target: "cortex-m-qemu"}, + {name: "linker-ramoverflow", target: "cortex-m-qemu"}, + {name: "linker-undefined", target: "darwin/arm64"}, + {name: "linker-undefined", target: "linux/amd64"}, + //{name: "linker-undefined", target: "windows/amd64"}, // TODO: no source location + {name: "linker-undefined", target: "cortex-m-qemu"}, + //{name: "linker-undefined", target: "wasip1"}, // TODO: no source location + {name: "loader-importcycle"}, + {name: "loader-invaliddep"}, + {name: "loader-invalidpackage"}, + {name: "loader-nopackage"}, + {name: "optimizer"}, + {name: "syntax"}, + {name: "types"}, + } { + name := tc.name + if tc.target != "" { + name += "#" + tc.target + } + target := tc.target + if target == "" { + target = "wasip1" + } + t.Run(name, func(t *testing.T) { + options := optionsFromTarget(target, sema) + testErrorMessages(t, "./testdata/errors/"+tc.name+".go", &options) + }) + } +} + +func testErrorMessages(t *testing.T, filename string, options *compileopts.Options) { + t.Parallel() + + // Parse expected error messages. + expected := readErrorMessages(t, filename) + + // Try to build a binary (this should fail with an error). + tmpdir := t.TempDir() + err := Build(filename, tmpdir+"/out", options) + if err == nil { + t.Fatal("expected to get a compiler error") + } + + // Get the full ./testdata/errors directory. + wd, absErr := filepath.Abs("testdata/errors") + if absErr != nil { + t.Fatal(absErr) + } + + // Write error message out as plain text. + var buf bytes.Buffer + diagnostics.CreateDiagnostics(err).WriteTo(&buf, wd) + actual := strings.TrimRight(buf.String(), "\n") + + // Check whether the error is as expected. + if !matchErrors(t, expected, actual) { + t.Errorf("expected error:\n%s\ngot:\n%s", indentText(expected, "> "), indentText(actual, "> ")) + } +} + +func matchErrors(t *testing.T, pattern, actual string) bool { + patternLines := strings.Split(pattern, "\n") + actualLines := strings.Split(actual, "\n") + if len(patternLines) != len(actualLines) { + return false + } + for i, patternLine := range patternLines { + indices := regexp.MustCompile(`\{\{.*?\}\}`).FindAllStringIndex(patternLine, -1) + patternParts := []string{"^"} + lastStop := 0 + for _, startstop := range indices { + start := startstop[0] + stop := startstop[1] + patternParts = append(patternParts, + regexp.QuoteMeta(patternLine[lastStop:start]), + patternLine[start+2:stop-2]) + lastStop = stop + } + patternParts = append(patternParts, regexp.QuoteMeta(patternLine[lastStop:]), "$") + pattern := strings.Join(patternParts, "") + re, err := regexp.Compile(pattern) + if err != nil { + t.Fatalf("could not compile regexp for %#v: %v", patternLine, err) + } + if !re.MatchString(actualLines[i]) { + return false + } + } + return true +} + +// Indent the given text with a given indentation string. +func indentText(text, indent string) string { + return indent + strings.ReplaceAll(text, "\n", "\n"+indent) +} + +// Read "// ERROR:" prefixed messages from the given file. +func readErrorMessages(t *testing.T, file string) string { + data, err := os.ReadFile(file) + if err != nil { + t.Fatal("could not read input file:", err) + } + + var errors []string + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, "// ERROR: ") { + errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n")) + } + } + return strings.Join(errors, "\n") +} diff --git a/flake.lock b/flake.lock index 6c453ad9bc..d541b769ca 100644 --- a/flake.lock +++ b/flake.lock @@ -20,16 +20,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1703068421, - "narHash": "sha256-WSw5Faqlw75McIflnl5v7qVD/B3S2sLh+968bpOGrWA=", + "lastModified": 1728500571, + "narHash": "sha256-dOymOQ3AfNI4Z337yEwHGohrVQb4yPODCW9MDUyAc4w=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d65bceaee0fb1e64363f7871bc43dc1c6ecad99f", + "rev": "d51c28603def282a24fa034bcb007e2bcb5b5dd0", "type": "github" }, "original": { "id": "nixpkgs", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "type": "indirect" } }, diff --git a/flake.nix b/flake.nix index b98612d328..25ffc70205 100644 --- a/flake.nix +++ b/flake.nix @@ -18,10 +18,10 @@ # # But you'll need a bit more to make TinyGo actually able to compile code: # -# make llvm-source # fetch compiler-rt -# git submodule update --init --recursive # fetch lots of other libraries and SVD files -# make gen-device -j4 # build src/device/*/*.go files -# make wasi-libc # build support for wasi/wasm +# make llvm-source # fetch compiler-rt +# git submodule update --init # fetch lots of other libraries and SVD files +# make gen-device -j4 # build src/device/*/*.go files +# make wasi-libc # build support for wasi/wasm # # With this, you should have an environment that can compile anything - except # for the Xtensa architecture (ESP8266/ESP32) because support for that lives in @@ -35,7 +35,7 @@ inputs = { # Use a recent stable release, but fix the version to make it reproducible. # This version should be updated from time to time. - nixpkgs.url = "nixpkgs/nixos-23.11"; + nixpkgs.url = "nixpkgs/nixos-24.05"; flake-utils.url = "github:numtide/flake-utils"; }; outputs = { self, nixpkgs, flake-utils }: @@ -49,12 +49,11 @@ buildInputs = [ # These dependencies are required for building tinygo (go install). go - llvmPackages_17.llvm - llvmPackages_17.libclang - llvmPackages_17.libcxx + llvmPackages_18.llvm + llvmPackages_18.libclang # Additional dependencies needed at runtime, for building and/or # flashing. - llvmPackages_17.lld + llvmPackages_18.lld avrdude binaryen # Additional dependencies needed for on-chip debugging. @@ -69,7 +68,7 @@ # Without setting these explicitly, Homebrew versions might be used # or the default `ar` and `nm` tools might be used (which don't # support wasi). - export CLANG="clang-17 -resource-dir ${llvmPackages_17.clang.cc.lib}/lib/clang/17" + export CLANG="clang-18 -resource-dir ${llvmPackages_18.clang.cc.lib}/lib/clang/18" export LLVM_AR=llvm-ar export LLVM_NM=llvm-nm @@ -78,7 +77,7 @@ export MD5SUM=md5sum # Ugly hack to make the Clang resources directory available. - export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_17.clang.cc.lib}/lib/clang/17"\" + export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_18.clang.cc.lib}/lib/clang/18\" -tags=llvm18" ''; }; } diff --git a/go.mod b/go.mod index 42a7e5f1c7..ec52702d53 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/tinygo-org/tinygo -go 1.18 +go 1.19 require ( - github.com/aykevl/go-wasm v0.0.2-0.20220616010729-4a0a888aebdc + github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982 github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 github.com/chromedp/cdproto v0.0.0-20220113222801-0725d94bb6ee github.com/chromedp/chromedp v0.7.6 @@ -11,15 +11,16 @@ require ( github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 - github.com/mattn/go-colorable v0.1.8 + github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-tty v0.0.4 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 + github.com/tetratelabs/wazero v1.6.0 go.bug.st/serial v1.6.0 - golang.org/x/net v0.20.0 - golang.org/x/sys v0.16.0 - golang.org/x/tools v0.17.0 + golang.org/x/net v0.26.0 + golang.org/x/sys v0.21.0 + golang.org/x/tools v0.22.1-0.20240621165957-db513b091504 gopkg.in/yaml.v2 v2.4.0 - tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318 + tinygo.org/x/go-llvm v0.0.0-20250119132755-9dca92dfb4f9 ) require ( @@ -30,6 +31,7 @@ require ( github.com/gobwas/ws v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect - golang.org/x/text v0.14.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/text v0.16.0 // indirect ) diff --git a/go.sum b/go.sum index 28bd6e3387..f8cef17c11 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/aykevl/go-wasm v0.0.2-0.20220616010729-4a0a888aebdc h1:Yp49g+qqgQRPk/gcRSmAsXgnT16XPJ6Y5JM1poc6gYM= -github.com/aykevl/go-wasm v0.0.2-0.20220616010729-4a0a888aebdc/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= +github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982 h1:cD7QfvrJdYmBw2tFP/VyKPT8ZESlcrwSwo7SvH9Y4dc= +github.com/aykevl/go-wasm v0.0.2-0.20240825160117-b76c3f9f0982/go.mod h1:7sXyiaA0WtSogCu67R2252fQpVmJMh9JWJ9ddtGkpWw= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/chromedp/cdproto v0.0.0-20211126220118-81fa0469ad77/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= @@ -11,7 +11,7 @@ github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3I github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= @@ -31,12 +31,13 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw= github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E= github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28= @@ -45,29 +46,33 @@ github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzb github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +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/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g= +github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A= go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A= go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.22.1-0.20240621165957-db513b091504 h1:MMsD8mMfluf/578+3wrTn22pjI/Xkzm+gPW47SYfspY= +golang.org/x/tools v0.22.1-0.20240621165957-db513b091504/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318 h1:4KjZvPtcN1UwobevcGbdzeinx0L1i8HDdJu84bu7NI8= -tinygo.org/x/go-llvm v0.0.0-20240106122909-c2c543540318/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +tinygo.org/x/go-llvm v0.0.0-20250119132755-9dca92dfb4f9 h1:rMvEzuCYjyiR+pmdiCVWTQw3L6VqiSIXoL19I3lYufE= +tinygo.org/x/go-llvm v0.0.0-20250119132755-9dca92dfb4f9/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= diff --git a/goenv/goenv.go b/goenv/goenv.go index be1c631ca9..fe4c8bf63e 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -30,8 +30,11 @@ var Keys = []string{ } func init() { - if Get("GOARCH") == "arm" { + switch Get("GOARCH") { + case "arm": Keys = append(Keys, "GOARM") + case "mips", "mipsle": + Keys = append(Keys, "GOMIPS") } } @@ -128,6 +131,13 @@ func Get(name string) string { // difference between ARMv6 and ARMv7. ARMv6 binaries are much smaller, // especially when floating point instructions are involved. return "6" + case "GOMIPS": + gomips := os.Getenv("GOMIPS") + if gomips == "" { + // Default to hardfloat (this matches the Go toolchain). + gomips = "hardfloat" + } + return gomips case "GOROOT": readGoEnvVars() return goEnvVars.GOROOT @@ -159,6 +169,11 @@ func Get(name string) string { } return findWasmOpt() + case "WASMTOOLS": + if path := os.Getenv("WASMTOOLS"); path != "" { + return path + } + return "wasm-tools" default: return "" } diff --git a/goenv/version.go b/goenv/version.go index e9de723b12..8b0aa07631 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -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.31.2" - -var ( - // This variable is set at build time using -ldflags parameters. - // See: https://stackoverflow.com/a/11355611 - GitSha1 string -) +const version = "0.37.0" // 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) { @@ -34,27 +42,67 @@ func GetGorootVersion() (major, minor int, err error) { if err != nil { return 0, 0, err } + major, minor, _, err = Parse(s) + return major, minor, err +} - if s == "" || s[:2] != "go" { - return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") +// Parse parses the Go version (like "go1.3.2") in the parameter and return the +// 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") } - parts := strings.Split(s[2:], ".") + parts := strings.Split(version[2:], ".") if len(parts) < 2 { - return 0, 0, errors.New("could not parse Go version: version has less than two parts") + return 0, 0, 0, errors.New("could not parse Go version: version has less than two parts") } // Ignore the errors, we don't really handle errors here anyway. var trailing string - n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing) - if n == 2 && err == io.EOF { + n, err := fmt.Sscanf(version, "go%d.%d.%d%s", &major, &minor, &patch, &trailing) + if n == 2 { + n, err = fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing) + } + if n >= 2 && err == io.EOF { // Means there were no trailing characters (i.e., not an alpha/beta) err = nil } if err != nil { - return 0, 0, fmt.Errorf("failed to parse version: %s", err) + return 0, 0, 0, fmt.Errorf("failed to parse version: %s", err) + } + + return major, minor, patch, nil +} + +// Compare compares two Go version strings. +// The result will be 0 if a == b, -1 if a < b, and +1 if a > b. +// If either a or b is not a valid Go version, it is treated as "go0.0" +// and compared lexicographically. +// See [Parse] for more information. +func Compare(a, b string) int { + aMajor, aMinor, aPatch, _ := Parse(a) + bMajor, bMinor, bPatch, _ := Parse(b) + switch { + case aMajor < bMajor: + return -1 + case aMajor > bMajor: + return +1 + case aMinor < bMinor: + return -1 + case aMinor > bMinor: + return +1 + case aPatch < bPatch: + return -1 + case aPatch > bPatch: + return +1 + default: + return strings.Compare(a, b) } - return } // GorootVersionString returns the version string as reported by the Go diff --git a/goenv/version_test.go b/goenv/version_test.go new file mode 100644 index 0000000000..cb408a2eae --- /dev/null +++ b/goenv/version_test.go @@ -0,0 +1,72 @@ +package goenv + +import "testing" + +func TestParse(t *testing.T) { + tests := []struct { + v string + major int + minor int + patch int + wantErr bool + }{ + {"", 0, 0, 0, true}, + {"go", 0, 0, 0, true}, + {"go1", 0, 0, 0, true}, + {"go.0", 0, 0, 0, true}, + {"go1.0", 1, 0, 0, false}, + {"go1.1", 1, 1, 0, false}, + {"go1.23", 1, 23, 0, false}, + {"go1.23.5", 1, 23, 5, false}, + {"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) { + major, minor, patch, err := Parse(tt.v) + if err == nil && tt.wantErr { + t.Errorf("Parse(%q): expected err != nil", tt.v) + } + if err != nil && !tt.wantErr { + t.Errorf("Parse(%q): expected err == nil", tt.v) + } + if major != tt.major || minor != tt.minor || patch != tt.patch { + t.Errorf("Parse(%q): expected %d, %d, %d, nil; got %d, %d, %d, %v", + tt.v, tt.major, tt.minor, tt.patch, major, minor, patch, err) + } + }) + } +} + +func TestCompare(t *testing.T) { + tests := []struct { + a string + b string + want int + }{ + {"", "", 0}, + {"go0", "go0", 0}, + {"go0", "go1", -1}, + {"go1", "go0", 1}, + {"go1", "go2", -1}, + {"go2", "go1", 1}, + {"go1.1", "go1.2", -1}, + {"go1.2", "go1.1", 1}, + {"go1.1.0", "go1.2.0", -1}, + {"go1.2.0", "go1.1.0", 1}, + {"go1.2.0", "go2.3.0", -1}, + {"go1.23.2", "go1.23.10", -1}, + {"go0.1.22", "go1.23.101", -1}, + } + for _, tt := range tests { + t.Run(tt.a+" "+tt.b, func(t *testing.T) { + got := Compare(tt.a, tt.b) + if got != tt.want { + t.Errorf("Compare(%q, %q): expected %d; got %d", + tt.a, tt.b, tt.want, got) + } + }) + } +} diff --git a/hooks/post_checkout b/hooks/post_checkout index 71fa8f796d..1c6f495cf0 100755 --- a/hooks/post_checkout +++ b/hooks/post_checkout @@ -1,4 +1,4 @@ #!/bin/bash # Docker hub does a recursive clone, then checks the branch out, # so when a PR adds a submodule (or updates it), it fails. -git submodule update --init --recursive +git submodule update --init diff --git a/internal/tools/go.mod b/internal/tools/go.mod new file mode 100644 index 0000000000..854c93fc0a --- /dev/null +++ b/internal/tools/go.mod @@ -0,0 +1,30 @@ +// TODO: remove this (by merging it into the top-level go.mod) +// once the top level go.mod specifies a go new enough to make our version of misspell happy. + +module tools + +go 1.21 + +require ( + github.com/golangci/misspell v0.6.0 + github.com/mgechev/revive v1.3.9 +) + +require ( + github.com/BurntSushi/toml v1.4.0 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/fatih/color v1.17.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/afero v1.11.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.23.0 // indirect +) diff --git a/internal/tools/go.sum b/internal/tools/go.sum new file mode 100644 index 0000000000..dea0fb1f9c --- /dev/null +++ b/internal/tools/go.sum @@ -0,0 +1,56 @@ +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= +github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= +github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= +github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +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= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/tools/tools.go b/internal/tools/tools.go new file mode 100644 index 0000000000..0cbb56edf7 --- /dev/null +++ b/internal/tools/tools.go @@ -0,0 +1,13 @@ +//go:build tools + +// Install tools specified in go.mod. +// See https://marcofranssen.nl/manage-go-tools-via-go-modules for idiom. +package tools + +import ( + _ "github.com/golangci/misspell" + _ "github.com/mgechev/revive" +) + +//go:generate go install github.com/golangci/misspell/cmd/misspell +//go:generate go install github.com/mgechev/revive diff --git a/internal/wasm-tools/README.md b/internal/wasm-tools/README.md new file mode 100644 index 0000000000..5e3a94ec6e --- /dev/null +++ b/internal/wasm-tools/README.md @@ -0,0 +1,5 @@ +# wasm-tools directory + +This directory has a separate `go.mod` file because the `wasm-tools-go` module requires Go 1.22, while TinyGo itself supports Go 1.19. + +When the minimum Go version for TinyGo is 1.22, this directory can be folded into `internal/tools` and the `go.mod` and `go.sum` files deleted. diff --git a/internal/wasm-tools/go.mod b/internal/wasm-tools/go.mod new file mode 100644 index 0000000000..d91b5475cd --- /dev/null +++ b/internal/wasm-tools/go.mod @@ -0,0 +1,22 @@ +module github.com/tinygo-org/tinygo/internal/wasm-tools + +go 1.23.0 + +require ( + go.bytecodealliance.org v0.6.2 + go.bytecodealliance.org/cm v0.2.2 +) + +require ( + github.com/coreos/go-semver v0.3.1 // indirect + github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/regclient/regclient v0.8.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/tetratelabs/wazero v1.9.0 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect + github.com/urfave/cli/v3 v3.0.0-beta1 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/sys v0.31.0 // indirect +) diff --git a/internal/wasm-tools/go.sum b/internal/wasm-tools/go.sum new file mode 100644 index 0000000000..374f54e169 --- /dev/null +++ b/internal/wasm-tools/go.sum @@ -0,0 +1,48 @@ +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= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4= +github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/olareg/olareg v0.1.1 h1:Ui7q93zjcoF+U9U71sgqgZWByDoZOpqHitUXEu2xV+g= +github.com/olareg/olareg v0.1.1/go.mod h1:w8NP4SWrHHtxsFaUiv1lnCnYPm4sN1seCd2h7FK/dc0= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/regclient/regclient v0.8.2 h1:23BQ3jWgKYHHIXUhp/S9laVJDHDoOQaQCzXMJ4undVE= +github.com/regclient/regclient v0.8.2/go.mod h1:uGyetv0o6VLyRDjtfeBqp/QBwRLJ3Hcn07/+8QbhNcM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= +github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +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-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ= +go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA= +go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA= +go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/wasm-tools/tools.go b/internal/wasm-tools/tools.go new file mode 100644 index 0000000000..baf54a4a33 --- /dev/null +++ b/internal/wasm-tools/tools.go @@ -0,0 +1,12 @@ +//go:build tools + +// Install tools specified in go.mod. +// See https://marcofranssen.nl/manage-go-tools-via-go-modules for idiom. +package tools + +import ( + _ "go.bytecodealliance.org/cm" + _ "go.bytecodealliance.org/cmd/wit-bindgen-go" +) + +//go:generate go install go.bytecodealliance.org/cmd/wit-bindgen-go diff --git a/interp/README.md b/interp/README.md index c0a794cc75..26ab657c0c 100644 --- a/interp/README.md +++ b/interp/README.md @@ -28,7 +28,7 @@ All in all, this design provides several benefits: it should be a whole lot faster for loops as it doesn't have to call into LLVM (via CGo) for every operation. -As mentioned, this partial evaulator comes in three parts: a compiler, an +As mentioned, this partial evaluator comes in three parts: a compiler, an interpreter, and a memory manager. ## Compiler diff --git a/interp/interp.go b/interp/interp.go index 63a664920d..30b0872485 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -3,11 +3,13 @@ package interp import ( + "encoding/binary" "fmt" "os" "strings" "time" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -24,6 +26,7 @@ type runner struct { dataPtrType llvm.Type // often used type so created in advance uintptrType llvm.Type // equivalent to uintptr in Go maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result + byteOrder binary.ByteOrder // big-endian or little-endian debug bool // log debug messages pkgName string // package name of the currently executing package functionCache map[llvm.Value]*function // cache of compiled functions @@ -38,6 +41,7 @@ func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { r := runner{ mod: mod, targetData: llvm.NewTargetData(mod.DataLayout()), + byteOrder: llvmutil.ByteOrder(mod.Target()), debug: debug, functionCache: make(map[llvm.Value]*function), objects: []object{{}}, @@ -52,7 +56,7 @@ func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { return &r } -// Dispose deallocates all alloated LLVM resources. +// Dispose deallocates all allocated LLVM resources. func (r *runner) dispose() { r.targetData.Dispose() r.targetData = llvm.TargetData{} diff --git a/interp/interpreter.go b/interp/interpreter.go index 605f4d8fc6..3813035e1d 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -173,7 +173,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent case 3: // Conditional branch: [cond, thenBB, elseBB] lastBB = currentBB - switch operands[0].Uint() { + switch operands[0].Uint(r) { case 1: // true -> thenBB currentBB = int(operands[1].(literalValue).value.(uint32)) case 0: // false -> elseBB @@ -191,12 +191,12 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } case llvm.Switch: // Switch statement: [value, defaultLabel, case0, label0, case1, label1, ...] - value := operands[0].Uint() - targetLabel := operands[1].Uint() // default label + value := operands[0].Uint(r) + targetLabel := operands[1].Uint(r) // default label // Do a lazy switch by iterating over all cases. for i := 2; i < len(operands); i += 2 { - if value == operands[i].Uint() { - targetLabel = operands[i+1].Uint() + if value == operands[i].Uint(r) { + targetLabel = operands[i+1].Uint(r) break } } @@ -211,7 +211,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // Select is much like a ternary operator: it picks a result from // the second and third operand based on the boolean first operand. var result value - switch operands[0].Uint() { + switch operands[0].Uint(r) { case 1: result = operands[1] case 0: @@ -282,14 +282,22 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // by creating a global variable. // Get the requested memory size to be allocated. - size := operands[1].Uint() + size := operands[1].Uint(r) // Get the object layout, if it is available. llvmLayoutType := r.getLLVMTypeFromLayout(operands[2]) + // Get the alignment of the memory to be allocated. + alignment := 0 // use default alignment if unset + alignAttr := inst.llvmInst.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align")) + if !alignAttr.IsNil() { + alignment = int(alignAttr.GetEnumValue()) + } + // Create the object. alloc := object{ globalName: r.pkgName + "$alloc", + align: alignment, llvmLayoutType: llvmLayoutType, buffer: newRawValue(uint32(size)), size: uint32(size), @@ -318,9 +326,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // memmove(dst, src, n*elemSize) // return int(n) // } - dstLen := operands[3].Uint() - srcLen := operands[4].Uint() - elemSize := operands[5].Uint() + dstLen := operands[3].Uint(r) + srcLen := operands[4].Uint(r) + elemSize := operands[5].Uint(r) n := srcLen if n > dstLen { n = dstLen @@ -374,7 +382,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } - nBytes := uint32(operands[3].Uint()) + nBytes := uint32(operands[3].Uint(r)) dstObj := mem.getWritable(dst.index()) dstBuf := dstObj.buffer.asRawValue(r) if mem.get(src.index()).buffer == nil { @@ -417,6 +425,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } else { locals[inst.localIndex] = literalValue{uint8(0)} } + case callFn.name == "__tinygo_interp_raise_test_error": + // Special function that will trigger an error. + // This is used to test error reporting. + return nil, mem, r.errorAt(inst, errors.New("test error")) case strings.HasSuffix(callFn.name, ".$typeassert"): if r.debug { fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:]) @@ -427,9 +439,20 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } + // typecodePtr always point to the numMethod field in the type + // description struct. The methodSet, when present, comes right + // before the numMethod field (the compiler doesn't generate + // method sets for concrete types without methods). + // Considering that the compiler doesn't emit interface type + // asserts for interfaces with no methods (as the always succeed) + // then if the offset is zero, this assert must always fail. + if typecodePtr.offset() == 0 { + locals[inst.localIndex] = literalValue{uint8(0)} + break + } typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize)) if err != nil { - return nil, mem, r.errorAt(inst, err) // unlikely + return nil, mem, r.errorAt(inst, err) } methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r) if err != nil { @@ -631,6 +654,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent globalName: r.pkgName + "$alloca", buffer: newRawValue(uint32(size)), size: uint32(size), + align: inst.llvmInst.Alignment(), } index := len(r.objects) r.objects = append(r.objects, alloca) @@ -646,8 +670,8 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // pointer into the underlying object. var offset int64 for i := 1; i < len(operands); i += 2 { - index := operands[i].Int() - elementSize := operands[i+1].Int() + index := operands[i].Int(r) + elementSize := operands[i+1].Int(r) if elementSize < 0 { // This is a struct field. offset += index @@ -662,7 +686,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent return nil, mem, r.errorAt(inst, err) } // GEP on fixed pointer value (for example, memory-mapped I/O). - ptrValue := operands[0].Uint() + uint64(offset) + ptrValue := operands[0].Uint(r) + uint64(offset) locals[inst.localIndex] = makeLiteralInt(ptrValue, int(operands[0].len(r)*8)) continue } @@ -724,11 +748,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent var lhs, rhs float64 switch operands[0].len(r) { case 8: - lhs = math.Float64frombits(operands[0].Uint()) - rhs = math.Float64frombits(operands[1].Uint()) + lhs = math.Float64frombits(operands[0].Uint(r)) + rhs = math.Float64frombits(operands[1].Uint(r)) case 4: - lhs = float64(math.Float32frombits(uint32(operands[0].Uint()))) - rhs = float64(math.Float32frombits(uint32(operands[1].Uint()))) + lhs = float64(math.Float32frombits(uint32(operands[0].Uint(r)))) + rhs = float64(math.Float32frombits(uint32(operands[1].Uint(r)))) default: panic("unknown float type") } @@ -767,23 +791,23 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if inst.opcode == llvm.Add { // This likely means this is part of a // unsafe.Pointer(uintptr(ptr) + offset) pattern. - lhsPtr, err = lhsPtr.addOffset(int64(rhs.Uint())) + lhsPtr, err = lhsPtr.addOffset(int64(rhs.Uint(r))) if err != nil { return nil, mem, r.errorAt(inst, err) } locals[inst.localIndex] = lhsPtr - } else if inst.opcode == llvm.Xor && rhs.Uint() == 0 { + } else if inst.opcode == llvm.Xor && rhs.Uint(r) == 0 { // Special workaround for strings.noescape, see // src/strings/builder.go in the Go source tree. This is // the identity operator, so we can return the input. locals[inst.localIndex] = lhs - } else if inst.opcode == llvm.And && rhs.Uint() < 8 { + } else if inst.opcode == llvm.And && rhs.Uint(r) < 8 { // This is probably part of a pattern to get the lower bits // of a pointer for pointer tagging, like this: // uintptr(unsafe.Pointer(t)) & 0b11 // We can actually support this easily by ANDing with the // pointer offset. - result := uint64(lhsPtr.offset()) & rhs.Uint() + result := uint64(lhsPtr.offset()) & rhs.Uint(r) locals[inst.localIndex] = makeLiteralInt(result, int(lhs.len(r)*8)) } else { // Catch-all for weird operations that should just be done @@ -798,31 +822,31 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent var result uint64 switch inst.opcode { case llvm.Add: - result = lhs.Uint() + rhs.Uint() + result = lhs.Uint(r) + rhs.Uint(r) case llvm.Sub: - result = lhs.Uint() - rhs.Uint() + result = lhs.Uint(r) - rhs.Uint(r) case llvm.Mul: - result = lhs.Uint() * rhs.Uint() + result = lhs.Uint(r) * rhs.Uint(r) case llvm.UDiv: - result = lhs.Uint() / rhs.Uint() + result = lhs.Uint(r) / rhs.Uint(r) case llvm.SDiv: - result = uint64(lhs.Int() / rhs.Int()) + result = uint64(lhs.Int(r) / rhs.Int(r)) case llvm.URem: - result = lhs.Uint() % rhs.Uint() + result = lhs.Uint(r) % rhs.Uint(r) case llvm.SRem: - result = uint64(lhs.Int() % rhs.Int()) + result = uint64(lhs.Int(r) % rhs.Int(r)) case llvm.Shl: - result = lhs.Uint() << rhs.Uint() + result = lhs.Uint(r) << rhs.Uint(r) case llvm.LShr: - result = lhs.Uint() >> rhs.Uint() + result = lhs.Uint(r) >> rhs.Uint(r) case llvm.AShr: - result = uint64(lhs.Int() >> rhs.Uint()) + result = uint64(lhs.Int(r) >> rhs.Uint(r)) case llvm.And: - result = lhs.Uint() & rhs.Uint() + result = lhs.Uint(r) & rhs.Uint(r) case llvm.Or: - result = lhs.Uint() | rhs.Uint() + result = lhs.Uint(r) | rhs.Uint(r) case llvm.Xor: - result = lhs.Uint() ^ rhs.Uint() + result = lhs.Uint(r) ^ rhs.Uint(r) default: panic("unreachable") } @@ -840,11 +864,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // and then truncating it as necessary. var value uint64 if inst.opcode == llvm.SExt { - value = uint64(operands[0].Int()) + value = uint64(operands[0].Int(r)) } else { - value = operands[0].Uint() + value = operands[0].Uint(r) } - bitwidth := operands[1].Uint() + bitwidth := operands[1].Uint(r) if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth) } @@ -853,11 +877,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent var value float64 switch inst.opcode { case llvm.SIToFP: - value = float64(operands[0].Int()) + value = float64(operands[0].Int(r)) case llvm.UIToFP: - value = float64(operands[0].Uint()) + value = float64(operands[0].Uint(r)) } - bitwidth := operands[1].Uint() + bitwidth := operands[1].Uint(r) if r.debug { fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth) } @@ -903,21 +927,21 @@ func (r *runner) interpretICmp(lhs, rhs value, predicate llvm.IntPredicate) bool } return result case llvm.IntUGT: - return lhs.Uint() > rhs.Uint() + return lhs.Uint(r) > rhs.Uint(r) case llvm.IntUGE: - return lhs.Uint() >= rhs.Uint() + return lhs.Uint(r) >= rhs.Uint(r) case llvm.IntULT: - return lhs.Uint() < rhs.Uint() + return lhs.Uint(r) < rhs.Uint(r) case llvm.IntULE: - return lhs.Uint() <= rhs.Uint() + return lhs.Uint(r) <= rhs.Uint(r) case llvm.IntSGT: - return lhs.Int() > rhs.Int() + return lhs.Int(r) > rhs.Int(r) case llvm.IntSGE: - return lhs.Int() >= rhs.Int() + return lhs.Int(r) >= rhs.Int(r) case llvm.IntSLT: - return lhs.Int() < rhs.Int() + return lhs.Int(r) < rhs.Int(r) case llvm.IntSLE: - return lhs.Int() <= rhs.Int() + return lhs.Int(r) <= rhs.Int(r) default: // _should_ be unreachable, until LLVM adds new icmp operands (unlikely) panic("interp: unsupported icmp") @@ -946,9 +970,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me case llvm.Call: llvmFn := operands[len(operands)-1] args := operands[:len(operands)-1] - for _, arg := range args { - if arg.Type().TypeKind() == llvm.PointerTypeKind { - err := mem.markExternalStore(arg) + for _, op := range operands { + if op.Type().TypeKind() == llvm.PointerTypeKind { + err := mem.markExternalStore(op) if err != nil { return r.errorAt(inst, err) } diff --git a/interp/memory.go b/interp/memory.go index 759a1ffe48..2812cd01c2 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -42,6 +42,7 @@ type object struct { globalName string // name, if not yet created (not guaranteed to be the final name) buffer value // buffer with value as given by interp, nil if external size uint32 // must match buffer.len(), if available + align int // alignment of the object (may be 0 if unknown) constant bool // true if this is a constant global marked uint8 // 0 means unmarked, 1 means external read, 2 means external write } @@ -361,8 +362,8 @@ type value interface { clone() value asPointer(*runner) (pointerValue, error) asRawValue(*runner) rawValue - Uint() uint64 - Int() int64 + Uint(*runner) uint64 + Int(*runner) int64 toLLVMValue(llvm.Type, *memoryView) (llvm.Value, error) String() string } @@ -405,7 +406,8 @@ func (v literalValue) len(r *runner) uint32 { } func (v literalValue) String() string { - return strconv.FormatInt(v.Int(), 10) + // Note: passing a nil *runner to v.Int because we know it won't use it. + return strconv.FormatInt(v.Int(nil), 10) } func (v literalValue) clone() value { @@ -421,13 +423,13 @@ func (v literalValue) asRawValue(r *runner) rawValue { switch value := v.value.(type) { case uint64: buf = make([]byte, 8) - binary.LittleEndian.PutUint64(buf, value) + r.byteOrder.PutUint64(buf, value) case uint32: buf = make([]byte, 4) - binary.LittleEndian.PutUint32(buf, uint32(value)) + r.byteOrder.PutUint32(buf, uint32(value)) case uint16: buf = make([]byte, 2) - binary.LittleEndian.PutUint16(buf, uint16(value)) + r.byteOrder.PutUint16(buf, uint16(value)) case uint8: buf = []byte{uint8(value)} default: @@ -440,7 +442,7 @@ func (v literalValue) asRawValue(r *runner) rawValue { return raw } -func (v literalValue) Uint() uint64 { +func (v literalValue) Uint(r *runner) uint64 { switch value := v.value.(type) { case uint64: return value @@ -455,7 +457,7 @@ func (v literalValue) Uint() uint64 { } } -func (v literalValue) Int() int64 { +func (v literalValue) Int(r *runner) int64 { switch value := v.value.(type) { case uint64: return int64(value) @@ -553,11 +555,11 @@ func (v pointerValue) asRawValue(r *runner) rawValue { return rv } -func (v pointerValue) Uint() uint64 { +func (v pointerValue) Uint(r *runner) uint64 { panic("cannot convert pointer to integer") } -func (v pointerValue) Int() int64 { +func (v pointerValue) Int(r *runner) int64 { panic("cannot convert pointer to integer") } @@ -592,6 +594,12 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val // runtime.alloc. // First allocate a new global for this object. obj := mem.get(v.index()) + alignment := obj.align + if alignment == 0 { + // Unknown alignment, perhaps from a direct call to runtime.alloc in + // the runtime. Use a conservative default instead. + alignment = mem.r.maxAlign + } if obj.llvmType.IsNil() && obj.llvmLayoutType.IsNil() { // Create an initializer without knowing the global type. // This is probably the result of a runtime.alloc call. @@ -602,7 +610,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val globalType := initializer.Type() llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName) llvmValue.SetInitializer(initializer) - llvmValue.SetAlignment(mem.r.maxAlign) + llvmValue.SetAlignment(alignment) obj.llvmGlobal = llvmValue mem.put(v.index(), obj) } else { @@ -641,11 +649,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val return llvm.Value{}, errors.New("interp: allocated value does not match allocated type") } llvmValue.SetInitializer(initializer) - if obj.llvmType.IsNil() { - // The exact type isn't known (only the layout), so use the - // alignment that would normally be expected from runtime.alloc. - llvmValue.SetAlignment(mem.r.maxAlign) - } + llvmValue.SetAlignment(alignment) } // It should be included in r.globals because otherwise markExternal @@ -702,7 +706,12 @@ func (v rawValue) String() string { } // Format as number if none of the buf is a pointer. if !v.hasPointer() { - return strconv.FormatInt(v.Int(), 10) + // Construct a fake runner, which is little endian. + // We only use String() for debugging, so this is is good enough + // (the printed value will just be slightly wrong when debugging the + // interp package with GOOS=mips for example). + r := &runner{byteOrder: binary.LittleEndian} + return strconv.FormatInt(v.Int(r), 10) } } return "<[…" + strconv.Itoa(len(v.buf)) + "]>" @@ -738,33 +747,33 @@ func (v rawValue) bytes() []byte { return buf } -func (v rawValue) Uint() uint64 { +func (v rawValue) Uint(r *runner) uint64 { buf := v.bytes() switch len(v.buf) { case 1: return uint64(buf[0]) case 2: - return uint64(binary.LittleEndian.Uint16(buf)) + return uint64(r.byteOrder.Uint16(buf)) case 4: - return uint64(binary.LittleEndian.Uint32(buf)) + return uint64(r.byteOrder.Uint32(buf)) case 8: - return binary.LittleEndian.Uint64(buf) + return r.byteOrder.Uint64(buf) default: panic("unknown integer size") } } -func (v rawValue) Int() int64 { +func (v rawValue) Int(r *runner) int64 { switch len(v.buf) { case 1: - return int64(int8(v.Uint())) + return int64(int8(v.Uint(r))) case 2: - return int64(int16(v.Uint())) + return int64(int16(v.Uint(r))) case 4: - return int64(int32(v.Uint())) + return int64(int32(v.Uint(r))) case 8: - return int64(int64(v.Uint())) + return int64(int64(v.Uint(r))) default: panic("unknown integer size") } @@ -815,19 +824,6 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) { if err != nil { return llvm.Value{}, err } - if !field.IsAGlobalVariable().IsNil() { - elementType := field.GlobalValueType() - if elementType.TypeKind() == llvm.StructTypeKind { - // There are some special pointer types that should be used - // as a ptrtoint, so that they can be used in certain - // optimizations. - name := elementType.StructName() - if name == "runtime.funcValueWithSignature" { - uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8) - field = llvm.ConstPtrToInt(field, uintptrType) - } - } - } structFields = append(structFields, field) i += mem.r.pointerSize continue @@ -878,11 +874,11 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, var n uint64 switch llvmType.IntTypeWidth() { case 64: - n = rawValue{v.buf[:8]}.Uint() + n = rawValue{v.buf[:8]}.Uint(mem.r) case 32: - n = rawValue{v.buf[:4]}.Uint() + n = rawValue{v.buf[:4]}.Uint(mem.r) case 16: - n = rawValue{v.buf[:2]}.Uint() + n = rawValue{v.buf[:2]}.Uint(mem.r) case 8: n = uint64(v.buf[0]) case 1: @@ -951,7 +947,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, } // This is either a null pointer or a raw pointer for memory-mapped I/O // (such as 0xe000ed00). - ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint() + ptr := rawValue{v.buf[:mem.r.pointerSize]}.Uint(mem.r) if ptr == 0 { // Null pointer. return llvm.ConstNull(llvmType), nil @@ -969,11 +965,11 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, } return llvm.ConstIntToPtr(ptrValue, llvmType), nil case llvm.DoubleTypeKind: - b := rawValue{v.buf[:8]}.Uint() + b := rawValue{v.buf[:8]}.Uint(mem.r) f := math.Float64frombits(b) return llvm.ConstFloat(llvmType, f), nil case llvm.FloatTypeKind: - b := uint32(rawValue{v.buf[:4]}.Uint()) + b := uint32(rawValue{v.buf[:4]}.Uint(mem.r)) f := math.Float32frombits(b) return llvm.ConstFloat(llvmType, float64(f)), nil default: @@ -1037,6 +1033,8 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) { v.buf[i] = ptrValue.pointer } case llvm.ICmp: + // Note: constant icmp isn't supported anymore in LLVM 19. + // Once we drop support for LLVM 18, this can be removed. size := r.targetData.TypeAllocSize(llvmValue.Operand(0).Type()) lhs := newRawValue(uint32(size)) rhs := newRawValue(uint32(size)) @@ -1065,19 +1063,19 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) { switch llvmValue.Type().IntTypeWidth() { case 64: var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], n) + r.byteOrder.PutUint64(buf[:], n) for i, b := range buf { v.buf[i] = uint64(b) } case 32: var buf [4]byte - binary.LittleEndian.PutUint32(buf[:], uint32(n)) + r.byteOrder.PutUint32(buf[:], uint32(n)) for i, b := range buf { v.buf[i] = uint64(b) } case 16: var buf [2]byte - binary.LittleEndian.PutUint16(buf[:], uint16(n)) + r.byteOrder.PutUint16(buf[:], uint16(n)) for i, b := range buf { v.buf[i] = uint64(b) } @@ -1109,14 +1107,14 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) { case llvm.DoubleTypeKind: f, _ := llvmValue.DoubleValue() var buf [8]byte - binary.LittleEndian.PutUint64(buf[:], math.Float64bits(f)) + r.byteOrder.PutUint64(buf[:], math.Float64bits(f)) for i, b := range buf { v.buf[i] = uint64(b) } case llvm.FloatTypeKind: f, _ := llvmValue.DoubleValue() var buf [4]byte - binary.LittleEndian.PutUint32(buf[:], math.Float32bits(float32(f))) + r.byteOrder.PutUint32(buf[:], math.Float32bits(float32(f))) for i, b := range buf { v.buf[i] = uint64(b) } @@ -1166,11 +1164,11 @@ func (v localValue) asRawValue(r *runner) rawValue { panic("interp: localValue.asRawValue") } -func (v localValue) Uint() uint64 { +func (v localValue) Uint(r *runner) uint64 { panic("interp: localValue.Uint") } -func (v localValue) Int() int64 { +func (v localValue) Int(r *runner) int64 { panic("interp: localValue.Int") } @@ -1254,7 +1252,7 @@ func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { ptr, err := layoutValue.asPointer(r) if err == errIntegerAsPointer { // It's an integer, which means it's a small object or unknown. - layout := layoutValue.Uint() + layout := layoutValue.Uint(r) if layout == 0 { // Nil pointer, which means the layout is unknown. return 0, nil @@ -1287,7 +1285,7 @@ func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { // Read the object size in words and the bitmap from the global. buf := r.objects[ptr.index()].buffer.(rawValue) - objectSizeWords := rawValue{buf: buf.buf[:r.pointerSize]}.Uint() + objectSizeWords := rawValue{buf: buf.buf[:r.pointerSize]}.Uint(r) rawByteValues := buf.buf[r.pointerSize:] rawBytes := make([]byte, len(rawByteValues)) for i, v := range rawByteValues { diff --git a/interp/testdata/consteval.ll b/interp/testdata/consteval.ll index 3cbe7fcf73..3845719d55 100644 --- a/interp/testdata/consteval.ll +++ b/interp/testdata/consteval.ll @@ -3,7 +3,6 @@ target triple = "x86_64--linux" @intToPtrResult = global i8 0 @ptrToIntResult = global i8 0 -@icmpResult = global i8 0 @pointerTagResult = global i64 0 @someArray = internal global {i16, i8, i8} zeroinitializer @someArrayPointer = global ptr zeroinitializer @@ -17,7 +16,6 @@ define internal void @main.init() { call void @testIntToPtr() call void @testPtrToInt() call void @testConstGEP() - call void @testICmp() call void @testPointerTag() ret void } @@ -53,19 +51,6 @@ define internal void @testConstGEP() { ret void } -define internal void @testICmp() { - br i1 icmp eq (i64 ptrtoint (ptr @ptrToIntResult to i64), i64 0), label %equal, label %unequal -equal: - ; should not be reached - store i8 1, ptr @icmpResult - ret void -unequal: - ; should be reached - store i8 2, ptr @icmpResult - ret void - ret void -} - define internal void @testPointerTag() { %val = and i64 ptrtoint (ptr getelementptr inbounds (i8, ptr @someArray, i32 2) to i64), 3 store i64 %val, ptr @pointerTagResult diff --git a/interp/testdata/consteval.out.ll b/interp/testdata/consteval.out.ll index fff0e18f96..679f09f625 100644 --- a/interp/testdata/consteval.out.ll +++ b/interp/testdata/consteval.out.ll @@ -3,10 +3,9 @@ target triple = "x86_64--linux" @intToPtrResult = local_unnamed_addr global i8 2 @ptrToIntResult = local_unnamed_addr global i8 2 -@icmpResult = local_unnamed_addr global i8 2 @pointerTagResult = local_unnamed_addr global i64 2 @someArray = internal global { i16, i8, i8 } zeroinitializer -@someArrayPointer = local_unnamed_addr global ptr getelementptr inbounds ({ i16, i8, i8 }, ptr @someArray, i64 0, i32 1) +@someArrayPointer = local_unnamed_addr global ptr getelementptr inbounds (i8, ptr @someArray, i64 2) define void @runtime.initAll() local_unnamed_addr { ret void diff --git a/lib/bdwgc b/lib/bdwgc new file mode 160000 index 0000000000..d1ff06cc50 --- /dev/null +++ b/lib/bdwgc @@ -0,0 +1 @@ +Subproject commit d1ff06cc503a74dca0150d5e988f2c93158b0cdf diff --git a/lib/cmsis-svd b/lib/cmsis-svd index 40327a4d2d..05a9562ec5 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit 40327a4d2dff0992682be2872aaa6e096f35d2f4 +Subproject commit 05a9562ec59b87945a8d7177a4b08b7aa2f2fd58 diff --git a/lib/macos-minimal-sdk b/lib/macos-minimal-sdk index ebb736fda2..9b69407cb5 160000 --- a/lib/macos-minimal-sdk +++ b/lib/macos-minimal-sdk @@ -1 +1 @@ -Subproject commit ebb736fda2bec7cea38dcda807518b835a539525 +Subproject commit 9b69407cb59f8ccbb674bb77b358df7befcbb42b diff --git a/lib/renesas-svd b/lib/renesas-svd deleted file mode 160000 index 03d7688085..0000000000 --- a/lib/renesas-svd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 03d76880854b9042f5d043f4355cdf8eef522fa5 diff --git a/lib/wasi-cli b/lib/wasi-cli new file mode 160000 index 0000000000..6ae8261709 --- /dev/null +++ b/lib/wasi-cli @@ -0,0 +1 @@ +Subproject commit 6ae82617096e83e6606047736e84ac397b788631 diff --git a/loader/goroot.go b/loader/goroot.go index ea2f705dc9..00a7124d80 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -214,11 +214,11 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) return merges, nil } -// needsSyscallPackage returns whether the syscall package should be overriden +// needsSyscallPackage returns whether the syscall package should be overridden // with the TinyGo version. This is the case on some targets. func needsSyscallPackage(buildTags []string) bool { for _, tag := range buildTags { - if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "tinygo.wasm" { + if tag == "baremetal" || tag == "nintendoswitch" || tag == "tinygo.wasm" { return true } } @@ -229,26 +229,36 @@ func needsSyscallPackage(buildTags []string) bool { // means use the TinyGo version. func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { paths := map[string]bool{ - "": true, - "crypto/": true, - "crypto/rand/": false, - "crypto/tls/": false, - "device/": false, - "examples/": false, - "internal/": true, - "internal/bytealg/": false, - "internal/fuzz/": false, - "internal/reflectlite/": false, - "internal/task/": false, - "machine/": false, - "net/": true, - "net/http/": false, - "os/": true, - "os/user/": false, - "reflect/": false, - "runtime/": false, - "sync/": true, - "testing/": true, + "": true, + "crypto/": true, + "crypto/rand/": false, + "crypto/tls/": false, + "crypto/x509/": true, + "crypto/x509/internal/": true, + "crypto/x509/internal/macos/": false, + "device/": false, + "examples/": false, + "internal/": true, + "internal/abi/": false, + "internal/binary/": false, + "internal/bytealg/": false, + "internal/cm/": false, + "internal/futex/": false, + "internal/fuzz/": false, + "internal/reflectlite/": false, + "internal/gclayout": false, + "internal/task/": false, + "internal/wasi/": false, + "machine/": false, + "net/": true, + "net/http/": false, + "os/": true, + "reflect/": false, + "runtime/": false, + "sync/": true, + "testing/": true, + "tinygo/": false, + "unique/": false, } if goMinor >= 19 { @@ -259,6 +269,8 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool { if needsSyscallPackage { paths["syscall/"] = true // include syscall/js + paths["internal/syscall/"] = true + paths["internal/syscall/unix/"] = false } return paths } diff --git a/loader/loader.go b/loader/loader.go index e66812a8e4..e935a9de3a 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -128,7 +128,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) } // List the dependencies of this package, in raw JSON format. - extraArgs := []string{"-json", "-deps"} + extraArgs := []string{"-json", "-deps", "-e"} if config.TestConfig.CompileTestBinary { extraArgs = append(extraArgs, "-test") } @@ -149,6 +149,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) // Parse the returned json from `go list`. decoder := json.NewDecoder(buf) + var pkgErrors []error for { pkg := &Package{ program: p, @@ -179,7 +180,7 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) if len(fields) >= 2 { // There is some file/line/column information. if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil { - // Format: filename.go:line:colum + // Format: filename.go:line:column pos.Filename = strings.Join(fields[:len(fields)-2], ":") pos.Line = n pos.Column, _ = strconv.Atoi(fields[len(fields)-1]) @@ -188,6 +189,12 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) pos.Filename = strings.Join(fields[:len(fields)-1], ":") pos.Line, _ = strconv.Atoi(fields[len(fields)-1]) } + if abs, err := filepath.Abs(pos.Filename); err == nil { + // Make the path absolute, so that error messages will be + // prettier (it will be turned back into a relative path + // when printing the error). + pos.Filename = abs + } pos.Filename = p.getOriginalPath(pos.Filename) } err := scanner.Error{ @@ -195,10 +202,11 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) Msg: pkg.Error.Err, } if len(pkg.Error.ImportStack) != 0 { - return nil, Error{ + pkgErrors = append(pkgErrors, Error{ ImportStack: pkg.Error.ImportStack, Err: err, - } + }) + continue } return nil, err } @@ -241,6 +249,13 @@ func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) p.Packages[pkg.ImportPath] = pkg } + if len(pkgErrors) != 0 { + // TODO: use errors.Join in Go 1.20. + return nil, Errors{ + Errs: pkgErrors, + } + } + if config.TestConfig.CompileTestBinary && !strings.HasSuffix(p.sorted[len(p.sorted)-1].ImportPath, ".test") { // Trying to compile a test binary but there are no test files in this // package. @@ -403,8 +418,12 @@ func (p *Package) Check() error { packageName := p.ImportPath if p == p.program.MainPkg() { if p.Name != "main" { - // Sanity check. Should not ever trigger. - panic("expected main package to have name 'main'") + return Errors{p, []error{ + scanner.Error{ + Pos: p.program.fset.Position(p.Files[0].Name.Pos()), + Msg: fmt.Sprintf("expected main package to have name \"main\", not %#v", p.Name), + }, + }} } packageName = "main" } @@ -413,7 +432,15 @@ func (p *Package) Check() error { if err, ok := err.(Errors); ok { return err } - return Errors{p, typeErrors} + if len(typeErrors) != 0 { + // Got type errors, so return them. + return Errors{p, typeErrors} + } + // This can happen in some weird cases. + // The only case I know is when compiling a Go 1.23 program, with a + // TinyGo version that supports Go 1.23 but is compiled using Go 1.22. + // So this should be pretty rare. + return Errors{p, []error{err}} } p.Pkg = typesPkg @@ -430,7 +457,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) { var files []*ast.File var fileErrs []error - // Parse all files (incuding CgoFiles). + // Parse all files (including CgoFiles). parseFile := func(file string) { if !filepath.IsAbs(file) { file = filepath.Join(p.Dir, file) @@ -458,7 +485,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) { var initialCFlags []string initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...) initialCFlags = append(initialCFlags, "-I"+p.Dir) - generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags) + generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS()) p.CFlags = append(initialCFlags, cflags...) p.CGoHeaders = headerCode for path, hash := range accessedFiles { diff --git a/main.go b/main.go index 8c0117fa19..d4562fc4f6 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,6 @@ import ( "errors" "flag" "fmt" - "go/scanner" - "go/types" "io" "os" "os/exec" @@ -30,8 +28,8 @@ import ( "github.com/mattn/go-colorable" "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/diagnostics" "github.com/tinygo-org/tinygo/goenv" - "github.com/tinygo-org/tinygo/interp" "github.com/tinygo-org/tinygo/loader" "golang.org/x/tools/go/buildutil" "tinygo.org/x/go-llvm" @@ -285,39 +283,6 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options // Tests are always run in the package directory. cmd.Dir = result.MainDir - // wasmtime is the default emulator used for `-target=wasi`. wasmtime - // is a WebAssembly runtime CLI with WASI enabled by default. However, - // only stdio are allowed by default. For example, while STDOUT routes - // to the host, other files don't. It also does not inherit environment - // variables from the host. Some tests read testdata files, often from - // outside the package directory. Other tests require temporary - // writeable directories. We allow this by adding wasmtime flags below. - if config.EmulatorName() == "wasmtime" { - // At this point, The current working directory is at the package - // directory. Ex. $GOROOT/src/compress/flate for compress/flate. - // buildAndRun has already added arguments for wasmtime, that allow - // read-access to files such as "testdata/huffman-zero.in". - // - // Ex. main(.wasm) --dir=. -- -test.v - - // Below adds additional wasmtime flags in case a test reads files - // outside its directory, like "../testdata/e.txt". This allows any - // relative directory up to the module root, even if the test never - // reads any files. - // - // Ex. run --dir=.. --dir=../.. --dir=../../.. - dirs := dirsToModuleRoot(result.MainDir, result.ModuleRoot) - args := []string{"run"} - for _, d := range dirs[1:] { - args = append(args, "--dir="+d) - } - - // The below re-organizes the arguments so that the current - // directory is added last. - args = append(args, cmd.Args[1:]...) - cmd.Args = append(cmd.Args[:1:1], args...) - } - // Run the test. start := time.Now() err = cmd.Run() @@ -338,6 +303,11 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options } return err }) + + if testConfig.CompileOnly { + return true, nil + } + importPath := strings.TrimSuffix(result.ImportPath, ".test") var w io.Writer = stdout @@ -348,7 +318,7 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options fmt.Fprintf(w, "? \t%s\t[no test files]\n", err.ImportPath) // Pretend the test passed - it at least didn't fail. return true, nil - } else if passed && !testConfig.CompileOnly { + } else if passed { fmt.Fprintf(w, "ok \t%s\t%.3fs\n", importPath, duration.Seconds()) } else { fmt.Fprintf(w, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) @@ -356,8 +326,8 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options return passed, err } -func dirsToModuleRoot(maindir, modroot string) []string { - var dirs = []string{"."} +func dirsToModuleRootRel(maindir, modroot string) []string { + var dirs []string last := ".." // strip off path elements until we hit the module root // adding `..`, `../..`, `../../..` until we're done @@ -366,6 +336,20 @@ func dirsToModuleRoot(maindir, modroot string) []string { last = filepath.Join(last, "..") maindir = filepath.Dir(maindir) } + dirs = append(dirs, ".") + return dirs +} + +func dirsToModuleRootAbs(maindir, modroot string) []string { + var dirs = []string{maindir} + last := filepath.Join(maindir, "..") + // strip off path elements until we hit the module root + // adding `..`, `../..`, `../../..` until we're done + for maindir != modroot { + dirs = append(dirs, last) + last = filepath.Join(last, "..") + maindir = filepath.Dir(maindir) + } return dirs } @@ -782,7 +766,7 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { // buildAndRun builds and runs the given program, writing output to stdout and // errors to os.Stderr. It takes care of emulators (qemu, wasmtime, etc) and -// passes command line arguments and evironment variables in a way appropriate +// passes command line arguments and environment variables in a way appropriate // for the given emulator. func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) { // Determine whether we're on a system that supports environment variables @@ -797,7 +781,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c needsEnvInVars = true } } - var args, emuArgs, env []string + var args, env []string var extraCmdEnv []string if needsEnvInVars { runtimeGlobals := make(map[string]string) @@ -817,24 +801,6 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c "runtime": runtimeGlobals, } } - } else if config.EmulatorName() == "wasmtime" { - // Wasmtime needs some special flags to pass environment variables - // and allow reading from the current directory. - emuArgs = append(emuArgs, "--dir=.") - for _, v := range environmentVars { - emuArgs = append(emuArgs, "--env", v) - } - if len(cmdArgs) != 0 { - // Use of '--' argument no longer necessary as of Wasmtime v14: - // https://github.com/bytecodealliance/wasmtime/pull/6946 - // args = append(args, "--") - args = append(args, cmdArgs...) - } - - // Set this for nicer backtraces during tests, but don't override the user. - if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok { - extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1") - } } else { // Pass environment variables and command line parameters as usual. // This also works on qemu-aarch64 etc. @@ -876,9 +842,62 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c if err != nil { return result, err } - name = emulator[0] - emuArgs = append(emuArgs, emulator[1:]...) - args = append(emuArgs, args...) + + name, emulator = emulator[0], emulator[1:] + + // wasmtime is a WebAssembly runtime CLI with WASI enabled by default. + // By default, only stdio is allowed. For example, while STDOUT routes + // to the host, other files don't. It also does not inherit environment + // variables from the host. Some tests read testdata files, often from + // outside the package directory. Other tests require temporary + // writeable directories. We allow this by adding wasmtime flags below. + if name == "wasmtime" { + var emuArgs []string + + // Extract the wasmtime subcommand (e.g. "run" or "serve") + if len(emulator) > 1 { + emuArgs = append(emuArgs, emulator[0]) + emulator = emulator[1:] + } + + wd, _ := os.Getwd() + + // Below adds additional wasmtime flags in case a test reads files + // outside its directory, like "../testdata/e.txt". This allows any + // relative directory up to the module root, even if the test never + // reads any files. + if config.TestConfig.CompileTestBinary { + // Set working directory to package dir + wd = result.MainDir + + // Add relative dirs (../, ../..) up to module root (for wasip1) + dirs := dirsToModuleRootRel(result.MainDir, result.ModuleRoot) + + // Add absolute dirs up to module root (for wasip2) + dirs = append(dirs, dirsToModuleRootAbs(result.MainDir, result.ModuleRoot)...) + + for _, d := range dirs { + emuArgs = append(emuArgs, "--dir="+d) + } + } else { + emuArgs = append(emuArgs, "--dir=.") + } + + emuArgs = append(emuArgs, "--dir="+wd) + emuArgs = append(emuArgs, "--env=PWD="+wd) + for _, v := range environmentVars { + emuArgs = append(emuArgs, "--env", v) + } + + // Set this for nicer backtraces during tests, but don't override the user. + if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok { + extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1") + } + + emulator = append(emuArgs, emulator...) + } + + args = append(emulator, args...) } var cmd *exec.Cmd if ctx != nil { @@ -891,7 +910,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // Configure stdout/stderr. The stdout may go to a buffer, not a real // stdout. - cmd.Stdout = stdout + cmd.Stdout = newOutputWriter(stdout, result.Executable) cmd.Stderr = os.Stderr if config.EmulatorName() == "simavr" { cmd.Stdout = nil // don't print initial load commands @@ -908,12 +927,12 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // Run binary. if config.Options.PrintCommands != nil { - config.Options.PrintCommands(cmd.Path, cmd.Args...) + config.Options.PrintCommands(cmd.Path, cmd.Args[1:]...) } err = run(cmd, result) if err != nil { if ctx != nil && ctx.Err() == context.DeadlineExceeded { - stdout.Write([]byte(fmt.Sprintf("--- timeout of %s exceeded, terminating...\n", timeout))) + fmt.Fprintf(stdout, "--- timeout of %s exceeded, terminating...\n", timeout) err = ctx.Err() } return result, &commandError{"failed to run compiled binary", result.Binary, err} @@ -1030,20 +1049,21 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { continue } fstype := fields[2] - if fstype != "vfat" { + // chromeos bind mounts use 9p + if !(fstype == "vfat" || fstype == "9p") { continue } + fspath := strings.ReplaceAll(fields[1], "\\040", " ") points = append(points, mountPoint{ - name: filepath.Base(fields[1]), - path: fields[1], + name: filepath.Base(fspath), + path: fspath, }) } return points, nil case "windows": // Obtain a list of all currently mounted volumes. - cmd := executeCommand(options, "wmic", - "PATH", "Win32_LogicalDisk", - "get", "DeviceID,VolumeName,FileSystem,DriveType") + cmd := executeCommand(options, "powershell", "-c", + "Get-CimInstance -ClassName Win32_LogicalDisk | Select-Object DeviceID, DriveType, FileSystem, VolumeName") var out bytes.Buffer cmd.Stdout = &out err := cmd.Run() @@ -1211,115 +1231,178 @@ func getBMPPorts() (gdbPort, uartPort string, err error) { } } -func usage(command string) { - switch command { - default: - fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") - fmt.Fprintln(os.Stderr, "version:", goenv.Version()) - fmt.Fprintf(os.Stderr, "usage: %s [arguments]\n", os.Args[0]) - fmt.Fprintln(os.Stderr, "\ncommands:") - fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") - fmt.Fprintln(os.Stderr, " run: compile and run immediately") - fmt.Fprintln(os.Stderr, " test: test packages") - fmt.Fprintln(os.Stderr, " flash: compile and flash to the device") - fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB") - fmt.Fprintln(os.Stderr, " lldb: run/flash and immediately enter LLDB") - fmt.Fprintln(os.Stderr, " monitor: open communication port") - fmt.Fprintln(os.Stderr, " ports: list available serial ports") - fmt.Fprintln(os.Stderr, " env: list environment variables used during build") - fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root") - fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")") - fmt.Fprintln(os.Stderr, " targets: list targets") - fmt.Fprintln(os.Stderr, " info: show info for specified target") - fmt.Fprintln(os.Stderr, " version: show version") - fmt.Fprintln(os.Stderr, " help: print this help text") +const ( + usageBuild = `Build compiles the packages named by the import paths, along with their +dependencies, but it does not install the results. The output binary is +specified using the -o parameter. The generated file type depends on the +extension: + + .o: + Create a relocatable object file. You can use this option if you + don't want to use the TinyGo build system or want to do other custom + things. + + .ll: + Create textual LLVM IR, after optimization. This is mainly useful + for debugging. + + .bc: + Create LLVM bitcode, after optimization. This may be useful for + debugging or for linking into other programs using LTO. + + .hex: + Create an Intel HEX file to flash it to a microcontroller. + + .bin: + Similar, but create a binary file. + + .wasm: + Compile and link a WebAssembly file. + +(all other) Compile and link the program into a regular executable. For +microcontrollers, it is common to use the .elf file extension to indicate a +linked ELF file is generated. For Linux, it is common to build binaries with no +extension at all.` + + usageRun = `Run the program, either directly on the host or in an emulated environment +(depending on -target).` + + usageFlash = `Flash the program to a microcontroller. Some common flags are described below. + + -target={name}: + Specifies the type of microcontroller that is used. The name of the + microcontroller is given on the individual pages for each board type + listed under Microcontrollers + (https://tinygo.org/docs/reference/microcontrollers/). + Examples: "arduino-nano", "d1mini", "xiao". + + -monitor: + Start the serial monitor (see below) immediately after + flashing. However, some microcontrollers need a split second + or two to configure the serial port after flashing, and + using the "-monitor" flag can fail because the serial + monitor starts too quickly. In that case, use the "tinygo + monitor" command explicitly.` + + usageMonitor = `Start the serial monitor on the serial port that is connected to the +microcontroller. If there is only a single board attached to the host computer, +the default values for various options should be sufficient. In other +situations, particularly if you have multiple microcontrollers attached, some +parameters may need to be overridden using the following flags: + + -port={port}: + If there are multiple microcontroller attached, an error + message will display a list of potential serial ports. The + appropriate port can be specified by this flag. On Linux, + the port will be something like /dev/ttyUSB0 or /dev/ttyACM1. + On MacOS, the port will look like /dev/cu.usbserial-1420. On + Windows, the port will be something like COM1 or COM31. + + -baudrate={rate}: + The default baud rate is 115200. Boards using the AVR + processor (e.g. Arduino Nano, Arduino Mega 2560) use 9600 + instead. + + -target={name}: + If you have more than one microcontrollers attached, you can + sometimes just specify the target name and let tinygo + monitor figure out the port. Sometimes, this does not work + and you have to explicitly use the -port flag. + +The serial monitor intercepts several control characters for its own use instead of sending them +to the microcontroller: + + Control-C: terminates the tinygo monitor + Control-Z: suspends the tinygo monitor and drops back into shell + Control-\: terminates the tinygo monitor with a stack trace + Control-S: flow control, suspends output to the console + Control-Q: flow control, resumes output to the console + Control-@: thrown away by tinygo monitor + +Note: If you are using os.Stdin on the microcontroller, you may find that a CR +character on the host computer (also known as Enter, ^M, or \r) is transmitted +to the microcontroller without conversion, so os.Stdin returns a \r character +instead of the expected \n (also known as ^J, NL, or LF) to indicate +end-of-line. You may be able to get around this problem by hitting Control-J in +tinygo monitor to transmit the \n end-of-line character.` + + usageGdb = `Build the program, optionally flash it to a microcontroller if it is a remote +target, and drop into a GDB shell. From there you can set breakpoints, start the +program with "run" or "continue" ("run" for a local program, continue for +on-chip debugging), single-step, show a backtrace, break and resume the program +with Ctrl-C/"continue", etc. You may need to install extra tools (like openocd +and arm-none-eabi-gdb) to be able to do this. Also, you may need a dedicated +debugger to be able to debug certain boards if no debugger is integrated. Some +boards (like the BBC micro:bit and most professional evaluation boards) have an +integrated debugger.` + + usageClean = `Clean the cache directory, normally stored in $HOME/.cache/tinygo. This is not +normally needed.` + + usageHelp = `Print a short summary of the available commands, plus a list of command flags.` + usageVersion = `Print the version of the command and the version of the used $GOROOT.` + usageEnv = `Print a list of environment variables that affect TinyGo (as a shell script). +If one or more variable names are given as arguments, env prints the value of +each on a new line.` + + usageDefault = `TinyGo is a Go compiler for small places. +version: %s +usage: %s [arguments] +commands: + build: compile packages and dependencies + run: compile and run immediately + test: test packages + flash: compile and flash to the device + gdb: run/flash and immediately enter GDB + lldb: run/flash and immediately enter LLDB + monitor: open communication port + ports: list available serial ports + env: list environment variables used during build + list: run go list using the TinyGo root + clean: empty cache directory (%s) + targets: list targets + info: show info for specified target + version: show version + help: print this help text` +) +var ( + commandHelp = map[string]string{ + "build": usageBuild, + "run": usageRun, + "flash": usageFlash, + "monitor": usageMonitor, + "gdb": usageGdb, + "clean": usageClean, + "help": usageHelp, + "version": usageVersion, + "env": usageEnv, + } +) + +func usage(command string) { + val, ok := commandHelp[command] + if !ok { + fmt.Fprintf(os.Stderr, usageDefault, goenv.Version(), os.Args[0], goenv.Get("GOCACHE")) if flag.Parsed() { fmt.Fprintln(os.Stderr, "\nflags:") flag.PrintDefaults() } fmt.Fprintln(os.Stderr, "\nfor more details, see https://tinygo.org/docs/reference/usage/") + } else { + fmt.Fprintln(os.Stderr, val) } -} - -// try to make the path relative to the current working directory. If any error -// occurs, this error is ignored and the absolute path is returned instead. -func tryToMakePathRelative(dir string) string { - wd, err := os.Getwd() - if err != nil { - return dir - } - relpath, err := filepath.Rel(wd, dir) - if err != nil { - return dir - } - return relpath -} -// printCompilerError prints compiler errors using the provided logger function -// (similar to fmt.Println). -// -// There is one exception: interp errors may print to stderr unconditionally due -// to limitations in the LLVM bindings. -func printCompilerError(logln func(...interface{}), err error) { - switch err := err.(type) { - case types.Error: - printCompilerError(logln, scanner.Error{ - Pos: err.Fset.Position(err.Pos), - Msg: err.Msg, - }) - case scanner.Error: - if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { - // This file is not from the standard library (either the GOROOT or - // the TINYGOROOT). Make the path relative, for easier reading. - // Ignore any errors in the process (falling back to the absolute - // path). - err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename) - } - logln(err) - case scanner.ErrorList: - for _, scannerErr := range err { - printCompilerError(logln, *scannerErr) - } - case *interp.Error: - logln("#", err.ImportPath) - logln(err.Error()) - if len(err.Inst) != 0 { - logln(err.Inst) - } - if len(err.Traceback) > 0 { - logln("\ntraceback:") - for _, line := range err.Traceback { - logln(line.Pos.String() + ":") - logln(line.Inst) - } - } - case loader.Errors: - logln("#", err.Pkg.ImportPath) - for _, err := range err.Errs { - printCompilerError(logln, err) - } - case loader.Error: - logln(err.Err.Error()) - logln("package", err.ImportStack[0]) - for _, pkgPath := range err.ImportStack[1:] { - logln("\timports", pkgPath) - } - case *builder.MultiError: - for _, err := range err.Errs { - printCompilerError(logln, err) - } - default: - logln("error:", err) - } } func handleCompilerError(err error) { if err != nil { - printCompilerError(func(args ...interface{}) { - fmt.Fprintln(os.Stderr, args...) - }, err) + wd, getwdErr := os.Getwd() + if getwdErr != nil { + wd = "" + } + diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd) os.Exit(1) } } @@ -1355,19 +1438,20 @@ func (m globalValuesFlag) Set(value string) error { // parseGoLinkFlag parses the -ldflags parameter. Its primary purpose right now // is the -X flag, for setting the value of global string variables. -func parseGoLinkFlag(flagsString string) (map[string]map[string]string, error) { +func parseGoLinkFlag(flagsString string) (map[string]map[string]string, string, error) { set := flag.NewFlagSet("link", flag.ExitOnError) globalVarValues := make(globalValuesFlag) set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.") + extLDFlags := set.String("extldflags", "", "additional flags to pass to external linker") flags, err := shlex.Split(flagsString) if err != nil { - return nil, err + return nil, "", err } err = set.Parse(flags) if err != nil { - return nil, err + return nil, "", err } - return map[string]map[string]string(globalVarValues), nil + return map[string]map[string]string(globalVarValues), *extLDFlags, nil } // getListOfPackages returns a standard list of packages for a given list that might @@ -1411,19 +1495,20 @@ func main() { gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, asyncify)") - serial := flag.String("serial", "", "which serial output to use (none, uart, usb)") + serial := flag.String("serial", "", "which serial output to use (none, uart, usb, rtt)") work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") interpTimeout := flag.Duration("interp-timeout", 180*time.Second, "interp optimization pass timeout") var tags buildutil.TagsFlag flag.Var(&tags, "tags", "a space-separated list of extra build tags") target := flag.String("target", "", "chip/board name or JSON target specification file") + buildMode := flag.String("buildmode", "", "build mode to use (default, c-shared, wasi-legacy)") var stackSize uint64 flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error { size, err := bytesize.Parse(s) stackSize = uint64(size) return err }) - printSize := flag.String("size", "", "print sizes (none, short, full)") + printSize := flag.String("size", "", "print sizes (none, short, full, html)") printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines") printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed") printCommands := flag.Bool("x", false, "Print commands") @@ -1460,10 +1545,16 @@ func main() { flag.BoolVar(&flagTest, "test", false, "supply -test flag to go list") } var outpath string - if command == "help" || command == "build" || command == "build-library" || command == "test" { + if command == "help" || command == "build" || command == "test" { flag.StringVar(&outpath, "o", "", "output filename") } + var witPackage, witWorld string + if command == "help" || command == "build" || command == "test" || command == "run" { + flag.StringVar(&witPackage, "wit-package", "", "wit package for wasm component embedding") + flag.StringVar(&witWorld, "wit-world", "", "wit world for wasm component embedding") + } + var testConfig compileopts.TestConfig if command == "help" || command == "test" { flag.BoolVar(&testConfig.CompileOnly, "c", false, "compile the test binary but do not run it") @@ -1480,18 +1571,20 @@ func main() { // Early command processing, before commands are interpreted by the Go flag // library. + handleChdirFlag() switch command { case "clang", "ld.lld", "wasm-ld": err := builder.RunTool(command, os.Args[2:]...) if err != nil { - fmt.Fprintln(os.Stderr, err) + // The tool should have printed an error message already. + // Don't print another error message here. os.Exit(1) } os.Exit(0) } flag.CommandLine.Parse(os.Args[2:]) - globalVarValues, err := parseGoLinkFlag(*ldflags) + globalVarValues, extLDFlags, err := parseGoLinkFlag(*ldflags) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) @@ -1515,7 +1608,9 @@ func main() { GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), + GOMIPS: goenv.Get("GOMIPS"), Target: *target, + BuildMode: *buildMode, StackSize: stackSize, Opt: *opt, GC: *gc, @@ -1543,11 +1638,21 @@ func main() { Monitor: *monitor, BaudRate: *baudrate, Timeout: *timeout, + WITPackage: witPackage, + WITWorld: witWorld, } 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()) @@ -1579,56 +1684,28 @@ func main() { usage(command) os.Exit(1) } - if options.Target == "" && filepath.Ext(outpath) == ".wasm" { - options.Target = "wasm" + if options.Target == "" { + switch { + case options.GOARCH == "wasm": + switch options.GOOS { + case "js": + options.Target = "wasm" + case "wasip1": + options.Target = "wasip1" + case "wasip2": + options.Target = "wasip2" + default: + fmt.Fprintln(os.Stderr, "GOARCH=wasm but GOOS is not set correctly. Please set GOOS to wasm, wasip1, or wasip2.") + os.Exit(1) + } + case filepath.Ext(outpath) == ".wasm": + fmt.Fprintln(os.Stderr, "you appear to want to build a wasm file, but have not specified either a target flag, or the GOARCH/GOOS to use.") + os.Exit(1) + } } err := Build(pkgName, outpath, options) handleCompilerError(err) - case "build-library": - // Note: this command is only meant to be used while making a release! - if outpath == "" { - fmt.Fprintln(os.Stderr, "No output filename supplied (-o).") - usage(command) - os.Exit(1) - } - if *target == "" { - fmt.Fprintln(os.Stderr, "No target (-target).") - } - if flag.NArg() != 1 { - fmt.Fprintf(os.Stderr, "Build-library only accepts exactly one library name as argument, %d given\n", flag.NArg()) - usage(command) - os.Exit(1) - } - var lib *builder.Library - switch name := flag.Arg(0); name { - case "compiler-rt": - lib = &builder.CompilerRT - case "picolibc": - lib = &builder.Picolibc - default: - fmt.Fprintf(os.Stderr, "Unknown library: %s\n", name) - os.Exit(1) - } - tmpdir, err := os.MkdirTemp("", "tinygo*") - if err != nil { - handleCompilerError(err) - } - defer os.RemoveAll(tmpdir) - spec, err := compileopts.LoadTarget(options) - if err != nil { - handleCompilerError(err) - } - config := &compileopts.Config{ - Options: options, - Target: spec, - } - path, err := lib.Load(config, tmpdir) - handleCompilerError(err) - err = copyFile(path, outpath) - if err != nil { - handleCompilerError(err) - } case "flash", "gdb", "lldb": pkgName := filepath.ToSlash(flag.Arg(0)) if command == "flash" { @@ -1688,7 +1765,7 @@ func main() { for i := range bufs { err := bufs[i].flush(os.Stdout, os.Stderr) if err != nil { - // There was an error writing to stdout or stderr, so we probbably cannot print this. + // There was an error writing to stdout or stderr, so we probably cannot print this. select { case fail <- struct{}{}: default: @@ -1713,9 +1790,11 @@ func main() { stderr := (*testStderr)(buf) passed, err := Test(pkgName, stdout, stderr, options, outpath) if err != nil { - printCompilerError(func(args ...interface{}) { - fmt.Fprintln(stderr, args...) - }, err) + wd, getwdErr := os.Getwd() + if getwdErr != nil { + wd = "" + } + diagnostics.CreateDiagnostics(err).WriteTo(os.Stderr, wd) } if !passed { select { @@ -1793,6 +1872,7 @@ func main() { GOOS string `json:"goos"` GOARCH string `json:"goarch"` GOARM string `json:"goarm"` + GOMIPS string `json:"gomips"` BuildTags []string `json:"build_tags"` GC string `json:"garbage_collector"` Scheduler string `json:"scheduler"` @@ -1803,6 +1883,7 @@ func main() { GOOS: config.GOOS(), GOARCH: config.GOARCH(), GOARM: config.GOARM(), + GOMIPS: config.GOMIPS(), BuildTags: config.BuildTags(), GC: config.GC(), Scheduler: config.Scheduler(), @@ -2001,3 +2082,56 @@ type outputEntry struct { stderr bool data []byte } + +// handleChdirFlag handles the -C flag before doing anything else. +// The -C flag must be the first flag on the command line, to make it easy to find +// even with commands that have custom flag parsing. +// handleChdirFlag handles the flag by chdir'ing to the directory +// and then removing that flag from the command line entirely. +// +// We have to handle the -C flag this way for two reasons: +// +// 1. Toolchain selection needs to be in the right directory to look for go.mod and go.work. +// +// 2. A toolchain switch later on reinvokes the new go command with the same arguments. +// The parent toolchain has already done the chdir; the child must not try to do it again. + +func handleChdirFlag() { + used := 2 // b.c. command at os.Args[1] + if used >= len(os.Args) { + return + } + + var dir string + switch a := os.Args[used]; { + default: + return + + case a == "-C", a == "--C": + if used+1 >= len(os.Args) { + return + } + dir = os.Args[used+1] + os.Args = slicesDelete(os.Args, used, used+2) + + case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="): + _, dir, _ = strings.Cut(a, "=") + os.Args = slicesDelete(os.Args, used, used+1) + } + + if err := os.Chdir(dir); err != nil { + fmt.Fprintln(os.Stderr, "cannot chdir:", err) + os.Exit(1) + } +} + +// go1.19 compatibility: lacks slices package +func slicesDelete[S ~[]E, E any](s S, i, j int) S { + _ = s[i:j:len(s)] // bounds check + + if i == j { + return s + } + + return append(s[:i], s[j:]...) +} diff --git a/main_test.go b/main_test.go index b3e86420f4..f193f46799 100644 --- a/main_test.go +++ b/main_test.go @@ -6,9 +6,9 @@ package main import ( "bufio" "bytes" + "context" "errors" "flag" - "fmt" "io" "os" "os/exec" @@ -20,8 +20,14 @@ import ( "testing" "time" + "github.com/aykevl/go-wasm" + "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" ) @@ -34,6 +40,7 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", + "MIPSLinux": "linux/mips/hardfloat", "WASIp1": "wasip1/wasm", } @@ -72,6 +79,7 @@ func TestBuild(t *testing.T) { "oldgo/", "print.go", "reflect.go", + "signal.go", "slice.go", "sort.go", "stdlib.go", @@ -94,6 +102,9 @@ func TestBuild(t *testing.T) { if minor >= 22 { tests = append(tests, "go1.22/") } + if minor >= 23 { + tests = append(tests, "go1.23/") + } if *testTarget != "" { // This makes it possible to run one specific test (instead of all), @@ -171,13 +182,26 @@ func TestBuild(t *testing.T) { }) } } + t.Run("MIPS little-endian", func(t *testing.T) { + // Run a single test for GOARCH=mipsle to see whether it works at + // all. It is already mostly tested because GOARCH=mips and + // GOARCH=mipsle are so similar, but it's good to have an extra test + // to be sure. + t.Parallel() + options := optionsFromOSARCH("linux/mipsle/softfloat", sema) + runTest("cgo/", options, t, nil, nil) + }) t.Run("WebAssembly", func(t *testing.T) { t.Parallel() runPlatTests(optionsFromTarget("wasm", sema), tests, t) }) t.Run("WASI", func(t *testing.T) { t.Parallel() - runPlatTests(optionsFromTarget("wasi", sema), tests, t) + runPlatTests(optionsFromTarget("wasip1", sema), tests, t) + }) + t.Run("WASIp2", func(t *testing.T) { + t.Parallel() + runPlatTests(optionsFromTarget("wasip2", sema), tests, t) }) } } @@ -190,7 +214,11 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { t.Fatal("failed to load target spec:", err) } - isWebAssembly := options.Target == "wasi" || options.Target == "wasm" || (options.Target == "" && options.GOARCH == "wasm") + // FIXME: this should really be: + // isWebAssembly := strings.HasPrefix(spec.Triple, "wasm") + isWASI := strings.HasPrefix(options.Target, "wasi") + isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm")) + isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu" for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { @@ -201,6 +229,19 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { continue } } + if options.GOOS == "linux" && (options.GOARCH == "mips" || options.GOARCH == "mipsle") { + if name == "atomic.go" || name == "timers.go" { + // 64-bit atomic operations aren't currently supported on MIPS. + continue + } + } + if options.GOOS == "linux" && options.GOARCH == "mips" { + if name == "cgo/" { + // CGo isn't supported yet on big-endian systems (needs updates + // to bitfield access methods). + continue + } + } if options.Target == "simavr" { // Not all tests are currently supported on AVR. // Skip the ones that aren't. @@ -227,9 +268,29 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { // some compiler changes). continue + case "timers.go": + // Crashes starting with Go 1.23. + // Bug: https://github.com/llvm/llvm-project/issues/104032 + continue + default: } } + if options.Target == "wasip2" { + switch name { + case "cgo/": + // waisp2 use our own libc; cgo tests fail + continue + } + } + if isWebAssembly || isBaremetal || options.GOOS == "windows" { + switch name { + case "signal.go": + // Signals only work on POSIX-like systems. + continue + } + } + name := name // redefine to avoid race condition t.Run(name, func(t *testing.T) { t.Parallel() @@ -250,13 +311,13 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("alias.go", options, t, nil, nil) }) } - if options.Target == "" || options.Target == "wasi" { + if options.Target == "" || isWASI { t.Run("filesystem.go", func(t *testing.T) { t.Parallel() runTest("filesystem.go", options, t, nil, nil) }) } - if options.Target == "" || options.Target == "wasi" || options.Target == "wasm" { + if options.Target == "" || options.Target == "wasm" || isWASI { t.Run("rand.go", func(t *testing.T) { t.Parallel() runTest("rand.go", options, t, nil, nil) @@ -292,11 +353,16 @@ func emuCheck(t *testing.T, options compileopts.Options) { } func optionsFromTarget(target string, sema chan struct{}) compileopts.Options { + separators := strings.Count(target, "/") + if (separators == 1 || separators == 2) && !strings.HasSuffix(target, ".json") { + return optionsFromOSARCH(target, sema) + } return compileopts.Options{ // GOOS/GOARCH are only used if target == "" GOOS: goenv.Get("GOOS"), GOARCH: goenv.Get("GOARCH"), GOARM: goenv.Get("GOARM"), + GOMIPS: goenv.Get("GOMIPS"), Target: target, Semaphore: sema, InterpTimeout: 180 * time.Second, @@ -320,8 +386,11 @@ func optionsFromOSARCH(osarch string, sema chan struct{}) compileopts.Options { VerifyIR: true, Opt: "z", } - if options.GOARCH == "arm" { + switch options.GOARCH { + case "arm": options.GOARM = parts[2] + case "mips", "mipsle": + options.GOMIPS = parts[2] } return options } @@ -336,17 +405,13 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c // of the path. path := TESTDATA + "/" + name // Get the expected output for this test. - txtpath := path[:len(path)-3] + ".txt" + expectedOutputPath := path[:len(path)-3] + ".txt" pkgName := "./" + path if path[len(path)-1] == '/' { - txtpath = path + "out.txt" + expectedOutputPath = path + "out.txt" options.Directory = path pkgName = "." } - expected, err := os.ReadFile(txtpath) - if err != nil { - t.Fatal("could not read expected output file:", err) - } config, err := builder.NewConfig(&options) if err != nil { @@ -359,15 +424,19 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c return cmd.Run() }) if err != nil { - printCompilerError(t.Log, err) + 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 } - // putchar() prints CRLF, convert it to LF. - actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1) - expected = bytes.Replace(expected, []byte{'\r', '\n'}, []byte{'\n'}, -1) // for Windows - + actual := stdout.Bytes() if config.EmulatorName() == "simavr" { // Strip simavr log formatting. actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1) @@ -382,16 +451,12 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c } // Check whether the command ran successfully. - fail := false if err != nil { - t.Log("failed to run:", err) - fail = true - } else if !bytes.Equal(expected, actual) { - t.Logf("output did not match (expected %d bytes, got %d bytes):", len(expected), len(actual)) - fail = true + t.Error("failed to run:", err) } + checkOutput(t, expectedOutputPath, actual) - if fail { + if t.Failed() { r := bufio.NewReader(bytes.NewReader(actual)) for { line, err := r.ReadString('\n') @@ -404,6 +469,398 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c } } +// Test WebAssembly files for certain properties. +func TestWebAssembly(t *testing.T) { + t.Parallel() + type testCase struct { + name string + target string + panicStrategy string + imports []string + } + for _, tc := range []testCase{ + // Test whether there really are no imports when using -panic=trap. This + // tests the bugfix for https://github.com/tinygo-org/tinygo/issues/4161. + {name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}}, + {name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}}, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + tmpdir := t.TempDir() + options := optionsFromTarget(tc.target, sema) + options.PanicStrategy = tc.panicStrategy + config, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + + result, err := builder.Build("testdata/trivialpanic.go", ".wasm", tmpdir, config) + if err != nil { + t.Fatal("failed to build binary:", err) + } + f, err := os.Open(result.Binary) + if err != nil { + t.Fatal("could not open output binary:", err) + } + defer f.Close() + module, err := wasm.Parse(f) + if err != nil { + t.Fatal("could not parse output binary:", err) + } + + // Test the list of imports. + if tc.imports != nil { + var imports []string + for _, section := range module.Sections { + switch section := section.(type) { + case *wasm.SectionImport: + for _, symbol := range section.Entries { + imports = append(imports, symbol.Module+"."+symbol.Field) + } + } + } + if !stringSlicesEqual(imports, tc.imports) { + t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports) + } + } + }) + } +} + +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() + + type testCase struct { + name string + target string + buildMode string + scheduler string + file string + noOutput bool + command bool // call _start (command mode) instead of _initialize + } + + tests := []testCase{ + // "command mode" WASI + { + name: "WASIp1-command", + target: "wasip1", + command: true, + }, + // "reactor mode" WASI (with -buildmode=c-shared) + { + name: "WASIp1-reactor", + target: "wasip1", + buildMode: "c-shared", + }, + // Make sure reactor mode also works without a scheduler. + { + name: "WASIp1-reactor-noscheduler", + target: "wasip1", + buildMode: "c-shared", + scheduler: "none", + file: "wasmexport-noscheduler.go", + }, + // Test -target=wasm-unknown with the default build mode (which is + // c-shared). + { + name: "wasm-unknown-reactor", + target: "wasm-unknown", + file: "wasmexport-noscheduler.go", + noOutput: true, // wasm-unknown cannot produce output + }, + // Test -target=wasm-unknown with -buildmode=default, which makes it run + // in command mode. + { + name: "wasm-unknown-command", + target: "wasm-unknown", + buildMode: "default", + file: "wasmexport-noscheduler.go", + noOutput: true, // wasm-unknown cannot produce output + command: true, + }, + // Test buildmode=wasi-legacy with WASI. + { + name: "WASIp1-legacy", + target: "wasip1", + buildMode: "wasi-legacy", + scheduler: "none", + file: "wasmexport-noscheduler.go", + command: true, + }, + } + + 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(tc.target, sema) + options.BuildMode = tc.buildMode + options.Scheduler = tc.scheduler + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + filename := "wasmexport.go" + if tc.file != "" { + filename = tc.file + } + result, err := builder.Build("testdata/"+filename, ".wasm", tmpdir, buildConfig) + if err != nil { + t.Fatal("failed to build binary:", err) + } + + // Read the wasm binary back into memory. + data, err := os.ReadFile(result.Binary) + if err != nil { + t.Fatal("could not read wasm binary: ", err) + } + + // Set up the wazero runtime. + output := &bytes.Buffer{} + ctx := context.Background() + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) + defer r.Close(ctx) + config := wazero.NewModuleConfig(). + WithStdout(output).WithStderr(output). + WithStartFunctions() + + // Prepare for testing. + var mod api.Module + mustCall := func(results []uint64, err error) []uint64 { + if err != nil { + t.Error("failed to run function:", err) + } + return results + } + checkResult := func(name string, results []uint64, expected []uint64) { + if len(results) != len(expected) { + t.Errorf("%s: expected %v but got %v", name, expected, results) + } + for i, result := range results { + if result != expected[i] { + t.Errorf("%s: expected %v but got %v", name, expected, results) + break + } + } + } + runTests := func() { + // Test an exported function without params or return value. + checkResult("hello()", mustCall(mod.ExportedFunction("hello").Call(ctx)), nil) + + // Test that we can call an exported function more than once. + checkResult("add(3, 5)", mustCall(mod.ExportedFunction("add").Call(ctx, 3, 5)), []uint64{8}) + checkResult("add(7, 9)", mustCall(mod.ExportedFunction("add").Call(ctx, 7, 9)), []uint64{16}) + checkResult("add(6, 1)", mustCall(mod.ExportedFunction("add").Call(ctx, 6, 1)), []uint64{7}) + + // Test that imported functions can call exported functions + // again. + checkResult("reentrantCall(2, 3)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 2, 3)), []uint64{5}) + checkResult("reentrantCall(1, 8)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 1, 8)), []uint64{9}) + } + + // Add wasip1 module. + wasi_snapshot_preview1.MustInstantiate(ctx, r) + + // Add custom "tester" module. + callOutside := func(a, b int32) int32 { + results, err := mod.ExportedFunction("add").Call(ctx, uint64(a), uint64(b)) + if err != nil { + t.Error("could not call exported add function:", err) + } + return int32(results[0]) + } + callTestMain := func() { + runTests() + } + builder := r.NewHostModuleBuilder("tester") + builder.NewFunctionBuilder().WithFunc(callOutside).Export("callOutside") + builder.NewFunctionBuilder().WithFunc(callTestMain).Export("callTestMain") + _, err = builder.Instantiate(ctx) + if err != nil { + t.Fatal(err) + } + + // Parse and instantiate the wasm. + mod, err = r.InstantiateWithConfig(ctx, data, config) + if err != nil { + t.Fatal("could not instantiate wasm module:", err) + } + + // Initialize the module and run the tests. + if tc.command { + // Call _start (the entry point), which calls + // tester.callTestMain, which then runs all the tests. + _, err := mod.ExportedFunction("_start").Call(ctx) + if err != nil { + if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() == 0 { + // Exited with code 0. Nothing to worry about. + } else { + t.Error("failed to run _start:", err) + } + } + } else { + // Run the _initialize call, because this is reactor mode wasm. + mustCall(mod.ExportedFunction("_initialize").Call(ctx)) + runTests() + } + + // Check that the output matches the expected output. + // (Skip this for wasm-unknown because it can't produce output). + if !tc.noOutput { + checkOutput(t, "testdata/wasmexport.txt", output.Bytes()) + } + }) + } +} + +// Test js.FuncOf (for syscall/js). +// This test might be extended in the future to cover more cases in syscall/js. +func TestWasmFuncOf(t *testing.T) { + // Build the wasm binary. + tmpdir := t.TempDir() + options := optionsFromTarget("wasm", sema) + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + result, err := builder.Build("testdata/wasmfunc.go", ".wasm", tmpdir, buildConfig) + if err != nil { + t.Fatal("failed to build binary:", err) + } + + // Test the resulting binary using NodeJS. + output := &bytes.Buffer{} + cmd := exec.Command("node", "testdata/wasmfunc.js", result.Binary, buildConfig.BuildMode()) + cmd.Stdout = output + cmd.Stderr = output + err = cmd.Run() + if err != nil { + t.Error("failed to run node:", err) + } + checkOutput(t, "testdata/wasmfunc.txt", output.Bytes()) +} + +// Test //go:wasmexport in JavaScript (using NodeJS). +func TestWasmExportJS(t *testing.T) { + t.Parallel() + type testCase struct { + name string + buildMode string + } + + tests := []testCase{ + {name: "default"}, + {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) + options.BuildMode = tc.buildMode + buildConfig, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + result, err := builder.Build("testdata/wasmexport-noscheduler.go", ".wasm", tmpdir, buildConfig) + if err != nil { + t.Fatal("failed to build binary:", err) + } + + // Test the resulting binary using NodeJS. + output := &bytes.Buffer{} + cmd := exec.Command("node", "testdata/wasmexport.js", result.Binary, buildConfig.BuildMode()) + cmd.Stdout = output + cmd.Stderr = output + err = cmd.Run() + if err != nil { + t.Error("failed to run node:", err) + } + checkOutput(t, "testdata/wasmexport.txt", output.Bytes()) + }) + } +} + +// 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")) + + if !bytes.Equal(actual, expectedOutput) { + t.Errorf("output did not match (expected %d bytes, got %d bytes):", len(expectedOutput), len(actual)) + t.Error(string(Diff("expected", expectedOutput, "actual", actual))) + } +} + func TestTest(t *testing.T) { t.Parallel() @@ -432,7 +889,7 @@ func TestTest(t *testing.T) { // Node/Wasmtime targ{"WASM", optionsFromTarget("wasm", sema)}, - targ{"WASI", optionsFromTarget("wasi", sema)}, + targ{"WASI", optionsFromTarget("wasip1", sema)}, ) } for _, targ := range targs { @@ -604,7 +1061,8 @@ func TestMain(m *testing.M) { // Invoke a specific tool. err := builder.RunTool(os.Args[1], os.Args[2:]...) if err != nil { - fmt.Fprintln(os.Stderr, err) + // The tool should have printed an error message already. + // Don't print another error message here. os.Exit(1) } os.Exit(0) diff --git a/misspell.csv b/misspell.csv new file mode 100644 index 0000000000..9962ee11fd --- /dev/null +++ b/misspell.csv @@ -0,0 +1,41 @@ +acces,access +acuire,acquire +addess,address +adust,adjust +allcoate,allocate +alloated,allocated +archtecture,architecture +arcive,archive +ardiuno,arduino +beconfigured,be configured +calcluate,calculate +colum,column +configration,configuration +contants,constants +cricital,critical +deffered,deferred +evaulator,evaluator +evironment,environment +freqency,frequency +frquency,frequency +implmented,implemented +interrput,interrupt +interrut,interrupt +interupt,interrupt +measuing,measuring +numer of,number of +orignal,original +overrided,overridden +poiners,pointers +poitner,pointer +probbably,probably +recogized,recognized +refection,reflection +requries,requires +satisifying,satisfying +simulataneously,simultaneously +suggets,suggests +transmition,transmission +undefied,undefined +unecessary,unnecessary +unsiged,unsigned diff --git a/monitor.go b/monitor.go index 7b9c896434..46f3de927c 100644 --- a/monitor.go +++ b/monitor.go @@ -197,31 +197,14 @@ func Monitor(executable, port string, config *compileopts.Config) error { go func() { buf := make([]byte, 100*1024) - var line []byte + writer := newOutputWriter(os.Stdout, executable) for { n, err := serialConn.Read(buf) if err != nil { errCh <- fmt.Errorf("read error: %w", err) return } - start := 0 - for i, c := range buf[:n] { - if c == '\n' { - os.Stdout.Write(buf[start : i+1]) - start = i + 1 - address := extractPanicAddress(line) - if address != 0 { - loc, err := addressToLine(executable, address) - if err == nil && loc.IsValid() { - fmt.Printf("[tinygo: panic at %s]\n", loc.String()) - } - } - line = line[:0] - } else { - line = append(line, c) - } - } - os.Stdout.Write(buf[start:n]) + writer.Write(buf[:n]) } }() @@ -400,3 +383,42 @@ func readDWARF(executable string) (*dwarf.Data, error) { return nil, errors.New("unknown binary format") } } + +type outputWriter struct { + out io.Writer + executable string + line []byte +} + +// newOutputWriter returns an io.Writer that will intercept panic addresses and +// will try to insert a source location in the output if the source location can +// be found in the executable. +func newOutputWriter(out io.Writer, executable string) *outputWriter { + return &outputWriter{ + out: out, + executable: executable, + } +} + +func (w *outputWriter) Write(p []byte) (n int, err error) { + start := 0 + for i, c := range p { + if c == '\n' { + w.out.Write(p[start : i+1]) + start = i + 1 + address := extractPanicAddress(w.line) + if address != 0 { + loc, err := addressToLine(w.executable, address) + if err == nil && loc.Filename != "" { + fmt.Printf("[tinygo: panic at %s]\n", loc.String()) + } + } + w.line = w.line[:0] + } else { + w.line = append(w.line, c) + } + } + w.out.Write(p[start:]) + n = len(p) + return +} diff --git a/revive.toml b/revive.toml new file mode 100644 index 0000000000..37778be501 --- /dev/null +++ b/revive.toml @@ -0,0 +1,35 @@ +ignoreGeneratedHeader = false +severity = "warning" +confidence = 0.8 +errorCode = 0 +warningCode = 0 + +# Enable these as we fix them +[rule.blank-imports] + Exclude=["src/os/file_other.go"] +[rule.context-as-argument] +[rule.context-keys-type] +[rule.dot-imports] + Exclude=["**/*_test.go"] +[rule.error-return] +[rule.error-strings] +[rule.error-naming] +[rule.exported] + Exclude=["src/reflect/*.go"] +[rule.increment-decrement] +[rule.var-naming] + Exclude=["src/os/*.go"] +[rule.var-declaration] +#[rule.package-comments] +[rule.range] +[rule.receiver-naming] +[rule.time-naming] +[rule.unexported-return] +#[rule.indent-error-flow] +[rule.errorf] +#[rule.empty-block] +[rule.superfluous-else] +#[rule.unused-parameter] +[rule.unreachable-code] + Exclude=["src/reflect/visiblefields_test.go", "src/reflect/all_test.go"] +#[rule.redefines-builtin-id] diff --git a/src/crypto/rand/rand_arc4random.go b/src/crypto/rand/rand_arc4random.go index 1b1796b4d6..ada1a96192 100644 --- a/src/crypto/rand/rand_arc4random.go +++ b/src/crypto/rand/rand_arc4random.go @@ -1,9 +1,9 @@ -//go:build darwin || tinygo.wasm +//go:build darwin || wasip1 || wasip2 || wasm // This implementation of crypto/rand uses the arc4random_buf function // (available on both MacOS and WASI) to generate random numbers. // -// Note: arc4random_buf (unlike what the name suggets) does not use the insecure +// Note: arc4random_buf (unlike what the name suggests) does not use the insecure // RC4 cipher. Instead, it uses a high-quality cipher, varying by the libc // implementation. diff --git a/src/crypto/rand/rand_baremetal.go b/src/crypto/rand/rand_baremetal.go index 15fc916ca6..5711f23eb0 100644 --- a/src/crypto/rand/rand_baremetal.go +++ b/src/crypto/rand/rand_baremetal.go @@ -1,4 +1,4 @@ -//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 +//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt) // If you update the above build constraint, you'll probably also need to update // src/runtime/rand_hwrng.go. diff --git a/src/crypto/rand/rand_urandom.go b/src/crypto/rand/rand_urandom.go index 2a55a0ea5e..53554529b4 100644 --- a/src/crypto/rand/rand_urandom.go +++ b/src/crypto/rand/rand_urandom.go @@ -1,4 +1,4 @@ -//go:build linux && !baremetal && !wasi +//go:build linux && !baremetal && !wasip1 && !wasip2 // This implementation of crypto/rand uses the /dev/urandom pseudo-file to // generate random numbers. diff --git a/src/crypto/tls/common.go b/src/crypto/tls/common.go index 5b9c0c5f59..21a1a20f50 100644 --- a/src/crypto/tls/common.go +++ b/src/crypto/tls/common.go @@ -10,12 +10,44 @@ import ( "context" "crypto" "crypto/x509" + "fmt" "io" "net" "sync" "time" ) +const ( + VersionTLS10 = 0x0301 + VersionTLS11 = 0x0302 + VersionTLS12 = 0x0303 + VersionTLS13 = 0x0304 + + // Deprecated: SSLv3 is cryptographically broken, and is no longer + // supported by this package. See golang.org/issue/32716. + VersionSSL30 = 0x0300 +) + +// VersionName returns the name for the provided TLS version number +// (e.g. "TLS 1.3"), or a fallback representation of the value if the +// version is not implemented by this package. +func VersionName(version uint16) string { + switch version { + case VersionSSL30: + return "SSLv3" + case VersionTLS10: + return "TLS 1.0" + case VersionTLS11: + return "TLS 1.1" + case VersionTLS12: + return "TLS 1.2" + case VersionTLS13: + return "TLS 1.3" + default: + return fmt.Sprintf("0x%04X", version) + } +} + // CurveID is the type of a TLS identifier for an elliptic curve. See // https://www.iana.org/assignments/tls-parameters/tls-parameters.xml#tls-parameters-8. // @@ -23,9 +55,21 @@ import ( // only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7. type CurveID uint16 +// CipherSuiteName returns the standard name for the passed cipher suite ID +// +// Not Implemented. +func CipherSuiteName(id uint16) string { + return fmt.Sprintf("0x%04X", id) +} + // ConnectionState records basic TLS details about the connection. type ConnectionState struct { // TINYGO: empty; TLS connection offloaded to device + // + // Minimum (empty) fields for fortio.org/log http logging and others + // to compile and run. + PeerCertificates []*x509.Certificate + CipherSuite uint16 } // ClientAuthType declares the policy the server will follow for diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go index 75f4a25093..6fdedc39fc 100644 --- a/src/crypto/tls/tls.go +++ b/src/crypto/tls/tls.go @@ -101,7 +101,13 @@ type Dialer struct { // // The returned Conn, if any, will always be of type *Conn. func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - return nil, errors.New("tls:DialContext not implmented") + switch network { + case "tcp", "tcp4": + default: + return nil, fmt.Errorf("Network %s not supported", network) + } + + return net.DialTLS(addr) } // LoadX509KeyPair reads and parses a public/private key pair from a pair diff --git a/src/crypto/x509/internal/macos/macos.go b/src/crypto/x509/internal/macos/macos.go new file mode 100644 index 0000000000..e9ec2ef843 --- /dev/null +++ b/src/crypto/x509/internal/macos/macos.go @@ -0,0 +1,185 @@ +package macos + +import ( + "errors" + "time" +) + +// Exported symbols copied from Big Go, but stripped of functionality. +// Allows building of crypto/x509 on macOS. + +const ( + ErrSecCertificateExpired = -67818 + ErrSecHostNameMismatch = -67602 + ErrSecNotTrusted = -67843 +) + +var ErrNoTrustSettings = errors.New("no trust settings found") +var SecPolicyAppleSSL = StringToCFString("1.2.840.113635.100.1.3") // defined by POLICYMACRO +var SecPolicyOid = StringToCFString("SecPolicyOid") +var SecTrustSettingsPolicy = StringToCFString("kSecTrustSettingsPolicy") +var SecTrustSettingsPolicyString = StringToCFString("kSecTrustSettingsPolicyString") +var SecTrustSettingsResultKey = StringToCFString("kSecTrustSettingsResult") + +func CFArrayAppendValue(array CFRef, val CFRef) {} + +func CFArrayGetCount(array CFRef) int { + return 0 +} + +func CFDataGetBytePtr(data CFRef) uintptr { + return 0 +} + +func CFDataGetLength(data CFRef) int { + return 0 +} + +func CFDataToSlice(data CFRef) []byte { + return nil +} + +func CFEqual(a, b CFRef) bool { + return false +} + +func CFErrorGetCode(errRef CFRef) int { + return 0 +} + +func CFNumberGetValue(num CFRef) (int32, error) { + return 0, errors.New("not implemented") +} + +func CFRelease(ref CFRef) {} + +func CFStringToString(ref CFRef) string { + return "" +} + +func ReleaseCFArray(array CFRef) {} + +func SecCertificateCopyData(cert CFRef) ([]byte, error) { + return nil, errors.New("not implemented") +} + +func SecTrustEvaluateWithError(trustObj CFRef) (int, error) { + return 0, errors.New("not implemented") +} + +func SecTrustGetCertificateCount(trustObj CFRef) int { + return 0 +} + +func SecTrustGetResult(trustObj CFRef, result CFRef) (CFRef, CFRef, error) { + return 0, 0, errors.New("not implemented") +} + +func SecTrustSetVerifyDate(trustObj CFRef, dateRef CFRef) error { + return errors.New("not implemented") +} + +type CFRef uintptr + +func BytesToCFData(b []byte) CFRef { + return 0 +} + +func CFArrayCreateMutable() CFRef { + return 0 +} + +func CFArrayGetValueAtIndex(array CFRef, index int) CFRef { + return 0 +} + +func CFDateCreate(seconds float64) CFRef { + return 0 +} + +func CFDictionaryGetValueIfPresent(dict CFRef, key CFString) (value CFRef, ok bool) { + return 0, false +} + +func CFErrorCopyDescription(errRef CFRef) CFRef { + return 0 +} + +func CFStringCreateExternalRepresentation(strRef CFRef) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecCertificateCreateWithData(b []byte) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecPolicyCreateSSL(name string) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustCreateWithCertificates(certs CFRef, policies CFRef) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustEvaluate(trustObj CFRef) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustGetCertificateAtIndex(trustObj CFRef, i int) (CFRef, error) { + return 0, errors.New("not implemented") +} + +func SecTrustSettingsCopyCertificates(domain SecTrustSettingsDomain) (certArray CFRef, err error) { + return 0, errors.New("not implemented") +} + +func SecTrustSettingsCopyTrustSettings(cert CFRef, domain SecTrustSettingsDomain) (trustSettings CFRef, err error) { + return 0, errors.New("not implemented") +} + +func TimeToCFDateRef(t time.Time) CFRef { + return 0 +} + +type CFString CFRef + +func StringToCFString(s string) CFString { + return 0 +} + +type OSStatus struct { + // Has unexported fields. +} + +func (s OSStatus) Error() string + +type SecTrustResultType int32 + +const ( + SecTrustResultInvalid SecTrustResultType = iota + SecTrustResultProceed + SecTrustResultConfirm // deprecated + SecTrustResultDeny + SecTrustResultUnspecified + SecTrustResultRecoverableTrustFailure + SecTrustResultFatalTrustFailure + SecTrustResultOtherError +) + +type SecTrustSettingsDomain int32 + +const ( + SecTrustSettingsDomainUser SecTrustSettingsDomain = iota + SecTrustSettingsDomainAdmin + SecTrustSettingsDomainSystem +) + +type SecTrustSettingsResult int32 + +const ( + SecTrustSettingsResultInvalid SecTrustSettingsResult = iota + SecTrustSettingsResultTrustRoot + SecTrustSettingsResultTrustAsRoot + SecTrustSettingsResultDeny + SecTrustSettingsResultUnspecified +) diff --git a/src/device/tkey/tkey.go b/src/device/tkey/tkey.go new file mode 100644 index 0000000000..89a370414e --- /dev/null +++ b/src/device/tkey/tkey.go @@ -0,0 +1,139 @@ +//go:build tkey + +// Hand written file based on https://github.com/tillitis/tkey-libs/blob/main/include/tkey/tk1_mem.h + +package tkey + +import ( + "runtime/volatile" + "unsafe" +) + +// Peripherals +var ( + TRNG = (*TRNG_Type)(unsafe.Pointer(TK1_MMIO_TRNG_BASE)) + + TIMER = (*TIMER_Type)(unsafe.Pointer(TK1_MMIO_TIMER_BASE)) + + UDS = (*UDS_Type)(unsafe.Pointer(TK1_MMIO_UDS_BASE)) + + UART = (*UART_Type)(unsafe.Pointer(TK1_MMIO_UART_BASE)) + + TOUCH = (*TOUCH_Type)(unsafe.Pointer(TK1_MMIO_TOUCH_BASE)) + + TK1 = (*TK1_Type)(unsafe.Pointer(TK1_MMIO_TK1_BASE)) +) + +// Memory sections +const ( + TK1_ROM_BASE uintptr = 0x00000000 + + TK1_RAM_BASE uintptr = 0x40000000 + + TK1_MMIO_BASE uintptr = 0xc0000000 + + TK1_MMIO_TRNG_BASE uintptr = 0xc0000000 + + TK1_MMIO_TIMER_BASE uintptr = 0xc1000000 + + TK1_MMIO_UDS_BASE uintptr = 0xc2000000 + + TK1_MMIO_UART_BASE uintptr = 0xc3000000 + + TK1_MMIO_TOUCH_BASE uintptr = 0xc4000000 + + TK1_MMIO_FW_RAM_BASE uintptr = 0xd0000000 + + TK1_MMIO_TK1_BASE uintptr = 0xff000000 +) + +// Memory section sizes +const ( + TK1_RAM_SIZE uintptr = 0x20000 + + TK1_MMIO_SIZE uintptr = 0x3fffffff +) + +type TRNG_Type struct { + _ [36]byte + STATUS volatile.Register32 + _ [88]byte + ENTROPY volatile.Register32 +} + +type TIMER_Type struct { + _ [32]byte + CTRL volatile.Register32 + STATUS volatile.Register32 + PRESCALER volatile.Register32 + TIMER volatile.Register32 +} + +type UDS_Type struct { + _ [64]byte + DATA [8]volatile.Register32 +} + +type UART_Type struct { + _ [128]byte + RX_STATUS volatile.Register32 + RX_DATA volatile.Register32 + RX_BYTES volatile.Register32 + _ [116]byte + TX_STATUS volatile.Register32 + TX_DATA volatile.Register32 +} + +type TOUCH_Type struct { + _ [36]byte + STATUS volatile.Register32 +} + +type TK1_Type struct { + NAME0 volatile.Register32 + NAME1 volatile.Register32 + VERSION volatile.Register32 + _ [16]byte + SWITCH_APP volatile.Register32 + _ [4]byte + LED volatile.Register32 + GPIO volatile.Register16 + APP_ADDR volatile.Register32 + APP_SIZE volatile.Register32 + BLAKE2S volatile.Register32 + _ [72]byte + CDI_FIRST [8]volatile.Register32 + _ [32]byte + UDI_FIRST [2]volatile.Register32 + _ [62]byte + RAM_ADDR_RAND volatile.Register16 + _ [2]byte + RAM_DATA_RAND volatile.Register16 + _ [126]byte + CPU_MON_CTRL volatile.Register16 + _ [2]byte + CPU_MON_FIRST volatile.Register32 + CPU_MON_LAST volatile.Register32 + _ [60]byte + SYSTEM_RESET volatile.Register16 + _ [66]byte + SPI_EN volatile.Register32 + SPI_XFER volatile.Register32 + SPI_DATA volatile.Register32 +} + +const ( + TK1_MMIO_TIMER_CTRL_START_BIT = 0 + TK1_MMIO_TIMER_CTRL_STOP_BIT = 1 + TK1_MMIO_TIMER_CTRL_START = 1 << TK1_MMIO_TIMER_CTRL_START_BIT + TK1_MMIO_TIMER_CTRL_STOP = 1 << TK1_MMIO_TIMER_CTRL_STOP_BIT + + TK1_MMIO_TK1_LED_R_BIT = 2 + TK1_MMIO_TK1_LED_G_BIT = 1 + TK1_MMIO_TK1_LED_B_BIT = 0 + + TK1_MMIO_TK1_GPIO1_BIT = 0 + TK1_MMIO_TK1_GPIO2_BIT = 1 + TK1_MMIO_TK1_GPIO3_BIT = 2 + TK1_MMIO_TK1_GPIO4_BIT = 3 +) diff --git a/src/examples/bench-goro/bench.go b/src/examples/bench-goro/bench.go new file mode 100644 index 0000000000..db08a03c19 --- /dev/null +++ b/src/examples/bench-goro/bench.go @@ -0,0 +1,40 @@ +package main + +import ( + "machine" + "runtime" + "sync" + "time" +) + +const N = 500000 +const Ngoro = 4 + +func main() { + start := time.Now() + var wg sync.WaitGroup + wg.Add(Ngoro) + for i := 0; i < Ngoro; i++ { + go adder(&wg, N) + } + wg.Wait() + elapsed := time.Since(start) + goroutineCtxSwitchOverhead := (elapsed / (Ngoro * N)).String() + + elapsedstr := elapsed.String() + machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) + for { + println("bench:", elapsedstr, "goroutine ctx switch:", goroutineCtxSwitchOverhead) + machine.LED.High() + time.Sleep(elapsed) + machine.LED.Low() + time.Sleep(elapsed) + } +} + +func adder(wg *sync.WaitGroup, num int) { + for i := 0; i < num; i++ { + runtime.Gosched() + } + wg.Done() +} diff --git a/src/examples/echo/echo.go b/src/examples/echo/echo.go index be129dd093..a917b809ff 100644 --- a/src/examples/echo/echo.go +++ b/src/examples/echo/echo.go @@ -7,15 +7,13 @@ import ( "time" ) -// change these to test a different UART or pins if available var ( uart = machine.Serial - tx = machine.UART_TX_PIN - rx = machine.UART_RX_PIN ) func main() { - uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) + // use default settings for UART + uart.Configure(machine.UARTConfig{}) uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n")) input := make([]byte, 64) diff --git a/src/examples/hello-wasm-unknown/main.go b/src/examples/hello-wasm-unknown/main.go index 557f4a3c36..ff2ec88f18 100644 --- a/src/examples/hello-wasm-unknown/main.go +++ b/src/examples/hello-wasm-unknown/main.go @@ -3,6 +3,10 @@ // tinygo build -size short -o hello-unknown.wasm -target wasm-unknown -gc=leaking -no-debug ./src/examples/hello-wasm-unknown/ package main +// Smoke test: make sure the fmt package can be imported (even if it isn't +// really useful for wasm-unknown). +import _ "os" + var x int32 //go:wasmimport hosted echo_i32 diff --git a/src/examples/i2s/i2s.go b/src/examples/i2s/i2s.go index 4936d92ff5..06857c4882 100644 --- a/src/examples/i2s/i2s.go +++ b/src/examples/i2s/i2s.go @@ -13,11 +13,11 @@ func main() { Stereo: true, }) - data := make([]uint32, 64) + data := make([]uint16, 64) for { // get the next group of samples - machine.I2S0.Read(data) + machine.I2S0.ReadMono(data) println("data", data[0], data[1], data[2], data[4], "...") } diff --git a/src/examples/machinetest/machinetest.go b/src/examples/machinetest/machinetest.go new file mode 100644 index 0000000000..e79e38b18d --- /dev/null +++ b/src/examples/machinetest/machinetest.go @@ -0,0 +1,17 @@ +package main + +// This is the same as examples/serial, but it also imports the machine package. +// It is used as a smoke test for the machine package (for boards that don't +// have an on-board LED and therefore can't use examples/blinky1). + +import ( + _ "machine" // smoke test for the machine package + "time" +) + +func main() { + for { + println("hello world!") + time.Sleep(time.Second) + } +} diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go index e13ba5eb3a..df3a0a9056 100644 --- a/src/examples/pininterrupt/pininterrupt.go +++ b/src/examples/pininterrupt/pininterrupt.go @@ -3,7 +3,7 @@ package main // This example demonstrates how to use pin change interrupts. // // This is only an example and should not be copied directly in any serious -// circuit, because it lacks an important feature: debouncing. +// circuit, because it only naively implements an important feature: debouncing. // See: https://en.wikipedia.org/wiki/Switch#Contact_bounce import ( @@ -15,6 +15,8 @@ const ( led = machine.LED ) +var lastPress time.Time + func main() { // Configure the LED, defaulting to on (usually setting the pin to low will @@ -29,6 +31,13 @@ func main() { // Set an interrupt on this pin. err := button.SetInterrupt(buttonPinChange, func(machine.Pin) { + + // Ignore events that are too close to the last registered press (debouncing) + if time.Since(lastPress) < 100*time.Millisecond { + return + } + lastPress = time.Now() + led.Set(!led.Get()) }) if err != nil { diff --git a/src/internal/abi/abi.go b/src/internal/abi/abi.go new file mode 100644 index 0000000000..ee8e212a83 --- /dev/null +++ b/src/internal/abi/abi.go @@ -0,0 +1,2 @@ +// Package abi exposes low-level details of the Go compiler/runtime +package abi diff --git a/src/internal/abi/escape.go b/src/internal/abi/escape.go new file mode 100644 index 0000000000..0ecdf80308 --- /dev/null +++ b/src/internal/abi/escape.go @@ -0,0 +1,10 @@ +package abi + +import "unsafe" + +// Tell the compiler the given pointer doesn't escape. +// The compiler knows about this function and will give the nocapture parameter +// attribute. +func NoEscape(p unsafe.Pointer) unsafe.Pointer { + return p +} diff --git a/src/internal/abi/funcpc.go b/src/internal/abi/funcpc.go new file mode 100644 index 0000000000..0661ed7178 --- /dev/null +++ b/src/internal/abi/funcpc.go @@ -0,0 +1,12 @@ +package abi + +// These two signatures are present to satisfy the expectation of some programs +// (in particular internal/syscall/unix on MacOS). They do not currently have an +// implementation, in part because TinyGo doesn't use ABI0 or ABIInternal (it +// uses a C-like calling convention). +// Calls to FuncPCABI0 however are treated specially by the compiler when +// compiling for MacOS. + +func FuncPCABI0(f interface{}) uintptr + +func FuncPCABIInternal(f interface{}) uintptr diff --git a/src/internal/abi/type.go b/src/internal/abi/type.go new file mode 100644 index 0000000000..d1853e0f3d --- /dev/null +++ b/src/internal/abi/type.go @@ -0,0 +1,7 @@ +package abi + +type Type struct { + // Intentionally left empty. TinyGo uses a different way to represent types, + // so this is unimplementable. The type definition here is purely for + // compatibility. +} diff --git a/src/internal/binary/binary.go b/src/internal/binary/binary.go new file mode 100644 index 0000000000..25f8d39632 --- /dev/null +++ b/src/internal/binary/binary.go @@ -0,0 +1,37 @@ +// Package binary is a lightweight replacement package for encoding/binary. +package binary + +// This file contains small helper functions for working with binary data. + +var LittleEndian = littleEndian{} + +type littleEndian struct{} + +// Encode data like encoding/binary.LittleEndian.Uint16. +func (littleEndian) Uint16(b []byte) uint16 { + return uint16(b[0]) | uint16(b[1])<<8 +} + +// Store data like binary.LittleEndian.PutUint16. +func (littleEndian) PutUint16(b []byte, v uint16) { + b[0] = byte(v) + b[1] = byte(v >> 8) +} + +// Append data like binary.LittleEndian.AppendUint16. +func (littleEndian) AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} + +// Encode data like encoding/binary.LittleEndian.Uint32. +func (littleEndian) Uint32(b []byte) uint32 { + return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 +} + +func (littleEndian) Uint64(b []byte) uint64 { + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 +} diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go index eb5fcbfbd4..33ece2bbae 100644 --- a/src/internal/bytealg/bytealg.go +++ b/src/internal/bytealg/bytealg.go @@ -42,6 +42,31 @@ func Compare(a, b []byte) int { } } +// This function was copied from the Go 1.23 source tree (with runtime_cmpstring +// manually inlined). +func CompareString(a, b string) int { + l := len(a) + if len(b) < l { + l = len(b) + } + for i := 0; i < l; i++ { + c1, c2 := a[i], b[i] + if c1 < c2 { + return -1 + } + if c1 > c2 { + return +1 + } + } + if len(a) < len(b) { + return -1 + } + if len(a) > len(b) { + return +1 + } + return 0 +} + // Count the number of instances of a byte in a slice. func Count(b []byte, c byte) int { // Use a simple implementation, as there is no intrinsic that does this like we want. @@ -57,7 +82,7 @@ func Count(b []byte, c byte) int { // Count the number of instances of a byte in a string. func CountString(s string, c byte) int { // Use a simple implementation, as there is no intrinsic that does this like we want. - // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be seperate from Count. + // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be separate from Count. n := 0 for i := 0; i < len(s); i++ { if s[i] == c { @@ -216,7 +241,7 @@ func HashStrRev[T string | []byte](sep T) (uint32, uint32) { } // IndexRabinKarpBytes uses the Rabin-Karp search algorithm to return the index of the -// first occurence of substr in s, or -1 if not present. +// first occurrence of substr in s, or -1 if not present. // // This function was removed in Go 1.22. func IndexRabinKarpBytes(s, sep []byte) int { diff --git a/src/internal/cm/abi.go b/src/internal/cm/abi.go new file mode 100644 index 0000000000..4d63036784 --- /dev/null +++ b/src/internal/cm/abi.go @@ -0,0 +1,142 @@ +package cm + +import "unsafe" + +// AnyInteger is a type constraint for any integer type. +type AnyInteger interface { + ~int | ~uint | ~uintptr | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64 +} + +// Reinterpret reinterprets the bits of type From into type T. +// Will panic if the size of From is smaller than the size of To. +func Reinterpret[T, From any](from From) (to T) { + if unsafe.Sizeof(to) > unsafe.Sizeof(from) { + panic("reinterpret: size of to > from") + } + return *(*T)(unsafe.Pointer(&from)) +} + +// LowerString lowers a [string] into a pair of Core WebAssembly types. +// +// [string]: https://pkg.go.dev/builtin#string +func LowerString[S ~string](s S) (*byte, uint32) { + return unsafe.StringData(string(s)), uint32(len(s)) +} + +// LiftString lifts Core WebAssembly types into a [string]. +func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len AnyInteger](data Data, len Len) T { + return T(unsafe.String((*uint8)(unsafe.Pointer(data)), int(len))) +} + +// LowerList lowers a [List] into a pair of Core WebAssembly types. +func LowerList[L AnyList[T], T any](list L) (*T, uint32) { + l := (*List[T])(unsafe.Pointer(&list)) + return l.data, uint32(l.len) +} + +// LiftList lifts Core WebAssembly types into a [List]. +func LiftList[L AnyList[T], T any, Data unsafe.Pointer | uintptr | *T, Len AnyInteger](data Data, len Len) L { + return L(NewList((*T)(unsafe.Pointer(data)), len)) +} + +// BoolToU32 converts a value whose underlying type is [bool] into a [uint32]. +// Used to lower a [bool] into a Core WebAssembly i32 as specified in the [Canonical ABI]. +// +// [bool]: https://pkg.go.dev/builtin#bool +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func BoolToU32[B ~bool](v B) uint32 { return uint32(*(*uint8)(unsafe.Pointer(&v))) } + +// U32ToBool converts a [uint32] into a [bool]. +// Used to lift a Core WebAssembly i32 into a [bool] as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [bool]: https://pkg.go.dev/builtin#bool +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U32ToBool(v uint32) bool { tmp := uint8(v); return *(*bool)(unsafe.Pointer(&tmp)) } + +// F32ToU32 maps the bits of a [float32] into a [uint32]. +// Used to lower a [float32] into a Core WebAssembly i32 as specified in the [Canonical ABI]. +// +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +// [float32]: https://pkg.go.dev/builtin#float32 +// [uint32]: https://pkg.go.dev/builtin#uint32 +func F32ToU32(v float32) uint32 { return *(*uint32)(unsafe.Pointer(&v)) } + +// U32ToF32 maps the bits of a [uint32] into a [float32]. +// Used to lift a Core WebAssembly i32 into a [float32] as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [float32]: https://pkg.go.dev/builtin#float32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U32ToF32(v uint32) float32 { return *(*float32)(unsafe.Pointer(&v)) } + +// F64ToU64 maps the bits of a [float64] into a [uint64]. +// Used to lower a [float64] into a Core WebAssembly i64 as specified in the [Canonical ABI]. +// +// [float64]: https://pkg.go.dev/builtin#float64 +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +func F64ToU64(v float64) uint64 { return *(*uint64)(unsafe.Pointer(&v)) } + +// U64ToF64 maps the bits of a [uint64] into a [float64]. +// Used to lift a Core WebAssembly i64 into a [float64] as specified in the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [float64]: https://pkg.go.dev/builtin#float64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U64ToF64(v uint64) float64 { return *(*float64)(unsafe.Pointer(&v)) } + +// F32ToU64 maps the bits of a [float32] into a [uint64]. +// Used to lower a [float32] into a Core WebAssembly i64 when required by the [Canonical ABI]. +// +// [float32]: https://pkg.go.dev/builtin#float32 +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func F32ToU64(v float32) uint64 { return uint64(*(*uint32)(unsafe.Pointer(&v))) } + +// U64ToF32 maps the bits of a [uint64] into a [float32]. +// Used to lift a Core WebAssembly i64 into a [float32] when required by the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [float32]: https://pkg.go.dev/builtin#float32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U64ToF32(v uint64) float32 { + truncated := uint32(v) + return *(*float32)(unsafe.Pointer(&truncated)) +} + +// PointerToU32 converts a pointer of type *T into a [uint32]. +// Used to lower a pointer into a Core WebAssembly i32 as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func PointerToU32[T any](v *T) uint32 { return uint32(uintptr(unsafe.Pointer(v))) } + +// U32ToPointer converts a [uint32] into a pointer of type *T. +// Used to lift a Core WebAssembly i32 into a pointer as specified in the [Canonical ABI]. +// +// [uint32]: https://pkg.go.dev/builtin#uint32 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U32ToPointer[T any](v uint32) *T { return (*T)(unsafePointer(uintptr(v))) } + +// PointerToU64 converts a pointer of type *T into a [uint64]. +// Used to lower a pointer into a Core WebAssembly i64 as specified in the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func PointerToU64[T any](v *T) uint64 { return uint64(uintptr(unsafe.Pointer(v))) } + +// U64ToPointer converts a [uint64] into a pointer of type *T. +// Used to lift a Core WebAssembly i64 into a pointer as specified in the [Canonical ABI]. +// +// [uint64]: https://pkg.go.dev/builtin#uint64 +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +func U64ToPointer[T any](v uint64) *T { return (*T)(unsafePointer(uintptr(v))) } + +// Appease vet, see https://github.com/golang/go/issues/58625 +func unsafePointer(p uintptr) unsafe.Pointer { + return *(*unsafe.Pointer)(unsafe.Pointer(&p)) +} diff --git a/src/internal/cm/case.go b/src/internal/cm/case.go new file mode 100644 index 0000000000..2ca7c28da9 --- /dev/null +++ b/src/internal/cm/case.go @@ -0,0 +1,51 @@ +package cm + +// CaseUnmarshaler returns an function that can unmarshal text into +// [variant] or [enum] case T. +// +// [enum]: https://component-model.bytecodealliance.org/design/wit.html#enums +// [variant]: https://component-model.bytecodealliance.org/design/wit.html#variants +func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, text []byte) error { + if len(cases) <= linearScanThreshold { + return func(v *T, text []byte) error { + if len(text) == 0 { + return &emptyTextError{} + } + s := string(text) + for i := 0; i < len(cases); i++ { + if cases[i] == s { + *v = T(i) + return nil + } + } + return &noMatchingCaseError{} + } + } + + m := make(map[string]T, len(cases)) + for i, v := range cases { + m[v] = T(i) + } + + return func(v *T, text []byte) error { + if len(text) == 0 { + return &emptyTextError{} + } + c, ok := m[string(text)] + if !ok { + return &noMatchingCaseError{} + } + *v = c + return nil + } +} + +const linearScanThreshold = 16 + +type emptyTextError struct{} + +func (*emptyTextError) Error() string { return "empty text" } + +type noMatchingCaseError struct{} + +func (*noMatchingCaseError) Error() string { return "no matching case" } diff --git a/src/internal/cm/docs.go b/src/internal/cm/docs.go new file mode 100644 index 0000000000..5522cf9424 --- /dev/null +++ b/src/internal/cm/docs.go @@ -0,0 +1,8 @@ +// Package cm provides types and functions for interfacing with the WebAssembly Component Model. +// +// The types in this package (such as [List], [Option], [Result], and [Variant]) are designed to match the memory layout +// of [Component Model] types as specified in the [Canonical ABI]. +// +// [Component Model]: https://component-model.bytecodealliance.org/introduction.html +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#alignment +package cm diff --git a/src/internal/cm/empty.s b/src/internal/cm/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/cm/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/cm/error.go b/src/internal/cm/error.go new file mode 100644 index 0000000000..857d5ce7c1 --- /dev/null +++ b/src/internal/cm/error.go @@ -0,0 +1,40 @@ +package cm + +import "unsafe" + +// ErrorContext represents the Component Model [error-context] type, +// an immutable, non-deterministic, host-defined value meant to aid in debugging. +// +// [error-context]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-context-type +type ErrorContext struct { + _ HostLayout + errorContext +} + +type errorContext uint32 + +// Error implements the [error] interface. It returns the debug message associated with err. +func (err errorContext) Error() string { + return err.DebugMessage() +} + +// String implements [fmt.Stringer]. +func (err errorContext) String() string { + return err.DebugMessage() +} + +// DebugMessage represents the Canonical ABI [error-context.debug-message] function. +// +// [error-context.debug-message]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdebug-message +func (err errorContext) DebugMessage() string { + var s string + wasmimport_errorContextDebugMessage(err, unsafe.Pointer(&s)) + return s +} + +// Drop represents the Canonical ABI [error-context.drop] function. +// +// [error-context.drop]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#error-contextdrop +func (err errorContext) Drop() { + wasmimport_errorContextDrop(err) +} diff --git a/src/internal/cm/error.wasm.go b/src/internal/cm/error.wasm.go new file mode 100644 index 0000000000..112eb6d815 --- /dev/null +++ b/src/internal/cm/error.wasm.go @@ -0,0 +1,13 @@ +package cm + +import "unsafe" + +// msg uses unsafe.Pointer for compatibility with go1.23 and lower. +// +//go:wasmimport canon error-context.debug-message +//go:noescape +func wasmimport_errorContextDebugMessage(err errorContext, msg unsafe.Pointer) + +//go:wasmimport canon error-context.drop +//go:noescape +func wasmimport_errorContextDrop(err errorContext) diff --git a/src/internal/cm/future.go b/src/internal/cm/future.go new file mode 100644 index 0000000000..e82183f655 --- /dev/null +++ b/src/internal/cm/future.go @@ -0,0 +1,15 @@ +package cm + +// Future represents the Component Model [future] type. +// A future is a special case of stream. In non-error cases, +// a future delivers exactly one value before being automatically closed. +// +// [future]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types +type Future[T any] struct { + _ HostLayout + future[T] +} + +type future[T any] uint32 + +// TODO: implement methods on type future diff --git a/src/internal/cm/hostlayout_go122.go b/src/internal/cm/hostlayout_go122.go new file mode 100644 index 0000000000..25c1469762 --- /dev/null +++ b/src/internal/cm/hostlayout_go122.go @@ -0,0 +1,11 @@ +//go:build !go1.23 + +package cm + +// HostLayout marks a struct as using host memory layout. +// See [structs.HostLayout] in Go 1.23 or later. +type HostLayout struct { + _ hostLayout // prevent accidental conversion with plain struct{} +} + +type hostLayout struct{} diff --git a/src/internal/cm/hostlayout_go123.go b/src/internal/cm/hostlayout_go123.go new file mode 100644 index 0000000000..4fc3a2bdfa --- /dev/null +++ b/src/internal/cm/hostlayout_go123.go @@ -0,0 +1,9 @@ +//go:build go1.23 + +package cm + +import "structs" + +// HostLayout marks a struct as using host memory layout. +// See [structs.HostLayout] in Go 1.23 or later. +type HostLayout = structs.HostLayout diff --git a/src/internal/cm/list.go b/src/internal/cm/list.go new file mode 100644 index 0000000000..22d9d31f2e --- /dev/null +++ b/src/internal/cm/list.go @@ -0,0 +1,62 @@ +package cm + +import ( + "unsafe" +) + +// List represents a Component Model list. +// The binary representation of list is similar to a Go slice minus the cap field. +type List[T any] struct { + _ HostLayout + list[T] +} + +// AnyList is a type constraint for generic functions that accept any [List] type. +type AnyList[T any] interface { + ~struct { + _ HostLayout + list[T] + } +} + +// NewList returns a List[T] from data and len. +func NewList[T any, Len AnyInteger](data *T, len Len) List[T] { + return List[T]{ + list: list[T]{ + data: data, + len: uintptr(len), + }, + } +} + +// ToList returns a List[T] equivalent to the Go slice s. +// The underlying slice data is not copied, and the resulting List points at the +// same array storage as the slice. +func ToList[S ~[]T, T any](s S) List[T] { + return NewList[T](unsafe.SliceData([]T(s)), uintptr(len(s))) +} + +// list represents the internal representation of a Component Model list. +// It is intended to be embedded in a [List], so embedding types maintain +// the methods defined on this type. +type list[T any] struct { + _ HostLayout + data *T + len uintptr +} + +// Slice returns a Go slice representing the List. +func (l list[T]) Slice() []T { + return unsafe.Slice(l.data, l.len) +} + +// Data returns the data pointer for the list. +func (l list[T]) Data() *T { + return l.data +} + +// Len returns the length of the list. +// TODO: should this return an int instead of a uintptr? +func (l list[T]) Len() uintptr { + return l.len +} diff --git a/src/internal/cm/option.go b/src/internal/cm/option.go new file mode 100644 index 0000000000..cf7024aa62 --- /dev/null +++ b/src/internal/cm/option.go @@ -0,0 +1,59 @@ +package cm + +// Option represents a Component Model [option] type. +// +// [option]: https://component-model.bytecodealliance.org/design/wit.html#options +type Option[T any] struct { + _ HostLayout + option[T] +} + +// None returns an [Option] representing the none case, +// equivalent to the zero value. +func None[T any]() Option[T] { + return Option[T]{} +} + +// Some returns an [Option] representing the some case. +func Some[T any](v T) Option[T] { + return Option[T]{ + option: option[T]{ + isSome: true, + some: v, + }, + } +} + +// option represents the internal representation of a Component Model option type. +// The first byte is a bool representing none or some, +// followed by storage for the associated type T. +type option[T any] struct { + _ HostLayout + isSome bool + some T +} + +// None returns true if o represents the none case. +func (o *option[T]) None() bool { + return !o.isSome +} + +// Some returns a non-nil *T if o represents the some case, +// or nil if o represents the none case. +func (o *option[T]) Some() *T { + if o.isSome { + return &o.some + } + return nil +} + +// Value returns T if o represents the some case, +// or the zero value of T if o represents the none case. +// This does not have a pointer receiver, so it can be chained. +func (o option[T]) Value() T { + if !o.isSome { + var zero T + return zero + } + return o.some +} diff --git a/src/internal/cm/resource.go b/src/internal/cm/resource.go new file mode 100644 index 0000000000..830d76591a --- /dev/null +++ b/src/internal/cm/resource.go @@ -0,0 +1,21 @@ +package cm + +// Resource represents an opaque Component Model [resource handle]. +// It is represented in the [Canonical ABI] as an 32-bit integer. +// +// [resource handle]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#handle-types +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +type Resource uint32 + +// Rep represents a Component Model [resource rep], the core representation type of a resource. +// It is represented in the [Canonical ABI] as an 32-bit integer. +// +// [resource rep]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#canon-resourcerep +// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md +type Rep uint32 + +// ResourceNone is a sentinel value indicating a null or uninitialized resource. +// This is a reserved value specified in the [Canonical ABI runtime state]. +// +// [Canonical ABI runtime state]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#runtime-state +const ResourceNone = 0 diff --git a/src/internal/cm/result.go b/src/internal/cm/result.go new file mode 100644 index 0000000000..781dccc1a1 --- /dev/null +++ b/src/internal/cm/result.go @@ -0,0 +1,129 @@ +package cm + +import "unsafe" + +const ( + // ResultOK represents the OK case of a result. + ResultOK = false + + // ResultErr represents the error case of a result. + ResultErr = true +) + +// BoolResult represents a result with no OK or error type. +// False represents the OK case and true represents the error case. +type BoolResult bool + +// Result represents a result sized to hold the Shape type. +// The size of the Shape type must be greater than or equal to the size of OK and Err types. +// For results with two zero-length types, use [BoolResult]. +type Result[Shape, OK, Err any] struct { + _ HostLayout + result[Shape, OK, Err] +} + +// AnyResult is a type constraint for generic functions that accept any [Result] type. +type AnyResult[Shape, OK, Err any] interface { + ~struct { + _ HostLayout + result[Shape, OK, Err] + } +} + +// result represents the internal representation of a Component Model result type. +type result[Shape, OK, Err any] struct { + _ HostLayout + isErr bool + _ [0]OK + _ [0]Err + data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte +} + +// IsOK returns true if r represents the OK case. +func (r *result[Shape, OK, Err]) IsOK() bool { + r.validate() + return !r.isErr +} + +// IsErr returns true if r represents the error case. +func (r *result[Shape, OK, Err]) IsErr() bool { + r.validate() + return r.isErr +} + +// OK returns a non-nil *OK pointer if r represents the OK case. +// If r represents an error, then it returns nil. +func (r *result[Shape, OK, Err]) OK() *OK { + r.validate() + if r.isErr { + return nil + } + return (*OK)(unsafe.Pointer(&r.data)) +} + +// Err returns a non-nil *Err pointer if r represents the error case. +// If r represents the OK case, then it returns nil. +func (r *result[Shape, OK, Err]) Err() *Err { + r.validate() + if !r.isErr { + return nil + } + 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 + var ok OK + var err Err + + // Check if size of Shape is greater than both OK and Err + if unsafe.Sizeof(shape) > unsafe.Sizeof(ok) && unsafe.Sizeof(shape) > unsafe.Sizeof(err) { + panic("result: size of data type > OK and Err types") + } + + // Check if size of OK is greater than Shape + if unsafe.Sizeof(ok) > unsafe.Sizeof(shape) { + panic("result: size of OK type > data type") + } + + // Check if size of Err is greater than Shape + if unsafe.Sizeof(err) > unsafe.Sizeof(shape) { + panic("result: size of Err type > data type") + } + + // Check if Shape is zero-sized, but size of result != 1 + if unsafe.Sizeof(shape) == 0 && unsafe.Sizeof(*r) != 1 { + panic("result: size of data type == 0, but result size != 1") + } +} + +// OK returns an OK result with shape Shape and type OK and Err. +// Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. +func OK[R AnyResult[Shape, OK, Err], Shape, OK, Err any](ok OK) R { + var r Result[Shape, OK, Err] + r.validate() + r.isErr = ResultOK + *((*OK)(unsafe.Pointer(&r.data))) = ok + return R(r) +} + +// Err returns an error result with shape Shape and type OK and Err. +// Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument. +func Err[R AnyResult[Shape, OK, Err], Shape, OK, Err any](err Err) R { + var r Result[Shape, OK, Err] + r.validate() + r.isErr = ResultErr + *((*Err)(unsafe.Pointer(&r.data))) = err + return R(r) +} diff --git a/src/internal/cm/stream.go b/src/internal/cm/stream.go new file mode 100644 index 0000000000..80ef062e97 --- /dev/null +++ b/src/internal/cm/stream.go @@ -0,0 +1,15 @@ +package cm + +// Stream represents the Component Model [stream] type. +// A stream is a special case of stream. In non-error cases, +// a stream delivers exactly one value before being automatically closed. +// +// [stream]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#asynchronous-value-types +type Stream[T any] struct { + _ HostLayout + stream[T] +} + +type stream[T any] uint32 + +// TODO: implement methods on type stream diff --git a/src/internal/cm/tuple.go b/src/internal/cm/tuple.go new file mode 100644 index 0000000000..610a19be57 --- /dev/null +++ b/src/internal/cm/tuple.go @@ -0,0 +1,245 @@ +package cm + +// Tuple represents a [Component Model tuple] with 2 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple[T0, T1 any] struct { + _ HostLayout + F0 T0 + F1 T1 +} + +// Tuple3 represents a [Component Model tuple] with 3 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple3[T0, T1, T2 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 +} + +// Tuple4 represents a [Component Model tuple] with 4 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple4[T0, T1, T2, T3 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 +} + +// Tuple5 represents a [Component Model tuple] with 5 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple5[T0, T1, T2, T3, T4 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 +} + +// Tuple6 represents a [Component Model tuple] with 6 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple6[T0, T1, T2, T3, T4, T5 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 +} + +// Tuple7 represents a [Component Model tuple] with 7 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple7[T0, T1, T2, T3, T4, T5, T6 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 +} + +// Tuple8 represents a [Component Model tuple] with 8 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple8[T0, T1, T2, T3, T4, T5, T6, T7 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 +} + +// Tuple9 represents a [Component Model tuple] with 9 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple9[T0, T1, T2, T3, T4, T5, T6, T7, T8 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 +} + +// Tuple10 represents a [Component Model tuple] with 10 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 +} + +// Tuple11 represents a [Component Model tuple] with 11 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 +} + +// Tuple12 represents a [Component Model tuple] with 12 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 +} + +// Tuple13 represents a [Component Model tuple] with 13 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 +} + +// Tuple14 represents a [Component Model tuple] with 14 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 +} + +// Tuple15 represents a [Component Model tuple] with 15 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 +} + +// Tuple16 represents a [Component Model tuple] with 16 fields. +// +// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples +type Tuple16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any] struct { + _ HostLayout + F0 T0 + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 + F15 T15 +} + +// MaxTuple specifies the maximum number of fields in a Tuple* type, currently [Tuple16]. +// See https://github.com/WebAssembly/component-model/issues/373 for more information. +const MaxTuple = 16 diff --git a/src/internal/cm/variant.go b/src/internal/cm/variant.go new file mode 100644 index 0000000000..d0def34bb3 --- /dev/null +++ b/src/internal/cm/variant.go @@ -0,0 +1,85 @@ +package cm + +import "unsafe" + +// Discriminant is the set of types that can represent the tag or discriminator of a variant. +// Use uint8 where there are 256 or fewer cases, uint16 for up to 65,536 cases, or uint32 for anything greater. +type Discriminant interface { + uint8 | uint16 | uint32 +} + +// Variant represents a loosely-typed Component Model variant. +// Shape and Align must be non-zero sized types. To create a variant with no associated +// types, use an enum. +type Variant[Tag Discriminant, Shape, Align any] struct { + _ HostLayout + variant[Tag, Shape, Align] +} + +// AnyVariant is a type constraint for generic functions that accept any [Variant] type. +type AnyVariant[Tag Discriminant, Shape, Align any] interface { + ~struct { + _ HostLayout + variant[Tag, Shape, Align] + } +} + +// NewVariant returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, +// aligned to type Align, with a value of type T. +func NewVariant[Tag Discriminant, Shape, Align any, T any](tag Tag, data T) Variant[Tag, Shape, Align] { + validateVariant[Tag, Shape, Align, T]() + var v Variant[Tag, Shape, Align] + v.tag = tag + *(*T)(unsafe.Pointer(&v.data)) = data + return v +} + +// New returns a [Variant] with tag of type Disc, storage and GC shape of type Shape, +// aligned to type Align, with a value of type T. +func New[V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any, T any](tag Tag, data T) V { + validateVariant[Tag, Shape, Align, T]() + var v variant[Tag, Shape, Align] + v.tag = tag + *(*T)(unsafe.Pointer(&v.data)) = data + return *(*V)(unsafe.Pointer(&v)) +} + +// Case returns a non-nil *T if the [Variant] case is equal to tag, otherwise it returns nil. +func Case[T any, V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any](v *V, tag Tag) *T { + validateVariant[Tag, Shape, Align, T]() + v2 := (*variant[Tag, Shape, Align])(unsafe.Pointer(v)) + if v2.tag == tag { + return (*T)(unsafe.Pointer(&v2.data)) + } + return nil +} + +// variant is the internal representation of a Component Model variant. +// Shape and Align must be non-zero sized types. +type variant[Tag Discriminant, Shape, Align any] struct { + _ HostLayout + tag Tag + _ [0]Align + data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte +} + +// Tag returns the tag (discriminant) of variant v. +func (v *variant[Tag, Shape, Align]) Tag() Tag { + return v.tag +} + +// This function is sized so it can be inlined and optimized away. +func validateVariant[Disc Discriminant, Shape, Align any, T any]() { + var v variant[Disc, Shape, Align] + var t T + + // Check if size of T is greater than Shape + if unsafe.Sizeof(t) > unsafe.Sizeof(v.data) { + panic("variant: size of requested type > data type") + } + + // Check if Shape is zero-sized, but size of result != 1 + if unsafe.Sizeof(v.data) == 0 && unsafe.Sizeof(v) != 1 { + panic("variant: size of data type == 0, but variant size != 1") + } +} diff --git a/src/internal/futex/futex.go b/src/internal/futex/futex.go new file mode 100644 index 0000000000..5ecdd79c28 --- /dev/null +++ b/src/internal/futex/futex.go @@ -0,0 +1,72 @@ +package futex + +// Cross platform futex implementation. +// Futexes are supported on all major operating systems and on WebAssembly. +// +// For more information, see: https://outerproduct.net/futex-dictionary.html + +import ( + "sync/atomic" + "unsafe" +) + +// A futex is a way for userspace to wait with the pointer as the key, and for +// another thread to wake one or all waiting threads keyed on the same pointer. +// +// A futex does not change the underlying value, it only reads it before going +// to sleep (atomically) to prevent lost wake-ups. +type Futex struct { + atomic.Uint32 +} + +// Atomically check for cmp to still be equal to the futex value and if so, go +// to sleep. Return true if we were definitely awoken by a call to Wake or +// WakeAll, and false if we can't be sure of that. +func (f *Futex) Wait(cmp uint32) bool { + tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp) + + // We *could* detect a zero return value from the futex system call which + // would indicate we got awoken by a Wake or WakeAll call. However, this is + // what the manual page has to say: + // + // > Note that a wake-up can also be caused by common futex usage patterns + // > in unrelated code that happened to have previously used the futex + // > word's memory location (e.g., typical futex-based implementations of + // > Pthreads mutexes can cause this under some conditions). Therefore, + // > callers should always conservatively assume that a return value of 0 + // > can mean a spurious wake-up, and use the futex word's value (i.e., the + // > user-space synchronization scheme) to decide whether to continue to + // > block or not. + // + // I'm not sure whether we do anything like pthread does, so to be on the + // safe side we say we don't know whether the wakeup was spurious or not and + // return false. + return false +} + +// Like Wait, but times out after the number of nanoseconds in timeout. +func (f *Futex) WaitUntil(cmp uint32, timeout uint64) { + tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout) +} + +// Wake a single waiter. +func (f *Futex) Wake() { + tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32))) +} + +// Wake all waiters. +func (f *Futex) WakeAll() { + tinygo_futex_wake_all((*uint32)(unsafe.Pointer(&f.Uint32))) +} + +//export tinygo_futex_wait +func tinygo_futex_wait(addr *uint32, cmp uint32) + +//export tinygo_futex_wait_timeout +func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64) + +//export tinygo_futex_wake +func tinygo_futex_wake(addr *uint32) + +//export tinygo_futex_wake_all +func tinygo_futex_wake_all(addr *uint32) diff --git a/src/internal/futex/futex_darwin.c b/src/internal/futex/futex_darwin.c new file mode 100644 index 0000000000..358a87655f --- /dev/null +++ b/src/internal/futex/futex_darwin.c @@ -0,0 +1,49 @@ +//go:build none + +// This file is manually included, to avoid CGo which would cause a circular +// import. + +#include + +// This API isn't documented by Apple, but it is used by LLVM libc++ (so should +// be stable) and has been documented extensively here: +// https://outerproduct.net/futex-dictionary.html + +int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_us); +int __ulock_wait2(uint32_t operation, void *addr, uint64_t value, uint64_t timeout_ns, uint64_t value2); +int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value); + +// Operation code. +#define UL_COMPARE_AND_WAIT 1 + +// Flags to the operation value. +#define ULF_WAKE_ALL 0x00000100 +#define ULF_NO_ERRNO 0x01000000 + +void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { + __ulock_wait(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, 0); +} + +void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { + // Make sure that an accidental use of a zero timeout is not treated as an + // infinite timeout. Return if it's zero since it wouldn't be waiting for + // any significant time anyway. + // Probably unnecessary, but guards against potential bugs. + if (timeout == 0) { + return; + } + + // Note: __ulock_wait2 is available since MacOS 11. + // I think that's fine, since the version before that (MacOS 10.15) is EOL + // since 2022. Though if needed, we could certainly use __ulock_wait instead + // and deal with the smaller timeout value. + __ulock_wait2(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, timeout, 0); +} + +void tinygo_futex_wake(uint32_t *addr) { + __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, 0); +} + +void tinygo_futex_wake_all(uint32_t *addr) { + __ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO|ULF_WAKE_ALL, addr, 0); +} diff --git a/src/internal/futex/futex_linux.c b/src/internal/futex/futex_linux.c new file mode 100644 index 0000000000..ffefc97e49 --- /dev/null +++ b/src/internal/futex/futex_linux.c @@ -0,0 +1,33 @@ +//go:build none + +// This file is manually included, to avoid CGo which would cause a circular +// import. + +#include +#include +#include +#include +#include + +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +#define FUTEX_PRIVATE_FLAG 128 + +void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) { + syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0); +} + +void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) { + struct timespec ts = {0}; + ts.tv_sec = timeout / 1000000000; + ts.tv_nsec = timeout % 1000000000; + syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0); +} + +void tinygo_futex_wake(uint32_t *addr) { + syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0); +} + +void tinygo_futex_wake_all(uint32_t *addr) { + syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, INT_MAX, NULL, NULL, 0); +} diff --git a/src/internal/gclayout/gclayout.go b/src/internal/gclayout/gclayout.go new file mode 100644 index 0000000000..aa841d8048 --- /dev/null +++ b/src/internal/gclayout/gclayout.go @@ -0,0 +1,33 @@ +package gclayout + +import "unsafe" + +// Internal constants for gc layout +// See runtime/gc_precise.go + +var ( + NoPtrs unsafe.Pointer + Pointer unsafe.Pointer + String unsafe.Pointer + Slice unsafe.Pointer +) + +func init() { + var sizeBits uintptr + + switch unsafe.Sizeof(uintptr(0)) { + case 8: + sizeBits = 6 + case 4: + sizeBits = 5 + case 2: + sizeBits = 4 + } + + var sizeShift = sizeBits + 1 + + NoPtrs = unsafe.Pointer(uintptr(0b0< uintptr(addr2) { + // Canonicalize order to reduce number of entries in visited. + // Assumes non-moving garbage collector. + addr1, addr2 = addr2, addr1 + } + + // Short circuit if references are already seen. + v := visit{addr1, addr2, v1.typecode} + if _, ok := visited[v]; ok { + return true + } + + // Remember for later. + visited[v] = struct{}{} + } + + switch v1.Kind() { + case Array: + for i := 0; i < v1.Len(); i++ { + if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { + return false + } + } + return true + case Slice: + if v1.IsNil() != v2.IsNil() { + return false + } + if v1.Len() != v2.Len() { + return false + } + if v1.UnsafePointer() == v2.UnsafePointer() { + return true + } + for i := 0; i < v1.Len(); i++ { + if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { + return false + } + } + return true + case Interface: + if v1.IsNil() || v2.IsNil() { + return v1.IsNil() == v2.IsNil() + } + return deepValueEqual(v1.Elem(), v2.Elem(), visited) + case Ptr: + if v1.UnsafePointer() == v2.UnsafePointer() { + return true + } + return deepValueEqual(v1.Elem(), v2.Elem(), visited) + case Struct: + for i, n := 0, v1.NumField(); i < n; i++ { + if !deepValueEqual(v1.Field(i), v2.Field(i), visited) { + return false + } + } + return true + case Map: + if v1.IsNil() != v2.IsNil() { + return false + } + if v1.Len() != v2.Len() { + return false + } + if v1.UnsafePointer() == v2.UnsafePointer() { + return true + } + for _, k := range v1.MapKeys() { + val1 := v1.MapIndex(k) + val2 := v2.MapIndex(k) + if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) { + return false + } + } + return true + case Func: + if v1.IsNil() && v2.IsNil() { + return true + } + // Can't do better than this: + return false + default: + // Normal equality suffices + return valueInterfaceUnsafe(v1) == valueInterfaceUnsafe(v2) + } +} + +// DeepEqual reports whether x and y are “deeply equal”, defined as follows. +// Two values of identical type are deeply equal if one of the following cases applies. +// Values of distinct types are never deeply equal. +// +// Array values are deeply equal when their corresponding elements are deeply equal. +// +// Struct values are deeply equal if their corresponding fields, +// both exported and unexported, are deeply equal. +// +// Func values are deeply equal if both are nil; otherwise they are not deeply equal. +// +// Interface values are deeply equal if they hold deeply equal concrete values. +// +// Map values are deeply equal when all of the following are true: +// they are both nil or both non-nil, they have the same length, +// and either they are the same map object or their corresponding keys +// (matched using Go equality) map to deeply equal values. +// +// Pointer values are deeply equal if they are equal using Go's == operator +// or if they point to deeply equal values. +// +// Slice values are deeply equal when all of the following are true: +// they are both nil or both non-nil, they have the same length, +// and either they point to the same initial entry of the same underlying array +// (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. +// Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) +// are not deeply equal. +// +// Other values - numbers, bools, strings, and channels - are deeply equal +// if they are equal using Go's == operator. +// +// In general DeepEqual is a recursive relaxation of Go's == operator. +// However, this idea is impossible to implement without some inconsistency. +// Specifically, it is possible for a value to be unequal to itself, +// either because it is of func type (uncomparable in general) +// or because it is a floating-point NaN value (not equal to itself in floating-point comparison), +// or because it is an array, struct, or interface containing +// such a value. +// On the other hand, pointer values are always equal to themselves, +// even if they point at or contain such problematic values, +// because they compare equal using Go's == operator, and that +// is a sufficient condition to be deeply equal, regardless of content. +// DeepEqual has been defined so that the same short-cut applies +// to slices and maps: if x and y are the same slice or the same map, +// they are deeply equal regardless of content. +// +// As DeepEqual traverses the data values it may find a cycle. The +// second and subsequent times that DeepEqual compares two pointer +// values that have been compared before, it treats the values as +// equal rather than examining the values to which they point. +// This ensures that DeepEqual terminates. +func DeepEqual(x, y interface{}) bool { + if x == nil || y == nil { + return x == y + } + v1 := ValueOf(x) + v2 := ValueOf(y) + if v1.typecode != v2.typecode { + return false + } + return deepValueEqual(v1, v2, make(map[visit]struct{})) +} diff --git a/src/internal/reflectlite/endian_big.go b/src/internal/reflectlite/endian_big.go new file mode 100644 index 0000000000..5ad792dcc3 --- /dev/null +++ b/src/internal/reflectlite/endian_big.go @@ -0,0 +1,36 @@ +//go:build mips + +package reflectlite + +import "unsafe" + +// loadValue loads a value that may or may not be word-aligned. The number of +// bytes given in size are loaded. The biggest possible size it can load is that +// of an uintptr. +func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { + loadedValue := uintptr(0) + for i := uintptr(0); i < size; i++ { + loadedValue <<= 8 + loadedValue |= uintptr(*(*byte)(ptr)) + ptr = unsafe.Add(ptr, 1) + } + return loadedValue +} + +// storeValue is the inverse of loadValue. It stores a value to a pointer that +// doesn't need to be aligned. +func storeValue(ptr unsafe.Pointer, size, value uintptr) { + // This could perhaps be optimized using bits.ReverseBytes32 if needed. + value <<= (unsafe.Sizeof(uintptr(0)) - size) * 8 + for i := uintptr(0); i < size; i++ { + *(*byte)(ptr) = byte(value >> ((unsafe.Sizeof(uintptr(0)) - 1) * 8)) + ptr = unsafe.Add(ptr, 1) + value <<= 8 + } +} + +// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. +func maskAndShift(value, offset, size uintptr) uintptr { + mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) + return (uintptr(value) >> ((unsafe.Sizeof(uintptr(0)) - offset - size) * 8)) & mask +} diff --git a/src/internal/reflectlite/endian_little.go b/src/internal/reflectlite/endian_little.go new file mode 100644 index 0000000000..035ec01d8b --- /dev/null +++ b/src/internal/reflectlite/endian_little.go @@ -0,0 +1,35 @@ +//go:build !mips + +package reflectlite + +import "unsafe" + +// loadValue loads a value that may or may not be word-aligned. The number of +// bytes given in size are loaded. The biggest possible size it can load is that +// of an uintptr. +func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { + loadedValue := uintptr(0) + shift := uintptr(0) + for i := uintptr(0); i < size; i++ { + loadedValue |= uintptr(*(*byte)(ptr)) << shift + shift += 8 + ptr = unsafe.Add(ptr, 1) + } + return loadedValue +} + +// storeValue is the inverse of loadValue. It stores a value to a pointer that +// doesn't need to be aligned. +func storeValue(ptr unsafe.Pointer, size, value uintptr) { + for i := uintptr(0); i < size; i++ { + *(*byte)(ptr) = byte(value) + ptr = unsafe.Add(ptr, 1) + value >>= 8 + } +} + +// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. +func maskAndShift(value, offset, size uintptr) uintptr { + mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) + return (uintptr(value) >> (offset * 8)) & mask +} diff --git a/src/internal/reflectlite/reflect.go b/src/internal/reflectlite/reflect.go deleted file mode 100644 index 938e56a556..0000000000 --- a/src/internal/reflectlite/reflect.go +++ /dev/null @@ -1,51 +0,0 @@ -package reflectlite - -import "reflect" - -func Swapper(slice interface{}) func(i, j int) { - return reflect.Swapper(slice) -} - -type Kind = reflect.Kind -type Type = reflect.Type -type Value = reflect.Value - -const ( - Invalid Kind = reflect.Invalid - Bool Kind = reflect.Bool - Int Kind = reflect.Int - Int8 Kind = reflect.Int8 - Int16 Kind = reflect.Int16 - Int32 Kind = reflect.Int32 - Int64 Kind = reflect.Int64 - Uint Kind = reflect.Uint - Uint8 Kind = reflect.Uint8 - Uint16 Kind = reflect.Uint16 - Uint32 Kind = reflect.Uint32 - Uint64 Kind = reflect.Uint64 - Uintptr Kind = reflect.Uintptr - Float32 Kind = reflect.Float32 - Float64 Kind = reflect.Float64 - Complex64 Kind = reflect.Complex64 - Complex128 Kind = reflect.Complex128 - Array Kind = reflect.Array - Chan Kind = reflect.Chan - Func Kind = reflect.Func - Interface Kind = reflect.Interface - Map Kind = reflect.Map - Ptr Kind = reflect.Ptr - Slice Kind = reflect.Slice - String Kind = reflect.String - Struct Kind = reflect.Struct - UnsafePointer Kind = reflect.UnsafePointer -) - -func ValueOf(i interface{}) reflect.Value { - return reflect.ValueOf(i) -} - -func TypeOf(i interface{}) reflect.Type { - return reflect.TypeOf(i) -} - -type ValueError = reflect.ValueError diff --git a/src/reflect/strconv.go b/src/internal/reflectlite/strconv.go similarity index 99% rename from src/reflect/strconv.go rename to src/internal/reflectlite/strconv.go index 2d8131c9df..deabe4a5c7 100644 --- a/src/reflect/strconv.go +++ b/src/internal/reflectlite/strconv.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package reflect +package reflectlite import ( "unicode/utf8" diff --git a/src/internal/reflectlite/swapper.go b/src/internal/reflectlite/swapper.go new file mode 100644 index 0000000000..b8d85a9442 --- /dev/null +++ b/src/internal/reflectlite/swapper.go @@ -0,0 +1,40 @@ +package reflectlite + +import "unsafe" + +// Some of code here has been copied from the Go sources: +// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go +// It has the following copyright note: +// +// Copyright 2016 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. + +func Swapper(slice interface{}) func(i, j int) { + v := ValueOf(slice) + if v.Kind() != Slice { + panic(&ValueError{Method: "Swapper"}) + } + + // Just return Nop func if nothing to swap. + if v.Len() < 2 { + return func(i, j int) {} + } + + typ := v.typecode.Elem() + size := typ.Size() + + header := (*sliceHeader)(v.value) + tmp := unsafe.Pointer(&make([]byte, size)[0]) + + return func(i, j int) { + if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { + panic("reflect: slice index out of range") + } + val1 := unsafe.Add(header.data, uintptr(i)*size) + val2 := unsafe.Add(header.data, uintptr(j)*size) + memcpy(tmp, val1, size) + memcpy(val1, val2, size) + memcpy(val2, tmp, size) + } +} diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go new file mode 100644 index 0000000000..10350676c6 --- /dev/null +++ b/src/internal/reflectlite/type.go @@ -0,0 +1,1130 @@ +package reflectlite + +import ( + "internal/gclayout" + "internal/itoa" + "unsafe" +) + +// Flags stored in the first byte of the struct field byte array. Must be kept +// up to date with compiler/interface.go. +const ( + structFieldFlagAnonymous = 1 << iota + structFieldFlagHasTag + structFieldFlagIsExported + structFieldFlagIsEmbedded +) + +type Kind uint8 + +// Copied from reflect/type.go +// https://golang.org/src/reflect/type.go?s=8302:8316#L217 +// These constants must match basicTypes and the typeKind* constants in +// compiler/interface.go +const ( + Invalid Kind = iota + Bool + Int + Int8 + Int16 + Int32 + Int64 + Uint + Uint8 + Uint16 + Uint32 + Uint64 + Uintptr + Float32 + Float64 + Complex64 + Complex128 + String + UnsafePointer + Chan + Interface + Pointer + Slice + Array + Func + Map + Struct +) + +// Ptr is the old name for the Pointer kind. +const Ptr = Pointer + +func (k Kind) String() string { + switch k { + case Invalid: + return "invalid" + case Bool: + return "bool" + case Int: + return "int" + case Int8: + return "int8" + case Int16: + return "int16" + case Int32: + return "int32" + case Int64: + return "int64" + case Uint: + return "uint" + case Uint8: + return "uint8" + case Uint16: + return "uint16" + case Uint32: + return "uint32" + case Uint64: + return "uint64" + case Uintptr: + return "uintptr" + case Float32: + return "float32" + case Float64: + return "float64" + case Complex64: + return "complex64" + case Complex128: + return "complex128" + case String: + return "string" + case UnsafePointer: + return "unsafe.Pointer" + case Chan: + return "chan" + case Interface: + return "interface" + case Pointer: + return "ptr" + case Slice: + return "slice" + case Array: + return "array" + case Func: + return "func" + case Map: + return "map" + case Struct: + return "struct" + default: + return "kind" + itoa.Itoa(int(int8(k))) + } +} + +// Copied from reflect/type.go +// https://go.dev/src/reflect/type.go?#L348 + +// ChanDir represents a channel type's direction. +type ChanDir int + +const ( + RecvDir ChanDir = 1 << iota // <-chan + SendDir // chan<- + BothDir = RecvDir | SendDir // chan +) + +// Type represents the minimal interface for a Go type. +type Type interface { + // These should match the reflectlite.Type implementation in Go. + AssignableTo(u Type) bool + Comparable() bool + Elem() Type + Implements(u Type) bool + Kind() Kind + Name() string + PkgPath() string + Size() uintptr + String() string + + // Additional methods shared with reflect.Type. + Align() int + Field(i int) StructField + Key() Type + Len() int + NumField() int + NumMethod() int +} + +// Constants for the 'meta' byte. +const ( + kindMask = 31 // mask to apply to the meta byte to get the Kind value + flagNamed = 32 // flag that is set if this is a named type + flagComparable = 64 // flag that is set if this type is comparable + flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm +) + +// The base type struct. All type structs start with this. +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). +type elemType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType +} + +type ptrType struct { + RawType + numMethod uint16 + elem *RawType +} + +type interfaceType struct { + RawType + ptrTo *RawType + // TODO: methods +} + +type arrayType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + arrayLen uintptr + slicePtr *RawType +} + +type mapType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + key *RawType +} + +type namedType struct { + RawType + numMethod uint16 + ptrTo *RawType + elem *RawType + pkg *byte + name [1]byte +} + +// Type for struct types. The numField value is intentionally put before ptrTo +// for better struct packing on 32-bit and 64-bit architectures. On these +// architectures, the ptrTo field still has the same offset as in all the other +// type structs. +// The fields array isn't necessarily 1 structField long, instead it is as long +// as numFields. The array is given a length of 1 to satisfy the Go type +// checker. +type structType struct { + RawType + numMethod uint16 + ptrTo *RawType + pkgpath *byte + size uint32 + numField uint16 + fields [1]structField // the remaining fields are all of type structField +} + +type structField struct { + fieldType *RawType + data unsafe.Pointer // various bits of information, packed in a byte array +} + +// Equivalent to (go/types.Type).Underlying(): if this is a named type return +// the underlying type, else just return the type itself. +func (t *RawType) underlying() *RawType { + if t.isNamed() { + return (*elemType)(unsafe.Pointer(t)).elem + } + return t +} + +func (t *RawType) ptrtag() uintptr { + return uintptr(unsafe.Pointer(t)) & 0b11 +} + +func (t *RawType) isNamed() bool { + if tag := t.ptrtag(); tag != 0 { + return false + } + return t.meta&flagNamed != 0 +} + +func TypeOf(i interface{}) Type { + if i == nil { + return nil + } + typecode, _ := decomposeInterface(i) + return (*RawType)(typecode) +} + +func PtrTo(t Type) Type { return PointerTo(t) } + +func PointerTo(t Type) Type { + return pointerTo(t.(*RawType)) +} + +func pointerTo(t *RawType) *RawType { + if t.isNamed() { + return (*elemType)(unsafe.Pointer(t)).ptrTo + } + + switch t.Kind() { + case Pointer: + if tag := t.ptrtag(); tag < 3 { + return (*RawType)(unsafe.Add(unsafe.Pointer(t), 1)) + } + + // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 + // We need to be able to create types that match existing types to prevent typecode equality. + panic("reflect: cannot make *****T type") + case Struct: + return (*structType)(unsafe.Pointer(t)).ptrTo + default: + return (*elemType)(unsafe.Pointer(t)).ptrTo + } +} + +func (t *RawType) String() string { + if t.isNamed() { + s := t.name() + if s[0] == '.' { + return s[1:] + } + return s + } + switch t.Kind() { + case Chan: + elem := t.elem().String() + switch t.ChanDir() { + case SendDir: + return "chan<- " + elem + case RecvDir: + return "<-chan " + elem + case BothDir: + if elem[0] == '<' { + // typ is recv chan, need parentheses as "<-" associates with leftmost + // chan possible, see: + // * https://golang.org/ref/spec#Channel_types + // * https://github.com/golang/go/issues/39897 + return "chan (" + elem + ")" + } + return "chan " + elem + } + + case Pointer: + return "*" + t.elem().String() + case Slice: + return "[]" + t.elem().String() + case Array: + return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() + case Map: + return "map[" + t.key().String() + "]" + t.elem().String() + case Struct: + numField := t.NumField() + if numField == 0 { + return "struct {}" + } + s := "struct {" + for i := 0; i < numField; i++ { + f := t.rawField(i) + s += " " + f.Name + " " + f.Type.String() + if f.Tag != "" { + s += " " + quote(string(f.Tag)) + } + // every field except the last needs a semicolon + if i < numField-1 { + s += ";" + } + } + s += " }" + return s + case Interface: + // TODO(dgryski): Needs actual method set info + return "interface {}" + default: + return t.Kind().String() + } + + return t.Kind().String() +} + +func (t *RawType) Kind() Kind { + if t == nil { + return Invalid + } + + if tag := t.ptrtag(); tag != 0 { + return Pointer + } + + return Kind(t.meta & kindMask) +} + +var ( + errTypeElem = &TypeError{"Elem"} + errTypeKey = &TypeError{"Key"} + errTypeField = &TypeError{"Field"} + errTypeBits = &TypeError{"Bits"} + errTypeLen = &TypeError{"Len"} + errTypeNumField = &TypeError{"NumField"} + errTypeChanDir = &TypeError{"ChanDir"} + errTypeFieldByName = &TypeError{"FieldByName"} + errTypeFieldByIndex = &TypeError{"FieldByIndex"} +) + +// Elem returns the element type for channel, slice and array types, the +// pointed-to value for pointer types, and the key type for map types. +func (t *RawType) Elem() Type { + return t.elem() +} + +func (t *RawType) elem() *RawType { + if tag := t.ptrtag(); tag != 0 { + return (*RawType)(unsafe.Add(unsafe.Pointer(t), -1)) + } + + underlying := t.underlying() + switch underlying.Kind() { + case Pointer: + return (*ptrType)(unsafe.Pointer(underlying)).elem + case Chan, Slice, Array, Map: + return (*elemType)(unsafe.Pointer(underlying)).elem + default: + panic(errTypeElem) + } +} + +func (t *RawType) key() *RawType { + underlying := t.underlying() + if underlying.Kind() != Map { + panic(errTypeKey) + } + return (*mapType)(unsafe.Pointer(underlying)).key +} + +// Field returns the type of the i'th field of this struct type. It panics if t +// is not a struct type. +func (t *RawType) Field(i int) StructField { + field := t.rawField(i) + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: []int{i}, + } +} + +func rawStructFieldFromPointer(descriptor *structType, fieldType *RawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { + // Read the field tag, if there is one. + var tag string + if flagsByte&structFieldFlagHasTag != 0 { + data = unsafe.Add(data, 1) // C: data+1 + tagLen := uintptr(*(*byte)(data)) + data = unsafe.Add(data, 1) // C: data+1 + tag = *(*string)(unsafe.Pointer(&stringHeader{ + data: data, + len: tagLen, + })) + } + + // Set the PkgPath to some (arbitrary) value if the package path is not + // exported. + pkgPath := "" + if flagsByte&structFieldFlagIsExported == 0 { + // This field is unexported. + pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) + } + + return rawStructField{ + Name: name, + PkgPath: pkgPath, + Type: fieldType, + Tag: StructTag(tag), + Anonymous: flagsByte&structFieldFlagAnonymous != 0, + Offset: uintptr(offset), + } +} + +// rawField returns nearly the same value as Field but without converting the +// Type member to an interface. +// +// For internal use only. +func (t *RawType) rawField(n int) rawStructField { + if t.Kind() != Struct { + panic(errTypeField) + } + descriptor := (*structType)(unsafe.Pointer(t.underlying())) + if uint(n) >= uint(descriptor.numField) { + panic("reflect: field index out of range") + } + + // Iterate over all the fields to calculate the offset. + // This offset could have been stored directly in the array (to make the + // lookup faster), but by calculating it on-the-fly a bit of storage can be + // saved. + field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) + data = unsafe.Add(data, lenOffs) + + name := readStringZ(data) + data = unsafe.Add(data, len(name)) + + return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) +} + +// rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the +// Type member to an interface. +// +// For internal use only. +func (t *RawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { + if t.Kind() != Struct { + panic(errTypeField) + } + + type fieldWalker struct { + t *RawType + index []int + } + + queue := make([]fieldWalker, 0, 4) + queue = append(queue, fieldWalker{t, nil}) + + for len(queue) > 0 { + type result struct { + r rawStructField + index []int + } + + var found []result + var nextlevel []fieldWalker + + // For all the structs at this level.. + for _, ll := range queue { + // Iterate over all the fields looking for the matching name + // Also calculate field offset. + + descriptor := (*structType)(unsafe.Pointer(ll.t.underlying())) + field := &descriptor.fields[0] + + for i := uint16(0); i < descriptor.numField; i++ { + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + + offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) + data = unsafe.Add(data, lenOffs) + + name := readStringZ(data) + data = unsafe.Add(data, len(name)) + if match(name) { + found = append(found, result{ + rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), + append(ll.index[:len(ll.index):len(ll.index)], int(i)), + }) + } + + structOrPtrToStruct := field.fieldType.Kind() == Struct || (field.fieldType.Kind() == Pointer && field.fieldType.elem().Kind() == Struct) + if flagsByte&structFieldFlagIsEmbedded == structFieldFlagIsEmbedded && structOrPtrToStruct { + embedded := field.fieldType + if embedded.Kind() == Pointer { + embedded = embedded.elem() + } + + nextlevel = append(nextlevel, fieldWalker{ + t: embedded, + index: append(ll.index[:len(ll.index):len(ll.index)], int(i)), + }) + } + + // update offset/field pointer if there *is* a next field + if i < descriptor.numField-1 { + // Increment pointer to the next field. + field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) + } + } + } + + // found multiple hits at this level + if len(found) > 1 { + return rawStructField{}, nil, false + } + + // found the field we were looking for + if len(found) == 1 { + r := found[0] + return r.r, r.index, true + } + + // else len(found) == 0, move on to the next level + queue = append(queue[:0], nextlevel...) + } + + // didn't find it + return rawStructField{}, nil, false +} + +// Bits returns the number of bits that this type uses. It is only valid for +// arithmetic types (integers, floats, and complex numbers). For other types, it +// will panic. +func (t *RawType) Bits() int { + kind := t.Kind() + if kind >= Int && kind <= Complex128 { + return int(t.Size()) * 8 + } + panic(errTypeBits) +} + +// Len returns the number of elements in this array. It panics of the type kind +// is not Array. +func (t *RawType) Len() int { + if t.Kind() != Array { + panic(errTypeLen) + } + + return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) +} + +// NumField returns the number of fields of a struct type. It panics for other +// type kinds. +func (t *RawType) NumField() int { + if t.Kind() != Struct { + panic(errTypeNumField) + } + return int((*structType)(unsafe.Pointer(t.underlying())).numField) +} + +// Size returns the size in bytes of a given type. It is similar to +// unsafe.Sizeof. +func (t *RawType) Size() uintptr { + switch t.Kind() { + case Bool, Int8, Uint8: + return 1 + case Int16, Uint16: + return 2 + case Int32, Uint32: + return 4 + case Int64, Uint64: + return 8 + case Int, Uint: + return unsafe.Sizeof(int(0)) + case Uintptr: + return unsafe.Sizeof(uintptr(0)) + case Float32: + return 4 + case Float64: + return 8 + case Complex64: + return 8 + case Complex128: + return 16 + case String: + return unsafe.Sizeof("") + case UnsafePointer, Chan, Map, Pointer: + return unsafe.Sizeof(uintptr(0)) + case Slice: + return unsafe.Sizeof([]int{}) + case Interface: + return unsafe.Sizeof(interface{}(nil)) + case Func: + var f func() + return unsafe.Sizeof(f) + case Array: + return t.elem().Size() * uintptr(t.Len()) + case Struct: + u := t.underlying() + return uintptr((*structType)(unsafe.Pointer(u)).size) + default: + panic("unimplemented: size of type") + } +} + +// Align returns the alignment of this type. It is similar to calling +// unsafe.Alignof. +func (t *RawType) Align() int { + switch t.Kind() { + case Bool, Int8, Uint8: + return int(unsafe.Alignof(int8(0))) + case Int16, Uint16: + return int(unsafe.Alignof(int16(0))) + case Int32, Uint32: + return int(unsafe.Alignof(int32(0))) + case Int64, Uint64: + return int(unsafe.Alignof(int64(0))) + case Int, Uint: + return int(unsafe.Alignof(int(0))) + case Uintptr: + return int(unsafe.Alignof(uintptr(0))) + case Float32: + return int(unsafe.Alignof(float32(0))) + case Float64: + return int(unsafe.Alignof(float64(0))) + case Complex64: + return int(unsafe.Alignof(complex64(0))) + case Complex128: + return int(unsafe.Alignof(complex128(0))) + case String: + return int(unsafe.Alignof("")) + case UnsafePointer, Chan, Map, Pointer: + return int(unsafe.Alignof(uintptr(0))) + case Slice: + return int(unsafe.Alignof([]int(nil))) + case Interface: + return int(unsafe.Alignof(interface{}(nil))) + case Func: + var f func() + return int(unsafe.Alignof(f)) + case Struct: + numField := t.NumField() + alignment := 1 + for i := 0; i < numField; i++ { + fieldAlignment := t.rawField(i).Type.Align() + if fieldAlignment > alignment { + alignment = fieldAlignment + } + } + return alignment + case Array: + return t.elem().Align() + default: + panic("unimplemented: alignment of type") + } +} + +func (r *RawType) gcLayout() unsafe.Pointer { + kind := r.Kind() + + if kind < String { + return gclayout.NoPtrs + } + + switch kind { + case Pointer, UnsafePointer, Chan, Map: + return gclayout.Pointer + case String: + return gclayout.String + case Slice: + return gclayout.Slice + } + + // Unknown (for now); let the conservative pointer scanning handle it + return nil +} + +// FieldAlign returns the alignment if this type is used in a struct field. It +// is currently an alias for Align() but this might change in the future. +func (t *RawType) FieldAlign() int { + return t.Align() +} + +// AssignableTo returns whether a value of type t can be assigned to a variable +// of type u. +func (t *RawType) AssignableTo(u Type) bool { + if t == u.(*RawType) { + return true + } + + if t.underlying() == u.(*RawType).underlying() && (!t.isNamed() || !u.(*RawType).isNamed()) { + return true + } + + if u.Kind() == Interface && u.NumMethod() == 0 { + return true + } + + if u.Kind() == Interface { + panic("reflect: unimplemented: AssignableTo with interface") + } + return false +} + +func (t *RawType) Implements(u Type) bool { + if u.Kind() != Interface { + panic("reflect: non-interface type passed to Type.Implements") + } + return t.AssignableTo(u) +} + +// Comparable returns whether values of this type can be compared to each other. +func (t *RawType) Comparable() bool { + return (t.meta & flagComparable) == flagComparable +} + +// isBinary returns if the hashmapAlgorithmBinary functions can be used on this type +func (t *RawType) isBinary() bool { + return (t.meta & flagIsBinary) == flagIsBinary +} + +func (t *RawType) ChanDir() ChanDir { + if t.Kind() != Chan { + panic(errTypeChanDir) + } + + dir := int((*elemType)(unsafe.Pointer(t)).numMethod) + + // nummethod is overloaded for channel to store channel direction + return ChanDir(dir) +} + +func (t *RawType) NumMethod() int { + + if t.isNamed() { + return int((*namedType)(unsafe.Pointer(t)).numMethod) + } + + switch t.Kind() { + case Pointer: + 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. + return 0 +} + +// Read and return a null terminated string starting from data. +func readStringZ(data unsafe.Pointer) string { + start := data + var len uintptr + for *(*byte)(data) != 0 { + len++ + data = unsafe.Add(data, 1) // C: data++ + } + + return *(*string)(unsafe.Pointer(&stringHeader{ + data: start, + len: len, + })) +} + +func (t *RawType) name() string { + ntype := (*namedType)(unsafe.Pointer(t)) + return readStringZ(unsafe.Pointer(&ntype.name[0])) +} + +func (t *RawType) Name() string { + if t.isNamed() { + name := t.name() + for i := 0; i < len(name); i++ { + if name[i] == '.' { + return name[i+1:] + } + } + panic("corrupt name data") + } + + if kind := t.Kind(); kind < UnsafePointer { + return t.Kind().String() + } else if kind == UnsafePointer { + return "Pointer" + } + + return "" +} + +func (t *RawType) Key() Type { + return t.key() +} + +// OverflowComplex reports whether the complex128 x cannot be represented by type t. +// It panics if t's Kind is not Complex64 or Complex128. +func (t RawType) OverflowComplex(x complex128) bool { + k := t.Kind() + switch k { + case Complex64: + return overflowFloat32(real(x)) || overflowFloat32(imag(x)) + case Complex128: + return false + } + panic("reflect: OverflowComplex of non-complex type") +} + +// OverflowFloat reports whether the float64 x cannot be represented by type t. +// It panics if t's Kind is not Float32 or Float64. +func (t RawType) OverflowFloat(x float64) bool { + k := t.Kind() + switch k { + case Float32: + return overflowFloat32(x) + case Float64: + return false + } + panic("reflect: OverflowFloat of non-float type") +} + +// OverflowInt reports whether the int64 x cannot be represented by type t. +// It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. +func (t RawType) OverflowInt(x int64) bool { + k := t.Kind() + switch k { + case Int, Int8, Int16, Int32, Int64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowInt of non-int type") +} + +// OverflowUint reports whether the uint64 x cannot be represented by type t. +// It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. +func (t RawType) OverflowUint(x uint64) bool { + k := t.Kind() + switch k { + case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: + bitSize := t.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic("reflect: OverflowUint of non-uint type") +} + +func (t *RawType) PkgPath() string { + if t.isNamed() { + ntype := (*namedType)(unsafe.Pointer(t)) + return readStringZ(unsafe.Pointer(ntype.pkg)) + } + + return "" +} + +func (t *RawType) FieldByName(name string) (StructField, bool) { + if t.Kind() != Struct { + panic(errTypeFieldByName) + } + + field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) + if !ok { + return StructField{}, false + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + }, true +} + +func (t *RawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { + if t.Kind() != Struct { + panic(TypeError{"FieldByNameFunc"}) + } + + field, index, ok := t.rawFieldByNameFunc(match) + if !ok { + return StructField{}, false + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + }, true +} + +func (t *RawType) FieldByIndex(index []int) StructField { + ftype := t + var field rawStructField + + for _, n := range index { + structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) + if !structOrPtrToStruct { + panic(errTypeFieldByIndex) + } + + if ftype.Kind() == Pointer { + ftype = ftype.elem() + } + + field = ftype.rawField(n) + ftype = field.Type + } + + return StructField{ + Name: field.Name, + PkgPath: field.PkgPath, + Type: field.Type, // note: converts RawType to Type + Tag: field.Tag, + Anonymous: field.Anonymous, + Offset: field.Offset, + Index: index, + } +} + +// A StructField describes a single field in a struct. +// This must be kept in sync with [reflect.StructField]. +type StructField struct { + // Name indicates the field name. + Name string + + // PkgPath is the package path where the struct containing this field is + // declared for unexported fields, or the empty string for exported fields. + PkgPath string + + Type Type + Tag StructTag // field tag string + Offset uintptr + Index []int // index sequence for Type.FieldByIndex + Anonymous bool +} + +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" +} + +// rawStructField is the same as StructField but with the Type member replaced +// with RawType. For internal use only. Avoiding this conversion to the Type +// interface improves code size in many cases. +type rawStructField struct { + Name string + PkgPath string + Type *RawType + Tag StructTag + Offset uintptr + Anonymous bool +} + +// A StructTag is the tag string in a struct field. +type StructTag string + +// TODO: it would be feasible to do the key/value splitting at compile time, +// avoiding the code size cost of doing it at runtime + +// Get returns the value associated with key in the tag string. +func (tag StructTag) Get(key string) string { + v, _ := tag.Lookup(key) + return v +} + +// Lookup returns the value associated with key in the tag string. +func (tag StructTag) Lookup(key string) (value string, ok bool) { + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if key == name { + value, err := unquote(qvalue) + if err != nil { + break + } + return value, true + } + } + return "", false +} + +// TypeError is the error that is used in a panic when invoking a method on a +// type that is not applicable to that type. +type TypeError struct { + Method string +} + +func (e *TypeError) Error() string { + return "reflect: call of reflect.Type." + e.Method + " on invalid type" +} + +func align(offset uintptr, alignment uintptr) uintptr { + return (offset + alignment - 1) &^ (alignment - 1) +} + +func SliceOf(t Type) Type { + panic("unimplemented: reflect.SliceOf()") +} + +func ArrayOf(n int, t Type) Type { + panic("unimplemented: reflect.ArrayOf()") +} + +func StructOf([]StructField) Type { + panic("unimplemented: reflect.StructOf()") +} + +func MapOf(key, value Type) Type { + panic("unimplemented: reflect.MapOf()") +} + +func FuncOf(in, out []Type, variadic bool) Type { + panic("unimplemented: reflect.FuncOf()") +} + +const maxVarintLen32 = 5 + +// encoding/binary.Uvarint, specialized for uint32 +func uvarint32(buf []byte) (uint32, int) { + var x uint32 + var s uint + for i, b := range buf { + if b < 0x80 { + return x | uint32(b)< unsafe.Sizeof(uintptr(0)) { + return int64(*(*int)(v.value)) + } else { + return int64(int(uintptr(v.value))) + } + case Int8: + if v.isIndirect() { + return int64(*(*int8)(v.value)) + } else { + return int64(int8(uintptr(v.value))) + } + case Int16: + if v.isIndirect() { + return int64(*(*int16)(v.value)) + } else { + return int64(int16(uintptr(v.value))) + } + case Int32: + if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) { + return int64(*(*int32)(v.value)) + } else { + return int64(int32(uintptr(v.value))) + } + case Int64: + if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { + return int64(*(*int64)(v.value)) + } else { + return int64(int64(uintptr(v.value))) + } + default: + panic(&ValueError{Method: "Int", Kind: v.Kind()}) + } +} + +// CanUint reports whether Uint can be used without panicking. +func (v Value) CanUint() bool { + switch v.Kind() { + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return true + default: + return false + } +} + +func (v Value) Uint() uint64 { + switch v.Kind() { + case Uintptr: + if v.isIndirect() { + return uint64(*(*uintptr)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint8: + if v.isIndirect() { + return uint64(*(*uint8)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint16: + if v.isIndirect() { + return uint64(*(*uint16)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint: + if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint32: + if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint32)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint64: + if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint64)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + default: + panic(&ValueError{Method: "Uint", Kind: v.Kind()}) + } +} + +// CanFloat reports whether Float can be used without panicking. +func (v Value) CanFloat() bool { + switch v.Kind() { + case Float32, Float64: + return true + default: + return false + } +} + +func (v Value) Float32() float32 { + switch v.Kind() { + case Float32: + if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { + // The float is stored as an external value on systems with 16-bit + // pointers. + return *(*float32)(v.value) + } else { + // The float is directly stored in the interface value on systems + // with 32-bit and 64-bit pointers. + return *(*float32)(unsafe.Pointer(&v.value)) + } + + case Float64: + return float32(v.Float()) + + } + + panic(&ValueError{Method: "Float", Kind: v.Kind()}) +} + +func (v Value) Float() float64 { + switch v.Kind() { + case Float32: + if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { + // The float is stored as an external value on systems with 16-bit + // pointers. + return float64(*(*float32)(v.value)) + } else { + // The float is directly stored in the interface value on systems + // with 32-bit and 64-bit pointers. + return float64(*(*float32)(unsafe.Pointer(&v.value))) + } + case Float64: + if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) { + // For systems with 16-bit and 32-bit pointers. + return *(*float64)(v.value) + } else { + // The float is directly stored in the interface value on systems + // with 64-bit pointers. + return *(*float64)(unsafe.Pointer(&v.value)) + } + default: + panic(&ValueError{Method: "Float", Kind: v.Kind()}) + } +} + +// CanComplex reports whether Complex can be used without panicking. +func (v Value) CanComplex() bool { + switch v.Kind() { + case Complex64, Complex128: + return true + default: + return false + } +} + +func (v Value) Complex() complex128 { + switch v.Kind() { + case Complex64: + if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) { + // The complex number is stored as an external value on systems with + // 16-bit and 32-bit pointers. + return complex128(*(*complex64)(v.value)) + } else { + // The complex number is directly stored in the interface value on + // systems with 64-bit pointers. + return complex128(*(*complex64)(unsafe.Pointer(&v.value))) + } + case Complex128: + // This is a 128-bit value, which is always stored as an external value. + // It may be stored in the pointer directly on very uncommon + // architectures with 128-bit pointers, however. + return *(*complex128)(v.value) + default: + panic(&ValueError{Method: "Complex", Kind: v.Kind()}) + } +} + +func (v Value) String() string { + switch v.Kind() { + case String: + // A string value is always bigger than a pointer as it is made of a + // pointer and a length. + return *(*string)(v.value) + default: + // Special case because of the special treatment of .String() in Go. + return "<" + v.typecode.String() + " Value>" + } +} + +func (v Value) Bytes() []byte { + switch v.Kind() { + case Slice: + if v.typecode.elem().Kind() != Uint8 { + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + } + return *(*[]byte)(v.value) + + case Array: + v.checkAddressable() + + if v.typecode.elem().Kind() != Uint8 { + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + } + + // Small inline arrays are not addressable, so we only have to + // handle addressable arrays which will be stored as pointers + // in v.value + return unsafe.Slice((*byte)(v.value), v.Len()) + } + + panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) +} + +func (v Value) Slice(i, j int) Value { + switch v.Kind() { + case Slice: + hdr := *(*sliceHeader)(v.value) + i, j := uintptr(i), uintptr(j) + + if j < i || hdr.cap < j { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + hdr.len = j - i + hdr.cap = hdr.cap - i + hdr.data = unsafe.Add(hdr.data, i*elemSize) + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case Array: + v.checkAddressable() + buf, length := buflen(v) + i, j := uintptr(i), uintptr(j) + if j < i || length < j { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + var hdr sliceHeader + hdr.len = j - i + hdr.cap = length - i + hdr.data = unsafe.Add(buf, i*elemSize) + + sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr + return Value{ + typecode: sliceType, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case String: + i, j := uintptr(i), uintptr(j) + str := *(*stringHeader)(v.value) + + if j < i || str.len < j { + slicePanic() + } + + hdr := stringHeader{ + data: unsafe.Add(str.data, i), + len: j - i, + } + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + } + + panic(&ValueError{Method: "Slice", Kind: v.Kind()}) +} + +func (v Value) Slice3(i, j, k int) Value { + switch v.Kind() { + case Slice: + hdr := *(*sliceHeader)(v.value) + i, j, k := uintptr(i), uintptr(j), uintptr(k) + + if j < i || k < j || hdr.len < k { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + hdr.len = j - i + hdr.cap = k - i + hdr.data = unsafe.Add(hdr.data, i*elemSize) + + return Value{ + typecode: v.typecode, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + + case Array: + v.checkAddressable() + buf, length := buflen(v) + i, j, k := uintptr(i), uintptr(j), uintptr(k) + if j < i || k < j || length < k { + slicePanic() + } + + elemSize := v.typecode.underlying().elem().Size() + + var hdr sliceHeader + hdr.len = j - i + hdr.cap = k - i + hdr.data = unsafe.Add(buf, i*elemSize) + + sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr + return Value{ + typecode: sliceType, + value: unsafe.Pointer(&hdr), + flags: v.flags, + } + } + + panic("unimplemented: (reflect.Value).Slice3()") +} + +//go:linkname maplen runtime.hashmapLen +func maplen(p unsafe.Pointer) int + +//go:linkname chanlen runtime.chanLen +func chanlen(p unsafe.Pointer) int + +// Len returns the length of this value for slices, strings, arrays, channels, +// and maps. For other types, it panics. +func (v Value) Len() int { + switch v.typecode.Kind() { + case Array: + return v.typecode.Len() + case Chan: + return chanlen(v.pointer()) + case Map: + return maplen(v.pointer()) + case Slice: + return int((*sliceHeader)(v.value).len) + case String: + return int((*stringHeader)(v.value).len) + default: + panic(&ValueError{Method: "Len", Kind: v.Kind()}) + } +} + +//go:linkname chancap runtime.chanCap +func chancap(p unsafe.Pointer) int + +// Cap returns the capacity of this value for arrays, channels and slices. +// For other types, it panics. +func (v Value) Cap() int { + switch v.typecode.Kind() { + case Array: + return v.typecode.Len() + case Chan: + return chancap(v.pointer()) + case Slice: + return int((*sliceHeader)(v.value).cap) + default: + panic(&ValueError{Method: "Cap", Kind: v.Kind()}) + } +} + +//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 { + return v.typecode.NumField() +} + +func (v Value) Elem() Value { + switch v.Kind() { + case Ptr: + ptr := v.pointer() + if ptr == nil { + return Value{} + } + // Don't copy RO flags + flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect + return Value{ + typecode: v.typecode.elem(), + value: ptr, + flags: flags, + } + case Interface: + typecode, value := decomposeInterface(*(*interface{})(v.value)) + return Value{ + typecode: (*RawType)(typecode), + value: value, + flags: v.flags &^ valueFlagIndirect, + } + default: + panic(&ValueError{Method: "Elem", Kind: v.Kind()}) + } +} + +// Field returns the value of the i'th field of this struct. +func (v Value) Field(i int) Value { + if v.Kind() != Struct { + panic(&ValueError{Method: "Field", Kind: v.Kind()}) + } + structField := v.typecode.rawField(i) + + // Copy flags but clear EmbedRO; we're not an embedded field anymore + flags := v.flags & ^valueFlagEmbedRO + if structField.PkgPath != "" { + // No PkgPath => not exported. + // Clear exported flag even if the parent was exported. + flags &^= valueFlagExported + + // Update the RO flag + if structField.Anonymous { + // Embedded field + flags |= valueFlagEmbedRO + } else { + flags |= valueFlagStickyRO + } + } else { + // Parent field may not have been exported but we are + flags |= valueFlagExported + } + + size := v.typecode.Size() + fieldType := structField.Type + fieldSize := fieldType.Size() + if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) { + // v.value was already a pointer to the value and it should stay that + // way. + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Add(v.value, structField.Offset), + } + } + + // The fieldSize is smaller than uintptr, which means that the value will + // have to be stored directly in the interface value. + + if fieldSize == 0 { + // The struct field is zero sized. + // This is a rare situation, but because it's undefined behavior + // to shift the size of the value (zeroing the value), handle this + // situation explicitly. + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Pointer(nil), + } + } + + if size > unsafe.Sizeof(uintptr(0)) { + // The value was not stored in the interface before but will be + // afterwards, so load the value (from the correct offset) and return + // it. + ptr := unsafe.Add(v.value, structField.Offset) + value := unsafe.Pointer(loadValue(ptr, fieldSize)) + return Value{ + flags: flags &^ valueFlagIndirect, + typecode: fieldType, + value: value, + } + } + + // The value was already stored directly in the interface and it still + // is. Cut out the part of the value that we need. + value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Pointer(value), + } +} + +var uint8Type = TypeOf(uint8(0)).(*RawType) + +func (v Value) Index(i int) Value { + switch v.Kind() { + case Slice: + // Extract an element from the slice. + slice := *(*sliceHeader)(v.value) + if uint(i) >= uint(slice.len) { + panic("reflect: slice index out of range") + } + flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() + elem := Value{ + typecode: v.typecode.elem(), + flags: flags, + } + elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value + return elem + case String: + // Extract a character from a string. + // A string is never stored directly in the interface, but always as a + // pointer to the string value. + // Keeping valueFlagExported if set, but don't set valueFlagIndirect + // otherwise CanSet will return true for string elements (which is bad, + // strings are read-only). + s := *(*stringHeader)(v.value) + if uint(i) >= uint(s.len) { + panic("reflect: string index out of range") + } + return Value{ + typecode: uint8Type, + value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), + flags: v.flags & valueFlagExported, + } + case Array: + // Extract an element from the array. + elemType := v.typecode.elem() + elemSize := elemType.Size() + size := v.typecode.Size() + if size == 0 { + // The element size is 0 and/or the length of the array is 0. + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + } + } + if elemSize > unsafe.Sizeof(uintptr(0)) { + // The resulting value doesn't fit in a pointer so must be + // indirect. Also, because size != 0 this implies that the array + // length must be != 0, and thus that the total size is at least + // elemSize. + addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: addr, + } + } + + if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { + // The element fits in a pointer, but the array is not stored in the pointer directly. + // Load the value from the pointer. + addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + value := addr + if !v.isIndirect() { + // Use a pointer to the value (don't load the value) if the + // 'indirect' flag is set. + value = unsafe.Pointer(loadValue(addr, elemSize)) + } + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: value, + } + } + + // The value fits in a pointer, so extract it with some shifting and + // masking. + offset := elemSize * uintptr(i) + value := maskAndShift(uintptr(v.value), offset, elemSize) + return Value{ + typecode: v.typecode.elem(), + flags: v.flags, + value: unsafe.Pointer(value), + } + default: + panic(&ValueError{Method: "Index", Kind: v.Kind()}) + } +} + +func (v Value) NumMethod() int { + if v.typecode == nil { + panic(&ValueError{Method: "reflect.Value.NumMethod", Kind: Invalid}) + } + return v.typecode.NumMethod() +} + +// OverflowFloat reports whether the float64 x cannot be represented by v's type. +// It panics if v's Kind is not Float32 or Float64. +func (v Value) OverflowFloat(x float64) bool { + k := v.Kind() + switch k { + case Float32: + return overflowFloat32(x) + case Float64: + return false + } + panic(&ValueError{Method: "reflect.Value.OverflowFloat", Kind: v.Kind()}) +} + +func overflowFloat32(x float64) bool { + if x < 0 { + x = -x + } + return math.MaxFloat32 < x && x <= math.MaxFloat64 +} + +func (v Value) MapKeys() []Value { + if v.Kind() != Map { + panic(&ValueError{Method: "MapKeys", Kind: v.Kind()}) + } + + // empty map + if v.Len() == 0 { + return nil + } + + keys := make([]Value, 0, v.Len()) + + it := hashmapNewIterator() + k := New(v.typecode.Key()) + e := New(v.typecode.Elem()) + + keyType := v.typecode.key() + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + for hashmapNext(v.pointer(), it, k.value, e.value) { + if shouldUnpackInterface { + intf := *(*interface{})(k.value) + v := ValueOf(intf) + keys = append(keys, v) + } else { + keys = append(keys, k.Elem()) + } + k = New(v.typecode.Key()) + } + + return keys +} + +//go:linkname hashmapStringGet runtime.hashmapStringGet +func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool + +//go:linkname hashmapBinaryGet runtime.hashmapBinaryGet +func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool + +//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGet +func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool + +func (v Value) MapIndex(key Value) Value { + if v.Kind() != Map { + panic(&ValueError{Method: "MapIndex", Kind: v.Kind()}) + } + + vkey := v.typecode.key() + + // compare key type with actual key type of map + if !key.typecode.AssignableTo(vkey) { + // type error? + panic("reflect.Value.MapIndex: incompatible types for key") + } + + elemType := v.typecode.Elem() + elem := New(elemType) + + if vkey.Kind() == String { + if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } else if vkey.isBinary() { + var keyptr unsafe.Pointer + if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + keyptr = key.value + } else { + keyptr = unsafe.Pointer(&key.value) + } + //TODO(dgryski): zero out padding bytes in key, if any + if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } else { + if ok := hashmapInterfaceGet(v.pointer(), key.Interface(), elem.value, elemType.Size()); !ok { + return Value{} + } + return elem.Elem() + } +} + +//go:linkname hashmapNewIterator runtime.hashmapNewIterator +func hashmapNewIterator() unsafe.Pointer + +//go:linkname hashmapNext runtime.hashmapNext +func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool + +func (v Value) MapRange() *MapIter { + iter := &MapIter{} + iter.Reset(v) + return iter +} + +type MapIter struct { + m Value + it unsafe.Pointer + key Value + val Value + + valid bool + unpackKeyInterface bool +} + +func (it *MapIter) Key() Value { + if !it.valid { + panic("reflect.MapIter.Key called on invalid iterator") + } + + if it.unpackKeyInterface { + intf := *(*interface{})(it.key.value) + v := ValueOf(intf) + return v + } + + return it.key.Elem() +} + +func (v Value) SetIterKey(iter *MapIter) { + v.Set(iter.Key()) +} + +func (it *MapIter) Value() Value { + if !it.valid { + panic("reflect.MapIter.Value called on invalid iterator") + } + + return it.val.Elem() +} + +func (v Value) SetIterValue(iter *MapIter) { + v.Set(iter.Value()) +} + +func (it *MapIter) Next() bool { + it.key = New(it.m.typecode.Key()) + it.val = New(it.m.typecode.Elem()) + + it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value) + return it.valid +} + +func (iter *MapIter) Reset(v Value) { + if v.Kind() != Map { + panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) + } + + keyType := v.typecode.key() + + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() + + *iter = MapIter{ + m: v, + it: hashmapNewIterator(), + unpackKeyInterface: shouldUnpackInterface, + } +} + +func (v Value) Set(x Value) { + v.checkAddressable() + v.checkRO() + if !x.typecode.AssignableTo(v.typecode) { + panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) + } + + if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { + // move the value of x back into the interface, if possible + if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { + x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) + } + + intf := composeInterface(unsafe.Pointer(x.typecode), x.value) + x = Value{ + typecode: v.typecode, + value: unsafe.Pointer(&intf), + } + } + + size := v.typecode.Size() + if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { + storeValue(v.value, size, uintptr(x.value)) + } else { + memcpy(v.value, x.value, size) + } +} + +func (v Value) SetZero() { + v.checkAddressable() + v.checkRO() + size := v.typecode.Size() + memzero(v.value, size) +} + +func (v Value) SetBool(x bool) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Bool: + *(*bool)(v.value) = x + default: + panic(&ValueError{Method: "SetBool", Kind: v.Kind()}) + } +} + +func (v Value) SetInt(x int64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Int: + *(*int)(v.value) = int(x) + case Int8: + *(*int8)(v.value) = int8(x) + case Int16: + *(*int16)(v.value) = int16(x) + case Int32: + *(*int32)(v.value) = int32(x) + case Int64: + *(*int64)(v.value) = x + default: + panic(&ValueError{Method: "SetInt", Kind: v.Kind()}) + } +} + +func (v Value) SetUint(x uint64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Uint: + *(*uint)(v.value) = uint(x) + case Uint8: + *(*uint8)(v.value) = uint8(x) + case Uint16: + *(*uint16)(v.value) = uint16(x) + case Uint32: + *(*uint32)(v.value) = uint32(x) + case Uint64: + *(*uint64)(v.value) = x + case Uintptr: + *(*uintptr)(v.value) = uintptr(x) + default: + panic(&ValueError{Method: "SetUint", Kind: v.Kind()}) + } +} + +func (v Value) SetFloat(x float64) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Float32: + *(*float32)(v.value) = float32(x) + case Float64: + *(*float64)(v.value) = x + default: + panic(&ValueError{Method: "SetFloat", Kind: v.Kind()}) + } +} + +func (v Value) SetComplex(x complex128) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case Complex64: + *(*complex64)(v.value) = complex64(x) + case Complex128: + *(*complex128)(v.value) = x + default: + panic(&ValueError{Method: "SetComplex", Kind: v.Kind()}) + } +} + +func (v Value) SetString(x string) { + v.checkAddressable() + v.checkRO() + switch v.Kind() { + case String: + *(*string)(v.value) = x + default: + panic(&ValueError{Method: "SetString", Kind: v.Kind()}) + } +} + +func (v Value) SetBytes(x []byte) { + v.checkAddressable() + v.checkRO() + if v.typecode.Kind() != Slice || v.typecode.elem().Kind() != Uint8 { + panic("reflect.Value.SetBytes called on not []byte") + } + + // copy the header contents over + *(*[]byte)(v.value) = x +} + +func (v Value) SetCap(n int) { + panic("unimplemented: (reflect.Value).SetCap()") +} + +func (v Value) SetLen(n int) { + if v.typecode.Kind() != Slice { + panic(&ValueError{Method: "reflect.Value.SetLen", Kind: v.Kind()}) + } + v.checkAddressable() + hdr := (*sliceHeader)(v.value) + if int(uintptr(n)) != n || uintptr(n) > hdr.cap { + panic("reflect.Value.SetLen: slice length out of range") + } + hdr.len = uintptr(n) +} + +func (v Value) checkAddressable() { + if !v.isIndirect() { + panic("reflect: value is not addressable") + } +} + +// OverflowInt reports whether the int64 x cannot be represented by v's type. +// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. +func (v Value) OverflowInt(x int64) bool { + switch v.Kind() { + case Int, Int8, Int16, Int32, Int64: + bitSize := v.typecode.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic(&ValueError{Method: "reflect.Value.OverflowInt", Kind: v.Kind()}) +} + +// OverflowUint reports whether the uint64 x cannot be represented by v's type. +// It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. +func (v Value) OverflowUint(x uint64) bool { + k := v.Kind() + switch k { + case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: + bitSize := v.typecode.Size() * 8 + trunc := (x << (64 - bitSize)) >> (64 - bitSize) + return x != trunc + } + panic(&ValueError{Method: "reflect.Value.OverflowUint", Kind: v.Kind()}) +} + +func (v Value) CanConvert(t Type) bool { + // TODO: Optimize this to not actually perform a conversion + _, ok := convertOp(v, t) + return ok +} + +func (v Value) Convert(t Type) Value { + if v, ok := convertOp(v, t); ok { + return v + } + + panic("reflect.Value.Convert: value of type " + v.typecode.String() + " cannot be converted to type " + t.String()) +} + +func convertOp(src Value, typ Type) (Value, bool) { + + // Easy check first. Do we even need to do anything? + if src.typecode.underlying() == typ.(*RawType).underlying() { + return Value{ + typecode: typ.(*RawType), + value: src.value, + flags: src.flags, + }, 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() { + case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtInt(src, rtype), true + case Float32, Float64: + return cvtIntFloat(src, rtype), true + case String: + return cvtIntString(src, rtype), true + } + + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtUint(src, rtype), true + case Float32, Float64: + return cvtUintFloat(src, rtype), true + case String: + return cvtUintString(src, rtype), true + } + + case Float32, Float64: + switch rtype := typ.(*RawType); rtype.Kind() { + case Int, Int8, Int16, Int32, Int64: + return cvtFloatInt(src, rtype), true + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return cvtFloatUint(src, rtype), true + case Float32, Float64: + return cvtFloat(src, rtype), true + } + + /* + case Complex64, Complex128: + switch src.Kind() { + case Complex64, Complex128: + return cvtComplex + } + */ + + case Slice: + switch rtype := typ.(*RawType); rtype.Kind() { + case Array: + if src.typecode.elem() == rtype.elem() && rtype.Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags | valueFlagIndirect, + }, true + } + case Pointer: + if rtype.Elem().Kind() == Array { + if src.typecode.elem() == rtype.elem().elem() && rtype.elem().Len() <= src.Len() { + return Value{ + typecode: rtype, + value: (*sliceHeader)(src.value).data, + flags: src.flags & (valueFlagExported | valueFlagRO), + }, true + } + } + case String: + if !src.typecode.elem().isNamed() { + switch src.Type().Elem().Kind() { + case Uint8: + return cvtBytesString(src, rtype), true + case Int32: + return cvtRunesString(src, rtype), true + } + } + } + + case String: + rtype := typ.(*RawType) + if typ.Kind() == Slice && !rtype.elem().isNamed() { + switch typ.Elem().Kind() { + case Uint8: + return cvtStringBytes(src, rtype), true + case Int32: + return cvtStringRunes(src, rtype), true + } + } + } + + // TODO(dgryski): Unimplemented: + // Chan + // Non-defined pointers types with same underlying base type + // Interface <-> Type conversions + + return Value{}, false +} + +func cvtInt(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(v.Int()), t) +} + +func cvtUint(v Value, t *RawType) Value { + return makeInt(v.flags, v.Uint(), t) +} + +func cvtIntFloat(v Value, t *RawType) Value { + return makeFloat(v.flags, float64(v.Int()), t) +} + +func cvtUintFloat(v Value, t *RawType) Value { + return makeFloat(v.flags, float64(v.Uint()), t) +} + +func cvtFloatInt(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(int64(v.Float())), t) +} + +func cvtFloatUint(v Value, t *RawType) Value { + return makeInt(v.flags, uint64(v.Float()), t) +} + +func cvtFloat(v Value, t *RawType) Value { + if v.Type().Kind() == Float32 && t.Kind() == Float32 { + // Don't do any conversion if both types have underlying type float32. + // This avoids converting to float64 and back, which will + // convert a signaling NaN to a quiet NaN. See issue 36400. + return makeFloat32(v.flags, v.Float32(), t) + } + return makeFloat(v.flags, v.Float(), t) +} + +//go:linkname stringToBytes runtime.stringToBytes +func stringToBytes(x string) []byte + +func cvtStringBytes(v Value, t *RawType) Value { + b := stringToBytes(*(*string)(v.value)) + return Value{ + typecode: t, + value: unsafe.Pointer(&b), + flags: v.flags, + } +} + +//go:linkname stringFromBytes runtime.stringFromBytes +func stringFromBytes(x []byte) string + +func cvtBytesString(v Value, t *RawType) Value { + s := stringFromBytes(*(*[]byte)(v.value)) + return Value{ + typecode: t, + value: unsafe.Pointer(&s), + flags: v.flags, + } +} + +func makeInt(flags valueFlags, bits uint64, t *RawType) Value { + size := t.Size() + + v := Value{ + typecode: t, + flags: flags, + } + + ptr := unsafe.Pointer(&v.value) + if size > unsafe.Sizeof(uintptr(0)) { + ptr = alloc(size, nil) + v.value = ptr + } + + switch size { + case 1: + *(*uint8)(ptr) = uint8(bits) + case 2: + *(*uint16)(ptr) = uint16(bits) + case 4: + *(*uint32)(ptr) = uint32(bits) + case 8: + *(*uint64)(ptr) = bits + } + return v +} + +func makeFloat(flags valueFlags, f float64, t *RawType) Value { + size := t.Size() + + v := Value{ + typecode: t, + flags: flags, + } + + ptr := unsafe.Pointer(&v.value) + if size > unsafe.Sizeof(uintptr(0)) { + ptr = alloc(size, nil) + v.value = ptr + } + + switch size { + case 4: + *(*float32)(ptr) = float32(f) + case 8: + *(*float64)(ptr) = f + } + return v +} + +func makeFloat32(flags valueFlags, f float32, t *RawType) Value { + v := Value{ + typecode: t, + flags: flags, + } + *(*float32)(unsafe.Pointer(&v.value)) = float32(f) + return v +} + +func cvtIntString(src Value, t *RawType) Value { + panic("cvtUintString: unimplemented") +} + +func cvtUintString(src Value, t *RawType) Value { + panic("cvtUintString: unimplemented") +} + +func cvtStringRunes(src Value, t *RawType) Value { + panic("cvsStringRunes: unimplemented") +} + +func cvtRunesString(src Value, t *RawType) Value { + panic("cvsRunesString: unimplemented") +} + +//go:linkname slicePanic runtime.slicePanic +func slicePanic() + +func MakeSlice(typ Type, len, cap int) Value { + if typ.Kind() != Slice { + panic("reflect.MakeSlice of non-slice type") + } + + rtype := typ.(*RawType) + + ulen := uint(len) + ucap := uint(cap) + maxSize := (^uintptr(0)) / 2 + elem := rtype.elem() + elementSize := elem.Size() + if elementSize > 1 { + maxSize /= uintptr(elementSize) + } + if ulen > ucap || ucap > uint(maxSize) { + slicePanic() + } + + // This can't overflow because of the above checks. + size := uintptr(ucap) * elementSize + + var slice sliceHeader + slice.cap = uintptr(ucap) + slice.len = uintptr(ulen) + layout := elem.gcLayout() + + slice.data = alloc(size, layout) + + return Value{ + typecode: rtype, + value: unsafe.Pointer(&slice), + flags: valueFlagExported, + } +} + +var zerobuffer unsafe.Pointer + +const zerobufferLen = 32 + +func init() { + // 32 characters of zero bytes + zerobufferStr := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + s := (*stringHeader)(unsafe.Pointer(&zerobufferStr)) + zerobuffer = s.data +} + +func Zero(typ Type) Value { + size := typ.Size() + if size <= unsafe.Sizeof(uintptr(0)) { + return Value{ + typecode: typ.(*RawType), + value: nil, + flags: valueFlagExported | valueFlagRO, + } + } + + if size <= zerobufferLen { + return Value{ + typecode: typ.(*RawType), + value: unsafe.Pointer(zerobuffer), + flags: valueFlagExported | valueFlagRO, + } + } + + return Value{ + typecode: typ.(*RawType), + value: alloc(size, nil), + flags: valueFlagExported | valueFlagRO, + } +} + +// New is the reflect equivalent of the new(T) keyword, returning a pointer to a +// new value of the given type. +func New(typ Type) Value { + return Value{ + typecode: pointerTo(typ.(*RawType)), + value: alloc(typ.Size(), nil), + flags: valueFlagExported, + } +} + +type funcHeader struct { + Context unsafe.Pointer + Code unsafe.Pointer +} + +// Slice header that matches the underlying structure. Used for when we switch +// to a precise GC, which needs to know exactly where pointers live. +type sliceHeader struct { + data unsafe.Pointer + len uintptr + cap uintptr +} + +// Like sliceHeader, this type is used internally to make sure pointer and +// non-pointer fields match those of actual strings. +type stringHeader struct { + data unsafe.Pointer + len uintptr +} + +// Verify SliceHeader and StringHeader sizes. +// See https://github.com/tinygo-org/tinygo/pull/4156 +// and https://github.com/tinygo-org/tinygo/issues/1284. +var ( + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(sliceHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} +) + +type ValueError struct { + Method string + Kind Kind +} + +func (e *ValueError) Error() string { + if e.Kind == 0 { + return "reflect: call of " + e.Method + " on zero Value" + } + return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" +} + +//go:linkname memcpy runtime.memcpy +func memcpy(dst, src unsafe.Pointer, size uintptr) + +//go:linkname memzero runtime.memzero +func memzero(ptr unsafe.Pointer, size uintptr) + +//go:linkname alloc runtime.alloc +func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer + +//go:linkname sliceAppend runtime.sliceAppend +func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) + +//go:linkname sliceCopy runtime.sliceCopy +func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int + +// Copy copies the contents of src into dst until either +// dst has been filled or src has been exhausted. +func Copy(dst, src Value) int { + compatibleTypes := false || + // dst and src are both slices or arrays with equal types + ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && + (src.typecode.Kind() == Slice || src.typecode.Kind() == Array) && + (dst.typecode.elem() == src.typecode.elem())) || + // dst is array or slice of uint8 and src is string + ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && + dst.typecode.elem().Kind() == Uint8 && + src.typecode.Kind() == String) + + if !compatibleTypes { + panic("Copy: type mismatch: " + dst.typecode.String() + "/" + src.typecode.String()) + } + + // Can read from an unaddressable array but not write to one. + if dst.typecode.Kind() == Array && !dst.isIndirect() { + panic("reflect.Copy: unaddressable array value") + } + + dstbuf, dstlen := buflen(dst) + srcbuf, srclen := buflen(src) + + if srclen > 0 { + dst.checkRO() + } + + return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) +} + +func buflen(v Value) (unsafe.Pointer, uintptr) { + var buf unsafe.Pointer + var len uintptr + switch v.typecode.Kind() { + case Slice: + hdr := (*sliceHeader)(v.value) + buf = hdr.data + len = hdr.len + case Array: + if v.isIndirect() || v.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + buf = v.value + } else { + buf = unsafe.Pointer(&v.value) + } + len = uintptr(v.Len()) + case String: + hdr := (*stringHeader)(v.value) + buf = hdr.data + len = hdr.len + default: + // This shouldn't happen + panic("reflect.Copy: not slice or array or string") + } + + return buf, len +} + +//go:linkname sliceGrow runtime.sliceGrow +func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) + +// extend slice to hold n new elements +func extendSlice(v Value, n int) sliceHeader { + if v.Kind() != Slice { + panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) + } + + var old sliceHeader + if v.value != nil { + old = *(*sliceHeader)(v.value) + } + + nbuf, nlen, ncap := sliceGrow(old.data, old.len, old.cap, old.len+uintptr(n), v.typecode.elem().Size()) + + return sliceHeader{ + data: nbuf, + len: nlen + uintptr(n), + cap: ncap, + } +} + +// Append appends the values x to a slice s and returns the resulting slice. +// As in Go, each x's value must be assignable to the slice's element type. +func Append(v Value, x ...Value) Value { + if v.Kind() != Slice { + panic(&ValueError{Method: "Append", Kind: v.Kind()}) + } + oldLen := v.Len() + newslice := extendSlice(v, len(x)) + v.flags = valueFlagExported + v.value = (unsafe.Pointer)(&newslice) + for i, xx := range x { + v.Index(oldLen + i).Set(xx) + } + return v +} + +// AppendSlice appends a slice t to a slice s and returns the resulting slice. +// The slices s and t must have the same element type. +func AppendSlice(s, t Value) Value { + if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { + // Not a very helpful error message, but shortened to just one error to + // keep code size down. + panic("reflect.AppendSlice: invalid types") + } + if !s.isExported() || !t.isExported() { + // One of the sides was not exported, so can't access the data. + panic("reflect.AppendSlice: unexported") + } + sSlice := (*sliceHeader)(s.value) + tSlice := (*sliceHeader)(t.value) + elemSize := s.typecode.elem().Size() + ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) + result := &sliceHeader{ + data: ptr, + len: len, + cap: cap, + } + return Value{ + typecode: s.typecode, + value: unsafe.Pointer(result), + flags: valueFlagExported, + } +} + +// Grow increases the slice's capacity, if necessary, to guarantee space for +// another n elements. After Grow(n), at least n elements can be appended +// to the slice without another allocation. +// +// It panics if v's Kind is not a Slice or if n is negative or too large to +// allocate the memory. +func (v Value) Grow(n int) { + v.checkAddressable() + if n < 0 { + panic("reflect.Grow: negative length") + } + if v.Kind() != Slice { + panic(&ValueError{Method: "Grow", Kind: v.Kind()}) + } + slice := (*sliceHeader)(v.value) + newslice := extendSlice(v, n) + // Only copy the new data and cap: the len remains unchanged. + slice.data = newslice.data + slice.cap = newslice.cap +} + +//go:linkname hashmapStringSet runtime.hashmapStringSet +func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) + +//go:linkname hashmapBinarySet runtime.hashmapBinarySet +func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) + +//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSet +func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) + +//go:linkname hashmapStringDelete runtime.hashmapStringDelete +func hashmapStringDelete(m unsafe.Pointer, key string) + +//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDelete +func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) + +//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDelete +func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) + +func (v Value) SetMapIndex(key, elem Value) { + v.checkRO() + if v.Kind() != Map { + panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()}) + } + + vkey := v.typecode.key() + + // compare key type with actual key type of map + if !key.typecode.AssignableTo(vkey) { + panic("reflect.Value.SetMapIndex: incompatible types for key") + } + + // if elem is the zero Value, it means delete + del := elem == Value{} + + if !del && !elem.typecode.AssignableTo(v.typecode.elem()) { + panic("reflect.Value.SetMapIndex: incompatible types for value") + } + + // make elem an interface if it needs to be converted + if v.typecode.elem().Kind() == Interface && elem.typecode.Kind() != Interface { + intf := composeInterface(unsafe.Pointer(elem.typecode), elem.value) + elem = Value{ + typecode: v.typecode.elem(), + value: unsafe.Pointer(&intf), + } + } + + if key.Kind() == String { + if del { + hashmapStringDelete(v.pointer(), *(*string)(key.value)) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr) + } + + } else if key.typecode.isBinary() { + var keyptr unsafe.Pointer + if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + keyptr = key.value + } else { + keyptr = unsafe.Pointer(&key.value) + } + + if del { + hashmapBinaryDelete(v.pointer(), keyptr) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + hashmapBinarySet(v.pointer(), keyptr, elemptr) + } + } else { + if del { + hashmapInterfaceDelete(v.pointer(), key.Interface()) + } else { + var elemptr unsafe.Pointer + if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { + elemptr = elem.value + } else { + elemptr = unsafe.Pointer(&elem.value) + } + + hashmapInterfaceSet(v.pointer(), key.Interface(), elemptr) + } + } +} + +// FieldByIndex returns the nested field corresponding to index. +func (v Value) FieldByIndex(index []int) Value { + if len(index) == 1 { + return v.Field(index[0]) + } + if v.Kind() != Struct { + panic(&ValueError{"FieldByIndex", v.Kind()}) + } + for i, x := range index { + if i > 0 { + if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct { + if v.IsNil() { + panic("reflect: indirection through nil pointer to embedded struct") + } + v = v.Elem() + } + } + v = v.Field(x) + } + return v +} + +// FieldByIndexErr returns the nested field corresponding to index. +func (v Value) FieldByIndexErr(index []int) (Value, error) { + return Value{}, &ValueError{Method: "FieldByIndexErr"} +} + +func (v Value) FieldByName(name string) Value { + if v.Kind() != Struct { + panic(&ValueError{"FieldByName", v.Kind()}) + } + + if field, ok := v.typecode.FieldByName(name); ok { + return v.FieldByIndex(field.Index) + } + return Value{} +} + +func (v Value) FieldByNameFunc(match func(string) bool) Value { + if v.Kind() != Struct { + panic(&ValueError{"FieldByName", v.Kind()}) + } + + if field, ok := v.typecode.FieldByNameFunc(match); ok { + return v.FieldByIndex(field.Index) + } + return Value{} +} + +//go:linkname hashmapMake runtime.hashmapMake +func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer + +// MakeMapWithSize creates a new map with the specified type and initial space +// for approximately n elements. +func MakeMapWithSize(typ Type, n int) Value { + + // TODO(dgryski): deduplicate these? runtime and reflect both need them. + const ( + hashmapAlgorithmBinary uint8 = iota + hashmapAlgorithmString + hashmapAlgorithmInterface + ) + + if typ.Kind() != Map { + panic(&ValueError{Method: "MakeMap", Kind: typ.Kind()}) + } + + if n < 0 { + panic("reflect.MakeMapWithSize: negative size hint") + } + + key := typ.Key().(*RawType) + val := typ.Elem().(*RawType) + + var alg uint8 + + if key.Kind() == String { + alg = hashmapAlgorithmString + } else if key.isBinary() { + alg = hashmapAlgorithmBinary + } else { + alg = hashmapAlgorithmInterface + } + + m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg) + + return Value{ + typecode: typ.(*RawType), + value: m, + flags: valueFlagExported, + } +} + +// MakeMap creates a new map with the specified type. +func MakeMap(typ Type) Value { + return MakeMapWithSize(typ, 8) +} + +func (v Value) Call(in []Value) []Value { + panic("unimplemented: (reflect.Value).Call()") +} + +func (v Value) CallSlice(in []Value) []Value { + panic("unimplemented: (reflect.Value).CallSlice()") +} + +func (v Value) Method(i int) Value { + panic("unimplemented: (reflect.Value).Method()") +} + +func (v Value) MethodByName(name string) Value { + panic("unimplemented: (reflect.Value).MethodByName()") +} + +func (v Value) Recv() (x Value, ok bool) { + panic("unimplemented: (reflect.Value).Recv()") +} + +func NewAt(typ Type, p unsafe.Pointer) Value { + panic("unimplemented: reflect.New()") +} diff --git a/src/internal/reflectlite/visiblefields.go b/src/internal/reflectlite/visiblefields.go new file mode 100644 index 0000000000..b21af6178f --- /dev/null +++ b/src/internal/reflectlite/visiblefields.go @@ -0,0 +1,105 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflectlite + +// VisibleFields returns all the visible fields in t, which must be a +// struct type. A field is defined as visible if it's accessible +// directly with a FieldByName call. The returned fields include fields +// inside anonymous struct members and unexported fields. They follow +// the same order found in the struct, with anonymous fields followed +// immediately by their promoted fields. +// +// For each element e of the returned slice, the corresponding field +// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index). +func VisibleFields(t Type) []StructField { + if t == nil { + panic("reflect: VisibleFields(nil)") + } + if t.Kind() != Struct { + panic("reflect.VisibleFields of non-struct type") + } + w := &visibleFieldsWalker{ + byName: make(map[string]int), + visiting: make(map[Type]bool), + fields: make([]StructField, 0, t.NumField()), + index: make([]int, 0, 2), + } + w.walk(t) + // Remove all the fields that have been hidden. + // Use an in-place removal that avoids copying in + // the common case that there are no hidden fields. + j := 0 + for i := range w.fields { + f := &w.fields[i] + if f.Name == "" { + continue + } + if i != j { + // A field has been removed. We need to shuffle + // all the subsequent elements up. + w.fields[j] = *f + } + j++ + } + return w.fields[:j] +} + +type visibleFieldsWalker struct { + byName map[string]int + visiting map[Type]bool + fields []StructField + index []int +} + +// walk walks all the fields in the struct type t, visiting +// fields in index preorder and appending them to w.fields +// (this maintains the required ordering). +// Fields that have been overridden have their +// Name field cleared. +func (w *visibleFieldsWalker) walk(t Type) { + if w.visiting[t] { + return + } + w.visiting[t] = true + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + w.index = append(w.index, i) + add := true + if oldIndex, ok := w.byName[f.Name]; ok { + old := &w.fields[oldIndex] + if len(w.index) == len(old.Index) { + // Fields with the same name at the same depth + // cancel one another out. Set the field name + // to empty to signify that has happened, and + // there's no need to add this field. + old.Name = "" + add = false + } else if len(w.index) < len(old.Index) { + // The old field loses because it's deeper than the new one. + old.Name = "" + } else { + // The old field wins because it's shallower than the new one. + add = false + } + } + if add { + // Copy the index so that it's not overwritten + // by the other appends. + f.Index = append([]int(nil), w.index...) + w.byName[f.Name] = len(w.fields) + w.fields = append(w.fields, f) + } + if f.Anonymous { + if f.Type.Kind() == Pointer { + f.Type = f.Type.Elem() + } + if f.Type.Kind() == Struct { + w.walk(f.Type) + } + } + w.index = w.index[:len(w.index)-1] + } + delete(w.visiting, t) +} diff --git a/src/internal/syscall/unix/constants.go b/src/internal/syscall/unix/constants.go new file mode 100644 index 0000000000..46fc1d0598 --- /dev/null +++ b/src/internal/syscall/unix/constants.go @@ -0,0 +1,7 @@ +package unix + +const ( + R_OK = 0x4 + W_OK = 0x2 + X_OK = 0x1 +) diff --git a/src/internal/syscall/unix/eaccess.go b/src/internal/syscall/unix/eaccess.go new file mode 100644 index 0000000000..53f105f065 --- /dev/null +++ b/src/internal/syscall/unix/eaccess.go @@ -0,0 +1,10 @@ +package unix + +import "syscall" + +func Eaccess(path string, mode uint32) error { + // We don't support this syscall on baremetal or wasm. + // Callers are generally able to deal with this since unix.Eaccess also + // isn't available on Android. + return syscall.ENOSYS +} diff --git a/src/internal/syscall/unix/getrandom.go b/src/internal/syscall/unix/getrandom.go new file mode 100644 index 0000000000..7ffab77e69 --- /dev/null +++ b/src/internal/syscall/unix/getrandom.go @@ -0,0 +1,12 @@ +package unix + +type GetRandomFlag uintptr + +const ( + GRND_NONBLOCK GetRandomFlag = 0x0001 + GRND_RANDOM GetRandomFlag = 0x0002 +) + +func GetRandom(p []byte, flags GetRandomFlag) (n int, err error) { + panic("todo: unix.GetRandom") +} diff --git a/src/internal/task/atomic-cooperative.go b/src/internal/task/atomic-cooperative.go new file mode 100644 index 0000000000..60eb917a8e --- /dev/null +++ b/src/internal/task/atomic-cooperative.go @@ -0,0 +1,41 @@ +package task + +// Atomics implementation for cooperative systems. The atomic types here aren't +// actually atomic, they assume that accesses cannot be interrupted by a +// different goroutine or interrupt happening at the same time. + +type atomicIntegerType interface { + uintptr | uint32 | uint64 +} + +type pseudoAtomic[T atomicIntegerType] struct { + v T +} + +func (x *pseudoAtomic[T]) Add(delta T) T { x.v += delta; return x.v } +func (x *pseudoAtomic[T]) Load() T { return x.v } +func (x *pseudoAtomic[T]) Store(val T) { x.v = val } +func (x *pseudoAtomic[T]) CompareAndSwap(old, new T) (swapped bool) { + if x.v != old { + return false + } + x.v = new + return true +} +func (x *pseudoAtomic[T]) Swap(new T) (old T) { + old = x.v + x.v = new + return +} + +// Uintptr is an atomic uintptr when multithreading is enabled, and a plain old +// uintptr otherwise. +type Uintptr = pseudoAtomic[uintptr] + +// Uint32 is an atomic uint32 when multithreading is enabled, and a plain old +// uint32 otherwise. +type Uint32 = pseudoAtomic[uint32] + +// Uint64 is an atomic uint64 when multithreading is enabled, and a plain old +// uint64 otherwise. +type Uint64 = pseudoAtomic[uint64] diff --git a/src/internal/task/futex-cooperative.go b/src/internal/task/futex-cooperative.go new file mode 100644 index 0000000000..8351f88774 --- /dev/null +++ b/src/internal/task/futex-cooperative.go @@ -0,0 +1,44 @@ +package task + +// A futex is a way for userspace to wait with the pointer as the key, and for +// another thread to wake one or all waiting threads keyed on the same pointer. +// +// A futex does not change the underlying value, it only reads it before to prevent +// lost wake-ups. +type Futex struct { + Uint32 + waiters Stack +} + +// Atomically check for cmp to still be equal to the futex value and if so, go +// to sleep. Return true if we were definitely awoken by a call to Wake or +// WakeAll, and false if we can't be sure of that. +func (f *Futex) Wait(cmp uint32) (awoken bool) { + if f.Uint32.v != cmp { + return false + } + + // Push the current goroutine onto the waiter stack. + f.waiters.Push(Current()) + + // Pause until the waiters are awoken by Wake/WakeAll. + Pause() + + // We were awoken by a call to Wake or WakeAll. There is no chance for + // spurious wakeups. + return true +} + +// Wake a single waiter. +func (f *Futex) Wake() { + if t := f.waiters.Pop(); t != nil { + scheduleTask(t) + } +} + +// Wake all waiters. +func (f *Futex) WakeAll() { + for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() { + scheduleTask(t) + } +} diff --git a/src/internal/task/mutex-cooperative.go b/src/internal/task/mutex-cooperative.go new file mode 100644 index 0000000000..e40966bed4 --- /dev/null +++ b/src/internal/task/mutex-cooperative.go @@ -0,0 +1,43 @@ +package task + +type Mutex struct { + locked bool + blocked Stack +} + +func (m *Mutex) Lock() { + if m.locked { + // Push self onto stack of blocked tasks, and wait to be resumed. + m.blocked.Push(Current()) + Pause() + return + } + + m.locked = true +} + +func (m *Mutex) Unlock() { + if !m.locked { + panic("sync: unlock of unlocked Mutex") + } + + // Wake up a blocked task, if applicable. + if t := m.blocked.Pop(); t != nil { + scheduleTask(t) + } else { + m.locked = false + } +} + +// TryLock tries to lock m and reports whether it succeeded. +// +// Note that while correct uses of TryLock do exist, they are rare, +// and use of TryLock is often a sign of a deeper problem +// in a particular use of mutexes. +func (m *Mutex) TryLock() bool { + if m.locked { + return false + } + m.Lock() + return true +} diff --git a/src/internal/task/pmutex-cooperative.go b/src/internal/task/pmutex-cooperative.go new file mode 100644 index 0000000000..ae2aa4bad8 --- /dev/null +++ b/src/internal/task/pmutex-cooperative.go @@ -0,0 +1,16 @@ +package task + +// PMutex is a real mutex on systems that can be either preemptive or threaded, +// and a dummy lock on other (purely cooperative) systems. +// +// It is mainly useful for short operations that need a lock when threading may +// be involved, but which do not need a lock with a purely cooperative +// scheduler. +type PMutex struct { +} + +func (m *PMutex) Lock() { +} + +func (m *PMutex) Unlock() { +} diff --git a/src/internal/task/task.go b/src/internal/task/task.go index c457390977..58c02fe846 100644 --- a/src/internal/task/task.go +++ b/src/internal/task/task.go @@ -21,12 +21,38 @@ type Task struct { // state is the underlying running state of the task. state state + // This is needed for some crypto packages. + FipsIndicator uint8 + // DeferFrame stores a pointer to the (stack allocated) defer frame of the // goroutine that is used for the recover builtin. DeferFrame unsafe.Pointer } +// DataUint32 returns the Data field as a uint32. The value is only valid after +// setting it through SetDataUint32 or by storing to it using DataAtomicUint32. +func (t *Task) DataUint32() uint32 { + return *(*uint32)(unsafe.Pointer(&t.Data)) +} + +// SetDataUint32 updates the uint32 portion of the Data field (which could be +// the first 4 or last 4 bytes depending on the architecture endianness). +func (t *Task) SetDataUint32(val uint32) { + *(*uint32)(unsafe.Pointer(&t.Data)) = val +} + +// DataAtomicUint32 returns the Data field as an atomic-if-needed Uint32 value. +func (t *Task) DataAtomicUint32() *Uint32 { + return (*Uint32)(unsafe.Pointer(&t.Data)) +} + // getGoroutineStackSize is a compiler intrinsic that returns the stack size for // the given function and falls back to the default stack size. It is replaced // with a load from a special section just before codegen. func getGoroutineStackSize(fn uintptr) uintptr + +//go:linkname runtime_alloc runtime.alloc +func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer + +//go:linkname scheduleTask runtime.scheduleTask +func scheduleTask(*Task) diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index b16ddd7d83..637a6b2237 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -35,11 +35,16 @@ type state struct { type stackState struct { // asyncify is the stack pointer of the asyncify stack. // This starts from the bottom and grows upwards. - asyncifysp uintptr + asyncifysp unsafe.Pointer // asyncify is stack pointer of the C stack. // This starts from the top and grows downwards. - csp uintptr + csp unsafe.Pointer + + // Pointer to the first (lowest address) of the stack. It must never be + // overwritten. It can be checked from time to time to see whether a stack + // overflow happened in the past. + canaryPtr *uintptr } // start creates and starts a new goroutine with the given function and arguments. @@ -47,7 +52,7 @@ type stackState struct { func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.initialize(fn, args, stackSize) - runqueuePushBack(t) + scheduleTask(t) } //export tinygo_launch @@ -63,17 +68,20 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { s.args = args // Create a stack. - stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0))) + stack := runtime_alloc(stackSize, nil) + + // Set up the stack canary, a random number that should be checked when + // switching from the task back to the scheduler. The stack canary pointer + // points to the first word of the stack. If it has changed between now and + // the next stack switch, there was a stack overflow. + s.canaryPtr = (*uintptr)(stack) + *s.canaryPtr = stackCanary // Calculate stack base addresses. - s.asyncifysp = uintptr(unsafe.Pointer(&stack[0])) - s.csp = uintptr(unsafe.Pointer(&stack[0])) + uintptr(len(stack))*unsafe.Sizeof(uintptr(0)) - stack[0] = stackCanary + s.asyncifysp = unsafe.Add(stack, unsafe.Sizeof(uintptr(0))) + s.csp = unsafe.Add(stack, stackSize) } -//go:linkname runqueuePushBack runtime.runqueuePushBack -func runqueuePushBack(*Task) - // currentTask is the current running task, or nil if currently in the scheduler. var currentTask *Task @@ -85,14 +93,11 @@ func Current() *Task { // Pause suspends the current task and returns to the scheduler. // This function may only be called when running on a goroutine stack, not when running on the system stack. func Pause() { - // This is mildly unsafe but this is also the only place we can do this. - if *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) != stackCanary { + if *currentTask.state.canaryPtr != stackCanary { runtimePanic("stack overflow") } currentTask.state.unwind() - - *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) = stackCanary } //export tinygo_unwind @@ -113,7 +118,7 @@ func (t *Task) Resume() { } currentTask = prevTask t.gcData.swap() - if t.state.asyncifysp > t.state.csp { + if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) { runtimePanic("stack overflow") } } diff --git a/src/internal/task/task_stack.go b/src/internal/task/task_stack.go index 81e0f9ad76..88a0970685 100644 --- a/src/internal/task/task_stack.go +++ b/src/internal/task/task_stack.go @@ -44,7 +44,7 @@ func Current() *Task { // This function may only be called when running on a goroutine stack, not when running on the system stack or in an interrupt. func Pause() { // Check whether the canary (the lowest address of the stack) is still - // valid. If it is not, a stack overflow has occured. + // valid. If it is not, a stack overflow has occurred. if *currentTask.state.canaryPtr != stackCanary { runtimePanic("goroutine stack overflow") } @@ -101,18 +101,12 @@ func swapTask(oldStack uintptr, newStack *uintptr) //go:extern tinygo_startTask var startTask [0]uint8 -//go:linkname runqueuePushBack runtime.runqueuePushBack -func runqueuePushBack(*Task) - -//go:linkname runtime_alloc runtime.alloc -func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer - // start creates and starts a new goroutine with the given function and arguments. // The new goroutine is scheduled to run later. func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) { t := &Task{} t.state.initialize(fn, args, stackSize) - runqueuePushBack(t) + scheduleTask(t) } // OnSystemStack returns whether the caller is running on the system stack. diff --git a/src/internal/task/task_stack_avr.S b/src/internal/task/task_stack_avr.S index 2e68b17545..d8aed8b96b 100644 --- a/src/internal/task/task_stack_avr.S +++ b/src/internal/task/task_stack_avr.S @@ -28,7 +28,7 @@ tinygo_startTask: // After return, exit this goroutine. This is a tail call. #if __AVR_ARCH__ == 2 || __AVR_ARCH__ == 25 // Small memory devices (≤8kB flash) that do not have the long call - // instruction availble will need to use rcall instead. + // instruction available will need to use rcall instead. // Note that they will probably not be able to run more than the main // goroutine anyway, but this file is compiled for all AVRs so it needs to // compile at least. diff --git a/src/internal/task/task_stack_esp32.S b/src/internal/task/task_stack_esp32.S index c2e4acc2c9..fe0afe98d6 100644 --- a/src/internal/task/task_stack_esp32.S +++ b/src/internal/task/task_stack_esp32.S @@ -78,7 +78,7 @@ tinygo_swapTask: // Switch to the new stack pointer (newStack). mov.n sp, a2 - // Load a0, which is the previous return addres from before the previous + // Load a0, which is the previous return address from before the previous // switch or the constructed return address to tinygo_startTask. This // register also stores the parent register window. l32i.n a0, sp, 0 diff --git a/src/internal/task/task_stack_mipsx.S b/src/internal/task/task_stack_mipsx.S new file mode 100644 index 0000000000..018c63d935 --- /dev/null +++ b/src/internal/task/task_stack_mipsx.S @@ -0,0 +1,70 @@ +// Do not reorder instructions to insert a branch delay slot. +// We know what we're doing, and will manually fill the branch delay slot. +.set noreorder + +.section .text.tinygo_startTask +.global tinygo_startTask +.type tinygo_startTask, %function +tinygo_startTask: + // Small assembly stub for starting a goroutine. This is already run on the + // new stack, with the callee-saved registers already loaded. + // Most importantly, s0 contains the pc of the to-be-started function and s1 + // contains the only argument it is given. Multiple arguments are packed + // into one by storing them in a new allocation. + + // Set the first argument of the goroutine start wrapper, which contains all + // the arguments. + move $a0, $s1 + + // Branch to the "goroutine start" function. Use jalr to write the return + // address to ra so we'll return here after the goroutine exits. + jalr $s0 + nop + + // After return, exit this goroutine. This is a tail call. + j tinygo_pause + nop + +.section .text.tinygo_swapTask +.global tinygo_swapTask +.type tinygo_swapTask, %function +tinygo_swapTask: + // This function gets the following parameters: + // a0 = newStack uintptr + // a1 = oldStack *uintptr + + // Push all callee-saved registers. + addiu $sp, $sp, -40 + sw $ra, 36($sp) + sw $s8, 32($sp) + sw $s7, 28($sp) + sw $s6, 24($sp) + sw $s5, 20($sp) + sw $s4, 16($sp) + sw $s3, 12($sp) + sw $s2, 8($sp) + sw $s1, 4($sp) + sw $s0, ($sp) + + // Save the current stack pointer in oldStack. + sw $sp, 0($a1) + + // Switch to the new stack pointer. + move $sp, $a0 + + // Pop all saved registers from this new stack. + lw $ra, 36($sp) + lw $s8, 32($sp) + lw $s7, 28($sp) + lw $s6, 24($sp) + lw $s5, 20($sp) + lw $s4, 16($sp) + lw $s3, 12($sp) + lw $s2, 8($sp) + lw $s1, 4($sp) + lw $s0, ($sp) + addiu $sp, $sp, 40 + + // Return into the task. + jalr $ra + nop diff --git a/src/internal/task/task_stack_mipsx.go b/src/internal/task/task_stack_mipsx.go new file mode 100644 index 0000000000..2a7fb5cbf0 --- /dev/null +++ b/src/internal/task/task_stack_mipsx.go @@ -0,0 +1,61 @@ +//go:build scheduler.tasks && (mips || mipsle) + +package task + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see task_stack_mips.S that relies on the exact +// layout of this struct. +type calleeSavedRegs struct { + s0 uintptr + s1 uintptr + s2 uintptr + s3 uintptr + s4 uintptr + s5 uintptr + s6 uintptr + s7 uintptr + s8 uintptr + ra uintptr +} + +// archInit runs architecture-specific setup for the goroutine startup. +func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { + // Store the initial sp for the startTask function (implemented in assembly). + s.sp = uintptr(unsafe.Pointer(r)) + + // Initialize the registers. + // These will be popped off of the stack on the first resume of the goroutine. + + // Start the function at tinygo_startTask (defined in src/internal/task/task_stack_mipsle.S). + // This assembly code calls a function (passed in s0) with a single argument + // (passed in s1). After the function returns, it calls Pause(). + r.ra = uintptr(unsafe.Pointer(&startTask)) + + // Pass the function to call in s0. + // This function is a compiler-generated wrapper which loads arguments out of a struct pointer. + // See createGoroutineStartWrapper (defined in compiler/goroutine.go) for more information. + r.s0 = fn + + // Pass the pointer to the arguments struct in s1. + r.s1 = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +// SystemStack returns the system stack pointer when called from a task stack. +// When called from the system stack, it returns 0. +func SystemStack() uintptr { + return systemStack +} diff --git a/src/internal/wasi/cli/v0.2.0/command/command.wit.go b/src/internal/wasi/cli/v0.2.0/command/command.wit.go new file mode 100644 index 0000000000..cdd985d607 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/command/command.wit.go @@ -0,0 +1,4 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package command represents the world "wasi:cli/command@0.2.0". +package command diff --git a/src/internal/wasi/cli/v0.2.0/environment/empty.s b/src/internal/wasi/cli/v0.2.0/environment/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/environment/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go new file mode 100644 index 0000000000..07d9509af7 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/environment/environment.wasm.go @@ -0,0 +1,21 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package environment + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/environment@0.2.0 get-environment +//go:noescape +func wasmimport_GetEnvironment(result *cm.List[[2]string]) + +//go:wasmimport wasi:cli/environment@0.2.0 get-arguments +//go:noescape +func wasmimport_GetArguments(result *cm.List[string]) + +//go:wasmimport wasi:cli/environment@0.2.0 initial-cwd +//go:noescape +func wasmimport_InitialCWD(result *cm.Option[string]) diff --git a/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go new file mode 100644 index 0000000000..0b84245156 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/environment/environment.wit.go @@ -0,0 +1,52 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package environment represents the imported interface "wasi:cli/environment@0.2.0". +package environment + +import ( + "internal/cm" +) + +// GetEnvironment represents the imported function "get-environment". +// +// Get the POSIX-style environment variables. +// +// Each environment variable is provided as a pair of string variable names +// and string value. +// +// Morally, these are a value import, but until value imports are available +// in the component model, this import function should return the same +// values each time it is called. +// +// get-environment: func() -> list> +// +//go:nosplit +func GetEnvironment() (result cm.List[[2]string]) { + wasmimport_GetEnvironment(&result) + return +} + +// GetArguments represents the imported function "get-arguments". +// +// Get the POSIX-style arguments to the program. +// +// get-arguments: func() -> list +// +//go:nosplit +func GetArguments() (result cm.List[string]) { + wasmimport_GetArguments(&result) + return +} + +// InitialCWD represents the imported function "initial-cwd". +// +// Return a path that programs should use as their initial current working +// directory, interpreting `.` as shorthand for this. +// +// initial-cwd: func() -> option +// +//go:nosplit +func InitialCWD() (result cm.Option[string]) { + wasmimport_InitialCWD(&result) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/exit/empty.s b/src/internal/wasi/cli/v0.2.0/exit/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/exit/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go new file mode 100644 index 0000000000..849d5f503f --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package exit + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/exit@0.2.0 exit +//go:noescape +func wasmimport_Exit(status0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go new file mode 100644 index 0000000000..caeeb269db --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/exit/exit.wit.go @@ -0,0 +1,21 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package exit represents the imported interface "wasi:cli/exit@0.2.0". +package exit + +import ( + "internal/cm" +) + +// Exit represents the imported function "exit". +// +// Exit the current instance and any linked instances. +// +// exit: func(status: result) +// +//go:nosplit +func Exit(status cm.BoolResult) { + status0 := (uint32)(cm.BoolToU32(status)) + wasmimport_Exit((uint32)(status0)) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/run/empty.s b/src/internal/wasi/cli/v0.2.0/run/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/run/run.exports.go b/src/internal/wasi/cli/v0.2.0/run/run.exports.go new file mode 100644 index 0000000000..ae848313f5 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/run.exports.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package run + +import ( + "internal/cm" +) + +// Exports represents the caller-defined exports from "wasi:cli/run@0.2.0". +var Exports struct { + // Run represents the caller-defined, exported function "run". + // + // Run the program. + // + // run: func() -> result + Run func() (result cm.BoolResult) +} diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wasm.go b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go new file mode 100644 index 0000000000..12a14e763e --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/run.wasm.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package run + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmexport wasi:cli/run@0.2.0#run +//export wasi:cli/run@0.2.0#run +func wasmexport_Run() (result0 uint32) { + result := Exports.Run() + result0 = (uint32)(cm.BoolToU32(result)) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/run/run.wit.go b/src/internal/wasi/cli/v0.2.0/run/run.wit.go new file mode 100644 index 0000000000..4cea75d300 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/run/run.wit.go @@ -0,0 +1,4 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package run represents the exported interface "wasi:cli/run@0.2.0". +package run diff --git a/src/internal/wasi/cli/v0.2.0/stderr/empty.s b/src/internal/wasi/cli/v0.2.0/stderr/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stderr/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go new file mode 100644 index 0000000000..462cf172ca --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package stderr + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/stderr@0.2.0 get-stderr +//go:noescape +func wasmimport_GetStderr() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go new file mode 100644 index 0000000000..8ebfdeed5f --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stderr/stderr.wit.go @@ -0,0 +1,25 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package stderr represents the imported interface "wasi:cli/stderr@0.2.0". +package stderr + +import ( + "internal/cm" + "internal/wasi/io/v0.2.0/streams" +) + +// OutputStream represents the imported type alias "wasi:cli/stderr@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + +// GetStderr represents the imported function "get-stderr". +// +// get-stderr: func() -> output-stream +// +//go:nosplit +func GetStderr() (result OutputStream) { + result0 := wasmimport_GetStderr() + result = cm.Reinterpret[OutputStream]((uint32)(result0)) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/stdin/empty.s b/src/internal/wasi/cli/v0.2.0/stdin/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdin/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go new file mode 100644 index 0000000000..374eb2531f --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package stdin + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/stdin@0.2.0 get-stdin +//go:noescape +func wasmimport_GetStdin() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go new file mode 100644 index 0000000000..e697cd15d4 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdin/stdin.wit.go @@ -0,0 +1,25 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package stdin represents the imported interface "wasi:cli/stdin@0.2.0". +package stdin + +import ( + "internal/cm" + "internal/wasi/io/v0.2.0/streams" +) + +// InputStream represents the imported type alias "wasi:cli/stdin@0.2.0#input-stream". +// +// See [streams.InputStream] for more information. +type InputStream = streams.InputStream + +// GetStdin represents the imported function "get-stdin". +// +// get-stdin: func() -> input-stream +// +//go:nosplit +func GetStdin() (result InputStream) { + result0 := wasmimport_GetStdin() + result = cm.Reinterpret[InputStream]((uint32)(result0)) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/stdout/empty.s b/src/internal/wasi/cli/v0.2.0/stdout/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdout/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go new file mode 100644 index 0000000000..68e4a3dac9 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package stdout + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/stdout@0.2.0 get-stdout +//go:noescape +func wasmimport_GetStdout() (result0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go new file mode 100644 index 0000000000..a9934fe2f3 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/stdout/stdout.wit.go @@ -0,0 +1,25 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package stdout represents the imported interface "wasi:cli/stdout@0.2.0". +package stdout + +import ( + "internal/cm" + "internal/wasi/io/v0.2.0/streams" +) + +// OutputStream represents the imported type alias "wasi:cli/stdout@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + +// GetStdout represents the imported function "get-stdout". +// +// get-stdout: func() -> output-stream +// +//go:nosplit +func GetStdout() (result OutputStream) { + result0 := wasmimport_GetStdout() + result = cm.Reinterpret[OutputStream]((uint32)(result0)) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go new file mode 100644 index 0000000000..1df3794f11 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalinput + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-input@0.2.0 [resource-drop]terminal-input +//go:noescape +func wasmimport_TerminalInputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go new file mode 100644 index 0000000000..8313be74b2 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-input/terminal-input.wit.go @@ -0,0 +1,32 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package terminalinput represents the imported interface "wasi:cli/terminal-input@0.2.0". +// +// Terminal input. +// +// In the future, this may include functions for disabling echoing, +// disabling input buffering so that keyboard events are sent through +// immediately, querying supported features, and so on. +package terminalinput + +import ( + "internal/cm" +) + +// TerminalInput represents the imported resource "wasi:cli/terminal-input@0.2.0#terminal-input". +// +// The input side of a terminal. +// +// resource terminal-input +type TerminalInput cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "terminal-input". +// +// Drops a resource handle. +// +//go:nosplit +func (self TerminalInput) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TerminalInputResourceDrop((uint32)(self0)) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go new file mode 100644 index 0000000000..fb35fc418e --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminaloutput + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-output@0.2.0 [resource-drop]terminal-output +//go:noescape +func wasmimport_TerminalOutputResourceDrop(self0 uint32) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go new file mode 100644 index 0000000000..00d7231560 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-output/terminal-output.wit.go @@ -0,0 +1,32 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package terminaloutput represents the imported interface "wasi:cli/terminal-output@0.2.0". +// +// Terminal output. +// +// In the future, this may include functions for querying the terminal +// size, being notified of terminal size changes, querying supported +// features, and so on. +package terminaloutput + +import ( + "internal/cm" +) + +// TerminalOutput represents the imported resource "wasi:cli/terminal-output@0.2.0#terminal-output". +// +// The output side of a terminal. +// +// resource terminal-output +type TerminalOutput cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "terminal-output". +// +// Drops a resource handle. +// +//go:nosplit +func (self TerminalOutput) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TerminalOutputResourceDrop((uint32)(self0)) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go new file mode 100644 index 0000000000..ea8902ee89 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalstderr + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-stderr@0.2.0 get-terminal-stderr +//go:noescape +func wasmimport_GetTerminalStderr(result *cm.Option[TerminalOutput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go new file mode 100644 index 0000000000..9f942f3efb --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stderr/terminal-stderr.wit.go @@ -0,0 +1,30 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package terminalstderr represents the imported interface "wasi:cli/terminal-stderr@0.2.0". +// +// An interface providing an optional `terminal-output` for stderr as a +// link-time authority. +package terminalstderr + +import ( + "internal/cm" + terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" +) + +// TerminalOutput represents the imported type alias "wasi:cli/terminal-stderr@0.2.0#terminal-output". +// +// See [terminaloutput.TerminalOutput] for more information. +type TerminalOutput = terminaloutput.TerminalOutput + +// GetTerminalStderr represents the imported function "get-terminal-stderr". +// +// If stderr is connected to a terminal, return a `terminal-output` handle +// allowing further interaction with it. +// +// get-terminal-stderr: func() -> option +// +//go:nosplit +func GetTerminalStderr() (result cm.Option[TerminalOutput]) { + wasmimport_GetTerminalStderr(&result) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go new file mode 100644 index 0000000000..d9417f353a --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalstdin + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-stdin@0.2.0 get-terminal-stdin +//go:noescape +func wasmimport_GetTerminalStdin(result *cm.Option[TerminalInput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go new file mode 100644 index 0000000000..33a35eff36 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdin/terminal-stdin.wit.go @@ -0,0 +1,30 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package terminalstdin represents the imported interface "wasi:cli/terminal-stdin@0.2.0". +// +// An interface providing an optional `terminal-input` for stdin as a +// link-time authority. +package terminalstdin + +import ( + "internal/cm" + terminalinput "internal/wasi/cli/v0.2.0/terminal-input" +) + +// TerminalInput represents the imported type alias "wasi:cli/terminal-stdin@0.2.0#terminal-input". +// +// See [terminalinput.TerminalInput] for more information. +type TerminalInput = terminalinput.TerminalInput + +// GetTerminalStdin represents the imported function "get-terminal-stdin". +// +// If stdin is connected to a terminal, return a `terminal-input` handle +// allowing further interaction with it. +// +// get-terminal-stdin: func() -> option +// +//go:nosplit +func GetTerminalStdin() (result cm.Option[TerminalInput]) { + wasmimport_GetTerminalStdin(&result) + return +} diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go new file mode 100644 index 0000000000..1c9e3719b8 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package terminalstdout + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0". + +//go:wasmimport wasi:cli/terminal-stdout@0.2.0 get-terminal-stdout +//go:noescape +func wasmimport_GetTerminalStdout(result *cm.Option[TerminalOutput]) diff --git a/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go new file mode 100644 index 0000000000..e6be82dd30 --- /dev/null +++ b/src/internal/wasi/cli/v0.2.0/terminal-stdout/terminal-stdout.wit.go @@ -0,0 +1,30 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package terminalstdout represents the imported interface "wasi:cli/terminal-stdout@0.2.0". +// +// An interface providing an optional `terminal-output` for stdout as a +// link-time authority. +package terminalstdout + +import ( + "internal/cm" + terminaloutput "internal/wasi/cli/v0.2.0/terminal-output" +) + +// TerminalOutput represents the imported type alias "wasi:cli/terminal-stdout@0.2.0#terminal-output". +// +// See [terminaloutput.TerminalOutput] for more information. +type TerminalOutput = terminaloutput.TerminalOutput + +// GetTerminalStdout represents the imported function "get-terminal-stdout". +// +// If stdout is connected to a terminal, return a `terminal-output` handle +// allowing further interaction with it. +// +// get-terminal-stdout: func() -> option +// +//go:nosplit +func GetTerminalStdout() (result cm.Option[TerminalOutput]) { + wasmimport_GetTerminalStdout(&result) + return +} diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go new file mode 100644 index 0000000000..36a1720a6d --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wasm.go @@ -0,0 +1,21 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package monotonicclock + +// This file contains wasmimport and wasmexport declarations for "wasi:clocks@0.2.0". + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 now +//go:noescape +func wasmimport_Now() (result0 uint64) + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 resolution +//go:noescape +func wasmimport_Resolution() (result0 uint64) + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-instant +//go:noescape +func wasmimport_SubscribeInstant(when0 uint64) (result0 uint32) + +//go:wasmimport wasi:clocks/monotonic-clock@0.2.0 subscribe-duration +//go:noescape +func wasmimport_SubscribeDuration(when0 uint64) (result0 uint32) diff --git a/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go new file mode 100644 index 0000000000..7cfa7d1405 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/monotonic-clock/monotonic-clock.wit.go @@ -0,0 +1,102 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package monotonicclock represents the imported interface "wasi:clocks/monotonic-clock@0.2.0". +// +// WASI Monotonic Clock is a clock API intended to let users measure elapsed +// time. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +// +// A monotonic clock is a clock which has an unspecified initial value, and +// successive reads of the clock will produce non-decreasing values. +// +// It is intended for measuring elapsed time. +package monotonicclock + +import ( + "internal/cm" + "internal/wasi/io/v0.2.0/poll" +) + +// Pollable represents the imported type alias "wasi:clocks/monotonic-clock@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// Instant represents the u64 "wasi:clocks/monotonic-clock@0.2.0#instant". +// +// An instant in time, in nanoseconds. An instant is relative to an +// unspecified initial value, and can only be compared to instances from +// the same monotonic-clock. +// +// type instant = u64 +type Instant uint64 + +// Duration represents the u64 "wasi:clocks/monotonic-clock@0.2.0#duration". +// +// A duration of time, in nanoseconds. +// +// type duration = u64 +type Duration uint64 + +// Now represents the imported function "now". +// +// Read the current value of the clock. +// +// The clock is monotonic, therefore calling this function repeatedly will +// produce a sequence of non-decreasing values. +// +// now: func() -> instant +// +//go:nosplit +func Now() (result Instant) { + result0 := wasmimport_Now() + result = (Instant)((uint64)(result0)) + return +} + +// Resolution represents the imported function "resolution". +// +// Query the resolution of the clock. Returns the duration of time +// corresponding to a clock tick. +// +// resolution: func() -> duration +// +//go:nosplit +func Resolution() (result Duration) { + result0 := wasmimport_Resolution() + result = (Duration)((uint64)(result0)) + return +} + +// SubscribeInstant represents the imported function "subscribe-instant". +// +// Create a `pollable` which will resolve once the specified instant +// occured. +// +// subscribe-instant: func(when: instant) -> pollable +// +//go:nosplit +func SubscribeInstant(when Instant) (result Pollable) { + when0 := (uint64)(when) + result0 := wasmimport_SubscribeInstant((uint64)(when0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} + +// SubscribeDuration represents the imported function "subscribe-duration". +// +// Create a `pollable` which will resolve once the given duration has +// elapsed, starting at the time at which this function was called. +// occured. +// +// subscribe-duration: func(when: duration) -> pollable +// +//go:nosplit +func SubscribeDuration(when Duration) (result Pollable) { + when0 := (uint64)(when) + result0 := wasmimport_SubscribeDuration((uint64)(when0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go new file mode 100644 index 0000000000..321ff3f1c3 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package wallclock + +// This file contains wasmimport and wasmexport declarations for "wasi:clocks@0.2.0". + +//go:wasmimport wasi:clocks/wall-clock@0.2.0 now +//go:noescape +func wasmimport_Now(result *DateTime) + +//go:wasmimport wasi:clocks/wall-clock@0.2.0 resolution +//go:noescape +func wasmimport_Resolution(result *DateTime) diff --git a/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go new file mode 100644 index 0000000000..acf22248b8 --- /dev/null +++ b/src/internal/wasi/clocks/v0.2.0/wall-clock/wall-clock.wit.go @@ -0,0 +1,75 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package wallclock represents the imported interface "wasi:clocks/wall-clock@0.2.0". +// +// WASI Wall Clock is a clock API intended to let users query the current +// time. The name "wall" makes an analogy to a "clock on the wall", which +// is not necessarily monotonic as it may be reset. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +// +// A wall clock is a clock which measures the date and time according to +// some external reference. +// +// External references may be reset, so this clock is not necessarily +// monotonic, making it unsuitable for measuring elapsed time. +// +// It is intended for reporting the current date and time for humans. +package wallclock + +import ( + "internal/cm" +) + +// DateTime represents the record "wasi:clocks/wall-clock@0.2.0#datetime". +// +// A time and date in seconds plus nanoseconds. +// +// record datetime { +// seconds: u64, +// nanoseconds: u32, +// } +type DateTime struct { + _ cm.HostLayout `json:"-"` + Seconds uint64 `json:"seconds"` + Nanoseconds uint32 `json:"nanoseconds"` +} + +// Now represents the imported function "now". +// +// Read the current value of the clock. +// +// This clock is not monotonic, therefore calling this function repeatedly +// will not necessarily produce a sequence of non-decreasing values. +// +// The returned timestamps represent the number of seconds since +// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], +// also known as [Unix Time]. +// +// The nanoseconds field of the output is always less than 1000000000. +// +// now: func() -> datetime +// +// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 +// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time +// +//go:nosplit +func Now() (result DateTime) { + wasmimport_Now(&result) + return +} + +// Resolution represents the imported function "resolution". +// +// Query the resolution of the clock. +// +// The nanoseconds field of the output is always less than 1000000000. +// +// resolution: func() -> datetime +// +//go:nosplit +func Resolution() (result DateTime) { + wasmimport_Resolution(&result) + return +} diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go new file mode 100644 index 0000000000..c5bae30593 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package preopens + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:filesystem@0.2.0". + +//go:wasmimport wasi:filesystem/preopens@0.2.0 get-directories +//go:noescape +func wasmimport_GetDirectories(result *cm.List[cm.Tuple[Descriptor, string]]) diff --git a/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go new file mode 100644 index 0000000000..f655e854cb --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/preopens/preopens.wit.go @@ -0,0 +1,26 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package preopens represents the imported interface "wasi:filesystem/preopens@0.2.0". +package preopens + +import ( + "internal/cm" + "internal/wasi/filesystem/v0.2.0/types" +) + +// Descriptor represents the imported type alias "wasi:filesystem/preopens@0.2.0#descriptor". +// +// See [types.Descriptor] for more information. +type Descriptor = types.Descriptor + +// GetDirectories represents the imported function "get-directories". +// +// Return the set of preopened directories, and their path. +// +// get-directories: func() -> list> +// +//go:nosplit +func GetDirectories() (result cm.List[cm.Tuple[Descriptor, string]]) { + wasmimport_GetDirectories(&result) + return +} diff --git a/src/internal/wasi/filesystem/v0.2.0/types/abi.go b/src/internal/wasi/filesystem/v0.2.0/types/abi.go new file mode 100644 index 0000000000..30f39af5fb --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/abi.go @@ -0,0 +1,50 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package types + +import ( + "internal/cm" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + "unsafe" +) + +// MetadataHashValueShape is used for storage in variant or result types. +type MetadataHashValueShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(MetadataHashValue{})]byte +} + +// TupleListU8BoolShape is used for storage in variant or result types. +type TupleListU8BoolShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Tuple[cm.List[uint8], bool]{})]byte +} + +func lower_DateTime(v wallclock.DateTime) (f0 uint64, f1 uint32) { + f0 = (uint64)(v.Seconds) + f1 = (uint32)(v.Nanoseconds) + return +} + +func lower_NewTimestamp(v NewTimestamp) (f0 uint32, f1 uint64, f2 uint32) { + f0 = (uint32)(v.Tag()) + switch f0 { + case 2: // timestamp + v1, v2 := lower_DateTime(*cm.Case[DateTime](&v, 2)) + f1 = (uint64)(v1) + f2 = (uint32)(v2) + } + return +} + +// DescriptorStatShape is used for storage in variant or result types. +type DescriptorStatShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(DescriptorStat{})]byte +} + +// OptionDirectoryEntryShape is used for storage in variant or result types. +type OptionDirectoryEntryShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Option[DirectoryEntry]{})]byte +} diff --git a/src/internal/wasi/filesystem/v0.2.0/types/empty.s b/src/internal/wasi/filesystem/v0.2.0/types/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go new file mode 100644 index 0000000000..d1a37fb2f1 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wasm.go @@ -0,0 +1,133 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package types + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:filesystem@0.2.0". + +//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]descriptor +//go:noescape +func wasmimport_DescriptorResourceDrop(self0 uint32) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.advise +//go:noescape +func wasmimport_DescriptorAdvise(self0 uint32, offset0 uint64, length0 uint64, advice0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.append-via-stream +//go:noescape +func wasmimport_DescriptorAppendViaStream(self0 uint32, result *cm.Result[OutputStream, OutputStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.create-directory-at +//go:noescape +func wasmimport_DescriptorCreateDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-flags +//go:noescape +func wasmimport_DescriptorGetFlags(self0 uint32, result *cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.get-type +//go:noescape +func wasmimport_DescriptorGetType(self0 uint32, result *cm.Result[DescriptorType, DescriptorType, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.is-same-object +//go:noescape +func wasmimport_DescriptorIsSameObject(self0 uint32, other0 uint32) (result0 uint32) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.link-at +//go:noescape +func wasmimport_DescriptorLinkAt(self0 uint32, oldPathFlags0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash +//go:noescape +func wasmimport_DescriptorMetadataHash(self0 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.metadata-hash-at +//go:noescape +func wasmimport_DescriptorMetadataHashAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.open-at +//go:noescape +func wasmimport_DescriptorOpenAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, openFlags0 uint32, flags0 uint32, result *cm.Result[Descriptor, Descriptor, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read +//go:noescape +func wasmimport_DescriptorRead(self0 uint32, length0 uint64, offset0 uint64, result *cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-directory +//go:noescape +func wasmimport_DescriptorReadDirectory(self0 uint32, result *cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.read-via-stream +//go:noescape +func wasmimport_DescriptorReadViaStream(self0 uint32, offset0 uint64, result *cm.Result[InputStream, InputStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.readlink-at +//go:noescape +func wasmimport_DescriptorReadLinkAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[string, string, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.remove-directory-at +//go:noescape +func wasmimport_DescriptorRemoveDirectoryAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.rename-at +//go:noescape +func wasmimport_DescriptorRenameAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newDescriptor0 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-size +//go:noescape +func wasmimport_DescriptorSetSize(self0 uint32, size0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times +//go:noescape +func wasmimport_DescriptorSetTimes(self0 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.set-times-at +//go:noescape +func wasmimport_DescriptorSetTimesAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, dataAccessTimestamp0 uint32, dataAccessTimestamp1 uint64, dataAccessTimestamp2 uint32, dataModificationTimestamp0 uint32, dataModificationTimestamp1 uint64, dataModificationTimestamp2 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat +//go:noescape +func wasmimport_DescriptorStat(self0 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.stat-at +//go:noescape +func wasmimport_DescriptorStatAt(self0 uint32, pathFlags0 uint32, path0 *uint8, path1 uint32, result *cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.symlink-at +//go:noescape +func wasmimport_DescriptorSymlinkAt(self0 uint32, oldPath0 *uint8, oldPath1 uint32, newPath0 *uint8, newPath1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync +//go:noescape +func wasmimport_DescriptorSync(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.sync-data +//go:noescape +func wasmimport_DescriptorSyncData(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.unlink-file-at +//go:noescape +func wasmimport_DescriptorUnlinkFileAt(self0 uint32, path0 *uint8, path1 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write +//go:noescape +func wasmimport_DescriptorWrite(self0 uint32, buffer0 *uint8, buffer1 uint32, offset0 uint64, result *cm.Result[uint64, FileSize, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]descriptor.write-via-stream +//go:noescape +func wasmimport_DescriptorWriteViaStream(self0 uint32, offset0 uint64, result *cm.Result[OutputStream, OutputStream, ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 [resource-drop]directory-entry-stream +//go:noescape +func wasmimport_DirectoryEntryStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:filesystem/types@0.2.0 [method]directory-entry-stream.read-directory-entry +//go:noescape +func wasmimport_DirectoryEntryStreamReadDirectoryEntry(self0 uint32, result *cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) + +//go:wasmimport wasi:filesystem/types@0.2.0 filesystem-error-code +//go:noescape +func wasmimport_FilesystemErrorCode(err0 uint32, result *cm.Option[ErrorCode]) diff --git a/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go new file mode 100644 index 0000000000..46c94b0040 --- /dev/null +++ b/src/internal/wasi/filesystem/v0.2.0/types/types.wit.go @@ -0,0 +1,1305 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package types represents the imported interface "wasi:filesystem/types@0.2.0". +// +// WASI filesystem is a filesystem API primarily intended to let users run WASI +// programs that access their files on their existing filesystems, without +// significant overhead. +// +// It is intended to be roughly portable between Unix-family platforms and +// Windows, though it does not hide many of the major differences. +// +// Paths are passed as interface-type `string`s, meaning they must consist of +// a sequence of Unicode Scalar Values (USVs). Some filesystems may contain +// paths which are not accessible by this API. +// +// The directory separator in WASI is always the forward-slash (`/`). +// +// All paths in WASI are relative paths, and are interpreted relative to a +// `descriptor` referring to a base directory. If a `path` argument to any WASI +// function starts with `/`, or if any step of resolving a `path`, including +// `..` and symbolic link steps, reaches a directory outside of the base +// directory, or reaches a symlink to an absolute or rooted path in the +// underlying filesystem, the function fails with `error-code::not-permitted`. +// +// For more information about WASI path resolution and sandboxing, see +// [WASI filesystem path resolution]. +// +// [WASI filesystem path resolution]: https://github.com/WebAssembly/wasi-filesystem/blob/main/path-resolution.md +package types + +import ( + "internal/cm" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + "internal/wasi/io/v0.2.0/streams" +) + +// InputStream represents the imported type alias "wasi:filesystem/types@0.2.0#input-stream". +// +// See [streams.InputStream] for more information. +type InputStream = streams.InputStream + +// OutputStream represents the imported type alias "wasi:filesystem/types@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + +// Error represents the imported type alias "wasi:filesystem/types@0.2.0#error". +// +// See [streams.Error] for more information. +type Error = streams.Error + +// DateTime represents the type alias "wasi:filesystem/types@0.2.0#datetime". +// +// See [wallclock.DateTime] for more information. +type DateTime = wallclock.DateTime + +// FileSize represents the u64 "wasi:filesystem/types@0.2.0#filesize". +// +// File size or length of a region within a file. +// +// type filesize = u64 +type FileSize uint64 + +// DescriptorType represents the enum "wasi:filesystem/types@0.2.0#descriptor-type". +// +// The type of a filesystem object referenced by a descriptor. +// +// Note: This was called `filetype` in earlier versions of WASI. +// +// enum descriptor-type { +// unknown, +// block-device, +// character-device, +// directory, +// fifo, +// symbolic-link, +// regular-file, +// socket +// } +type DescriptorType uint8 + +const ( + // The type of the descriptor or file is unknown or is different from + // any of the other types specified. + DescriptorTypeUnknown DescriptorType = iota + + // The descriptor refers to a block device inode. + DescriptorTypeBlockDevice + + // The descriptor refers to a character device inode. + DescriptorTypeCharacterDevice + + // The descriptor refers to a directory inode. + DescriptorTypeDirectory + + // The descriptor refers to a named pipe. + DescriptorTypeFIFO + + // The file refers to a symbolic link inode. + DescriptorTypeSymbolicLink + + // The descriptor refers to a regular file inode. + DescriptorTypeRegularFile + + // The descriptor refers to a socket. + DescriptorTypeSocket +) + +var _DescriptorTypeStrings = [8]string{ + "unknown", + "block-device", + "character-device", + "directory", + "fifo", + "symbolic-link", + "regular-file", + "socket", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e DescriptorType) String() string { + return _DescriptorTypeStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e DescriptorType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *DescriptorType) UnmarshalText(text []byte) error { + return _DescriptorTypeUnmarshalCase(e, text) +} + +var _DescriptorTypeUnmarshalCase = cm.CaseUnmarshaler[DescriptorType](_DescriptorTypeStrings[:]) + +// DescriptorFlags represents the flags "wasi:filesystem/types@0.2.0#descriptor-flags". +// +// Descriptor flags. +// +// Note: This was called `fdflags` in earlier versions of WASI. +// +// flags descriptor-flags { +// read, +// write, +// file-integrity-sync, +// data-integrity-sync, +// requested-write-sync, +// mutate-directory, +// } +type DescriptorFlags uint8 + +const ( + // Read mode: Data can be read. + DescriptorFlagsRead DescriptorFlags = 1 << iota + + // Write mode: Data can be written to. + DescriptorFlagsWrite + + // Request that writes be performed according to synchronized I/O file + // integrity completion. The data stored in the file and the file's + // metadata are synchronized. This is similar to `O_SYNC` in POSIX. + // + // The precise semantics of this operation have not yet been defined for + // WASI. At this time, it should be interpreted as a request, and not a + // requirement. + DescriptorFlagsFileIntegritySync + + // Request that writes be performed according to synchronized I/O data + // integrity completion. Only the data stored in the file is + // synchronized. This is similar to `O_DSYNC` in POSIX. + // + // The precise semantics of this operation have not yet been defined for + // WASI. At this time, it should be interpreted as a request, and not a + // requirement. + DescriptorFlagsDataIntegritySync + + // Requests that reads be performed at the same level of integrety + // requested for writes. This is similar to `O_RSYNC` in POSIX. + // + // The precise semantics of this operation have not yet been defined for + // WASI. At this time, it should be interpreted as a request, and not a + // requirement. + DescriptorFlagsRequestedWriteSync + + // Mutating directories mode: Directory contents may be mutated. + // + // When this flag is unset on a descriptor, operations using the + // descriptor which would create, rename, delete, modify the data or + // metadata of filesystem objects, or obtain another handle which + // would permit any of those, shall fail with `error-code::read-only` if + // they would otherwise succeed. + // + // This may only be set on directories. + DescriptorFlagsMutateDirectory +) + +// PathFlags represents the flags "wasi:filesystem/types@0.2.0#path-flags". +// +// Flags determining the method of how paths are resolved. +// +// flags path-flags { +// symlink-follow, +// } +type PathFlags uint8 + +const ( + // As long as the resolved path corresponds to a symbolic link, it is + // expanded. + PathFlagsSymlinkFollow PathFlags = 1 << iota +) + +// OpenFlags represents the flags "wasi:filesystem/types@0.2.0#open-flags". +// +// Open flags used by `open-at`. +// +// flags open-flags { +// create, +// directory, +// exclusive, +// truncate, +// } +type OpenFlags uint8 + +const ( + // Create file if it does not exist, similar to `O_CREAT` in POSIX. + OpenFlagsCreate OpenFlags = 1 << iota + + // Fail if not a directory, similar to `O_DIRECTORY` in POSIX. + OpenFlagsDirectory + + // Fail if file already exists, similar to `O_EXCL` in POSIX. + OpenFlagsExclusive + + // Truncate file to size 0, similar to `O_TRUNC` in POSIX. + OpenFlagsTruncate +) + +// LinkCount represents the u64 "wasi:filesystem/types@0.2.0#link-count". +// +// Number of hard links to an inode. +// +// type link-count = u64 +type LinkCount uint64 + +// DescriptorStat represents the record "wasi:filesystem/types@0.2.0#descriptor-stat". +// +// File attributes. +// +// Note: This was called `filestat` in earlier versions of WASI. +// +// record descriptor-stat { +// %type: descriptor-type, +// link-count: link-count, +// size: filesize, +// data-access-timestamp: option, +// data-modification-timestamp: option, +// status-change-timestamp: option, +// } +type DescriptorStat struct { + _ cm.HostLayout `json:"-"` + // File type. + Type DescriptorType `json:"type"` + + // Number of hard links to the file. + LinkCount LinkCount `json:"link-count"` + + // For regular files, the file size in bytes. For symbolic links, the + // length in bytes of the pathname contained in the symbolic link. + Size FileSize `json:"size"` + + // Last data access timestamp. + // + // If the `option` is none, the platform doesn't maintain an access + // timestamp for this file. + DataAccessTimestamp cm.Option[DateTime] `json:"data-access-timestamp"` + + // Last data modification timestamp. + // + // If the `option` is none, the platform doesn't maintain a + // modification timestamp for this file. + DataModificationTimestamp cm.Option[DateTime] `json:"data-modification-timestamp"` + + // Last file status-change timestamp. + // + // If the `option` is none, the platform doesn't maintain a + // status-change timestamp for this file. + StatusChangeTimestamp cm.Option[DateTime] `json:"status-change-timestamp"` +} + +// NewTimestamp represents the variant "wasi:filesystem/types@0.2.0#new-timestamp". +// +// When setting a timestamp, this gives the value to set it to. +// +// variant new-timestamp { +// no-change, +// now, +// timestamp(datetime), +// } +type NewTimestamp cm.Variant[uint8, DateTime, DateTime] + +// NewTimestampNoChange returns a [NewTimestamp] of case "no-change". +// +// Leave the timestamp set to its previous value. +func NewTimestampNoChange() NewTimestamp { + var data struct{} + return cm.New[NewTimestamp](0, data) +} + +// NoChange returns true if [NewTimestamp] represents the variant case "no-change". +func (self *NewTimestamp) NoChange() bool { + return self.Tag() == 0 +} + +// NewTimestampNow returns a [NewTimestamp] of case "now". +// +// Set the timestamp to the current time of the system clock associated +// with the filesystem. +func NewTimestampNow() NewTimestamp { + var data struct{} + return cm.New[NewTimestamp](1, data) +} + +// Now returns true if [NewTimestamp] represents the variant case "now". +func (self *NewTimestamp) Now() bool { + return self.Tag() == 1 +} + +// NewTimestampTimestamp returns a [NewTimestamp] of case "timestamp". +// +// Set the timestamp to the given value. +func NewTimestampTimestamp(data DateTime) NewTimestamp { + return cm.New[NewTimestamp](2, data) +} + +// Timestamp returns a non-nil *[DateTime] if [NewTimestamp] represents the variant case "timestamp". +func (self *NewTimestamp) Timestamp() *DateTime { + return cm.Case[DateTime](self, 2) +} + +var _NewTimestampStrings = [3]string{ + "no-change", + "now", + "timestamp", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v NewTimestamp) String() string { + return _NewTimestampStrings[v.Tag()] +} + +// DirectoryEntry represents the record "wasi:filesystem/types@0.2.0#directory-entry". +// +// A directory entry. +// +// record directory-entry { +// %type: descriptor-type, +// name: string, +// } +type DirectoryEntry struct { + _ cm.HostLayout `json:"-"` + // The type of the file referred to by this directory entry. + Type DescriptorType `json:"type"` + + // The name of the object. + Name string `json:"name"` +} + +// ErrorCode represents the enum "wasi:filesystem/types@0.2.0#error-code". +// +// Error codes returned by functions, similar to `errno` in POSIX. +// Not all of these error codes are returned by the functions provided by this +// API; some are used in higher-level library layers, and others are provided +// merely for alignment with POSIX. +// +// enum error-code { +// access, +// would-block, +// already, +// bad-descriptor, +// busy, +// deadlock, +// quota, +// exist, +// file-too-large, +// illegal-byte-sequence, +// in-progress, +// interrupted, +// invalid, +// io, +// is-directory, +// loop, +// too-many-links, +// message-size, +// name-too-long, +// no-device, +// no-entry, +// no-lock, +// insufficient-memory, +// insufficient-space, +// not-directory, +// not-empty, +// not-recoverable, +// unsupported, +// no-tty, +// no-such-device, +// overflow, +// not-permitted, +// pipe, +// read-only, +// invalid-seek, +// text-file-busy, +// cross-device +// } +type ErrorCode uint8 + +const ( + // Permission denied, similar to `EACCES` in POSIX. + ErrorCodeAccess ErrorCode = iota + + // Resource unavailable, or operation would block, similar to `EAGAIN` and `EWOULDBLOCK` + // in POSIX. + ErrorCodeWouldBlock + + // Connection already in progress, similar to `EALREADY` in POSIX. + ErrorCodeAlready + + // Bad descriptor, similar to `EBADF` in POSIX. + ErrorCodeBadDescriptor + + // Device or resource busy, similar to `EBUSY` in POSIX. + ErrorCodeBusy + + // Resource deadlock would occur, similar to `EDEADLK` in POSIX. + ErrorCodeDeadlock + + // Storage quota exceeded, similar to `EDQUOT` in POSIX. + ErrorCodeQuota + + // File exists, similar to `EEXIST` in POSIX. + ErrorCodeExist + + // File too large, similar to `EFBIG` in POSIX. + ErrorCodeFileTooLarge + + // Illegal byte sequence, similar to `EILSEQ` in POSIX. + ErrorCodeIllegalByteSequence + + // Operation in progress, similar to `EINPROGRESS` in POSIX. + ErrorCodeInProgress + + // Interrupted function, similar to `EINTR` in POSIX. + ErrorCodeInterrupted + + // Invalid argument, similar to `EINVAL` in POSIX. + ErrorCodeInvalid + + // I/O error, similar to `EIO` in POSIX. + ErrorCodeIO + + // Is a directory, similar to `EISDIR` in POSIX. + ErrorCodeIsDirectory + + // Too many levels of symbolic links, similar to `ELOOP` in POSIX. + ErrorCodeLoop + + // Too many links, similar to `EMLINK` in POSIX. + ErrorCodeTooManyLinks + + // Message too large, similar to `EMSGSIZE` in POSIX. + ErrorCodeMessageSize + + // Filename too long, similar to `ENAMETOOLONG` in POSIX. + ErrorCodeNameTooLong + + // No such device, similar to `ENODEV` in POSIX. + ErrorCodeNoDevice + + // No such file or directory, similar to `ENOENT` in POSIX. + ErrorCodeNoEntry + + // No locks available, similar to `ENOLCK` in POSIX. + ErrorCodeNoLock + + // Not enough space, similar to `ENOMEM` in POSIX. + ErrorCodeInsufficientMemory + + // No space left on device, similar to `ENOSPC` in POSIX. + ErrorCodeInsufficientSpace + + // Not a directory or a symbolic link to a directory, similar to `ENOTDIR` in POSIX. + ErrorCodeNotDirectory + + // Directory not empty, similar to `ENOTEMPTY` in POSIX. + ErrorCodeNotEmpty + + // State not recoverable, similar to `ENOTRECOVERABLE` in POSIX. + ErrorCodeNotRecoverable + + // Not supported, similar to `ENOTSUP` and `ENOSYS` in POSIX. + ErrorCodeUnsupported + + // Inappropriate I/O control operation, similar to `ENOTTY` in POSIX. + ErrorCodeNoTTY + + // No such device or address, similar to `ENXIO` in POSIX. + ErrorCodeNoSuchDevice + + // Value too large to be stored in data type, similar to `EOVERFLOW` in POSIX. + ErrorCodeOverflow + + // Operation not permitted, similar to `EPERM` in POSIX. + ErrorCodeNotPermitted + + // Broken pipe, similar to `EPIPE` in POSIX. + ErrorCodePipe + + // Read-only file system, similar to `EROFS` in POSIX. + ErrorCodeReadOnly + + // Invalid seek, similar to `ESPIPE` in POSIX. + ErrorCodeInvalidSeek + + // Text file busy, similar to `ETXTBSY` in POSIX. + ErrorCodeTextFileBusy + + // Cross-device link, similar to `EXDEV` in POSIX. + ErrorCodeCrossDevice +) + +var _ErrorCodeStrings = [37]string{ + "access", + "would-block", + "already", + "bad-descriptor", + "busy", + "deadlock", + "quota", + "exist", + "file-too-large", + "illegal-byte-sequence", + "in-progress", + "interrupted", + "invalid", + "io", + "is-directory", + "loop", + "too-many-links", + "message-size", + "name-too-long", + "no-device", + "no-entry", + "no-lock", + "insufficient-memory", + "insufficient-space", + "not-directory", + "not-empty", + "not-recoverable", + "unsupported", + "no-tty", + "no-such-device", + "overflow", + "not-permitted", + "pipe", + "read-only", + "invalid-seek", + "text-file-busy", + "cross-device", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e ErrorCode) String() string { + return _ErrorCodeStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + return _ErrorCodeUnmarshalCase(e, text) +} + +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + +// Advice represents the enum "wasi:filesystem/types@0.2.0#advice". +// +// File or memory access pattern advisory information. +// +// enum advice { +// normal, +// sequential, +// random, +// will-need, +// dont-need, +// no-reuse +// } +type Advice uint8 + +const ( + // The application has no advice to give on its behavior with respect + // to the specified data. + AdviceNormal Advice = iota + + // The application expects to access the specified data sequentially + // from lower offsets to higher offsets. + AdviceSequential + + // The application expects to access the specified data in a random + // order. + AdviceRandom + + // The application expects to access the specified data in the near + // future. + AdviceWillNeed + + // The application expects that it will not access the specified data + // in the near future. + AdviceDontNeed + + // The application expects to access the specified data once and then + // not reuse it thereafter. + AdviceNoReuse +) + +var _AdviceStrings = [6]string{ + "normal", + "sequential", + "random", + "will-need", + "dont-need", + "no-reuse", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e Advice) String() string { + return _AdviceStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e Advice) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *Advice) UnmarshalText(text []byte) error { + return _AdviceUnmarshalCase(e, text) +} + +var _AdviceUnmarshalCase = cm.CaseUnmarshaler[Advice](_AdviceStrings[:]) + +// MetadataHashValue represents the record "wasi:filesystem/types@0.2.0#metadata-hash-value". +// +// A 128-bit hash value, split into parts because wasm doesn't have a +// 128-bit integer type. +// +// record metadata-hash-value { +// lower: u64, +// upper: u64, +// } +type MetadataHashValue struct { + _ cm.HostLayout `json:"-"` + // 64 bits of a 128-bit hash value. + Lower uint64 `json:"lower"` + + // Another 64 bits of a 128-bit hash value. + Upper uint64 `json:"upper"` +} + +// Descriptor represents the imported resource "wasi:filesystem/types@0.2.0#descriptor". +// +// A descriptor is a reference to a filesystem object, which may be a file, +// directory, named pipe, special file, or other object on which filesystem +// calls may be made. +// +// resource descriptor +type Descriptor cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "descriptor". +// +// Drops a resource handle. +// +//go:nosplit +func (self Descriptor) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorResourceDrop((uint32)(self0)) + return +} + +// Advise represents the imported method "advise". +// +// Provide file advisory information on a descriptor. +// +// This is similar to `posix_fadvise` in POSIX. +// +// advise: func(offset: filesize, length: filesize, advice: advice) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) Advise(offset FileSize, length FileSize, advice Advice) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + offset0 := (uint64)(offset) + length0 := (uint64)(length) + advice0 := (uint32)(advice) + wasmimport_DescriptorAdvise((uint32)(self0), (uint64)(offset0), (uint64)(length0), (uint32)(advice0), &result) + return +} + +// AppendViaStream represents the imported method "append-via-stream". +// +// Return a stream for appending to a file, if available. +// +// May fail with an error-code describing why the file cannot be appended. +// +// Note: This allows using `write-stream`, which is similar to `write` with +// `O_APPEND` in in POSIX. +// +// append-via-stream: func() -> result +// +//go:nosplit +func (self Descriptor) AppendViaStream() (result cm.Result[OutputStream, OutputStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorAppendViaStream((uint32)(self0), &result) + return +} + +// CreateDirectoryAt represents the imported method "create-directory-at". +// +// Create a directory. +// +// Note: This is similar to `mkdirat` in POSIX. +// +// create-directory-at: func(path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) CreateDirectoryAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorCreateDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +// GetFlags represents the imported method "get-flags". +// +// Get flags associated with a descriptor. +// +// Note: This returns similar flags to `fcntl(fd, F_GETFL)` in POSIX. +// +// Note: This returns the value that was the `fs_flags` value returned +// from `fdstat_get` in earlier versions of WASI. +// +// get-flags: func() -> result +// +//go:nosplit +func (self Descriptor) GetFlags() (result cm.Result[DescriptorFlags, DescriptorFlags, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorGetFlags((uint32)(self0), &result) + return +} + +// GetType represents the imported method "get-type". +// +// Get the dynamic type of a descriptor. +// +// Note: This returns the same value as the `type` field of the `fd-stat` +// returned by `stat`, `stat-at` and similar. +// +// Note: This returns similar flags to the `st_mode & S_IFMT` value provided +// by `fstat` in POSIX. +// +// Note: This returns the value that was the `fs_filetype` value returned +// from `fdstat_get` in earlier versions of WASI. +// +// get-type: func() -> result +// +//go:nosplit +func (self Descriptor) GetType() (result cm.Result[DescriptorType, DescriptorType, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorGetType((uint32)(self0), &result) + return +} + +// IsSameObject represents the imported method "is-same-object". +// +// Test whether two descriptors refer to the same filesystem object. +// +// In POSIX, this corresponds to testing whether the two descriptors have the +// same device (`st_dev`) and inode (`st_ino` or `d_ino`) numbers. +// wasi-filesystem does not expose device and inode numbers, so this function +// may be used instead. +// +// is-same-object: func(other: borrow) -> bool +// +//go:nosplit +func (self Descriptor) IsSameObject(other Descriptor) (result bool) { + self0 := cm.Reinterpret[uint32](self) + other0 := cm.Reinterpret[uint32](other) + result0 := wasmimport_DescriptorIsSameObject((uint32)(self0), (uint32)(other0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) + return +} + +// LinkAt represents the imported method "link-at". +// +// Create a hard link. +// +// Note: This is similar to `linkat` in POSIX. +// +// link-at: func(old-path-flags: path-flags, old-path: string, new-descriptor: borrow, +// new-path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) LinkAt(oldPathFlags PathFlags, oldPath string, newDescriptor Descriptor, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + oldPathFlags0 := (uint32)(oldPathFlags) + oldPath0, oldPath1 := cm.LowerString(oldPath) + newDescriptor0 := cm.Reinterpret[uint32](newDescriptor) + newPath0, newPath1 := cm.LowerString(newPath) + wasmimport_DescriptorLinkAt((uint32)(self0), (uint32)(oldPathFlags0), (*uint8)(oldPath0), (uint32)(oldPath1), (uint32)(newDescriptor0), (*uint8)(newPath0), (uint32)(newPath1), &result) + return +} + +// MetadataHash represents the imported method "metadata-hash". +// +// Return a hash of the metadata associated with a filesystem object referred +// to by a descriptor. +// +// This returns a hash of the last-modification timestamp and file size, and +// may also include the inode number, device number, birth timestamp, and +// other metadata fields that may change when the file is modified or +// replaced. It may also include a secret value chosen by the +// implementation and not otherwise exposed. +// +// Implementations are encourated to provide the following properties: +// +// - If the file is not modified or replaced, the computed hash value should +// usually not change. +// - If the object is modified or replaced, the computed hash value should +// usually change. +// - The inputs to the hash should not be easily computable from the +// computed hash. +// +// However, none of these is required. +// +// metadata-hash: func() -> result +// +//go:nosplit +func (self Descriptor) MetadataHash() (result cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorMetadataHash((uint32)(self0), &result) + return +} + +// MetadataHashAt represents the imported method "metadata-hash-at". +// +// Return a hash of the metadata associated with a filesystem object referred +// to by a directory descriptor and a relative path. +// +// This performs the same hash computation as `metadata-hash`. +// +// metadata-hash-at: func(path-flags: path-flags, path: string) -> result +// +//go:nosplit +func (self Descriptor) MetadataHashAt(pathFlags PathFlags, path string) (result cm.Result[MetadataHashValueShape, MetadataHashValue, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorMetadataHashAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +// OpenAt represents the imported method "open-at". +// +// Open a file or directory. +// +// The returned descriptor is not guaranteed to be the lowest-numbered +// descriptor not currently open/ it is randomized to prevent applications +// from depending on making assumptions about indexes, since this is +// error-prone in multi-threaded contexts. The returned descriptor is +// guaranteed to be less than 2**31. +// +// If `flags` contains `descriptor-flags::mutate-directory`, and the base +// descriptor doesn't have `descriptor-flags::mutate-directory` set, +// `open-at` fails with `error-code::read-only`. +// +// If `flags` contains `write` or `mutate-directory`, or `open-flags` +// contains `truncate` or `create`, and the base descriptor doesn't have +// `descriptor-flags::mutate-directory` set, `open-at` fails with +// `error-code::read-only`. +// +// Note: This is similar to `openat` in POSIX. +// +// open-at: func(path-flags: path-flags, path: string, open-flags: open-flags, %flags: +// descriptor-flags) -> result +// +//go:nosplit +func (self Descriptor) OpenAt(pathFlags PathFlags, path string, openFlags OpenFlags, flags DescriptorFlags) (result cm.Result[Descriptor, Descriptor, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + openFlags0 := (uint32)(openFlags) + flags0 := (uint32)(flags) + wasmimport_DescriptorOpenAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), (uint32)(openFlags0), (uint32)(flags0), &result) + return +} + +// Read represents the imported method "read". +// +// Read from a descriptor, without using and updating the descriptor's offset. +// +// This function returns a list of bytes containing the data that was +// read, along with a bool which, when true, indicates that the end of the +// file was reached. The returned list will contain up to `length` bytes; it +// may return fewer than requested, if the end of the file is reached or +// if the I/O operation is interrupted. +// +// In the future, this may change to return a `stream`. +// +// Note: This is similar to `pread` in POSIX. +// +// read: func(length: filesize, offset: filesize) -> result, bool>, +// error-code> +// +//go:nosplit +func (self Descriptor) Read(length FileSize, offset FileSize) (result cm.Result[TupleListU8BoolShape, cm.Tuple[cm.List[uint8], bool], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + length0 := (uint64)(length) + offset0 := (uint64)(offset) + wasmimport_DescriptorRead((uint32)(self0), (uint64)(length0), (uint64)(offset0), &result) + return +} + +// ReadDirectory represents the imported method "read-directory". +// +// Read directory entries from a directory. +// +// On filesystems where directories contain entries referring to themselves +// and their parents, often named `.` and `..` respectively, these entries +// are omitted. +// +// This always returns a new stream which starts at the beginning of the +// directory. Multiple streams may be active on the same directory, and they +// do not interfere with each other. +// +// read-directory: func() -> result +// +//go:nosplit +func (self Descriptor) ReadDirectory() (result cm.Result[DirectoryEntryStream, DirectoryEntryStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorReadDirectory((uint32)(self0), &result) + return +} + +// ReadViaStream represents the imported method "read-via-stream". +// +// Return a stream for reading from a file, if available. +// +// May fail with an error-code describing why the file cannot be read. +// +// Multiple read, write, and append streams may be active on the same open +// file and they do not interfere with each other. +// +// Note: This allows using `read-stream`, which is similar to `read` in POSIX. +// +// read-via-stream: func(offset: filesize) -> result +// +//go:nosplit +func (self Descriptor) ReadViaStream(offset FileSize) (result cm.Result[InputStream, InputStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + offset0 := (uint64)(offset) + wasmimport_DescriptorReadViaStream((uint32)(self0), (uint64)(offset0), &result) + return +} + +// ReadLinkAt represents the imported method "readlink-at". +// +// Read the contents of a symbolic link. +// +// If the contents contain an absolute or rooted path in the underlying +// filesystem, this function fails with `error-code::not-permitted`. +// +// Note: This is similar to `readlinkat` in POSIX. +// +// readlink-at: func(path: string) -> result +// +//go:nosplit +func (self Descriptor) ReadLinkAt(path string) (result cm.Result[string, string, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorReadLinkAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +// RemoveDirectoryAt represents the imported method "remove-directory-at". +// +// Remove a directory. +// +// Return `error-code::not-empty` if the directory is not empty. +// +// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. +// +// remove-directory-at: func(path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) RemoveDirectoryAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorRemoveDirectoryAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +// RenameAt represents the imported method "rename-at". +// +// Rename a filesystem object. +// +// Note: This is similar to `renameat` in POSIX. +// +// rename-at: func(old-path: string, new-descriptor: borrow, new-path: +// string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) RenameAt(oldPath string, newDescriptor Descriptor, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + oldPath0, oldPath1 := cm.LowerString(oldPath) + newDescriptor0 := cm.Reinterpret[uint32](newDescriptor) + newPath0, newPath1 := cm.LowerString(newPath) + wasmimport_DescriptorRenameAt((uint32)(self0), (*uint8)(oldPath0), (uint32)(oldPath1), (uint32)(newDescriptor0), (*uint8)(newPath0), (uint32)(newPath1), &result) + return +} + +// SetSize represents the imported method "set-size". +// +// Adjust the size of an open file. If this increases the file's size, the +// extra bytes are filled with zeros. +// +// Note: This was called `fd_filestat_set_size` in earlier versions of WASI. +// +// set-size: func(size: filesize) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SetSize(size FileSize) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + size0 := (uint64)(size) + wasmimport_DescriptorSetSize((uint32)(self0), (uint64)(size0), &result) + return +} + +// SetTimes represents the imported method "set-times". +// +// Adjust the timestamps of an open file or directory. +// +// Note: This is similar to `futimens` in POSIX. +// +// Note: This was called `fd_filestat_set_times` in earlier versions of WASI. +// +// set-times: func(data-access-timestamp: new-timestamp, data-modification-timestamp: +// new-timestamp) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SetTimes(dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + dataAccessTimestamp0, dataAccessTimestamp1, dataAccessTimestamp2 := lower_NewTimestamp(dataAccessTimestamp) + dataModificationTimestamp0, dataModificationTimestamp1, dataModificationTimestamp2 := lower_NewTimestamp(dataModificationTimestamp) + wasmimport_DescriptorSetTimes((uint32)(self0), (uint32)(dataAccessTimestamp0), (uint64)(dataAccessTimestamp1), (uint32)(dataAccessTimestamp2), (uint32)(dataModificationTimestamp0), (uint64)(dataModificationTimestamp1), (uint32)(dataModificationTimestamp2), &result) + return +} + +// SetTimesAt represents the imported method "set-times-at". +// +// Adjust the timestamps of a file or directory. +// +// Note: This is similar to `utimensat` in POSIX. +// +// Note: This was called `path_filestat_set_times` in earlier versions of +// WASI. +// +// set-times-at: func(path-flags: path-flags, path: string, data-access-timestamp: +// new-timestamp, data-modification-timestamp: new-timestamp) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SetTimesAt(pathFlags PathFlags, path string, dataAccessTimestamp NewTimestamp, dataModificationTimestamp NewTimestamp) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + dataAccessTimestamp0, dataAccessTimestamp1, dataAccessTimestamp2 := lower_NewTimestamp(dataAccessTimestamp) + dataModificationTimestamp0, dataModificationTimestamp1, dataModificationTimestamp2 := lower_NewTimestamp(dataModificationTimestamp) + wasmimport_DescriptorSetTimesAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), (uint32)(dataAccessTimestamp0), (uint64)(dataAccessTimestamp1), (uint32)(dataAccessTimestamp2), (uint32)(dataModificationTimestamp0), (uint64)(dataModificationTimestamp1), (uint32)(dataModificationTimestamp2), &result) + return +} + +// Stat represents the imported method "stat". +// +// Return the attributes of an open file or directory. +// +// Note: This is similar to `fstat` in POSIX, except that it does not return +// device and inode information. For testing whether two descriptors refer to +// the same underlying filesystem object, use `is-same-object`. To obtain +// additional data that can be used do determine whether a file has been +// modified, use `metadata-hash`. +// +// Note: This was called `fd_filestat_get` in earlier versions of WASI. +// +// stat: func() -> result +// +//go:nosplit +func (self Descriptor) Stat() (result cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorStat((uint32)(self0), &result) + return +} + +// StatAt represents the imported method "stat-at". +// +// Return the attributes of a file or directory. +// +// Note: This is similar to `fstatat` in POSIX, except that it does not +// return device and inode information. See the `stat` description for a +// discussion of alternatives. +// +// Note: This was called `path_filestat_get` in earlier versions of WASI. +// +// stat-at: func(path-flags: path-flags, path: string) -> result +// +//go:nosplit +func (self Descriptor) StatAt(pathFlags PathFlags, path string) (result cm.Result[DescriptorStatShape, DescriptorStat, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + pathFlags0 := (uint32)(pathFlags) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorStatAt((uint32)(self0), (uint32)(pathFlags0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +// SymlinkAt represents the imported method "symlink-at". +// +// Create a symbolic link (also known as a "symlink"). +// +// If `old-path` starts with `/`, the function fails with +// `error-code::not-permitted`. +// +// Note: This is similar to `symlinkat` in POSIX. +// +// symlink-at: func(old-path: string, new-path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SymlinkAt(oldPath string, newPath string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + oldPath0, oldPath1 := cm.LowerString(oldPath) + newPath0, newPath1 := cm.LowerString(newPath) + wasmimport_DescriptorSymlinkAt((uint32)(self0), (*uint8)(oldPath0), (uint32)(oldPath1), (*uint8)(newPath0), (uint32)(newPath1), &result) + return +} + +// Sync represents the imported method "sync". +// +// Synchronize the data and metadata of a file to disk. +// +// This function succeeds with no effect if the file descriptor is not +// opened for writing. +// +// Note: This is similar to `fsync` in POSIX. +// +// sync: func() -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) Sync() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorSync((uint32)(self0), &result) + return +} + +// SyncData represents the imported method "sync-data". +// +// Synchronize the data of a file to disk. +// +// This function succeeds with no effect if the file descriptor is not +// opened for writing. +// +// Note: This is similar to `fdatasync` in POSIX. +// +// sync-data: func() -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) SyncData() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DescriptorSyncData((uint32)(self0), &result) + return +} + +// UnlinkFileAt represents the imported method "unlink-file-at". +// +// Unlink a filesystem object that is not a directory. +// +// Return `error-code::is-directory` if the path refers to a directory. +// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. +// +// unlink-file-at: func(path: string) -> result<_, error-code> +// +//go:nosplit +func (self Descriptor) UnlinkFileAt(path string) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + path0, path1 := cm.LowerString(path) + wasmimport_DescriptorUnlinkFileAt((uint32)(self0), (*uint8)(path0), (uint32)(path1), &result) + return +} + +// Write represents the imported method "write". +// +// Write to a descriptor, without using and updating the descriptor's offset. +// +// It is valid to write past the end of a file; the file is extended to the +// extent of the write, with bytes between the previous end and the start of +// the write set to zero. +// +// In the future, this may change to take a `stream`. +// +// Note: This is similar to `pwrite` in POSIX. +// +// write: func(buffer: list, offset: filesize) -> result +// +//go:nosplit +func (self Descriptor) Write(buffer cm.List[uint8], offset FileSize) (result cm.Result[uint64, FileSize, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + buffer0, buffer1 := cm.LowerList(buffer) + offset0 := (uint64)(offset) + wasmimport_DescriptorWrite((uint32)(self0), (*uint8)(buffer0), (uint32)(buffer1), (uint64)(offset0), &result) + return +} + +// WriteViaStream represents the imported method "write-via-stream". +// +// Return a stream for writing to a file, if available. +// +// May fail with an error-code describing why the file cannot be written. +// +// Note: This allows using `write-stream`, which is similar to `write` in +// POSIX. +// +// write-via-stream: func(offset: filesize) -> result +// +//go:nosplit +func (self Descriptor) WriteViaStream(offset FileSize) (result cm.Result[OutputStream, OutputStream, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + offset0 := (uint64)(offset) + wasmimport_DescriptorWriteViaStream((uint32)(self0), (uint64)(offset0), &result) + return +} + +// DirectoryEntryStream represents the imported resource "wasi:filesystem/types@0.2.0#directory-entry-stream". +// +// A stream of directory entries. +// +// resource directory-entry-stream +type DirectoryEntryStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "directory-entry-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self DirectoryEntryStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DirectoryEntryStreamResourceDrop((uint32)(self0)) + return +} + +// ReadDirectoryEntry represents the imported method "read-directory-entry". +// +// Read a single directory entry from a `directory-entry-stream`. +// +// read-directory-entry: func() -> result, error-code> +// +//go:nosplit +func (self DirectoryEntryStream) ReadDirectoryEntry() (result cm.Result[OptionDirectoryEntryShape, cm.Option[DirectoryEntry], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_DirectoryEntryStreamReadDirectoryEntry((uint32)(self0), &result) + return +} + +// FilesystemErrorCode represents the imported function "filesystem-error-code". +// +// Attempts to extract a filesystem-related `error-code` from the stream +// `error` provided. +// +// Stream operations which return `stream-error::last-operation-failed` +// have a payload with more information about the operation that failed. +// This payload can be passed through to this function to see if there's +// filesystem-related information about the error to return. +// +// Note that this function is fallible because not all stream-related +// errors are filesystem-related errors. +// +// filesystem-error-code: func(err: borrow) -> option +// +//go:nosplit +func FilesystemErrorCode(err Error) (result cm.Option[ErrorCode]) { + err0 := cm.Reinterpret[uint32](err) + wasmimport_FilesystemErrorCode((uint32)(err0), &result) + return +} diff --git a/src/internal/wasi/io/v0.2.0/error/empty.s b/src/internal/wasi/io/v0.2.0/error/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/error/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/io/v0.2.0/error/error.wasm.go b/src/internal/wasi/io/v0.2.0/error/error.wasm.go new file mode 100644 index 0000000000..e254b5d86f --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/error/error.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package ioerror + +// This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". + +//go:wasmimport wasi:io/error@0.2.0 [resource-drop]error +//go:noescape +func wasmimport_ErrorResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/error@0.2.0 [method]error.to-debug-string +//go:noescape +func wasmimport_ErrorToDebugString(self0 uint32, result *string) diff --git a/src/internal/wasi/io/v0.2.0/error/error.wit.go b/src/internal/wasi/io/v0.2.0/error/error.wit.go new file mode 100644 index 0000000000..20827b053a --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/error/error.wit.go @@ -0,0 +1,63 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package ioerror represents the imported interface "wasi:io/error@0.2.0". +package ioerror + +import ( + "internal/cm" +) + +// Error represents the imported resource "wasi:io/error@0.2.0#error". +// +// A resource which represents some error information. +// +// The only method provided by this resource is `to-debug-string`, +// which provides some human-readable information about the error. +// +// In the `wasi:io` package, this resource is returned through the +// `wasi:io/streams/stream-error` type. +// +// To provide more specific error information, other interfaces may +// provide functions to further "downcast" this error into more specific +// error information. For example, `error`s returned in streams derived +// from filesystem types to be described using the filesystem's own +// error-code type, using the function +// `wasi:filesystem/types/filesystem-error-code`, which takes a parameter +// `borrow` and returns +// `option`. +// +// The set of functions which can "downcast" an `error` into a more +// concrete type is open. +// +// resource error +type Error cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "error". +// +// Drops a resource handle. +// +//go:nosplit +func (self Error) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ErrorResourceDrop((uint32)(self0)) + return +} + +// ToDebugString represents the imported method "to-debug-string". +// +// Returns a string that is suitable to assist humans in debugging +// this error. +// +// WARNING: The returned string should not be consumed mechanically! +// It may change across platforms, hosts, or other implementation +// details. Parsing this string is a major platform-compatibility +// hazard. +// +// to-debug-string: func() -> string +// +//go:nosplit +func (self Error) ToDebugString() (result string) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ErrorToDebugString((uint32)(self0), &result) + return +} diff --git a/src/internal/wasi/io/v0.2.0/poll/empty.s b/src/internal/wasi/io/v0.2.0/poll/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/poll/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go new file mode 100644 index 0000000000..d807d77280 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wasm.go @@ -0,0 +1,25 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package poll + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". + +//go:wasmimport wasi:io/poll@0.2.0 [resource-drop]pollable +//go:noescape +func wasmimport_PollableResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.block +//go:noescape +func wasmimport_PollableBlock(self0 uint32) + +//go:wasmimport wasi:io/poll@0.2.0 [method]pollable.ready +//go:noescape +func wasmimport_PollableReady(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:io/poll@0.2.0 poll +//go:noescape +func wasmimport_Poll(in0 *Pollable, in1 uint32, result *cm.List[uint32]) diff --git a/src/internal/wasi/io/v0.2.0/poll/poll.wit.go b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go new file mode 100644 index 0000000000..4261f161a2 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/poll/poll.wit.go @@ -0,0 +1,92 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package poll represents the imported interface "wasi:io/poll@0.2.0". +// +// A poll API intended to let users wait for I/O events on multiple handles +// at once. +package poll + +import ( + "internal/cm" +) + +// Pollable represents the imported resource "wasi:io/poll@0.2.0#pollable". +// +// `pollable` represents a single I/O event which may be ready, or not. +// +// resource pollable +type Pollable cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "pollable". +// +// Drops a resource handle. +// +//go:nosplit +func (self Pollable) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_PollableResourceDrop((uint32)(self0)) + return +} + +// Block represents the imported method "block". +// +// `block` returns immediately if the pollable is ready, and otherwise +// blocks until ready. +// +// This function is equivalent to calling `poll.poll` on a list +// containing only this pollable. +// +// block: func() +// +//go:nosplit +func (self Pollable) Block() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_PollableBlock((uint32)(self0)) + return +} + +// Ready represents the imported method "ready". +// +// Return the readiness of a pollable. This function never blocks. +// +// Returns `true` when the pollable is ready, and `false` otherwise. +// +// ready: func() -> bool +// +//go:nosplit +func (self Pollable) Ready() (result bool) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_PollableReady((uint32)(self0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) + return +} + +// Poll represents the imported function "poll". +// +// Poll for completion on a set of pollables. +// +// This function takes a list of pollables, which identify I/O sources of +// interest, and waits until one or more of the events is ready for I/O. +// +// The result `list` contains one or more indices of handles in the +// argument list that is ready for I/O. +// +// If the list contains more elements than can be indexed with a `u32` +// value, this function traps. +// +// A timeout can be implemented by adding a pollable from the +// wasi-clocks API to the list. +// +// This function does not return a `result`; polling in itself does not +// do any I/O so it doesn't fail. If any of the I/O sources identified by +// the pollables has an error, it is indicated by marking the source as +// being reaedy for I/O. +// +// poll: func(in: list>) -> list +// +//go:nosplit +func Poll(in cm.List[Pollable]) (result cm.List[uint32]) { + in0, in1 := cm.LowerList(in) + wasmimport_Poll((*Pollable)(in0), (uint32)(in1), &result) + return +} diff --git a/src/internal/wasi/io/v0.2.0/streams/empty.s b/src/internal/wasi/io/v0.2.0/streams/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/streams/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go new file mode 100644 index 0000000000..c317ea5c15 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wasm.go @@ -0,0 +1,77 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package streams + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:io@0.2.0". + +//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]input-stream +//go:noescape +func wasmimport_InputStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-read +//go:noescape +func wasmimport_InputStreamBlockingRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.blocking-skip +//go:noescape +func wasmimport_InputStreamBlockingSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.read +//go:noescape +func wasmimport_InputStreamRead(self0 uint32, len0 uint64, result *cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.skip +//go:noescape +func wasmimport_InputStreamSkip(self0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]input-stream.subscribe +//go:noescape +func wasmimport_InputStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [resource-drop]output-stream +//go:noescape +func wasmimport_OutputStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-flush +//go:noescape +func wasmimport_OutputStreamBlockingFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-splice +//go:noescape +func wasmimport_OutputStreamBlockingSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-and-flush +//go:noescape +func wasmimport_OutputStreamBlockingWriteAndFlush(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.blocking-write-zeroes-and-flush +//go:noescape +func wasmimport_OutputStreamBlockingWriteZeroesAndFlush(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.check-write +//go:noescape +func wasmimport_OutputStreamCheckWrite(self0 uint32, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.flush +//go:noescape +func wasmimport_OutputStreamFlush(self0 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.splice +//go:noescape +func wasmimport_OutputStreamSplice(self0 uint32, src0 uint32, len0 uint64, result *cm.Result[uint64, uint64, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.subscribe +//go:noescape +func wasmimport_OutputStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write +//go:noescape +func wasmimport_OutputStreamWrite(self0 uint32, contents0 *uint8, contents1 uint32, result *cm.Result[StreamError, struct{}, StreamError]) + +//go:wasmimport wasi:io/streams@0.2.0 [method]output-stream.write-zeroes +//go:noescape +func wasmimport_OutputStreamWriteZeroes(self0 uint32, len0 uint64, result *cm.Result[StreamError, struct{}, StreamError]) diff --git a/src/internal/wasi/io/v0.2.0/streams/streams.wit.go b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go new file mode 100644 index 0000000000..e67a512c66 --- /dev/null +++ b/src/internal/wasi/io/v0.2.0/streams/streams.wit.go @@ -0,0 +1,471 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package streams represents the imported interface "wasi:io/streams@0.2.0". +// +// WASI I/O is an I/O abstraction API which is currently focused on providing +// stream types. +// +// In the future, the component model is expected to add built-in stream types; +// when it does, they are expected to subsume this API. +package streams + +import ( + "internal/cm" + ioerror "internal/wasi/io/v0.2.0/error" + "internal/wasi/io/v0.2.0/poll" +) + +// Error represents the imported type alias "wasi:io/streams@0.2.0#error". +// +// See [ioerror.Error] for more information. +type Error = ioerror.Error + +// Pollable represents the imported type alias "wasi:io/streams@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// StreamError represents the imported variant "wasi:io/streams@0.2.0#stream-error". +// +// An error for input-stream and output-stream operations. +// +// variant stream-error { +// last-operation-failed(error), +// closed, +// } +type StreamError cm.Variant[uint8, Error, Error] + +// StreamErrorLastOperationFailed returns a [StreamError] of case "last-operation-failed". +// +// The last operation (a write or flush) failed before completion. +// +// More information is available in the `error` payload. +func StreamErrorLastOperationFailed(data Error) StreamError { + return cm.New[StreamError](0, data) +} + +// LastOperationFailed returns a non-nil *[Error] if [StreamError] represents the variant case "last-operation-failed". +func (self *StreamError) LastOperationFailed() *Error { + return cm.Case[Error](self, 0) +} + +// StreamErrorClosed returns a [StreamError] of case "closed". +// +// The stream is closed: no more input will be accepted by the +// stream. A closed output-stream will return this error on all +// future operations. +func StreamErrorClosed() StreamError { + var data struct{} + return cm.New[StreamError](1, data) +} + +// Closed returns true if [StreamError] represents the variant case "closed". +func (self *StreamError) Closed() bool { + return self.Tag() == 1 +} + +var _StreamErrorStrings = [2]string{ + "last-operation-failed", + "closed", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v StreamError) String() string { + return _StreamErrorStrings[v.Tag()] +} + +// InputStream represents the imported resource "wasi:io/streams@0.2.0#input-stream". +// +// An input bytestream. +// +// `input-stream`s are *non-blocking* to the extent practical on underlying +// platforms. I/O operations always return promptly; if fewer bytes are +// promptly available than requested, they return the number of bytes promptly +// available, which could even be zero. To wait for data to be available, +// use the `subscribe` function to obtain a `pollable` which can be polled +// for using `wasi:io/poll`. +// +// resource input-stream +type InputStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "input-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self InputStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_InputStreamResourceDrop((uint32)(self0)) + return +} + +// BlockingRead represents the imported method "blocking-read". +// +// Read bytes from a stream, after blocking until at least one byte can +// be read. Except for blocking, behavior is identical to `read`. +// +// blocking-read: func(len: u64) -> result, stream-error> +// +//go:nosplit +func (self InputStream) BlockingRead(len_ uint64) (result cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamBlockingRead((uint32)(self0), (uint64)(len0), &result) + return +} + +// BlockingSkip represents the imported method "blocking-skip". +// +// Skip bytes from a stream, after blocking until at least one byte +// can be skipped. Except for blocking behavior, identical to `skip`. +// +// blocking-skip: func(len: u64) -> result +// +//go:nosplit +func (self InputStream) BlockingSkip(len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamBlockingSkip((uint32)(self0), (uint64)(len0), &result) + return +} + +// Read represents the imported method "read". +// +// Perform a non-blocking read from the stream. +// +// When the source of a `read` is binary data, the bytes from the source +// are returned verbatim. When the source of a `read` is known to the +// implementation to be text, bytes containing the UTF-8 encoding of the +// text are returned. +// +// This function returns a list of bytes containing the read data, +// when successful. The returned list will contain up to `len` bytes; +// it may return fewer than requested, but not more. The list is +// empty when no bytes are available for reading at this time. The +// pollable given by `subscribe` will be ready when more bytes are +// available. +// +// This function fails with a `stream-error` when the operation +// encounters an error, giving `last-operation-failed`, or when the +// stream is closed, giving `closed`. +// +// When the caller gives a `len` of 0, it represents a request to +// read 0 bytes. If the stream is still open, this call should +// succeed and return an empty list, or otherwise fail with `closed`. +// +// The `len` parameter is a `u64`, which could represent a list of u8 which +// is not possible to allocate in wasm32, or not desirable to allocate as +// as a return value by the callee. The callee may return a list of bytes +// less than `len` in size while more bytes are available for reading. +// +// read: func(len: u64) -> result, stream-error> +// +//go:nosplit +func (self InputStream) Read(len_ uint64) (result cm.Result[cm.List[uint8], cm.List[uint8], StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamRead((uint32)(self0), (uint64)(len0), &result) + return +} + +// Skip represents the imported method "skip". +// +// Skip bytes from a stream. Returns number of bytes skipped. +// +// Behaves identical to `read`, except instead of returning a list +// of bytes, returns the number of bytes consumed from the stream. +// +// skip: func(len: u64) -> result +// +//go:nosplit +func (self InputStream) Skip(len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_InputStreamSkip((uint32)(self0), (uint64)(len0), &result) + return +} + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once either the specified stream +// has bytes available to read or the other end of the stream has been +// closed. +// The created `pollable` is a child resource of the `input-stream`. +// Implementations may trap if the `input-stream` is dropped before +// all derived `pollable`s created with this function are dropped. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self InputStream) Subscribe() (result Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_InputStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} + +// OutputStream represents the imported resource "wasi:io/streams@0.2.0#output-stream". +// +// An output bytestream. +// +// `output-stream`s are *non-blocking* to the extent practical on +// underlying platforms. Except where specified otherwise, I/O operations also +// always return promptly, after the number of bytes that can be written +// promptly, which could even be zero. To wait for the stream to be ready to +// accept data, the `subscribe` function to obtain a `pollable` which can be +// polled for using `wasi:io/poll`. +// +// resource output-stream +type OutputStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "output-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self OutputStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamResourceDrop((uint32)(self0)) + return +} + +// BlockingFlush represents the imported method "blocking-flush". +// +// Request to flush buffered output, and block until flush completes +// and stream is ready for writing again. +// +// blocking-flush: func() -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) BlockingFlush() (result cm.Result[StreamError, struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamBlockingFlush((uint32)(self0), &result) + return +} + +// BlockingSplice represents the imported method "blocking-splice". +// +// Read from one stream and write to another, with blocking. +// +// This is similar to `splice`, except that it blocks until the +// `output-stream` is ready for writing, and the `input-stream` +// is ready for reading, before performing the `splice`. +// +// blocking-splice: func(src: borrow, len: u64) -> result +// +//go:nosplit +func (self OutputStream) BlockingSplice(src InputStream, len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + src0 := cm.Reinterpret[uint32](src) + len0 := (uint64)(len_) + wasmimport_OutputStreamBlockingSplice((uint32)(self0), (uint32)(src0), (uint64)(len0), &result) + return +} + +// BlockingWriteAndFlush represents the imported method "blocking-write-and-flush". +// +// Perform a write of up to 4096 bytes, and then flush the stream. Block +// until all of these operations are complete, or an error occurs. +// +// This is a convenience wrapper around the use of `check-write`, +// `subscribe`, `write`, and `flush`, and is implemented with the +// following pseudo-code: +// +// let pollable = this.subscribe(); +// while !contents.is_empty() { +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, contents.len()); +// let (chunk, rest) = contents.split_at(len); +// this.write(chunk ); // eliding error handling +// contents = rest; +// } +// this.flush(); +// // Wait for completion of `flush` +// pollable.block(); +// // Check for any errors that arose during `flush` +// let _ = this.check-write(); // eliding error handling +// +// blocking-write-and-flush: func(contents: list) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) BlockingWriteAndFlush(contents cm.List[uint8]) (result cm.Result[StreamError, struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + contents0, contents1 := cm.LowerList(contents) + wasmimport_OutputStreamBlockingWriteAndFlush((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) + return +} + +// BlockingWriteZeroesAndFlush represents the imported method "blocking-write-zeroes-and-flush". +// +// Perform a write of up to 4096 zeroes, and then flush the stream. +// Block until all of these operations are complete, or an error +// occurs. +// +// This is a convenience wrapper around the use of `check-write`, +// `subscribe`, `write-zeroes`, and `flush`, and is implemented with +// the following pseudo-code: +// +// let pollable = this.subscribe(); +// while num_zeroes != 0 { +// // Wait for the stream to become writable +// pollable.block(); +// let Ok(n) = this.check-write(); // eliding error handling +// let len = min(n, num_zeroes); +// this.write-zeroes(len); // eliding error handling +// num_zeroes -= len; +// } +// this.flush(); +// // Wait for completion of `flush` +// pollable.block(); +// // Check for any errors that arose during `flush` +// let _ = this.check-write(); // eliding error handling +// +// blocking-write-zeroes-and-flush: func(len: u64) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) BlockingWriteZeroesAndFlush(len_ uint64) (result cm.Result[StreamError, struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_OutputStreamBlockingWriteZeroesAndFlush((uint32)(self0), (uint64)(len0), &result) + return +} + +// CheckWrite represents the imported method "check-write". +// +// Check readiness for writing. This function never blocks. +// +// Returns the number of bytes permitted for the next call to `write`, +// or an error. Calling `write` with more bytes than this function has +// permitted will trap. +// +// When this function returns 0 bytes, the `subscribe` pollable will +// become ready when this function will report at least 1 byte, or an +// error. +// +// check-write: func() -> result +// +//go:nosplit +func (self OutputStream) CheckWrite() (result cm.Result[uint64, uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamCheckWrite((uint32)(self0), &result) + return +} + +// Flush represents the imported method "flush". +// +// Request to flush buffered output. This function never blocks. +// +// This tells the output-stream that the caller intends any buffered +// output to be flushed. the output which is expected to be flushed +// is all that has been passed to `write` prior to this call. +// +// Upon calling this function, the `output-stream` will not accept any +// writes (`check-write` will return `ok(0)`) until the flush has +// completed. The `subscribe` pollable will become ready when the +// flush has completed and the stream can accept more writes. +// +// flush: func() -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) Flush() (result cm.Result[StreamError, struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutputStreamFlush((uint32)(self0), &result) + return +} + +// Splice represents the imported method "splice". +// +// Read from one stream and write to another. +// +// The behavior of splice is equivelant to: +// 1. calling `check-write` on the `output-stream` +// 2. calling `read` on the `input-stream` with the smaller of the +// `check-write` permitted length and the `len` provided to `splice` +// 3. calling `write` on the `output-stream` with that read data. +// +// Any error reported by the call to `check-write`, `read`, or +// `write` ends the splice and reports that error. +// +// This function returns the number of bytes transferred; it may be less +// than `len`. +// +// splice: func(src: borrow, len: u64) -> result +// +//go:nosplit +func (self OutputStream) Splice(src InputStream, len_ uint64) (result cm.Result[uint64, uint64, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + src0 := cm.Reinterpret[uint32](src) + len0 := (uint64)(len_) + wasmimport_OutputStreamSplice((uint32)(self0), (uint32)(src0), (uint64)(len0), &result) + return +} + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the output-stream +// is ready for more writing, or an error has occured. When this +// pollable is ready, `check-write` will return `ok(n)` with n>0, or an +// error. +// +// If the stream is closed, this pollable is always ready immediately. +// +// The created `pollable` is a child resource of the `output-stream`. +// Implementations may trap if the `output-stream` is dropped before +// all derived `pollable`s created with this function are dropped. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self OutputStream) Subscribe() (result Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_OutputStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} + +// Write represents the imported method "write". +// +// Perform a write. This function never blocks. +// +// When the destination of a `write` is binary data, the bytes from +// `contents` are written verbatim. When the destination of a `write` is +// known to the implementation to be text, the bytes of `contents` are +// transcoded from UTF-8 into the encoding of the destination and then +// written. +// +// Precondition: check-write gave permit of Ok(n) and contents has a +// length of less than or equal to n. Otherwise, this function will trap. +// +// returns Err(closed) without writing if the stream has closed since +// the last call to check-write provided a permit. +// +// write: func(contents: list) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) Write(contents cm.List[uint8]) (result cm.Result[StreamError, struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + contents0, contents1 := cm.LowerList(contents) + wasmimport_OutputStreamWrite((uint32)(self0), (*uint8)(contents0), (uint32)(contents1), &result) + return +} + +// WriteZeroes represents the imported method "write-zeroes". +// +// Write zeroes to a stream. +// +// This should be used precisely like `write` with the exact same +// preconditions (must use check-write first), but instead of +// passing a list of bytes, you simply pass the number of zero-bytes +// that should be written. +// +// write-zeroes: func(len: u64) -> result<_, stream-error> +// +//go:nosplit +func (self OutputStream) WriteZeroes(len_ uint64) (result cm.Result[StreamError, struct{}, StreamError]) { + self0 := cm.Reinterpret[uint32](self) + len0 := (uint64)(len_) + wasmimport_OutputStreamWriteZeroes((uint32)(self0), (uint64)(len0), &result) + return +} diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go new file mode 100644 index 0000000000..e94356df03 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package insecureseed + +// This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". + +//go:wasmimport wasi:random/insecure-seed@0.2.0 insecure-seed +//go:noescape +func wasmimport_InsecureSeed(result *[2]uint64) diff --git a/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go new file mode 100644 index 0000000000..6f36367382 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure-seed/insecure-seed.wit.go @@ -0,0 +1,37 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package insecureseed represents the imported interface "wasi:random/insecure-seed@0.2.0". +// +// The insecure-seed interface for seeding hash-map DoS resistance. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +package insecureseed + +// InsecureSeed represents the imported function "insecure-seed". +// +// Return a 128-bit value that may contain a pseudo-random value. +// +// The returned value is not required to be computed from a CSPRNG, and may +// even be entirely deterministic. Host implementations are encouraged to +// provide pseudo-random values to any program exposed to +// attacker-controlled content, to enable DoS protection built into many +// languages' hash-map implementations. +// +// This function is intended to only be called once, by a source language +// to initialize Denial Of Service (DoS) protection in its hash-map +// implementation. +// +// # Expected future evolution +// +// This will likely be changed to a value import, to prevent it from being +// called multiple times and potentially used for purposes other than DoS +// protection. +// +// insecure-seed: func() -> tuple +// +//go:nosplit +func InsecureSeed() (result [2]uint64) { + wasmimport_InsecureSeed(&result) + return +} diff --git a/src/internal/wasi/random/v0.2.0/insecure/empty.s b/src/internal/wasi/random/v0.2.0/insecure/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go new file mode 100644 index 0000000000..ea62b81317 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure/insecure.wasm.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package insecure + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". + +//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-bytes +//go:noescape +func wasmimport_GetInsecureRandomBytes(len0 uint64, result *cm.List[uint8]) + +//go:wasmimport wasi:random/insecure@0.2.0 get-insecure-random-u64 +//go:noescape +func wasmimport_GetInsecureRandomU64() (result0 uint64) diff --git a/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go new file mode 100644 index 0000000000..625ca5a905 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/insecure/insecure.wit.go @@ -0,0 +1,49 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package insecure represents the imported interface "wasi:random/insecure@0.2.0". +// +// The insecure interface for insecure pseudo-random numbers. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +package insecure + +import ( + "internal/cm" +) + +// GetInsecureRandomBytes represents the imported function "get-insecure-random-bytes". +// +// Return `len` insecure pseudo-random bytes. +// +// This function is not cryptographically secure. Do not use it for +// anything related to security. +// +// There are no requirements on the values of the returned bytes, however +// implementations are encouraged to return evenly distributed values with +// a long period. +// +// get-insecure-random-bytes: func(len: u64) -> list +// +//go:nosplit +func GetInsecureRandomBytes(len_ uint64) (result cm.List[uint8]) { + len0 := (uint64)(len_) + wasmimport_GetInsecureRandomBytes((uint64)(len0), &result) + return +} + +// GetInsecureRandomU64 represents the imported function "get-insecure-random-u64". +// +// Return an insecure pseudo-random `u64` value. +// +// This function returns the same type of pseudo-random data as +// `get-insecure-random-bytes`, represented as a `u64`. +// +// get-insecure-random-u64: func() -> u64 +// +//go:nosplit +func GetInsecureRandomU64() (result uint64) { + result0 := wasmimport_GetInsecureRandomU64() + result = (uint64)((uint64)(result0)) + return +} diff --git a/src/internal/wasi/random/v0.2.0/random/empty.s b/src/internal/wasi/random/v0.2.0/random/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/random/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/random/v0.2.0/random/random.wasm.go b/src/internal/wasi/random/v0.2.0/random/random.wasm.go new file mode 100644 index 0000000000..1738d49aee --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/random/random.wasm.go @@ -0,0 +1,17 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package random + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:random@0.2.0". + +//go:wasmimport wasi:random/random@0.2.0 get-random-bytes +//go:noescape +func wasmimport_GetRandomBytes(len0 uint64, result *cm.List[uint8]) + +//go:wasmimport wasi:random/random@0.2.0 get-random-u64 +//go:noescape +func wasmimport_GetRandomU64() (result0 uint64) diff --git a/src/internal/wasi/random/v0.2.0/random/random.wit.go b/src/internal/wasi/random/v0.2.0/random/random.wit.go new file mode 100644 index 0000000000..49b054bd24 --- /dev/null +++ b/src/internal/wasi/random/v0.2.0/random/random.wit.go @@ -0,0 +1,53 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package random represents the imported interface "wasi:random/random@0.2.0". +// +// WASI Random is a random data API. +// +// It is intended to be portable at least between Unix-family platforms and +// Windows. +package random + +import ( + "internal/cm" +) + +// GetRandomBytes represents the imported function "get-random-bytes". +// +// Return `len` cryptographically-secure random or pseudo-random bytes. +// +// This function must produce data at least as cryptographically secure and +// fast as an adequately seeded cryptographically-secure pseudo-random +// number generator (CSPRNG). It must not block, from the perspective of +// the calling program, under any circumstances, including on the first +// request and on requests for numbers of bytes. The returned data must +// always be unpredictable. +// +// This function must always return fresh data. Deterministic environments +// must omit this function, rather than implementing it with deterministic +// data. +// +// get-random-bytes: func(len: u64) -> list +// +//go:nosplit +func GetRandomBytes(len_ uint64) (result cm.List[uint8]) { + len0 := (uint64)(len_) + wasmimport_GetRandomBytes((uint64)(len0), &result) + return +} + +// GetRandomU64 represents the imported function "get-random-u64". +// +// Return a cryptographically-secure random or pseudo-random `u64` value. +// +// This function returns the same type of data as `get-random-bytes`, +// represented as a `u64`. +// +// get-random-u64: func() -> u64 +// +//go:nosplit +func GetRandomU64() (result uint64) { + result0 := wasmimport_GetRandomU64() + result = (uint64)((uint64)(result0)) + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go new file mode 100644 index 0000000000..eb113e217c --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package instancenetwork + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/instance-network@0.2.0 instance-network +//go:noescape +func wasmimport_InstanceNetwork() (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go new file mode 100644 index 0000000000..378bba6896 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/instance-network/instance-network.wit.go @@ -0,0 +1,29 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package instancenetwork represents the imported interface "wasi:sockets/instance-network@0.2.0". +// +// This interface provides a value-export of the default network handle.. +package instancenetwork + +import ( + "internal/cm" + "internal/wasi/sockets/v0.2.0/network" +) + +// Network represents the imported type alias "wasi:sockets/instance-network@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// InstanceNetwork represents the imported function "instance-network". +// +// Get a handle to the default network. +// +// instance-network: func() -> network +// +//go:nosplit +func InstanceNetwork() (result Network) { + result0 := wasmimport_InstanceNetwork() + result = cm.Reinterpret[Network]((uint32)(result0)) + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go new file mode 100644 index 0000000000..3d73f356a0 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/abi.go @@ -0,0 +1,14 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package ipnamelookup + +import ( + "internal/cm" + "unsafe" +) + +// OptionIPAddressShape is used for storage in variant or result types. +type OptionIPAddressShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Option[IPAddress]{})]byte +} diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go new file mode 100644 index 0000000000..6693408f69 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wasm.go @@ -0,0 +1,25 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package ipnamelookup + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [resource-drop]resolve-address-stream +//go:noescape +func wasmimport_ResolveAddressStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.resolve-next-address +//go:noescape +func wasmimport_ResolveAddressStreamResolveNextAddress(self0 uint32, result *cm.Result[OptionIPAddressShape, cm.Option[IPAddress], ErrorCode]) + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 [method]resolve-address-stream.subscribe +//go:noescape +func wasmimport_ResolveAddressStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/ip-name-lookup@0.2.0 resolve-addresses +//go:noescape +func wasmimport_ResolveAddresses(network0 uint32, name0 *uint8, name1 uint32, result *cm.Result[ResolveAddressStream, ResolveAddressStream, ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go new file mode 100644 index 0000000000..6e982857da --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/ip-name-lookup/ip-name-lookup.wit.go @@ -0,0 +1,125 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package ipnamelookup represents the imported interface "wasi:sockets/ip-name-lookup@0.2.0". +package ipnamelookup + +import ( + "internal/cm" + "internal/wasi/io/v0.2.0/poll" + "internal/wasi/sockets/v0.2.0/network" +) + +// Pollable represents the imported type alias "wasi:sockets/ip-name-lookup@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// Network represents the imported type alias "wasi:sockets/ip-name-lookup@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/ip-name-lookup@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPAddress represents the type alias "wasi:sockets/ip-name-lookup@0.2.0#ip-address". +// +// See [network.IPAddress] for more information. +type IPAddress = network.IPAddress + +// ResolveAddressStream represents the imported resource "wasi:sockets/ip-name-lookup@0.2.0#resolve-address-stream". +// +// resource resolve-address-stream +type ResolveAddressStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "resolve-address-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self ResolveAddressStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ResolveAddressStreamResourceDrop((uint32)(self0)) + return +} + +// ResolveNextAddress represents the imported method "resolve-next-address". +// +// Returns the next address from the resolver. +// +// This function should be called multiple times. On each call, it will +// return the next address in connection order preference. If all +// addresses have been exhausted, this function returns `none`. +// +// This function never returns IPv4-mapped IPv6 addresses. +// +// # Typical errors +// - `name-unresolvable`: Name does not exist or has no suitable associated +// IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) +// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. +// (EAI_AGAIN) +// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. +// (EAI_FAIL) +// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) +// +// resolve-next-address: func() -> result, error-code> +// +//go:nosplit +func (self ResolveAddressStream) ResolveNextAddress() (result cm.Result[OptionIPAddressShape, cm.Option[IPAddress], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_ResolveAddressStreamResolveNextAddress((uint32)(self0), &result) + return +} + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the stream is ready for I/O. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self ResolveAddressStream) Subscribe() (result Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_ResolveAddressStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} + +// ResolveAddresses represents the imported function "resolve-addresses". +// +// Resolve an internet host name to a list of IP addresses. +// +// Unicode domain names are automatically converted to ASCII using IDNA encoding. +// If the input is an IP address string, the address is parsed and returned +// as-is without making any external requests. +// +// See the wasi-socket proposal README.md for a comparison with getaddrinfo. +// +// This function never blocks. It either immediately fails or immediately +// returns successfully with a `resolve-address-stream` that can be used +// to (asynchronously) fetch the results. +// +// # Typical errors +// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. +// +// # References: +// - +// - +// - +// - +// +// resolve-addresses: func(network: borrow, name: string) -> result +// +//go:nosplit +func ResolveAddresses(network_ Network, name string) (result cm.Result[ResolveAddressStream, ResolveAddressStream, ErrorCode]) { + network0 := cm.Reinterpret[uint32](network_) + name0, name1 := cm.LowerString(name) + wasmimport_ResolveAddresses((uint32)(network0), (*uint8)(name0), (uint32)(name1), &result) + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/network/abi.go b/src/internal/wasi/sockets/v0.2.0/network/abi.go new file mode 100644 index 0000000000..0f42e109db --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/abi.go @@ -0,0 +1,14 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package network + +import ( + "internal/cm" + "unsafe" +) + +// IPv6SocketAddressShape is used for storage in variant or result types. +type IPv6SocketAddressShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(IPv6SocketAddress{})]byte +} diff --git a/src/internal/wasi/sockets/v0.2.0/network/empty.s b/src/internal/wasi/sockets/v0.2.0/network/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go new file mode 100644 index 0000000000..012a79ffc7 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wasm.go @@ -0,0 +1,9 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package network + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/network@0.2.0 [resource-drop]network +//go:noescape +func wasmimport_NetworkResourceDrop(self0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/network/network.wit.go b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go new file mode 100644 index 0000000000..987c4c4c35 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/network/network.wit.go @@ -0,0 +1,359 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package network represents the imported interface "wasi:sockets/network@0.2.0". +package network + +import ( + "internal/cm" +) + +// Network represents the imported resource "wasi:sockets/network@0.2.0#network". +// +// An opaque resource that represents access to (a subset of) the network. +// This enables context-based security for networking. +// There is no need for this to map 1:1 to a physical network interface. +// +// resource network +type Network cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "network". +// +// Drops a resource handle. +// +//go:nosplit +func (self Network) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_NetworkResourceDrop((uint32)(self0)) + return +} + +// ErrorCode represents the enum "wasi:sockets/network@0.2.0#error-code". +// +// Error codes. +// +// In theory, every API can return any error code. +// In practice, API's typically only return the errors documented per API +// combined with a couple of errors that are always possible: +// - `unknown` +// - `access-denied` +// - `not-supported` +// - `out-of-memory` +// - `concurrency-conflict` +// +// See each individual API for what the POSIX equivalents are. They sometimes differ +// per API. +// +// enum error-code { +// unknown, +// access-denied, +// not-supported, +// invalid-argument, +// out-of-memory, +// timeout, +// concurrency-conflict, +// not-in-progress, +// would-block, +// invalid-state, +// new-socket-limit, +// address-not-bindable, +// address-in-use, +// remote-unreachable, +// connection-refused, +// connection-reset, +// connection-aborted, +// datagram-too-large, +// name-unresolvable, +// temporary-resolver-failure, +// permanent-resolver-failure +// } +type ErrorCode uint8 + +const ( + // Unknown error + ErrorCodeUnknown ErrorCode = iota + + // Access denied. + // + // POSIX equivalent: EACCES, EPERM + ErrorCodeAccessDenied + + // The operation is not supported. + // + // POSIX equivalent: EOPNOTSUPP + ErrorCodeNotSupported + + // One of the arguments is invalid. + // + // POSIX equivalent: EINVAL + ErrorCodeInvalidArgument + + // Not enough memory to complete the operation. + // + // POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + ErrorCodeOutOfMemory + + // The operation timed out before it could finish completely. + ErrorCodeTimeout + + // This operation is incompatible with another asynchronous operation that is already + // in progress. + // + // POSIX equivalent: EALREADY + ErrorCodeConcurrencyConflict + + // Trying to finish an asynchronous operation that: + // - has not been started yet, or: + // - was already finished by a previous `finish-*` call. + // + // Note: this is scheduled to be removed when `future`s are natively supported. + ErrorCodeNotInProgress + + // The operation has been aborted because it could not be completed immediately. + // + // Note: this is scheduled to be removed when `future`s are natively supported. + ErrorCodeWouldBlock + + // The operation is not valid in the socket's current state. + ErrorCodeInvalidState + + // A new socket resource could not be created because of a system limit. + ErrorCodeNewSocketLimit + + // A bind operation failed because the provided address is not an address that the + // `network` can bind to. + ErrorCodeAddressNotBindable + + // A bind operation failed because the provided address is already in use or because + // there are no ephemeral ports available. + ErrorCodeAddressInUse + + // The remote address is not reachable + ErrorCodeRemoteUnreachable + + // The TCP connection was forcefully rejected + ErrorCodeConnectionRefused + + // The TCP connection was reset. + ErrorCodeConnectionReset + + // A TCP connection was aborted. + ErrorCodeConnectionAborted + + // The size of a datagram sent to a UDP socket exceeded the maximum + // supported size. + ErrorCodeDatagramTooLarge + + // Name does not exist or has no suitable associated IP addresses. + ErrorCodeNameUnresolvable + + // A temporary failure in name resolution occurred. + ErrorCodeTemporaryResolverFailure + + // A permanent failure in name resolution occurred. + ErrorCodePermanentResolverFailure +) + +var _ErrorCodeStrings = [21]string{ + "unknown", + "access-denied", + "not-supported", + "invalid-argument", + "out-of-memory", + "timeout", + "concurrency-conflict", + "not-in-progress", + "would-block", + "invalid-state", + "new-socket-limit", + "address-not-bindable", + "address-in-use", + "remote-unreachable", + "connection-refused", + "connection-reset", + "connection-aborted", + "datagram-too-large", + "name-unresolvable", + "temporary-resolver-failure", + "permanent-resolver-failure", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e ErrorCode) String() string { + return _ErrorCodeStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e ErrorCode) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ErrorCode) UnmarshalText(text []byte) error { + return _ErrorCodeUnmarshalCase(e, text) +} + +var _ErrorCodeUnmarshalCase = cm.CaseUnmarshaler[ErrorCode](_ErrorCodeStrings[:]) + +// IPAddressFamily represents the enum "wasi:sockets/network@0.2.0#ip-address-family". +// +// enum ip-address-family { +// ipv4, +// ipv6 +// } +type IPAddressFamily uint8 + +const ( + // Similar to `AF_INET` in POSIX. + IPAddressFamilyIPv4 IPAddressFamily = iota + + // Similar to `AF_INET6` in POSIX. + IPAddressFamilyIPv6 +) + +var _IPAddressFamilyStrings = [2]string{ + "ipv4", + "ipv6", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e IPAddressFamily) String() string { + return _IPAddressFamilyStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e IPAddressFamily) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *IPAddressFamily) UnmarshalText(text []byte) error { + return _IPAddressFamilyUnmarshalCase(e, text) +} + +var _IPAddressFamilyUnmarshalCase = cm.CaseUnmarshaler[IPAddressFamily](_IPAddressFamilyStrings[:]) + +// IPv4Address represents the tuple "wasi:sockets/network@0.2.0#ipv4-address". +// +// type ipv4-address = tuple +type IPv4Address [4]uint8 + +// IPv6Address represents the tuple "wasi:sockets/network@0.2.0#ipv6-address". +// +// type ipv6-address = tuple +type IPv6Address [8]uint16 + +// IPAddress represents the variant "wasi:sockets/network@0.2.0#ip-address". +// +// variant ip-address { +// ipv4(ipv4-address), +// ipv6(ipv6-address), +// } +type IPAddress cm.Variant[uint8, IPv6Address, IPv6Address] + +// IPAddressIPv4 returns a [IPAddress] of case "ipv4". +func IPAddressIPv4(data IPv4Address) IPAddress { + return cm.New[IPAddress](0, data) +} + +// IPv4 returns a non-nil *[IPv4Address] if [IPAddress] represents the variant case "ipv4". +func (self *IPAddress) IPv4() *IPv4Address { + return cm.Case[IPv4Address](self, 0) +} + +// IPAddressIPv6 returns a [IPAddress] of case "ipv6". +func IPAddressIPv6(data IPv6Address) IPAddress { + return cm.New[IPAddress](1, data) +} + +// IPv6 returns a non-nil *[IPv6Address] if [IPAddress] represents the variant case "ipv6". +func (self *IPAddress) IPv6() *IPv6Address { + return cm.Case[IPv6Address](self, 1) +} + +var _IPAddressStrings = [2]string{ + "ipv4", + "ipv6", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v IPAddress) String() string { + return _IPAddressStrings[v.Tag()] +} + +// IPv4SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv4-socket-address". +// +// record ipv4-socket-address { +// port: u16, +// address: ipv4-address, +// } +type IPv4SocketAddress struct { + _ cm.HostLayout `json:"-"` + // sin_port + Port uint16 `json:"port"` + + // sin_addr + Address IPv4Address `json:"address"` +} + +// IPv6SocketAddress represents the record "wasi:sockets/network@0.2.0#ipv6-socket-address". +// +// record ipv6-socket-address { +// port: u16, +// flow-info: u32, +// address: ipv6-address, +// scope-id: u32, +// } +type IPv6SocketAddress struct { + _ cm.HostLayout `json:"-"` + // sin6_port + Port uint16 `json:"port"` + + // sin6_flowinfo + FlowInfo uint32 `json:"flow-info"` + + // sin6_addr + Address IPv6Address `json:"address"` + + // sin6_scope_id + ScopeID uint32 `json:"scope-id"` +} + +// IPSocketAddress represents the variant "wasi:sockets/network@0.2.0#ip-socket-address". +// +// variant ip-socket-address { +// ipv4(ipv4-socket-address), +// ipv6(ipv6-socket-address), +// } +type IPSocketAddress cm.Variant[uint8, IPv6SocketAddressShape, IPv6SocketAddress] + +// IPSocketAddressIPv4 returns a [IPSocketAddress] of case "ipv4". +func IPSocketAddressIPv4(data IPv4SocketAddress) IPSocketAddress { + return cm.New[IPSocketAddress](0, data) +} + +// IPv4 returns a non-nil *[IPv4SocketAddress] if [IPSocketAddress] represents the variant case "ipv4". +func (self *IPSocketAddress) IPv4() *IPv4SocketAddress { + return cm.Case[IPv4SocketAddress](self, 0) +} + +// IPSocketAddressIPv6 returns a [IPSocketAddress] of case "ipv6". +func IPSocketAddressIPv6(data IPv6SocketAddress) IPSocketAddress { + return cm.New[IPSocketAddress](1, data) +} + +// IPv6 returns a non-nil *[IPv6SocketAddress] if [IPSocketAddress] represents the variant case "ipv6". +func (self *IPSocketAddress) IPv6() *IPv6SocketAddress { + return cm.Case[IPv6SocketAddress](self, 1) +} + +var _IPSocketAddressStrings = [2]string{ + "ipv4", + "ipv6", +} + +// String implements [fmt.Stringer], returning the variant case name of v. +func (v IPSocketAddress) String() string { + return _IPSocketAddressStrings[v.Tag()] +} diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go new file mode 100644 index 0000000000..9adf8e4158 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package tcpcreatesocket + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/tcp-create-socket@0.2.0 create-tcp-socket +//go:noescape +func wasmimport_CreateTCPSocket(addressFamily0 uint32, result *cm.Result[TCPSocket, TCPSocket, ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go new file mode 100644 index 0000000000..4462a33c6a --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp-create-socket/tcp-create-socket.wit.go @@ -0,0 +1,68 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package tcpcreatesocket represents the imported interface "wasi:sockets/tcp-create-socket@0.2.0". +package tcpcreatesocket + +import ( + "internal/cm" + "internal/wasi/sockets/v0.2.0/network" + "internal/wasi/sockets/v0.2.0/tcp" +) + +// Network represents the imported type alias "wasi:sockets/tcp-create-socket@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/tcp-create-socket@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPAddressFamily represents the type alias "wasi:sockets/tcp-create-socket@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + +// TCPSocket represents the imported type alias "wasi:sockets/tcp-create-socket@0.2.0#tcp-socket". +// +// See [tcp.TCPSocket] for more information. +type TCPSocket = tcp.TCPSocket + +// CreateTCPSocket represents the imported function "create-tcp-socket". +// +// Create a new TCP socket. +// +// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. +// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. +// +// This function does not require a network capability handle. This is considered +// to be safe because +// at time of creation, the socket is not bound to any `network` yet. Up to the moment +// `bind`/`connect` +// is called, the socket is effectively an in-memory configuration object, unable +// to communicate with the outside world. +// +// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous +// operations. +// +// # Typical errors +// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) +// - `new-socket-limit`: The new socket resource could not be created because of +// a system limit. (EMFILE, ENFILE) +// +// # References +// - +// - +// - +// - +// +// create-tcp-socket: func(address-family: ip-address-family) -> result +// +//go:nosplit +func CreateTCPSocket(addressFamily IPAddressFamily) (result cm.Result[TCPSocket, TCPSocket, ErrorCode]) { + addressFamily0 := (uint32)(addressFamily) + wasmimport_CreateTCPSocket((uint32)(addressFamily0), &result) + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/abi.go b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go new file mode 100644 index 0000000000..8bfc6292e3 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/abi.go @@ -0,0 +1,88 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package tcp + +import ( + "internal/cm" + "internal/wasi/sockets/v0.2.0/network" + "unsafe" +) + +// TupleTCPSocketInputStreamOutputStreamShape is used for storage in variant or result types. +type TupleTCPSocketInputStreamOutputStreamShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Tuple3[TCPSocket, InputStream, OutputStream]{})]byte +} + +// TupleInputStreamOutputStreamShape is used for storage in variant or result types. +type TupleInputStreamOutputStreamShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Tuple[InputStream, OutputStream]{})]byte +} + +// IPSocketAddressShape is used for storage in variant or result types. +type IPSocketAddressShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(IPSocketAddress{})]byte +} + +func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + return +} + +func lower_IPv4SocketAddress(v network.IPv4SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32) { + f0 = (uint32)(v.Port) + f1, f2, f3, f4 = lower_IPv4Address(v.Address) + return +} + +func lower_IPv6Address(v network.IPv6Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + f4 = (uint32)(v[4]) + f5 = (uint32)(v[5]) + f6 = (uint32)(v[6]) + f7 = (uint32)(v[7]) + return +} + +func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32) { + f0 = (uint32)(v.Port) + f1 = (uint32)(v.FlowInfo) + f2, f3, f4, f5, f6, f7, f8, f9 = lower_IPv6Address(v.Address) + f10 = (uint32)(v.ScopeID) + return +} + +func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { + f0 = (uint32)(v.Tag()) + switch f0 { + case 0: // ipv4 + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + case 1: // ipv6 + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + f6 = (uint32)(v6) + f7 = (uint32)(v7) + f8 = (uint32)(v8) + f9 = (uint32)(v9) + f10 = (uint32)(v10) + f11 = (uint32)(v11) + } + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/empty.s b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go new file mode 100644 index 0000000000..3ab1acde6d --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wasm.go @@ -0,0 +1,125 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package tcp + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/tcp@0.2.0 [resource-drop]tcp-socket +//go:noescape +func wasmimport_TCPSocketResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.accept +//go:noescape +func wasmimport_TCPSocketAccept(self0 uint32, result *cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, InputStream, OutputStream], ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.address-family +//go:noescape +func wasmimport_TCPSocketAddressFamily(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-bind +//go:noescape +func wasmimport_TCPSocketFinishBind(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-connect +//go:noescape +func wasmimport_TCPSocketFinishConnect(self0 uint32, result *cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[InputStream, OutputStream], ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.finish-listen +//go:noescape +func wasmimport_TCPSocketFinishListen(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.hop-limit +//go:noescape +func wasmimport_TCPSocketHopLimit(self0 uint32, result *cm.Result[uint8, uint8, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.is-listening +//go:noescape +func wasmimport_TCPSocketIsListening(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-count +//go:noescape +func wasmimport_TCPSocketKeepAliveCount(self0 uint32, result *cm.Result[uint32, uint32, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-enabled +//go:noescape +func wasmimport_TCPSocketKeepAliveEnabled(self0 uint32, result *cm.Result[ErrorCode, bool, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-idle-time +//go:noescape +func wasmimport_TCPSocketKeepAliveIdleTime(self0 uint32, result *cm.Result[uint64, Duration, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.keep-alive-interval +//go:noescape +func wasmimport_TCPSocketKeepAliveInterval(self0 uint32, result *cm.Result[uint64, Duration, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.local-address +//go:noescape +func wasmimport_TCPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.receive-buffer-size +//go:noescape +func wasmimport_TCPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.remote-address +//go:noescape +func wasmimport_TCPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.send-buffer-size +//go:noescape +func wasmimport_TCPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-hop-limit +//go:noescape +func wasmimport_TCPSocketSetHopLimit(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-count +//go:noescape +func wasmimport_TCPSocketSetKeepAliveCount(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-enabled +//go:noescape +func wasmimport_TCPSocketSetKeepAliveEnabled(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-idle-time +//go:noescape +func wasmimport_TCPSocketSetKeepAliveIdleTime(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-keep-alive-interval +//go:noescape +func wasmimport_TCPSocketSetKeepAliveInterval(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-listen-backlog-size +//go:noescape +func wasmimport_TCPSocketSetListenBacklogSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-receive-buffer-size +//go:noescape +func wasmimport_TCPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.set-send-buffer-size +//go:noescape +func wasmimport_TCPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.shutdown +//go:noescape +func wasmimport_TCPSocketShutdown(self0 uint32, shutdownType0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-bind +//go:noescape +func wasmimport_TCPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-connect +//go:noescape +func wasmimport_TCPSocketStartConnect(self0 uint32, network0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.start-listen +//go:noescape +func wasmimport_TCPSocketStartListen(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/tcp@0.2.0 [method]tcp-socket.subscribe +//go:noescape +func wasmimport_TCPSocketSubscribe(self0 uint32) (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go new file mode 100644 index 0000000000..c82fd59337 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/tcp/tcp.wit.go @@ -0,0 +1,785 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package tcp represents the imported interface "wasi:sockets/tcp@0.2.0". +package tcp + +import ( + "internal/cm" + monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" + "internal/wasi/io/v0.2.0/poll" + "internal/wasi/io/v0.2.0/streams" + "internal/wasi/sockets/v0.2.0/network" +) + +// InputStream represents the imported type alias "wasi:sockets/tcp@0.2.0#input-stream". +// +// See [streams.InputStream] for more information. +type InputStream = streams.InputStream + +// OutputStream represents the imported type alias "wasi:sockets/tcp@0.2.0#output-stream". +// +// See [streams.OutputStream] for more information. +type OutputStream = streams.OutputStream + +// Pollable represents the imported type alias "wasi:sockets/tcp@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// Duration represents the type alias "wasi:sockets/tcp@0.2.0#duration". +// +// See [monotonicclock.Duration] for more information. +type Duration = monotonicclock.Duration + +// Network represents the imported type alias "wasi:sockets/tcp@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/tcp@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPSocketAddress represents the type alias "wasi:sockets/tcp@0.2.0#ip-socket-address". +// +// See [network.IPSocketAddress] for more information. +type IPSocketAddress = network.IPSocketAddress + +// IPAddressFamily represents the type alias "wasi:sockets/tcp@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + +// ShutdownType represents the enum "wasi:sockets/tcp@0.2.0#shutdown-type". +// +// enum shutdown-type { +// receive, +// send, +// both +// } +type ShutdownType uint8 + +const ( + // Similar to `SHUT_RD` in POSIX. + ShutdownTypeReceive ShutdownType = iota + + // Similar to `SHUT_WR` in POSIX. + ShutdownTypeSend + + // Similar to `SHUT_RDWR` in POSIX. + ShutdownTypeBoth +) + +var _ShutdownTypeStrings = [3]string{ + "receive", + "send", + "both", +} + +// String implements [fmt.Stringer], returning the enum case name of e. +func (e ShutdownType) String() string { + return _ShutdownTypeStrings[e] +} + +// MarshalText implements [encoding.TextMarshaler]. +func (e ShutdownType) MarshalText() ([]byte, error) { + return []byte(e.String()), nil +} + +// UnmarshalText implements [encoding.TextUnmarshaler], unmarshaling into an enum +// case. Returns an error if the supplied text is not one of the enum cases. +func (e *ShutdownType) UnmarshalText(text []byte) error { + return _ShutdownTypeUnmarshalCase(e, text) +} + +var _ShutdownTypeUnmarshalCase = cm.CaseUnmarshaler[ShutdownType](_ShutdownTypeStrings[:]) + +// TCPSocket represents the imported resource "wasi:sockets/tcp@0.2.0#tcp-socket". +// +// A TCP socket resource. +// +// The socket can be in one of the following states: +// - `unbound` +// - `bind-in-progress` +// - `bound` (See note below) +// - `listen-in-progress` +// - `listening` +// - `connect-in-progress` +// - `connected` +// - `closed` +// See +// for a more information. +// +// Note: Except where explicitly mentioned, whenever this documentation uses +// the term "bound" without backticks it actually means: in the `bound` state *or +// higher*. +// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) +// +// In addition to the general error codes documented on the +// `network::error-code` type, TCP socket methods may always return +// `error(invalid-state)` when in the `closed` state. +// +// resource tcp-socket +type TCPSocket cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "tcp-socket". +// +// Drops a resource handle. +// +//go:nosplit +func (self TCPSocket) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketResourceDrop((uint32)(self0)) + return +} + +// Accept represents the imported method "accept". +// +// Accept a new client socket. +// +// The returned socket is bound and in the `connected` state. The following properties +// are inherited from the listener socket: +// - `address-family` +// - `keep-alive-enabled` +// - `keep-alive-idle-time` +// - `keep-alive-interval` +// - `keep-alive-count` +// - `hop-limit` +// - `receive-buffer-size` +// - `send-buffer-size` +// +// On success, this function returns the newly accepted client socket along with +// a pair of streams that can be used to read & write to the connection. +// +// # Typical errors +// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) +// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) +// - `connection-aborted`: An incoming connection was pending, but was terminated +// by the client before this listener could accept it. (ECONNABORTED) +// - `new-socket-limit`: The new socket resource could not be created because of +// a system limit. (EMFILE, ENFILE) +// +// # References +// - +// - +// - +// - +// +// accept: func() -> result, error-code> +// +//go:nosplit +func (self TCPSocket) Accept() (result cm.Result[TupleTCPSocketInputStreamOutputStreamShape, cm.Tuple3[TCPSocket, InputStream, OutputStream], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketAccept((uint32)(self0), &result) + return +} + +// AddressFamily represents the imported method "address-family". +// +// Whether this is a IPv4 or IPv6 socket. +// +// Equivalent to the SO_DOMAIN socket option. +// +// address-family: func() -> ip-address-family +// +//go:nosplit +func (self TCPSocket) AddressFamily() (result IPAddressFamily) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_TCPSocketAddressFamily((uint32)(self0)) + result = (network.IPAddressFamily)((uint32)(result0)) + return +} + +// FinishBind represents the imported method "finish-bind". +// +// finish-bind: func() -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) FinishBind() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketFinishBind((uint32)(self0), &result) + return +} + +// FinishConnect represents the imported method "finish-connect". +// +// finish-connect: func() -> result, error-code> +// +//go:nosplit +func (self TCPSocket) FinishConnect() (result cm.Result[TupleInputStreamOutputStreamShape, cm.Tuple[InputStream, OutputStream], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketFinishConnect((uint32)(self0), &result) + return +} + +// FinishListen represents the imported method "finish-listen". +// +// finish-listen: func() -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) FinishListen() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketFinishListen((uint32)(self0), &result) + return +} + +// HopLimit represents the imported method "hop-limit". +// +// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// +// # Typical errors +// - `invalid-argument`: (set) The TTL value must be 1 or higher. +// +// hop-limit: func() -> result +// +//go:nosplit +func (self TCPSocket) HopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketHopLimit((uint32)(self0), &result) + return +} + +// IsListening represents the imported method "is-listening". +// +// Whether the socket is in the `listening` state. +// +// Equivalent to the SO_ACCEPTCONN socket option. +// +// is-listening: func() -> bool +// +//go:nosplit +func (self TCPSocket) IsListening() (result bool) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_TCPSocketIsListening((uint32)(self0)) + result = (bool)(cm.U32ToBool((uint32)(result0))) + return +} + +// KeepAliveCount represents the imported method "keep-alive-count". +// +// The maximum amount of keepalive packets TCP should send before aborting the connection. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the TCP_KEEPCNT socket option. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// keep-alive-count: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveCount() (result cm.Result[uint32, uint32, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveCount((uint32)(self0), &result) + return +} + +// KeepAliveEnabled represents the imported method "keep-alive-enabled". +// +// Enables or disables keepalive. +// +// The keepalive behavior can be adjusted using: +// - `keep-alive-idle-time` +// - `keep-alive-interval` +// - `keep-alive-count` +// These properties can be configured while `keep-alive-enabled` is false, but only +// come into effect when `keep-alive-enabled` is true. +// +// Equivalent to the SO_KEEPALIVE socket option. +// +// keep-alive-enabled: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveEnabled() (result cm.Result[ErrorCode, bool, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveEnabled((uint32)(self0), &result) + return +} + +// KeepAliveIdleTime represents the imported method "keep-alive-idle-time". +// +// Amount of time the connection has to be idle before TCP starts sending keepalive +// packets. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// keep-alive-idle-time: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveIdleTime() (result cm.Result[uint64, Duration, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveIdleTime((uint32)(self0), &result) + return +} + +// KeepAliveInterval represents the imported method "keep-alive-interval". +// +// The time between keepalive packets. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the TCP_KEEPINTVL socket option. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// keep-alive-interval: func() -> result +// +//go:nosplit +func (self TCPSocket) KeepAliveInterval() (result cm.Result[uint64, Duration, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketKeepAliveInterval((uint32)(self0), &result) + return +} + +// LocalAddress represents the imported method "local-address". +// +// Get the bound local address. +// +// POSIX mentions: +// > If the socket has not been bound to a local name, the value +// > stored in the object pointed to by `address` is unspecified. +// +// WASI is stricter and requires `local-address` to return `invalid-state` when the +// socket hasn't been bound yet. +// +// # Typical errors +// - `invalid-state`: The socket is not bound to any local address. +// +// # References +// - +// - +// - +// - +// +// local-address: func() -> result +// +//go:nosplit +func (self TCPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketLocalAddress((uint32)(self0), &result) + return +} + +// ReceiveBufferSize represents the imported method "receive-buffer-size". +// +// The kernel buffer space reserved for sends/receives on this socket. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// receive-buffer-size: func() -> result +// +//go:nosplit +func (self TCPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketReceiveBufferSize((uint32)(self0), &result) + return +} + +// RemoteAddress represents the imported method "remote-address". +// +// Get the remote address. +// +// # Typical errors +// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) +// +// # References +// - +// - +// - +// - +// +// remote-address: func() -> result +// +//go:nosplit +func (self TCPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketRemoteAddress((uint32)(self0), &result) + return +} + +// SendBufferSize represents the imported method "send-buffer-size". +// +// send-buffer-size: func() -> result +// +//go:nosplit +func (self TCPSocket) SendBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketSendBufferSize((uint32)(self0), &result) + return +} + +// SetHopLimit represents the imported method "set-hop-limit". +// +// set-hop-limit: func(value: u8) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetHopLimit(value uint8) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint32)(value) + wasmimport_TCPSocketSetHopLimit((uint32)(self0), (uint32)(value0), &result) + return +} + +// SetKeepAliveCount represents the imported method "set-keep-alive-count". +// +// set-keep-alive-count: func(value: u32) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveCount(value uint32) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint32)(value) + wasmimport_TCPSocketSetKeepAliveCount((uint32)(self0), (uint32)(value0), &result) + return +} + +// SetKeepAliveEnabled represents the imported method "set-keep-alive-enabled". +// +// set-keep-alive-enabled: func(value: bool) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveEnabled(value bool) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint32)(cm.BoolToU32(value)) + wasmimport_TCPSocketSetKeepAliveEnabled((uint32)(self0), (uint32)(value0), &result) + return +} + +// SetKeepAliveIdleTime represents the imported method "set-keep-alive-idle-time". +// +// set-keep-alive-idle-time: func(value: duration) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveIdleTime(value Duration) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetKeepAliveIdleTime((uint32)(self0), (uint64)(value0), &result) + return +} + +// SetKeepAliveInterval represents the imported method "set-keep-alive-interval". +// +// set-keep-alive-interval: func(value: duration) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetKeepAliveInterval(value Duration) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetKeepAliveInterval((uint32)(self0), (uint64)(value0), &result) + return +} + +// SetListenBacklogSize represents the imported method "set-listen-backlog-size". +// +// Hints the desired listen queue size. Implementations are free to ignore this. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// +// # Typical errors +// - `not-supported`: (set) The platform does not support changing the backlog +// size after the initial listen. +// - `invalid-argument`: (set) The provided value was 0. +// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` +// state. +// +// set-listen-backlog-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetListenBacklogSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetListenBacklogSize((uint32)(self0), (uint64)(value0), &result) + return +} + +// SetReceiveBufferSize represents the imported method "set-receive-buffer-size". +// +// set-receive-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +// SetSendBufferSize represents the imported method "set-send-buffer-size". +// +// set-send-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) SetSendBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_TCPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +// Shutdown represents the imported method "shutdown". +// +// Initiate a graceful shutdown. +// +// - `receive`: The socket is not expecting to receive any data from +// the peer. The `input-stream` associated with this socket will be +// closed. Any data still in the receive queue at time of calling +// this method will be discarded. +// - `send`: The socket has no more data to send to the peer. The `output-stream` +// associated with this socket will be closed and a FIN packet will be sent. +// - `both`: Same effect as `receive` & `send` combined. +// +// This function is idempotent. Shutting a down a direction more than once +// has no effect and returns `ok`. +// +// The shutdown function does not close (drop) the socket. +// +// # Typical errors +// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) +// +// # References +// - +// - +// - +// - +// +// shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) Shutdown(shutdownType ShutdownType) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + shutdownType0 := (uint32)(shutdownType) + wasmimport_TCPSocketShutdown((uint32)(self0), (uint32)(shutdownType0), &result) + return +} + +// StartBind represents the imported method "start-bind". +// +// Bind the socket to a specific network on the provided IP address and port. +// +// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the +// implementation to decide which +// network interface(s) to bind to. +// If the TCP/UDP port is zero, the socket will be bound to a random free port. +// +// Bind can be attempted multiple times on the same socket, even with +// different arguments on each iteration. But never concurrently and +// only as long as the previous bind failed. Once a bind succeeds, the +// binding can't be changed anymore. +// +// # Typical errors +// - `invalid-argument`: The `local-address` has the wrong address family. +// (EAFNOSUPPORT, EFAULT on Windows) +// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) +// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. +// (EINVAL) +// - `invalid-state`: The socket is already bound. (EINVAL) +// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS +// on Windows) +// - `address-in-use`: Address is already in use. (EADDRINUSE) +// - `address-not-bindable`: `local-address` is not an address that the `network` +// can bind to. (EADDRNOTAVAIL) +// - `not-in-progress`: A `bind` operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// When binding to a non-zero port, this bind operation shouldn't be affected by the +// TIME_WAIT +// state of a recently closed socket on the same local address. In practice this means +// that the SO_REUSEADDR +// socket option should be set implicitly on all platforms, except on Windows where +// this is the default behavior +// and SO_REUSEADDR performs something different entirely. +// +// Unlike in POSIX, in WASI the bind operation is async. This enables +// interactive WASI hosts to inject permission prompts. Runtimes that +// don't want to make use of this ability can simply call the native +// `bind` as part of either `start-bind` or `finish-bind`. +// +// # References +// - +// - +// - +// - +// +// start-bind: func(network: borrow, local-address: ip-socket-address) -> +// result<_, error-code> +// +//go:nosplit +func (self TCPSocket) StartBind(network_ Network, localAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + network0 := cm.Reinterpret[uint32](network_) + localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) + wasmimport_TCPSocketStartBind((uint32)(self0), (uint32)(network0), (uint32)(localAddress0), (uint32)(localAddress1), (uint32)(localAddress2), (uint32)(localAddress3), (uint32)(localAddress4), (uint32)(localAddress5), (uint32)(localAddress6), (uint32)(localAddress7), (uint32)(localAddress8), (uint32)(localAddress9), (uint32)(localAddress10), (uint32)(localAddress11), &result) + return +} + +// StartConnect represents the imported method "start-connect". +// +// Connect to a remote endpoint. +// +// On success: +// - the socket is transitioned into the `connection` state. +// - a pair of streams is returned that can be used to read & write to the connection +// +// After a failed connection attempt, the socket will be in the `closed` +// state and the only valid action left is to `drop` the socket. A single +// socket can not be used to connect more than once. +// +// # Typical errors +// - `invalid-argument`: The `remote-address` has the wrong address family. +// (EAFNOSUPPORT) +// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, +// ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) +// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. +// (EINVAL, EADDRNOTAVAIL on Illumos) +// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY +// (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) +// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL +// on Windows) +// - `invalid-argument`: The socket is already attached to a different network. +// The `network` passed to `connect` must be identical to the one passed to `bind`. +// - `invalid-state`: The socket is already in the `connected` state. +// (EISCONN) +// - `invalid-state`: The socket is already in the `listening` state. +// (EOPNOTSUPP, EINVAL on Windows) +// - `timeout`: Connection timed out. (ETIMEDOUT) +// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) +// - `connection-reset`: The connection was reset. (ECONNRESET) +// - `connection-aborted`: The connection was aborted. (ECONNABORTED) +// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, +// EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `address-in-use`: Tried to perform an implicit bind, but there were +// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) +// - `not-in-progress`: A connect operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// The POSIX equivalent of `start-connect` is the regular `connect` syscall. +// Because all WASI sockets are non-blocking this is expected to return +// EINPROGRESS, which should be translated to `ok()` in WASI. +// +// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` +// with a timeout of 0 on the socket descriptor. Followed by a check for +// the `SO_ERROR` socket option, in case the poll signaled readiness. +// +// # References +// - +// - +// - +// - +// +// start-connect: func(network: borrow, remote-address: ip-socket-address) +// -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) StartConnect(network_ Network, remoteAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + network0 := cm.Reinterpret[uint32](network_) + remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11 := lower_IPSocketAddress(remoteAddress) + wasmimport_TCPSocketStartConnect((uint32)(self0), (uint32)(network0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), &result) + return +} + +// StartListen represents the imported method "start-listen". +// +// Start listening for new connections. +// +// Transitions the socket into the `listening` state. +// +// Unlike POSIX, the socket must already be explicitly bound. +// +// # Typical errors +// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) +// - `invalid-state`: The socket is already in the `connected` state. +// (EISCONN, EINVAL on BSD) +// - `invalid-state`: The socket is already in the `listening` state. +// - `address-in-use`: Tried to perform an implicit bind, but there were +// no ephemeral ports available. (EADDRINUSE) +// - `not-in-progress`: A listen operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// Unlike in POSIX, in WASI the listen operation is async. This enables +// interactive WASI hosts to inject permission prompts. Runtimes that +// don't want to make use of this ability can simply call the native +// `listen` as part of either `start-listen` or `finish-listen`. +// +// # References +// - +// - +// - +// - +// +// start-listen: func() -> result<_, error-code> +// +//go:nosplit +func (self TCPSocket) StartListen() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_TCPSocketStartListen((uint32)(self0), &result) + return +} + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which can be used to poll for, or block on, +// completion of any of the asynchronous operations of this socket. +// +// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` +// return `error(would-block)`, this pollable can be used to wait for +// their success or failure, after which the method can be retried. +// +// The pollable is not limited to the async operation that happens to be +// in progress at the time of calling `subscribe` (if any). Theoretically, +// `subscribe` only has to be called once per socket and can then be +// (re)used for the remainder of the socket's lifetime. +// +// See +// for a more information. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self TCPSocket) Subscribe() (result Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_TCPSocketSubscribe((uint32)(self0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go new file mode 100644 index 0000000000..0f56e12bb2 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wasm.go @@ -0,0 +1,13 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package udpcreatesocket + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/udp-create-socket@0.2.0 create-udp-socket +//go:noescape +func wasmimport_CreateUDPSocket(addressFamily0 uint32, result *cm.Result[UDPSocket, UDPSocket, ErrorCode]) diff --git a/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go new file mode 100644 index 0000000000..c0f31d725d --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp-create-socket/udp-create-socket.wit.go @@ -0,0 +1,68 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package udpcreatesocket represents the imported interface "wasi:sockets/udp-create-socket@0.2.0". +package udpcreatesocket + +import ( + "internal/cm" + "internal/wasi/sockets/v0.2.0/network" + "internal/wasi/sockets/v0.2.0/udp" +) + +// Network represents the imported type alias "wasi:sockets/udp-create-socket@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/udp-create-socket@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPAddressFamily represents the type alias "wasi:sockets/udp-create-socket@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + +// UDPSocket represents the imported type alias "wasi:sockets/udp-create-socket@0.2.0#udp-socket". +// +// See [udp.UDPSocket] for more information. +type UDPSocket = udp.UDPSocket + +// CreateUDPSocket represents the imported function "create-udp-socket". +// +// Create a new UDP socket. +// +// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. +// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. +// +// This function does not require a network capability handle. This is considered +// to be safe because +// at time of creation, the socket is not bound to any `network` yet. Up to the moment +// `bind` is called, +// the socket is effectively an in-memory configuration object, unable to communicate +// with the outside world. +// +// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous +// operations. +// +// # Typical errors +// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) +// - `new-socket-limit`: The new socket resource could not be created because of +// a system limit. (EMFILE, ENFILE) +// +// # References: +// - +// - +// - +// - +// +// create-udp-socket: func(address-family: ip-address-family) -> result +// +//go:nosplit +func CreateUDPSocket(addressFamily IPAddressFamily) (result cm.Result[UDPSocket, UDPSocket, ErrorCode]) { + addressFamily0 := (uint32)(addressFamily) + wasmimport_CreateUDPSocket((uint32)(addressFamily0), &result) + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/udp/abi.go b/src/internal/wasi/sockets/v0.2.0/udp/abi.go new file mode 100644 index 0000000000..e535da64ad --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/abi.go @@ -0,0 +1,103 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package udp + +import ( + "internal/cm" + "internal/wasi/sockets/v0.2.0/network" + "unsafe" +) + +// IPSocketAddressShape is used for storage in variant or result types. +type IPSocketAddressShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(IPSocketAddress{})]byte +} + +func lower_IPv4Address(v network.IPv4Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + return +} + +func lower_IPv4SocketAddress(v network.IPv4SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32) { + f0 = (uint32)(v.Port) + f1, f2, f3, f4 = lower_IPv4Address(v.Address) + return +} + +func lower_IPv6Address(v network.IPv6Address) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32) { + f0 = (uint32)(v[0]) + f1 = (uint32)(v[1]) + f2 = (uint32)(v[2]) + f3 = (uint32)(v[3]) + f4 = (uint32)(v[4]) + f5 = (uint32)(v[5]) + f6 = (uint32)(v[6]) + f7 = (uint32)(v[7]) + return +} + +func lower_IPv6SocketAddress(v network.IPv6SocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32) { + f0 = (uint32)(v.Port) + f1 = (uint32)(v.FlowInfo) + f2, f3, f4, f5, f6, f7, f8, f9 = lower_IPv6Address(v.Address) + f10 = (uint32)(v.ScopeID) + return +} + +func lower_IPSocketAddress(v network.IPSocketAddress) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32) { + f0 = (uint32)(v.Tag()) + switch f0 { + case 0: // ipv4 + v1, v2, v3, v4, v5 := lower_IPv4SocketAddress(*cm.Case[network.IPv4SocketAddress](&v, 0)) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + case 1: // ipv6 + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 := lower_IPv6SocketAddress(*cm.Case[network.IPv6SocketAddress](&v, 1)) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + f6 = (uint32)(v6) + f7 = (uint32)(v7) + f8 = (uint32)(v8) + f9 = (uint32)(v9) + f10 = (uint32)(v10) + f11 = (uint32)(v11) + } + return +} + +// TupleIncomingDatagramStreamOutgoingDatagramStreamShape is used for storage in variant or result types. +type TupleIncomingDatagramStreamOutgoingDatagramStreamShape struct { + _ cm.HostLayout + shape [unsafe.Sizeof(cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream]{})]byte +} + +func lower_OptionIPSocketAddress(v cm.Option[IPSocketAddress]) (f0 uint32, f1 uint32, f2 uint32, f3 uint32, f4 uint32, f5 uint32, f6 uint32, f7 uint32, f8 uint32, f9 uint32, f10 uint32, f11 uint32, f12 uint32) { + some := v.Some() + if some != nil { + f0 = 1 + v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12 := lower_IPSocketAddress(*some) + f1 = (uint32)(v1) + f2 = (uint32)(v2) + f3 = (uint32)(v3) + f4 = (uint32)(v4) + f5 = (uint32)(v5) + f6 = (uint32)(v6) + f7 = (uint32)(v7) + f8 = (uint32)(v8) + f9 = (uint32)(v9) + f10 = (uint32)(v10) + f11 = (uint32)(v11) + f12 = (uint32)(v12) + } + return +} diff --git a/src/internal/wasi/sockets/v0.2.0/udp/empty.s b/src/internal/wasi/sockets/v0.2.0/udp/empty.s new file mode 100644 index 0000000000..5444f20065 --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/empty.s @@ -0,0 +1,3 @@ +// This file exists for testing this package without WebAssembly, +// allowing empty function bodies with a //go:wasmimport directive. +// See https://pkg.go.dev/cmd/compile for more information. diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go new file mode 100644 index 0000000000..3e3b9e554a --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wasm.go @@ -0,0 +1,93 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +package udp + +import ( + "internal/cm" +) + +// This file contains wasmimport and wasmexport declarations for "wasi:sockets@0.2.0". + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]udp-socket +//go:noescape +func wasmimport_UDPSocketResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.address-family +//go:noescape +func wasmimport_UDPSocketAddressFamily(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.finish-bind +//go:noescape +func wasmimport_UDPSocketFinishBind(self0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.local-address +//go:noescape +func wasmimport_UDPSocketLocalAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.receive-buffer-size +//go:noescape +func wasmimport_UDPSocketReceiveBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.remote-address +//go:noescape +func wasmimport_UDPSocketRemoteAddress(self0 uint32, result *cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.send-buffer-size +//go:noescape +func wasmimport_UDPSocketSendBufferSize(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-receive-buffer-size +//go:noescape +func wasmimport_UDPSocketSetReceiveBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-send-buffer-size +//go:noescape +func wasmimport_UDPSocketSetSendBufferSize(self0 uint32, value0 uint64, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.set-unicast-hop-limit +//go:noescape +func wasmimport_UDPSocketSetUnicastHopLimit(self0 uint32, value0 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.start-bind +//go:noescape +func wasmimport_UDPSocketStartBind(self0 uint32, network0 uint32, localAddress0 uint32, localAddress1 uint32, localAddress2 uint32, localAddress3 uint32, localAddress4 uint32, localAddress5 uint32, localAddress6 uint32, localAddress7 uint32, localAddress8 uint32, localAddress9 uint32, localAddress10 uint32, localAddress11 uint32, result *cm.Result[ErrorCode, struct{}, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.stream +//go:noescape +func wasmimport_UDPSocketStream(self0 uint32, remoteAddress0 uint32, remoteAddress1 uint32, remoteAddress2 uint32, remoteAddress3 uint32, remoteAddress4 uint32, remoteAddress5 uint32, remoteAddress6 uint32, remoteAddress7 uint32, remoteAddress8 uint32, remoteAddress9 uint32, remoteAddress10 uint32, remoteAddress11 uint32, remoteAddress12 uint32, result *cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.subscribe +//go:noescape +func wasmimport_UDPSocketSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]udp-socket.unicast-hop-limit +//go:noescape +func wasmimport_UDPSocketUnicastHopLimit(self0 uint32, result *cm.Result[uint8, uint8, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]incoming-datagram-stream +//go:noescape +func wasmimport_IncomingDatagramStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.receive +//go:noescape +func wasmimport_IncomingDatagramStreamReceive(self0 uint32, maxResults0 uint64, result *cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]incoming-datagram-stream.subscribe +//go:noescape +func wasmimport_IncomingDatagramStreamSubscribe(self0 uint32) (result0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [resource-drop]outgoing-datagram-stream +//go:noescape +func wasmimport_OutgoingDatagramStreamResourceDrop(self0 uint32) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.check-send +//go:noescape +func wasmimport_OutgoingDatagramStreamCheckSend(self0 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.send +//go:noescape +func wasmimport_OutgoingDatagramStreamSend(self0 uint32, datagrams0 *OutgoingDatagram, datagrams1 uint32, result *cm.Result[uint64, uint64, ErrorCode]) + +//go:wasmimport wasi:sockets/udp@0.2.0 [method]outgoing-datagram-stream.subscribe +//go:noescape +func wasmimport_OutgoingDatagramStreamSubscribe(self0 uint32) (result0 uint32) diff --git a/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go new file mode 100644 index 0000000000..f10df3091d --- /dev/null +++ b/src/internal/wasi/sockets/v0.2.0/udp/udp.wit.go @@ -0,0 +1,584 @@ +// Code generated by wit-bindgen-go. DO NOT EDIT. + +// Package udp represents the imported interface "wasi:sockets/udp@0.2.0". +package udp + +import ( + "internal/cm" + "internal/wasi/io/v0.2.0/poll" + "internal/wasi/sockets/v0.2.0/network" +) + +// Pollable represents the imported type alias "wasi:sockets/udp@0.2.0#pollable". +// +// See [poll.Pollable] for more information. +type Pollable = poll.Pollable + +// Network represents the imported type alias "wasi:sockets/udp@0.2.0#network". +// +// See [network.Network] for more information. +type Network = network.Network + +// ErrorCode represents the type alias "wasi:sockets/udp@0.2.0#error-code". +// +// See [network.ErrorCode] for more information. +type ErrorCode = network.ErrorCode + +// IPSocketAddress represents the type alias "wasi:sockets/udp@0.2.0#ip-socket-address". +// +// See [network.IPSocketAddress] for more information. +type IPSocketAddress = network.IPSocketAddress + +// IPAddressFamily represents the type alias "wasi:sockets/udp@0.2.0#ip-address-family". +// +// See [network.IPAddressFamily] for more information. +type IPAddressFamily = network.IPAddressFamily + +// IncomingDatagram represents the record "wasi:sockets/udp@0.2.0#incoming-datagram". +// +// A received datagram. +// +// record incoming-datagram { +// data: list, +// remote-address: ip-socket-address, +// } +type IncomingDatagram struct { + _ cm.HostLayout `json:"-"` + // The payload. + // + // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + Data cm.List[uint8] `json:"data"` + + // The source address. + // + // This field is guaranteed to match the remote address the stream was initialized + // with, if any. + // + // Equivalent to the `src_addr` out parameter of `recvfrom`. + RemoteAddress IPSocketAddress `json:"remote-address"` +} + +// OutgoingDatagram represents the record "wasi:sockets/udp@0.2.0#outgoing-datagram". +// +// A datagram to be sent out. +// +// record outgoing-datagram { +// data: list, +// remote-address: option, +// } +type OutgoingDatagram struct { + _ cm.HostLayout `json:"-"` + // The payload. + Data cm.List[uint8] `json:"data"` + + // The destination address. + // + // The requirements on this field depend on how the stream was initialized: + // - with a remote address: this field must be None or match the stream's remote address + // exactly. + // - without a remote address: this field is required. + // + // If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise + // it is equivalent to `sendto`. + RemoteAddress cm.Option[IPSocketAddress] `json:"remote-address"` +} + +// UDPSocket represents the imported resource "wasi:sockets/udp@0.2.0#udp-socket". +// +// A UDP socket handle. +// +// resource udp-socket +type UDPSocket cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "udp-socket". +// +// Drops a resource handle. +// +//go:nosplit +func (self UDPSocket) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketResourceDrop((uint32)(self0)) + return +} + +// AddressFamily represents the imported method "address-family". +// +// Whether this is a IPv4 or IPv6 socket. +// +// Equivalent to the SO_DOMAIN socket option. +// +// address-family: func() -> ip-address-family +// +//go:nosplit +func (self UDPSocket) AddressFamily() (result IPAddressFamily) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_UDPSocketAddressFamily((uint32)(self0)) + result = (network.IPAddressFamily)((uint32)(result0)) + return +} + +// FinishBind represents the imported method "finish-bind". +// +// finish-bind: func() -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) FinishBind() (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketFinishBind((uint32)(self0), &result) + return +} + +// LocalAddress represents the imported method "local-address". +// +// Get the current bound address. +// +// POSIX mentions: +// > If the socket has not been bound to a local name, the value +// > stored in the object pointed to by `address` is unspecified. +// +// WASI is stricter and requires `local-address` to return `invalid-state` when the +// socket hasn't been bound yet. +// +// # Typical errors +// - `invalid-state`: The socket is not bound to any local address. +// +// # References +// - +// - +// - +// - +// +// local-address: func() -> result +// +//go:nosplit +func (self UDPSocket) LocalAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketLocalAddress((uint32)(self0), &result) + return +} + +// ReceiveBufferSize represents the imported method "receive-buffer-size". +// +// The kernel buffer space reserved for sends/receives on this socket. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// Any other value will never cause an error, but it might be silently clamped and/or +// rounded. +// I.e. after setting a value, reading the same setting back may return a different +// value. +// +// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. +// +// # Typical errors +// - `invalid-argument`: (set) The provided value was 0. +// +// receive-buffer-size: func() -> result +// +//go:nosplit +func (self UDPSocket) ReceiveBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketReceiveBufferSize((uint32)(self0), &result) + return +} + +// RemoteAddress represents the imported method "remote-address". +// +// Get the address the socket is currently streaming to. +// +// # Typical errors +// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) +// +// # References +// - +// - +// - +// - +// +// remote-address: func() -> result +// +//go:nosplit +func (self UDPSocket) RemoteAddress() (result cm.Result[IPSocketAddressShape, IPSocketAddress, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketRemoteAddress((uint32)(self0), &result) + return +} + +// SendBufferSize represents the imported method "send-buffer-size". +// +// send-buffer-size: func() -> result +// +//go:nosplit +func (self UDPSocket) SendBufferSize() (result cm.Result[uint64, uint64, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketSendBufferSize((uint32)(self0), &result) + return +} + +// SetReceiveBufferSize represents the imported method "set-receive-buffer-size". +// +// set-receive-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) SetReceiveBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_UDPSocketSetReceiveBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +// SetSendBufferSize represents the imported method "set-send-buffer-size". +// +// set-send-buffer-size: func(value: u64) -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) SetSendBufferSize(value uint64) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint64)(value) + wasmimport_UDPSocketSetSendBufferSize((uint32)(self0), (uint64)(value0), &result) + return +} + +// SetUnicastHopLimit represents the imported method "set-unicast-hop-limit". +// +// set-unicast-hop-limit: func(value: u8) -> result<_, error-code> +// +//go:nosplit +func (self UDPSocket) SetUnicastHopLimit(value uint8) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + value0 := (uint32)(value) + wasmimport_UDPSocketSetUnicastHopLimit((uint32)(self0), (uint32)(value0), &result) + return +} + +// StartBind represents the imported method "start-bind". +// +// Bind the socket to a specific network on the provided IP address and port. +// +// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the +// implementation to decide which +// network interface(s) to bind to. +// If the port is zero, the socket will be bound to a random free port. +// +// # Typical errors +// - `invalid-argument`: The `local-address` has the wrong address family. +// (EAFNOSUPPORT, EFAULT on Windows) +// - `invalid-state`: The socket is already bound. (EINVAL) +// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS +// on Windows) +// - `address-in-use`: Address is already in use. (EADDRINUSE) +// - `address-not-bindable`: `local-address` is not an address that the `network` +// can bind to. (EADDRNOTAVAIL) +// - `not-in-progress`: A `bind` operation is not in progress. +// - `would-block`: Can't finish the operation, it is still in progress. +// (EWOULDBLOCK, EAGAIN) +// +// # Implementors note +// Unlike in POSIX, in WASI the bind operation is async. This enables +// interactive WASI hosts to inject permission prompts. Runtimes that +// don't want to make use of this ability can simply call the native +// `bind` as part of either `start-bind` or `finish-bind`. +// +// # References +// - +// - +// - +// - +// +// start-bind: func(network: borrow, local-address: ip-socket-address) -> +// result<_, error-code> +// +//go:nosplit +func (self UDPSocket) StartBind(network_ Network, localAddress IPSocketAddress) (result cm.Result[ErrorCode, struct{}, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + network0 := cm.Reinterpret[uint32](network_) + localAddress0, localAddress1, localAddress2, localAddress3, localAddress4, localAddress5, localAddress6, localAddress7, localAddress8, localAddress9, localAddress10, localAddress11 := lower_IPSocketAddress(localAddress) + wasmimport_UDPSocketStartBind((uint32)(self0), (uint32)(network0), (uint32)(localAddress0), (uint32)(localAddress1), (uint32)(localAddress2), (uint32)(localAddress3), (uint32)(localAddress4), (uint32)(localAddress5), (uint32)(localAddress6), (uint32)(localAddress7), (uint32)(localAddress8), (uint32)(localAddress9), (uint32)(localAddress10), (uint32)(localAddress11), &result) + return +} + +// Stream represents the imported method "stream". +// +// Set up inbound & outbound communication channels, optionally to a specific peer. +// +// This function only changes the local socket configuration and does not generate +// any network traffic. +// On success, the `remote-address` of the socket is updated. The `local-address` +// may be updated as well, +// based on the best network path to `remote-address`. +// +// When a `remote-address` is provided, the returned streams are limited to communicating +// with that specific peer: +// - `send` can only be used to send to this destination. +// - `receive` will only return datagrams sent from the provided `remote-address`. +// +// This method may be called multiple times on the same socket to change its association, +// but +// only the most recently returned pair of streams will be operational. Implementations +// may trap if +// the streams returned by a previous invocation haven't been dropped yet before calling +// `stream` again. +// +// The POSIX equivalent in pseudo-code is: +// +// if (was previously connected) { +// connect(s, AF_UNSPEC) +// } +// if (remote_address is Some) { +// connect(s, remote_address) +// } +// +// Unlike in POSIX, the socket must already be explicitly bound. +// +// # Typical errors +// - `invalid-argument`: The `remote-address` has the wrong address family. +// (EAFNOSUPPORT) +// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY +// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) +// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, +// EADDRNOTAVAIL) +// - `invalid-state`: The socket is not bound. +// - `address-in-use`: Tried to perform an implicit bind, but there were +// no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) +// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, +// ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `connection-refused`: The connection was refused. (ECONNREFUSED) +// +// # References +// - +// - +// - +// - +// +// %stream: func(remote-address: option) -> result, error-code> +// +//go:nosplit +func (self UDPSocket) Stream(remoteAddress cm.Option[IPSocketAddress]) (result cm.Result[TupleIncomingDatagramStreamOutgoingDatagramStreamShape, cm.Tuple[IncomingDatagramStream, OutgoingDatagramStream], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + remoteAddress0, remoteAddress1, remoteAddress2, remoteAddress3, remoteAddress4, remoteAddress5, remoteAddress6, remoteAddress7, remoteAddress8, remoteAddress9, remoteAddress10, remoteAddress11, remoteAddress12 := lower_OptionIPSocketAddress(remoteAddress) + wasmimport_UDPSocketStream((uint32)(self0), (uint32)(remoteAddress0), (uint32)(remoteAddress1), (uint32)(remoteAddress2), (uint32)(remoteAddress3), (uint32)(remoteAddress4), (uint32)(remoteAddress5), (uint32)(remoteAddress6), (uint32)(remoteAddress7), (uint32)(remoteAddress8), (uint32)(remoteAddress9), (uint32)(remoteAddress10), (uint32)(remoteAddress11), (uint32)(remoteAddress12), &result) + return +} + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the socket is ready for I/O. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self UDPSocket) Subscribe() (result Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_UDPSocketSubscribe((uint32)(self0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} + +// UnicastHopLimit represents the imported method "unicast-hop-limit". +// +// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. +// +// If the provided value is 0, an `invalid-argument` error is returned. +// +// # Typical errors +// - `invalid-argument`: (set) The TTL value must be 1 or higher. +// +// unicast-hop-limit: func() -> result +// +//go:nosplit +func (self UDPSocket) UnicastHopLimit() (result cm.Result[uint8, uint8, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_UDPSocketUnicastHopLimit((uint32)(self0), &result) + return +} + +// IncomingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#incoming-datagram-stream". +// +// resource incoming-datagram-stream +type IncomingDatagramStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "incoming-datagram-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self IncomingDatagramStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_IncomingDatagramStreamResourceDrop((uint32)(self0)) + return +} + +// Receive represents the imported method "receive". +// +// Receive messages on the socket. +// +// This function attempts to receive up to `max-results` datagrams on the socket without +// blocking. +// The returned list may contain fewer elements than requested, but never more. +// +// This function returns successfully with an empty list when either: +// - `max-results` is 0, or: +// - `max-results` is greater than 0, but no results are immediately available. +// This function never returns `error(would-block)`. +// +// # Typical errors +// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET +// on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `connection-refused`: The connection was refused. (ECONNREFUSED) +// +// # References +// - +// - +// - +// - +// - +// - +// - +// - +// +// receive: func(max-results: u64) -> result, error-code> +// +//go:nosplit +func (self IncomingDatagramStream) Receive(maxResults uint64) (result cm.Result[cm.List[IncomingDatagram], cm.List[IncomingDatagram], ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + maxResults0 := (uint64)(maxResults) + wasmimport_IncomingDatagramStreamReceive((uint32)(self0), (uint64)(maxResults0), &result) + return +} + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the stream is ready to receive again. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self IncomingDatagramStream) Subscribe() (result Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_IncomingDatagramStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} + +// OutgoingDatagramStream represents the imported resource "wasi:sockets/udp@0.2.0#outgoing-datagram-stream". +// +// resource outgoing-datagram-stream +type OutgoingDatagramStream cm.Resource + +// ResourceDrop represents the imported resource-drop for resource "outgoing-datagram-stream". +// +// Drops a resource handle. +// +//go:nosplit +func (self OutgoingDatagramStream) ResourceDrop() { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutgoingDatagramStreamResourceDrop((uint32)(self0)) + return +} + +// CheckSend represents the imported method "check-send". +// +// Check readiness for sending. This function never blocks. +// +// Returns the number of datagrams permitted for the next call to `send`, +// or an error. Calling `send` with more datagrams than this function has +// permitted will trap. +// +// When this function returns ok(0), the `subscribe` pollable will +// become ready when this function will report at least ok(1), or an +// error. +// +// Never returns `would-block`. +// +// check-send: func() -> result +// +//go:nosplit +func (self OutgoingDatagramStream) CheckSend() (result cm.Result[uint64, uint64, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + wasmimport_OutgoingDatagramStreamCheckSend((uint32)(self0), &result) + return +} + +// Send represents the imported method "send". +// +// Send messages on the socket. +// +// This function attempts to send all provided `datagrams` on the socket without blocking +// and +// returns how many messages were actually sent (or queued for sending). This function +// never +// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` +// is returned. +// +// This function semantically behaves the same as iterating the `datagrams` list and +// sequentially +// sending each individual datagram until either the end of the list has been reached +// or the first error occurred. +// If at least one datagram has been sent successfully, this function never returns +// an error. +// +// If the input list is empty, the function returns `ok(0)`. +// +// Each call to `send` must be permitted by a preceding `check-send`. Implementations +// must trap if +// either `check-send` was not called or `datagrams` contains more items than `check-send` +// permitted. +// +// # Typical errors +// - `invalid-argument`: The `remote-address` has the wrong address family. +// (EAFNOSUPPORT) +// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY +// (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) +// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, +// EADDRNOTAVAIL) +// - `invalid-argument`: The socket is in "connected" mode and `remote-address` +// is `some` value that does not match the address passed to `stream`. (EISCONN) +// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` +// was provided. (EDESTADDRREQ) +// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, +// ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) +// - `connection-refused`: The connection was refused. (ECONNREFUSED) +// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) +// +// # References +// - +// - +// - +// - +// - +// - +// - +// - +// +// send: func(datagrams: list) -> result +// +//go:nosplit +func (self OutgoingDatagramStream) Send(datagrams cm.List[OutgoingDatagram]) (result cm.Result[uint64, uint64, ErrorCode]) { + self0 := cm.Reinterpret[uint32](self) + datagrams0, datagrams1 := cm.LowerList(datagrams) + wasmimport_OutgoingDatagramStreamSend((uint32)(self0), (*OutgoingDatagram)(datagrams0), (uint32)(datagrams1), &result) + return +} + +// Subscribe represents the imported method "subscribe". +// +// Create a `pollable` which will resolve once the stream is ready to send again. +// +// Note: this function is here for WASI Preview2 only. +// It's planned to be removed when `future` is natively supported in Preview3. +// +// subscribe: func() -> pollable +// +//go:nosplit +func (self OutgoingDatagramStream) Subscribe() (result Pollable) { + self0 := cm.Reinterpret[uint32](self) + result0 := wasmimport_OutgoingDatagramStreamSubscribe((uint32)(self0)) + result = cm.Reinterpret[Pollable]((uint32)(result0)) + return +} diff --git a/src/machine/board_adafruit-esp32-feather-v2.go b/src/machine/board_adafruit-esp32-feather-v2.go new file mode 100644 index 0000000000..f971dcad85 --- /dev/null +++ b/src/machine/board_adafruit-esp32-feather-v2.go @@ -0,0 +1,125 @@ +//go:build adafruit_esp32_feather_v2 + +package machine + +const GPIO20 Pin = 20 + +const ( + IO0 = GPIO0 + IO2 = GPIO2 + IO4 = GPIO4 + IO5 = GPIO5 + IO7 = GPIO7 + IO8 = GPIO8 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO19 = GPIO19 + IO20 = GPIO20 + IO21 = GPIO21 + IO22 = GPIO22 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 +) + +// Digital pins +const ( + D12 = IO12 + D13 = IO13 + D14 = IO14 + D15 = IO15 + D27 = IO27 + D32 = IO32 + D33 = IO33 + D37 = IO37 +) + +// Analog pins +const ( + A0 = IO26 + A1 = IO25 + A2 = IO34 + A3 = IO39 + A4 = IO36 + A5 = IO4 +) + +// Built-in LEDs and Button +const ( + WS2812 = IO0 + NEOPIXEL = WS2812 + NEOPIXEL_I2C_POWER = IO2 + LED = IO13 + BUTTON = IO38 +) + +// SPI pins +const ( + SPI_SCK_PIN = IO5 + SPI_MOSI_PIN = IO19 + SPI_MISO_PIN = IO21 + + SPI_SDO_PIN = SPI_MOSI_PIN + SPI_SDI_PIN = SPI_MISO_PIN + + // Silk labels + SCK = SPI_SCK_PIN + MO = SPI_MOSI_PIN + MI = SPI_MISO_PIN +) + +// I2C pins +const ( + I2C_SCL_PIN = IO20 + I2C_SDA_PIN = IO22 + + // Silk labels + SCL = I2C_SCL_PIN + SDA = I2C_SDA_PIN +) + +// ADC pins +const ( + ADC1_0 = IO36 + ADC1_1 = IO37 + ADC1_2 = IO38 + ADC1_3 = IO39 + ADC1_4 = IO32 + ADC1_5 = IO33 + ADC1_6 = IO34 + ADC1_7 = IO35 + + ADC2_0 = IO4 + ADC2_1 = IO0 + ADC2_2 = IO2 + ADC2_3 = IO15 + ADC2_4 = IO13 + ADC2_5 = IO12 + ADC2_6 = IO14 + ADC2_7 = IO27 + ADC2_8 = IO25 + ADC2_9 = IO26 +) + +// UART pins +const ( + UART_TX_PIN = IO19 + UART_RX_PIN = IO22 + + UART2_TX_PIN = IO8 + UART2_RX_PIN = IO7 + + // Silk labels + RX = UART2_RX_PIN + TX = UART2_TX_PIN +) diff --git a/src/machine/board_ae_rp2040.go b/src/machine/board_ae_rp2040.go index 91432e4b41..716cf72b31 100644 --- a/src/machine/board_ae_rp2040.go +++ b/src/machine/board_ae_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // GPIO pins const ( GP0 Pin = GPIO0 @@ -77,28 +72,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "AE-RP2040" diff --git a/src/machine/board_arduino_mkr1000.go b/src/machine/board_arduino_mkr1000.go index 2c9ae603f4..f5130120e7 100644 --- a/src/machine/board_arduino_mkr1000.go +++ b/src/machine/board_arduino_mkr1000.go @@ -74,8 +74,9 @@ const ( // I2S pins const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA07 - I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33. + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin + I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR1000 ) // USB CDC identifiers diff --git a/src/machine/board_arduino_mkrwifi1010.go b/src/machine/board_arduino_mkrwifi1010.go index 18330f37f7..c68da9b626 100644 --- a/src/machine/board_arduino_mkrwifi1010.go +++ b/src/machine/board_arduino_mkrwifi1010.go @@ -74,7 +74,8 @@ const ( // I2S pins const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA07 + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino MKR WiFi 1010. ) diff --git a/src/machine/board_arduino_nano33.go b/src/machine/board_arduino_nano33.go index 17f255443e..9232d38190 100644 --- a/src/machine/board_arduino_nano33.go +++ b/src/machine/board_arduino_nano33.go @@ -118,7 +118,8 @@ const ( // I2S pins const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA08 + I2S_SDO_PIN Pin = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33. ) diff --git a/src/machine/board_arduino_zero.go b/src/machine/board_arduino_zero.go index f09fb47c56..758fcb16e0 100644 --- a/src/machine/board_arduino_zero.go +++ b/src/machine/board_arduino_zero.go @@ -69,7 +69,8 @@ const ( // I2S pins - might not be exposed const ( I2S_SCK_PIN Pin = PA10 - I2S_SD_PIN Pin = PA07 + I2S_SDO_PIN Pin = PA07 + I2S_SDI_PIN = NoPin I2S_WS_PIN Pin = PA11 ) diff --git a/src/machine/board_badger2040-w.go b/src/machine/board_badger2040-w.go new file mode 100644 index 0000000000..d0982653b3 --- /dev/null +++ b/src/machine/board_badger2040-w.go @@ -0,0 +1,95 @@ +//go:build badger2040_w + +// This contains the pin mappings for the Badger 2040 W board. +// +// For more information, see: https://shop.pimoroni.com/products/badger-2040-w +// Also +// - Badger 2040 W schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_w_schematic.pdf?v=1675859004 +package machine + +const ( + LED Pin = GPIO22 + + BUTTON_A Pin = GPIO12 + BUTTON_B Pin = GPIO13 + BUTTON_C Pin = GPIO14 + BUTTON_UP Pin = GPIO15 + BUTTON_DOWN Pin = GPIO11 + BUTTON_USER Pin = NoPin // Not available on Badger 2040 W + + EPD_BUSY_PIN Pin = GPIO26 + EPD_RESET_PIN Pin = GPIO21 + EPD_DC_PIN Pin = GPIO20 + EPD_CS_PIN Pin = GPIO17 + EPD_SCK_PIN Pin = GPIO18 + EPD_SDO_PIN Pin = GPIO19 + + VBUS_DETECT Pin = GPIO24 + VREF_POWER Pin = GPIO27 + VREF_1V24 Pin = GPIO28 + VBAT_SENSE Pin = GPIO29 + ENABLE_3V3 Pin = GPIO10 + + BATTERY = VBAT_SENSE + RTC_ALARM = GPIO8 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GPIO4 + I2C0_SCL_PIN Pin = GPIO5 + + I2C1_SDA_PIN Pin = NoPin + I2C1_SCL_PIN Pin = NoPin +) + +// SPI pins. +const ( + SPI0_SCK_PIN Pin = GPIO18 + SPI0_SDO_PIN Pin = GPIO19 + SPI0_SDI_PIN Pin = GPIO16 + + SPI1_SCK_PIN Pin = NoPin + SPI1_SDO_PIN Pin = NoPin + SPI1_SDI_PIN Pin = NoPin +) + +// QSPI pins¿? +const ( +/* + TODO + +SPI0_SD0_PIN Pin = QSPI_SD0 +SPI0_SD1_PIN Pin = QSPI_SD1 +SPI0_SD2_PIN Pin = QSPI_SD2 +SPI0_SD3_PIN Pin = QSPI_SD3 +SPI0_SCK_PIN Pin = QSPI_SCLKGPIO6 +SPI0_CS_PIN Pin = QSPI_CS +*/ +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Badger 2040 W" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 diff --git a/src/machine/board_badger2040.go b/src/machine/board_badger2040.go index d1b29f4c64..73f802a909 100644 --- a/src/machine/board_badger2040.go +++ b/src/machine/board_badger2040.go @@ -1,17 +1,12 @@ //go:build badger2040 -// This contains the pin mappings for the Badger 2040 Connect board. +// This contains the pin mappings for the Badger 2040 board. // // For more information, see: https://shop.pimoroni.com/products/badger-2040 // Also // - Badger 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/badger_2040_schematic.pdf?v=1645702148 package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( LED Pin = GPIO25 @@ -30,8 +25,12 @@ const ( EPD_SDO_PIN Pin = GPIO19 VBUS_DETECT Pin = GPIO24 - BATTERY Pin = GPIO29 + VREF_POWER Pin = GPIO27 + VREF_1V24 Pin = GPIO28 + VBAT_SENSE Pin = GPIO29 ENABLE_3V3 Pin = GPIO10 + + BATTERY = VBAT_SENSE ) // I2C pins @@ -92,17 +91,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} diff --git a/src/machine/board_challenger_rp2040.go b/src/machine/board_challenger_rp2040.go index b67b3245d3..9a85aa0aef 100644 --- a/src/machine/board_challenger_rp2040.go +++ b/src/machine/board_challenger_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( LED = GPIO24 @@ -84,28 +79,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Challenger 2040 LoRa" diff --git a/src/machine/board_circuitplay_express.go b/src/machine/board_circuitplay_express.go index 1601fcab32..ce1f29c75b 100644 --- a/src/machine/board_circuitplay_express.go +++ b/src/machine/board_circuitplay_express.go @@ -102,7 +102,8 @@ var SPI0 = sercomSPIM3 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync ) diff --git a/src/machine/board_elecrow-rp2040-w5.go b/src/machine/board_elecrow-rp2040-w5.go new file mode 100644 index 0000000000..1ea0181d1f --- /dev/null +++ b/src/machine/board_elecrow-rp2040-w5.go @@ -0,0 +1,94 @@ +//go:build elecrow_rp2040 + +// This file contains the pin mappings for the Elecrow Pico rp2040 W5 boards. +// +// Elecrow Pico rp2040 W5 is a microcontroller using the Raspberry Pi RP2040 +// chip and rtl8720d Wifi chip. +// +// - https://www.elecrow.com/wiki/PICO_W5_RP2040_Dev_Board.html +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx + UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/board_elecrow-rp2350-w5.go b/src/machine/board_elecrow-rp2350-w5.go new file mode 100644 index 0000000000..80a8436444 --- /dev/null +++ b/src/machine/board_elecrow-rp2350-w5.go @@ -0,0 +1,94 @@ +//go:build elecrow_rp2350 + +// This file contains the pin mappings for the Elecrow Pico rp2350 W5 boards. +// +// Elecrow Pico rp2350 W5 is a microcontroller using the Raspberry Pi RP2350 +// chip and rtl8720d Wifi chip. +// +// - https://www.elecrow.com/pico-w5-microcontroller-development-boards-rp2350-microcontroller-board.html +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 // Wired to rtl8720d UART1_Tx + UART1_RX_PIN = GPIO5 // Wired to rtl8720n UART1_Rx + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico2" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/src/machine/board_esp-c3-32s-kit.go b/src/machine/board_esp-c3-32s-kit.go new file mode 100644 index 0000000000..09385aa3d8 --- /dev/null +++ b/src/machine/board_esp-c3-32s-kit.go @@ -0,0 +1,40 @@ +//go:build esp_c3_32s_kit + +package machine + +// See: +// * https://www.waveshare.com/w/upload/8/8f/Esp32-c3s_specification.pdf +// * https://www.waveshare.com/w/upload/4/46/Nodemcu-esp-c3-32s-kit-schematics.pdf + +// Digital Pins +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO18 = GPIO18 + IO19 = GPIO19 +) + +const ( + LED_RED = IO3 + LED_GREEN = IO4 + LED_BLUE = IO5 + + LED = LED_RED + + LED1 = LED_RED + LED2 = LED_GREEN +) + +// I2C pins +const ( + SDA_PIN = NoPin + SCL_PIN = NoPin +) diff --git a/src/machine/board_esp32-c3-devkit-rust-1.go b/src/machine/board_esp32-c3-devkit-rust-1.go index 3cba69d1bb..8e47269f95 100644 --- a/src/machine/board_esp32-c3-devkit-rust-1.go +++ b/src/machine/board_esp32-c3-devkit-rust-1.go @@ -64,8 +64,8 @@ const ( // I2C pins const ( - I2C_SCL_PIN = D8 - I2C_SDA_PIN = D10 + SCL_PIN = D8 + SDA_PIN = D10 ) // USBCDC pins diff --git a/src/machine/board_esp32c3-12f.go b/src/machine/board_esp32c3-12f.go index df28915a4f..f023bb9d61 100644 --- a/src/machine/board_esp32c3-12f.go +++ b/src/machine/board_esp32c3-12f.go @@ -46,3 +46,9 @@ const ( UART_TX_PIN = TXD UART_RX_PIN = RXD ) + +// I2C pins +const ( + SCL_PIN = NoPin + SDA_PIN = NoPin +) diff --git a/src/machine/board_esp32c3-supermini.go b/src/machine/board_esp32c3-supermini.go new file mode 100644 index 0000000000..c180ff0e3e --- /dev/null +++ b/src/machine/board_esp32c3-supermini.go @@ -0,0 +1,57 @@ +//go:build esp32c3_supermini + +// This file contains the pin mappings for the ESP32 supermini boards. +// +// - https://web.archive.org/web/20240805232453/https://dl.artronshop.co.th/ESP32-C3%20SuperMini%20datasheet.pdf + +package machine + +// Digital Pins +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO20 = GPIO20 + IO21 = GPIO21 +) + +// Built-in LED +const LED = GPIO8 + +// Analog pins +const ( + A0 = GPIO0 + A1 = GPIO1 + A2 = GPIO2 + A3 = GPIO3 + A4 = GPIO4 + A5 = GPIO5 +) + +// UART pins +const ( + UART_RX_PIN = GPIO20 + UART_TX_PIN = GPIO21 +) + +// I2C pins +const ( + SDA_PIN = GPIO8 + SCL_PIN = GPIO9 +) + +// SPI pins +const ( + SPI_MISO_PIN = GPIO5 + SPI_MOSI_PIN = GPIO6 + SPI_SS_PIN = GPIO7 + SPI_SCK_PIN = GPIO4 +) diff --git a/src/machine/board_feather-m0-express.go b/src/machine/board_feather-m0-express.go index a0f7c23055..226369ffcd 100644 --- a/src/machine/board_feather-m0-express.go +++ b/src/machine/board_feather-m0-express.go @@ -81,7 +81,8 @@ var SPI0 = sercomSPIM4 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA07 + I2S_SDO_PIN = PA07 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0 Express. ) diff --git a/src/machine/board_feather-m0.go b/src/machine/board_feather-m0.go index 5cd3393400..f38d8ec889 100644 --- a/src/machine/board_feather-m0.go +++ b/src/machine/board_feather-m0.go @@ -76,7 +76,8 @@ var SPI0 = sercomSPIM4 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0. ) diff --git a/src/machine/board_feather-nrf52840-sense.go b/src/machine/board_feather-nrf52840-sense.go index 2a78450818..ab1c3fbe1b 100644 --- a/src/machine/board_feather-nrf52840-sense.go +++ b/src/machine/board_feather-nrf52840-sense.go @@ -2,7 +2,7 @@ package machine -const HasLowFrequencyCrystal = true +const HasLowFrequencyCrystal = false // GPIO Pins const ( diff --git a/src/machine/board_feather-stm32f405.go b/src/machine/board_feather-stm32f405.go index 5b980867f5..4a184bad51 100644 --- a/src/machine/board_feather-stm32f405.go +++ b/src/machine/board_feather-stm32f405.go @@ -186,15 +186,15 @@ const ( ) var ( - SPI1 = SPI{ + SPI1 = &SPI{ Bus: stm32.SPI2, AltFuncSelector: AF5_SPI1_SPI2, } - SPI2 = SPI{ + SPI2 = &SPI{ Bus: stm32.SPI3, AltFuncSelector: AF6_SPI3, } - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } diff --git a/src/machine/board_feather_rp2040.go b/src/machine/board_feather_rp2040.go index f121a1e832..44091e56e9 100644 --- a/src/machine/board_feather_rp2040.go +++ b/src/machine/board_feather_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -73,28 +68,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Feather RP2040" diff --git a/src/machine/board_gemma-m0.go b/src/machine/board_gemma-m0.go index 3702c74c3c..af1caaad6e 100644 --- a/src/machine/board_gemma-m0.go +++ b/src/machine/board_gemma-m0.go @@ -76,7 +76,8 @@ var ( // I2S (not connected, needed for atsamd21). const ( I2S_SCK_PIN = NoPin - I2S_SD_PIN = NoPin + I2S_SDO_PIN = NoPin + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin ) diff --git a/src/machine/board_gnse.go b/src/machine/board_gnse.go index 02a8038b14..8e78b43e5a 100644 --- a/src/machine/board_gnse.go +++ b/src/machine/board_gnse.go @@ -77,7 +77,7 @@ var ( I2C0 = I2C1 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_gopher-badge.go b/src/machine/board_gopher-badge.go index e7eff25f84..7af27118b2 100644 --- a/src/machine/board_gopher-badge.go +++ b/src/machine/board_gopher-badge.go @@ -5,11 +5,6 @@ // For more information, see: https://gopherbadge.com/ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( /*ADC0 Pin = GPIO26 ADC1 Pin = GPIO27 @@ -92,17 +87,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART1 - -func init() { - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} diff --git a/src/machine/board_grandcentral-m4.go b/src/machine/board_grandcentral-m4.go index 46fb956978..61ef6a89b8 100644 --- a/src/machine/board_grandcentral-m4.go +++ b/src/machine/board_grandcentral-m4.go @@ -224,7 +224,8 @@ const ( I2S_SCK_PIN = I2S0_SCK_PIN // default pins I2S_WS_PIN = I2S0_FS_PIN // - I2S_SD_PIN = I2S0_SDO_PIN // + I2S_SDO_PIN = I2S0_SDO_PIN + I2S_SDI_PIN = NoPin ) // SD card pins diff --git a/src/machine/board_hifive1b_baremetal.go b/src/machine/board_hifive1b_baremetal.go index cea0443e69..f621d3bc56 100644 --- a/src/machine/board_hifive1b_baremetal.go +++ b/src/machine/board_hifive1b_baremetal.go @@ -6,7 +6,7 @@ import "device/sifive" // SPI on the HiFive1. var ( - SPI1 = SPI{ + SPI1 = &SPI{ Bus: sifive.QSPI1, } ) diff --git a/src/machine/board_hw-651.go b/src/machine/board_hw-651.go new file mode 100644 index 0000000000..54731502e1 --- /dev/null +++ b/src/machine/board_hw-651.go @@ -0,0 +1,77 @@ +//go:build hw_651 + +package machine + +// No-name brand board based on the nRF51822 chip with low frequency crystal on board. +// Pinout (reverse engineered from the board) can be found here: +// https://aviatorahmet.blogspot.com/2020/12/pinout-of-nrf51822-board.html +// https://cr0wg4n.medium.com/pinout-nrf51822-board-hw-651-78da2eda8894 + +const HasLowFrequencyCrystal = true + +var DefaultUART = UART0 + +// GPIO pins on header J1 +const ( + J1_01 = P0_21 + J1_03 = P0_23 + J1_04 = P0_22 + J1_05 = P0_25 + J1_06 = P0_24 + J1_09 = P0_29 + J1_10 = P0_28 + J1_11 = P0_30 + J1_13 = P0_00 + J1_15 = P0_02 + J1_17 = P0_04 + J1_16 = P0_01 + J1_18 = P0_03 +) + +// GPIO pins on header J2 +const ( + J2_01 = P0_20 + J2_03 = P0_18 + J2_04 = P0_19 + J2_07 = P0_16 + J2_08 = P0_15 + J2_09 = P0_14 + J2_10 = P0_13 + J2_11 = P0_12 + J2_12 = P0_11 + J2_13 = P0_10 + J2_14 = P0_09 + J2_15 = P0_08 + J2_16 = P0_07 + J2_17 = P0_06 + J2_18 = P0_05 +) + +// UART pins +const ( + UART_TX_PIN = P0_24 // J1_06 on the board + UART_RX_PIN = P0_25 // J1_05 on the board +) + +// ADC pins +const ( + ADC0 = P0_03 // J1_18 on the board + ADC1 = P0_02 // J1_15 on the board + ADC2 = P0_01 // J1_16 on the board + ADC3 = P0_04 // J1_17 on the board + ADC4 = P0_05 // J2_18 on the board + ADC5 = P0_06 // J2_17 on the board +) + +// I2C pins +const ( + SDA_PIN = P0_30 // J1_11 on the board + SCL_PIN = P0_00 // J1_13 on the board +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_23 // J1_03 on the board + SPI0_SDO_PIN = P0_21 // J1_01 on the board + SPI0_SDI_PIN = P0_22 // J1_04 on the board +) diff --git a/src/machine/board_itsybitsy-m0.go b/src/machine/board_itsybitsy-m0.go index 67ebdee904..0cc6cad313 100644 --- a/src/machine/board_itsybitsy-m0.go +++ b/src/machine/board_itsybitsy-m0.go @@ -89,7 +89,8 @@ var SPI1 = sercomSPIM5 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0. ) diff --git a/src/machine/board_kb2040.go b/src/machine/board_kb2040.go index 288f1ddf3c..1a6f353623 100644 --- a/src/machine/board_kb2040.go +++ b/src/machine/board_kb2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -75,28 +70,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "KB2040" diff --git a/src/machine/board_lgt92.go b/src/machine/board_lgt92.go index ff4d03cd82..71b05083cc 100644 --- a/src/machine/board_lgt92.go +++ b/src/machine/board_lgt92.go @@ -84,10 +84,10 @@ var ( I2C0 = I2C1 // SPI - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, } - SPI1 = &SPI0 + SPI1 = SPI0 ) func init() { diff --git a/src/machine/board_lorae5.go b/src/machine/board_lorae5.go index f2c26997c4..18b5d8e363 100644 --- a/src/machine/board_lorae5.go +++ b/src/machine/board_lorae5.go @@ -85,7 +85,7 @@ var ( I2C0 = I2C2 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_m5paper.go b/src/machine/board_m5paper.go new file mode 100644 index 0000000000..7c20f4dba3 --- /dev/null +++ b/src/machine/board_m5paper.go @@ -0,0 +1,112 @@ +//go:build m5paper + +package machine + +const ( + IO0 = GPIO0 + IO1 = GPIO1 + IO2 = GPIO2 + IO3 = GPIO3 + IO4 = GPIO4 + IO5 = GPIO5 + IO6 = GPIO6 + IO7 = GPIO7 + IO8 = GPIO8 + IO9 = GPIO9 + IO10 = GPIO10 + IO11 = GPIO11 + IO12 = GPIO12 + IO13 = GPIO13 + IO14 = GPIO14 + IO15 = GPIO15 + IO16 = GPIO16 + IO17 = GPIO17 + IO18 = GPIO18 + IO19 = GPIO19 + IO21 = GPIO21 + IO22 = GPIO22 + IO23 = GPIO23 + IO25 = GPIO25 + IO26 = GPIO26 + IO27 = GPIO27 + IO32 = GPIO32 + IO33 = GPIO33 + IO34 = GPIO34 + IO35 = GPIO35 + IO36 = GPIO36 + IO37 = GPIO37 + IO38 = GPIO38 + IO39 = GPIO39 +) + +const ( + POWER_PIN = IO2 + EXT_POWER_PIN = IO5 + EPD_POWER_PIN = IO23 + + // Buttons + BUTTON_RIGHT = IO39 + BUTTON_PUSH = IO38 + BUTTON_LEFT = IO37 + BUTTON = BUTTON_PUSH + + // Touch Screen Interrupt + TOUCH_INT = IO36 +) + +// SPI pins +const ( + SPI0_SCK_PIN = IO14 + SPI0_SDO_PIN = IO12 + SPI0_SDI_PIN = IO13 + + // EPD (IT8951) + EPD_SCK_PIN = SPI0_SCK_PIN + EPD_SDO_PIN = SPI0_SDO_PIN + EPD_SDI_PIN = SPI0_SDI_PIN + EPD_CS_PIN = IO15 + EPD_BUSY_PIN = IO27 + + // SD CARD + SDCARD_SCK_PIN = SPI0_SCK_PIN + SDCARD_SDO_PIN = SPI0_SDO_PIN + SDCARD_SDI_PIN = SPI0_SDI_PIN + SDCARD_CS_PIN = IO4 +) + +// I2C pins +const ( + SDA0_PIN = IO21 + SCL0_PIN = IO22 + + SDA_PIN = SDA0_PIN + SCL_PIN = SCL0_PIN + + I2C_TEMP_ADDR = 0x44 // temperature sensor (SHT30) + I2C_CLOCK_ADDR = 0x51 // real time clock (BM8563) + I2C_TOUCH_ADDR = 0x5D // touch screen controller (GT911) +) + +// ADC pins +const ( + ADC1 Pin = IO35 + ADC2 Pin = IO36 + + BATTERY_ADC_PIN = ADC1 +) + +// DAC pins +const ( + DAC1 Pin = IO25 + DAC2 Pin = IO26 +) + +// UART pins +const ( + // UART0 (CP2104) + UART0_TX_PIN = IO1 + UART0_RX_PIN = IO3 + + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) diff --git a/src/machine/board_macropad-rp2040.go b/src/machine/board_macropad-rp2040.go index eca20f7970..78bd2b749e 100644 --- a/src/machine/board_macropad-rp2040.go +++ b/src/machine/board_macropad-rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( NeopixelCount = 12 @@ -78,21 +73,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "MacroPad RP2040" diff --git a/src/machine/board_maixbit_baremetal.go b/src/machine/board_maixbit_baremetal.go index 7318cfa9e3..f5a7e8d4cb 100644 --- a/src/machine/board_maixbit_baremetal.go +++ b/src/machine/board_maixbit_baremetal.go @@ -6,10 +6,10 @@ import "device/kendryte" // SPI on the MAix Bit. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: kendryte.SPI0, } - SPI1 = SPI{ + SPI1 = &SPI{ Bus: kendryte.SPI1, } ) diff --git a/src/machine/board_makerfabs-esp32c3spi35.go b/src/machine/board_makerfabs-esp32c3spi35.go index 8e4401f5f8..6e4e6f4bef 100644 --- a/src/machine/board_makerfabs-esp32c3spi35.go +++ b/src/machine/board_makerfabs-esp32c3spi35.go @@ -64,8 +64,8 @@ const ( // Touchscreen pins const ( TS_CS_PIN = D0 - TS_SDA_PIN = I2C_SDA_PIN - TS_SCL_PIN = I2C_SCL_PIN + TS_SDA_PIN = SDA_PIN + TS_SCL_PIN = SCL_PIN ) // MicroSD pins @@ -90,8 +90,8 @@ const ( // I2C pins const ( - I2C_SDA_PIN = D2 - I2C_SCL_PIN = D3 + SDA_PIN = D2 + SCL_PIN = D3 ) // SPI pins diff --git a/src/machine/board_mksnanov3.go b/src/machine/board_mksnanov3.go index d096cdb9d1..35fef682d4 100644 --- a/src/machine/board_mksnanov3.go +++ b/src/machine/board_mksnanov3.go @@ -89,11 +89,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/board_nano-33-ble.go b/src/machine/board_nano-33-ble.go index 911be0add5..758d5434e1 100644 --- a/src/machine/board_nano-33-ble.go +++ b/src/machine/board_nano-33-ble.go @@ -26,7 +26,7 @@ // SoftDevice (s140v7) must be flashed first to enable use of bluetooth on this board. // See https://github.com/tinygo-org/bluetooth // -// SoftDevice overwrites original bootloader and flashing method described above is not avalable anymore. +// SoftDevice overwrites original bootloader and flashing method described above is not available anymore. // Instead, please use debug probe and flash your code with "nano-33-ble-s140v7" target. package machine diff --git a/src/machine/board_nano-rp2040.go b/src/machine/board_nano-rp2040.go index 4f5757e865..8155523134 100644 --- a/src/machine/board_nano-rp2040.go +++ b/src/machine/board_nano-rp2040.go @@ -11,11 +11,6 @@ // - Nano RP2040 Connect technical reference: https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-01-technical-reference package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Digital Pins const ( D2 Pin = GPIO25 @@ -125,27 +120,7 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } - - // UART_NINA on the Arduino Nano RP2040 connects to the NINA HCI. - UART_NINA = UART1 -) +// UART_NINA on the Arduino Nano RP2040 connects to the NINA HCI. +var UART_NINA = UART1 var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} diff --git a/src/machine/board_nucleol031k6.go b/src/machine/board_nucleol031k6.go index 1a57289b9d..aea92d8be0 100644 --- a/src/machine/board_nucleol031k6.go +++ b/src/machine/board_nucleol031k6.go @@ -86,11 +86,11 @@ var ( I2C0 = I2C1 // SPI - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: 0, } - SPI1 = &SPI0 + SPI1 = SPI0 ) func init() { diff --git a/src/machine/board_nucleol476rg.go b/src/machine/board_nucleol476rg.go new file mode 100644 index 0000000000..0a173afee9 --- /dev/null +++ b/src/machine/board_nucleol476rg.go @@ -0,0 +1,105 @@ +//go:build nucleol476rg + +// Schematic: https://www.st.com/resource/en/user_manual/um1724-stm32-nucleo64-boards-mb1136-stmicroelectronics.pdf +// Datasheet: https://www.st.com/resource/en/datasheet/stm32l476je.pdf + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +const ( + // Arduino Pins + A0 = PA0 + A1 = PA1 + A2 = PA4 + A3 = PB0 + A4 = PC1 + A5 = PC0 + + D0 = PA3 + D1 = PA2 + D2 = PA10 + D3 = PB3 + D4 = PB5 + D5 = PB4 + D6 = PB10 + D7 = PA8 + D8 = PA9 + D9 = PC7 + D10 = PB6 + D11 = PA7 + D12 = PA6 + D13 = PA5 + D14 = PB9 + D15 = PB8 +) + +// User LD2: the green LED is a user LED connected to ARDUINO® signal D13 corresponding +// to STM32 I/O PA5 (pin 21) or PB13 (pin 34) depending on the STM32 target. +const ( + LED = LED_BUILTIN + LED_BUILTIN = LED_GREEN + LED_GREEN = PA5 +) + +const ( + // This board does not have a user button, so + // use first GPIO pin by default + BUTTON = PA0 +) + +const ( + // UART pins + // PA2 and PA3 are connected to the ST-Link Virtual Com Port (VCP) + UART_TX_PIN = PA2 + UART_RX_PIN = PA3 + + // I2C pins + // With default solder bridge settings: + // PB8 / Arduino D5 / CN3 Pin 8 is SCL + // PB7 / Arduino D4 / CN3 Pin 7 is SDA + I2C0_SCL_PIN = PB8 + I2C0_SDA_PIN = PB9 + + // SPI pins + SPI1_SCK_PIN = PA5 + SPI1_SDI_PIN = PA6 + SPI1_SDO_PIN = PA7 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +var ( + // USART2 is the hardware serial port connected to the onboard ST-LINK + // debugger to be exposed as virtual COM port over USB on Nucleo boards. + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART2, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART1 + + // I2C1 is documented, alias to I2C0 as well + I2C1 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } + I2C0 = I2C1 + + // SPI1 is documented, alias to SPI0 as well + SPI1 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_2, + } + SPI0 = SPI1 +) + +func init() { + UART1.Interrupt = interrupt.New(stm32.IRQ_USART2, _UART1.handleInterrupt) +} diff --git a/src/machine/board_nucleowl55jc.go b/src/machine/board_nucleowl55jc.go index fd4883cc02..a8e88dd8bb 100644 --- a/src/machine/board_nucleowl55jc.go +++ b/src/machine/board_nucleowl55jc.go @@ -84,7 +84,7 @@ var ( I2C0 = I2C1 // SPI - SPI3 = SPI{ + SPI3 = &SPI{ Bus: stm32.SPI3, } ) diff --git a/src/machine/board_p1am-100.go b/src/machine/board_p1am-100.go index d6fbdcb36a..f2a7d13f95 100644 --- a/src/machine/board_p1am-100.go +++ b/src/machine/board_p1am-100.go @@ -123,7 +123,8 @@ var ( // I2S pins const ( I2S_SCK_PIN Pin = D2 - I2S_SD_PIN Pin = A6 + I2S_SDO_PIN Pin = A6 + I2S_SDI_PIN = NoPin I2S_WS_PIN = D3 ) diff --git a/src/machine/board_pga2350.go b/src/machine/board_pga2350.go new file mode 100644 index 0000000000..710f14d85a --- /dev/null +++ b/src/machine/board_pga2350.go @@ -0,0 +1,98 @@ +//go:build pga2350 + +package machine + +// PGA2350 pin definitions. +const ( + GP0 = GPIO0 + GP1 = GPIO1 + GP2 = GPIO2 + GP3 = GPIO3 + GP4 = GPIO4 + GP5 = GPIO5 + GP6 = GPIO6 + GP7 = GPIO7 + GP8 = GPIO8 + GP9 = GPIO9 + GP10 = GPIO10 + GP11 = GPIO11 + GP12 = GPIO12 + GP13 = GPIO13 + GP14 = GPIO14 + GP15 = GPIO15 + GP16 = GPIO16 + GP17 = GPIO17 + GP18 = GPIO18 + GP19 = GPIO19 + GP20 = GPIO20 + GP21 = GPIO21 + GP22 = GPIO22 + GP26 = GPIO26 + GP27 = GPIO27 + GP28 = GPIO28 + GP29 = GPIO29 + GP30 = GPIO30 // peripherals: PWM7 channel A + GP31 = GPIO31 // peripherals: PWM7 channel B + GP32 = GPIO32 // peripherals: PWM8 channel A + GP33 = GPIO33 // peripherals: PWM8 channel B + GP34 = GPIO34 // peripherals: PWM9 channel A + GP35 = GPIO35 // peripherals: PWM9 channel B + GP36 = GPIO36 // peripherals: PWM10 channel A + GP37 = GPIO37 // peripherals: PWM10 channel B + GP38 = GPIO38 // peripherals: PWM11 channel A + GP39 = GPIO39 // peripherals: PWM11 channel B + GP40 = GPIO40 // peripherals: PWM8 channel A + GP41 = GPIO41 // peripherals: PWM8 channel B + GP42 = GPIO42 // peripherals: PWM9 channel A + GP43 = GPIO43 // peripherals: PWM9 channel B + GP44 = GPIO44 // peripherals: PWM10 channel A + GP45 = GPIO45 // peripherals: PWM10 channel B + GP46 = GPIO46 // peripherals: PWM11 channel A + GP47 = GPIO47 // peripherals: PWM11 channel B + +) + +var DefaultUART = UART0 + +// Peripheral defaults. +const ( + xoscFreq = 12 // MHz + + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 + + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx + + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB identifiers +const ( + usb_STRING_PRODUCT = "PGA2350" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/board_pico.go b/src/machine/board_pico.go index 41109af03d..efbd6ef7dc 100644 --- a/src/machine/board_pico.go +++ b/src/machine/board_pico.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // GPIO pins const ( GP0 Pin = GPIO0 @@ -79,28 +74,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Pico" diff --git a/src/machine/board_pico2.go b/src/machine/board_pico2.go new file mode 100644 index 0000000000..327c542fbc --- /dev/null +++ b/src/machine/board_pico2.go @@ -0,0 +1,88 @@ +//go:build pico2 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico2" + usb_STRING_MANUFACTURER = "Raspberry Pi" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/board_pico_plus2.go b/src/machine/board_pico_plus2.go new file mode 100644 index 0000000000..c21c9ea25d --- /dev/null +++ b/src/machine/board_pico_plus2.go @@ -0,0 +1,93 @@ +//go:build pico_plus2 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = GPIO17 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP21 Pin = GPIO21 + GP22 Pin = GPIO22 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP32 Pin = GPIO32 + GP33 Pin = GPIO33 + GP34 Pin = GPIO34 + GP35 Pin = GPIO35 + GP36 Pin = GPIO36 + + // Onboard LED + LED Pin = GPIO25 + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Raspberry Pico. +const ( + I2C0_SDA_PIN = GP4 + I2C0_SCL_PIN = GP5 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO18 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO19 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO16 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO10 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO11 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO12 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO8 + UART1_RX_PIN = GPIO9 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Pico Plus2" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/src/machine/board_qtpy.go b/src/machine/board_qtpy.go index 49bb9c97b5..e8a93e38d9 100644 --- a/src/machine/board_qtpy.go +++ b/src/machine/board_qtpy.go @@ -81,7 +81,8 @@ var ( // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on QT Py M0. I2S_WS_PIN = NoPin // TODO: figure out what this is on QT Py M0. ) diff --git a/src/machine/board_qtpy_rp2040.go b/src/machine/board_qtpy_rp2040.go index 3f37023f89..3eabf0c9b6 100644 --- a/src/machine/board_qtpy_rp2040.go +++ b/src/machine/board_qtpy_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -84,28 +79,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "QT Py RP2040" diff --git a/src/machine/board_rak4631.go b/src/machine/board_rak4631.go new file mode 100644 index 0000000000..c59f3717b7 --- /dev/null +++ b/src/machine/board_rak4631.go @@ -0,0 +1,86 @@ +//go:build rak4631 + +package machine + +const HasLowFrequencyCrystal = true + +// Digital Pins +const ( + D0 Pin = P0_28 + D1 Pin = P0_02 +) + +// Analog pins +const ( + A0 Pin = P0_17 + A1 Pin = P1_02 + A2 Pin = P0_21 +) + +// Onboard LEDs +const ( + LED = LED2 + LED1 = P1_03 + LED2 = P1_04 +) + +// UART pins +const ( + // Default to UART1 + UART_RX_PIN = UART0_RX_PIN + UART_TX_PIN = UART0_TX_PIN + + // UART1 + UART0_RX_PIN = P0_19 + UART0_TX_PIN = P0_20 + + // UART2 + UART1_RX_PIN = P0_15 + UART1_TX_PIN = P0_16 +) + +// I2C pins +const ( + SDA_PIN = SDA1_PIN + SCL_PIN = SCL1_PIN + + SDA1_PIN = P0_13 + SCL1_PIN = P0_14 + + SDA2_PIN = P0_24 + SCL2_PIN = P0_25 +) + +// SPI pins +const ( + SPI0_SCK_PIN = P0_03 + SPI0_SDO_PIN = P0_29 + SPI0_SDI_PIN = P0_30 +) + +// Peripherals +const ( + LORA_NSS = P1_10 + LORA_SCK = P1_11 + LORA_MOSI = P1_12 + LORA_MISO = P1_13 + LORA_BUSY = P1_14 + LORA_DIO1 = P1_15 + LORA_NRESET = P1_06 + LORA_POWER = P1_05 +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "WisCore RAK4631 Board" + usb_STRING_MANUFACTURER = "RAKwireless" +) + +var ( + usb_VID uint16 = 0x239a + usb_PID uint16 = 0x8029 +) + +var ( + DefaultUART = UART0 +) diff --git a/src/machine/board_stm32f469disco.go b/src/machine/board_stm32f469disco.go index a4eef1420a..8fb5cde2ea 100644 --- a/src/machine/board_stm32f469disco.go +++ b/src/machine/board_stm32f469disco.go @@ -59,11 +59,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/board_stm32f4disco.go b/src/machine/board_stm32f4disco.go index 291109c5de..d048fcacf4 100644 --- a/src/machine/board_stm32f4disco.go +++ b/src/machine/board_stm32f4disco.go @@ -86,11 +86,11 @@ const ( // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI0 = SPI{ + SPI0 = &SPI{ Bus: stm32.SPI1, AltFuncSelector: AF5_SPI1_SPI2, } - SPI1 = &SPI0 + SPI1 = SPI0 ) const ( diff --git a/src/machine/board_teensy40.go b/src/machine/board_teensy40.go index 92fe81bce9..22529a8d75 100644 --- a/src/machine/board_teensy40.go +++ b/src/machine/board_teensy40.go @@ -263,9 +263,8 @@ const ( ) var ( - SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) - SPI1 = &_SPI1 - _SPI1 = SPI{ + SPI0 = SPI1 // SPI0 is an alias of SPI1 (LPSPI4) + SPI1 = &SPI{ Bus: nxp.LPSPI4, muxSDI: muxSelect{ // D12 (PB1 [B0_01]) mux: nxp.IOMUXC_LPSPI4_SDI_SELECT_INPUT_DAISY_GPIO_B0_01_ALT3, @@ -284,8 +283,7 @@ var ( sel: &nxp.IOMUXC.LPSPI4_PCS0_SELECT_INPUT, }, } - SPI2 = &_SPI2 - _SPI2 = SPI{ + SPI2 = &SPI{ Bus: nxp.LPSPI3, muxSDI: muxSelect{ // D1 (PA2 [AD_B0_02]) mux: nxp.IOMUXC_LPSPI3_SDI_SELECT_INPUT_DAISY_GPIO_AD_B0_02_ALT7, @@ -304,8 +302,7 @@ var ( sel: &nxp.IOMUXC.LPSPI3_PCS0_SELECT_INPUT, }, } - SPI3 = &_SPI3 - _SPI3 = SPI{ + SPI3 = &SPI{ Bus: nxp.LPSPI1, muxSDI: muxSelect{ // D34 (PC15 [SD_B0_03]) mux: nxp.IOMUXC_LPSPI1_SDI_SELECT_INPUT_DAISY_GPIO_SD_B0_03_ALT4, diff --git a/src/machine/board_thingplus_rp2040.go b/src/machine/board_thingplus_rp2040.go index ac40ee4a4b..48292d261e 100644 --- a/src/machine/board_thingplus_rp2040.go +++ b/src/machine/board_thingplus_rp2040.go @@ -2,11 +2,6 @@ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Onboard crystal oscillator frequency, in MHz. const xoscFreq = 12 // MHz @@ -51,7 +46,11 @@ const ( A3 = GPIO29 ) -const LED = GPIO25 +// Onboard LEDs +const ( + LED = GPIO25 + WS2812 = GPIO8 +) // I2C Pins. const ( @@ -90,21 +89,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} - // USB identifiers const ( usb_STRING_PRODUCT = "Thing Plus RP2040" diff --git a/src/machine/board_thumby.go b/src/machine/board_thumby.go index dc1f82beb3..f89a8b7059 100644 --- a/src/machine/board_thumby.go +++ b/src/machine/board_thumby.go @@ -5,11 +5,6 @@ // https://thumby.us/ package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( THUMBY_SCK_PIN = I2C1_SDA_PIN THUMBY_SDA_PIN = I2C1_SCL_PIN @@ -78,17 +73,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the Thumby -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} diff --git a/src/machine/board_tiny2350.go b/src/machine/board_tiny2350.go new file mode 100644 index 0000000000..f04fa061b6 --- /dev/null +++ b/src/machine/board_tiny2350.go @@ -0,0 +1,82 @@ +//go:build tiny2350 + +package machine + +// GPIO pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP18 Pin = GPIO18 + GP19 Pin = GPIO19 + GP20 Pin = GPIO20 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP29 Pin = GPIO29 + + // Onboard LED + LED_RED Pin = GPIO18 + LED_GREEN Pin = GPIO19 + LED_BLUE Pin = GPIO20 + LED = LED_RED + + // Onboard crystal oscillator frequency, in MHz. + xoscFreq = 12 // MHz +) + +// I2C Default pins on Tiny2350. +const ( + I2C0_SDA_PIN = GP12 + I2C0_SCL_PIN = GP13 + + I2C1_SDA_PIN = GP2 + I2C1_SCL_PIN = GP3 +) + +// SPI default pins +const ( + // Default Serial Clock Bus 0 for SPI communications + SPI0_SCK_PIN = GPIO6 + // Default Serial Out Bus 0 for SPI communications + SPI0_SDO_PIN = GPIO7 // Tx + // Default Serial In Bus 0 for SPI communications + SPI0_SDI_PIN = GPIO4 // Rx + + // Default Serial Clock Bus 1 for SPI communications + SPI1_SCK_PIN = GPIO26 + // Default Serial Out Bus 1 for SPI communications + SPI1_SDO_PIN = GPIO27 // Tx + // Default Serial In Bus 1 for SPI communications + SPI1_SDI_PIN = GPIO28 // Rx +) + +// UART pins +const ( + UART0_TX_PIN = GPIO0 + UART0_RX_PIN = GPIO1 + UART1_TX_PIN = GPIO4 + UART1_RX_PIN = GPIO5 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +var DefaultUART = UART0 + +// USB identifiers +const ( + usb_STRING_PRODUCT = "Tiny2350" + usb_STRING_MANUFACTURER = "Pimoroni" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000F +) diff --git a/src/machine/board_trinket.go b/src/machine/board_trinket.go index 2ce419a4ac..089eadbf09 100644 --- a/src/machine/board_trinket.go +++ b/src/machine/board_trinket.go @@ -67,7 +67,8 @@ var ( // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on Trinket M0. I2S_WS_PIN = NoPin // TODO: figure out what this is on Trinket M0. ) diff --git a/src/machine/board_tufty2040.go b/src/machine/board_tufty2040.go index d82cb99515..57d244f28b 100644 --- a/src/machine/board_tufty2040.go +++ b/src/machine/board_tufty2040.go @@ -7,11 +7,6 @@ // - Tufty 2040 schematic: https://cdn.shopify.com/s/files/1/0174/1800/files/tufty_schematic.pdf?v=1655385675 package machine -import ( - "device/rp" - "runtime/interrupt" -) - const ( LED Pin = GPIO25 @@ -87,17 +82,4 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 - -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} diff --git a/src/machine/board_waveshare-rp2040-zero.go b/src/machine/board_waveshare-rp2040-zero.go index 65e7a9481c..00ddc53a51 100644 --- a/src/machine/board_waveshare-rp2040-zero.go +++ b/src/machine/board_waveshare-rp2040-zero.go @@ -7,11 +7,6 @@ // - https://www.waveshare.com/wiki/RP2040-Zero package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Digital Pins const ( D0 Pin = GPIO0 @@ -57,6 +52,7 @@ const ( // Onboard LEDs const ( NEOPIXEL = GPIO16 + WS2812 = GPIO16 ) // I2C pins @@ -94,27 +90,8 @@ const ( UART1_RX_PIN = GPIO9 ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } - UART1 = &_UART1 - _UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART1, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) - UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) -} - // USB CDC identifiers const ( usb_STRING_PRODUCT = "RP2040-Zero" diff --git a/src/machine/board_waveshare_rp2040_tiny.go b/src/machine/board_waveshare_rp2040_tiny.go new file mode 100644 index 0000000000..a3ef354041 --- /dev/null +++ b/src/machine/board_waveshare_rp2040_tiny.go @@ -0,0 +1,121 @@ +//go:build waveshare_rp2040_tiny + +// This file contains the pin mappings for the Waveshare RP2040-Tiny boards. +// +// Waveshare RP2040-Tiny is a microcontroller using the Raspberry Pi RP2040 chip. +// +// - https://www.waveshare.com/wiki/RP2040-Tiny +package machine + +// Digital Pins +const ( + GP0 Pin = GPIO0 + GP1 Pin = GPIO1 + GP2 Pin = GPIO2 + GP3 Pin = GPIO3 + GP4 Pin = GPIO4 + GP5 Pin = GPIO5 + GP6 Pin = GPIO6 + GP7 Pin = GPIO7 + GP8 Pin = GPIO8 + GP9 Pin = GPIO9 + GP10 Pin = GPIO10 + GP11 Pin = GPIO11 + GP12 Pin = GPIO12 + GP13 Pin = GPIO13 + GP14 Pin = GPIO14 + GP15 Pin = GPIO15 + GP16 Pin = GPIO16 + GP17 Pin = NoPin + GP18 Pin = NoPin + GP19 Pin = NoPin + GP20 Pin = NoPin + GP21 Pin = NoPin + GP22 Pin = NoPin + GP23 Pin = NoPin + GP24 Pin = GPIO24 + GP25 Pin = GPIO25 + GP26 Pin = GPIO26 + GP27 Pin = GPIO27 + GP28 Pin = GPIO28 + GP29 Pin = GPIO29 +) + +// Analog pins +const ( + A0 Pin = GP26 + A1 Pin = GP27 + A2 Pin = GP28 + A3 Pin = GP29 +) + +// Onboard LEDs +const ( + LED = GP16 + WS2812 = GP16 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = GP0 + I2C0_SCL_PIN Pin = GP1 + I2C1_SDA_PIN Pin = GP2 + I2C1_SCL_PIN Pin = GP3 + + // default I2C0 + I2C_SDA_PIN Pin = I2C0_SDA_PIN + I2C_SCL_PIN Pin = I2C0_SCL_PIN +) + +// SPI pins +const ( + SPI0_RX_PIN Pin = GP0 + SPI0_CSN_PIN Pin = GP1 + SPI0_SCK_PIN Pin = GP2 + SPI0_TX_PIN Pin = GP3 + SPI0_SDO_PIN Pin = SPI0_TX_PIN + SPI0_SDI_PIN Pin = SPI0_RX_PIN + + SPI1_RX_PIN Pin = GP8 + SPI1_CSN_PIN Pin = GP9 + SPI1_SCK_PIN Pin = GP10 + SPI1_TX_PIN Pin = GP11 + SPI1_SDO_PIN Pin = SPI1_TX_PIN + SPI1_SDI_PIN Pin = SPI1_RX_PIN + + // default SPI0 + SPI_RX_PIN Pin = SPI0_RX_PIN + SPI_CSN_PIN Pin = SPI0_CSN_PIN + SPI_SCK_PIN Pin = SPI0_SCK_PIN + SPI_TX_PIN Pin = SPI0_TX_PIN + SPI_SDO_PIN Pin = SPI0_TX_PIN + SPI_SDI_PIN Pin = SPI0_RX_PIN +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// UART pins +const ( + UART0_TX_PIN = GP0 + UART0_RX_PIN = GP1 + UART1_TX_PIN = GP8 + UART1_RX_PIN = GP9 + + // default UART0 + UART_TX_PIN = UART0_TX_PIN + UART_RX_PIN = UART0_RX_PIN +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "RP2040-Tiny" + usb_STRING_MANUFACTURER = "Waveshare" +) + +var ( + usb_VID uint16 = 0x2e8a + usb_PID uint16 = 0x0003 +) diff --git a/src/machine/board_wioterminal.go b/src/machine/board_wioterminal.go index 99a04d6ecc..6997120b93 100644 --- a/src/machine/board_wioterminal.go +++ b/src/machine/board_wioterminal.go @@ -376,6 +376,14 @@ var ( I2C1 = sercomI2CM3 ) +// I2S pins +const ( + I2S_SCK_PIN = BCM18 + I2S_SDO_PIN = BCM21 + I2S_SDI_PIN = BCM20 + I2S_WS_PIN = BCM19 +) + // SPI pins const ( SPI0_SCK_PIN = SCK // SCK: SERCOM5/PAD[1] diff --git a/src/machine/board_xiao-rp2040.go b/src/machine/board_xiao-rp2040.go index 272fcc599d..b010314557 100644 --- a/src/machine/board_xiao-rp2040.go +++ b/src/machine/board_xiao-rp2040.go @@ -7,11 +7,6 @@ // - https://wiki.seeedstudio.com/XIAO-RP2040/ package machine -import ( - "device/rp" - "runtime/interrupt" -) - // Digital Pins const ( D0 Pin = GPIO26 @@ -81,21 +76,8 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) - var DefaultUART = UART0 -func init() { - UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) -} - // USB CDC identifiers const ( usb_STRING_PRODUCT = "XIAO RP2040" diff --git a/src/machine/board_xiao.go b/src/machine/board_xiao.go index f0ecf068e3..5bbb34d686 100644 --- a/src/machine/board_xiao.go +++ b/src/machine/board_xiao.go @@ -82,7 +82,8 @@ var SPI0 = sercomSPIM0 // I2S pins const ( I2S_SCK_PIN = PA10 - I2S_SD_PIN = PA08 + I2S_SDO_PIN = PA08 + I2S_SDI_PIN = NoPin // TODO: figure out what this is on Xiao I2S_WS_PIN = NoPin // TODO: figure out what this is on Xiao ) diff --git a/src/machine/flash.go b/src/machine/flash.go index 716fc4a53b..c89c091b91 100644 --- a/src/machine/flash.go +++ b/src/machine/flash.go @@ -1,4 +1,4 @@ -//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 +//go:build nrf || nrf51 || nrf52 || nrf528xx || stm32f4 || stm32l4 || stm32wlx || atsamd21 || atsamd51 || atsame5x || rp2040 || rp2350 package machine @@ -64,3 +64,14 @@ type BlockDevice interface { // EraseBlockSize to map addresses to blocks. EraseBlocks(start, len int64) error } + +// pad data if needed so it is long enough for correct byte alignment on writes. +func flashPad(p []byte, writeBlockSize int) []byte { + overflow := len(p) % writeBlockSize + if overflow != 0 { + for i := 0; i < writeBlockSize-overflow; i++ { + p = append(p, 0xff) + } + } + return p +} diff --git a/src/machine/i2c.go b/src/machine/i2c.go index 5c4d1a5c0b..3b1b4dd4da 100644 --- a/src/machine/i2c.go +++ b/src/machine/i2c.go @@ -1,4 +1,4 @@ -//go:build atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || mimxrt1062 || (esp32c3 && !m5stamp_c3) +//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 package machine diff --git a/src/machine/i2s.go b/src/machine/i2s.go index 8f5e309532..13dc80f61a 100644 --- a/src/machine/i2s.go +++ b/src/machine/i2s.go @@ -1,4 +1,4 @@ -//go:build sam +//go:build sam && atsamd21 // This is the definition for I2S bus functions. // Actual implementations if available for any given hardware @@ -9,6 +9,22 @@ package machine +import "errors" + +// If you are getting a compile error on this line please check to see you've +// correctly implemented the methods on the I2S type. They must match +// the interface method signatures type to type perfectly. +// If not implementing the I2S type please remove your target from the build tags +// at the top of this file. +var _ interface { + SetSampleFrequency(freq uint32) error + ReadMono(b []uint16) (int, error) + ReadStereo(b []uint32) (int, error) + WriteMono(b []uint16) (int, error) + WriteStereo(b []uint32) (int, error) + Enable(enabled bool) +} = (*I2S)(nil) + type I2SMode uint8 type I2SStandard uint8 type I2SClockSource uint8 @@ -18,6 +34,7 @@ const ( I2SModeSource I2SMode = iota I2SModeReceiver I2SModePDM + I2SModeSourceReceiver ) const ( @@ -39,11 +56,20 @@ const ( I2SDataFormat32bit = 32 ) +var ( + ErrInvalidSampleFrequency = errors.New("i2s: invalid sample frequency") +) + // All fields are optional and may not be required or used on a particular platform. type I2SConfig struct { - SCK Pin - WS Pin - SD Pin + // clock + SCK Pin + // word select + WS Pin + // data out + SDO Pin + // data in + SDI Pin Mode I2SMode Standard I2SStandard ClockSource I2SClockSource diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index 46dee5ef1e..7a59e5e092 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -257,7 +257,7 @@ type SPI struct { } // Configure is intended to setup the SPI interface. -func (s SPI) Configure(config SPIConfig) error { +func (s *SPI) Configure(config SPIConfig) error { // This is only here to help catch a bug with the configuration // where a machine missed a value. @@ -330,7 +330,7 @@ func (s SPI) Configure(config SPIConfig) error { } // Transfer writes the byte into the register and returns the read content -func (s SPI) Transfer(b byte) (byte, error) { +func (s *SPI) Transfer(b byte) (byte, error) { s.spdr.Set(uint8(b)) for !s.spsr.HasBits(s.spsrSPIF) { diff --git a/src/machine/machine_atmega1280.go b/src/machine/machine_atmega1280.go index 49564a3ce3..ad33dcf8c0 100644 --- a/src/machine/machine_atmega1280.go +++ b/src/machine/machine_atmega1280.go @@ -180,7 +180,7 @@ func (pwm PWM) Configure(config PWMConfig) error { // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) @@ -718,7 +718,7 @@ func (pwm PWM) Set(channel uint8, value uint32) { } } // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() case 1: mask := interrupt.Disable() @@ -927,7 +927,7 @@ func (pwm PWM) Set(channel uint8, value uint32) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega1284p.go b/src/machine/machine_atmega1284p.go index 511f9e8730..db8fd65a2f 100644 --- a/src/machine/machine_atmega1284p.go +++ b/src/machine/machine_atmega1284p.go @@ -71,7 +71,7 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spsr: avr.SPSR, spdr: avr.SPDR, diff --git a/src/machine/machine_atmega2560.go b/src/machine/machine_atmega2560.go index 339c35ae77..ede862a931 100644 --- a/src/machine/machine_atmega2560.go +++ b/src/machine/machine_atmega2560.go @@ -131,7 +131,7 @@ func (p Pin) getPortMask() (*volatile.Register8, uint8) { } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega328.go b/src/machine/machine_atmega328.go index e4b2bb06f9..c354ccb888 100644 --- a/src/machine/machine_atmega328.go +++ b/src/machine/machine_atmega328.go @@ -56,7 +56,7 @@ func (pwm PWM) Configure(config PWMConfig) error { // Set the PWM mode to fast PWM (mode = 3). avr.TCCR0A.Set(avr.TCCR0A_WGM00 | avr.TCCR0A_WGM01) // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() } else { avr.TCCR2B.Set(prescaler) @@ -385,7 +385,7 @@ func (pwm PWM) Set(channel uint8, value uint32) { } } // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed + // we must adjust internal settings of monotonic timer when PWM:0 settings changed adjustMonotonicTimer() case 1: mask := interrupt.Disable() diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 1995403aab..5bacfb8f20 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -25,7 +25,7 @@ var I2C0 = &I2C{ } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, diff --git a/src/machine/machine_atmega328pb.go b/src/machine/machine_atmega328pb.go index 091fb6337f..935c581d54 100644 --- a/src/machine/machine_atmega328pb.go +++ b/src/machine/machine_atmega328pb.go @@ -60,7 +60,7 @@ var I2C1 = &I2C{ } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR0, spdr: avr.SPDR0, spsr: avr.SPSR0, @@ -82,7 +82,7 @@ var SPI0 = SPI{ cs: PB2, } -var SPI1 = SPI{ +var SPI1 = &SPI{ spcr: avr.SPCR1, spdr: avr.SPDR1, spsr: avr.SPSR1, diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 34e2f11edc..e0c5f2cc29 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -7,11 +7,10 @@ package machine import ( - "bytes" "device/arm" "device/sam" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "unsafe" ) @@ -926,23 +925,26 @@ func (i2c *I2C) readByte() byte { // I2S type I2S struct { - Bus *sam.I2S_Type + Bus *sam.I2S_Type + Frequency uint32 + DataFormat I2SDataFormat } var I2S0 = I2S{Bus: sam.I2S} // Configure is used to configure the I2S interface. You must call this // before you can use the I2S bus. -func (i2s I2S) Configure(config I2SConfig) { +func (i2s *I2S) Configure(config I2SConfig) error { // handle defaults if config.SCK == 0 { config.SCK = I2S_SCK_PIN config.WS = I2S_WS_PIN - config.SD = I2S_SD_PIN + config.SDO = I2S_SDO_PIN + config.SDI = I2S_SDI_PIN } if config.AudioFrequency == 0 { - config.AudioFrequency = 48000 + config.AudioFrequency = 44100 } if config.DataFormat == I2SDataFormatDefault { @@ -952,39 +954,17 @@ func (i2s I2S) Configure(config I2SConfig) { config.DataFormat = I2SDataFormat32bit } } + i2s.DataFormat = config.DataFormat // Turn on clock for I2S sam.PM.APBCMASK.SetBits(sam.PM_APBCMASK_I2S_) - // setting clock rate for sample. - division_factor := CPUFrequency() / (config.AudioFrequency * uint32(config.DataFormat)) - - // Switch Generic Clock Generator 3 to DFLL48M. - sam.GCLK.GENDIV.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) | - (division_factor << sam.GCLK_GENDIV_DIV_Pos)) - waitForSync() - - sam.GCLK.GENCTRL.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) | - (sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) | - sam.GCLK_GENCTRL_IDC | - sam.GCLK_GENCTRL_GENEN) - waitForSync() - - // Use Generic Clock Generator 3 as source for I2S. - sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) | - (sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) | - sam.GCLK_CLKCTRL_CLKEN) - waitForSync() - - // reset the device - i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SWRST) - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SWRST) { + if err := i2s.SetSampleFrequency(config.AudioFrequency); err != nil { + return err } // disable device before continuing - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { - } - i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE) + i2s.Enable(false) // setup clock if config.ClockSource == I2SClockSourceInternal { @@ -1067,19 +1047,25 @@ func (i2s I2S) Configure(config I2SConfig) { } // set serializer mode. - if config.Mode == I2SModePDM { + switch config.Mode { + case I2SModePDM: i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_PDM2) - } else { + case I2SModeSource: + i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_TX) + case I2SModeReceiver: i2s.Bus.SERCTRL1.SetBits(sam.I2S_SERCTRL_SERMODE_RX) } - // configure data pin - config.SD.Configure(PinConfig{Mode: PinCom}) + // configure data pins + if config.SDO != NoPin { + config.SDO.Configure(PinConfig{Mode: PinCom}) + } + if config.SDI != NoPin { + config.SDI.Configure(PinConfig{Mode: PinCom}) + } // re-enable - i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_ENABLE) - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { - } + i2s.Enable(true) // enable i2s clock i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_CKEN0) @@ -1090,11 +1076,23 @@ func (i2s I2S) Configure(config I2SConfig) { i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SEREN1) for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SEREN1) { } + + return nil } -// Read data from the I2S bus into the provided slice. +// Read mono data from the I2S bus into the provided slice. // The I2S bus must already have been configured correctly. -func (i2s I2S) Read(p []uint32) (n int, err error) { +func (i2s *I2S) ReadMono(p []uint16) (n int, err error) { + return i2sRead(i2s, p) +} + +// Read stereo data from the I2S bus into the provided slice. +// The I2S bus must already have been configured correctly. +func (i2s *I2S) ReadStereo(p []uint32) (n int, err error) { + return i2sRead(i2s, p) +} + +func i2sRead[T uint16 | uint32](i2s *I2S, p []T) (int, error) { i := 0 for i = 0; i < len(p); i++ { // Wait until ready @@ -1105,7 +1103,7 @@ func (i2s I2S) Read(p []uint32) (n int, err error) { } // read data - p[i] = i2s.Bus.DATA1.Get() + p[i] = T(i2s.Bus.DATA1.Get()) // indicate read complete i2s.Bus.INTFLAG.Set(sam.I2S_INTFLAG_RXRDY1) @@ -1114,9 +1112,19 @@ func (i2s I2S) Read(p []uint32) (n int, err error) { return i, nil } -// Write data to the I2S bus from the provided slice. +// Write mono data to the I2S bus from the provided slice. // The I2S bus must already have been configured correctly. -func (i2s I2S) Write(p []uint32) (n int, err error) { +func (i2s *I2S) WriteMono(p []uint16) (n int, err error) { + return i2sWrite(i2s, p) +} + +// Write stereo data to the I2S bus from the provided slice. +// The I2S bus must already have been configured correctly. +func (i2s *I2S) WriteStereo(p []uint32) (n int, err error) { + return i2sWrite(i2s, p) +} + +func i2sWrite[T uint16 | uint32](i2s *I2S, p []T) (int, error) { i := 0 for i = 0; i < len(p); i++ { // Wait until ready @@ -1127,7 +1135,7 @@ func (i2s I2S) Write(p []uint32) (n int, err error) { } // write data - i2s.Bus.DATA1.Set(p[i]) + i2s.Bus.DATA1.Set(uint32(p[i])) // indicate write complete i2s.Bus.INTFLAG.Set(sam.I2S_INTFLAG_TXRDY1) @@ -1136,18 +1144,64 @@ func (i2s I2S) Write(p []uint32) (n int, err error) { return i, nil } -// Close the I2S bus. -func (i2s I2S) Close() error { - // Sync wait - for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { +// SetSampleFrequency is used to set the sample frequency for the I2S bus. +func (i2s *I2S) SetSampleFrequency(freq uint32) error { + if freq == 0 { + return ErrInvalidSampleFrequency } - // disable I2S - i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE) + if i2s.Frequency == freq { + return nil + } + + i2s.Frequency = freq + + // setting clock rate for sample. + division_factor := CPUFrequency() / (i2s.Frequency * uint32(i2s.DataFormat)) + + // Switch Generic Clock Generator 3 to DFLL48M. + sam.GCLK.GENDIV.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENDIV_ID_Pos) | + (division_factor << sam.GCLK_GENDIV_DIV_Pos)) + waitForSync() + + sam.GCLK.GENCTRL.Set((sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_GENCTRL_ID_Pos) | + (sam.GCLK_GENCTRL_SRC_DFLL48M << sam.GCLK_GENCTRL_SRC_Pos) | + sam.GCLK_GENCTRL_IDC | + sam.GCLK_GENCTRL_GENEN) + waitForSync() + + // Use Generic Clock Generator 3 as source for I2S. + sam.GCLK.CLKCTRL.Set((sam.GCLK_CLKCTRL_ID_I2S_0 << sam.GCLK_CLKCTRL_ID_Pos) | + (sam.GCLK_CLKCTRL_GEN_GCLK3 << sam.GCLK_CLKCTRL_GEN_Pos) | + sam.GCLK_CLKCTRL_CLKEN) + waitForSync() + + // reset the device + i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_SWRST) + for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_SWRST) { + } return nil } +// Enabled is used to enable or disable the I2S bus. +func (i2s *I2S) Enable(enabled bool) { + if enabled { + i2s.Bus.CTRLA.SetBits(sam.I2S_CTRLA_ENABLE) + for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { + } + + return + } + + // disable + for i2s.Bus.SYNCBUSY.HasBits(sam.I2S_SYNCBUSY_ENABLE) { + } + i2s.Bus.CTRLA.ClearBits(sam.I2S_CTRLA_ENABLE) + + return +} + func waitForSync() { for sam.GCLK.STATUS.HasBits(sam.GCLK_STATUS_SYNCBUSY) { } @@ -1170,7 +1224,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -1292,7 +1346,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) @@ -1304,7 +1358,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.DATA.Get()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // @@ -1321,7 +1375,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. @@ -1342,7 +1396,7 @@ func (spi SPI) Tx(w, r []byte) error { return nil } -func (spi SPI) tx(tx []byte) { +func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } @@ -1357,7 +1411,7 @@ func (spi SPI) tx(tx []byte) { } } -func (spi SPI) rx(rx []byte) { +func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } @@ -1373,7 +1427,7 @@ func (spi SPI) rx(rx []byte) { rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } -func (spi SPI) txrx(tx, rx []byte) { +func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPI_INTFLAG_DRE) { } @@ -1459,7 +1513,7 @@ func (tcc *TCC) Configure(config PWMConfig) error { for tcc.timer().SYNCBUSY.Get() != 0 { } - // Return any error that might have occured in the tcc.setPeriod call. + // Return any error that might have occurred in the tcc.setPeriod call. return err } @@ -1606,7 +1660,7 @@ func (tcc *TCC) Counter() uint32 { return tcc.timer().COUNT.Get() } -// Some constans to make pinTimerMapping below easier to read. +// Some constants to make pinTimerMapping below easier to read. const ( pinTCC0 = 1 pinTCC1 = 2 @@ -1657,7 +1711,7 @@ var pinTimerMapping = [...]uint8{ PB30 / 2: pinTCC0Ch0 | pinTCC1Ch2<<4, } -// findPinPadMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel +// findPinTimerMapping returns the pin mode (PinTCC or PinTCCAlt) and the channel // number for a given timer and pin. A zero PinMode is returned if no mapping // could be found. func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { @@ -1759,7 +1813,7 @@ func (tcc *TCC) Set(channel uint8, value uint32) { } } -// EnterBootloader should perform a system reset in preperation +// EnterBootloader should perform a system reset in preparation // to switch to the bootloader to flash new firmware. func EnterBootloader() { arm.DisableInterrupts() @@ -1862,7 +1916,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { f.ensureInitComplete() address := FlashDataStart() + uintptr(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) waitWhileFlashBusy() @@ -1937,17 +1991,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - func (f flashBlockDevice) ensureInitComplete() { if f.initComplete { return diff --git a/src/machine/machine_atsamd21e18.go b/src/machine/machine_atsamd21e18.go index 85d7ff2552..85d6853bc7 100644 --- a/src/machine/machine_atsamd21e18.go +++ b/src/machine/machine_atsamd21e18.go @@ -22,10 +22,10 @@ var ( sercomI2CM2 = &I2C{Bus: sam.SERCOM2_I2CM, SERCOM: 2} sercomI2CM3 = &I2C{Bus: sam.SERCOM3_I2CM, SERCOM: 3} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} ) func init() { diff --git a/src/machine/machine_atsamd21g18.go b/src/machine/machine_atsamd21g18.go index 9b78ad3367..9e845cf3bc 100644 --- a/src/machine/machine_atsamd21g18.go +++ b/src/machine/machine_atsamd21g18.go @@ -26,12 +26,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPI, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPI, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPI, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPI, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPI, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPI, SERCOM: 5} ) func init() { diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 0d786dba72..d169eab995 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -7,11 +7,10 @@ package machine import ( - "bytes" "device/arm" "device/sam" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "unsafe" ) @@ -1432,7 +1431,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -1575,7 +1574,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // write data spi.Bus.DATA.Set(uint32(w)) @@ -1587,7 +1586,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.DATA.Get()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // @@ -1604,7 +1603,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { switch { case w == nil: // read only, so write zero and read a result. @@ -1625,7 +1624,7 @@ func (spi SPI) Tx(w, r []byte) error { return nil } -func (spi SPI) tx(tx []byte) { +func (spi *SPI) tx(tx []byte) { for i := 0; i < len(tx); i++ { for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } @@ -1640,7 +1639,7 @@ func (spi SPI) tx(tx []byte) { } } -func (spi SPI) rx(rx []byte) { +func (spi *SPI) rx(rx []byte) { spi.Bus.DATA.Set(0) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } @@ -1656,7 +1655,7 @@ func (spi SPI) rx(rx []byte) { rx[len(rx)-1] = byte(spi.Bus.DATA.Get()) } -func (spi SPI) txrx(tx, rx []byte) { +func (spi *SPI) txrx(tx, rx []byte) { spi.Bus.DATA.Set(uint32(tx[0])) for !spi.Bus.INTFLAG.HasBits(sam.SERCOM_SPIM_INTFLAG_DRE) { } @@ -1720,7 +1719,7 @@ func (tcc *TCC) Configure(config PWMConfig) error { for tcc.timer().SYNCBUSY.Get() != 0 { } - // Return any error that might have occured in the tcc.setPeriod call. + // Return any error that might have occurred in the tcc.setPeriod call. return err } @@ -1928,7 +1927,7 @@ var pinTimerMapping = [...]struct{ F, G uint8 }{ PB02 / 2: {pinTCC2_2, 0}, } -// findPinPadMapping returns the pin mode (PinTCCF or PinTCCG) and the channel +// findPinTimerMapping returns the pin mode (PinTCCF or PinTCCG) and the channel // number for a given timer and pin. A zero PinMode is returned if no mapping // could be found. func findPinTimerMapping(timer uint8, pin Pin) (PinMode, uint8) { @@ -2174,7 +2173,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { } address := FlashDataStart() + uintptr(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) settings := disableFlashCache() defer restoreFlashCache(settings) @@ -2263,17 +2262,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - func disableFlashCache() uint16 { settings := sam.NVMCTRL.CTRLA.Get() diff --git a/src/machine/machine_atsamd51g19.go b/src/machine/machine_atsamd51g19.go index ade031bb04..f223f6ebc8 100644 --- a/src/machine/machine_atsamd51g19.go +++ b/src/machine/machine_atsamd51g19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51j19.go b/src/machine/machine_atsamd51j19.go index b41c25c145..640e1ef263 100644 --- a/src/machine/machine_atsamd51j19.go +++ b/src/machine/machine_atsamd51j19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51j20.go b/src/machine/machine_atsamd51j20.go index 2c5391afe7..d582278760 100644 --- a/src/machine/machine_atsamd51j20.go +++ b/src/machine/machine_atsamd51j20.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51p19.go b/src/machine/machine_atsamd51p19.go index 70050c2d6f..bcd66a93a7 100644 --- a/src/machine/machine_atsamd51p19.go +++ b/src/machine/machine_atsamd51p19.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsamd51p20.go b/src/machine/machine_atsamd51p20.go index 9f52ba257f..40e435fa1b 100644 --- a/src/machine/machine_atsamd51p20.go +++ b/src/machine/machine_atsamd51p20.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsame51j19.go b/src/machine/machine_atsame51j19.go index 8f8294a266..29ea411784 100644 --- a/src/machine/machine_atsame51j19.go +++ b/src/machine/machine_atsame51j19.go @@ -18,12 +18,12 @@ var ( sercomI2CM4 = &I2C{Bus: sam.SERCOM4_I2CM, SERCOM: 4} sercomI2CM5 = &I2C{Bus: sam.SERCOM5_I2CM, SERCOM: 5} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsame54p20.go b/src/machine/machine_atsame54p20.go index 922ee31474..d7cc31f62d 100644 --- a/src/machine/machine_atsame54p20.go +++ b/src/machine/machine_atsame54p20.go @@ -20,14 +20,14 @@ var ( sercomI2CM6 = &I2C{Bus: sam.SERCOM6_I2CM, SERCOM: 6} sercomI2CM7 = &I2C{Bus: sam.SERCOM7_I2CM, SERCOM: 7} - sercomSPIM0 = SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} - sercomSPIM1 = SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} - sercomSPIM2 = SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} - sercomSPIM3 = SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} - sercomSPIM4 = SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} - sercomSPIM5 = SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} - sercomSPIM6 = SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} - sercomSPIM7 = SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} + sercomSPIM0 = &SPI{Bus: sam.SERCOM0_SPIM, SERCOM: 0} + sercomSPIM1 = &SPI{Bus: sam.SERCOM1_SPIM, SERCOM: 1} + sercomSPIM2 = &SPI{Bus: sam.SERCOM2_SPIM, SERCOM: 2} + sercomSPIM3 = &SPI{Bus: sam.SERCOM3_SPIM, SERCOM: 3} + sercomSPIM4 = &SPI{Bus: sam.SERCOM4_SPIM, SERCOM: 4} + sercomSPIM5 = &SPI{Bus: sam.SERCOM5_SPIM, SERCOM: 5} + sercomSPIM6 = &SPI{Bus: sam.SERCOM6_SPIM, SERCOM: 6} + sercomSPIM7 = &SPI{Bus: sam.SERCOM7_SPIM, SERCOM: 7} ) // setSERCOMClockGenerator sets the GCLK for sercom diff --git a/src/machine/machine_atsame5x_can.go b/src/machine/machine_atsame5x_can.go index b404bdaa68..bf38cd8988 100644 --- a/src/machine/machine_atsame5x_can.go +++ b/src/machine/machine_atsame5x_can.go @@ -155,7 +155,7 @@ func (can *CAN) Configure(config CANConfig) error { } // Callbacks to be called for CAN.SetInterrupt(). Wre're using the magic -// constant 2 and 32 here beacuse th SAM E51/E54 has 2 CAN and 32 interrupt +// constant 2 and 32 here because the SAM E51/E54 has 2 CAN and 32 interrupt // sources. var ( canInstances [2]*CAN @@ -221,6 +221,11 @@ func (can *CAN) TxFifoIsFull() bool { return (can.Bus.TXFQS.Get() & sam.CAN_TXFQS_TFQF_Msk) == sam.CAN_TXFQS_TFQF_Msk } +// TxFifoFreeLevel returns how many messages can still be set in the TxFifo. +func (can *CAN) TxFifoFreeLevel() int { + return int(can.Bus.GetTXFQS_TFFL()) +} + // TxRaw sends a CAN Frame according to CANTxBufferElement. func (can *CAN) TxRaw(e *CANTxBufferElement) { putIndex := (can.Bus.TXFQS.Get() & sam.CAN_TXFQS_TFQPI_Msk) >> sam.CAN_TXFQS_TFQPI_Pos diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index 0bc17b4416..237a1292c2 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -334,8 +334,8 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. - SPI2 = SPI{esp.SPI2} - SPI3 = SPI{esp.SPI3} + SPI2 = &SPI{esp.SPI2} + SPI3 = &SPI{esp.SPI3} ) // SPIConfig configures a SPI peripheral on the ESP32. Make sure to set at least @@ -354,7 +354,7 @@ type SPIConfig struct { } // Configure and make the SPI peripheral ready to use. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { if config.Frequency == 0 { config.Frequency = 4e6 // default to 4MHz } @@ -445,7 +445,7 @@ func (spi SPI) Configure(config SPIConfig) error { // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.MISO_DLEN.Set(7 << esp.SPI_MISO_DLEN_USR_MISO_DBITLEN_Pos) spi.Bus.MOSI_DLEN.Set(7 << esp.SPI_MOSI_DLEN_USR_MOSI_DBITLEN_Pos) @@ -460,11 +460,11 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.W0.Get()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) diff --git a/src/machine/machine_esp32_i2c.go b/src/machine/machine_esp32_i2c.go new file mode 100644 index 0000000000..746e722dc2 --- /dev/null +++ b/src/machine/machine_esp32_i2c.go @@ -0,0 +1,412 @@ +//go:build esp32 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +var ( + I2C0 = &I2C{Bus: esp.I2C0, funcSCL: 29, funcSDA: 30} + I2C1 = &I2C{Bus: esp.I2C1, funcSCL: 95, funcSDA: 96} +) + +type I2C struct { + Bus *esp.I2C_Type + funcSCL, funcSDA uint32 + config I2CConfig +} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 // in Hz + SCL Pin + SDA Pin +} + +const ( + i2cClkSourceFrequency = uint32(80 * MHz) +) + +func (i2c *I2C) Configure(config I2CConfig) error { + if config.Frequency == 0 { + config.Frequency = 400 * KHz + } + if config.SCL == 0 { + config.SCL = SCL_PIN + } + if config.SDA == 0 { + config.SDA = SDA_PIN + } + i2c.config = config + + i2c.initAll() + return nil +} + +func (i2c *I2C) initAll() { + i2c.initClock() + i2c.initNoiseFilter() + i2c.initPins() + i2c.initFrequency() + i2c.startMaster() +} + +//go:inline +func (i2c *I2C) initClock() { + // reset I2C clock + if i2c.Bus == esp.I2C0 { + esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(1) + esp.DPORT.SetPERIP_CLK_EN_I2C0_EXT0_CLK_EN(1) + esp.DPORT.SetPERIP_RST_EN_I2C0_EXT0_RST(0) + } else { + esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(1) + esp.DPORT.SetPERIP_CLK_EN_I2C_EXT1_CLK_EN(1) + esp.DPORT.SetPERIP_RST_EN_I2C_EXT1_RST(0) + } + // disable interrupts + i2c.Bus.INT_ENA.Set(0) + i2c.Bus.INT_CLR.Set(0x3fff) + + i2c.Bus.SetCTR_CLK_EN(1) +} + +//go:inline +func (i2c *I2C) initNoiseFilter() { + i2c.Bus.SCL_FILTER_CFG.Set(0xF) + i2c.Bus.SDA_FILTER_CFG.Set(0xF) +} + +//go:inline +func (i2c *I2C) initPins() { + var muxConfig uint32 + const function = 2 // function 2 is just GPIO + + // SDA + muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO0_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos + i2c.config.SDA.mux().Set(muxConfig) + i2c.config.SDA.outFunc().Set(i2c.funcSDA) + inFunc(i2c.funcSDA).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SDA)) + i2c.config.SDA.Set(true) + // Configure the pad with the given IO mux configuration. + i2c.config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SDA)) + i2c.Bus.SetCTR_SDA_FORCE_OUT(1) + + // SCL + muxConfig = function << esp.IO_MUX_GPIO0_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO0_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO0_FUN_DRV_Pos + i2c.config.SCL.mux().Set(muxConfig) + i2c.config.SCL.outFunc().Set(i2c.funcSCL) + inFunc(i2c.funcSCL).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | i2c.config.SCL)) + i2c.config.SCL.Set(true) + // Configure the pad with the given IO mux configuration. + i2c.config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE_W1TS.Set(1 << int(i2c.config.SCL)) + i2c.Bus.SetCTR_SCL_FORCE_OUT(1) +} + +//go:inline +func (i2c *I2C) initFrequency() { + clkmDiv := i2cClkSourceFrequency/(i2c.config.Frequency*1024) + 1 + sclkFreq := i2cClkSourceFrequency / clkmDiv + halfCycle := sclkFreq / i2c.config.Frequency / 2 + //SCL + sclLow := halfCycle + sclWaitHigh := uint32(0) + if i2c.config.Frequency > 50000 { + sclWaitHigh = halfCycle / 8 // compensate the time when freq > 50K + } + sclHigh := halfCycle - sclWaitHigh + // SDA + sdaHold := halfCycle / 4 + sda_sample := halfCycle / 2 + setup := halfCycle + hold := halfCycle + + i2c.Bus.SetSCL_LOW_PERIOD(sclLow - 1) + i2c.Bus.SetSCL_HIGH_PERIOD(sclHigh) + i2c.Bus.SetSCL_RSTART_SETUP_TIME(setup) + i2c.Bus.SetSCL_STOP_SETUP_TIME(setup) + i2c.Bus.SetSCL_START_HOLD_TIME(hold - 1) + i2c.Bus.SetSCL_STOP_HOLD_TIME(hold - 1) + i2c.Bus.SetSDA_SAMPLE_TIME(sda_sample) + i2c.Bus.SetSDA_HOLD_TIME(sdaHold) + // set timeout value + i2c.Bus.SetTO_TIME_OUT(20 * halfCycle) +} + +//go:inline +func (i2c *I2C) startMaster() { + // FIFO mode for data + i2c.Bus.SetFIFO_CONF_NONFIFO_EN(0) + // Reset TX & RX buffers + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_RX_FIFO_RST(0) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(1) + i2c.Bus.SetFIFO_CONF_TX_FIFO_RST(0) + // enable master mode + i2c.Bus.SetCTR_MS_MODE(1) +} + +func (i2c *I2C) resetBus() { + // unlike esp32c3, the esp32 i2c modules do not have a reset fsm register, + // so we need to: + // 1. disconnect the pins + // 2. generate a stop condition manually + // 3. do a full reset + // 4. redo all configuration + + i2c.config.SDA.mux().Set(2< 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { + i2c.Bus.SetDATA_FIFO_RDATA(uint32(c.data[c.head])) + } + reg.Set(i2cCMD_WRITE | uint32(32-count)) + reg = nextAddress(reg) + + if c.head < len(c.data) { + reg.Set(i2cCMD_END) + reg = nil + } else { + cmdIdx++ + } + needRestart = true + + case i2cCMD_READ: + if needAddress { + needAddress = false + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + i2c.Bus.SLAVE_ADDR.Set(uint32(addr)) + reg.Set(i2cCMD_WRITE | 1) + reg = nextAddress(reg) + } + if needRestart { + // We need to send RESTART again after i2cCMD_WRITE. + reg.Set(i2cCMD_RSTART) + + reg = nextAddress(reg) + reg.Set(i2cCMD_WRITE | 1) + + reg = nextAddress(reg) + i2c.Bus.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + needRestart = false + } + count := 32 + bytes := len(c.data) - c.head + // Only last byte in sequence must be sent with ACK set to 1 to indicate end of data. + split := bytes <= count + if split { + bytes-- + } + if bytes > 32 { + bytes = 32 + } + if bytes > 0 { + reg.Set(i2cCMD_READ | uint32(bytes)) + reg = nextAddress(reg) + } + + if split { + readLast = true + reg.Set(i2cCMD_READLAST | 1) + reg = nextAddress(reg) + readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte + cmdIdx++ + } else { + reg.Set(i2cCMD_END) + readTo = c.data[c.head : c.head+bytes] + reg = nil + } + + case i2cCMD_STOP: + reg.Set(i2cCMD_STOP) + reg = nil + cmdIdx++ + } + if reg == nil { + // transmit now + i2c.Bus.SetCTR_TRANS_START(1) + end := nanotime() + timeoutNS + var mask uint32 + for mask = i2c.Bus.INT_STATUS.Get(); mask&intMask == 0; mask = i2c.Bus.INT_STATUS.Get() { + if nanotime() > end { + // timeout leaves the bus in an undefined state, reset + i2c.resetBus() + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + } + switch { + case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0 && !readLast: + return errI2CAckExpected + case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: + // timeout leaves the bus in an undefined state, reset + i2c.resetBus() + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + i2c.Bus.INT_CLR.SetBits(intMask) + for i := 0; i < len(readTo); i++ { + readTo[i] = byte(i2c.Bus.GetDATA_FIFO_RDATA() & 0xff) + c.head++ + } + readTo = nil + reg = &i2c.Bus.COMD0 + } + } + return nil +} + +// Tx does a single I2C transaction at the specified address. +// It clocks out the given address, writes the bytes in w, reads back len(r) +// bytes and stores them in r, and generates a stop condition on the bus. +func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { + // timeout in microseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := make([]i2cCommand, 0, 8) + cmd = append(cmd, i2cCommand{cmd: i2cCMD_RSTART}) + if len(w) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_WRITE, data: w}) + } + if len(r) > 0 { + cmd = append(cmd, i2cCommand{cmd: i2cCMD_READ, data: r}) + } + cmd = append(cmd, i2cCommand{cmd: i2cCMD_STOP}) + + return i2c.transmit(addr, cmd, timeout) +} + +func (i2c *I2C) SetBaudRate(br uint32) error { + return errI2CNotImplemented +} + +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + +func nextAddress(reg *volatile.Register32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) +} + +// CheckDevice does an empty I2C transaction at the specified address. +// This can be used to find out if any device with that address is +// connected, e.g. for enumerating all devices on the bus. +func (i2c *I2C) CheckDevice(addr uint16) bool { + // timeout in microseconds. + const timeout = 40 // 40ms is a reasonable time for a real-time system. + + cmd := []i2cCommand{ + {cmd: i2cCMD_RSTART}, + {cmd: i2cCMD_WRITE}, + {cmd: i2cCMD_STOP}, + } + return i2c.transmit(addr, cmd, timeout) == nil +} diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index 2745f7af21..727fcc1e64 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -312,7 +312,7 @@ func (uart *UART) configure(config UARTConfig, regs registerSet) error { initUARTClock(uart.Bus, regs) - // - disbale TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration + // - disable TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration uart.Bus.SetCLK_CONF_TX_SCLK_EN(0) uart.Bus.SetCLK_CONF_RX_SCLK_EN(0) @@ -480,7 +480,7 @@ func (uart *UART) enableTransmitter() { uart.Bus.SetCONF0_TXFIFO_RST(0) // TXINFO empty threshold is when txfifo_empty_int interrupt produced after the amount of data in Tx-FIFO is less than this register value. uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default) - // we are not using interrut on TX since write we are waiting for FIFO to have space. + // we are not using interrupt on TX since write we are waiting for FIFO to have space. // uart.Bus.INT_ENA.SetBits(esp.UART_INT_ENA_TXFIFO_EMPTY_INT_ENA) } diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index 5e90c9f5bb..aec3ca77a8 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -52,7 +52,7 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. - SPI2 = SPI{esp.SPI2} + SPI2 = &SPI{esp.SPI2} ) // SPIConfig is used to store config info for SPI. @@ -114,7 +114,7 @@ func freqToClockDiv(hz uint32) uint32 { } // Configure and make the SPI peripheral ready to use. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // right now this is only setup to work for the esp32c3 spi2 bus if spi.Bus != esp.SPI2 { return ErrInvalidSPIBus @@ -216,7 +216,7 @@ func (spi SPI) Configure(config SPIConfig) error { // Transfer writes/reads a single byte using the SPI interface. If you need to // transfer larger amounts of data, Tx will be faster. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SetMS_DLEN_MS_DATA_BITLEN(7) spi.Bus.SetW0(uint32(w)) @@ -234,11 +234,11 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(spi.Bus.GetW0()), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // This is accomplished by sending zero bits if r is bigger than w or discarding // the incoming data if w is bigger than r. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { toTransfer := len(w) if len(r) > toTransfer { toTransfer = len(r) diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go index 37f3ee07e8..2f716d6168 100644 --- a/src/machine/machine_fe310.go +++ b/src/machine/machine_fe310.go @@ -26,6 +26,8 @@ const ( func (p Pin) Configure(config PinConfig) { sifive.GPIO0.INPUT_EN.SetBits(1 << uint8(p)) switch config.Mode { + case PinInput: + sifive.GPIO0.OUTPUT_EN.ClearBits(1 << uint8(p)) case PinOutput: sifive.GPIO0.OUTPUT_EN.SetBits(1 << uint8(p)) case PinPWM: @@ -138,7 +140,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -195,7 +197,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // wait for tx ready for spi.Bus.TXDATA.HasBits(sifive.QSPI_TXDATA_FULL) { } diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index d1070a9a03..8bfa097f03 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -2,20 +2,23 @@ package machine +import ( + "crypto/rand" +) + // Dummy machine package that calls out to external functions. const deviceName = "generic" var ( - UART0 = &UART{0} - USB = &UART{100} + USB = &UART{100} ) // The Serial port always points to the default UART in a simulated environment. // // TODO: perhaps this should be a special serial object that outputs via WASI // stdout calls. -var Serial = UART0 +var Serial = hardwareUART0 const ( PinInput PinMode = iota @@ -57,22 +60,57 @@ type SPIConfig struct { Mode uint8 } -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { spiConfigure(spi.Bus, config.SCK, config.SDO, config.SDI) return nil } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { return spiTransfer(spi.Bus, w), nil } +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read +// interface, there must always be the same number of bytes written as bytes read. +// The Tx method knows about this, and offers a few different ways of calling it. +// +// This form sends the bytes in tx buffer, putting the resulting bytes read into the rx buffer. +// Note that the tx and rx buffers must be the same size: +// +// spi.Tx(tx, rx) +// +// This form sends the tx buffer, ignoring the result. Useful for sending "commands" that return zeros +// until all the bytes in the command packet have been received: +// +// spi.Tx(tx, nil) +// +// This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": +// +// spi.Tx(nil, rx) +func (spi *SPI) Tx(w, r []byte) error { + var wptr, rptr *byte + var wlen, rlen int + if len(w) != 0 { + wptr = &w[0] + wlen = len(w) + } + if len(r) != 0 { + rptr = &r[0] + rlen = len(r) + } + spiTX(spi.Bus, wptr, wlen, rptr, rlen) + return nil +} + //export __tinygo_spi_configure func spiConfigure(bus uint8, sck Pin, SDO Pin, SDI Pin) //export __tinygo_spi_transfer func spiTransfer(bus uint8, w uint8) uint8 +//export __tinygo_spi_tx +func spiTX(bus uint8, wptr *byte, wlen int, rptr *byte, rlen int) uint8 + // InitADC enables support for ADC peripherals. func InitADC() { // Nothing to do here. @@ -116,7 +154,17 @@ func (i2c *I2C) SetBaudRate(br uint32) error { // Tx does a single I2C transaction at the specified address. func (i2c *I2C) Tx(addr uint16, w, r []byte) error { - i2cTransfer(i2c.Bus, &w[0], len(w), &r[0], len(r)) + var wptr, rptr *byte + var wlen, rlen int + if len(w) != 0 { + wptr = &w[0] + wlen = len(w) + } + if len(r) != 0 { + rptr = &r[0] + rlen = len(r) + } + i2cTransfer(i2c.Bus, wptr, wlen, rptr, rlen) // TODO: do something with the returned error code. return nil } @@ -176,6 +224,11 @@ func uartRead(bus uint8, buf *byte, bufLen int) int //export __tinygo_uart_write func uartWrite(bus uint8, buf *byte, bufLen int) int +var ( + hardwareUART0 = &UART{0} + hardwareUART1 = &UART{1} +) + // Some objects used by Atmel SAM D chips (samd21, samd51). // Defined here (without build tag) for convenience. var ( @@ -195,12 +248,22 @@ var ( sercomI2CM6 = &I2C{6} sercomI2CM7 = &I2C{7} - sercomSPIM0 = SPI{0} - sercomSPIM1 = SPI{1} - sercomSPIM2 = SPI{2} - sercomSPIM3 = SPI{3} - sercomSPIM4 = SPI{4} - sercomSPIM5 = SPI{5} - sercomSPIM6 = SPI{6} - sercomSPIM7 = SPI{7} + sercomSPIM0 = &SPI{0} + sercomSPIM1 = &SPI{1} + sercomSPIM2 = &SPI{2} + sercomSPIM3 = &SPI{3} + sercomSPIM4 = &SPI{4} + sercomSPIM5 = &SPI{5} + sercomSPIM6 = &SPI{6} + sercomSPIM7 = &SPI{7} ) + +// GetRNG returns 32 bits of random data from the WASI random source. +func GetRNG() (uint32, error) { + var buf [4]byte + _, err := rand.Read(buf[:]) + if err != nil { + return 0, err + } + return uint32(buf[0])<<0 | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil +} diff --git a/src/machine/machine_generic_peripherals.go b/src/machine/machine_generic_peripherals.go index 5491a26983..6c95c206cd 100644 --- a/src/machine/machine_generic_peripherals.go +++ b/src/machine/machine_generic_peripherals.go @@ -6,6 +6,9 @@ package machine // boards that define their peripherals in the board file (e.g. board_qtpy.go). var ( - SPI0 = SPI{0} - I2C0 = &I2C{0} + UART0 = hardwareUART0 + UART1 = hardwareUART1 + SPI0 = &SPI{0} + SPI1 = &SPI{1} + I2C0 = &I2C{0} ) diff --git a/src/machine/machine_k210.go b/src/machine/machine_k210.go index e0821670ba..d83576a617 100644 --- a/src/machine/machine_k210.go +++ b/src/machine/machine_k210.go @@ -49,7 +49,7 @@ const ( var ( errUnsupportedSPIController = errors.New("SPI controller not supported. Use SPI0 or SPI1.") - errI2CTxAbort = errors.New("I2C transmition has been aborted.") + errI2CTxAbort = errors.New("I2C transmission has been aborted.") ) func (p Pin) setFPIOAIOPull(pull fpioaPullMode) { @@ -419,7 +419,7 @@ type SPIConfig struct { // Configure is intended to setup the SPI interface. // Only SPI controller 0 and 1 can be used because SPI2 is a special // peripheral-mode controller and SPI3 is used for flashing. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Use default pins if not set. if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { config.SCK = SPI0_SCK_PIN @@ -476,7 +476,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.SSIENR.Set(0) // Set transfer-receive mode. @@ -619,7 +619,7 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error { dataLen -= fifoLen } - // Wait for transmition to complete. + // Wait for transmission to complete. for i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_ACTIVITY) || !i2c.Bus.STATUS.HasBits(kendryte.I2C_STATUS_TFE) { } diff --git a/src/machine/machine_mimxrt1062.go b/src/machine/machine_mimxrt1062.go index 8c42c55645..74d01c7661 100644 --- a/src/machine/machine_mimxrt1062.go +++ b/src/machine/machine_mimxrt1062.go @@ -451,7 +451,7 @@ func (p Pin) getGPIO() (norm *nxp.GPIO_Type, fast *nxp.GPIO_Type) { } } -// getPad returns both the pad and mux configration registers for a given Pin. +// getPad returns both the pad and mux configuration registers for a given Pin. func (p Pin) getPad() (pad *volatile.Register32, mux *volatile.Register32) { switch p.getPort() { case portA: diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index cc68c1d916..d6d6349f29 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -3,9 +3,8 @@ package machine import ( - "bytes" "device/nrf" - "encoding/binary" + "internal/binary" "runtime/interrupt" "unsafe" ) @@ -108,7 +107,7 @@ func (p Pin) Get() bool { func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { // Some variables to easily check whether a channel was already configured // as an event channel for the given pin. - // This is not just an optimization, this is requred: the datasheet says + // This is not just an optimization, this is required: the datasheet says // that configuring more than one channel for a given pin results in // unpredictable behavior. expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk) @@ -318,9 +317,9 @@ func (i2c *I2C) signalStop() error { var rngStarted = false -// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// getRNG returns 32 bits of non-deterministic random data based on internal thermal noise. // According to Nordic's documentation, the random output is suitable for cryptographic purposes. -func GetRNG() (ret uint32, err error) { +func getRNG() (ret uint32, err error) { // There's no apparent way to check the status of the RNG peripheral's task, so simply start it // to avoid deadlocking while waiting for output. if !rngStarted { @@ -386,7 +385,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { } address := FlashDataStart() + uintptr(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) waitWhileFlashBusy() @@ -444,17 +443,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - func waitWhileFlashBusy() { for nrf.NVMC.GetREADY() != nrf.NVMC_READY_READY_Ready { } diff --git a/src/machine/machine_nrf51.go b/src/machine/machine_nrf51.go index ae6ff61a84..d627d63c21 100644 --- a/src/machine/machine_nrf51.go +++ b/src/machine/machine_nrf51.go @@ -34,8 +34,8 @@ type SPI struct { // There are 2 SPI interfaces on the NRF51. var ( - SPI0 = SPI{Bus: nrf.SPI0} - SPI1 = SPI{Bus: nrf.SPI1} + SPI0 = &SPI{Bus: nrf.SPI0} + SPI1 = &SPI{Bus: nrf.SPI1} ) // SPIConfig is used to store config info for SPI. @@ -49,7 +49,7 @@ type SPIConfig struct { } // Configure is intended to setup the SPI interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // Disable bus to configure it spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) @@ -122,7 +122,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { spi.Bus.TXD.Set(uint32(w)) for spi.Bus.EVENTS_READY.Get() == 0 { } @@ -133,7 +133,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { return byte(r), nil } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // @@ -150,7 +150,7 @@ func (spi SPI) Transfer(w byte) (byte, error) { // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var err error switch { diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index 04fbaaf9dc..a582a7aa56 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -14,20 +14,17 @@ func CPUFrequency() uint32 { // InitADC initializes the registers needed for ADC. func InitADC() { - return // no specific setup on nrf52 machine. -} - -// Configure configures an ADC pin to be able to read analog data. -func (a ADC) Configure(config ADCConfig) { // Enable ADC. - // The ADC does not consume a noticeable amount of current simply by being - // enabled. + // The ADC does not consume a noticeable amount of current by being enabled. nrf.SAADC.ENABLE.Set(nrf.SAADC_ENABLE_ENABLE_Enabled << nrf.SAADC_ENABLE_ENABLE_Pos) +} - // Use fixed resolution of 12 bits. - // TODO: is it useful for users to change this? - nrf.SAADC.RESOLUTION.Set(nrf.SAADC_RESOLUTION_VAL_12bit) - +// Configure configures an ADC pin to be able to read analog data. +// Reference voltage can be 150, 300, 600, 1200, 1800, 2400, 3000(default), 3600 mV +// Resolution can be 8, 10, 12(default), 14 bits +// SampleTime will be ceiled to 3(default), 5, 10, 15, 20 or 40(max) µS respectively +// Samples can be 1(default), 2, 4, 8, 16, 32, 64, 128, 256 samples +func (a *ADC) Configure(config ADCConfig) { var configVal uint32 = nrf.SAADC_CH_CONFIG_RESP_Bypass< len(r) the extra bytes received will be // dropped and if len(w) < len(r) extra 0 bytes will be sent. -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { // Unfortunately the hardware (on the nrf52832) only supports up to 255 // bytes in the buffers, so if either w or r is longer than that the // transfer needs to be broken up in pieces. diff --git a/src/machine/machine_nrf_bare.go b/src/machine/machine_nrf_bare.go new file mode 100644 index 0000000000..b94886ed91 --- /dev/null +++ b/src/machine/machine_nrf_bare.go @@ -0,0 +1,9 @@ +//go:build nrf && !softdevice + +package machine + +// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// According to Nordic's documentation, the random output is suitable for cryptographic purposes. +func GetRNG() (ret uint32, err error) { + return getRNG() +} diff --git a/src/machine/machine_nrf_sd.go b/src/machine/machine_nrf_sd.go new file mode 100644 index 0000000000..b816e62ee0 --- /dev/null +++ b/src/machine/machine_nrf_sd.go @@ -0,0 +1,59 @@ +//go:build nrf && softdevice + +package machine + +import ( + "device/arm" + "device/nrf" + + "errors" +) + +// avoid a heap allocation in GetRNG. +var ( + softdeviceEnabled uint8 + bytesAvailable uint8 + buf [4]uint8 + + errNoSoftDeviceSupport = errors.New("rng: softdevice not supported on this device") + errNotEnoughRandomData = errors.New("rng: not enough random data available") +) + +// GetRNG returns 32 bits of non-deterministic random data based on internal thermal noise. +// According to Nordic's documentation, the random output is suitable for cryptographic purposes. +func GetRNG() (ret uint32, err error) { + // First check whether the SoftDevice is enabled. + // sd_rand_application_bytes_available_get cannot be called when the SoftDevice is not enabled. + arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled + + if softdeviceEnabled == 0 { + return getRNG() + } + + // call into the SoftDevice to get random data bytes available + switch nrf.Device { + case "nrf51": + // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 + arm.SVCall1(0x2B+4, &bytesAvailable) + case "nrf52", "nrf52840", "nrf52833": + // sd_rand_application_bytes_available_get: SOC_SVC_BASE_NOT_AVAILABLE + 4 + arm.SVCall1(0x2C+4, &bytesAvailable) + default: + return 0, errNoSoftDeviceSupport + } + + if bytesAvailable < 4 { + return 0, errNotEnoughRandomData + } + + switch nrf.Device { + case "nrf51": + // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 + arm.SVCall2(0x2B+5, &buf, 4) + case "nrf52", "nrf52840", "nrf52833": + // sd_rand_application_vector_get: SOC_SVC_BASE_NOT_AVAILABLE + 5 + arm.SVCall2(0x2C+5, &buf, 4) + } + + return uint32(buf[0]) | uint32(buf[1])<<8 | uint32(buf[2])<<16 | uint32(buf[3])<<24, nil +} diff --git a/src/machine/machine_rp2040.go b/src/machine/machine_rp2.go similarity index 53% rename from src/machine/machine_rp2040.go rename to src/machine/machine_rp2.go index e76a85e199..9afefa5387 100644 --- a/src/machine/machine_rp2040.go +++ b/src/machine/machine_rp2.go @@ -1,9 +1,10 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine import ( "device/rp" + "runtime/interrupt" "runtime/volatile" "unsafe" ) @@ -11,68 +12,46 @@ import ( const deviceName = rp.Device const ( - // GPIO pins - GPIO0 Pin = 0 // peripherals: PWM0 channel A - GPIO1 Pin = 1 // peripherals: PWM0 channel B - GPIO2 Pin = 2 // peripherals: PWM1 channel A - GPIO3 Pin = 3 // peripherals: PWM1 channel B - GPIO4 Pin = 4 // peripherals: PWM2 channel A - GPIO5 Pin = 5 // peripherals: PWM2 channel B - GPIO6 Pin = 6 // peripherals: PWM3 channel A - GPIO7 Pin = 7 // peripherals: PWM3 channel B - GPIO8 Pin = 8 // peripherals: PWM4 channel A - GPIO9 Pin = 9 // peripherals: PWM4 channel B - GPIO10 Pin = 10 // peripherals: PWM5 channel A - GPIO11 Pin = 11 // peripherals: PWM5 channel B - GPIO12 Pin = 12 // peripherals: PWM6 channel A - GPIO13 Pin = 13 // peripherals: PWM6 channel B - GPIO14 Pin = 14 // peripherals: PWM7 channel A - GPIO15 Pin = 15 // peripherals: PWM7 channel B - GPIO16 Pin = 16 // peripherals: PWM0 channel A - GPIO17 Pin = 17 // peripherals: PWM0 channel B - GPIO18 Pin = 18 // peripherals: PWM1 channel A - GPIO19 Pin = 19 // peripherals: PWM1 channel B - GPIO20 Pin = 20 // peripherals: PWM2 channel A - GPIO21 Pin = 21 // peripherals: PWM2 channel B - GPIO22 Pin = 22 // peripherals: PWM3 channel A - GPIO23 Pin = 23 // peripherals: PWM3 channel B - GPIO24 Pin = 24 // peripherals: PWM4 channel A - GPIO25 Pin = 25 // peripherals: PWM4 channel B - GPIO26 Pin = 26 // peripherals: PWM5 channel A - GPIO27 Pin = 27 // peripherals: PWM5 channel B - GPIO28 Pin = 28 // peripherals: PWM6 channel A - GPIO29 Pin = 29 // peripherals: PWM6 channel B - - // Analog pins - ADC0 Pin = GPIO26 - ADC1 Pin = GPIO27 - ADC2 Pin = GPIO28 - ADC3 Pin = GPIO29 + // Number of spin locks available + // Note: On RP2350, most spinlocks are unusable due to Errata 2 + _NUMSPINLOCKS = 32 + _PICO_SPINLOCK_ID_IRQ = 9 + // is48Pin notes whether the chip is RP2040 with 32 pins or RP2350 with 48 pins. + is48Pin = _NUMBANK0_GPIOS == 48 ) +// UART on the RP2040 +var ( + UART0 = &_UART0 + _UART0 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART0, + } + + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: rp.UART1, + } +) + +func init() { + UART0.Interrupt = interrupt.New(rp.IRQ_UART0_IRQ, _UART0.handleInterrupt) + UART1.Interrupt = interrupt.New(rp.IRQ_UART1_IRQ, _UART1.handleInterrupt) +} + //go:linkname machineInit runtime.machineInit func machineInit() { // Reset all peripherals to put system into a known state, // except for QSPI pads and the XIP IO bank, as this is fatal if running from flash // and the PLLs, as this is fatal if clock muxing has not been reset on this boot // and USB, syscfg, as this disturbs USB-to-SWD on core 1 - bits := ^uint32(rp.RESETS_RESET_IO_QSPI | - rp.RESETS_RESET_PADS_QSPI | - rp.RESETS_RESET_PLL_USB | - rp.RESETS_RESET_USBCTRL | - rp.RESETS_RESET_SYSCFG | - rp.RESETS_RESET_PLL_SYS) + bits := ^uint32(initDontReset) resetBlock(bits) // Remove reset from peripherals which are clocked only by clkSys and // clkRef. Other peripherals stay in reset until we've configured clocks. - bits = ^uint32(rp.RESETS_RESET_ADC | - rp.RESETS_RESET_RTC | - rp.RESETS_RESET_SPI0 | - rp.RESETS_RESET_SPI1 | - rp.RESETS_RESET_UART0 | - rp.RESETS_RESET_UART1 | - rp.RESETS_RESET_USBCTRL) + bits = ^uint32(initUnreset) unresetBlockWait(bits) clocks.init() @@ -134,4 +113,25 @@ const ( ) // DMA channels usable on the RP2040. -var dmaChannels = (*[12]dmaChannel)(unsafe.Pointer(rp.DMA)) +var dmaChannels = (*[12 + 4*rp2350ExtraReg]dmaChannel)(unsafe.Pointer(rp.DMA)) + +//go:inline +func boolToBit(a bool) uint32 { + if a { + return 1 + } + return 0 +} + +//go:inline +func u32max(a, b uint32) uint32 { + if a > b { + return a + } + return b +} + +//go:inline +func isReservedI2CAddr(addr uint8) bool { + return (addr&0x78) == 0 || (addr&0x78) == 0x78 +} diff --git a/src/machine/machine_rp2040_pll.go b/src/machine/machine_rp2040_pll.go deleted file mode 100644 index d611f2924d..0000000000 --- a/src/machine/machine_rp2040_pll.go +++ /dev/null @@ -1,100 +0,0 @@ -//go:build rp2040 - -package machine - -import ( - "device/rp" - "runtime/volatile" - "unsafe" -) - -type pll struct { - cs volatile.Register32 - pwr volatile.Register32 - fbDivInt volatile.Register32 - prim volatile.Register32 -} - -var ( - pllSys = (*pll)(unsafe.Pointer(rp.PLL_SYS)) - pllUSB = (*pll)(unsafe.Pointer(rp.PLL_USB)) -) - -// init initializes pll (Sys or USB) given the following parameters. -// -// Input clock divider, refdiv. -// -// Requested output frequency from the VCO (voltage controlled oscillator), vcoFreq. -// -// Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2. -// -// Post Divider 2, postDiv2 with range 1-7. -func (pll *pll) init(refdiv, vcoFreq, postDiv1, postDiv2 uint32) { - refFreq := xoscFreq / refdiv - - // What are we multiplying the reference clock by to get the vco freq - // (The regs are called div, because you divide the vco output and compare it to the refclk) - fbdiv := vcoFreq / (refFreq * MHz) - - // Check fbdiv range - if !(fbdiv >= 16 && fbdiv <= 320) { - panic("fbdiv should be in the range [16,320]") - } - - // Check divider ranges - if !((postDiv1 >= 1 && postDiv1 <= 7) && (postDiv2 >= 1 && postDiv2 <= 7)) { - panic("postdiv1, postdiv1 should be in the range [1,7]") - } - - // postDiv1 should be >= postDiv2 - // from appnote page 11 - // postdiv1 is designed to operate with a higher input frequency - // than postdiv2 - if postDiv1 < postDiv2 { - panic("postdiv1 should be greater than or equal to postdiv2") - } - - // Check that reference frequency is no greater than vco / 16 - if refFreq > vcoFreq/16 { - panic("reference frequency should not be greater than vco frequency divided by 16") - } - - // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 - pdiv := postDiv1< 0 { rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_SETUP_REC) - setup := usb.NewSetup(usbDPSRAM.setupBytes()) + setup := usb.NewSetup(_usbDPSRAM.setupBytes()) ok := false if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { @@ -136,54 +136,55 @@ func handleUSBIRQ(intr interrupt.Interrupt) { func initEndpoint(ep, config uint32) { val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff) - offset := ep*2*USBBufferLen + 0x100 + offset := ep*2*usbBufferLen + 0x100 val |= offset switch config { case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: val |= usbEpControlEndpointTypeInterrupt - usbDPSRAM.EPxControl[ep].In.Set(val) + _usbDPSRAM.EPxControl[ep].In.Set(val) case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: val |= usbEpControlEndpointTypeBulk - usbDPSRAM.EPxControl[ep].Out.Set(val) - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: val |= usbEpControlEndpointTypeInterrupt - usbDPSRAM.EPxControl[ep].Out.Set(val) - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: val |= usbEpControlEndpointTypeBulk - usbDPSRAM.EPxControl[ep].In.Set(val) + _usbDPSRAM.EPxControl[ep].In.Set(val) case usb.ENDPOINT_TYPE_CONTROL: val |= usbEpControlEndpointTypeControl - usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) } } func handleUSBSetAddress(setup usb.Setup) bool { - sendUSBPacket(0, []byte{}, 0) + // Using 570μs timeout which is exactly the same as SAMD21. + const ackTimeout = 570 - // last, set the device address to that requested by host - // wait for transfer to complete - timeout := 3000 rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() for (rp.USBCTRL_REGS.SIE_STATUS.Get() & rp.USBCTRL_REGS_SIE_STATUS_ACK_REC) == 0 { - timeout-- - if timeout == 0 { - return true + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false } } + // Set the device address to that requested by host. rp.USBCTRL_REGS.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USBCTRL_REGS_ADDR_ENDP_ADDRESS_Msk) - return true } @@ -219,37 +220,37 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { var b [cdcLineInfoSize]byte ep := 0 - for !usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { + for !_usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { // TODO: timeout } - ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask - copy(b[:], usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) + copy(b[:], _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) return b, nil } func handleEndpointRx(ep uint32) []byte { - ctrl := usbDPSRAM.EPxBufferControl[ep].Out.Get() - usbDPSRAM.EPxBufferControl[ep].Out.Set(USBBufferLen & usbBuf0CtrlLenMask) + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) sz := ctrl & usbBuf0CtrlLenMask - return usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] + return _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] } func handleEndpointRxComplete(ep uint32) { epXdata0[ep] = !epXdata0[ep] if epXdata0[ep] || ep == 0 { - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) } - usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) } func SendZlp() { @@ -269,8 +270,8 @@ func sendViaEPIn(ep uint32, data []byte, count int) { // Mark as full val |= usbBuf0CtrlFull - copy(usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) } func sendStallViaEPIn(ep uint32) { @@ -279,41 +280,41 @@ func sendStallViaEPIn(ep uint32) { rp.USBCTRL_REGS.EP_STALL_ARM.Set(rp.USBCTRL_REGS_EP_STALL_ARM_EP0_IN) } val := uint32(usbBuf0CtrlFull) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) val |= uint32(usbBuf0CtrlStall) - usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) } -type USBDPSRAM struct { +type usbDPSRAM struct { // Note that EPxControl[0] is not EP0Control but 8-byte setup data. - EPxControl [16]USBEndpointControlRegister + EPxControl [16]usbEndpointControlRegister - EPxBufferControl [16]USBBufferControlRegister + EPxBufferControl [16]usbBufferControlRegister - EPxBuffer [16]USBBuffer + EPxBuffer [16]usbBuffer } -type USBEndpointControlRegister struct { +type usbEndpointControlRegister struct { In volatile.Register32 Out volatile.Register32 } -type USBBufferControlRegister struct { +type usbBufferControlRegister struct { In volatile.Register32 Out volatile.Register32 } -type USBBuffer struct { - Buffer0 [USBBufferLen]byte - Buffer1 [USBBufferLen]byte +type usbBuffer struct { + Buffer0 [usbBufferLen]byte + Buffer1 [usbBufferLen]byte } var ( - usbDPSRAM = (*USBDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) + _usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) epXdata0 [16]bool setupBytes [8]byte ) -func (d *USBDPSRAM) setupBytes() []byte { +func (d *usbDPSRAM) setupBytes() []byte { data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get() setupBytes[0] = byte(data) @@ -330,7 +331,7 @@ func (d *USBDPSRAM) setupBytes() []byte { return setupBytes[:] } -func (d *USBDPSRAM) clear() { +func (d *usbDPSRAM) clear() { for i := 0; i < len(d.EPxControl); i++ { d.EPxControl[i].In.Set(0) d.EPxControl[i].Out.Set(0) @@ -373,5 +374,5 @@ const ( usbBuf0CtrlAvail = 0x00000400 usbBuf0CtrlLenMask = 0x000003FF - USBBufferLen = 64 + usbBufferLen = 64 ) diff --git a/src/machine/machine_rp2350_rom.go b/src/machine/machine_rp2350_rom.go new file mode 100644 index 0000000000..665464ae69 --- /dev/null +++ b/src/machine/machine_rp2350_rom.go @@ -0,0 +1,560 @@ +//go:build tinygo && rp2350 + +package machine + +import ( + "runtime/interrupt" + "unsafe" +) + +/* +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned long uint32_t; +typedef unsigned long size_t; +typedef unsigned long uintptr_t; +typedef long int intptr_t; + +typedef const volatile uint16_t io_ro_16; +typedef const volatile uint32_t io_ro_32; +typedef volatile uint16_t io_rw_16; +typedef volatile uint32_t io_rw_32; +typedef volatile uint32_t io_wo_32; + +#define false 0 +#define true 1 +typedef int bool; + +#define ram_func __attribute__((section(".ramfuncs"),noinline)) + +typedef void (*flash_exit_xip_fn)(void); +typedef void (*flash_flush_cache_fn)(void); +typedef void (*flash_connect_internal_fn)(void); +typedef void (*flash_range_erase_fn)(uint32_t, size_t, uint32_t, uint16_t); +typedef void (*flash_range_program_fn)(uint32_t, const uint8_t*, size_t); +static inline __attribute__((always_inline)) void __compiler_memory_barrier(void) { + __asm__ volatile ("" : : : "memory"); +} + +// https://datasheets.raspberrypi.com/rp2350/rp2350-datasheet.pdf +// 13.9. Predefined OTP Data Locations +// OTP_DATA: FLASH_DEVINFO Register + +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS 0x0F00 +#define OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB 8 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS 0xF000 +#define OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB 12 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/addressmap.h + +#define REG_ALIAS_RW_BITS (0x0 << 12) +#define REG_ALIAS_XOR_BITS (0x1 << 12) +#define REG_ALIAS_SET_BITS (0x2 << 12) +#define REG_ALIAS_CLR_BITS (0x3 << 12) + +#define XIP_BASE 0x10000000 +#define XIP_QMI_BASE 0x400d0000 +#define IO_QSPI_BASE 0x40030000 +#define BOOTRAM_BASE 0x400e0000 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_base/include/hardware/address_mapped.h + +#define hw_alias_check_addr(addr) ((uintptr_t)(addr)) +#define hw_set_alias_untyped(addr) ((void *)(REG_ALIAS_SET_BITS + hw_alias_check_addr(addr))) +#define hw_clear_alias_untyped(addr) ((void *)(REG_ALIAS_CLR_BITS + hw_alias_check_addr(addr))) +#define hw_xor_alias_untyped(addr) ((void *)(REG_ALIAS_XOR_BITS + hw_alias_check_addr(addr))) + +__attribute__((always_inline)) +static void hw_set_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_set_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_clear_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_clear_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_xor_bits(io_rw_32 *addr, uint32_t mask) { + *(io_rw_32 *) hw_xor_alias_untyped((volatile void *) addr) = mask; +} + +__attribute__((always_inline)) +static void hw_write_masked(io_rw_32 *addr, uint32_t values, uint32_t write_mask) { + hw_xor_bits(addr, (*addr ^ values) & write_mask); +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_platform_compiler/include/pico/platform/compiler.h + +#define pico_default_asm_volatile(...) __asm volatile (".syntax unified\n" __VA_ARGS__) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/pico_platform/include/pico/platform.h + +static bool pico_processor_state_is_nonsecure(void) { +// // todo add a define to disable NS checking at all? +// // IDAU-Exempt addresses return S=1 when tested in the Secure state, +// // whereas executing a tt in the NonSecure state will always return S=0. +// uint32_t tt; +// pico_default_asm_volatile ( +// "movs %0, #0\n" +// "tt %0, %0\n" +// : "=r" (tt) : : "cc" +// ); +// return !(tt & (1u << 22)); + + return false; +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/include/pico/bootrom_constants.h + +// RP2040 & RP2350 +#define ROM_DATA_SOFTWARE_GIT_REVISION ROM_TABLE_CODE('G', 'R') +#define ROM_FUNC_FLASH_ENTER_CMD_XIP ROM_TABLE_CODE('C', 'X') +#define ROM_FUNC_FLASH_EXIT_XIP ROM_TABLE_CODE('E', 'X') +#define ROM_FUNC_FLASH_FLUSH_CACHE ROM_TABLE_CODE('F', 'C') +#define ROM_FUNC_CONNECT_INTERNAL_FLASH ROM_TABLE_CODE('I', 'F') +#define ROM_FUNC_FLASH_RANGE_ERASE ROM_TABLE_CODE('R', 'E') +#define ROM_FUNC_FLASH_RANGE_PROGRAM ROM_TABLE_CODE('R', 'P') + +// RP2350 only +#define ROM_FUNC_PICK_AB_PARTITION ROM_TABLE_CODE('A', 'B') +#define ROM_FUNC_CHAIN_IMAGE ROM_TABLE_CODE('C', 'I') +#define ROM_FUNC_EXPLICIT_BUY ROM_TABLE_CODE('E', 'B') +#define ROM_FUNC_FLASH_RUNTIME_TO_STORAGE_ADDR ROM_TABLE_CODE('F', 'A') +#define ROM_DATA_FLASH_DEVINFO16_PTR ROM_TABLE_CODE('F', 'D') +#define ROM_FUNC_FLASH_OP ROM_TABLE_CODE('F', 'O') +#define ROM_FUNC_GET_B_PARTITION ROM_TABLE_CODE('G', 'B') +#define ROM_FUNC_GET_PARTITION_TABLE_INFO ROM_TABLE_CODE('G', 'P') +#define ROM_FUNC_GET_SYS_INFO ROM_TABLE_CODE('G', 'S') +#define ROM_FUNC_GET_UF2_TARGET_PARTITION ROM_TABLE_CODE('G', 'U') +#define ROM_FUNC_LOAD_PARTITION_TABLE ROM_TABLE_CODE('L', 'P') +#define ROM_FUNC_OTP_ACCESS ROM_TABLE_CODE('O', 'A') +#define ROM_DATA_PARTITION_TABLE_PTR ROM_TABLE_CODE('P', 'T') +#define ROM_FUNC_FLASH_RESET_ADDRESS_TRANS ROM_TABLE_CODE('R', 'A') +#define ROM_FUNC_REBOOT ROM_TABLE_CODE('R', 'B') +#define ROM_FUNC_SET_ROM_CALLBACK ROM_TABLE_CODE('R', 'C') +#define ROM_FUNC_SECURE_CALL ROM_TABLE_CODE('S', 'C') +#define ROM_FUNC_SET_NS_API_PERMISSION ROM_TABLE_CODE('S', 'P') +#define ROM_FUNC_BOOTROM_STATE_RESET ROM_TABLE_CODE('S', 'R') +#define ROM_FUNC_SET_BOOTROM_STACK ROM_TABLE_CODE('S', 'S') +#define ROM_DATA_SAVED_XIP_SETUP_FUNC_PTR ROM_TABLE_CODE('X', 'F') +#define ROM_FUNC_FLASH_SELECT_XIP_READ_MODE ROM_TABLE_CODE('X', 'M') +#define ROM_FUNC_VALIDATE_NS_BUFFER ROM_TABLE_CODE('V', 'B') + +#define BOOTSEL_FLAG_GPIO_PIN_SPECIFIED 0x20 + +#define BOOTROM_FUNC_TABLE_OFFSET 0x14 + +// todo remove this (or #ifdef it for A1/A2) +#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2) +#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4) + +#define BOOTROM_VTABLE_OFFSET 0x00 +#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE) + + +// https://github.com/raspberrypi/pico-sdk +// src/common/boot_picoboot_headers/include/boot/picoboot_constants.h + +// values 0-7 are secure/non-secure +#define REBOOT2_FLAG_REBOOT_TYPE_NORMAL 0x0 // param0 = diagnostic partition +#define REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL 0x2 // param0 = bootsel_flags, param1 = gpio_config +#define REBOOT2_FLAG_REBOOT_TYPE_RAM_IMAGE 0x3 // param0 = image_base, param1 = image_end +#define REBOOT2_FLAG_REBOOT_TYPE_FLASH_UPDATE 0x4 // param0 = update_base + +#define REBOOT2_FLAG_NO_RETURN_ON_SUCCESS 0x100 + +#define RT_FLAG_FUNC_ARM_SEC 0x0004 +#define RT_FLAG_FUNC_ARM_NONSEC 0x0010 +#define RT_FLAG_DATA 0x0040 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/include/pico/bootrom.h + +#define ROM_TABLE_CODE(c1, c2) ((c1) | ((c2) << 8)) + +typedef void *(*rom_table_lookup_fn)(uint32_t code, uint32_t mask); + +__attribute__((always_inline)) +static void *rom_func_lookup_inline(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); + if (pico_processor_state_is_nonsecure()) { + return rom_table_lookup(code, RT_FLAG_FUNC_ARM_NONSEC); + } else { + return rom_table_lookup(code, RT_FLAG_FUNC_ARM_SEC); + } +} + +__attribute__((always_inline)) +static void *rom_data_lookup_inline(uint32_t code) { + rom_table_lookup_fn rom_table_lookup = (rom_table_lookup_fn) (uintptr_t)*(uint16_t*)(BOOTROM_TABLE_LOOKUP_OFFSET); + return rom_table_lookup(code, RT_FLAG_DATA); +} + +typedef int (*rom_reboot_fn)(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1); + +__attribute__((always_inline)) +int rom_reboot(uint32_t flags, uint32_t delay_ms, uint32_t p0, uint32_t p1) { + rom_reboot_fn func = (rom_reboot_fn) rom_func_lookup_inline(ROM_FUNC_REBOOT); + return func(flags, delay_ms, p0, p1); +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/pico_bootrom/bootrom.c + +void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interface_mask) { + uint32_t flags = disable_interface_mask; + if (usb_activity_gpio_pin_mask) { + flags |= BOOTSEL_FLAG_GPIO_PIN_SPECIFIED; + // the parameter is actually the gpio number, but we only care if BOOTSEL_FLAG_GPIO_PIN_SPECIFIED + usb_activity_gpio_pin_mask = (uint32_t)__builtin_ctz(usb_activity_gpio_pin_mask); + } + rom_reboot(REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL | REBOOT2_FLAG_NO_RETURN_ON_SUCCESS, 10, flags, usb_activity_gpio_pin_mask); + __builtin_unreachable(); +} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/qmi.h + +#define QMI_DIRECT_CSR_EN_BITS 0x00000001 +#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000 +#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400 +#define QMI_M1_WFMT_RESET 0x00001000 +#define QMI_M1_WCMD_RESET 0x0000a002 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_regs/include/hardware/regs/io_qspi.h + +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS 0x00003000 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB 12 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW 0x2 +#define IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH 0x3 + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/io_qspi.h + +typedef struct { + io_rw_32 inte; // IO_QSPI_PROC0_INTE + io_rw_32 intf; // IO_QSPI_PROC0_INTF + io_ro_32 ints; // IO_QSPI_PROC0_INTS +} io_qspi_irq_ctrl_hw_t; + +typedef struct { + io_ro_32 status; // IO_QSPI_GPIO_QSPI_SCLK_STATUS + io_rw_32 ctrl; // IO_QSPI_GPIO_QSPI_SCLK_CTRL +} io_qspi_status_ctrl_hw_t; + +typedef struct { + io_ro_32 usbphy_dp_status; // IO_QSPI_USBPHY_DP_STATUS + io_rw_32 usbphy_dp_ctrl; // IO_QSPI_USBPHY_DP_CTRL + io_ro_32 usbphy_dm_status; // IO_QSPI_USBPHY_DM_STATUS + io_rw_32 usbphy_dm_ctrl; // IO_QSPI_USBPHY_DM_CTRL + io_qspi_status_ctrl_hw_t io[6]; + uint32_t _pad0[112]; + io_ro_32 irqsummary_proc0_secure; // IO_QSPI_IRQSUMMARY_PROC0_SECURE + io_ro_32 irqsummary_proc0_nonsecure; // IO_QSPI_IRQSUMMARY_PROC0_NONSECURE + io_ro_32 irqsummary_proc1_secure; // IO_QSPI_IRQSUMMARY_PROC1_SECURE + io_ro_32 irqsummary_proc1_nonsecure; // IO_QSPI_IRQSUMMARY_PROC1_NONSECURE + io_ro_32 irqsummary_dormant_wake_secure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_SECURE + io_ro_32 irqsummary_dormant_wake_nonsecure; // IO_QSPI_IRQSUMMARY_DORMANT_WAKE_NONSECURE + io_rw_32 intr; // IO_QSPI_INTR + + union { + struct { + io_qspi_irq_ctrl_hw_t proc0_irq_ctrl; + io_qspi_irq_ctrl_hw_t proc1_irq_ctrl; + io_qspi_irq_ctrl_hw_t dormant_wake_irq_ctrl; + }; + io_qspi_irq_ctrl_hw_t irq_ctrl[3]; + }; +} io_qspi_hw_t; + +#define io_qspi_hw ((io_qspi_hw_t *)IO_QSPI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2350/hardware_structs/include/hardware/structs/qmi.h + +typedef struct { + io_rw_32 timing; // QMI_M0_TIMING + io_rw_32 rfmt; // QMI_M0_RFMT + io_rw_32 rcmd; // QMI_M0_RCMD + io_rw_32 wfmt; // QMI_M0_WFMT + io_rw_32 wcmd; // QMI_M0_WCMD +} qmi_mem_hw_t; + +typedef struct { + io_rw_32 direct_csr; // QMI_DIRECT_CSR + io_wo_32 direct_tx; // QMI_DIRECT_TX + io_ro_32 direct_rx; // QMI_DIRECT_RX + qmi_mem_hw_t m[2]; + io_rw_32 atrans[8]; // QMI_ATRANS0 +} qmi_hw_t; + +#define qmi_hw ((qmi_hw_t *)XIP_QMI_BASE) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_xip_cache/include/hardware/xip_cache.h + +// Noop unless using XIP Cache-as-SRAM +// Non-noop version in src/rp2_common/hardware_xip_cache/xip_cache.c +static inline void xip_cache_clean_all(void) {} + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/include/hardware/flash.h + +#define FLASH_PAGE_SIZE (1u << 8) +#define FLASH_SECTOR_SIZE (1u << 12) +#define FLASH_BLOCK_SIZE (1u << 16) + + +// https://github.com/raspberrypi/pico-sdk +// src/rp2_common/hardware_flash/flash.c + +#define BOOT2_SIZE_WORDS 64 +#define FLASH_BLOCK_ERASE_CMD 0xd8 + +static uint32_t boot2_copyout[BOOT2_SIZE_WORDS]; +static bool boot2_copyout_valid = false; + +static ram_func void flash_init_boot2_copyout(void) { + if (boot2_copyout_valid) + return; + for (int i = 0; i < BOOT2_SIZE_WORDS; ++i) + boot2_copyout[i] = ((uint32_t *)BOOTRAM_BASE)[i]; + __compiler_memory_barrier(); + boot2_copyout_valid = true; +} + +static ram_func void flash_enable_xip_via_boot2(void) { + ((void (*)(void))((intptr_t)boot2_copyout+1))(); +} + +// This is a static symbol because the layout of FLASH_DEVINFO is liable to change from device to +// device, so fields must have getters/setters. +static io_rw_16 * ram_func flash_devinfo_ptr(void) { + // Note the lookup returns a pointer to a 32-bit pointer literal in the ROM + io_rw_16 **p = (io_rw_16 **) rom_data_lookup_inline(ROM_DATA_FLASH_DEVINFO16_PTR); + return *p; +} + +// This is a RAM function because may be called during flash programming to enable save/restore of +// QMI window 1 registers on RP2350: +uint8_t ram_func flash_devinfo_get_cs_size(uint8_t cs) { + io_ro_16 *devinfo = (io_ro_16 *) flash_devinfo_ptr(); + if (cs == 0u) { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS0_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS0_SIZE_LSB + ); + } else { + return (uint8_t) ( + (*devinfo & OTP_DATA_FLASH_DEVINFO_CS1_SIZE_BITS) >> OTP_DATA_FLASH_DEVINFO_CS1_SIZE_LSB + ); + } +} + +// This is specifically for saving/restoring the registers modified by RP2350 +// flash_exit_xip() ROM func, not the entirety of the QMI window state. +typedef struct flash_rp2350_qmi_save_state { + uint32_t timing; + uint32_t rcmd; + uint32_t rfmt; +} flash_rp2350_qmi_save_state_t; + +static ram_func void flash_rp2350_save_qmi_cs1(flash_rp2350_qmi_save_state_t *state) { + state->timing = qmi_hw->m[1].timing; + state->rcmd = qmi_hw->m[1].rcmd; + state->rfmt = qmi_hw->m[1].rfmt; +} + +static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_state_t *state) { + if (flash_devinfo_get_cs_size(1) == 0) { + // Case 1: The RP2350 ROM sets QMI to a clean (03h read) configuration + // during flash_exit_xip(), even though when CS1 is not enabled via + // FLASH_DEVINFO it does not issue an XIP exit sequence to CS1. In + // this case, restore the original register config for CS1 as it is + // still the correct config. + qmi_hw->m[1].timing = state->timing; + qmi_hw->m[1].rcmd = state->rcmd; + qmi_hw->m[1].rfmt = state->rfmt; + } else { + // Case 2: If RAM is attached to CS1, and the ROM has issued an XIP + // exit sequence to it, then the ROM re-initialisation of the QMI + // registers has actually not gone far enough. The old XIP write mode + // is no longer valid when the QSPI RAM is returned to a serial + // command state. Restore the default 02h serial write command config. + qmi_hw->m[1].wfmt = QMI_M1_WFMT_RESET; + qmi_hw->m[1].wcmd = QMI_M1_WCMD_RESET; + } +} + +void ram_func flash_cs_force(bool high) { + uint32_t field_val = high ? + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH : + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW; + hw_write_masked(&io_qspi_hw->io[1].ctrl, + field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB, + IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS + ); +} + +// Adapted from flash_range_program() +void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_program_fn flash_range_program_func = (flash_range_program_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_PROGRAM); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_program_func(offset, data, count); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +// Adapted from flash_range_erase() +void ram_func flash_erase_blocks(uint32_t offset, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_range_erase_fn flash_range_erase_func = (flash_range_erase_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_RANGE_ERASE); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + // Commit any pending writes to external RAM, to avoid losing them in the subsequent flush: + xip_cache_clean_all(); + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + // No flash accesses after this point + __compiler_memory_barrier(); + + flash_connect_internal_func(); + flash_exit_xip_func(); + flash_range_erase_func(offset, count, FLASH_BLOCK_SIZE, FLASH_BLOCK_ERASE_CMD); + flash_flush_cache_func(); // Note this is needed to remove CSn IO force as well as cache flushing + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +void ram_func flash_do_cmd(const uint8_t *txbuf, uint8_t *rxbuf, size_t count) { + flash_connect_internal_fn flash_connect_internal_func = (flash_connect_internal_fn)rom_func_lookup_inline(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn)rom_func_lookup_inline(ROM_FUNC_FLASH_FLUSH_CACHE); + flash_init_boot2_copyout(); + xip_cache_clean_all(); + + flash_rp2350_qmi_save_state_t qmi_save; + flash_rp2350_save_qmi_cs1(&qmi_save); + + __compiler_memory_barrier(); + flash_connect_internal_func(); + flash_exit_xip_func(); + + flash_cs_force(0); + size_t tx_remaining = count; + size_t rx_remaining = count; + + // QMI version -- no need to bound FIFO contents as QMI stalls on full DIRECT_RX. + hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + while (tx_remaining || rx_remaining) { + uint32_t flags = qmi_hw->direct_csr; + bool can_put = !(flags & QMI_DIRECT_CSR_TXFULL_BITS); + bool can_get = !(flags & QMI_DIRECT_CSR_RXEMPTY_BITS); + if (can_put && tx_remaining) { + qmi_hw->direct_tx = *txbuf++; + --tx_remaining; + } + if (can_get && rx_remaining) { + *rxbuf++ = (uint8_t)qmi_hw->direct_rx; + --rx_remaining; + } + } + hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_EN_BITS); + + flash_cs_force(1); + + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); + flash_rp2350_restore_qmi_cs1(&qmi_save); +} + +*/ +import "C" + +func enterBootloader() { + C.reset_usb_boot(0, 0) +} + +func doFlashCommand(tx []byte, rx []byte) error { + if len(tx) != len(rx) { + return errFlashInvalidWriteLength + } + + C.flash_do_cmd( + (*C.uint8_t)(unsafe.Pointer(&tx[0])), + (*C.uint8_t)(unsafe.Pointer(&rx[0])), + C.ulong(len(tx))) + + return nil +} + +// Flash related code +const memoryStart = C.XIP_BASE // memory start for purpose of erase + +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { + if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { + return 0, errFlashCannotWritePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + // rp2350 writes to offset, not actual address + // e.g. real address 0x10003000 is written to at + // 0x00003000 + address := writeAddress(off) + padded := flashPad(p, int(f.WriteBlockSize())) + + C.flash_range_write(C.uint32_t(address), + (*C.uint8_t)(unsafe.Pointer(&padded[0])), + C.ulong(len(padded))) + + return len(padded), nil +} + +func (f flashBlockDevice) eraseBlocks(start, length int64) error { + address := writeAddress(start * f.EraseBlockSize()) + if address+uintptr(C.XIP_BASE) > FlashDataEnd() { + return errFlashCannotErasePastEOF + } + + state := interrupt.Disable() + defer interrupt.Restore(state) + + C.flash_erase_blocks(C.uint32_t(address), C.ulong(length*f.EraseBlockSize())) + + return nil +} diff --git a/src/machine/machine_rp2350_usb.go b/src/machine/machine_rp2350_usb.go new file mode 100644 index 0000000000..b42ce09cca --- /dev/null +++ b/src/machine/machine_rp2350_usb.go @@ -0,0 +1,381 @@ +//go:build rp2350 + +package machine + +import ( + "device/rp" + "machine/usb" + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +var ( + sendOnEP0DATADONE struct { + offset int + data []byte + pid uint32 + } +) + +// Configure the USB peripheral. The config is here for compatibility with the UART interface. +func (dev *USBDevice) Configure(config UARTConfig) { + // Reset usb controller + resetBlock(rp.RESETS_RESET_USBCTRL) + unresetBlockWait(rp.RESETS_RESET_USBCTRL) + + // Clear any previous state in dpram just in case + _usbDPSRAM.clear() + + // Enable USB interrupt at processor + rp.USB.INTE.Set(0) + intr := interrupt.New(rp.IRQ_USBCTRL_IRQ, handleUSBIRQ) + intr.SetPriority(0x00) + intr.Enable() + irqSet(rp.IRQ_USBCTRL_IRQ, true) + + // Mux the controller to the onboard usb phy + rp.USB.USB_MUXING.Set(rp.USB_USB_MUXING_TO_PHY | rp.USB_USB_MUXING_SOFTCON) + + // Force VBUS detect so the device thinks it is plugged into a host + rp.USB.USB_PWR.Set(rp.USB_USB_PWR_VBUS_DETECT | rp.USB_USB_PWR_VBUS_DETECT_OVERRIDE_EN) + + // Enable the USB controller in device mode. + rp.USB.MAIN_CTRL.Set(rp.USB_MAIN_CTRL_CONTROLLER_EN) + + // Enable an interrupt per EP0 transaction + rp.USB.SIE_CTRL.Set(rp.USB_SIE_CTRL_EP0_INT_1BUF) + + // Enable interrupts for when a buffer is done, when the bus is reset, + // and when a setup packet is received + rp.USB.INTE.Set(rp.USB_INTE_BUFF_STATUS | + rp.USB_INTE_BUS_RESET | + rp.USB_INTE_SETUP_REQ) + + // Present full speed device by enabling pull up on DP + rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN) + + // 12.7.2 Disable phy isolation + rp.USB.SetMAIN_CTRL_PHY_ISO(0x0) +} + +func handleUSBIRQ(intr interrupt.Interrupt) { + status := rp.USB.INTS.Get() + + // Setup packet received + if (status & rp.USB_INTS_SETUP_REQ) > 0 { + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_SETUP_REC) + setup := usb.NewSetup(_usbDPSRAM.setupBytes()) + + ok := false + if (setup.BmRequestType & usb.REQUEST_TYPE) == usb.REQUEST_STANDARD { + // Standard Requests + ok = handleStandardSetup(setup) + } else { + // Class Interface Requests + if setup.WIndex < uint16(len(usbSetupHandler)) && usbSetupHandler[setup.WIndex] != nil { + ok = usbSetupHandler[setup.WIndex](setup) + } + } + + if !ok { + // Stall endpoint? + sendStallViaEPIn(0) + } + + } + + // Buffer status, one or more buffers have completed + if (status & rp.USB_INTS_BUFF_STATUS) > 0 { + if sendOnEP0DATADONE.offset > 0 { + ep := uint32(0) + data := sendOnEP0DATADONE.data + count := len(data) - sendOnEP0DATADONE.offset + if ep == 0 && count > usb.EndpointPacketSize { + count = usb.EndpointPacketSize + } + + sendViaEPIn(ep, data[sendOnEP0DATADONE.offset:], count) + sendOnEP0DATADONE.offset += count + if sendOnEP0DATADONE.offset == len(data) { + sendOnEP0DATADONE.offset = 0 + } + } + + s2 := rp.USB.BUFF_STATUS.Get() + + // OUT (PC -> rp2040) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2+1)) > 0 { + buf := handleEndpointRx(uint32(i)) + if usbRxHandler[i] != nil { + usbRxHandler[i](buf) + } + handleEndpointRxComplete(uint32(i)) + } + } + + // IN (rp2040 -> PC) + for i := 0; i < 16; i++ { + if s2&(1<<(i*2)) > 0 { + if usbTxHandler[i] != nil { + usbTxHandler[i]() + } + } + } + + rp.USB.BUFF_STATUS.Set(s2) + } + + // Bus is reset + if (status & rp.USB_INTS_BUS_RESET) > 0 { + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_BUS_RESET) + //fixRP2040UsbDeviceEnumeration() + + rp.USB.ADDR_ENDP.Set(0) + initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) + } +} + +func initEndpoint(ep, config uint32) { + val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff) + offset := ep*2*usbBufferLen + 0x100 + val |= offset + + switch config { + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn: + val |= usbEpControlEndpointTypeInterrupt + _usbDPSRAM.EPxControl[ep].In.Set(val) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut: + val |= usbEpControlEndpointTypeBulk + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut: + val |= usbEpControlEndpointTypeInterrupt + _usbDPSRAM.EPxControl[ep].Out.Set(val) + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn: + val |= usbEpControlEndpointTypeBulk + _usbDPSRAM.EPxControl[ep].In.Set(val) + + case usb.ENDPOINT_TYPE_CONTROL: + val |= usbEpControlEndpointTypeControl + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + } +} + +func handleUSBSetAddress(setup usb.Setup) bool { + // Using 570μs timeout which is exactly the same as SAMD21. + + const ackTimeout = 570 + rp.USB.SIE_STATUS.Set(rp.USB_SIE_STATUS_ACK_REC) + sendUSBPacket(0, []byte{}, 0) + + // Wait for transfer to complete with a timeout. + t := timer.timeElapsed() + for (rp.USB.SIE_STATUS.Get() & rp.USB_SIE_STATUS_ACK_REC) == 0 { + if dt := timer.timeElapsed() - t; dt >= ackTimeout { + return false + } + } + + // Set the device address to that requested by host. + rp.USB.ADDR_ENDP.Set(uint32(setup.WValueL) & rp.USB_ADDR_ENDP_ADDRESS_Msk) + return true +} + +// SendUSBInPacket sends a packet for USB (interrupt in / bulk in). +func SendUSBInPacket(ep uint32, data []byte) bool { + sendUSBPacket(ep, data, 0) + return true +} + +//go:noinline +func sendUSBPacket(ep uint32, data []byte, maxsize uint16) { + count := len(data) + if 0 < int(maxsize) && int(maxsize) < count { + count = int(maxsize) + } + + if ep == 0 { + if count > usb.EndpointPacketSize { + count = usb.EndpointPacketSize + + sendOnEP0DATADONE.offset = count + sendOnEP0DATADONE.data = data + } else { + sendOnEP0DATADONE.offset = 0 + } + epXdata0[ep] = true + } + + sendViaEPIn(ep, data, count) +} + +func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) { + var b [cdcLineInfoSize]byte + ep := 0 + + for !_usbDPSRAM.EPxBufferControl[ep].Out.HasBits(usbBuf0CtrlFull) { + // TODO: timeout + } + + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + sz := ctrl & usbBuf0CtrlLenMask + + copy(b[:], _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz]) + + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) + + return b, nil +} + +func handleEndpointRx(ep uint32) []byte { + ctrl := _usbDPSRAM.EPxBufferControl[ep].Out.Get() + _usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask) + sz := ctrl & usbBuf0CtrlLenMask + + return _usbDPSRAM.EPxBuffer[ep].Buffer0[:sz] +} + +func handleEndpointRxComplete(ep uint32) { + epXdata0[ep] = !epXdata0[ep] + if epXdata0[ep] || ep == 0 { + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid) + } + + _usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail) +} + +func SendZlp() { + sendUSBPacket(0, []byte{}, 0) +} + +func sendViaEPIn(ep uint32, data []byte, count int) { + // Prepare buffer control register value + val := uint32(count) | usbBuf0CtrlAvail + + // DATA0 or DATA1 + epXdata0[ep&0x7F] = !epXdata0[ep&0x7F] + if !epXdata0[ep&0x7F] { + val |= usbBuf0CtrlData1Pid + } + + // Mark as full + val |= usbBuf0CtrlFull + + copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count]) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) +} + +func sendStallViaEPIn(ep uint32) { + // Prepare buffer control register value + if ep == 0 { + rp.USB.EP_STALL_ARM.Set(rp.USB_EP_STALL_ARM_EP0_IN) + } + val := uint32(usbBuf0CtrlFull) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) + val |= uint32(usbBuf0CtrlStall) + _usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val) +} + +type usbDPSRAM struct { + // Note that EPxControl[0] is not EP0Control but 8-byte setup data. + EPxControl [16]usbEndpointControlRegister + + EPxBufferControl [16]usbBufferControlRegister + + EPxBuffer [16]usbBuffer +} + +type usbEndpointControlRegister struct { + In volatile.Register32 + Out volatile.Register32 +} +type usbBufferControlRegister struct { + In volatile.Register32 + Out volatile.Register32 +} + +type usbBuffer struct { + Buffer0 [usbBufferLen]byte + Buffer1 [usbBufferLen]byte +} + +var ( + _usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000))) + epXdata0 [16]bool + setupBytes [8]byte +) + +func (d *usbDPSRAM) setupBytes() []byte { + + data := d.EPxControl[usb.CONTROL_ENDPOINT].In.Get() + setupBytes[0] = byte(data) + setupBytes[1] = byte(data >> 8) + setupBytes[2] = byte(data >> 16) + setupBytes[3] = byte(data >> 24) + + data = d.EPxControl[usb.CONTROL_ENDPOINT].Out.Get() + setupBytes[4] = byte(data) + setupBytes[5] = byte(data >> 8) + setupBytes[6] = byte(data >> 16) + setupBytes[7] = byte(data >> 24) + + return setupBytes[:] +} + +func (d *usbDPSRAM) clear() { + for i := 0; i < len(d.EPxControl); i++ { + d.EPxControl[i].In.Set(0) + d.EPxControl[i].Out.Set(0) + d.EPxBufferControl[i].In.Set(0) + d.EPxBufferControl[i].Out.Set(0) + } +} + +const ( + // DPRAM : Endpoint control register + usbEpControlEnable = 0x80000000 + usbEpControlDoubleBuffered = 0x40000000 + usbEpControlInterruptPerBuff = 0x20000000 + usbEpControlInterruptPerDoubleBuff = 0x10000000 + usbEpControlEndpointType = 0x0c000000 + usbEpControlInterruptOnStall = 0x00020000 + usbEpControlInterruptOnNak = 0x00010000 + usbEpControlBufferAddress = 0x0000ffff + + usbEpControlEndpointTypeControl = 0x00000000 + usbEpControlEndpointTypeISO = 0x04000000 + usbEpControlEndpointTypeBulk = 0x08000000 + usbEpControlEndpointTypeInterrupt = 0x0c000000 + + // Endpoint buffer control bits + usbBuf1CtrlFull = 0x80000000 + usbBuf1CtrlLast = 0x40000000 + usbBuf1CtrlData0Pid = 0x20000000 + usbBuf1CtrlData1Pid = 0x00000000 + usbBuf1CtrlSel = 0x10000000 + usbBuf1CtrlStall = 0x08000000 + usbBuf1CtrlAvail = 0x04000000 + usbBuf1CtrlLenMask = 0x03FF0000 + usbBuf0CtrlFull = 0x00008000 + usbBuf0CtrlLast = 0x00004000 + usbBuf0CtrlData0Pid = 0x00000000 + usbBuf0CtrlData1Pid = 0x00002000 + usbBuf0CtrlSel = 0x00001000 + usbBuf0CtrlStall = 0x00000800 + usbBuf0CtrlAvail = 0x00000400 + usbBuf0CtrlLenMask = 0x000003FF + + usbBufferLen = 64 +) diff --git a/src/machine/machine_rp2_2040.go b/src/machine/machine_rp2_2040.go new file mode 100644 index 0000000000..9cdb3a072e --- /dev/null +++ b/src/machine/machine_rp2_2040.go @@ -0,0 +1,223 @@ +//go:build rp2040 + +package machine + +import ( + "device/rp" + "runtime/volatile" + "unsafe" +) + +const ( + cpuFreq = 200 * MHz + _NUMBANK0_GPIOS = 30 + _NUMBANK0_IRQS = 4 + _NUMIRQ = 32 + rp2350ExtraReg = 0 + RESETS_RESET_Msk = 0x01ffffff + initUnreset = rp.RESETS_RESET_ADC | + rp.RESETS_RESET_RTC | + rp.RESETS_RESET_SPI0 | + rp.RESETS_RESET_SPI1 | + rp.RESETS_RESET_UART0 | + rp.RESETS_RESET_UART1 | + rp.RESETS_RESET_USBCTRL + initDontReset = rp.RESETS_RESET_IO_QSPI | + rp.RESETS_RESET_PADS_QSPI | + rp.RESETS_RESET_PLL_USB | + rp.RESETS_RESET_USBCTRL | + rp.RESETS_RESET_SYSCFG | + rp.RESETS_RESET_PLL_SYS + padEnableMask = rp.PADS_BANK0_GPIO0_IE_Msk | + rp.PADS_BANK0_GPIO0_OD_Msk +) + +const ( + PinOutput PinMode = iota + PinInput + PinInputPulldown + PinInputPullup + PinAnalog + PinUART + PinPWM + PinI2C + PinSPI + PinPIO0 + PinPIO1 +) + +// Analog pins on RP2040. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + thermADC = 30 +) + +const ( + clkGPOUT0 clockIndex = iota // GPIO Muxing 0 + clkGPOUT1 // GPIO Muxing 1 + clkGPOUT2 // GPIO Muxing 2 + clkGPOUT3 // GPIO Muxing 3 + clkRef // Watchdog and timers reference clock + clkSys // Processors, bus fabric, memory, memory mapped registers + clkPeri // Peripheral clock for UART and SPI + clkUSB // USB clock + clkADC // ADC clock + clkRTC // Real time clock + numClocks +) + +func calcClockDiv(srcFreq, freq uint32) uint32 { + // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) + return uint32((uint64(srcFreq) << 8) / uint64(freq)) +} + +type clocksType struct { + clk [numClocks]clockType + resus struct { + ctrl volatile.Register32 + status volatile.Register32 + } + fc0 fc + wakeEN0 volatile.Register32 + wakeEN1 volatile.Register32 + sleepEN0 volatile.Register32 + sleepEN1 volatile.Register32 + enabled0 volatile.Register32 + enabled1 volatile.Register32 + intR volatile.Register32 + intE volatile.Register32 + intF volatile.Register32 + intS volatile.Register32 +} + +// GPIO function selectors +const ( + fnJTAG pinFunc = 0 + fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO + fnUART pinFunc = 2 + fnI2C pinFunc = 3 + // Connect a PWM slice to GPIO. There are eight PWM slices, + // each with two outputchannels (A/B). The B pin can also be used as an input, + // for frequency and duty cyclemeasurement + fnPWM pinFunc = 4 + // Software control of GPIO, from the single-cycle IO (SIO) block. + // The SIO function (F5)must be selected for the processors to drive a GPIO, + // but the input is always connected,so software can check the state of GPIOs at any time. + fnSIO pinFunc = 5 + // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, + // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. + // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, + // so the PIOs canalways see the state of all pins. + fnPIO0, fnPIO1 pinFunc = 6, 7 + // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, + // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. + // e.g. Output: optional integer divide + fnGPCK pinFunc = 8 + // USB power control signals to/from the internal USB controller + fnUSB pinFunc = 9 + fnNULL pinFunc = 0x1f + + fnXIP pinFunc = 0 +) + +// Configure configures the gpio pin as per mode. +func (p Pin) Configure(config PinConfig) { + if p == NoPin { + return + } + p.init() + mask := uint32(1) << p + switch config.Mode { + case PinOutput: + p.setFunc(fnSIO) + rp.SIO.GPIO_OE_SET.Set(mask) + case PinInput: + p.setFunc(fnSIO) + p.pulloff() + case PinInputPulldown: + p.setFunc(fnSIO) + p.pulldown() + case PinInputPullup: + p.setFunc(fnSIO) + p.pullup() + case PinAnalog: + p.setFunc(fnNULL) + p.pulloff() + case PinUART: + p.setFunc(fnUART) + case PinPWM: + p.setFunc(fnPWM) + case PinI2C: + // IO config according to 4.3.1.3 of rp2040 datasheet. + p.setFunc(fnI2C) + p.pullup() + p.setSchmitt(true) + p.setSlew(false) + case PinSPI: + p.setFunc(fnSPI) + case PinPIO0: + p.setFunc(fnPIO0) + case PinPIO1: + p.setFunc(fnPIO1) + } +} + +var ( + timer = (*timerType)(unsafe.Pointer(rp.TIMER)) +) + +// Enable or disable a specific interrupt on the executing core. +// num is the interrupt number which must be in [0,31]. +func irqSet(num uint32, enabled bool) { + if num >= _NUMIRQ { + return + } + irqSetMask(1<= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OE_SET.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OE_SET.Set(mask) + } + case PinInput: + p.setFunc(fnSIO) + p.pulloff() + case PinInputPulldown: + p.setFunc(fnSIO) + p.pulldown() + case PinInputPullup: + p.setFunc(fnSIO) + p.pullup() + case PinAnalog: + p.setFunc(fnNULL) + p.pulloff() + case PinUART: + p.setFunc(fnUART) + case PinPWM: + p.setFunc(fnPWM) + case PinI2C: + // IO config according to 4.3.1.3 of rp2040 datasheet. + p.setFunc(fnI2C) + p.pullup() + p.setSchmitt(true) + p.setSlew(false) + case PinSPI: + p.setFunc(fnSPI) + case PinPIO0: + p.setFunc(fnPIO0) + case PinPIO1: + p.setFunc(fnPIO1) + case PinPIO2: + p.setFunc(fnPIO2) + } +} + +var ( + timer = (*timerType)(unsafe.Pointer(rp.TIMER0)) +) + +// Enable or disable a specific interrupt on the executing core. +// num is the interrupt number which must be in [0,_NUMIRQ). +func irqSet(num uint32, enabled bool) { + if num >= _NUMIRQ { + return + } + + register_index := num / 32 + var mask uint32 = 1 << (num % 32) + + if enabled { + // Clear pending before enable + //(if IRQ is actually asserted, it will immediately re-pend) + if register_index == 0 { + rp.PPB.NVIC_ICPR0.Set(mask) + rp.PPB.NVIC_ISER0.Set(mask) + } else { + rp.PPB.NVIC_ICPR1.Set(mask) + rp.PPB.NVIC_ISER1.Set(mask) + } + } else { + if register_index == 0 { + rp.PPB.NVIC_ICER0.Set(mask) + } else { + rp.PPB.NVIC_ICER1.Set(mask) + } + } +} + +func (clks *clocksType) initRTC() {} // No RTC on RP2350. + +func (clks *clocksType) initTicks() { + rp.TICKS.SetTIMER0_CTRL_ENABLE(0) + rp.TICKS.SetTIMER0_CYCLES(12) + rp.TICKS.SetTIMER0_CTRL_ENABLE(1) +} + +// startTick starts the watchdog tick. +// On RP2040, the watchdog contained a tick generator used to generate a 1μs tick for the watchdog. This was also +// distributed to the system timer. On RP2350, the watchdog instead takes a tick input from the system-level ticks block. See Section 8.5. +func (wd *watchdogImpl) startTick(cycles uint32) { + rp.TICKS.WATCHDOG_CTRL.SetBits(1) +} + +func adjustCoreVoltage() bool { + return false +} diff --git a/src/machine/machine_rp2_2350a.go b/src/machine/machine_rp2_2350a.go new file mode 100644 index 0000000000..09ec8a1190 --- /dev/null +++ b/src/machine/machine_rp2_2350a.go @@ -0,0 +1,14 @@ +//go:build rp2350 && !rp2350b + +package machine + +// Analog pins on RP2350a. +const ( + ADC0 Pin = GPIO26 + ADC1 Pin = GPIO27 + ADC2 Pin = GPIO28 + ADC3 Pin = GPIO29 + + // fifth ADC channel. + thermADC = 30 +) diff --git a/src/machine/machine_rp2_2350b.go b/src/machine/machine_rp2_2350b.go new file mode 100644 index 0000000000..bd5ceebe4c --- /dev/null +++ b/src/machine/machine_rp2_2350b.go @@ -0,0 +1,48 @@ +//go:build rp2350b + +package machine + +// RP2350B has additional pins. + +const ( + GPIO30 Pin = 30 // peripherals: PWM7 channel A + GPIO31 Pin = 31 // peripherals: PWM7 channel B + GPIO32 Pin = 32 // peripherals: PWM8 channel A + GPIO33 Pin = 33 // peripherals: PWM8 channel B + GPIO34 Pin = 34 // peripherals: PWM9 channel A + GPIO35 Pin = 35 // peripherals: PWM9 channel B + GPIO36 Pin = 36 // peripherals: PWM10 channel A + GPIO37 Pin = 37 // peripherals: PWM10 channel B + GPIO38 Pin = 38 // peripherals: PWM11 channel A + GPIO39 Pin = 39 // peripherals: PWM11 channel B + GPIO40 Pin = 40 // peripherals: PWM8 channel A + GPIO41 Pin = 41 // peripherals: PWM8 channel B + GPIO42 Pin = 42 // peripherals: PWM9 channel A + GPIO43 Pin = 43 // peripherals: PWM9 channel B + GPIO44 Pin = 44 // peripherals: PWM10 channel A + GPIO45 Pin = 45 // peripherals: PWM10 channel B + GPIO46 Pin = 46 // peripherals: PWM11 channel A + GPIO47 Pin = 47 // peripherals: PWM11 channel B +) + +// Analog pins on 2350b. +const ( + ADC0 Pin = GPIO40 + ADC1 Pin = GPIO41 + ADC2 Pin = GPIO42 + ADC3 Pin = GPIO43 + ADC4 Pin = GPIO44 + ADC5 Pin = GPIO45 + ADC6 Pin = GPIO46 + ADC7 Pin = GPIO47 + // Ninth ADC channel. + thermADC = 48 +) + +// Additional PWMs on the RP2350B. +var ( + PWM8 = getPWMGroup(8) + PWM9 = getPWMGroup(9) + PWM10 = getPWMGroup(10) + PWM11 = getPWMGroup(11) +) diff --git a/src/machine/machine_rp2040_adc.go b/src/machine/machine_rp2_adc.go similarity index 77% rename from src/machine/machine_rp2040_adc.go rename to src/machine/machine_rp2_adc.go index 1f05684ebc..fc9a8268e7 100644 --- a/src/machine/machine_rp2040_adc.go +++ b/src/machine/machine_rp2_adc.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -11,15 +11,6 @@ import ( // ADCChannel is the ADC peripheral mux channel. 0-4. type ADCChannel uint8 -// ADC channels. Only ADC_TEMP_SENSOR is public. The other channels are accessed via Machine.ADC objects -const ( - adc0_CH ADCChannel = iota - adc1_CH - adc2_CH - adc3_CH // Note: GPIO29 not broken out on pico board - adcTempSensor // Internal temperature sensor channel -) - // Used to serialise ADC sampling var adcLock sync.Mutex @@ -58,20 +49,10 @@ func (a ADC) Get() uint16 { // GetADCChannel returns the channel associated with the ADC pin. func (a ADC) GetADCChannel() (c ADCChannel, err error) { - err = nil - switch a.Pin { - case ADC0: - c = adc0_CH - case ADC1: - c = adc1_CH - case ADC2: - c = adc2_CH - case ADC3: - c = adc3_CH - default: - err = errors.New("no ADC channel for pin value") + if a.Pin < ADC0 { + return 0, errors.New("no ADC channel for pin value") } - return c, err + return ADCChannel(a.Pin - ADC0), nil } // Configure sets the channel's associated pin to analog input mode. @@ -113,12 +94,12 @@ func ReadTemperature() (millicelsius int32) { if rp.ADC.CS.Get()&rp.ADC_CS_EN == 0 { InitADC() } - + thermChan, _ := ADC{Pin: thermADC}.GetADCChannel() // Enable temperature sensor bias source rp.ADC.CS.SetBits(rp.ADC_CS_TS_EN) // T = 27 - (ADC_voltage - 0.706)/0.001721 - return (27000<<16 - (int32(adcTempSensor.getVoltage())-706<<16)*581) >> 16 + return (27000<<16 - (int32(thermChan.getVoltage())-706<<16)*581) >> 16 } // waitForReady spins waiting for the ADC peripheral to become ready. @@ -129,18 +110,5 @@ func waitForReady() { // The Pin method returns the GPIO Pin associated with the ADC mux channel, if it has one. func (c ADCChannel) Pin() (p Pin, err error) { - err = nil - switch c { - case adc0_CH: - p = ADC0 - case adc1_CH: - p = ADC1 - case adc2_CH: - p = ADC2 - case adc3_CH: - p = ADC3 - default: - err = errors.New("no associated pin for channel") - } - return p, err + return Pin(c) + ADC0, nil } diff --git a/src/machine/machine_rp2040_clocks.go b/src/machine/machine_rp2_clocks.go similarity index 72% rename from src/machine/machine_rp2040_clocks.go rename to src/machine/machine_rp2_clocks.go index 57dfa68b0b..dafebbe5d3 100644 --- a/src/machine/machine_rp2040_clocks.go +++ b/src/machine/machine_rp2_clocks.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -10,32 +10,12 @@ import ( ) func CPUFrequency() uint32 { - return 125 * MHz -} - -// Returns the period of a clock cycle for the raspberry pi pico in nanoseconds. -// Used in PWM API. -func cpuPeriod() uint32 { - return 1e9 / CPUFrequency() + return cpuFreq } // clockIndex identifies a hardware clock type clockIndex uint8 -const ( - clkGPOUT0 clockIndex = iota // GPIO Muxing 0 - clkGPOUT1 // GPIO Muxing 1 - clkGPOUT2 // GPIO Muxing 2 - clkGPOUT3 // GPIO Muxing 3 - clkRef // Watchdog and timers reference clock - clkSys // Processors, bus fabric, memory, memory mapped registers - clkPeri // Peripheral clock for UART and SPI - clkUSB // USB clock - clkADC // ADC clock - clkRTC // Real time clock - numClocks -) - type clockType struct { ctrl volatile.Register32 div volatile.Register32 @@ -53,25 +33,6 @@ type fc struct { result volatile.Register32 } -type clocksType struct { - clk [numClocks]clockType - resus struct { - ctrl volatile.Register32 - status volatile.Register32 - } - fc0 fc - wakeEN0 volatile.Register32 - wakeEN1 volatile.Register32 - sleepEN0 volatile.Register32 - sleepEN1 volatile.Register32 - enabled0 volatile.Register32 - enabled1 volatile.Register32 - intR volatile.Register32 - intE volatile.Register32 - intF volatile.Register32 - intS volatile.Register32 -} - var clocks = (*clocksType)(unsafe.Pointer(rp.CLOCKS)) var configuredFreq [numClocks]uint32 @@ -81,6 +42,10 @@ type clock struct { cix clockIndex } +// The delay in seconds for core voltage adjustments to +// settle. Taken from the Pico SDK. +const _VREG_VOLTAGE_AUTO_ADJUST_DELAY = 1 / 1e3 + // clock returns the clock identified by cix. func (clks *clocksType) clock(cix clockIndex) clock { return clock{ @@ -113,8 +78,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { panic("clock frequency cannot be greater than source frequency") } - // Div register is 24.8 int.frac divider so multiply by 2^8 (left shift by 8) - div := uint32((uint64(srcFreq) << 8) / uint64(freq)) + div := calcClockDiv(srcFreq, freq) // If increasing divisor, set divisor before source. Otherwise set source // before divisor. This avoids a momentary overspeed when e.g. switching @@ -135,7 +99,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { // propagating when changing aux mux. Note it would be a really bad idea // to do this on one of the glitchless clocks (clkSys, clkRef). { - // Disable clock. On clkRef and clkSys this does nothing, + // Disable clock. On clkRef and ClkSys this does nothing, // all other clocks have the ENABLE bit in the same position. clk.ctrl.ClearBits(rp.CLOCKS_CLK_GPOUT0_CTRL_ENABLE_Msk) if configuredFreq[clk.cix] > 0 { @@ -177,6 +141,20 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { } +var pllsysFB, pllsysPD1, pllsysPD2 uint32 + +// Compute clock dividers. +// +// Note that the entire init function is computed at compile time +// by interp. +func init() { + fb, _, pd1, pd2, err := pllSearch{LockRefDiv: 1}.CalcDivs(xoscFreq*MHz, cpuFreq, MHz) + if err != nil { + panic(err) + } + pllsysFB, pllsysPD1, pllsysPD2 = uint32(fb), uint32(pd1), uint32(pd2) +} + // init initializes the clock hardware. // // Must be called before any other clock function. @@ -185,7 +163,7 @@ func (clks *clocksType) init() { Watchdog.startTick(xoscFreq) // Disable resus that may be enabled from previous software - clks.resus.ctrl.Set(0) + rp.CLOCKS.SetCLK_SYS_RESUS_CTRL_CLEAR(0) // Enable the xosc xosc.init() @@ -203,51 +181,56 @@ func (clks *clocksType) init() { // REF FBDIV VCO POSTDIV // pllSys: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz // pllUSB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz - pllSys.init(1, 1500*MHz, 6, 2) - pllUSB.init(1, 480*MHz, 5, 2) + pllSys.init(1, pllsysFB, pllsysPD1, pllsysPD2) + pllUSB.init(1, 40, 5, 2) // Configure clocks // clkRef = xosc (12MHz) / 1 = 12MHz - clkref := clks.clock(clkRef) - clkref.configure(rp.CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC, + cref := clks.clock(clkRef) + cref.configure(rp.CLOCKS_CLK_REF_CTRL_SRC_XOSC_CLKSRC, 0, // No aux mux - 12*MHz, - 12*MHz) + xoscFreq, + xoscFreq) + + if adjustCoreVoltage() { + // Wait for the voltage to settle. + const cycles = _VREG_VOLTAGE_AUTO_ADJUST_DELAY * xoscFreq * MHz + for i := 0; i < cycles; i++ { + arm.Asm("nop") + } + } // clkSys = pllSys (125MHz) / 1 = 125MHz - clksys := clks.clock(clkSys) - clksys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, + csys := clks.clock(clkSys) + csys.configure(rp.CLOCKS_CLK_SYS_CTRL_SRC_CLKSRC_CLK_SYS_AUX, rp.CLOCKS_CLK_SYS_CTRL_AUXSRC_CLKSRC_PLL_SYS, - 125*MHz, - 125*MHz) + cpuFreq, + cpuFreq) // clkUSB = pllUSB (48MHz) / 1 = 48MHz - clkusb := clks.clock(clkUSB) - clkusb.configure(0, // No GLMUX + cusb := clks.clock(clkUSB) + cusb.configure(0, // No GLMUX rp.CLOCKS_CLK_USB_CTRL_AUXSRC_CLKSRC_PLL_USB, 48*MHz, 48*MHz) // clkADC = pllUSB (48MHZ) / 1 = 48MHz - clkadc := clks.clock(clkADC) - clkadc.configure(0, // No GLMUX + cadc := clks.clock(clkADC) + cadc.configure(0, // No GLMUX rp.CLOCKS_CLK_ADC_CTRL_AUXSRC_CLKSRC_PLL_USB, 48*MHz, 48*MHz) - // clkRTC = pllUSB (48MHz) / 1024 = 46875Hz - clkrtc := clks.clock(clkRTC) - clkrtc.configure(0, // No GLMUX - rp.CLOCKS_CLK_RTC_CTRL_AUXSRC_CLKSRC_PLL_USB, - 48*MHz, - 46875) + clks.initRTC() // clkPeri = clkSys. Used as reference clock for Peripherals. // No dividers so just select and enable. // Normally choose clkSys or clkUSB. - clkperi := clks.clock(clkPeri) - clkperi.configure(0, + cperi := clks.clock(clkPeri) + cperi.configure(0, rp.CLOCKS_CLK_PERI_CTRL_AUXSRC_CLK_SYS, - 125*MHz, - 125*MHz) + cpuFreq, + cpuFreq) + + clks.initTicks() } diff --git a/src/machine/machine_rp2040_flash.go b/src/machine/machine_rp2_flash.go similarity index 90% rename from src/machine/machine_rp2040_flash.go rename to src/machine/machine_rp2_flash.go index 8ee881e19a..45263dfac3 100644 --- a/src/machine/machine_rp2040_flash.go +++ b/src/machine/machine_rp2_flash.go @@ -1,9 +1,8 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine import ( - "bytes" "unsafe" ) @@ -101,17 +100,6 @@ func (f flashBlockDevice) EraseBlocks(start, length int64) error { return f.eraseBlocks(start, length) } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - // return the correct address to be used for write func writeAddress(off int64) uintptr { return readAddress(off) - uintptr(memoryStart) diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2_gpio.go similarity index 66% rename from src/machine/machine_rp2040_gpio.go rename to src/machine/machine_rp2_gpio.go index 86cb09eb9c..25d76261fe 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2_gpio.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -15,14 +15,27 @@ type ioType struct { } type irqCtrl struct { - intE [4]volatile.Register32 - intF [4]volatile.Register32 - intS [4]volatile.Register32 + intE [_NUMBANK0_IRQS]volatile.Register32 + intF [_NUMBANK0_IRQS]volatile.Register32 + intS [_NUMBANK0_IRQS]volatile.Register32 +} + +type irqSummary struct { + proc [2]struct { + secure [2]volatile.Register32 + nonsecure [2]volatile.Register32 + } + comaWake struct { + secure [2]volatile.Register32 + nonsecure [2]volatile.Register32 + } } type ioBank0Type struct { - io [30]ioType - intR [4]volatile.Register32 + io [_NUMBANK0_GPIOS]ioType + _ [rp2350ExtraReg][128]byte + irqsum [rp2350ExtraReg]irqSummary + intR [_NUMBANK0_IRQS]volatile.Register32 proc0IRQctrl irqCtrl proc1IRQctrl irqCtrl dormantWakeIRQctrl irqCtrl @@ -32,7 +45,7 @@ var ioBank0 = (*ioBank0Type)(unsafe.Pointer(rp.IO_BANK0)) type padsBank0Type struct { voltageSelect volatile.Register32 - io [30]volatile.Register32 + io [_NUMBANK0_GPIOS]volatile.Register32 } var padsBank0 = (*padsBank0Type)(unsafe.Pointer(rp.PADS_BANK0)) @@ -45,58 +58,19 @@ var padsBank0 = (*padsBank0Type)(unsafe.Pointer(rp.PADS_BANK0)) // the peripheral sees the logical OR of these GPIO inputs. type pinFunc uint8 -// GPIO function selectors -const ( - fnJTAG pinFunc = 0 - fnSPI pinFunc = 1 // Connect one of the internal PL022 SPI peripherals to GPIO - fnUART pinFunc = 2 - fnI2C pinFunc = 3 - // Connect a PWM slice to GPIO. There are eight PWM slices, - // each with two outputchannels (A/B). The B pin can also be used as an input, - // for frequency and duty cyclemeasurement - fnPWM pinFunc = 4 - // Software control of GPIO, from the single-cycle IO (SIO) block. - // The SIO function (F5)must be selected for the processors to drive a GPIO, - // but the input is always connected,so software can check the state of GPIOs at any time. - fnSIO pinFunc = 5 - // Connect one of the programmable IO blocks (PIO) to GPIO. PIO can implement a widevariety of interfaces, - // and has its own internal pin mapping hardware, allowing flexibleplacement of digital interfaces on bank 0 GPIOs. - // The PIO function (F6, F7) must beselected for PIO to drive a GPIO, but the input is always connected, - // so the PIOs canalways see the state of all pins. - fnPIO0, fnPIO1 pinFunc = 6, 7 - // General purpose clock inputs/outputs. Can be routed to a number of internal clock domains onRP2040, - // e.g. Input: to provide a 1 Hz clock for the RTC, or can be connected to an internalfrequency counter. - // e.g. Output: optional integer divide - fnGPCK pinFunc = 8 - // USB power control signals to/from the internal USB controller - fnUSB pinFunc = 9 - fnNULL pinFunc = 0x1f - - fnXIP pinFunc = 0 -) - -const ( - PinOutput PinMode = iota - PinInput - PinInputPulldown - PinInputPullup - PinAnalog - PinUART - PinPWM - PinI2C - PinSPI - PinPIO0 - PinPIO1 -) - func (p Pin) PortMaskSet() (*uint32, uint32) { return (*uint32)(unsafe.Pointer(&rp.SIO.GPIO_OUT_SET)), 1 << p } // set drives the pin high func (p Pin) set() { - mask := uint32(1) << p - rp.SIO.GPIO_OUT_SET.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_SET.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_SET.Set(mask) + } } func (p Pin) PortMaskClear() (*uint32, uint32) { @@ -105,18 +79,31 @@ func (p Pin) PortMaskClear() (*uint32, uint32) { // clr drives the pin low func (p Pin) clr() { - mask := uint32(1) << p - rp.SIO.GPIO_OUT_CLR.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_CLR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_CLR.Set(mask) + } } // xor toggles the pin func (p Pin) xor() { - mask := uint32(1) << p - rp.SIO.GPIO_OUT_XOR.Set(mask) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OUT_XOR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OUT_XOR.Set(mask) + } } // get returns the pin value func (p Pin) get() bool { + if is48Pin && p >= 32 { + return rp.SIO.GPIO_HI_IN.HasBits(1 << (p % 32)) + } return rp.SIO.GPIO_IN.HasBits(1 << p) } @@ -157,8 +144,7 @@ func (p Pin) setSchmitt(trigger bool) { // setFunc will set pin function to fn. func (p Pin) setFunc(fn pinFunc) { // Set input enable, Clear output disable - p.padCtrl().ReplaceBits(rp.PADS_BANK0_GPIO0_IE, - rp.PADS_BANK0_GPIO0_IE_Msk|rp.PADS_BANK0_GPIO0_OD_Msk, 0) + p.padCtrl().ReplaceBits(rp.PADS_BANK0_GPIO0_IE, padEnableMask, 0) // Zero all fields apart from fsel; we want this IO to do what the peripheral tells it. // This doesn't affect e.g. pullup/pulldown, as these are in pad controls. @@ -167,51 +153,14 @@ func (p Pin) setFunc(fn pinFunc) { // init initializes the gpio pin func (p Pin) init() { - mask := uint32(1) << p - rp.SIO.GPIO_OE_CLR.Set(mask) - p.clr() -} - -// Configure configures the gpio pin as per mode. -func (p Pin) Configure(config PinConfig) { - if p == NoPin { - return - } - p.init() - mask := uint32(1) << p - switch config.Mode { - case PinOutput: - p.setFunc(fnSIO) - rp.SIO.GPIO_OE_SET.Set(mask) - case PinInput: - p.setFunc(fnSIO) - p.pulloff() - case PinInputPulldown: - p.setFunc(fnSIO) - p.pulldown() - case PinInputPullup: - p.setFunc(fnSIO) - p.pullup() - case PinAnalog: - p.setFunc(fnNULL) - p.pulloff() - case PinUART: - p.setFunc(fnUART) - case PinPWM: - p.setFunc(fnPWM) - case PinI2C: - // IO config according to 4.3.1.3 of rp2040 datasheet. - p.setFunc(fnI2C) - p.pullup() - p.setSchmitt(true) - p.setSlew(false) - case PinSPI: - p.setFunc(fnSPI) - case PinPIO0: - p.setFunc(fnPIO0) - case PinPIO1: - p.setFunc(fnPIO1) + if is48Pin && p >= 32 { + mask := uint32(1) << (p % 32) + rp.SIO.GPIO_HI_OE_CLR.Set(mask) + } else { + mask := uint32(1) << p + rp.SIO.GPIO_OE_CLR.Set(mask) } + p.clr() } // Set drives the pin high if value is true else drives it low. diff --git a/src/machine/machine_rp2040_i2c.go b/src/machine/machine_rp2_i2c.go similarity index 95% rename from src/machine/machine_rp2040_i2c.go rename to src/machine/machine_rp2_i2c.go index d51e14b8f2..2552eb94e6 100644 --- a/src/machine/machine_rp2040_i2c.go +++ b/src/machine/machine_rp2_i2c.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -8,7 +8,7 @@ import ( "internal/itoa" ) -// I2C on the RP2040. +// I2C on the RP2040/RP2350 var ( I2C0 = &_I2C0 _I2C0 = I2C{ @@ -24,7 +24,7 @@ var ( // here: https://github.com/vmilea/pico_i2c_slave // Features: Taken from datasheet. -// Default controller mode, with target mode available (not simulataneously). +// Default controller mode, with target mode available (not simultaneously). // Default target address of RP2040: 0x055 // Supports 10-bit addressing in controller mode // 16-element transmit buffer @@ -36,7 +36,7 @@ var ( // GPIO config // Each controller must connect its clock SCL and data SDA to one pair of GPIOs. // The I2C standard requires that drivers drivea signal low, or when not driven the signal will be pulled high. -// This applies to SCL and SDA. The GPIO pads should beconfigured for: +// This applies to SCL and SDA. The GPIO pads should be configured for: // Pull-up enabled // Slew rate limited // Schmitt trigger enabled @@ -348,7 +348,7 @@ func (i2c *I2C) tx(addr uint8, tx, rx []byte, timeout_us uint64) (err error) { } if abort || last { // If the transaction was aborted or if it completed - // successfully wait until the STOP condition has occured. + // successfully wait until the STOP condition has occurred. // TODO Could there be an abort while waiting for the STOP // condition here? If so, additional code would be needed here @@ -501,15 +501,23 @@ func (i2c *I2C) Reply(buf []byte) error { } for txPtr < len(buf) { - if stat&rp.I2C0_IC_INTR_MASK_M_TX_EMPTY != 0 { - i2c.Bus.IC_DATA_CMD.Set(uint32(buf[txPtr])) + if i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() != 0 { + i2c.Bus.SetIC_DATA_CMD_DAT(uint32(buf[txPtr])) txPtr++ + // The DW_apb_i2c flushes/resets/empties the + // TX_FIFO and RX_FIFO whenever there is a transmit abort + // caused by any of the events tracked by the + // IC_TX_ABRT_SOURCE register. + // In other words, it's safe to block until TX FIFO is + // EMPTY--it will empty from being transmitted or on error. + for i2c.Bus.GetIC_RAW_INTR_STAT_TX_EMPTY() == 0 { + } } // This Tx abort is a normal case - we're sending more // data than controller wants to receive - if stat&rp.I2C0_IC_INTR_MASK_M_TX_ABRT != 0 { - i2c.Bus.IC_CLR_TX_ABRT.Get() + if i2c.Bus.GetIC_RAW_INTR_STAT_TX_ABRT() != 0 { + i2c.Bus.GetIC_CLR_TX_ABRT_CLR_TX_ABRT() return nil } @@ -623,24 +631,3 @@ func (b i2cAbortError) Reasons() (reasons []string) { } return reasons } - -//go:inline -func boolToBit(a bool) uint32 { - if a { - return 1 - } - return 0 -} - -//go:inline -func u32max(a, b uint32) uint32 { - if a > b { - return a - } - return b -} - -//go:inline -func isReservedI2CAddr(addr uint8) bool { - return (addr&0x78) == 0 || (addr&0x78) == 0x78 -} diff --git a/src/machine/machine_rp2_pins.go b/src/machine/machine_rp2_pins.go new file mode 100644 index 0000000000..36e9bd629c --- /dev/null +++ b/src/machine/machine_rp2_pins.go @@ -0,0 +1,37 @@ +//go:build rp2040 || rp2350 || gopher_badge || pico + +package machine + +const ( + // GPIO pins + GPIO0 Pin = 0 // peripherals: PWM0 channel A + GPIO1 Pin = 1 // peripherals: PWM0 channel B + GPIO2 Pin = 2 // peripherals: PWM1 channel A + GPIO3 Pin = 3 // peripherals: PWM1 channel B + GPIO4 Pin = 4 // peripherals: PWM2 channel A + GPIO5 Pin = 5 // peripherals: PWM2 channel B + GPIO6 Pin = 6 // peripherals: PWM3 channel A + GPIO7 Pin = 7 // peripherals: PWM3 channel B + GPIO8 Pin = 8 // peripherals: PWM4 channel A + GPIO9 Pin = 9 // peripherals: PWM4 channel B + GPIO10 Pin = 10 // peripherals: PWM5 channel A + GPIO11 Pin = 11 // peripherals: PWM5 channel B + GPIO12 Pin = 12 // peripherals: PWM6 channel A + GPIO13 Pin = 13 // peripherals: PWM6 channel B + GPIO14 Pin = 14 // peripherals: PWM7 channel A + GPIO15 Pin = 15 // peripherals: PWM7 channel B + GPIO16 Pin = 16 // peripherals: PWM0 channel A + GPIO17 Pin = 17 // peripherals: PWM0 channel B + GPIO18 Pin = 18 // peripherals: PWM1 channel A + GPIO19 Pin = 19 // peripherals: PWM1 channel B + GPIO20 Pin = 20 // peripherals: PWM2 channel A + GPIO21 Pin = 21 // peripherals: PWM2 channel B + GPIO22 Pin = 22 // peripherals: PWM3 channel A + GPIO23 Pin = 23 // peripherals: PWM3 channel B + GPIO24 Pin = 24 // peripherals: PWM4 channel A + GPIO25 Pin = 25 // peripherals: PWM4 channel B + GPIO26 Pin = 26 // peripherals: PWM5 channel A + GPIO27 Pin = 27 // peripherals: PWM5 channel B + GPIO28 Pin = 28 // peripherals: PWM6 channel A + GPIO29 Pin = 29 // peripherals: PWM6 channel B +) diff --git a/src/machine/machine_rp2_pll.go b/src/machine/machine_rp2_pll.go new file mode 100644 index 0000000000..d5760842ba --- /dev/null +++ b/src/machine/machine_rp2_pll.go @@ -0,0 +1,279 @@ +//go:build rp2040 || rp2350 + +package machine + +import ( + "device/rp" + "errors" + "math" + "math/bits" + "runtime/volatile" + "unsafe" +) + +type pll struct { + cs volatile.Register32 + pwr volatile.Register32 + fbDivInt volatile.Register32 + prim volatile.Register32 +} + +var ( + pllSys = (*pll)(unsafe.Pointer(rp.PLL_SYS)) + pllUSB = (*pll)(unsafe.Pointer(rp.PLL_USB)) +) + +// init initializes pll (Sys or USB) given the following parameters. +// +// Input clock divider, refdiv. +// +// Requested output frequency from the VCO (voltage controlled oscillator), vcoFreq. +// +// Post Divider 1, postDiv1 with range 1-7 and be >= postDiv2. +// +// Post Divider 2, postDiv2 with range 1-7. +func (pll *pll) init(refdiv, fbdiv, postDiv1, postDiv2 uint32) { + refFreq := xoscFreq / refdiv + + // What are we multiplying the reference clock by to get the vco freq + // (The regs are called div, because you divide the vco output and compare it to the refclk) + + // Check fbdiv range + if !(fbdiv >= 16 && fbdiv <= 320) { + panic("fbdiv should be in the range [16,320]") + } + + // Check divider ranges + if !((postDiv1 >= 1 && postDiv1 <= 7) && (postDiv2 >= 1 && postDiv2 <= 7)) { + panic("postdiv1, postdiv1 should be in the range [1,7]") + } + + // postDiv1 should be >= postDiv2 + // from appnote page 11 + // postdiv1 is designed to operate with a higher input frequency + // than postdiv2 + if postDiv1 < postDiv2 { + panic("postdiv1 should be greater than or equal to postdiv2") + } + + // Check that reference frequency is no greater than vcoFreq / 16 + vcoFreq := calcVCO(xoscFreq, fbdiv, refdiv) + if refFreq > vcoFreq/16 { + panic("reference frequency should not be greater than vco frequency divided by 16") + } + + // div1 feeds into div2 so if div1 is 5 and div2 is 2 then you get a divide by 10 + pdiv := uint32(postDiv1)< maxVCO { + break + } + calcPD12 := vco / targetFreq + if calcPD12 < 1 { + calcPD12 = 1 + } else if calcPD12 > 49 { + calcPD12 = 49 + } + iters++ + pd1 = pdTable[calcPD12].hivco[0] + pd2 = pdTable[calcPD12].hivco[1] + fout, err := pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + found := false + margin := abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + pd1 = pdTable[calcPD12].lovco[0] + pd2 = pdTable[calcPD12].lovco[1] + fout, err = pllFreqOutPostdiv(xoscRef, fbdiv, MHz, refdiv, pd1, pd2) + margin = abs(int64(fout) - int64(targetFreq)) + if err == nil && margin <= bestMargin { + found = true + bestFreq = fout + bestFbdiv = fbdiv + bestpd1 = pd1 + bestpd2 = pd2 + bestRefdiv = refdiv + bestMargin = margin + } + if found && ps.LowerVCO { + break + } + } + } + if bestFreq == 0 { + return fbdiv, refdiv, pd1, pd2, errors.New("no best frequency found") + } + return bestFbdiv, bestRefdiv, bestpd1, bestpd2, nil +} + +func abs(a int64) int64 { + if a == math.MinInt64 { + return math.MaxInt64 + } else if a < 0 { + return -a + } + return a +} + +func pllFreqOutPostdiv(xosc, fbdiv, MHz uint64, refdiv, postdiv1, postdiv2 uint8) (foutpostdiv uint64, err error) { + // testing grounds. + const ( + mhz = 1 + cfref = 12 * mhz // given by crystal oscillator selection. + crefd = 1 + cfbdiv = 100 + cvco = cfref * cfbdiv / crefd + cpd1 = 6 + cpd2 = 2 + foutpd = (cfref / crefd) * cfbdiv / (cpd1 * cpd2) + ) + refFreq := xosc / uint64(refdiv) + overflow, vco := bits.Mul64(xosc, fbdiv) + vco /= uint64(refdiv) + foutpostdiv = vco / uint64(postdiv1*postdiv2) + switch { + case refdiv < 1 || refdiv > 63: + err = errors.New("reference divider out of range") + case fbdiv < 16 || fbdiv > 320: + err = errors.New("feedback divider out of range") + case postdiv1 < 1 || postdiv1 > 7: + err = errors.New("postdiv1 out of range") + case postdiv2 < 1 || postdiv2 > 7: + err = errors.New("postdiv2 out of range") + case postdiv1 < postdiv2: + err = errors.New("user error: use higher value for postdiv1 for lower power consumption") + case vco < 750*MHz || vco > 1600*MHz: + err = errors.New("VCO out of range") + case refFreq < 5*MHz: + err = errors.New("minimum reference frequency breach") + case refFreq > vco/16: + err = errors.New("maximum reference frequency breach") + case vco > 1200*MHz && vco < 1600*MHz && xosc < 75*MHz && refdiv != 1: + err = errors.New("refdiv should be 1 for given VCO and reference frequency") + case overflow != 0: + err = errVCOOverflow + } + if err != nil { + return 0, err + } + return foutpostdiv, nil +} + +func calcVCO(xoscFreq, fbdiv, refdiv uint32) uint32 { + const maxXoscMHz = math.MaxUint32 / 320 / MHz // 13MHz maximum xosc apparently. + if fbdiv > 320 || xoscFreq > math.MaxUint32/320 { + panic("invalid VCO calculation args") + } + return xoscFreq * fbdiv / refdiv +} + +var pdTable = [50]struct { + hivco [2]uint8 + lovco [2]uint8 +}{} + +func genTable() { + if pdTable[1].hivco[1] != 0 { + return // Already generated. + } + for product := 1; product < len(pdTable); product++ { + bestProdhi := 255 + bestProdlo := 255 + for pd1 := 7; pd1 > 0; pd1-- { + for pd2 := pd1; pd2 > 0; pd2-- { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdlo-product)) { + bestProdlo = gotprod + pdTable[product].lovco[0] = uint8(pd1) + pdTable[product].lovco[1] = uint8(pd2) + } + } + } + for pd1 := 1; pd1 < 8; pd1++ { + for pd2 := 1; pd2 <= pd1; pd2++ { + gotprod := pd1 * pd2 + if abs(int64(gotprod-product)) < abs(int64(bestProdhi-product)) { + bestProdhi = gotprod + pdTable[product].hivco[0] = uint8(pd1) + pdTable[product].hivco[1] = uint8(pd2) + } + } + } + } +} diff --git a/src/machine/machine_rp2040_pwm.go b/src/machine/machine_rp2_pwm.go similarity index 92% rename from src/machine/machine_rp2040_pwm.go rename to src/machine/machine_rp2_pwm.go index f72d6dd031..772811e368 100644 --- a/src/machine/machine_rp2040_pwm.go +++ b/src/machine/machine_rp2_pwm.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -15,7 +15,7 @@ var ( ) const ( - maxPWMPins = 29 + maxPWMPins = _NUMBANK0_GPIOS - 1 ) // pwmGroup is one PWM peripheral, which consists of a counter and two output @@ -146,12 +146,14 @@ func (p *pwmGroup) Counter() uint32 { // Period returns the used PWM period in nanoseconds. func (p *pwmGroup) Period() uint64 { - periodPerCycle := cpuPeriod() + // Lines below can overflow if operations done without care. + // maxInt=255, maxFrac=15, maxTop=65536, maxPHC=1 => maxProduct= (16*255+15) * (65536*2*1e9) = 5.3673e17 < MaxUint64=1.8e19 (close call.) + const compileTimeCheckPeriod uint64 = (255*16 + 15) * (65535 + 1) * 2 * 1e9 + freq := uint64(CPUFrequency()) top := p.getWrap() phc := p.getPhaseCorrect() Int, frac := p.getClockDiv() - // Line below can overflow if operations done without care. - return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*periodPerCycle) / 16 // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) + return (16*uint64(Int) + uint64(frac)) * uint64((top+1)*(phc+1)*1e9) / (16 * freq) // cycles = (TOP+1) * (CSRPHCorrect + 1) * (DIV_INT + DIV_FRAC/16) } // SetInverting sets whether to invert the output of this channel. @@ -266,6 +268,9 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // Maximum Period is 268369920ns on rp2040, given by (16*255+15)*8*(1+0xffff)*(1+1)/16 // With no phase shift max period is half of this value. maxPeriod = 268 * milliseconds + // This will be a compile time error if this method is at risk of overflowing. cpufreq=155MHz for typical RP2350. + maxCPUFreq = 4 * GHz // Can go up to 4GHz without overflowing :) + compileTimeCheckSetPeriod uint64 = 16 * maxPeriod * maxCPUFreq ) if period > maxPeriod || period < 8 { @@ -279,11 +284,13 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // DIV_INT + DIV_FRAC/16 = cycles / ( (TOP+1) * (CSRPHCorrect+1) ) // DIV_FRAC/16 is always 0 in this equation // where cycles must be converted to time: // target_period = cycles * period_per_cycle ==> cycles = target_period/period_per_cycle - periodPerCycle := uint64(cpuPeriod()) - phc := uint64(pwm.getPhaseCorrect()) - rhs := 16 * period / ((1 + phc) * periodPerCycle * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided - whole := rhs / 16 - frac := rhs % 16 + var ( + freq = uint64(CPUFrequency()) + phc = uint64(pwm.getPhaseCorrect()) + rhs = 16 * period * freq / ((1 + phc) * 1e9 * (1 + topStart)) // right-hand-side of equation, scaled so frac is not divided + whole = rhs / 16 + frac = rhs % 16 + ) switch { case whole > 0xff: whole = 0xff @@ -296,7 +303,7 @@ func (pwm *pwmGroup) setPeriod(period uint64) error { // Step 2 is acquiring a better top value. Clearing the equation: // TOP = cycles / ( (DIVINT+DIVFRAC/16) * (CSRPHCorrect+1) ) - 1 - top := 16*period/((16*whole+frac)*periodPerCycle*(1+phc)) - 1 + top := 16*period*freq/((1+phc)*1e9*(16*whole+frac)) - 1 if top > maxTop { top = maxTop } @@ -400,6 +407,9 @@ func (pwm *pwmGroup) getClockDiv() (Int, frac uint8) { // pwmGPIOToSlice Determine the PWM channel that is attached to the specified GPIO. // gpio must be less than 30. Returns the PWM slice number that controls the specified GPIO. func pwmGPIOToSlice(gpio Pin) (slicenum uint8) { + if is48Pin && gpio >= 32 { + return uint8(8 + ((gpio-32)/2)%4) + } return (uint8(gpio) >> 1) & 7 } diff --git a/src/machine/machine_rp2040_resets.go b/src/machine/machine_rp2_resets.go similarity index 51% rename from src/machine/machine_rp2040_resets.go rename to src/machine/machine_rp2_resets.go index 6f15e99528..245436c47c 100644 --- a/src/machine/machine_rp2040_resets.go +++ b/src/machine/machine_rp2_resets.go @@ -1,36 +1,24 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine import ( "device/rp" - "runtime/volatile" "unsafe" ) -// RESETS_RESET_Msk is bitmask to reset all peripherals -// -// TODO: This field is not available in the device file. -const RESETS_RESET_Msk = 0x01ffffff - -type resetsType struct { - reset volatile.Register32 - wdSel volatile.Register32 - resetDone volatile.Register32 -} - -var resets = (*resetsType)(unsafe.Pointer(rp.RESETS)) +var resets = (*rp.RESETS_Type)(unsafe.Pointer(rp.RESETS)) // resetBlock resets hardware blocks specified // by the bit pattern in bits. func resetBlock(bits uint32) { - resets.reset.SetBits(bits) + resets.RESET.SetBits(bits) } // unresetBlock brings hardware blocks specified by the // bit pattern in bits out of reset. func unresetBlock(bits uint32) { - resets.reset.ClearBits(bits) + resets.RESET.ClearBits(bits) } // unresetBlockWait brings specified hardware blocks @@ -38,6 +26,6 @@ func unresetBlock(bits uint32) { // out of reset and wait for completion. func unresetBlockWait(bits uint32) { unresetBlock(bits) - for !resets.resetDone.HasBits(bits) { + for !resets.RESET_DONE.HasBits(bits) { } } diff --git a/src/machine/machine_rp2040_rng.go b/src/machine/machine_rp2_rng.go similarity index 97% rename from src/machine/machine_rp2040_rng.go rename to src/machine/machine_rp2_rng.go index 1706785d0f..e619f05002 100644 --- a/src/machine/machine_rp2040_rng.go +++ b/src/machine/machine_rp2_rng.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 // Implementation based on code located here: // https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_lwip/random.c diff --git a/src/machine/machine_rp2040_spi.go b/src/machine/machine_rp2_spi.go similarity index 90% rename from src/machine/machine_rp2040_spi.go rename to src/machine/machine_rp2_spi.go index cb60fdbcb0..88301c98b2 100644 --- a/src/machine/machine_rp2040_spi.go +++ b/src/machine/machine_rp2_spi.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -10,12 +10,10 @@ import ( // SPI on the RP2040 var ( - SPI0 = &_SPI0 - _SPI0 = SPI{ + SPI0 = &SPI{ Bus: rp.SPI0, } - SPI1 = &_SPI1 - _SPI1 = SPI{ + SPI1 = &SPI{ Bus: rp.SPI1, } ) @@ -48,7 +46,7 @@ type SPI struct { Bus *rp.SPI0_Type } -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // @@ -73,7 +71,7 @@ type SPI struct { // // This form sends 0xff and puts the result into rx buffer. Useful for reading from SD cards // which require 0xff input on SI. -func (spi SPI) Tx(w, r []byte) (err error) { +func (spi *SPI) Tx(w, r []byte) (err error) { switch { case w == nil: // read only, so write zero and read a result. @@ -92,7 +90,7 @@ func (spi SPI) Tx(w, r []byte) (err error) { } // Write a single byte and read a single byte from TX/RX FIFO. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { for !spi.isWritable() { } @@ -103,14 +101,14 @@ func (spi SPI) Transfer(w byte) (byte, error) { return uint8(spi.Bus.SSPDR.Get()), nil } -func (spi SPI) SetBaudRate(br uint32) error { - const freqin uint32 = 125 * MHz +func (spi *SPI) SetBaudRate(br uint32) error { const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527. // Find smallest prescale value which puts output frequency in range of // post-divide. Prescale is an even number from 2 to 254 inclusive. var prescale, postdiv uint32 + freq := CPUFrequency() for prescale = 2; prescale < 255; prescale += 2 { - if freqin < (prescale+2)*256*br { + if freq < (prescale+2)*256*br { break } } @@ -120,7 +118,7 @@ func (spi SPI) SetBaudRate(br uint32) error { // Find largest post-divide which makes output <= baudrate. Post-divide is // an integer in the range 1 to 256 inclusive. for postdiv = 256; postdiv > 1; postdiv-- { - if freqin/(prescale*(postdiv-1)) > br { + if freq/(prescale*(postdiv-1)) > br { break } } @@ -129,8 +127,8 @@ func (spi SPI) SetBaudRate(br uint32) error { return nil } -func (spi SPI) GetBaudRate() uint32 { - const freqin uint32 = 125 * MHz +func (spi *SPI) GetBaudRate() uint32 { + freqin := CPUFrequency() prescale := spi.Bus.SSPCPSR.Get() postdiv := ((spi.Bus.SSPCR0.Get() & rp.SPI0_SSPCR0_SCR_Msk) >> rp.SPI0_SSPCR0_SCR_Pos) + 1 return freqin / (prescale * postdiv) @@ -152,7 +150,7 @@ func (spi SPI) GetBaudRate() uint32 { // SCK: 10, 14 // // No pin configuration is needed of SCK, SDO and SDI needed after calling Configure. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { const defaultBaud uint32 = 4 * MHz if config.SCK == 0 && config.SDO == 0 && config.SDI == 0 { // set default pins if config zero valued or invalid clock pin supplied. @@ -199,7 +197,7 @@ func (spi SPI) Configure(config SPIConfig) error { return spi.initSPI(config) } -func (spi SPI) initSPI(config SPIConfig) (err error) { +func (spi *SPI) initSPI(config SPIConfig) (err error) { spi.reset() // LSB-first not supported on PL022: if config.LSBFirst { @@ -207,7 +205,7 @@ func (spi SPI) initSPI(config SPIConfig) (err error) { } err = spi.SetBaudRate(config.Frequency) // Set SPI Format (CPHA and CPOL) and frame format (default is Motorola) - spi.setFormat(config.Mode, rp.XIP_SSI_CTRLR0_SPI_FRF_STD) + spi.setFormat(config.Mode) // Always enable DREQ signals -- harmless if DMA is not listening spi.Bus.SSPDMACR.SetBits(rp.SPI0_SSPDMACR_TXDMAE | rp.SPI0_SSPDMACR_RXDMAE) @@ -217,21 +215,20 @@ func (spi SPI) initSPI(config SPIConfig) (err error) { } //go:inline -func (spi SPI) setFormat(mode uint8, frameFormat uint32) { +func (spi *SPI) setFormat(mode uint8) { cpha := uint32(mode) & 1 cpol := uint32(mode>>1) & 1 spi.Bus.SSPCR0.ReplaceBits( (cpha<>3].Set(p.ioIntBit(change)) @@ -50,23 +37,3 @@ func (p Pin) ctrlSetInterrupt(change PinChange, enabled bool, base *irqCtrl) { enReg.ClearBits(p.ioIntBit(change)) } } - -// Enable or disable a specific interrupt on the executing core. -// num is the interrupt number which must be in [0,31]. -func irqSet(num uint32, enabled bool) { - if num >= _NUMIRQ { - return - } - irqSetMask(1<> 7 var fbrd uint32 @@ -153,7 +153,7 @@ func initUART(uart *UART) { // handleInterrupt should be called from the appropriate interrupt handler for // this UART instance. func (uart *UART) handleInterrupt(interrupt.Interrupt) { - for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) { + for !uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_RXFE) { + uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF))) } - uart.Receive(byte((uart.Bus.UARTDR.Get() & 0xFF))) } diff --git a/src/machine/machine_rp2040_watchdog.go b/src/machine/machine_rp2_watchdog.go similarity index 83% rename from src/machine/machine_rp2040_watchdog.go rename to src/machine/machine_rp2_watchdog.go index a67df80ca8..f776c5ca4d 100644 --- a/src/machine/machine_rp2040_watchdog.go +++ b/src/machine/machine_rp2_watchdog.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -60,11 +60,3 @@ func (wd *watchdogImpl) Start() error { func (wd *watchdogImpl) Update() { rp.WATCHDOG.LOAD.Set(wd.loadValue) } - -// startTick starts the watchdog tick. -// cycles needs to be a divider that when applied to the xosc input, -// produces a 1MHz clock. So if the xosc frequency is 12MHz, -// this will need to be 12. -func (wd *watchdogImpl) startTick(cycles uint32) { - rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE) -} diff --git a/src/machine/machine_rp2040_xosc.go b/src/machine/machine_rp2_xosc.go similarity index 93% rename from src/machine/machine_rp2040_xosc.go rename to src/machine/machine_rp2_xosc.go index 0acac1f1e8..c9ce58300b 100644 --- a/src/machine/machine_rp2040_xosc.go +++ b/src/machine/machine_rp2_xosc.go @@ -1,4 +1,4 @@ -//go:build rp2040 +//go:build rp2040 || rp2350 package machine @@ -18,7 +18,7 @@ type xoscType struct { status volatile.Register32 dormant volatile.Register32 startup volatile.Register32 - reserved [3]volatile.Register32 + reserved [3 - 3*rp2350ExtraReg]volatile.Register32 count volatile.Register32 } diff --git a/src/machine/machine_stm32_adc_f1.go b/src/machine/machine_stm32_adc_f1.go index f852f402b3..7076bdd8f6 100644 --- a/src/machine/machine_stm32_adc_f1.go +++ b/src/machine/machine_stm32_adc_f1.go @@ -23,17 +23,8 @@ func InitADC() { // Enable ADC clock enableAltFuncClock(unsafe.Pointer(stm32.ADC1)) - // set scan mode - stm32.ADC1.CR1.SetBits(stm32.ADC_CR1_SCAN) - - // clear CONT, ALIGN, EXTRIG and EXTSEL bits from CR2 - stm32.ADC1.CR2.ClearBits(stm32.ADC_CR2_CONT | stm32.ADC_CR2_ALIGN | stm32.ADC_CR2_EXTTRIG_Msk | stm32.ADC_CR2_EXTSEL_Msk) - - stm32.ADC1.SQR1.ClearBits(stm32.ADC_SQR1_L_Msk) - stm32.ADC1.SQR1.SetBits(2 << stm32.ADC_SQR1_L_Pos) // 2 means 3 conversions - // enable - stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON | stm32.ADC_CR2_ALIGN) return } @@ -58,25 +49,17 @@ func (a ADC) Configure(ADCConfig) { func (a ADC) Get() uint16 { // set rank ch := uint32(a.getChannel()) - stm32.ADC1.SQR3.SetBits(ch) + stm32.ADC1.SetSQR3_SQ1(ch) // start conversion - stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_SWSTART) + stm32.ADC1.CR2.SetBits(stm32.ADC_CR2_ADON) // wait for conversion to complete for !stm32.ADC1.SR.HasBits(stm32.ADC_SR_EOC) { } // read result as 16 bit value - result := uint16(stm32.ADC1.DR.Get()) << 4 - - // clear flag - stm32.ADC1.SR.ClearBits(stm32.ADC_SR_EOC) - - // clear rank - stm32.ADC1.SQR3.ClearBits(ch) - - return result + return uint16(stm32.ADC1.DR.Get()) } func (a ADC) getChannel() uint8 { diff --git a/src/machine/machine_stm32_flash.go b/src/machine/machine_stm32_flash.go index 710aa05d00..280dc8987e 100644 --- a/src/machine/machine_stm32_flash.go +++ b/src/machine/machine_stm32_flash.go @@ -5,7 +5,6 @@ package machine import ( "device/stm32" - "bytes" "unsafe" ) @@ -41,7 +40,8 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { unlockFlash() defer lockFlash() - return writeFlashData(FlashDataStart()+uintptr(off), f.pad(p)) + p = flashPad(p, int(f.WriteBlockSize())) + return writeFlashData(FlashDataStart()+uintptr(off), p) } // Size returns the number of bytes in this block device. @@ -90,17 +90,6 @@ func (f flashBlockDevice) EraseBlocks(start, len int64) error { return nil } -// pad data if needed so it is long enough for correct byte alignment on writes. -func (f flashBlockDevice) pad(p []byte) []byte { - overflow := int64(len(p)) % f.WriteBlockSize() - if overflow == 0 { - return p - } - - padding := bytes.Repeat([]byte{0xff}, int(f.WriteBlockSize()-overflow)) - return append(p, padding...) -} - const memoryStart = 0x08000000 func unlockFlash() { diff --git a/src/machine/machine_stm32_i2c_revb.go b/src/machine/machine_stm32_i2c_revb.go index 9253902940..006661f9ab 100644 --- a/src/machine/machine_stm32_i2c_revb.go +++ b/src/machine/machine_stm32_i2c_revb.go @@ -45,11 +45,21 @@ type I2C struct { // I2CConfig is used to store config info for I2C. type I2CConfig struct { - SCL Pin - SDA Pin + Frequency uint32 + SCL Pin + SDA Pin } func (i2c *I2C) Configure(config I2CConfig) error { + // Frequency range + switch config.Frequency { + case 0: + config.Frequency = 100 * KHz + case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: + default: + return errI2CNotImplemented + } + // disable I2C interface before any configuration changes i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) @@ -63,8 +73,7 @@ func (i2c *I2C) Configure(config I2CConfig) error { } i2c.configurePins(config) - // Frequency range - i2c.Bus.TIMINGR.Set(i2c.getFreqRange()) + i2c.Bus.TIMINGR.Set(i2c.getFreqRange(config.Frequency)) // Disable Own Address1 before set the Own Address1 configuration i2c.Bus.OAR1.ClearBits(stm32.I2C_OAR1_OA1EN) @@ -86,8 +95,21 @@ func (i2c *I2C) Configure(config I2CConfig) error { // SetBaudRate sets the communication speed for I2C. func (i2c *I2C) SetBaudRate(br uint32) error { - // TODO: implement - return errI2CNotImplemented + switch br { + case 10 * KHz, 100 * KHz, 400 * KHz, 500 * KHz: + default: + return errI2CNotImplemented + } + + // disable I2C interface before any configuration changes + i2c.Bus.CR1.ClearBits(stm32.I2C_CR1_PE) + + i2c.Bus.TIMINGR.Set(i2c.getFreqRange(br)) + + // Disable Generalcall and NoStretch, Enable peripheral + i2c.Bus.CR1.Set(stm32.I2C_CR1_PE) + + return nil } func (i2c *I2C) Tx(addr uint16, w, r []byte) error { diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index 72d4316616..3c6e0b6a88 100644 --- a/src/machine/machine_stm32_spi.go +++ b/src/machine/machine_stm32_spi.go @@ -21,7 +21,7 @@ type SPIConfig struct { } // Configure is intended to setup the STM32 SPI1 interface. -func (spi SPI) Configure(config SPIConfig) error { +func (spi *SPI) Configure(config SPIConfig) error { // -- CONFIGURING THE SPI IN MASTER MODE -- // @@ -98,7 +98,7 @@ func (spi SPI) Configure(config SPIConfig) error { } // Transfer writes/reads a single byte using the SPI interface. -func (spi SPI) Transfer(w byte) (byte, error) { +func (spi *SPI) Transfer(w byte) (byte, error) { // 1. Enable the SPI by setting the SPE bit to 1. // 2. Write the first data item to be transmitted into the SPI_DR register diff --git a/src/machine/machine_stm32f103.go b/src/machine/machine_stm32f103.go index 66a74d04b6..9e7bb347c9 100644 --- a/src/machine/machine_stm32f103.go +++ b/src/machine/machine_stm32f103.go @@ -221,14 +221,19 @@ func (p Pin) enableClock() { // Enable peripheral clock. Expand to include all the desired peripherals func enableAltFuncClock(bus unsafe.Pointer) { - if bus == unsafe.Pointer(stm32.USART1) { + switch bus { + case unsafe.Pointer(stm32.USART1): stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN) - } else if bus == unsafe.Pointer(stm32.USART2) { + case unsafe.Pointer(stm32.USART2): stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_USART2EN) - } else if bus == unsafe.Pointer(stm32.I2C1) { + case unsafe.Pointer(stm32.I2C1): stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_I2C1EN) - } else if bus == unsafe.Pointer(stm32.SPI1) { + case unsafe.Pointer(stm32.SPI1): stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_SPI1EN) + case unsafe.Pointer(stm32.ADC1): + stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_ADC1EN) + default: + panic("machine: unknown peripheral") } } @@ -328,16 +333,16 @@ type SPI struct { // Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. // TODO: implement SPI2 and SPI3. var ( - SPI1 = SPI{Bus: stm32.SPI1} + SPI1 = &SPI{Bus: stm32.SPI1} SPI0 = SPI1 ) -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // set frequency dependent on PCLK2 prescaler (div 1) @@ -363,7 +368,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDO.Configure(PinConfig{Mode: PinOutput50MHz + PinOutputModeAltPushPull}) config.SDI.Configure(PinConfig{Mode: PinInputModeFloating}) @@ -399,7 +404,7 @@ func (i2c *I2C) getFreqRange(config I2CConfig) uint32 { // pclk1 clock speed is main frequency divided by PCLK1 prescaler (div 2) pclk1 := CPUFrequency() / 2 - // set freqency range to PCLK1 clock speed in MHz + // set frequency range to PCLK1 clock speed in MHz // aka setting the value 36 means to use 36 MHz clock return pclk1 / 1000000 } diff --git a/src/machine/machine_stm32f4.go b/src/machine/machine_stm32f4.go index 42193a7397..31f1d2c635 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -6,8 +6,8 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "math/bits" "runtime/interrupt" "runtime/volatile" @@ -672,17 +672,17 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var clock uint32 switch spi.Bus { case stm32.SPI1: diff --git a/src/machine/machine_stm32f7x2.go b/src/machine/machine_stm32f7x2.go index 8755bc8fff..7da407071f 100644 --- a/src/machine/machine_stm32f7x2.go +++ b/src/machine/machine_stm32f7x2.go @@ -51,9 +51,20 @@ func (uart *UART) setRegisters() { //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 27MHz PCLK1 (216MHz CPU Freq / 8). // TODO: Do calculations based on PCLK1 - return 0x00606A9B + switch br { + case 10 * KHz: + return 0x5010C0FF + case 100 * KHz: + return 0x00606A9B + case 400 * KHz: + return 0x00201625 + case 500 * KHz: + return 0x00100429 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l0.go b/src/machine/machine_stm32l0.go index 844cfccb49..1ecd958b81 100644 --- a/src/machine/machine_stm32l0.go +++ b/src/machine/machine_stm32l0.go @@ -234,12 +234,12 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // no-op on this series } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 localFrequency := config.Frequency @@ -256,10 +256,10 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // set frequency dependent on PCLK prescaler. Since these are rather weird - // speeds due to the CPU freqency, pick a range up to that frquency for + // speeds due to the CPU frequency, pick a range up to that frequency for // clients to use more human-understandable numbers, e.g. nearest 100KHz - // These are based on APB2 clock frquency (84MHz on the discovery board) + // These are based on APB2 clock frequency (84MHz on the discovery board) // TODO: also include the MCU/APB clock setting in the equation switch { case localFrequency < 328125: @@ -289,7 +289,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) @@ -298,9 +298,20 @@ func (spi SPI) configurePins(config SPIConfig) { //---------- I2C related types and code // Gets the value for TIMINGR register -func (i2c I2C) getFreqRange() uint32 { +func (i2c I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX - // for 80MHz PCLK1. + // for 16MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x00303D5B + switch br { + case 10 * KHz: + return 0x40003EFF + case 100 * KHz: + return 0x00303D5B + case 400 * KHz: + return 0x0010061A + case 500 * KHz: + return 0x00000117 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l4.go b/src/machine/machine_stm32l4.go index 856320911b..b5babc0b2a 100644 --- a/src/machine/machine_stm32l4.go +++ b/src/machine/machine_stm32l4.go @@ -4,8 +4,8 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "runtime/volatile" "unsafe" @@ -309,14 +309,14 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte // (common STM32 SPI implementation does 8-bit transfers only) spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) } // Set baud rate for SPI -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var conf uint32 // Default @@ -327,10 +327,10 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { localFrequency := config.Frequency // set frequency dependent on PCLK prescaler. Since these are rather weird - // speeds due to the CPU freqency, pick a range up to that frquency for + // speeds due to the CPU frequency, pick a range up to that frequency for // clients to use more human-understandable numbers, e.g. nearest 100KHz - // These are based on 80MHz peripheral clock frquency + // These are based on 80MHz peripheral clock frequency switch { case localFrequency < 312500: conf = stm32.SPI_CR1_BR_Div256 @@ -359,7 +359,7 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { } // Configure SPI pins for input output and clock -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) diff --git a/src/machine/machine_stm32l4x2.go b/src/machine/machine_stm32l4x2.go index 497cf3e1d5..142a8f5f36 100644 --- a/src/machine/machine_stm32l4x2.go +++ b/src/machine/machine_stm32l4x2.go @@ -17,9 +17,20 @@ const APB2_TIM_FREQ = 80e6 // 80MHz //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { - // This is a 'magic' value calculated by STM32CubeMX +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // These are 'magic' values calculated by STM32CubeMX // for 80MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x10909CEC + switch br { + case 10 * KHz: + return 0xF010F3FE + case 100 * KHz: + return 0x10909CEC + case 400 * KHz: + return 0x00702991 + case 500 * KHz: + return 0x00300E84 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l4x5.go b/src/machine/machine_stm32l4x5.go index ec06544ba5..c8c550c3da 100644 --- a/src/machine/machine_stm32l4x5.go +++ b/src/machine/machine_stm32l4x5.go @@ -17,9 +17,20 @@ const APB2_TIM_FREQ = 120e6 // 120MHz //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 120MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x307075B1 + switch br { + case 10 * KHz: + return 0x0 // does this even work? zero is weird here. + case 100 * KHz: + return 0x307075B1 + case 400 * KHz: + return 0x00B03FDB + case 500 * KHz: + return 0x005017C7 + default: + return 0 + } } diff --git a/src/machine/machine_stm32l4x6.go b/src/machine/machine_stm32l4x6.go new file mode 100644 index 0000000000..de878ebe32 --- /dev/null +++ b/src/machine/machine_stm32l4x6.go @@ -0,0 +1,36 @@ +//go:build stm32l4x6 + +package machine + +// Peripheral abstraction layer for the stm32l4x6 + +func CPUFrequency() uint32 { + return 80e6 +} + +// Internal use: configured speed of the APB1 and APB2 timers, this should be kept +// in sync with any changes to runtime package which configures the oscillators +// and clock frequencies +const APB1_TIM_FREQ = 80e6 // 80MHz +const APB2_TIM_FREQ = 80e6 // 80MHz + +//---------- I2C related code + +// Gets the value for TIMINGR register +func (i2c *I2C) getFreqRange(br uint32) uint32 { + // This is a 'magic' value calculated by STM32CubeMX + // for 80MHz PCLK1. + // TODO: Do calculations based on PCLK1 + switch br { + case 10 * KHz: + return 0xF010F3FE + case 100 * KHz: + return 0x10909CEC + case 400 * KHz: + return 0x00702991 + case 500 * KHz: + return 0x00300E84 + default: + return 0 + } +} diff --git a/src/machine/machine_stm32l5x2.go b/src/machine/machine_stm32l5x2.go index 2fb3a0d638..82ca1ecf6c 100644 --- a/src/machine/machine_stm32l5x2.go +++ b/src/machine/machine_stm32l5x2.go @@ -23,7 +23,7 @@ const APB2_TIM_FREQ = 110e6 // 110MHz // Configure the UART. func (uart *UART) configurePins(config UARTConfig) { if config.RX.getPort() == stm32.GPIOG || config.TX.getPort() == stm32.GPIOG { - // Enable VDDIO2 power supply, which is an independant power supply for the PGx pins + // Enable VDDIO2 power supply, which is an independent power supply for the PGx pins stm32.PWR.CR2.SetBits(stm32.PWR_CR2_IOSV) } @@ -49,9 +49,20 @@ func (uart *UART) setRegisters() { //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 110MHz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x40505681 + switch br { + case 10 * KHz: + return 0x0 // does this even work? zero is weird here. + case 100 * KHz: + return 0x40505681 + case 400 * KHz: + return 0x00A03AC8 + case 500 * KHz: + return 0x005015B6 + default: + return 0 + } } diff --git a/src/machine/machine_stm32wlx.go b/src/machine/machine_stm32wlx.go index d42ef2e383..80ca791e6a 100644 --- a/src/machine/machine_stm32wlx.go +++ b/src/machine/machine_stm32wlx.go @@ -6,8 +6,8 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "math/bits" "runtime/interrupt" "runtime/volatile" @@ -235,19 +235,19 @@ type SPI struct { AltFuncSelector uint8 } -func (spi SPI) config8Bits() { +func (spi *SPI) config8Bits() { // Set rx threshold to 8-bits, so RXNE flag is set for 1 byte // (common STM32 SPI implementation does 8-bit transfers only) spi.Bus.CR2.SetBits(stm32.SPI_CR2_FRXTH) } -func (spi SPI) configurePins(config SPIConfig) { +func (spi *SPI) configurePins(config SPIConfig) { config.SCK.ConfigureAltFunc(PinConfig{Mode: PinModeSPICLK}, spi.AltFuncSelector) config.SDO.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDO}, spi.AltFuncSelector) config.SDI.ConfigureAltFunc(PinConfig{Mode: PinModeSPISDI}, spi.AltFuncSelector) } -func (spi SPI) getBaudRate(config SPIConfig) uint32 { +func (spi *SPI) getBaudRate(config SPIConfig) uint32 { var clock uint32 // We keep this switch and separate management of SPI Clocks @@ -289,11 +289,22 @@ func (spi SPI) getBaudRate(config SPIConfig) uint32 { //---------- I2C related code // Gets the value for TIMINGR register -func (i2c *I2C) getFreqRange() uint32 { +func (i2c *I2C) getFreqRange(br uint32) uint32 { // This is a 'magic' value calculated by STM32CubeMX // for 48Mhz PCLK1. // TODO: Do calculations based on PCLK1 - return 0x20303E5D + switch br { + case 10 * KHz: + return 0x9010DEFF + case 100 * KHz: + return 0x20303E5D + case 400 * KHz: + return 0x2010091A + case 500 * KHz: + return 0x00201441 + default: + return 0 + } } //---------- UART related code diff --git a/src/machine/machine_tkey.go b/src/machine/machine_tkey.go new file mode 100644 index 0000000000..78863d845c --- /dev/null +++ b/src/machine/machine_tkey.go @@ -0,0 +1,234 @@ +//go:build tkey + +package machine + +import ( + "device/tkey" + "errors" + "strconv" +) + +const deviceName = "TKey" + +// GPIO pins modes are only here to match the Pin interface. +// The actual configuration is fixed in the hardware. +const ( + PinOutput PinMode = iota + PinInput + PinInputPullup + PinInputPulldown +) + +const ( + LED_BLUE = Pin(tkey.TK1_MMIO_TK1_LED_B_BIT) + LED_GREEN = Pin(tkey.TK1_MMIO_TK1_LED_G_BIT) + LED_RED = Pin(tkey.TK1_MMIO_TK1_LED_R_BIT) + + LED = LED_GREEN + + TKEY_TOUCH = Pin(3) // 3 is unused, but we need a value here to match the Pin interface. + BUTTON = TKEY_TOUCH + + GPIO1 = Pin(tkey.TK1_MMIO_TK1_GPIO1_BIT + 8) + GPIO2 = Pin(tkey.TK1_MMIO_TK1_GPIO2_BIT + 8) + GPIO3 = Pin(tkey.TK1_MMIO_TK1_GPIO3_BIT + 8) + GPIO4 = Pin(tkey.TK1_MMIO_TK1_GPIO4_BIT + 8) +) + +var touchConfig, gpio1Config, gpio2Config PinConfig + +// No config needed for TKey, just to match the Pin interface. +func (p Pin) Configure(config PinConfig) { + switch p { + case BUTTON: + touchConfig = config + + // Clear any pending touch events. + tkey.TOUCH.STATUS.Set(0) + case GPIO1: + gpio1Config = config + case GPIO2: + gpio2Config = config + } +} + +// Set pin to high or low. +func (p Pin) Set(high bool) { + switch p { + case LED_BLUE, LED_GREEN, LED_RED: + if high { + tkey.TK1.LED.SetBits(1 << uint(p)) + } else { + tkey.TK1.LED.ClearBits(1 << uint(p)) + } + case GPIO3, GPIO4: + if high { + tkey.TK1.GPIO.SetBits(1 << uint(p-8)) + } else { + tkey.TK1.GPIO.ClearBits(1 << uint(p-8)) + } + } +} + +// Get returns the current value of a pin. +func (p Pin) Get() bool { + pushed := false + mode := PinInput + + switch p { + case BUTTON: + mode = touchConfig.Mode + if tkey.TOUCH.STATUS.HasBits(1) { + tkey.TOUCH.STATUS.Set(0) + pushed = true + } + case GPIO1: + mode = gpio1Config.Mode + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case GPIO2: + mode = gpio2Config.Mode + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case GPIO3, GPIO4: + mode = PinOutput + pushed = tkey.TK1.GPIO.HasBits(1 << uint(p-8)) + case LED_BLUE, LED_GREEN, LED_RED: + mode = PinOutput + pushed = tkey.TK1.LED.HasBits(1 << uint(p)) + } + + switch mode { + case PinInputPullup: + return !pushed + case PinInput, PinInputPulldown, PinOutput: + return pushed + } + + return false +} + +type UART struct { + Bus *tkey.UART_Type +} + +var ( + DefaultUART = UART0 + UART0 = &_UART0 + _UART0 = UART{Bus: tkey.UART} +) + +// The TKey UART is fixed at 62500 baud, 8N1. +func (uart *UART) Configure(config UARTConfig) error { + if !(config.BaudRate == 62500 || config.BaudRate == 0) { + return errors.New("uart: only 62500 baud rate is supported") + } + + return nil +} + +// Write a slice of data bytes to the UART. +func (uart *UART) Write(data []byte) (n int, err error) { + for _, c := range data { + if err := uart.WriteByte(c); err != nil { + return n, err + } + } + return len(data), nil +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) WriteByte(c byte) error { + for uart.Bus.TX_STATUS.Get() == 0 { + } + + uart.Bus.TX_DATA.Set(uint32(c)) + + return nil +} + +// Buffered returns the number of bytes buffered in the UART. +func (uart *UART) Buffered() int { + return int(uart.Bus.RX_BYTES.Get()) +} + +// ReadByte reads a byte of data from the UART. +func (uart *UART) ReadByte() (byte, error) { + for uart.Bus.RX_STATUS.Get() == 0 { + } + + return byte(uart.Bus.RX_DATA.Get()), nil +} + +// DTR is not available on the TKey. +func (uart *UART) DTR() bool { + return false +} + +// RTS is not available on the TKey. +func (uart *UART) RTS() bool { + return false +} + +// GetRNG returns 32 bits of cryptographically secure random data +func GetRNG() (uint32, error) { + for tkey.TRNG.STATUS.Get() == 0 { + } + + return uint32(tkey.TRNG.ENTROPY.Get()), nil +} + +// DesignName returns the FPGA design name. +func DesignName() (string, string) { + n0 := tkey.TK1.NAME0.Get() + name0 := string([]byte{byte(n0 >> 24), byte(n0 >> 16), byte(n0 >> 8), byte(n0)}) + n1 := tkey.TK1.NAME1.Get() + name1 := string([]byte{byte(n1 >> 24), byte(n1 >> 16), byte(n1 >> 8), byte(n1)}) + + return name0, name1 +} + +// DesignVersion returns the FPGA design version. +func DesignVersion() string { + version := tkey.TK1.VERSION.Get() + + return strconv.Itoa(int(version)) +} + +// CDI returns 8 words of Compound Device Identifier (CDI) generated and written by the firmware when the application is loaded. +func CDI() []byte { + cdi := make([]byte, 32) + for i := 0; i < 8; i++ { + c := tkey.TK1.CDI_FIRST[i].Get() + cdi[i*4] = byte(c >> 24) + cdi[i*4+1] = byte(c >> 16) + cdi[i*4+2] = byte(c >> 8) + cdi[i*4+3] = byte(c) + } + return cdi +} + +// UDI returns 2 words of Unique Device Identifier (UDI). Only available in firmware mode. +func UDI() []byte { + udi := make([]byte, 8) + for i := 0; i < 2; i++ { + c := tkey.TK1.UDI_FIRST[i].Get() + udi[i*4] = byte(c >> 24) + udi[i*4+1] = byte(c >> 16) + udi[i*4+2] = byte(c >> 8) + udi[i*4+3] = byte(c) + } + return udi +} + +// UDS returns 8 words of Unique Device Secret. Part of the FPGA design, changed when provisioning a TKey. +// Only available in firmware mode. UDS is only readable once per power cycle. +func UDS() []byte { + uds := make([]byte, 32) + for i := 0; i < 8; i++ { + c := tkey.UDS.DATA[i].Get() + uds[i*4] = byte(c >> 24) + uds[i*4+1] = byte(c >> 16) + uds[i*4+2] = byte(c >> 8) + uds[i*4+3] = byte(c) + } + return uds +} diff --git a/src/machine/machine_tkey_rom.go b/src/machine/machine_tkey_rom.go new file mode 100644 index 0000000000..bc7162e047 --- /dev/null +++ b/src/machine/machine_tkey_rom.go @@ -0,0 +1,59 @@ +//go:build tkey + +package machine + +/* + #define TK1_MMIO_TK1_BLAKE2S 0xff000040 + + typedef unsigned char uint8_t; + typedef unsigned long uint32_t; + typedef unsigned long size_t; + + // blake2s state context + typedef struct { + uint8_t b[64]; // input buffer + uint32_t h[8]; // chained state + uint32_t t[2]; // total number of bytes + size_t c; // pointer for b[] + size_t outlen; // digest size + } blake2s_ctx; + + typedef int (*fw_blake2s_p)(void *out, unsigned long outlen, const void *key, + unsigned long keylen, const void *in, + unsigned long inlen, blake2s_ctx *ctx); + + int blake2s(void *out, unsigned long outlen, const void *key, unsigned long keylen, const void *in, unsigned long inlen) + { + fw_blake2s_p const fw_blake2s = + (fw_blake2s_p) * (volatile uint32_t *)TK1_MMIO_TK1_BLAKE2S; + blake2s_ctx ctx; + + return fw_blake2s(out, outlen, key, keylen, in, inlen, &ctx); + } +*/ +import "C" +import ( + "errors" + "unsafe" +) + +var ( + ErrBLAKE2sInvalid = errors.New("invalid params for call to BLAKE2s") + ErrBLAKE2sFailed = errors.New("call to BLAKE2s failed") +) + +func BLAKE2s(output []byte, key []byte, input []byte) error { + if len(output) == 0 || len(input) == 0 { + return ErrBLAKE2sInvalid + } + + op := unsafe.Pointer(&output[0]) + kp := unsafe.Pointer(&key[0]) + ip := unsafe.Pointer(&input[0]) + + if res := C.blake2s(op, C.size_t(len(output)), kp, C.size_t(len(key)), ip, C.size_t(len(input))); res != 0 { + return ErrBLAKE2sFailed + } + + return nil +} diff --git a/src/machine/spi.go b/src/machine/spi.go index a6fd866d1d..9a1033ca7d 100644 --- a/src/machine/spi.go +++ b/src/machine/spi.go @@ -1,4 +1,4 @@ -//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build !baremetal || atmega || esp32 || fe310 || k210 || nrf || (nxp && !mk66f18) || rp2040 || rp2350 || sam || (stm32 && !stm32f7x2 && !stm32l5x2) package machine diff --git a/src/machine/spi_tx.go b/src/machine/spi_tx.go index bfc3bfb60c..97385bb596 100644 --- a/src/machine/spi_tx.go +++ b/src/machine/spi_tx.go @@ -1,11 +1,11 @@ -//go:build !baremetal || atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) +//go:build atmega || fe310 || k210 || (nxp && !mk66f18) || (stm32 && !stm32f7x2 && !stm32l5x2) // This file implements the SPI Tx function for targets that don't have a custom // (faster) implementation for it. package machine -// Tx handles read/write operation for SPI interface. Since SPI is a syncronous write/read +// Tx handles read/write operation for SPI interface. Since SPI is a synchronous write/read // interface, there must always be the same number of bytes written as bytes read. // The Tx method knows about this, and offers a few different ways of calling it. // @@ -22,7 +22,7 @@ package machine // This form sends zeros, putting the result into the rx buffer. Good for reading a "result packet": // // spi.Tx(nil, rx) -func (spi SPI) Tx(w, r []byte) error { +func (spi *SPI) Tx(w, r []byte) error { var err error switch { diff --git a/src/machine/uart.go b/src/machine/uart.go index eeeb7d6a0b..32462587b1 100644 --- a/src/machine/uart.go +++ b/src/machine/uart.go @@ -1,4 +1,4 @@ -//go:build atmega || esp || nrf || sam || sifive || stm32 || k210 || nxp || rp2040 +//go:build atmega || esp || nrf || sam || sifive || stm32 || k210 || nxp || rp2040 || rp2350 package machine diff --git a/src/machine/usb.go b/src/machine/usb.go index 2f77556905..1c577b5f6e 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -1,4 +1,4 @@ -//go:build sam || nrf52840 || rp2040 +//go:build sam || nrf52840 || rp2040 || rp2350 package machine diff --git a/src/machine/usb/adc/midi/messages.go b/src/machine/usb/adc/midi/messages.go index 7a681f9bf6..c123acb737 100644 --- a/src/machine/usb/adc/midi/messages.go +++ b/src/machine/usb/adc/midi/messages.go @@ -97,7 +97,7 @@ func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) error { return errInvalidMIDIVelocity } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINNoteOn, MsgNoteOn|(channel-1&0xf), byte(note)&0x7f, velocity&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOn, MsgNoteOn|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f _, err := m.Write(m.msg[:]) return err } @@ -115,7 +115,7 @@ func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) error { return errInvalidMIDIVelocity } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINNoteOff, MsgNoteOff|(channel-1&0xf), byte(note)&0x7f, velocity&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINNoteOff, MsgNoteOff|((channel-1)&0xf), byte(note)&0x7f, velocity&0x7f _, err := m.Write(m.msg[:]) return err } @@ -137,7 +137,7 @@ func (m *midi) ControlChange(cable, channel, control, value uint8) error { return errInvalidMIDIControlValue } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINControlChange, MsgControlChange|(channel-1&0xf), control&0x7f, value&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINControlChange, MsgControlChange|((channel-1)&0xf), control&0x7f, value&0x7f _, err := m.Write(m.msg[:]) return err } @@ -156,7 +156,7 @@ func (m *midi) ProgramChange(cable, channel uint8, patch uint8) error { return errInvalidMIDIPatch } - m.msg[0], m.msg[1], m.msg[2] = (cable&0xf<<4)|CINProgramChange, MsgProgramChange|(channel-1&0xf), patch&0x7f + m.msg[0], m.msg[1], m.msg[2] = ((cable&0xf)<<4)|CINProgramChange, MsgProgramChange|((channel-1)&0xf), patch&0x7f _, err := m.Write(m.msg[:3]) return err } @@ -177,7 +177,7 @@ func (m *midi) PitchBend(cable, channel uint8, bend uint16) error { return errInvalidMIDIPitchBend } - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINPitchBendChange, MsgPitchBend|(channel-1&0xf), byte(bend&0x7f), byte(bend>>8)&0x7f + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = ((cable&0xf)<<4)|CINPitchBendChange, MsgPitchBend|((channel-1)&0xf), byte(bend&0x7f), byte(bend>>7)&0x7f _, err := m.Write(m.msg[:]) return err } @@ -198,7 +198,7 @@ func (m *midi) SysEx(cable uint8, data []byte) error { } // write start - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, MsgSysExStart + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, MsgSysExStart m.msg[2], m.msg[3] = data[0], data[1] if _, err := m.Write(m.msg[:]); err != nil { return err @@ -207,7 +207,7 @@ func (m *midi) SysEx(cable uint8, data []byte) error { // write middle i := 2 for ; i < len(data)-2; i += 3 { - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, data[i] + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExStart, data[i] m.msg[2], m.msg[3] = data[i+1], data[i+2] if _, err := m.Write(m.msg[:]); err != nil { return err @@ -216,13 +216,13 @@ func (m *midi) SysEx(cable uint8, data []byte) error { // write end switch len(data) - i { case 2: - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd3, data[i] + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd3, data[i] m.msg[2], m.msg[3] = data[i+1], MsgSysExEnd case 1: - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd2, data[i] + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd2, data[i] m.msg[2], m.msg[3] = MsgSysExEnd, 0 case 0: - m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd1, MsgSysExEnd + m.msg[0], m.msg[1] = ((cable&0xf)<<4)|CINSysExEnd1, MsgSysExEnd m.msg[2], m.msg[3] = 0, 0 } if _, err := m.Write(m.msg[:]); err != nil { diff --git a/src/machine/usb/descriptor/configuration.go b/src/machine/usb/descriptor/configuration.go index d9446e6738..efb9ab1d2c 100644 --- a/src/machine/usb/descriptor/configuration.go +++ b/src/machine/usb/descriptor/configuration.go @@ -1,7 +1,7 @@ package descriptor import ( - "encoding/binary" + "internal/binary" ) const ( diff --git a/src/machine/usb/descriptor/device.go b/src/machine/usb/descriptor/device.go index 48229fbfd3..0c3ee92f9a 100644 --- a/src/machine/usb/descriptor/device.go +++ b/src/machine/usb/descriptor/device.go @@ -1,7 +1,7 @@ package descriptor import ( - "encoding/binary" + "internal/binary" ) const ( diff --git a/src/machine/usb/descriptor/endpoint.go b/src/machine/usb/descriptor/endpoint.go index affaffa0ad..c7fa011fad 100644 --- a/src/machine/usb/descriptor/endpoint.go +++ b/src/machine/usb/descriptor/endpoint.go @@ -1,7 +1,7 @@ package descriptor import ( - "encoding/binary" + "internal/binary" ) var endpointEP1IN = [endpointTypeLen]byte{ diff --git a/src/machine/usb/descriptor/hid.go b/src/machine/usb/descriptor/hid.go index cdd4fc7e57..06b9801530 100644 --- a/src/machine/usb/descriptor/hid.go +++ b/src/machine/usb/descriptor/hid.go @@ -1,9 +1,9 @@ package descriptor import ( - "bytes" - "encoding/binary" "errors" + "internal/binary" + "internal/bytealg" ) var configurationCDCHID = [configurationTypeLen]byte{ @@ -87,7 +87,7 @@ func FindClassHIDType(des, class []byte) (ClassHIDType, error) { // search only for ClassHIDType without the ClassLength, // in case it has already been set. - idx := bytes.Index(des, class[:ClassHIDTypeLen-2]) + idx := bytealg.Index(des, class[:ClassHIDTypeLen-2]) if idx == -1 { return ClassHIDType{}, errNoClassHIDFound } @@ -103,7 +103,7 @@ var classHID = [ClassHIDTypeLen]byte{ 0x00, // CountryCode 0x01, // NumDescriptors 0x22, // ClassType - 0x90, // ClassLength L + 0x91, // ClassLength L 0x00, // ClassLength H } @@ -131,7 +131,7 @@ var CDCHID = Descriptor{ EndpointEP5OUT.Bytes(), }), HID: map[uint16][]byte{ - 2: Append([][]byte{ + 2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified! HIDUsagePageGenericDesktop, HIDUsageDesktopKeyboard, HIDCollectionApplication, @@ -210,6 +210,7 @@ var CDCHID = Descriptor{ HIDReportSize(16), HIDReportCount(1), HIDInputDataAryAbs, - HIDCollectionEnd}), + HIDCollectionEnd, + }), }, } diff --git a/src/machine/usb/descriptor/hidreport.go b/src/machine/usb/descriptor/hidreport.go index 5819f6ad69..625578f8f9 100644 --- a/src/machine/usb/descriptor/hidreport.go +++ b/src/machine/usb/descriptor/hidreport.go @@ -1,22 +1,24 @@ package descriptor +import "math" + const ( - hidUsagePage = 0x05 - hidUsage = 0x09 + hidUsagePage = 0x04 + hidUsage = 0x08 hidLogicalMinimum = 0x14 hidLogicalMaximum = 0x24 hidUsageMinimum = 0x18 hidUsageMaximum = 0x28 hidPhysicalMinimum = 0x34 hidPhysicalMaximum = 0x44 - hidUnitExponent = 0x55 - hidUnit = 0x65 - hidCollection = 0xa1 - hidInput = 0x81 - hidOutput = 0x91 - hidReportSize = 0x75 - hidReportCount = 0x95 - hidReportID = 0x85 + hidUnitExponent = 0x54 + hidUnit = 0x64 + hidCollection = 0xA0 + hidInput = 0x80 + hidOutput = 0x90 + hidReportSize = 0x74 + hidReportCount = 0x94 + hidReportID = 0x84 ) const ( @@ -27,191 +29,192 @@ const ( ) var ( - HIDUsagePageGenericDesktop = []byte{hidUsagePage, 0x01} - HIDUsagePageSimulationControls = []byte{hidUsagePage, 0x02} - HIDUsagePageVRControls = []byte{hidUsagePage, 0x03} - HIDUsagePageSportControls = []byte{hidUsagePage, 0x04} - HIDUsagePageGameControls = []byte{hidUsagePage, 0x05} - HIDUsagePageGenericControls = []byte{hidUsagePage, 0x06} - HIDUsagePageKeyboard = []byte{hidUsagePage, 0x07} - HIDUsagePageLED = []byte{hidUsagePage, 0x08} - HIDUsagePageButton = []byte{hidUsagePage, 0x09} - HIDUsagePageOrdinal = []byte{hidUsagePage, 0x0A} - HIDUsagePageTelephony = []byte{hidUsagePage, 0x0B} - HIDUsagePageConsumer = []byte{hidUsagePage, 0x0C} - HIDUsagePageDigitizers = []byte{hidUsagePage, 0x0D} - HIDUsagePageHaptics = []byte{hidUsagePage, 0x0E} - HIDUsagePagePhysicalInput = []byte{hidUsagePage, 0x0F} - HIDUsagePageUnicode = []byte{hidUsagePage, 0x10} - HIDUsagePageSoC = []byte{hidUsagePage, 0x11} - HIDUsagePageEyeHeadTrackers = []byte{hidUsagePage, 0x12} - HIDUsagePageAuxDisplay = []byte{hidUsagePage, 0x14} - HIDUsagePageSensors = []byte{hidUsagePage, 0x20} - HIDUsagePageMedicalInstrument = []byte{hidUsagePage, 0x40} - HIDUsagePageBrailleDisplay = []byte{hidUsagePage, 0x41} - HIDUsagePageLighting = []byte{hidUsagePage, 0x59} - HIDUsagePageMonitor = []byte{hidUsagePage, 0x80} - HIDUsagePageMonitorEnum = []byte{hidUsagePage, 0x81} - HIDUsagePageVESA = []byte{hidUsagePage, 0x82} - HIDUsagePagePower = []byte{hidUsagePage, 0x84} - HIDUsagePageBatterySystem = []byte{hidUsagePage, 0x85} - HIDUsagePageBarcodeScanner = []byte{hidUsagePage, 0x8C} - HIDUsagePageScales = []byte{hidUsagePage, 0x8D} - HIDUsagePageMagneticStripe = []byte{hidUsagePage, 0x8E} - HIDUsagePageCameraControl = []byte{hidUsagePage, 0x90} - HIDUsagePageArcade = []byte{hidUsagePage, 0x91} - HIDUsagePageGaming = []byte{hidUsagePage, 0x92} + HIDUsagePageGenericDesktop = HIDUsagePage(0x01) + HIDUsagePageSimulationControls = HIDUsagePage(0x02) + HIDUsagePageVRControls = HIDUsagePage(0x03) + HIDUsagePageSportControls = HIDUsagePage(0x04) + HIDUsagePageGameControls = HIDUsagePage(0x05) + HIDUsagePageGenericControls = HIDUsagePage(0x06) + HIDUsagePageKeyboard = HIDUsagePage(0x07) + HIDUsagePageLED = HIDUsagePage(0x08) + HIDUsagePageButton = HIDUsagePage(0x09) + HIDUsagePageOrdinal = HIDUsagePage(0x0A) + HIDUsagePageTelephony = HIDUsagePage(0x0B) + HIDUsagePageConsumer = HIDUsagePage(0x0C) + HIDUsagePageDigitizers = HIDUsagePage(0x0D) + HIDUsagePageHaptics = HIDUsagePage(0x0E) + HIDUsagePagePhysicalInput = HIDUsagePage(0x0F) + HIDUsagePageUnicode = HIDUsagePage(0x10) + HIDUsagePageSoC = HIDUsagePage(0x11) + HIDUsagePageEyeHeadTrackers = HIDUsagePage(0x12) + HIDUsagePageAuxDisplay = HIDUsagePage(0x14) + HIDUsagePageSensors = HIDUsagePage(0x20) + HIDUsagePageMedicalInstrument = HIDUsagePage(0x40) + HIDUsagePageBrailleDisplay = HIDUsagePage(0x41) + HIDUsagePageLighting = HIDUsagePage(0x59) + HIDUsagePageMonitor = HIDUsagePage(0x80) + HIDUsagePageMonitorEnum = HIDUsagePage(0x81) + HIDUsagePageVESA = HIDUsagePage(0x82) + HIDUsagePagePower = HIDUsagePage(0x84) + HIDUsagePageBatterySystem = HIDUsagePage(0x85) + HIDUsagePageBarcodeScanner = HIDUsagePage(0x8C) + HIDUsagePageScales = HIDUsagePage(0x8D) + HIDUsagePageMagneticStripe = HIDUsagePage(0x8E) + HIDUsagePageCameraControl = HIDUsagePage(0x90) + HIDUsagePageArcade = HIDUsagePage(0x91) + HIDUsagePageGaming = HIDUsagePage(0x92) ) var ( - HIDUsageDesktopPointer = []byte{hidUsage, 0x01} - HIDUsageDesktopMouse = []byte{hidUsage, 0x02} - HIDUsageDesktopJoystick = []byte{hidUsage, 0x04} - HIDUsageDesktopGamepad = []byte{hidUsage, 0x05} - HIDUsageDesktopKeyboard = []byte{hidUsage, 0x06} - HIDUsageDesktopKeypad = []byte{hidUsage, 0x07} - HIDUsageDesktopMultiaxis = []byte{hidUsage, 0x08} - HIDUsageDesktopTablet = []byte{hidUsage, 0x09} - HIDUsageDesktopWaterCooling = []byte{hidUsage, 0x0A} - HIDUsageDesktopChassis = []byte{hidUsage, 0x0B} - HIDUsageDesktopWireless = []byte{hidUsage, 0x0C} - HIDUsageDesktopPortable = []byte{hidUsage, 0x0D} - HIDUsageDesktopSystemMultiaxis = []byte{hidUsage, 0x0E} - HIDUsageDesktopSpatial = []byte{hidUsage, 0x0F} - HIDUsageDesktopAssistive = []byte{hidUsage, 0x10} - HIDUsageDesktopDock = []byte{hidUsage, 0x11} - HIDUsageDesktopDockable = []byte{hidUsage, 0x12} - HIDUsageDesktopCallState = []byte{hidUsage, 0x13} - HIDUsageDesktopX = []byte{hidUsage, 0x30} - HIDUsageDesktopY = []byte{hidUsage, 0x31} - HIDUsageDesktopZ = []byte{hidUsage, 0x32} - HIDUsageDesktopRx = []byte{hidUsage, 0x33} - HIDUsageDesktopRy = []byte{hidUsage, 0x34} - HIDUsageDesktopRz = []byte{hidUsage, 0x35} - HIDUsageDesktopSlider = []byte{hidUsage, 0x36} - HIDUsageDesktopDial = []byte{hidUsage, 0x37} - HIDUsageDesktopWheel = []byte{hidUsage, 0x38} - HIDUsageDesktopHatSwitch = []byte{hidUsage, 0x39} - HIDUsageDesktopCountedBuffer = []byte{hidUsage, 0x3A} + HIDUsageDesktopPointer = HIDUsage(0x01) + HIDUsageDesktopMouse = HIDUsage(0x02) + HIDUsageDesktopJoystick = HIDUsage(0x04) + HIDUsageDesktopGamepad = HIDUsage(0x05) + HIDUsageDesktopKeyboard = HIDUsage(0x06) + HIDUsageDesktopKeypad = HIDUsage(0x07) + HIDUsageDesktopMultiaxis = HIDUsage(0x08) + HIDUsageDesktopTablet = HIDUsage(0x09) + HIDUsageDesktopWaterCooling = HIDUsage(0x0A) + HIDUsageDesktopChassis = HIDUsage(0x0B) + HIDUsageDesktopWireless = HIDUsage(0x0C) + HIDUsageDesktopPortable = HIDUsage(0x0D) + HIDUsageDesktopSystemMultiaxis = HIDUsage(0x0E) + HIDUsageDesktopSpatial = HIDUsage(0x0F) + HIDUsageDesktopAssistive = HIDUsage(0x10) + HIDUsageDesktopDock = HIDUsage(0x11) + HIDUsageDesktopDockable = HIDUsage(0x12) + HIDUsageDesktopCallState = HIDUsage(0x13) + HIDUsageDesktopX = HIDUsage(0x30) + HIDUsageDesktopY = HIDUsage(0x31) + HIDUsageDesktopZ = HIDUsage(0x32) + HIDUsageDesktopRx = HIDUsage(0x33) + HIDUsageDesktopRy = HIDUsage(0x34) + HIDUsageDesktopRz = HIDUsage(0x35) + HIDUsageDesktopSlider = HIDUsage(0x36) + HIDUsageDesktopDial = HIDUsage(0x37) + HIDUsageDesktopWheel = HIDUsage(0x38) + HIDUsageDesktopHatSwitch = HIDUsage(0x39) + HIDUsageDesktopCountedBuffer = HIDUsage(0x3A) ) var ( - HIDUsageConsumerControl = []byte{hidUsage, 0x01} - HIDUsageConsumerNumericKeypad = []byte{hidUsage, 0x02} - HIDUsageConsumerProgrammableButtons = []byte{hidUsage, 0x03} - HIDUsageConsumerMicrophone = []byte{hidUsage, 0x04} - HIDUsageConsumerHeadphone = []byte{hidUsage, 0x05} - HIDUsageConsumerGraphicEqualizer = []byte{hidUsage, 0x06} + HIDUsageConsumerControl = HIDUsage(0x01) + HIDUsageConsumerNumericKeypad = HIDUsage(0x02) + HIDUsageConsumerProgrammableButtons = HIDUsage(0x03) + HIDUsageConsumerMicrophone = HIDUsage(0x04) + HIDUsageConsumerHeadphone = HIDUsage(0x05) + HIDUsageConsumerGraphicEqualizer = HIDUsage(0x06) ) var ( - HIDCollectionPhysical = []byte{hidCollection, 0x00} - HIDCollectionApplication = []byte{hidCollection, 0x01} - HIDCollectionEnd = []byte{0xc0} + HIDCollectionPhysical = HIDCollection(0x00) + HIDCollectionApplication = HIDCollection(0x01) + HIDCollectionEnd = []byte{0xC0} ) var ( // Input (Data,Ary,Abs), Key arrays (6 bytes) - HIDInputDataAryAbs = []byte{hidInput, 0x00} + HIDInputDataAryAbs = HIDInput(0x00) // Input (Data, Variable, Absolute), Modifier byte - HIDInputDataVarAbs = []byte{hidInput, 0x02} + HIDInputDataVarAbs = HIDInput(0x02) // Input (Const,Var,Abs), Modifier byte - HIDInputConstVarAbs = []byte{hidInput, 0x03} + HIDInputConstVarAbs = HIDInput(0x03) // Input (Data, Variable, Relative), 2 position bytes (X & Y) - HIDInputDataVarRel = []byte{hidInput, 0x06} + HIDInputDataVarRel = HIDInput(0x06) // Output (Data, Variable, Absolute), Modifier byte - HIDOutputDataVarAbs = []byte{hidOutput, 0x02} + HIDOutputDataVarAbs = HIDOutput(0x02) // Output (Const, Variable, Absolute), Modifier byte - HIDOutputConstVarAbs = []byte{hidOutput, 0x03} + HIDOutputConstVarAbs = HIDOutput(0x03) ) +func hidShortItem(tag byte, value uint32) []byte { + switch { + case value <= math.MaxUint8: + return []byte{tag | hidSizeValue1, byte(value)} + case value <= math.MaxUint16: + return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} + default: + return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} + } +} + +func hidShortItemSigned(tag byte, value int32) []byte { + switch { + case math.MinInt8 <= value && value <= math.MaxInt8: + return []byte{tag | hidSizeValue1, byte(value)} + case math.MinInt16 <= value && value <= math.MaxInt16: + return []byte{tag | hidSizeValue2, byte(value), byte(value >> 8)} + default: + return []byte{tag | hidSizeValue4, byte(value), byte(value >> 8), byte(value >> 16), byte(value >> 24)} + } +} + func HIDReportSize(size int) []byte { - return []byte{hidReportSize, byte(size)} + return hidShortItem(hidReportSize, uint32(size)) } func HIDReportCount(count int) []byte { - return []byte{hidReportCount, byte(count)} + return hidShortItem(hidReportCount, uint32(count)) } func HIDReportID(id int) []byte { - return []byte{hidReportID, byte(id)} + return hidShortItem(hidReportID, uint32(id)) } func HIDLogicalMinimum(min int) []byte { - switch { - case min < -32767 || 65535 < min: - return []byte{hidLogicalMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} - case min < -127 || 255 < min: - return []byte{hidLogicalMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} - default: - return []byte{hidLogicalMinimum + hidSizeValue1, byte(min)} - } + return hidShortItemSigned(hidLogicalMinimum, int32(min)) } func HIDLogicalMaximum(max int) []byte { - switch { - case max < -32767 || 65535 < max: - return []byte{hidLogicalMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} - case max < -127 || 255 < max: - return []byte{hidLogicalMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} - default: - return []byte{hidLogicalMaximum + hidSizeValue1, byte(max)} - } + return hidShortItemSigned(hidLogicalMaximum, int32(max)) } func HIDUsageMinimum(min int) []byte { - switch { - case min < -32767 || 65535 < min: - return []byte{hidUsageMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} - case min < -127 || 255 < min: - return []byte{hidUsageMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} - default: - return []byte{hidUsageMinimum + hidSizeValue1, byte(min)} - } + return hidShortItem(hidUsageMinimum, uint32(min)) } func HIDUsageMaximum(max int) []byte { - switch { - case max < -32767 || 65535 < max: - return []byte{hidUsageMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} - case max < -127 || 255 < max: - return []byte{hidUsageMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} - default: - return []byte{hidUsageMaximum + hidSizeValue1, byte(max)} - } + return hidShortItem(hidUsageMaximum, uint32(max)) } func HIDPhysicalMinimum(min int) []byte { - switch { - case min < -32767 || 65535 < min: - return []byte{hidPhysicalMinimum + hidSizeValue4, uint8(min), uint8(min >> 8), uint8(min >> 16), uint8(min >> 24)} - case min < -127 || 255 < min: - return []byte{hidPhysicalMinimum + hidSizeValue2, uint8(min), uint8(min >> 8)} - default: - return []byte{hidPhysicalMinimum + hidSizeValue1, byte(min)} - } + return hidShortItemSigned(hidPhysicalMinimum, int32(min)) } func HIDPhysicalMaximum(max int) []byte { - switch { - case max < -32767 || 65535 < max: - return []byte{hidPhysicalMaximum + hidSizeValue4, uint8(max), uint8(max >> 8), uint8(max >> 16), uint8(max >> 24)} - case max < -127 || 255 < max: - return []byte{hidPhysicalMaximum + hidSizeValue2, uint8(max), uint8(max >> 8)} - default: - return []byte{hidPhysicalMaximum + hidSizeValue1, byte(max)} - } + return hidShortItemSigned(hidPhysicalMaximum, int32(max)) } func HIDUnitExponent(exp int) []byte { - return []byte{hidUnitExponent, byte(exp)} + // 4 Bit two's complement + return hidShortItem(hidUnitExponent, uint32(exp&0xF)) +} + +func HIDUnit(unit uint32) []byte { + return hidShortItem(hidUnit, unit) +} + +func HIDUsagePage(id uint16) []byte { + return hidShortItem(hidUsagePage, uint32(id)) +} + +func HIDUsage(id uint32) []byte { + return hidShortItem(hidUsage, id) +} + +func HIDCollection(id uint32) []byte { + return hidShortItem(hidCollection, id) +} + +func HIDInput(flags uint32) []byte { + return hidShortItem(hidInput, flags) } -func HIDUnit(unit int) []byte { - return []byte{hidUnit, byte(unit)} +func HIDOutput(flags uint32) []byte { + return hidShortItem(hidOutput, flags) } diff --git a/src/machine/usb/descriptor/joystick.go b/src/machine/usb/descriptor/joystick.go index 03bde25904..65756e0d63 100644 --- a/src/machine/usb/descriptor/joystick.go +++ b/src/machine/usb/descriptor/joystick.go @@ -95,17 +95,15 @@ var JoystickDefaultHIDReport = Append([][]byte{ HIDReportSize(4), HIDInputDataVarAbs, HIDUsageDesktopHatSwitch, - HIDLogicalMinimum(0), - HIDLogicalMaximum(7), - HIDPhysicalMinimum(0), - HIDPhysicalMaximum(315), - HIDUnit(0x14), HIDReportCount(1), HIDReportSize(4), - HIDInputDataVarAbs, + HIDInputConstVarAbs, HIDUsageDesktopPointer, HIDLogicalMinimum(-32767), HIDLogicalMaximum(32767), + HIDPhysicalMinimum(0), + HIDPhysicalMaximum(0), + HIDUnit(0), HIDReportCount(6), HIDReportSize(16), HIDCollectionPhysical, diff --git a/src/machine/usb/hid/joystick/state.go b/src/machine/usb/hid/joystick/state.go index 7cb47a77c7..08265ab128 100644 --- a/src/machine/usb/hid/joystick/state.go +++ b/src/machine/usb/hid/joystick/state.go @@ -67,6 +67,10 @@ func (c Definitions) Descriptor() []byte { func (c Definitions) NewState() State { bufSize := 1 + hatSwitches := make([]HatDirection, c.HatSwitchCnt) + for i := range hatSwitches { + hatSwitches[i] = HatCenter + } axises := make([]*AxisValue, 0, len(c.AxisDefs)) for _, v := range c.AxisDefs { @@ -77,16 +81,14 @@ func (c Definitions) NewState() State { } btnSize := (c.ButtonCnt + 7) / 8 bufSize += btnSize - if c.HatSwitchCnt > 0 { - bufSize++ - } + bufSize += (len(hatSwitches) + 1) / 2 bufSize += len(axises) * 2 initBuf := make([]byte, bufSize) initBuf[0] = c.ReportID return State{ buf: initBuf, Buttons: make([]byte, btnSize), - HatSwitches: make([]HatDirection, c.HatSwitchCnt), + HatSwitches: hatSwitches, Axises: axises, } } @@ -103,7 +105,11 @@ func (s State) MarshalBinary() ([]byte, error) { s.buf = append(s.buf, s.Buttons...) if len(s.HatSwitches) > 0 { hat := byte(0) - for _, v := range s.HatSwitches { + for i, v := range s.HatSwitches { + if i != 0 && i%2 == 0 { + s.buf = append(s.buf, hat) + hat = 0 + } hat <<= 4 hat |= byte(v & 0xf) } diff --git a/src/machine/virt.go b/src/machine/virt.go new file mode 100644 index 0000000000..2b28ae61e6 --- /dev/null +++ b/src/machine/virt.go @@ -0,0 +1,189 @@ +//go:build tinygo.riscv32 && virt + +// Machine implementation for VirtIO targets. +// At the moment only QEMU RISC-V is supported, but support for ARM for example +// should not be difficult to add with a change to virtioFindDevice. + +package machine + +import ( + "errors" + "runtime/volatile" + "sync" + "unsafe" +) + +const deviceName = "riscv-qemu" + +func (p Pin) Set(high bool) { + // no pins defined +} + +var rngLock sync.Mutex +var rngDevice *virtioDevice1 +var rngBuf volatile.Register32 + +var errNoRNG = errors.New("machine: no entropy source found") +var errNoRNGData = errors.New("machine: entropy source didn't return enough data") + +// GetRNG returns random numbers from a VirtIO entropy source. +// When running in QEMU, it requires adding the RNG device: +// +// -device virtio-rng-device +func GetRNG() (uint32, error) { + rngLock.Lock() + + // Initialize the device on first use. + if rngDevice == nil { + // Search for an available RNG. + rngDevice = virtioFindDevice(virtioDeviceEntropySource) + if rngDevice == nil { + rngLock.Unlock() + return 0, errNoRNG + } + + // Initialize the device. + rngDevice.status.Set(0) // reset device + rngDevice.status.Set(virtioDeviceStatusAcknowledge) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver) + rngDevice.hostFeaturesSel.Set(0) + rngDevice.status.Set(virtioDeviceStatusAcknowledge | virtioDeviceStatusDriver | virtioDeviceStatusDriverOk) + rngDevice.guestPageSize.Set(4096) + + // Configure queue, according to section 4.2.4 "Legacy interface". + // Note: we're skipping checks for queuePFM and queueNumMax. + rngDevice.queueSel.Set(0) // use queue 0 (the only queue) + rngDevice.queueNum.Set(1) // use a single buffer in the queue + rngDevice.queueAlign.Set(4096) // default alignment appears to be 4096 + rngDevice.queuePFN.Set(uint32(uintptr(unsafe.Pointer(&rngQueue))) / 4096) + + // Configure the only buffer in the queue (but don't increment + // rngQueue.available yet). + rngQueue.buffers[0].address = uint64(uintptr(unsafe.Pointer(&rngBuf))) + rngQueue.buffers[0].length = uint32(unsafe.Sizeof(rngBuf)) + rngQueue.buffers[0].flags = 2 // 2 means write-only buffer + } + + // Increment the available ring buffer. This doesn't actually change the + // buffer index (it's a ring with a single entry), but the number needs to + // be incremented otherwise the device won't recognize a new buffer. + index := rngQueue.available.index + rngQueue.available.index = index + 1 + rngDevice.queueNotify.Set(0) // notify the device of the 'new' (reused) buffer + for rngQueue.used.index.Get() != index+1 { + // Busy wait until the RNG buffer is filled. + // A better way would be to wait for an interrupt, but since this driver + // implementation is mostly used for testing it's good enough for now. + } + + // Check that we indeed got 4 bytes back. + if rngQueue.used.ring[0].length != 4 { + rngLock.Unlock() + return 0, errNoRNGData + } + + // Read the resulting random numbers. + result := rngBuf.Get() + + rngLock.Unlock() + + return result, nil +} + +// Implement a driver for the VirtIO entropy device. +// https://docs.oasis-open.org/virtio/virtio/v1.2/csd01/virtio-v1.2-csd01.html +// http://wiki.osdev.org/Virtio +// http://www.dumais.io/index.php?article=aca38a9a2b065b24dfa1dee728062a12 + +const ( + virtioDeviceStatusAcknowledge = 1 + virtioDeviceStatusDriver = 2 + virtioDeviceStatusDriverOk = 4 + virtioDeviceStatusFeaturesOk = 8 + virtioDeviceStatusFailed = 128 +) + +const ( + virtioDeviceReserved = iota + virtioDeviceNetworkCard + virtioDeviceBlockDevice + virtioDeviceConsole + virtioDeviceEntropySource + // there are more device types +) + +// VirtIO device version 1 +type virtioDevice1 struct { + magic volatile.Register32 // always 0x74726976 + version volatile.Register32 + deviceID volatile.Register32 + vendorID volatile.Register32 + hostFeatures volatile.Register32 + hostFeaturesSel volatile.Register32 + _ [2]uint32 + guestFeatures volatile.Register32 + guestFeaturesSel volatile.Register32 + guestPageSize volatile.Register32 + _ uint32 + queueSel volatile.Register32 + queueNumMax volatile.Register32 + queueNum volatile.Register32 + queueAlign volatile.Register32 + queuePFN volatile.Register32 + _ [3]uint32 + queueNotify volatile.Register32 + _ [3]uint32 + interruptStatus volatile.Register32 + interruptAck volatile.Register32 + _ [2]uint32 + status volatile.Register32 +} + +// VirtIO queue, with a single buffer. +type virtioQueue struct { + buffers [1]struct { + address uint64 + length uint32 + flags uint16 + next uint16 + } // 16 bytes + + available struct { + flags uint16 + index uint16 + ring [1]uint16 + eventIndex uint16 + } // 8 bytes + + _ [4096 - 16*1 - 8*1]byte // padding (to align on a 4096 byte boundary) + + used struct { + flags uint16 + index volatile.Register16 + ring [1]struct { + index uint32 + length uint32 + } + availEvent uint16 + } +} + +func virtioFindDevice(deviceID uint32) *virtioDevice1 { + // On RISC-V, QEMU defines 8 VirtIO devices starting at 0x10001000 and + // repeating every 0x1000 bytes. + // The memory map can be seen in the QEMU source code: + // https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c + for i := 0; i < 8; i++ { + dev := (*virtioDevice1)(unsafe.Pointer(uintptr(0x10001000 + i*0x1000))) + if dev.magic.Get() != 0x74726976 || dev.version.Get() != 1 || dev.deviceID.Get() != deviceID { + continue + } + return dev + } + return nil +} + +// A VirtIO queue needs to be page-aligned. +// +//go:align 4096 +var rngQueue virtioQueue diff --git a/src/machine/watchdog.go b/src/machine/watchdog.go index d1516350d7..d818a00860 100644 --- a/src/machine/watchdog.go +++ b/src/machine/watchdog.go @@ -1,4 +1,4 @@ -//go:build nrf52840 || nrf52833 || rp2040 || atsamd51 || atsame5x || stm32 +//go:build nrf52840 || nrf52833 || rp2040 || rp2350 || atsamd51 || atsame5x || stm32 package machine diff --git a/src/net b/src/net index a79417481d..ca7cd08f85 160000 --- a/src/net +++ b/src/net @@ -1 +1 @@ -Subproject commit a79417481d37e21f29d257c28fecc503df9703e0 +Subproject commit ca7cd08f851a1f3dde5fca2e217b7e06d17842ae diff --git a/src/os/deadline_test.go b/src/os/deadline_test.go new file mode 100644 index 0000000000..03097e46ef --- /dev/null +++ b/src/os/deadline_test.go @@ -0,0 +1,50 @@ +//go:build posix && !baremetal && !js + +package os_test + +import ( + "errors" + . "os" + "testing" +) + +func TestDeadlines(t *testing.T) { + // Create a handle to a known-good, existing file + f, err := Open("/dev/null") + if err != nil { + t.Fatal(err) + } + + if err := f.SetDeadline(0); err == nil { + if err != nil { + t.Errorf("wanted nil, got %v", err) + } + } + + if err := f.SetDeadline(1); err == nil { + if !errors.Is(err, ErrNotImplemented) { + t.Errorf("wanted ErrNotImplemented, got %v", err) + } + } + + if err := f.SetReadDeadline(1); err == nil { + if !errors.Is(err, ErrNotImplemented) { + t.Errorf("wanted ErrNotImplemented, got %v", err) + } + } + + if err := f.SetWriteDeadline(1); err == nil { + if !errors.Is(err, ErrNotImplemented) { + t.Errorf("wanted ErrNotImplemented, got %v", err) + } + } + + // Closed files must return an error + f.Close() + + if err := f.SetDeadline(0); err == nil { + if err != ErrClosed { + t.Errorf("wanted ErrClosed, got %v", err) + } + } +} diff --git a/src/os/dir_darwin.go b/src/os/dir_darwin.go index 3883a45c6a..645c91f8c1 100644 --- a/src/os/dir_darwin.go +++ b/src/os/dir_darwin.go @@ -135,7 +135,7 @@ func darwinOpenDir(fd syscallFd) (uintptr, string, error) { } var dir uintptr for { - dir, err = syscall.Fdopendir(fd2) + dir, err = fdopendir(fd2) if err != syscall.EINTR { break } @@ -149,6 +149,9 @@ func darwinOpenDir(fd syscallFd) (uintptr, string, error) { // Implemented in syscall/syscall_libc_darwin_*.go. +//go:linkname fdopendir syscall.fdopendir +func fdopendir(fd int) (dir uintptr, err error) + //go:linkname closedir syscall.closedir func closedir(dir uintptr) (err error) diff --git a/src/os/dir_other.go b/src/os/dir_other.go index 9a1b394213..0f32a12e4d 100644 --- a/src/os/dir_other.go +++ b/src/os/dir_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || windows +//go:build baremetal || js || windows || wasm_unknown || nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/dir_test.go b/src/os/dir_test.go index c09067df1c..e51e290b39 100644 --- a/src/os/dir_test.go +++ b/src/os/dir_test.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !js && !wasi && !386 && !arm) +//go:build darwin || (linux && !baremetal && !js && !wasip1 && !wasip2 && !386 && !arm) package os_test diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index baacdd68dc..7ff92d2318 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build linux && !baremetal && !wasi && !wasip1 +//go:build linux && !baremetal && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch package os diff --git a/src/os/dir_wasi.go b/src/os/dir_wasi.go index 23be3950ee..6d9313110e 100644 --- a/src/os/dir_wasi.go +++ b/src/os/dir_wasi.go @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// This file was derived from src/os/dir_darwin.go since the logic for wasi is +// This file was derived from src/os/dir_darwin.go since the logic for WASI is // fairly similar: we use fdopendir, fdclosedir, and readdir from wasi-libc in // a similar way that the darwin code uses functions from libc. -//go:build wasi || wasip1 +//go:build wasip1 || wasip2 package os diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go index 2527182fb6..9c2f611089 100644 --- a/src/os/dirent_linux.go +++ b/src/os/dirent_linux.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/env_unix_test.go b/src/os/env_unix_test.go index 034f481544..93dff91a14 100644 --- a/src/os/env_unix_test.go +++ b/src/os/env_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || linux || wasip1 +//go:build darwin || linux || wasip1 || wasip2 package os_test diff --git a/src/os/exec.go b/src/os/exec.go index a80c431696..28406f916b 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -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,11 +67,16 @@ 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{"fork/exec", name, ErrNotImplemented} + return startProcess(name, argv, attr) } func (p *Process) Wait() (*ProcessState, error) { + if p.Pid == -1 { + return nil, syscall.EINVAL + } return nil, ErrNotImplemented } @@ -72,3 +87,21 @@ func (p *Process) Kill() error { func (p *Process) Signal(sig Signal) error { return ErrNotImplemented } + +func Ignore(sig ...Signal) { + // leave all the signals unaltered + return +} + +// Release releases any resources associated with the Process p, +// rendering it unusable in the future. +// Release only needs to be called if Wait is not. +func (p *Process) Release() error { + return p.release() +} + +// FindProcess looks for a running process by its pid. +// Keep compatibility with golang and always succeed and return new proc with pid on Linux. +func FindProcess(pid int) (*Process, error) { + return findProcess(pid) +} diff --git a/src/os/exec_linux.go b/src/os/exec_linux.go new file mode 100644 index 0000000000..6914a2c285 --- /dev/null +++ b/src/os/exec_linux.go @@ -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 && !nintendoswitch + +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) +} diff --git a/src/os/exec_linux_test.go b/src/os/exec_linux_test.go new file mode 100644 index 0000000000..34f1fef983 --- /dev/null +++ b/src/os/exec_linux_test.go @@ -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) + } +} diff --git a/src/os/exec_other.go b/src/os/exec_other.go new file mode 100644 index 0000000000..b05e2830db --- /dev/null +++ b/src/os/exec_other.go @@ -0,0 +1,27 @@ +//go:build (!aix && !android && !freebsd && !linux && !netbsd && !openbsd && !plan9 && !solaris) || baremetal || tinygo.wasm || nintendoswitch + +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 +} diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go deleted file mode 100644 index 3ccb6963bb..0000000000 --- a/src/os/exec_posix.go +++ /dev/null @@ -1,21 +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 || windows - -package os - -import ( - "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 -) diff --git a/src/os/exec_test.go b/src/os/exec_test.go new file mode 100644 index 0000000000..b8adfb5487 --- /dev/null +++ b/src/os/exec_test.go @@ -0,0 +1,28 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "testing" +) + +func TestFindProcess(t *testing.T) { + // NOTE: For now, we only test the Linux case since only exec_posix.go is currently the only implementation. + // Linux guarantees that there is pid 0 + proc, err := FindProcess(0) + if err != nil { + t.Error("FindProcess(0): wanted err == nil, got %v:", err) + } + + if proc.Pid != 0 { + t.Error("Expected pid 0, got: ", proc.Pid) + } + + pid0 := Process{Pid: 0} + if *proc != pid0 { + t.Error("Expected &Process{Pid: 0}, got", *proc) + } +} diff --git a/src/os/file.go b/src/os/file.go index ff62899bd9..e7dd214b73 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -23,6 +23,7 @@ import ( "io/fs" "runtime" "syscall" + "time" ) // Seek whence values. @@ -256,6 +257,29 @@ func (f *File) SyscallConn() (conn syscall.RawConn, err error) { return } +// SetDeadline sets the read and write deadlines for a File. +// Calls to SetDeadline for files that do not support deadlines will return ErrNoDeadline +// This stub always returns ErrNoDeadline. +// A zero value for t means I/O operations will not time out. +func (f *File) SetDeadline(t time.Time) error { + if f.handle == nil { + return ErrClosed + } + return f.setDeadline(t) +} + +// SetReadDeadline sets the deadline for future Read calls and any +// currently-blocked Read call. +func (f *File) SetReadDeadline(t time.Time) error { + return f.setReadDeadline(t) +} + +// SetWriteDeadline sets the deadline for any future Write calls and any +// currently-blocked Write call. +func (f *File) SetWriteDeadline(t time.Time) error { + return f.setWriteDeadline(t) +} + // fd is an internal interface that is used to try a type assertion in order to // call the Fd() method of the underlying file handle if it is implemented. type fd interface { @@ -283,14 +307,26 @@ func (f *File) Sync() (err error) { return } -// Truncate is a stub, not yet implemented -func (f *File) Truncate(size int64) (err error) { +// Chmod changes the mode of the file to mode. If there is an error, it will be +// of type *PathError. +func (f *File) Chmod(mode FileMode) (err error) { if f.handle == nil { err = ErrClosed } else { - err = ErrNotImplemented + err = f.chmod(mode) } - return &PathError{Op: "truncate", Path: f.name, Err: err} + return +} + +// Chdir changes the current working directory to the file, which must be a +// directory. If there is an error, it will be of type *PathError. +func (f *File) Chdir() (err error) { + if f.handle == nil { + err = ErrClosed + } else { + err = f.chdir() + } + return } // LinkError records an error during a link or symlink or rename system call and @@ -310,6 +346,7 @@ func (e *LinkError) Unwrap() error { return e.Err } +// OpenFile flag values. const ( O_RDONLY int = syscall.O_RDONLY O_WRONLY int = syscall.O_WRONLY diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index 0436d1222e..33445e79f9 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -1,6 +1,6 @@ -//go:build !baremetal && !js +//go:build !baremetal && !js && !wasm_unknown && !nintendoswitch -// Portions copyright 2009 The Go Authors. All rights reserved. +// Portions copyright 2009-2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -97,6 +97,11 @@ type unixFileHandle uintptr // read and any error encountered. At end of file, Read returns 0, io.EOF. func (f unixFileHandle) Read(b []byte) (n int, err error) { n, err = syscall.Read(syscallFd(f), b) + // In case of EISDIR, n == -1. + // This breaks the assumption that n always represent the number of read bytes. + if err == syscall.EISDIR { + n = 0 + } err = handleSyscallError(err) if n == 0 && len(b) > 0 && err == nil { err = io.EOF @@ -147,6 +152,20 @@ func Chmod(name string, mode FileMode) error { return nil } +// Chown changes the numeric uid and gid of the named file. +// If the file is a symbolic link, it changes the uid and gid of the link's target. +// A uid or gid of -1 means to not change that value. +// If there is an error, it will be of type *PathError. +func Chown(name string, uid, gid int) error { + e := ignoringEINTR(func() error { + return syscall.Chown(name, uid, gid) + }) + if e != nil { + return &PathError{Op: "chown", Path: name, Err: e} + } + return nil +} + // ignoringEINTR makes a function call and repeats it if it returns an // EINTR error. This appears to be required even though we install all // signal handlers with SA_RESTART: see #22838, #38033, #38836, #40846. diff --git a/src/os/file_anyos_test.go b/src/os/file_anyos_test.go index c7d6e50ac7..97f8ea13e5 100644 --- a/src/os/file_anyos_test.go +++ b/src/os/file_anyos_test.go @@ -185,3 +185,22 @@ func TestClose(t *testing.T) { } } } + +func TestReadOnDir(t *testing.T) { + name := TempDir() + "/_os_test_TestReadOnDir" + defer Remove(name) + f, err := OpenFile(name, O_RDWR|O_CREATE, 0644) + if err != nil { + t.Errorf("OpenFile %s: %s", name, err) + return + } + var buf [32]byte + n, err := f.Read(buf[:]) + if err == nil { + t.Errorf("Error expected") + return + } + if n != 0 { + t.Errorf("Wrong read bytes: %s", err) + } +} diff --git a/src/os/file_darwin.go b/src/os/file_darwin.go new file mode 100644 index 0000000000..8d96b7296e --- /dev/null +++ b/src/os/file_darwin.go @@ -0,0 +1,7 @@ +package os + +import "syscall" + +func pipe(p []int) error { + return syscall.Pipe(p) +} diff --git a/src/os/file_notdarwin.go b/src/os/file_notdarwin.go new file mode 100644 index 0000000000..d59a8eb6c1 --- /dev/null +++ b/src/os/file_notdarwin.go @@ -0,0 +1,9 @@ +//go:build (linux && !baremetal && !wasm_unknown) || wasip1 || wasip2 + +package os + +import "syscall" + +func pipe(p []int) error { + return syscall.Pipe2(p, syscall.O_CLOEXEC) +} diff --git a/src/os/file_other.go b/src/os/file_other.go index 0ceee0020b..1fdf4b1ef4 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi && !wasip1) +//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) || nintendoswitch package os @@ -42,6 +42,12 @@ func NewFile(fd uintptr, name string) *File { return &File{&file{handle: stdioFileHandle(fd), name: name}} } +// Chdir changes the current working directory to the named directory. +// If there is an error, it will be of type *PathError. +func Chdir(dir string) error { + return ErrNotImplemented +} + // Rename renames (moves) oldpath to newpath. // If newpath already exists and is not a directory, Rename replaces it. // OS-specific restrictions may apply when oldpath and newpath are in different directories. @@ -128,6 +134,10 @@ func Pipe() (r *File, w *File, err error) { return nil, nil, ErrNotImplemented } +func Symlink(oldname, newname string) error { + return ErrNotImplemented +} + func Readlink(name string) (string, error) { return "", ErrNotImplemented } @@ -135,3 +145,25 @@ func Readlink(name string) (string, error) { func tempDir() string { return "/tmp" } + +// Truncate is unsupported on this system. +func Truncate(filename string, size int64) (err error) { + return ErrUnsupported +} + +// Truncate is unsupported on this system. +func (f *File) Truncate(size int64) (err error) { + if f.handle == nil { + return ErrClosed + } + + return Truncate(f.name, size) +} + +func (f *File) chmod(mode FileMode) error { + return ErrUnsupported +} + +func (f *File) chdir() error { + return ErrNotImplemented +} diff --git a/src/os/file_posix.go b/src/os/file_posix.go index b31453d645..a10ff49196 100644 --- a/src/os/file_posix.go +++ b/src/os/file_posix.go @@ -4,7 +4,35 @@ import ( "time" ) +//TODO: re-implement the ErrNoDeadline error in the correct code path + // Chtimes is a stub, not yet implemented func Chtimes(name string, atime time.Time, mtime time.Time) error { return ErrNotImplemented } + +// setDeadline sets the read and write deadline. +func (f *File) setDeadline(t time.Time) error { + if t.IsZero() { + return nil + } + return ErrNotImplemented +} + +// setReadDeadline sets the read deadline, not yet implemented +// A zero value for t means Read will not time out. +func (f *File) setReadDeadline(t time.Time) error { + if t.IsZero() { + return nil + } + return ErrNotImplemented +} + +// setWriteDeadline sets the write deadline, not yet implemented +// A zero value for t means Read will not time out. +func (f *File) setWriteDeadline(t time.Time) error { + if t.IsZero() { + return nil + } + return ErrNotImplemented +} diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 665fb0937e..9dc3a91e09 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // target wasi sets GOOS=linux and thus the +linux build tag, // even though it doesn't show up in "tinygo info target -wasi" @@ -12,6 +12,7 @@ package os import ( "io" "syscall" + _ "unsafe" ) const DevNull = "/dev/null" @@ -55,9 +56,22 @@ func NewFile(fd uintptr, name string) *File { return &File{&file{handle: unixFileHandle(fd), name: name}} } +// Truncate changes the size of the named file. +// If the file is a symbolic link, it changes the size of the link's target. +// If there is an error, it will be of type *PathError. +func Truncate(name string, size int64) error { + e := ignoringEINTR(func() error { + return syscall.Truncate(name, size) + }) + if e != nil { + return &PathError{Op: "truncate", Path: name, Err: e} + } + return nil +} + func Pipe() (r *File, w *File, err error) { var p [2]int - err = handleSyscallError(syscall.Pipe2(p[:], syscall.O_CLOEXEC)) + err = handleSyscallError(pipe(p[:])) if err != nil { return } @@ -74,6 +88,19 @@ func tempDir() string { return dir } +// Link creates newname as a hard link to the oldname file. +// If there is an error, it will be of type *LinkError. +func Link(oldname, newname string) error { + e := ignoringEINTR(func() error { + return syscall.Link(oldname, newname) + }) + + if e != nil { + return &LinkError{"link", oldname, newname, e} + } + return nil +} + // Symlink creates newname as a symbolic link to oldname. // On Windows, a symlink to a non-existent oldname creates a file symlink; // if oldname is later created as a directory the symlink will not work. @@ -112,6 +139,49 @@ func Readlink(name string) (string, error) { } } +// Truncate changes the size of the file. +// It does not change the I/O offset. +// If there is an error, it will be of type *PathError. +// Alternatively just use 'raw' syscall by file name +func (f *File) Truncate(size int64) (err error) { + if f.handle == nil { + return ErrClosed + } + + return Truncate(f.name, size) +} + +func (f *File) chmod(mode FileMode) error { + if f.handle == nil { + return ErrClosed + } + + longName := fixLongPath(f.name) + e := ignoringEINTR(func() error { + return syscall.Chmod(longName, syscallMode(mode)) + }) + if e != nil { + return &PathError{Op: "chmod", Path: f.name, Err: e} + } + return nil +} + +func (f *File) chdir() error { + if f.handle == nil { + return ErrClosed + } + + // TODO: use syscall.Fchdir instead + longName := fixLongPath(f.name) + e := ignoringEINTR(func() error { + return syscall.Chdir(longName) + }) + if e != nil { + return &PathError{Op: "chdir", Path: f.name, Err: e} + } + return nil +} + // ReadAt reads up to len(b) bytes from the File starting at the given absolute offset. // It returns the number of bytes read and any error encountered, possibly io.EOF. // At end of file, Pread returns 0, io.EOF. @@ -185,3 +255,17 @@ func newUnixDirent(parent, name string, typ FileMode) (DirEntry, error) { ude.info = info return ude, nil } + +// Since internal/poll is not available, we need to stub this out. +// Big go requires the option to add the fd to the polling system. +// +//go:linkname net_newUnixFile net.newUnixFile +func net_newUnixFile(fd int, name string) *File { + if fd < 0 { + panic("invalid FD") + } + + // see src/os/file_unix.go:162 newFile for the original implementation. + // return newFile(fd, name, kindSock, true) + return NewFile(uintptr(fd), name) +} diff --git a/src/os/file_windows.go b/src/os/file_windows.go index 70f3a3dd0c..50c5ee7a82 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -59,6 +59,16 @@ func Pipe() (r *File, w *File, err error) { return } +func (f *unixFileHandle) Truncate(size int64) error { + return ErrNotImplemented +} + +// Truncate changes the size of the named file. +// If the file is a symbolic link, it changes the size of the link's target. +func Truncate(name string, size int64) error { + return &PathError{Op: "truncate", Path: name, Err: ErrNotImplemented} +} + func tempDir() string { n := uint32(syscall.MAX_PATH) for { @@ -106,6 +116,15 @@ func (f unixFileHandle) Sync() error { return ErrNotImplemented } +// Truncate changes the size of the named file. +// If the file is a symbolic link, it changes the size of the link's target. +func (f *File) Truncate(size int64) error { + if f.handle == nil { + return &PathError{Op: "truncate", Path: f.name, Err: ErrClosed} + } + return Truncate(f.name, size) +} + // isWindowsNulName reports whether name is os.DevNull ('NUL') on Windows. // True is returned if name is 'NUL' whatever the case. func isWindowsNulName(name string) bool { @@ -123,3 +142,11 @@ func isWindowsNulName(name string) bool { } return true } + +func (f *File) chmod(mode FileMode) error { + return ErrNotImplemented +} + +func (f *File) chdir() error { + return ErrNotImplemented +} diff --git a/src/os/filesystem.go b/src/os/filesystem.go index f38b22660d..8b44367075 100644 --- a/src/os/filesystem.go +++ b/src/os/filesystem.go @@ -30,7 +30,7 @@ type Filesystem interface { // OpenFile opens the named file. OpenFile(name string, flag int, perm FileMode) (uintptr, error) - // Mkdir creates a new directoy with the specified permission (before + // Mkdir creates a new directory with the specified permission (before // umask). Some filesystems may not support directories or permissions. Mkdir(name string, perm FileMode) error diff --git a/src/os/getpagesize_test.go b/src/os/getpagesize_test.go index 80475552be..5017a9b807 100644 --- a/src/os/getpagesize_test.go +++ b/src/os/getpagesize_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal) || wasip1 +//go:build windows || darwin || (linux && !baremetal) || wasip1 || wasip2 package os_test diff --git a/src/os/is_wasi_no_test.go b/src/os/is_wasi_no_test.go deleted file mode 100644 index aa7745085b..0000000000 --- a/src/os/is_wasi_no_test.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build !wasi && !wasip1 - -package os_test - -const isWASI = false diff --git a/src/os/is_wasi_test.go b/src/os/is_wasi_test.go deleted file mode 100644 index 619b1cb64f..0000000000 --- a/src/os/is_wasi_test.go +++ /dev/null @@ -1,5 +0,0 @@ -//go:build wasi || wasip1 - -package os_test - -const isWASI = true diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 44606e163b..7b4b383772 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal) || wasip1 +//go:build windows || darwin || (linux && !baremetal) || wasip1 || wasip2 package os_test @@ -56,7 +56,7 @@ func TestStatBadDir(t *testing.T) { badDir := filepath.Join(dir, "not-exist/really-not-exist") _, err := Stat(badDir) if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir { - t.Errorf("Mkdir error = %#v; want PathError for path %q satisifying IsNotExist", err, badDir) + t.Errorf("Mkdir error = %#v; want PathError for path %q satisfying IsNotExist", err, badDir) } } @@ -275,7 +275,7 @@ func TestDirFS(t *testing.T) { t.Log("TODO: implement Readdir for Windows") return } - if isWASI { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("TODO: allow foo/bar/. as synonym for path foo/bar on wasi?") return } @@ -296,7 +296,7 @@ func TestDirFSPathsValid(t *testing.T) { t.Log("skipping on Windows") return } - if isWASI { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("skipping on wasi because it fails on wasi on windows") return } diff --git a/src/os/os_chmod_test.go b/src/os/os_chmod_test.go index 911438d954..ad151abb03 100644 --- a/src/os/os_chmod_test.go +++ b/src/os/os_chmod_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -9,12 +9,15 @@ package os_test import ( + "errors" + "io/fs" . "os" "runtime" "testing" ) func TestChmod(t *testing.T) { + // Chmod f := newFile("TestChmod", t) defer Remove(f.Name()) defer f.Close() @@ -28,4 +31,42 @@ func TestChmod(t *testing.T) { t.Fatalf("chmod %s %#o: %s", f.Name(), fm, err) } checkMode(t, f.Name(), fm) + +} + +// Since testing syscalls requires a static, predictable environment that has to be controlled +// by the CI, we don't test for success but for failures and verify that the error messages are as expected. +// EACCES is returned when the user does not have the required permissions to change the ownership of the file +// ENOENT is returned when the file does not exist +// ENOTDIR is returned when the file is not a directory +func TestChownErr(t *testing.T) { + if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { + t.Log("skipping on " + runtime.GOOS) + return + } + + var ( + TEST_UID_ROOT = 0 + TEST_GID_ROOT = 0 + ) + + f := newFile("TestChown", t) + defer Remove(f.Name()) + defer f.Close() + + // EACCES + if err := Chown(f.Name(), TEST_UID_ROOT, TEST_GID_ROOT); err != nil { + errCmp := fs.PathError{Op: "chown", Path: f.Name(), Err: errors.New("operation not permitted")} + if errors.Is(err, &errCmp) { + t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'operation not permitted'", f.Name(), TEST_UID_ROOT, TEST_GID_ROOT, err) + } + } + + // ENOENT + if err := Chown("invalid", Geteuid(), Getgid()); err != nil { + errCmp := fs.PathError{Op: "chown", Path: "invalid", Err: errors.New("no such file or directory")} + if errors.Is(err, &errCmp) { + t.Fatalf("chown(%s, uid=%v, gid=%v): got '%v', want 'no such file or directory'", f.Name(), Geteuid(), Getegid(), err) + } + } } diff --git a/src/os/os_hardlink_test.go b/src/os/os_hardlink_test.go new file mode 100644 index 0000000000..9fa1ecb75f --- /dev/null +++ b/src/os/os_hardlink_test.go @@ -0,0 +1,53 @@ +//go:build !windows && !baremetal && !js && !wasip1 && !wasm_unknown + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "syscall" + "testing" +) + +func TestHardlink(t *testing.T) { + defer chtmpdir(t)() + from, to := "hardlinktestfrom", "hardlinktestto" + + file, err := Create(to) + if err != nil { + t.Fatalf("Create(%q) failed: %v", to, err) + } + if err = file.Close(); err != nil { + t.Errorf("Close(%q) failed: %v", to, err) + } + err = Link(to, from) + if err != nil { + t.Fatalf("Link(%q, %q) failed: %v", to, from, err) + } + + tostat, err := Lstat(to) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", to, err) + } + fromstat, err := Stat(from) + if err != nil { + t.Fatalf("Stat(%q) failed: %v", from, err) + } + if !SameFile(tostat, fromstat) { + t.Errorf("Symlink(%q, %q) did not create symlink", to, from) + } + + fromstat, err = Lstat(from) + if err != nil { + t.Fatalf("Lstat(%q) failed: %v", from, err) + } + // if they have the same inode, they are hard links + if fromstat.Sys().(*syscall.Stat_t).Ino != tostat.Sys().(*syscall.Stat_t).Ino { + t.Fatalf("Lstat(%q).Sys().Ino = %v, Lstat(%q).Sys().Ino = %v, want the same", to, tostat.Sys().(*syscall.Stat_t).Ino, from, fromstat.Sys().(*syscall.Stat_t).Ino) + } + + file.Close() +} diff --git a/src/os/os_symlink_test.go b/src/os/os_symlink_test.go index f252116f5a..baa7047b27 100644 --- a/src/os/os_symlink_test.go +++ b/src/os/os_symlink_test.go @@ -1,4 +1,4 @@ -//go:build !windows && !baremetal && !js && !wasi && !wasip1 +//go:build !windows && !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/osexec.go b/src/os/osexec.go new file mode 100644 index 0000000000..57139d1b64 --- /dev/null +++ b/src/os/osexec.go @@ -0,0 +1,58 @@ +//go:build linux && !baremetal && !tinygo.wasm && !nintendoswitch + +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 diff --git a/src/os/path_unix.go b/src/os/path_unix.go index 97a028c5c5..9bb5c726b8 100644 --- a/src/os/path_unix.go +++ b/src/os/path_unix.go @@ -7,8 +7,8 @@ package os const ( - PathSeparator = '/' // OS-specific path separator - PathListSeparator = ':' // OS-specific path list separator + PathSeparator = '/' // PathSeparator is the OS-specific path separator + PathListSeparator = ':' // PathListSeparator is the OS-specific path list separator ) // IsPathSeparator reports whether c is a directory separator character. diff --git a/src/os/path_windows.go b/src/os/path_windows.go index a96245f358..7118c45b5a 100644 --- a/src/os/path_windows.go +++ b/src/os/path_windows.go @@ -5,8 +5,8 @@ package os const ( - PathSeparator = '\\' // OS-specific path separator - PathListSeparator = ';' // OS-specific path list separator + PathSeparator = '\\' // PathSeparator is the OS-specific path separator + PathListSeparator = ';' // PathListSeparator is the OS-specific path list separator ) // IsPathSeparator reports whether c is a directory separator character. diff --git a/src/os/pipe_test.go b/src/os/pipe_test.go index b0553ffdb4..98c86089fd 100644 --- a/src/os/pipe_test.go +++ b/src/os/pipe_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal && !wasi) +//go:build windows || darwin || (linux && !baremetal && !wasip1 && !wasip2) // Copyright 2021 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/read_test.go b/src/os/read_test.go index e037b23498..68eb02966b 100644 --- a/src/os/read_test.go +++ b/src/os/read_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index ae945c2497..4b37b3bab6 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasip2 && !wasm_unknown && !nintendoswitch package os diff --git a/src/os/removeall_other.go b/src/os/removeall_other.go index ec055a9875..bf3265dee8 100644 --- a/src/os/removeall_other.go +++ b/src/os/removeall_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasi || wasip1 +//go:build baremetal || js || wasip1 || wasip2 || wasm_unknown || nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go index ea7c83b4e4..9f49e6793b 100644 --- a/src/os/removeall_test.go +++ b/src/os/removeall_test.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal && !js && !wasi) +//go:build darwin || (linux && !baremetal && !js && !wasip1 && !wasip2) // TODO: implement ReadDir on windows diff --git a/src/os/seek_unix_bad.go b/src/os/seek_unix_bad.go index 1f09426aec..047c6346a4 100644 --- a/src/os/seek_unix_bad.go +++ b/src/os/seek_unix_bad.go @@ -1,4 +1,4 @@ -//go:build (linux && !baremetal && 386) || (linux && !baremetal && arm && !wasi) +//go:build (linux && !baremetal && 386) || (linux && !baremetal && arm && !wasip1 && !wasip2) package os @@ -11,7 +11,7 @@ import ( // In particular, on i386 and arm, the function syscall.seek is missing, breaking syscall.Seek. // This in turn causes os.(*File).Seek, time, io/fs, and path/filepath to fail to link. // -// To temporarly let all the above at least link, provide a stub for syscall.seek. +// To temporarily let all the above at least link, provide a stub for syscall.seek. // This belongs in syscall, but on linux, we use upstream's syscall. // Remove once we support Go Assembly. // TODO: make this a non-stub, and thus fix the whole problem? diff --git a/src/os/stat_linuxlike.go b/src/os/stat_linuxlike.go index f2ff8a5f61..c9cfd396ff 100644 --- a/src/os/stat_linuxlike.go +++ b/src/os/stat_linuxlike.go @@ -1,4 +1,4 @@ -//go:build (linux && !baremetal) || wasip1 +//go:build (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_other.go b/src/os/stat_other.go index ff1bc37745..59331bc510 100644 --- a/src/os/stat_other.go +++ b/src/os/stat_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi && !wasip1) +//go:build baremetal || (tinygo.wasm && !wasip1 && !wasip2) || nintendoswitch // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/stat_unix.go b/src/os/stat_unix.go index 54b2bb4857..9882608d65 100644 --- a/src/os/stat_unix.go +++ b/src/os/stat_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/tempfile.go b/src/os/tempfile.go index 383d4c1815..5a94a6e2aa 100644 --- a/src/os/tempfile.go +++ b/src/os/tempfile.go @@ -142,7 +142,7 @@ func joinPath(dir, name string) string { return dir + string(PathSeparator) + name } -// LastIndexByte from the strings package. +// lastIndex from the strings package. func lastIndex(s string, sep byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == sep { diff --git a/src/os/tempfile_test.go b/src/os/tempfile_test.go index 4b7416f4e0..88f6481c44 100644 --- a/src/os/tempfile_test.go +++ b/src/os/tempfile_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !baremetal && !js && !wasi && !wasip1 +//go:build !baremetal && !js && !wasip1 && !wasip2 package os_test @@ -158,7 +158,7 @@ func TestMkdirTempBadDir(t *testing.T) { badDir := filepath.Join(dir, "not-exist") _, err = MkdirTemp(badDir, "foo") if pe, ok := err.(*fs.PathError); !ok || !IsNotExist(err) || pe.Path != badDir { - t.Errorf("TempDir error = %#v; want PathError for path %q satisifying IsNotExist", err, badDir) + t.Errorf("TempDir error = %#v; want PathError for path %q satisfying IsNotExist", err, badDir) } } diff --git a/src/os/truncate_test.go b/src/os/truncate_test.go new file mode 100644 index 0000000000..2b1d982ba2 --- /dev/null +++ b/src/os/truncate_test.go @@ -0,0 +1,61 @@ +//go:build darwin || (linux && !baremetal && !js && !wasi) + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os_test + +import ( + . "os" + "path/filepath" + "runtime" + "testing" +) + +func TestTruncate(t *testing.T) { + // Truncate is not supported on Windows or wasi at the moment + if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { + t.Logf("skipping test on %s", runtime.GOOS) + return + } + + tmpDir := t.TempDir() + file := filepath.Join(tmpDir, "truncate_test") + + fd, err := Create(file) + if err != nil { + t.Fatalf("create %q: got %v, want nil", file, err) + } + defer fd.Close() + + // truncate up to 0x100 + if err := fd.Truncate(0x100); err != nil { + t.Fatalf("truncate %q: got %v, want nil", file, err) + } + + // check if size is 0x100 + fi, err := Stat(file) + if err != nil { + t.Fatalf("stat %q: got %v, want nil", file, err) + } + + if fi.Size() != 0x100 { + t.Fatalf("size of %q is %d; want 0x100", file, fi.Size()) + } + + // truncate down to 0x80 + if err := fd.Truncate(0x80); err != nil { + t.Fatalf("truncate %q: got %v, want nil", file, err) + } + + // check if size is 0x80 + fi, err = Stat(file) + if err != nil { + t.Fatalf("stat %q: got %v, want nil", file, err) + } + + if fi.Size() != 0x80 { + t.Fatalf("size of %q is %d; want 0x80", file, fi.Size()) + } +} diff --git a/src/os/types_anyos.go b/src/os/types_anyos.go index cb3a0b9491..9109fa6177 100644 --- a/src/os/types_anyos.go +++ b/src/os/types_anyos.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js +//go:build !baremetal && !js && !wasm_unknown && !nintendoswitch // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/types_unix.go b/src/os/types_unix.go index 943fc00f52..54c5c4969b 100644 --- a/src/os/types_unix.go +++ b/src/os/types_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) || wasip1 +//go:build darwin || (linux && !baremetal && !wasm_unknown && !nintendoswitch) || wasip1 || wasip2 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/user/user.go b/src/os/user/user.go deleted file mode 100644 index 7939380fb9..0000000000 --- a/src/os/user/user.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2016 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. - -package user - -import "errors" - -// User represents a user account. -type User struct { - // Uid is the user ID. - // On POSIX systems, this is a decimal number representing the uid. - // On Windows, this is a security identifier (SID) in a string format. - // On Plan 9, this is the contents of /dev/user. - Uid string - // Gid is the primary group ID. - // On POSIX systems, this is a decimal number representing the gid. - // On Windows, this is a SID in a string format. - // On Plan 9, this is the contents of /dev/user. - Gid string - // Username is the login name. - Username string - // Name is the user's real or display name. - // It might be blank. - // On POSIX systems, this is the first (or only) entry in the GECOS field - // list. - // On Windows, this is the user's display name. - // On Plan 9, this is the contents of /dev/user. - Name string - // HomeDir is the path to the user's home directory (if they have one). - HomeDir string -} - -// Current returns the current user. -// -// The first call will cache the current user information. -// Subsequent calls will return the cached value and will not reflect -// changes to the current user. -func Current() (*User, error) { - return nil, errors.New("user: Current not implemented") -} diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index ef8c7da433..b009c325a8 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -381,8 +381,6 @@ func TestSetValue(t *testing.T) { } } -/* - func TestMapIterSet(t *testing.T) { m := make(map[string]any, len(valueTests)) for _, tt := range valueTests { @@ -430,8 +428,6 @@ func TestMapIterSet(t *testing.T) { } } -*/ - func TestCanIntUintFloatComplex(t *testing.T) { type integer int type uinteger uint @@ -698,8 +694,6 @@ func TestMapSetNil(t *testing.T) { } } -/* - func TestAll(t *testing.T) { testType(t, 1, TypeOf((int8)(0)), "int8") testType(t, 2, TypeOf((*int8)(nil)).Elem(), "int8") @@ -747,8 +741,6 @@ func TestAll(t *testing.T) { testType(t, 14, typ, "[]uint32") } -*/ - func TestInterfaceGet(t *testing.T) { var inter struct { E any @@ -1272,8 +1264,6 @@ func TestDeepEqualUnexportedMap(t *testing.T) { } } -/* - var deepEqualPerfTests = []struct { x, y any }{ @@ -1339,8 +1329,6 @@ func TestDeepEqualAllocs(t *testing.T) { } } -*/ - func check2ndField(x any, offs uintptr, t *testing.T) { s := ValueOf(x) f := s.Type().Field(1) @@ -1600,7 +1588,8 @@ func TestIsZero(t *testing.T) { */ } -/* +// extra comment for gofmt + func TestInterfaceExtraction(t *testing.T) { var s struct { W io.Writer @@ -1612,9 +1601,6 @@ func TestInterfaceExtraction(t *testing.T) { t.Error("Interface() on interface: ", v, s.W) } } - -*/ - func TestNilPtrValueSub(t *testing.T) { var pi *int if pv := ValueOf(pi); pv.Elem().IsValid() { @@ -3368,6 +3354,8 @@ func TestNestedMethods(t *testing.T) { } } +*/ + type unexp struct{} func (*unexp) f() (int32, int8) { return 7, 7 } @@ -3379,8 +3367,6 @@ type unexpI interface { var unexpi unexpI = new(unexp) -/* - func TestUnexportedMethods(t *testing.T) { typ := TypeOf(unexpi) @@ -3389,8 +3375,6 @@ func TestUnexportedMethods(t *testing.T) { } } -*/ - type InnerInt struct { X int } @@ -3432,6 +3416,8 @@ func TestEmbeddedMethods(t *testing.T) { } } +*/ + type FuncDDD func(...any) error func (f FuncDDD) M() {} @@ -3443,6 +3429,7 @@ func TestNumMethodOnDDD(t *testing.T) { } } +/* func TestPtrTo(t *testing.T) { // This block of code means that the ptrToThis field of the // reflect data for *unsafe.Pointer is non zero, see @@ -3489,6 +3476,8 @@ func TestPtrToGC(t *testing.T) { } } +*/ + func TestAddr(t *testing.T) { var p struct { X, Y int @@ -3591,8 +3580,6 @@ func TestAllocations(t *testing.T) { }) } -*/ - func TestSmallNegativeInt(t *testing.T) { i := int16(-1) v := ValueOf(i) @@ -4906,7 +4893,7 @@ func TestComparable(t *testing.T) { } } -func TestOverflow(t *testing.T) { +func TestValueOverflow(t *testing.T) { if ovf := V(float64(0)).OverflowFloat(1e300); ovf { t.Errorf("%v wrongly overflows float64", 1e300) } @@ -4945,6 +4932,45 @@ func TestOverflow(t *testing.T) { } } +func TestTypeOverflow(t *testing.T) { + if ovf := TypeFor[float64]().OverflowFloat(1e300); ovf { + t.Errorf("%v wrongly overflows float64", 1e300) + } + + maxFloat32 := float64((1<<24 - 1) << (127 - 23)) + if ovf := TypeFor[float32]().OverflowFloat(maxFloat32); ovf { + t.Errorf("%v wrongly overflows float32", maxFloat32) + } + ovfFloat32 := float64((1<<24-1)<<(127-23) + 1<<(127-52)) + if ovf := TypeFor[float32]().OverflowFloat(ovfFloat32); !ovf { + t.Errorf("%v should overflow float32", ovfFloat32) + } + if ovf := TypeFor[float32]().OverflowFloat(-ovfFloat32); !ovf { + t.Errorf("%v should overflow float32", -ovfFloat32) + } + + maxInt32 := int64(0x7fffffff) + if ovf := TypeFor[int32]().OverflowInt(maxInt32); ovf { + t.Errorf("%v wrongly overflows int32", maxInt32) + } + if ovf := TypeFor[int32]().OverflowInt(-1 << 31); ovf { + t.Errorf("%v wrongly overflows int32", -int64(1)<<31) + } + ovfInt32 := int64(1 << 31) + if ovf := TypeFor[int32]().OverflowInt(ovfInt32); !ovf { + t.Errorf("%v should overflow int32", ovfInt32) + } + + maxUint32 := uint64(0xffffffff) + if ovf := TypeFor[uint32]().OverflowUint(maxUint32); ovf { + t.Errorf("%v wrongly overflows uint32", maxUint32) + } + ovfUint32 := uint64(1 << 32) + if ovf := TypeFor[uint32]().OverflowUint(ovfUint32); !ovf { + t.Errorf("%v should overflow uint32", ovfUint32) + } +} + /* func checkSameType(t *testing.T, x Type, y any) { @@ -7925,6 +7951,8 @@ func TestConvertibleTo(t *testing.T) { } } +*/ + func TestSetIter(t *testing.T) { data := map[string]int{ "foo": 1, @@ -8014,6 +8042,8 @@ func TestSetIter(t *testing.T) { } } +/* + func TestMethodCallValueCodePtr(t *testing.T) { m := ValueOf(Point{}).Method(1) want := MethodValueCallCodePtr() diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index 18a728458c..362385aed8 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -1,196 +1,7 @@ -// 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. - -// Deep equality test via reflection - package reflect -import "unsafe" - -// During deepValueEqual, must keep track of checks that are -// in progress. The comparison algorithm assumes that all -// checks in progress are true when it reencounters them. -// Visited comparisons are stored in a map indexed by visit. -type visit struct { - a1 unsafe.Pointer - a2 unsafe.Pointer - typ *rawType -} - -// Tests for deep equality using reflected types. The map argument tracks -// comparisons that have already been seen, which allows short circuiting on -// recursive types. -func deepValueEqual(v1, v2 Value, visited map[visit]struct{}) bool { - if !v1.IsValid() || !v2.IsValid() { - return v1.IsValid() == v2.IsValid() - } - if v1.typecode != v2.typecode { - return false - } - - // We want to avoid putting more in the visited map than we need to. - // For any possible reference cycle that might be encountered, - // hard(v1, v2) needs to return true for at least one of the types in the cycle, - // and it's safe and valid to get Value's internal pointer. - hard := func(v1, v2 Value) bool { - switch v1.Kind() { - case Map, Slice, Ptr, Interface: - // Nil pointers cannot be cyclic. Avoid putting them in the visited map. - return !v1.IsNil() && !v2.IsNil() - } - return false - } - - if hard(v1, v2) { - addr1 := v1.pointer() - addr2 := v2.pointer() - if uintptr(addr1) > uintptr(addr2) { - // Canonicalize order to reduce number of entries in visited. - // Assumes non-moving garbage collector. - addr1, addr2 = addr2, addr1 - } - - // Short circuit if references are already seen. - v := visit{addr1, addr2, v1.typecode} - if _, ok := visited[v]; ok { - return true - } - - // Remember for later. - visited[v] = struct{}{} - } - - switch v1.Kind() { - case Array: - for i := 0; i < v1.Len(); i++ { - if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { - return false - } - } - return true - case Slice: - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.Len() != v2.Len() { - return false - } - if v1.UnsafePointer() == v2.UnsafePointer() { - return true - } - for i := 0; i < v1.Len(); i++ { - if !deepValueEqual(v1.Index(i), v2.Index(i), visited) { - return false - } - } - return true - case Interface: - if v1.IsNil() || v2.IsNil() { - return v1.IsNil() == v2.IsNil() - } - return deepValueEqual(v1.Elem(), v2.Elem(), visited) - case Ptr: - if v1.UnsafePointer() == v2.UnsafePointer() { - return true - } - return deepValueEqual(v1.Elem(), v2.Elem(), visited) - case Struct: - for i, n := 0, v1.NumField(); i < n; i++ { - if !deepValueEqual(v1.Field(i), v2.Field(i), visited) { - return false - } - } - return true - case Map: - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.Len() != v2.Len() { - return false - } - if v1.UnsafePointer() == v2.UnsafePointer() { - return true - } - for _, k := range v1.MapKeys() { - val1 := v1.MapIndex(k) - val2 := v2.MapIndex(k) - if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) { - return false - } - } - return true - case Func: - if v1.IsNil() && v2.IsNil() { - return true - } - // Can't do better than this: - return false - default: - // Normal equality suffices - return valueInterfaceUnsafe(v1) == valueInterfaceUnsafe(v2) - } -} +import "internal/reflectlite" -// DeepEqual reports whether x and y are “deeply equal”, defined as follows. -// Two values of identical type are deeply equal if one of the following cases applies. -// Values of distinct types are never deeply equal. -// -// Array values are deeply equal when their corresponding elements are deeply equal. -// -// Struct values are deeply equal if their corresponding fields, -// both exported and unexported, are deeply equal. -// -// Func values are deeply equal if both are nil; otherwise they are not deeply equal. -// -// Interface values are deeply equal if they hold deeply equal concrete values. -// -// Map values are deeply equal when all of the following are true: -// they are both nil or both non-nil, they have the same length, -// and either they are the same map object or their corresponding keys -// (matched using Go equality) map to deeply equal values. -// -// Pointer values are deeply equal if they are equal using Go's == operator -// or if they point to deeply equal values. -// -// Slice values are deeply equal when all of the following are true: -// they are both nil or both non-nil, they have the same length, -// and either they point to the same initial entry of the same underlying array -// (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. -// Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil)) -// are not deeply equal. -// -// Other values - numbers, bools, strings, and channels - are deeply equal -// if they are equal using Go's == operator. -// -// In general DeepEqual is a recursive relaxation of Go's == operator. -// However, this idea is impossible to implement without some inconsistency. -// Specifically, it is possible for a value to be unequal to itself, -// either because it is of func type (uncomparable in general) -// or because it is a floating-point NaN value (not equal to itself in floating-point comparison), -// or because it is an array, struct, or interface containing -// such a value. -// On the other hand, pointer values are always equal to themselves, -// even if they point at or contain such problematic values, -// because they compare equal using Go's == operator, and that -// is a sufficient condition to be deeply equal, regardless of content. -// DeepEqual has been defined so that the same short-cut applies -// to slices and maps: if x and y are the same slice or the same map, -// they are deeply equal regardless of content. -// -// As DeepEqual traverses the data values it may find a cycle. The -// second and subsequent times that DeepEqual compares two pointer -// values that have been compared before, it treats the values as -// equal rather than examining the values to which they point. -// This ensures that DeepEqual terminates. func DeepEqual(x, y interface{}) bool { - if x == nil || y == nil { - return x == y - } - v1 := ValueOf(x) - v2 := ValueOf(y) - if v1.typecode != v2.typecode { - return false - } - return deepValueEqual(v1, v2, make(map[visit]struct{})) + return reflectlite.DeepEqual(x, y) } diff --git a/src/reflect/intw.go b/src/reflect/intw.go new file mode 100644 index 0000000000..20fbd4341e --- /dev/null +++ b/src/reflect/intw.go @@ -0,0 +1,8 @@ +//go:build !avr + +package reflect + +// intw is an integer type, used in places where an int is typically required, +// except architectures where the size of an int != word size. +// See https://github.com/tinygo-org/tinygo/issues/1284. +type intw = int diff --git a/src/reflect/intw_avr.go b/src/reflect/intw_avr.go new file mode 100644 index 0000000000..8f294eeee2 --- /dev/null +++ b/src/reflect/intw_avr.go @@ -0,0 +1,8 @@ +//go:build avr + +package reflect + +// intw is an integer type, used in places where an int is typically required, +// except architectures where the size of an int != word size. +// See https://github.com/tinygo-org/tinygo/issues/1284. +type intw = uintptr diff --git a/src/reflect/intw_test.go b/src/reflect/intw_test.go new file mode 100644 index 0000000000..1014a9ae4e --- /dev/null +++ b/src/reflect/intw_test.go @@ -0,0 +1,30 @@ +//go:build !avr + +package reflect_test + +import ( + "reflect" + "testing" + "unsafe" +) + +// Verify that SliceHeader is the same size as a slice. +var _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(reflect.SliceHeader{})]byte{} + +// TestSliceHeaderIntegerSize verifies that SliceHeader.Len and Cap are type int on non-AVR platforms. +// See https://github.com/tinygo-org/tinygo/issues/1284. +func TestSliceHeaderIntegerSize(t *testing.T) { + var h reflect.SliceHeader + h.Len = int(0) + h.Cap = int(0) +} + +// Verify that StringHeader is the same size as a string. +var _ [unsafe.Sizeof("hello")]byte = [unsafe.Sizeof(reflect.StringHeader{})]byte{} + +// TestStringHeaderIntegerSize verifies that StringHeader.Len and Cap are type int on non-AVR platforms. +// See https://github.com/tinygo-org/tinygo/issues/1284. +func TestStringHeaderIntegerSize(t *testing.T) { + var h reflect.StringHeader + h.Len = int(0) +} diff --git a/src/reflect/iter.go b/src/reflect/iter.go new file mode 100644 index 0000000000..4cc2df8fd9 --- /dev/null +++ b/src/reflect/iter.go @@ -0,0 +1,177 @@ +//go:build go1.23 + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect + +import ( + "iter" +) + +func rangeNum[T int8 | int16 | int32 | int64 | int | + uint8 | uint16 | uint32 | uint64 | uint | + uintptr, N int64 | uint64](num N, t Type) iter.Seq[Value] { + return func(yield func(v Value) bool) { + convert := t.PkgPath() != "" + // cannot use range T(v) because no core type. + for i := T(0); i < T(num); i++ { + tmp := ValueOf(i) + // if the iteration value type is define by + // type T built-in type. + if convert { + tmp = tmp.Convert(t) + } + if !yield(tmp) { + return + } + } + } +} + +// Seq returns an iter.Seq[Value] that loops over the elements of v. +// If v's kind is Func, it must be a function that has no results and +// that takes a single argument of type func(T) bool for some type T. +// If v's kind is Pointer, the pointer element type must have kind Array. +// Otherwise v's kind must be Int, Int8, Int16, Int32, Int64, +// Uint, Uint8, Uint16, Uint32, Uint64, Uintptr, +// Array, Chan, Map, Slice, or String. +func (v Value) Seq() iter.Seq[Value] { + // TODO: canRangeFunc + // if canRangeFunc(v.typ()) { + // return func(yield func(Value) bool) { + // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { + // return []Value{ValueOf(yield(in[0]))} + // }) + // v.Call([]Value{rf}) + // } + // } + switch v.Kind() { + case Int: + return rangeNum[int](v.Int(), v.Type()) + case Int8: + return rangeNum[int8](v.Int(), v.Type()) + case Int16: + return rangeNum[int16](v.Int(), v.Type()) + case Int32: + return rangeNum[int32](v.Int(), v.Type()) + case Int64: + return rangeNum[int64](v.Int(), v.Type()) + case Uint: + return rangeNum[uint](v.Uint(), v.Type()) + case Uint8: + return rangeNum[uint8](v.Uint(), v.Type()) + case Uint16: + return rangeNum[uint16](v.Uint(), v.Type()) + case Uint32: + return rangeNum[uint32](v.Uint(), v.Type()) + case Uint64: + return rangeNum[uint64](v.Uint(), v.Type()) + case Uintptr: + return rangeNum[uintptr](v.Uint(), v.Type()) + case Pointer: + if v.Elem().Kind() != Array { + break + } + return func(yield func(Value) bool) { + v = v.Elem() + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i)) { + return + } + } + } + case Array, Slice: + return func(yield func(Value) bool) { + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i)) { + return + } + } + } + case String: + return func(yield func(Value) bool) { + for i := range v.String() { + if !yield(ValueOf(i)) { + return + } + } + } + case Map: + return func(yield func(Value) bool) { + i := v.MapRange() + for i.Next() { + if !yield(i.Key()) { + return + } + } + } + case Chan: + return func(yield func(Value) bool) { + for value, ok := v.Recv(); ok; value, ok = v.Recv() { + if !yield(value) { + return + } + } + } + } + panic("reflect: " + v.Type().String() + " cannot produce iter.Seq[Value]") +} + +// Seq2 returns an iter.Seq2[Value, Value] that loops over the elements of v. +// If v's kind is Func, it must be a function that has no results and +// that takes a single argument of type func(K, V) bool for some type K, V. +// If v's kind is Pointer, the pointer element type must have kind Array. +// Otherwise v's kind must be Array, Map, Slice, or String. +func (v Value) Seq2() iter.Seq2[Value, Value] { + // TODO: canRangeFunc2 + // if canRangeFunc2(v.typ()) { + // return func(yield func(Value, Value) bool) { + // rf := MakeFunc(v.Type().In(0), func(in []Value) []Value { + // return []Value{ValueOf(yield(in[0], in[1]))} + // }) + // v.Call([]Value{rf}) + // } + // } + switch v.Kind() { + case Pointer: + if v.Elem().Kind() != Array { + break + } + return func(yield func(Value, Value) bool) { + v = v.Elem() + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i), v.Index(i)) { + return + } + } + } + case Array, Slice: + return func(yield func(Value, Value) bool) { + for i := 0; i < v.Len(); i++ { + if !yield(ValueOf(i), v.Index(i)) { + return + } + } + } + case String: + return func(yield func(Value, Value) bool) { + for i, v := range v.String() { + if !yield(ValueOf(i), ValueOf(v)) { + return + } + } + } + case Map: + return func(yield func(Value, Value) bool) { + i := v.MapRange() + for i.Next() { + if !yield(i.Key(), i.Value()) { + return + } + } + } + } + panic("reflect: " + v.Type().String() + " cannot produce iter.Seq2[Value, Value]") +} diff --git a/src/reflect/iter_test.go b/src/reflect/iter_test.go new file mode 100644 index 0000000000..48c62c477a --- /dev/null +++ b/src/reflect/iter_test.go @@ -0,0 +1,416 @@ +//go:build go1.23 + +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package reflect_test + +import ( + "iter" + "maps" + "reflect" + . "reflect" + "testing" +) + +type N int8 + +func TestValueSeq(t *testing.T) { + m := map[string]int{ + "1": 1, + "2": 2, + "3": 3, + "4": 4, + } + c := make(chan int, 3) + for i := range 3 { + c <- i + } + close(c) + tests := []struct { + name string + val Value + check func(*testing.T, iter.Seq[Value]) + }{ + {"int", ValueOf(4), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"int8", ValueOf(int8(4)), func(t *testing.T, s iter.Seq[Value]) { + i := int8(0) + for v := range s { + if v.Interface().(int8) != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"uint", ValueOf(uint64(4)), func(t *testing.T, s iter.Seq[Value]) { + i := uint64(0) + for v := range s { + if v.Uint() != i { + t.Fatalf("got %d, want %d", v.Uint(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"uint8", ValueOf(uint8(4)), func(t *testing.T, s iter.Seq[Value]) { + i := uint8(0) + for v := range s { + if v.Interface().(uint8) != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + for v := range s { + if v.Int() != i { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq[Value]) { + i := int64(0) + indexes := []int64{0, 1, 2, 5} + for v := range s { + if v.Int() != indexes[i] { + t.Fatalf("got %d, want %d", v.Int(), indexes[i]) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq[Value]) { + copy := maps.Clone(m) + for v := range s { + if _, ok := copy[v.String()]; !ok { + t.Fatalf("unexpected %v", v.Interface()) + } + delete(copy, v.String()) + } + if len(copy) != 0 { + t.Fatalf("should loop four times") + } + }}, + // {"chan int", ValueOf(c), func(t *testing.T, s iter.Seq[Value]) { + // i := 0 + // m := map[int64]bool{ + // 0: false, + // 1: false, + // 2: false, + // } + // for v := range s { + // if b, ok := m[v.Int()]; !ok || b { + // t.Fatalf("unexpected %v", v.Interface()) + // } + // m[v.Int()] = true + // i++ + // } + // if i != 3 { + // t.Fatalf("should loop three times") + // } + // }}, + // {"func", ValueOf(func(yield func(int) bool) { + // for i := range 4 { + // if !yield(i) { + // return + // } + // } + // }), func(t *testing.T, s iter.Seq[Value]) { + // i := int64(0) + // for v := range s { + // if v.Int() != i { + // t.Fatalf("got %d, want %d", v.Int(), i) + // } + // i++ + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + // {"method", ValueOf(methodIter{}).MethodByName("Seq"), func(t *testing.T, s iter.Seq[Value]) { + // i := int64(0) + // for v := range s { + // if v.Int() != i { + // t.Fatalf("got %d, want %d", v.Int(), i) + // } + // i++ + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + {"type N int8", ValueOf(N(4)), func(t *testing.T, s iter.Seq[Value]) { + i := N(0) + for v := range s { + if v.Int() != int64(i) { + t.Fatalf("got %d, want %d", v.Int(), i) + } + i++ + if v.Type() != reflect.TypeOf(i) { + j := ValueOf(i) + t.Logf("ValueOf(j): %s", j.Type()) + t.Fatalf("got %s, want %s", v.Type(), reflect.TypeOf(i)) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + } + for _, tc := range tests { + seq := tc.val.Seq() + tc.check(t, seq) + } +} + +func TestValueSeq2(t *testing.T) { + m := map[string]int{ + "1": 1, + "2": 2, + "3": 3, + "4": 4, + } + tests := []struct { + name string + val Value + check func(*testing.T, iter.Seq2[Value, Value]) + }{ + {"*[4]int", ValueOf(&[4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[4]int", ValueOf([4]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]int", ValueOf([]int{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := int64(0) + for v1, v2 := range s { + if v1.Int() != i { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != i { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"string", ValueOf("12语言"), func(t *testing.T, s iter.Seq2[Value, Value]) { + next, stop := iter.Pull2(s) + defer stop() + i := int64(0) + for j, s := range "12语言" { + v1, v2, ok := next() + if !ok { + t.Fatalf("should loop four times") + } + if v1.Int() != int64(j) { + t.Fatalf("got %d, want %d", v1.Int(), j) + } + if v2.Interface() != s { + t.Fatalf("got %v, want %v", v2.Interface(), s) + } + i++ + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"map[string]int", ValueOf(m), func(t *testing.T, s iter.Seq2[Value, Value]) { + copy := maps.Clone(m) + for v1, v2 := range s { + v, ok := copy[v1.String()] + if !ok { + t.Fatalf("unexpected %v", v1.String()) + } + if v != v2.Interface() { + t.Fatalf("got %v, want %d", v2.Interface(), v) + } + delete(copy, v1.String()) + } + if len(copy) != 0 { + t.Fatalf("should loop four times") + } + }}, + // {"func", ValueOf(func(f func(int, int) bool) { + // for i := range 4 { + // f(i, i+1) + // } + // }), func(t *testing.T, s iter.Seq2[Value, Value]) { + // i := int64(0) + // for v1, v2 := range s { + // if v1.Int() != i { + // t.Fatalf("got %d, want %d", v1.Int(), i) + // } + // i++ + // if v2.Int() != i { + // t.Fatalf("got %d, want %d", v2.Int(), i) + // } + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + // {"method", ValueOf(methodIter2{}).MethodByName("Seq2"), func(t *testing.T, s iter.Seq2[Value, Value]) { + // i := int64(0) + // for v1, v2 := range s { + // if v1.Int() != i { + // t.Fatalf("got %d, want %d", v1.Int(), i) + // } + // i++ + // if v2.Int() != i { + // t.Fatalf("got %d, want %d", v2.Int(), i) + // } + // } + // if i != 4 { + // t.Fatalf("should loop four times") + // } + // }}, + {"[4]N", ValueOf([4]N{0, 1, 2, 3}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := N(0) + for v1, v2 := range s { + if v1.Int() != int64(i) { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + if v2.Int() != int64(i) { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + i++ + if v2.Type() != reflect.TypeOf(i) { + t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + {"[]N", ValueOf([]N{1, 2, 3, 4}), func(t *testing.T, s iter.Seq2[Value, Value]) { + i := N(0) + for v1, v2 := range s { + if v1.Int() != int64(i) { + t.Fatalf("got %d, want %d", v1.Int(), i) + } + i++ + if v2.Int() != int64(i) { + t.Fatalf("got %d, want %d", v2.Int(), i) + } + if v2.Type() != reflect.TypeOf(i) { + t.Fatalf("got %s, want %s", v2.Type(), reflect.TypeOf(i)) + } + } + if i != 4 { + t.Fatalf("should loop four times") + } + }}, + } + for _, tc := range tests { + seq := tc.val.Seq2() + tc.check(t, seq) + } +} + +// methodIter is a type from which we can derive a method +// value that is an iter.Seq. +type methodIter struct{} + +func (methodIter) Seq(yield func(int) bool) { + for i := range 4 { + if !yield(i) { + return + } + } +} + +// For Type.CanSeq test. +func (methodIter) NonSeq(yield func(int)) {} + +// methodIter2 is a type from which we can derive a method +// value that is an iter.Seq2. +type methodIter2 struct{} + +func (methodIter2) Seq2(yield func(int, int) bool) { + for i := range 4 { + if !yield(i, i+1) { + return + } + } +} + +// For Type.CanSeq2 test. +func (methodIter2) NonSeq2(yield func(int, int)) {} diff --git a/src/reflect/swapper.go b/src/reflect/swapper.go index a2fa44cef0..d49e33e04c 100644 --- a/src/reflect/swapper.go +++ b/src/reflect/swapper.go @@ -1,40 +1,7 @@ package reflect -import "unsafe" - -// Some of code here has been copied from the Go sources: -// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go -// It has the following copyright note: -// -// Copyright 2016 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. +import "internal/reflectlite" func Swapper(slice interface{}) func(i, j int) { - v := ValueOf(slice) - if v.Kind() != Slice { - panic(&ValueError{Method: "Swapper"}) - } - - // Just return Nop func if nothing to swap. - if v.Len() < 2 { - return func(i, j int) {} - } - - typ := v.typecode.Elem() - size := typ.Size() - - header := (*sliceHeader)(v.value) - tmp := unsafe.Pointer(&make([]byte, size)[0]) - - return func(i, j int) { - if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { - panic("reflect: slice index out of range") - } - val1 := unsafe.Add(header.data, uintptr(i)*size) - val2 := unsafe.Add(header.data, uintptr(j)*size) - memcpy(tmp, val1, size) - memcpy(val1, val2, size) - memcpy(val2, tmp, size) - } + return reflectlite.Swapper(slice) } diff --git a/src/reflect/type.go b/src/reflect/type.go index 56d4767f71..a162aa7973 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -64,129 +64,50 @@ package reflect import ( - "internal/itoa" + "internal/reflectlite" "unsafe" ) -// Flags stored in the first byte of the struct field byte array. Must be kept -// up to date with compiler/interface.go. -const ( - structFieldFlagAnonymous = 1 << iota - structFieldFlagHasTag - structFieldFlagIsExported - structFieldFlagIsEmbedded -) +type Kind = reflectlite.Kind -type Kind uint8 - -// Copied from reflect/type.go -// https://golang.org/src/reflect/type.go?s=8302:8316#L217 -// These constants must match basicTypes and the typeKind* constants in -// compiler/interface.go const ( - Invalid Kind = iota - Bool - Int - Int8 - Int16 - Int32 - Int64 - Uint - Uint8 - Uint16 - Uint32 - Uint64 - Uintptr - Float32 - Float64 - Complex64 - Complex128 - String - UnsafePointer - Chan - Interface - Pointer - Slice - Array - Func - Map - Struct + Invalid Kind = reflectlite.Invalid + Bool Kind = reflectlite.Bool + Int Kind = reflectlite.Int + Int8 Kind = reflectlite.Int8 + Int16 Kind = reflectlite.Int16 + Int32 Kind = reflectlite.Int32 + Int64 Kind = reflectlite.Int64 + Uint Kind = reflectlite.Uint + Uint8 Kind = reflectlite.Uint8 + Uint16 Kind = reflectlite.Uint16 + Uint32 Kind = reflectlite.Uint32 + Uint64 Kind = reflectlite.Uint64 + Uintptr Kind = reflectlite.Uintptr + Float32 Kind = reflectlite.Float32 + Float64 Kind = reflectlite.Float64 + Complex64 Kind = reflectlite.Complex64 + Complex128 Kind = reflectlite.Complex128 + Array Kind = reflectlite.Array + Chan Kind = reflectlite.Chan + Func Kind = reflectlite.Func + Interface Kind = reflectlite.Interface + Map Kind = reflectlite.Map + Pointer Kind = reflectlite.Pointer + Slice Kind = reflectlite.Slice + String Kind = reflectlite.String + Struct Kind = reflectlite.Struct + UnsafePointer Kind = reflectlite.UnsafePointer ) -// Ptr is the old name for the Pointer kind. -const Ptr = Pointer - -func (k Kind) String() string { - switch k { - case Invalid: - return "invalid" - case Bool: - return "bool" - case Int: - return "int" - case Int8: - return "int8" - case Int16: - return "int16" - case Int32: - return "int32" - case Int64: - return "int64" - case Uint: - return "uint" - case Uint8: - return "uint8" - case Uint16: - return "uint16" - case Uint32: - return "uint32" - case Uint64: - return "uint64" - case Uintptr: - return "uintptr" - case Float32: - return "float32" - case Float64: - return "float64" - case Complex64: - return "complex64" - case Complex128: - return "complex128" - case String: - return "string" - case UnsafePointer: - return "unsafe.Pointer" - case Chan: - return "chan" - case Interface: - return "interface" - case Pointer: - return "ptr" - case Slice: - return "slice" - case Array: - return "array" - case Func: - return "func" - case Map: - return "map" - case Struct: - return "struct" - default: - return "kind" + itoa.Itoa(int(int8(k))) - } -} - -// Copied from reflect/type.go -// https://go.dev/src/reflect/type.go?#L348 +const Ptr = reflectlite.Ptr -// ChanDir represents a channel type's direction. -type ChanDir int +type ChanDir = reflectlite.ChanDir const ( - RecvDir ChanDir = 1 << iota // <-chan - SendDir // chan<- - BothDir = RecvDir | SendDir // chan + RecvDir = reflectlite.RecvDir + SendDir = reflectlite.SendDir + BothDir = reflectlite.BothDir ) // Method represents a single method. @@ -392,782 +313,155 @@ type Type interface { // It panics if the type's Kind is not Func. // It panics if i is not in the range [0, NumOut()). Out(i int) Type -} -// Constants for the 'meta' byte. -const ( - kindMask = 31 // mask to apply to the meta byte to get the Kind value - flagNamed = 32 // flag that is set if this is a named type - flagComparable = 64 // flag that is set if this type is comparable - flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm -) + // OverflowComplex reports whether the complex128 x cannot be represented by type t. + // It panics if t's Kind is not Complex64 or Complex128. + OverflowComplex(x complex128) bool -// The base type struct. All type structs start with this. -type rawType struct { - meta uint8 // metadata byte, contains kind and flags (see contants above) -} + // OverflowFloat reports whether the float64 x cannot be represented by type t. + // It panics if t's Kind is not Float32 or Float64. + OverflowFloat(x float64) bool -// All types that have an element type: named, chan, slice, array, map (but not -// pointer because it doesn't have ptrTo). -type elemType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType -} + // OverflowInt reports whether the int64 x cannot be represented by type t. + // It panics if t's Kind is not Int, Int8, Int16, Int32, or Int64. + OverflowInt(x int64) bool -type ptrType struct { - rawType - numMethod uint16 - elem *rawType -} + // OverflowUint reports whether the uint64 x cannot be represented by type t. + // It panics if t's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. + OverflowUint(x uint64) bool -type arrayType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - arrayLen uintptr - slicePtr *rawType -} - -type mapType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - key *rawType -} + // CanSeq reports whether a [Value] with this type can be iterated over using [Value.Seq]. + CanSeq() bool -type namedType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - pkg *byte - name [1]byte + // CanSeq2 reports whether a [Value] with this type can be iterated over using [Value.Seq2]. + CanSeq2() bool } -// Type for struct types. The numField value is intentionally put before ptrTo -// for better struct packing on 32-bit and 64-bit architectures. On these -// architectures, the ptrTo field still has the same offset as in all the other -// type structs. -// The fields array isn't necessarily 1 structField long, instead it is as long -// as numFields. The array is given a length of 1 to satisfy the Go type -// checker. -type structType struct { - rawType - numMethod uint16 - ptrTo *rawType - pkgpath *byte - size uint32 - numField uint16 - fields [1]structField // the remaining fields are all of type structField -} - -type structField struct { - fieldType *rawType - data unsafe.Pointer // various bits of information, packed in a byte array +type rawType struct { + reflectlite.RawType } -// Equivalent to (go/types.Type).Underlying(): if this is a named type return -// the underlying type, else just return the type itself. -func (t *rawType) underlying() *rawType { - if t.isNamed() { - return (*elemType)(unsafe.Pointer(t)).elem +func toType(t reflectlite.Type) Type { + if t == nil { + return nil } - return t + return (*rawType)(unsafe.Pointer(t.(*reflectlite.RawType))) } -func (t *rawType) ptrtag() uintptr { - return uintptr(unsafe.Pointer(t)) & 0b11 -} - -func (t *rawType) isNamed() bool { - if tag := t.ptrtag(); tag != 0 { - return false - } - - return t.meta&flagNamed != 0 +func toRawType(t Type) *reflectlite.RawType { + return (*reflectlite.RawType)(unsafe.Pointer(t.(*rawType))) } func TypeOf(i interface{}) Type { - if i == nil { - return nil - } - typecode, _ := decomposeInterface(i) - return (*rawType)(typecode) + return toType(reflectlite.TypeOf(i)) } -func PtrTo(t Type) Type { return PointerTo(t) } +func PtrTo(t Type) Type { + return PointerTo(t) +} func PointerTo(t Type) Type { - return pointerTo(t.(*rawType)) + return toType(reflectlite.PointerTo(toRawType(t))) } -func pointerTo(t *rawType) *rawType { - if t.isNamed() { - return (*elemType)(unsafe.Pointer(t)).ptrTo - } +func (t *rawType) AssignableTo(u Type) bool { + return t.RawType.AssignableTo(&(u.(*rawType).RawType)) +} +func (t *rawType) CanSeq() bool { switch t.Kind() { + case Int8, Int16, Int32, Int64, Int, Uint8, Uint16, Uint32, Uint64, Uint, Uintptr, Array, Slice, Chan, String, Map: + return true + case Func: + // TODO: implement canRangeFunc + // return canRangeFunc(t) + panic("unimplemented: (reflect.Type).CanSeq() for functions") case Pointer: - if tag := t.ptrtag(); tag < 3 { - return (*rawType)(unsafe.Add(unsafe.Pointer(t), 1)) - } - - // TODO(dgryski): This is blocking https://github.com/tinygo-org/tinygo/issues/3131 - // We need to be able to create types that match existing types to prevent typecode equality. - panic("reflect: cannot make *****T type") - case Struct: - return (*structType)(unsafe.Pointer(t)).ptrTo - default: - return (*elemType)(unsafe.Pointer(t)).ptrTo + return t.Elem().Kind() == Array } + return false } -func (t *rawType) String() string { - if t.isNamed() { - s := t.name() - if s[0] == '.' { - return s[1:] - } - return s - } - +func (t *rawType) CanSeq2() bool { switch t.Kind() { - case Chan: - elem := t.elem().String() - switch t.ChanDir() { - case SendDir: - return "chan<- " + elem - case RecvDir: - return "<-chan " + elem - case BothDir: - if elem[0] == '<' { - // typ is recv chan, need parentheses as "<-" associates with leftmost - // chan possible, see: - // * https://golang.org/ref/spec#Channel_types - // * https://github.com/golang/go/issues/39897 - return "chan (" + elem + ")" - } - return "chan " + elem - } - + case Array, Slice, String, Map: + return true + case Func: + // TODO: implement canRangeFunc2 + // return canRangeFunc2(t) + panic("unimplemented: (reflect.Type).CanSeq2() for functions") case Pointer: - return "*" + t.elem().String() - case Slice: - return "[]" + t.elem().String() - case Array: - return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() - case Map: - return "map[" + t.key().String() + "]" + t.elem().String() - case Struct: - numField := t.NumField() - if numField == 0 { - return "struct {}" - } - s := "struct {" - for i := 0; i < numField; i++ { - f := t.rawField(i) - s += " " + f.Name + " " + f.Type.String() - if f.Tag != "" { - s += " " + quote(string(f.Tag)) - } - // every field except the last needs a semicolon - if i < numField-1 { - s += ";" - } - } - s += " }" - return s - case Interface: - // TODO(dgryski): Needs actual method set info - return "interface {}" - default: - return t.Kind().String() + return t.Elem().Kind() == Array } - - return t.Kind().String() + return false } -func (t *rawType) Kind() Kind { - if t == nil { - return Invalid - } - - if tag := t.ptrtag(); tag != 0 { - return Pointer - } - - return Kind(t.meta & kindMask) +func (t *rawType) ConvertibleTo(u Type) bool { + panic("unimplemented: (reflect.Type).ConvertibleTo()") } -var ( - errTypeElem = &TypeError{"Elem"} - errTypeKey = &TypeError{"Key"} - errTypeField = &TypeError{"Field"} - errTypeBits = &TypeError{"Bits"} - errTypeLen = &TypeError{"Len"} - errTypeNumField = &TypeError{"NumField"} - errTypeChanDir = &TypeError{"ChanDir"} - errTypeFieldByName = &TypeError{"FieldByName"} - errTypeFieldByIndex = &TypeError{"FieldByIndex"} -) - -// Elem returns the element type for channel, slice and array types, the -// pointed-to value for pointer types, and the key type for map types. func (t *rawType) Elem() Type { - return t.elem() -} - -func (t *rawType) elem() *rawType { - if tag := t.ptrtag(); tag != 0 { - return (*rawType)(unsafe.Add(unsafe.Pointer(t), -1)) - } - - underlying := t.underlying() - switch underlying.Kind() { - case Pointer: - return (*ptrType)(unsafe.Pointer(underlying)).elem - case Chan, Slice, Array, Map: - return (*elemType)(unsafe.Pointer(underlying)).elem - default: - panic(errTypeElem) - } -} - -func (t *rawType) key() *rawType { - underlying := t.underlying() - if underlying.Kind() != Map { - panic(errTypeKey) - } - return (*mapType)(unsafe.Pointer(underlying)).key + return toType(t.RawType.Elem()) } -// Field returns the type of the i'th field of this struct type. It panics if t -// is not a struct type. func (t *rawType) Field(i int) StructField { - field := t.rawField(i) - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: []int{i}, - } + f := t.RawType.Field(i) + return toStructField(f) } -func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { - // Read the field tag, if there is one. - var tag string - if flagsByte&structFieldFlagHasTag != 0 { - data = unsafe.Add(data, 1) // C: data+1 - tagLen := uintptr(*(*byte)(data)) - data = unsafe.Add(data, 1) // C: data+1 - tag = *(*string)(unsafe.Pointer(&stringHeader{ - data: data, - len: tagLen, - })) - } - - // Set the PkgPath to some (arbitrary) value if the package path is not - // exported. - pkgPath := "" - if flagsByte&structFieldFlagIsExported == 0 { - // This field is unexported. - pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) - } - - return rawStructField{ - Name: name, - PkgPath: pkgPath, - Type: fieldType, - Tag: StructTag(tag), - Anonymous: flagsByte&structFieldFlagAnonymous != 0, - Offset: uintptr(offset), - } -} - -// rawField returns nearly the same value as Field but without converting the -// Type member to an interface. -// -// For internal use only. -func (t *rawType) rawField(n int) rawStructField { - if t.Kind() != Struct { - panic(errTypeField) - } - descriptor := (*structType)(unsafe.Pointer(t.underlying())) - if uint(n) >= uint(descriptor.numField) { - panic("reflect: field index out of range") - } - - // Iterate over all the fields to calculate the offset. - // This offset could have been stored directly in the array (to make the - // lookup faster), but by calculating it on-the-fly a bit of storage can be - // saved. - field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) - data := field.data - - // Read some flags of this field, like whether the field is an embedded - // field. See structFieldFlagAnonymous and similar flags. - flagsByte := *(*byte)(data) - data = unsafe.Add(data, 1) - offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) - data = unsafe.Add(data, lenOffs) - - name := readStringZ(data) - data = unsafe.Add(data, len(name)) - - return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) -} - -// rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the -// Type member to an interface. -// -// For internal use only. -func (t *rawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { - if t.Kind() != Struct { - panic(errTypeField) - } - - type fieldWalker struct { - t *rawType - index []int - } - - queue := make([]fieldWalker, 0, 4) - queue = append(queue, fieldWalker{t, nil}) - - for len(queue) > 0 { - type result struct { - r rawStructField - index []int - } - - var found []result - var nextlevel []fieldWalker - - // For all the structs at this level.. - for _, ll := range queue { - // Iterate over all the fields looking for the matching name - // Also calculate field offset. - - descriptor := (*structType)(unsafe.Pointer(ll.t.underlying())) - field := &descriptor.fields[0] - - for i := uint16(0); i < descriptor.numField; i++ { - data := field.data - - // Read some flags of this field, like whether the field is an embedded - // field. See structFieldFlagAnonymous and similar flags. - flagsByte := *(*byte)(data) - data = unsafe.Add(data, 1) - - offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) - data = unsafe.Add(data, lenOffs) - - name := readStringZ(data) - data = unsafe.Add(data, len(name)) - if match(name) { - found = append(found, result{ - rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset), - append(ll.index, int(i)), - }) - } - - structOrPtrToStruct := field.fieldType.Kind() == Struct || (field.fieldType.Kind() == Pointer && field.fieldType.elem().Kind() == Struct) - if flagsByte&structFieldFlagIsEmbedded == structFieldFlagIsEmbedded && structOrPtrToStruct { - embedded := field.fieldType - if embedded.Kind() == Pointer { - embedded = embedded.elem() - } - - nextlevel = append(nextlevel, fieldWalker{ - t: embedded, - index: append(ll.index, int(i)), - }) - } - - // update offset/field pointer if there *is* a next field - if i < descriptor.numField-1 { - // Increment pointer to the next field. - field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) - } - } - } - - // found multiple hits at this level - if len(found) > 1 { - return rawStructField{}, nil, false - } - - // found the field we were looking for - if len(found) == 1 { - r := found[0] - return r.r, r.index, true - } - - // else len(found) == 0, move on to the next level - queue = append(queue[:0], nextlevel...) - } - - // didn't find it - return rawStructField{}, nil, false -} - -// Bits returns the number of bits that this type uses. It is only valid for -// arithmetic types (integers, floats, and complex numbers). For other types, it -// will panic. -func (t *rawType) Bits() int { - kind := t.Kind() - if kind >= Int && kind <= Complex128 { - return int(t.Size()) * 8 - } - panic(errTypeBits) -} - -// Len returns the number of elements in this array. It panics of the type kind -// is not Array. -func (t *rawType) Len() int { - if t.Kind() != Array { - panic(errTypeLen) - } - - return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) -} - -// NumField returns the number of fields of a struct type. It panics for other -// type kinds. -func (t *rawType) NumField() int { - if t.Kind() != Struct { - panic(errTypeNumField) - } - return int((*structType)(unsafe.Pointer(t.underlying())).numField) -} - -// Size returns the size in bytes of a given type. It is similar to -// unsafe.Sizeof. -func (t *rawType) Size() uintptr { - switch t.Kind() { - case Bool, Int8, Uint8: - return 1 - case Int16, Uint16: - return 2 - case Int32, Uint32: - return 4 - case Int64, Uint64: - return 8 - case Int, Uint: - return unsafe.Sizeof(int(0)) - case Uintptr: - return unsafe.Sizeof(uintptr(0)) - case Float32: - return 4 - case Float64: - return 8 - case Complex64: - return 8 - case Complex128: - return 16 - case String: - return unsafe.Sizeof("") - case UnsafePointer, Chan, Map, Pointer: - return unsafe.Sizeof(uintptr(0)) - case Slice: - return unsafe.Sizeof([]int{}) - case Interface: - return unsafe.Sizeof(interface{}(nil)) - case Func: - var f func() - return unsafe.Sizeof(f) - case Array: - return t.elem().Size() * uintptr(t.Len()) - case Struct: - u := t.underlying() - return uintptr((*structType)(unsafe.Pointer(u)).size) - default: - panic("unimplemented: size of type") - } -} - -// Align returns the alignment of this type. It is similar to calling -// unsafe.Alignof. -func (t *rawType) Align() int { - switch t.Kind() { - case Bool, Int8, Uint8: - return int(unsafe.Alignof(int8(0))) - case Int16, Uint16: - return int(unsafe.Alignof(int16(0))) - case Int32, Uint32: - return int(unsafe.Alignof(int32(0))) - case Int64, Uint64: - return int(unsafe.Alignof(int64(0))) - case Int, Uint: - return int(unsafe.Alignof(int(0))) - case Uintptr: - return int(unsafe.Alignof(uintptr(0))) - case Float32: - return int(unsafe.Alignof(float32(0))) - case Float64: - return int(unsafe.Alignof(float64(0))) - case Complex64: - return int(unsafe.Alignof(complex64(0))) - case Complex128: - return int(unsafe.Alignof(complex128(0))) - case String: - return int(unsafe.Alignof("")) - case UnsafePointer, Chan, Map, Pointer: - return int(unsafe.Alignof(uintptr(0))) - case Slice: - return int(unsafe.Alignof([]int(nil))) - case Interface: - return int(unsafe.Alignof(interface{}(nil))) - case Func: - var f func() - return int(unsafe.Alignof(f)) - case Struct: - numField := t.NumField() - alignment := 1 - for i := 0; i < numField; i++ { - fieldAlignment := t.rawField(i).Type.Align() - if fieldAlignment > alignment { - alignment = fieldAlignment - } - } - return alignment - case Array: - return t.elem().Align() - default: - panic("unimplemented: alignment of type") - } +func (t *rawType) FieldByIndex(index []int) StructField { + f := t.RawType.FieldByIndex(index) + return toStructField(f) } -// FieldAlign returns the alignment if this type is used in a struct field. It -// is currently an alias for Align() but this might change in the future. -func (t *rawType) FieldAlign() int { - return t.Align() +func (t *rawType) FieldByName(name string) (StructField, bool) { + f, ok := t.RawType.FieldByName(name) + return toStructField(f), ok } -// AssignableTo returns whether a value of type t can be assigned to a variable -// of type u. -func (t *rawType) AssignableTo(u Type) bool { - if t == u.(*rawType) { - return true - } - - if u.Kind() == Interface && u.NumMethod() == 0 { - return true - } - - if u.Kind() == Interface { - panic("reflect: unimplemented: AssignableTo with interface") - } - return false +func (t *rawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { + f, ok := t.RawType.FieldByNameFunc(match) + return toStructField(f), ok } func (t *rawType) Implements(u Type) bool { - if u.Kind() != Interface { - panic("reflect: non-interface type passed to Type.Implements") - } - return t.AssignableTo(u) -} - -// Comparable returns whether values of this type can be compared to each other. -func (t *rawType) Comparable() bool { - return (t.meta & flagComparable) == flagComparable + return t.RawType.Implements(&(u.(*rawType).RawType)) } -// isbinary() returns if the hashmapAlgorithmBinary functions can be used on this type -func (t *rawType) isBinary() bool { - return (t.meta & flagIsBinary) == flagIsBinary -} - -func (t *rawType) ChanDir() ChanDir { - if t.Kind() != Chan { - panic(errTypeChanDir) - } - - dir := int((*elemType)(unsafe.Pointer(t)).numMethod) - - // nummethod is overloaded for channel to store channel direction - return ChanDir(dir) -} - -func (t *rawType) ConvertibleTo(u Type) bool { - panic("unimplemented: (reflect.Type).ConvertibleTo()") +func (t *rawType) In(i int) Type { + panic("unimplemented: (reflect.Type).In()") } func (t *rawType) IsVariadic() bool { panic("unimplemented: (reflect.Type).IsVariadic()") } -func (t *rawType) NumIn() int { - panic("unimplemented: (reflect.Type).NumIn()") -} - -func (t *rawType) NumOut() int { - panic("unimplemented: (reflect.Type).NumOut()") -} - -func (t *rawType) NumMethod() int { - - if t.isNamed() { - return int((*namedType)(unsafe.Pointer(t)).numMethod) - } - - switch t.Kind() { - case Pointer: - return int((*ptrType)(unsafe.Pointer(t)).numMethod) - case Struct: - return int((*structType)(unsafe.Pointer(t)).numMethod) - } - - // Other types have no methods attached. Note we don't panic here. - return 0 -} - -// Read and return a null terminated string starting from data. -func readStringZ(data unsafe.Pointer) string { - start := data - var len uintptr - for *(*byte)(data) != 0 { - len++ - data = unsafe.Add(data, 1) // C: data++ - } - - return *(*string)(unsafe.Pointer(&stringHeader{ - data: start, - len: len, - })) -} - -func (t *rawType) name() string { - ntype := (*namedType)(unsafe.Pointer(t)) - return readStringZ(unsafe.Pointer(&ntype.name[0])) -} - -func (t *rawType) Name() string { - if t.isNamed() { - name := t.name() - for i := 0; i < len(name); i++ { - if name[i] == '.' { - return name[i+1:] - } - } - panic("corrupt name data") - } - - if t.Kind() <= UnsafePointer { - return t.Kind().String() - } - - return "" -} - func (t *rawType) Key() Type { - return t.key() -} - -func (t rawType) In(i int) Type { - panic("unimplemented: (reflect.Type).In()") -} - -func (t rawType) Out(i int) Type { - panic("unimplemented: (reflect.Type).Out()") + return toType(t.RawType.Key()) } -func (t rawType) Method(i int) Method { +func (t *rawType) Method(i int) Method { panic("unimplemented: (reflect.Type).Method()") } -func (t rawType) MethodByName(name string) (Method, bool) { +func (t *rawType) MethodByName(name string) (Method, bool) { panic("unimplemented: (reflect.Type).MethodByName()") } -func (t *rawType) PkgPath() string { - if t.isNamed() { - ntype := (*namedType)(unsafe.Pointer(t)) - return readStringZ(unsafe.Pointer(ntype.pkg)) - } - - return "" -} - -func (t *rawType) FieldByName(name string) (StructField, bool) { - if t.Kind() != Struct { - panic(errTypeFieldByName) - } - - field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) - if !ok { - return StructField{}, false - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - }, true +func (t *rawType) NumIn() int { + panic("unimplemented: (reflect.Type).NumIn()") } -func (t *rawType) FieldByNameFunc(match func(string) bool) (StructField, bool) { - if t.Kind() != Struct { - panic(TypeError{"FieldByNameFunc"}) - } - - field, index, ok := t.rawFieldByNameFunc(match) - if !ok { - return StructField{}, false - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - }, true +func (t *rawType) NumOut() int { + panic("unimplemented: (reflect.Type).NumOut()") } -func (t *rawType) FieldByIndex(index []int) StructField { - ftype := t - var field rawStructField - - for _, n := range index { - structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) - if !structOrPtrToStruct { - panic(errTypeFieldByIndex) - } - - if ftype.Kind() == Pointer { - ftype = ftype.elem() - } - - field = ftype.rawField(n) - ftype = field.Type - } - - return StructField{ - Name: field.Name, - PkgPath: field.PkgPath, - Type: field.Type, // note: converts rawType to Type - Tag: field.Tag, - Anonymous: field.Anonymous, - Offset: field.Offset, - Index: index, - } +func (t *rawType) Out(i int) Type { + panic("unimplemented: (reflect.Type).Out()") } // A StructField describes a single field in a struct. +// This must be kept in sync with [reflectlite.StructField]. type StructField struct { // Name indicates the field name. Name string @@ -1183,139 +477,25 @@ type StructField struct { Anonymous bool } -// IsExported reports whether the field is exported. -func (f StructField) IsExported() bool { - return f.PkgPath == "" -} - -// rawStructField is the same as StructField but with the Type member replaced -// with rawType. For internal use only. Avoiding this conversion to the Type -// interface improves code size in many cases. -type rawStructField struct { - Name string - PkgPath string - Type *rawType - Tag StructTag - Offset uintptr - Anonymous bool -} - -// A StructTag is the tag string in a struct field. -type StructTag string - -// TODO: it would be feasible to do the key/value splitting at compile time, -// avoiding the code size cost of doing it at runtime - -// Get returns the value associated with key in the tag string. -func (tag StructTag) Get(key string) string { - v, _ := tag.Lookup(key) - return v -} - -// Lookup returns the value associated with key in the tag string. -func (tag StructTag) Lookup(key string) (value string, ok bool) { - for tag != "" { - // Skip leading space. - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // Scan to colon. A space, a quote or a control character is a syntax error. - // Strictly speaking, control chars include the range [0x7f, 0x9f], not just - // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters - // as it is simpler to inspect the tag's bytes than the tag's runes. - i = 0 - for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { - i++ - } - if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // Scan quoted string to find value. - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if key == name { - value, err := unquote(qvalue) - if err != nil { - break - } - return value, true - } +func toStructField(f reflectlite.StructField) StructField { + return StructField{ + Name: f.Name, + PkgPath: f.PkgPath, + Type: toType(f.Type), + Tag: f.Tag, + Offset: f.Offset, + Index: f.Index, + Anonymous: f.Anonymous, } - return "", false -} - -// TypeError is the error that is used in a panic when invoking a method on a -// type that is not applicable to that type. -type TypeError struct { - Method string -} - -func (e *TypeError) Error() string { - return "reflect: call of reflect.Type." + e.Method + " on invalid type" -} - -func align(offset uintptr, alignment uintptr) uintptr { - return (offset + alignment - 1) &^ (alignment - 1) -} - -func SliceOf(t Type) Type { - panic("unimplemented: reflect.SliceOf()") -} - -func ArrayOf(n int, t Type) Type { - panic("unimplemented: reflect.ArrayOf()") -} - -func StructOf([]StructField) Type { - panic("unimplemented: reflect.StructOf()") } -func MapOf(key, value Type) Type { - panic("unimplemented: reflect.MapOf()") -} - -func FuncOf(in, out []Type, variadic bool) Type { - panic("unimplemented: reflect.FuncOf()") +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" } -const maxVarintLen32 = 5 - -// encoding/binary.Uvarint, specialized for uint32 -func uvarint32(buf []byte) (uint32, int) { - var x uint32 - var s uint - for i, b := range buf { - if b < 0x80 { - return x | uint32(b)< unsafe.Sizeof(uintptr(0)) { - return int64(*(*int)(v.value)) - } else { - return int64(int(uintptr(v.value))) - } - case Int8: - if v.isIndirect() { - return int64(*(*int8)(v.value)) - } else { - return int64(int8(uintptr(v.value))) - } - case Int16: - if v.isIndirect() { - return int64(*(*int16)(v.value)) - } else { - return int64(int16(uintptr(v.value))) - } - case Int32: - if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) { - return int64(*(*int32)(v.value)) - } else { - return int64(int32(uintptr(v.value))) - } - case Int64: - if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { - return int64(*(*int64)(v.value)) - } else { - return int64(int64(uintptr(v.value))) - } - default: - panic(&ValueError{Method: "Int", Kind: v.Kind()}) - } -} - -// CanUint reports whether Uint can be used without panicking. -func (v Value) CanUint() bool { - switch v.Kind() { - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return true - default: - return false - } -} - -func (v Value) Uint() uint64 { - switch v.Kind() { - case Uintptr: - if v.isIndirect() { - return uint64(*(*uintptr)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint8: - if v.isIndirect() { - return uint64(*(*uint8)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint16: - if v.isIndirect() { - return uint64(*(*uint16)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint: - if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint32: - if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint32)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint64: - if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint64)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - default: - panic(&ValueError{Method: "Uint", Kind: v.Kind()}) - } -} - -// CanFloat reports whether Float can be used without panicking. -func (v Value) CanFloat() bool { - switch v.Kind() { - case Float32, Float64: - return true - default: - return false - } -} - -func (v Value) Float32() float32 { - switch v.Kind() { - case Float32: - if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { - // The float is stored as an external value on systems with 16-bit - // pointers. - return *(*float32)(v.value) - } else { - // The float is directly stored in the interface value on systems - // with 32-bit and 64-bit pointers. - return *(*float32)(unsafe.Pointer(&v.value)) - } - - case Float64: - return float32(v.Float()) - - } - - panic(&ValueError{Method: "Float", Kind: v.Kind()}) -} - -func (v Value) Float() float64 { - switch v.Kind() { - case Float32: - if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) { - // The float is stored as an external value on systems with 16-bit - // pointers. - return float64(*(*float32)(v.value)) - } else { - // The float is directly stored in the interface value on systems - // with 32-bit and 64-bit pointers. - return float64(*(*float32)(unsafe.Pointer(&v.value))) - } - case Float64: - if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) { - // For systems with 16-bit and 32-bit pointers. - return *(*float64)(v.value) - } else { - // The float is directly stored in the interface value on systems - // with 64-bit pointers. - return *(*float64)(unsafe.Pointer(&v.value)) - } - default: - panic(&ValueError{Method: "Float", Kind: v.Kind()}) - } -} - -// CanComplex reports whether Complex can be used without panicking. -func (v Value) CanComplex() bool { - switch v.Kind() { - case Complex64, Complex128: - return true - default: - return false - } -} - -func (v Value) Complex() complex128 { - switch v.Kind() { - case Complex64: - if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) { - // The complex number is stored as an external value on systems with - // 16-bit and 32-bit pointers. - return complex128(*(*complex64)(v.value)) - } else { - // The complex number is directly stored in the interface value on - // systems with 64-bit pointers. - return complex128(*(*complex64)(unsafe.Pointer(&v.value))) - } - case Complex128: - // This is a 128-bit value, which is always stored as an external value. - // It may be stored in the pointer directly on very uncommon - // architectures with 128-bit pointers, however. - return *(*complex128)(v.value) - default: - panic(&ValueError{Method: "Complex", Kind: v.Kind()}) - } -} - -func (v Value) String() string { - switch v.Kind() { - case String: - // A string value is always bigger than a pointer as it is made of a - // pointer and a length. - return *(*string)(v.value) - default: - // Special case because of the special treatment of .String() in Go. - return "<" + v.typecode.String() + " Value>" - } -} - -func (v Value) Bytes() []byte { - switch v.Kind() { - case Slice: - if v.typecode.elem().Kind() != Uint8 { - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) - } - return *(*[]byte)(v.value) - - case Array: - v.checkAddressable() - - if v.typecode.elem().Kind() != Uint8 { - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) - } - - // Small inline arrays are not addressable, so we only have to - // handle addressable arrays which will be stored as pointers - // in v.value - return unsafe.Slice((*byte)(v.value), v.Len()) - } - - panic(&ValueError{Method: "Bytes", Kind: v.Kind()}) + return Value{v.Value.Addr()} } func (v Value) Slice(i, j int) Value { - switch v.Kind() { - case Slice: - hdr := *(*sliceHeader)(v.value) - i, j := uintptr(i), uintptr(j) - - if j < i || hdr.cap < j { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - hdr.len = j - i - hdr.cap = hdr.cap - i - hdr.data = unsafe.Add(hdr.data, i*elemSize) - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case Array: - v.checkAddressable() - buf, length := buflen(v) - i, j := uintptr(i), uintptr(j) - if j < i || length < j { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - var hdr sliceHeader - hdr.len = j - i - hdr.cap = length - i - hdr.data = unsafe.Add(buf, i*elemSize) - - sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr - return Value{ - typecode: sliceType, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case String: - i, j := uintptr(i), uintptr(j) - str := *(*stringHeader)(v.value) - - if j < i || str.len < j { - slicePanic() - } - - hdr := stringHeader{ - data: unsafe.Add(str.data, i), - len: j - i, - } - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - } - - panic(&ValueError{Method: "Slice", Kind: v.Kind()}) + return Value{v.Value.Slice(i, j)} } func (v Value) Slice3(i, j, k int) Value { - switch v.Kind() { - case Slice: - hdr := *(*sliceHeader)(v.value) - i, j, k := uintptr(i), uintptr(j), uintptr(k) - - if j < i || k < j || hdr.len < k { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - hdr.len = j - i - hdr.cap = k - i - hdr.data = unsafe.Add(hdr.data, i*elemSize) - - return Value{ - typecode: v.typecode, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - - case Array: - v.checkAddressable() - buf, length := buflen(v) - i, j, k := uintptr(i), uintptr(j), uintptr(k) - if j < i || k < j || length < k { - slicePanic() - } - - elemSize := v.typecode.underlying().elem().Size() - - var hdr sliceHeader - hdr.len = j - i - hdr.cap = k - i - hdr.data = unsafe.Add(buf, i*elemSize) - - sliceType := (*arrayType)(unsafe.Pointer(v.typecode.underlying())).slicePtr - return Value{ - typecode: sliceType, - value: unsafe.Pointer(&hdr), - flags: v.flags, - } - } - - panic("unimplemented: (reflect.Value).Slice3()") -} - -//go:linkname maplen runtime.hashmapLenUnsafePointer -func maplen(p unsafe.Pointer) int - -//go:linkname chanlen runtime.chanLenUnsafePointer -func chanlen(p unsafe.Pointer) int - -// Len returns the length of this value for slices, strings, arrays, channels, -// and maps. For other types, it panics. -func (v Value) Len() int { - switch v.typecode.Kind() { - case Array: - return v.typecode.Len() - case Chan: - return chanlen(v.pointer()) - case Map: - return maplen(v.pointer()) - case Slice: - return int((*sliceHeader)(v.value).len) - case String: - return int((*stringHeader)(v.value).len) - default: - panic(&ValueError{Method: "Len", Kind: v.Kind()}) - } -} - -//go:linkname chancap runtime.chanCapUnsafePointer -func chancap(p unsafe.Pointer) int - -// Cap returns the capacity of this value for arrays, channels and slices. -// For other types, it panics. -func (v Value) Cap() int { - switch v.typecode.Kind() { - case Array: - return v.typecode.Len() - case Chan: - return chancap(v.pointer()) - case Slice: - return int((*sliceHeader)(v.value).cap) - default: - panic(&ValueError{Method: "Cap", Kind: v.Kind()}) - } -} - -// NumField returns the number of fields of this struct. It panics for other -// value types. -func (v Value) NumField() int { - return v.typecode.NumField() + return Value{v.Value.Slice3(i, j, k)} } func (v Value) Elem() Value { - switch v.Kind() { - case Ptr: - ptr := v.pointer() - if ptr == nil { - return Value{} - } - // Don't copy RO flags - flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect - return Value{ - typecode: v.typecode.elem(), - value: ptr, - flags: flags, - } - case Interface: - typecode, value := decomposeInterface(*(*interface{})(v.value)) - return Value{ - typecode: (*rawType)(typecode), - value: value, - flags: v.flags &^ valueFlagIndirect, - } - default: - panic(&ValueError{Method: "Elem", Kind: v.Kind()}) - } + return Value{v.Value.Elem()} } // Field returns the value of the i'th field of this struct. func (v Value) Field(i int) Value { - if v.Kind() != Struct { - panic(&ValueError{Method: "Field", Kind: v.Kind()}) - } - structField := v.typecode.rawField(i) - - // Copy flags but clear EmbedRO; we're not an embedded field anymore - flags := v.flags & ^valueFlagEmbedRO - if structField.PkgPath != "" { - // No PkgPath => not exported. - // Clear exported flag even if the parent was exported. - flags &^= valueFlagExported - - // Update the RO flag - if structField.Anonymous { - // Embedded field - flags |= valueFlagEmbedRO - } else { - flags |= valueFlagStickyRO - } - } else { - // Parent field may not have been exported but we are - flags |= valueFlagExported - } - - size := v.typecode.Size() - fieldType := structField.Type - fieldSize := fieldType.Size() - if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) { - // v.value was already a pointer to the value and it should stay that - // way. - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Add(v.value, structField.Offset), - } - } - - // The fieldSize is smaller than uintptr, which means that the value will - // have to be stored directly in the interface value. - - if fieldSize == 0 { - // The struct field is zero sized. - // This is a rare situation, but because it's undefined behavior - // to shift the size of the value (zeroing the value), handle this - // situation explicitly. - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Pointer(nil), - } - } - - if size > unsafe.Sizeof(uintptr(0)) { - // The value was not stored in the interface before but will be - // afterwards, so load the value (from the correct offset) and return - // it. - ptr := unsafe.Add(v.value, structField.Offset) - value := unsafe.Pointer(loadValue(ptr, fieldSize)) - return Value{ - flags: flags &^ valueFlagIndirect, - typecode: fieldType, - value: value, - } - } - - // The value was already stored directly in the interface and it still - // is. Cut out the part of the value that we need. - value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Pointer(value), - } + return Value{v.Value.Field(i)} } -var uint8Type = TypeOf(uint8(0)).(*rawType) - func (v Value) Index(i int) Value { - switch v.Kind() { - case Slice: - // Extract an element from the slice. - slice := *(*sliceHeader)(v.value) - if uint(i) >= uint(slice.len) { - panic("reflect: slice index out of range") - } - flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() - elem := Value{ - typecode: v.typecode.elem(), - flags: flags, - } - elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value - return elem - case String: - // Extract a character from a string. - // A string is never stored directly in the interface, but always as a - // pointer to the string value. - // Keeping valueFlagExported if set, but don't set valueFlagIndirect - // otherwise CanSet will return true for string elements (which is bad, - // strings are read-only). - s := *(*stringHeader)(v.value) - if uint(i) >= uint(s.len) { - panic("reflect: string index out of range") - } - return Value{ - typecode: uint8Type, - value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), - flags: v.flags & valueFlagExported, - } - case Array: - // Extract an element from the array. - elemType := v.typecode.elem() - elemSize := elemType.Size() - size := v.typecode.Size() - if size == 0 { - // The element size is 0 and/or the length of the array is 0. - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - } - } - if elemSize > unsafe.Sizeof(uintptr(0)) { - // The resulting value doesn't fit in a pointer so must be - // indirect. Also, because size != 0 this implies that the array - // length must be != 0, and thus that the total size is at least - // elemSize. - addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: addr, - } - } - - if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { - // The element fits in a pointer, but the array is not stored in the pointer directly. - // Load the value from the pointer. - addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - value := addr - if !v.isIndirect() { - // Use a pointer to the value (don't load the value) if the - // 'indirect' flag is set. - value = unsafe.Pointer(loadValue(addr, elemSize)) - } - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: value, - } - } - - // The value fits in a pointer, so extract it with some shifting and - // masking. - offset := elemSize * uintptr(i) - value := maskAndShift(uintptr(v.value), offset, elemSize) - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: unsafe.Pointer(value), - } - default: - panic(&ValueError{Method: "Index", Kind: v.Kind()}) - } -} - -// loadValue loads a value that may or may not be word-aligned. The number of -// bytes given in size are loaded. The biggest possible size it can load is that -// of an uintptr. -func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { - loadedValue := uintptr(0) - shift := uintptr(0) - for i := uintptr(0); i < size; i++ { - loadedValue |= uintptr(*(*byte)(ptr)) << shift - shift += 8 - ptr = unsafe.Add(ptr, 1) - } - return loadedValue -} - -// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. -func maskAndShift(value, offset, size uintptr) uintptr { - mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) - return (uintptr(value) >> (offset * 8)) & mask -} - -func (v Value) NumMethod() int { - return v.typecode.NumMethod() -} - -// OverflowFloat reports whether the float64 x cannot be represented by v's type. -// It panics if v's Kind is not Float32 or Float64. -func (v Value) OverflowFloat(x float64) bool { - k := v.Kind() - switch k { - case Float32: - return overflowFloat32(x) - case Float64: - return false - } - panic(&ValueError{Method: "reflect.Value.OverflowFloat", Kind: v.Kind()}) -} - -func overflowFloat32(x float64) bool { - if x < 0 { - x = -x - } - return math.MaxFloat32 < x && x <= math.MaxFloat64 + return Value{v.Value.Index(i)} } func (v Value) MapKeys() []Value { - if v.Kind() != Map { - panic(&ValueError{Method: "MapKeys", Kind: v.Kind()}) - } - - // empty map - if v.Len() == 0 { - return nil - } - - keys := make([]Value, 0, v.Len()) - - it := hashmapNewIterator() - k := New(v.typecode.Key()) - e := New(v.typecode.Elem()) - - keyType := v.typecode.key() - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - for hashmapNext(v.pointer(), it, k.value, e.value) { - if shouldUnpackInterface { - intf := *(*interface{})(k.value) - v := ValueOf(intf) - keys = append(keys, v) - } else { - keys = append(keys, k.Elem()) - } - k = New(v.typecode.Key()) - } - - return keys + keys := v.Value.MapKeys() + return *(*[]Value)(unsafe.Pointer(&keys)) } -//go:linkname hashmapStringGet runtime.hashmapStringGetUnsafePointer -func hashmapStringGet(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool - -//go:linkname hashmapBinaryGet runtime.hashmapBinaryGetUnsafePointer -func hashmapBinaryGet(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool - -//go:linkname hashmapInterfaceGet runtime.hashmapInterfaceGetUnsafePointer -func hashmapInterfaceGet(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool - func (v Value) MapIndex(key Value) Value { - if v.Kind() != Map { - panic(&ValueError{Method: "MapIndex", Kind: v.Kind()}) - } - - vkey := v.typecode.key() - - // compare key type with actual key type of map - if !key.typecode.AssignableTo(vkey) { - // type error? - panic("reflect.Value.MapIndex: incompatible types for key") - } - - elemType := v.typecode.Elem() - elem := New(elemType) - - if vkey.Kind() == String { - if ok := hashmapStringGet(v.pointer(), *(*string)(key.value), elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } else if vkey.isBinary() { - var keyptr unsafe.Pointer - if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - keyptr = key.value - } else { - keyptr = unsafe.Pointer(&key.value) - } - //TODO(dgryski): zero out padding bytes in key, if any - if ok := hashmapBinaryGet(v.pointer(), keyptr, elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } else { - if ok := hashmapInterfaceGet(v.pointer(), key.Interface(), elem.value, elemType.Size()); !ok { - return Value{} - } - return elem.Elem() - } + return Value{v.Value.MapIndex(key.Value)} } -//go:linkname hashmapNewIterator runtime.hashmapNewIterator -func hashmapNewIterator() unsafe.Pointer - -//go:linkname hashmapNext runtime.hashmapNextUnsafePointer -func hashmapNext(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool - func (v Value) MapRange() *MapIter { - if v.Kind() != Map { - panic(&ValueError{Method: "MapRange", Kind: v.Kind()}) - } - - keyType := v.typecode.key() - - keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 - shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() - - return &MapIter{ - m: v, - it: hashmapNewIterator(), - unpackKeyInterface: shouldUnpackInterface, - } + return (*MapIter)(v.Value.MapRange()) } -type MapIter struct { - m Value - it unsafe.Pointer - key Value - val Value - - valid bool - unpackKeyInterface bool -} +type MapIter reflectlite.MapIter func (it *MapIter) Key() Value { - if !it.valid { - panic("reflect.MapIter.Key called on invalid iterator") - } - - if it.unpackKeyInterface { - intf := *(*interface{})(it.key.value) - v := ValueOf(intf) - return v - } - - return it.key.Elem() + return Value{((*reflectlite.MapIter)(it)).Key()} } -func (it *MapIter) Value() Value { - if !it.valid { - panic("reflect.MapIter.Value called on invalid iterator") - } - - return it.val.Elem() +func (v Value) SetIterKey(iter *MapIter) { + v.Value.SetIterKey((*reflectlite.MapIter)(iter)) } -func (it *MapIter) Next() bool { - it.key = New(it.m.typecode.Key()) - it.val = New(it.m.typecode.Elem()) - - it.valid = hashmapNext(it.m.pointer(), it.it, it.key.value, it.val.value) - return it.valid -} - -func (v Value) Set(x Value) { - v.checkAddressable() - v.checkRO() - if !x.typecode.AssignableTo(v.typecode) { - panic("reflect: cannot set") - } - - if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { - // move the value of x back into the interface, if possible - if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { - var value uintptr - memcpy(unsafe.Pointer(&value), x.value, x.typecode.Size()) - x.value = unsafe.Pointer(value) - } - - intf := composeInterface(unsafe.Pointer(x.typecode), x.value) - x = Value{ - typecode: v.typecode, - value: unsafe.Pointer(&intf), - } - } - - size := v.typecode.Size() - xptr := x.value - if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { - value := x.value - xptr = unsafe.Pointer(&value) - } - memcpy(v.value, xptr, size) -} - -func (v Value) SetZero() { - v.checkAddressable() - v.checkRO() - size := v.typecode.Size() - memzero(v.value, size) -} - -func (v Value) SetBool(x bool) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Bool: - *(*bool)(v.value) = x - default: - panic(&ValueError{Method: "SetBool", Kind: v.Kind()}) - } -} - -func (v Value) SetInt(x int64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Int: - *(*int)(v.value) = int(x) - case Int8: - *(*int8)(v.value) = int8(x) - case Int16: - *(*int16)(v.value) = int16(x) - case Int32: - *(*int32)(v.value) = int32(x) - case Int64: - *(*int64)(v.value) = x - default: - panic(&ValueError{Method: "SetInt", Kind: v.Kind()}) - } -} - -func (v Value) SetUint(x uint64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Uint: - *(*uint)(v.value) = uint(x) - case Uint8: - *(*uint8)(v.value) = uint8(x) - case Uint16: - *(*uint16)(v.value) = uint16(x) - case Uint32: - *(*uint32)(v.value) = uint32(x) - case Uint64: - *(*uint64)(v.value) = x - case Uintptr: - *(*uintptr)(v.value) = uintptr(x) - default: - panic(&ValueError{Method: "SetUint", Kind: v.Kind()}) - } -} - -func (v Value) SetFloat(x float64) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Float32: - *(*float32)(v.value) = float32(x) - case Float64: - *(*float64)(v.value) = x - default: - panic(&ValueError{Method: "SetFloat", Kind: v.Kind()}) - } -} - -func (v Value) SetComplex(x complex128) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case Complex64: - *(*complex64)(v.value) = complex64(x) - case Complex128: - *(*complex128)(v.value) = x - default: - panic(&ValueError{Method: "SetComplex", Kind: v.Kind()}) - } -} - -func (v Value) SetString(x string) { - v.checkAddressable() - v.checkRO() - switch v.Kind() { - case String: - *(*string)(v.value) = x - default: - panic(&ValueError{Method: "SetString", Kind: v.Kind()}) - } -} - -func (v Value) SetBytes(x []byte) { - v.checkAddressable() - v.checkRO() - if v.typecode.Kind() != Slice || v.typecode.elem().Kind() != Uint8 { - panic("reflect.Value.SetBytes called on not []byte") - } - - // copy the header contents over - *(*[]byte)(v.value) = x -} - -func (v Value) SetCap(n int) { - panic("unimplemented: (reflect.Value).SetCap()") +func (it *MapIter) Value() Value { + return Value{((*reflectlite.MapIter)(it)).Value()} } -func (v Value) SetLen(n int) { - if v.typecode.Kind() != Slice { - panic(&ValueError{Method: "reflect.Value.SetLen", Kind: v.Kind()}) - } - v.checkAddressable() - hdr := (*sliceHeader)(v.value) - if int(uintptr(n)) != n || uintptr(n) > hdr.cap { - panic("reflect.Value.SetLen: slice length out of range") - } - hdr.len = uintptr(n) +func (v Value) SetIterValue(iter *MapIter) { + v.Value.SetIterValue((*reflectlite.MapIter)(iter)) } -func (v Value) checkAddressable() { - if !v.isIndirect() { - panic("reflect: value is not addressable") - } +func (it *MapIter) Next() bool { + return ((*reflectlite.MapIter)(it)).Next() } -// OverflowInt reports whether the int64 x cannot be represented by v's type. -// It panics if v's Kind is not Int, Int8, Int16, Int32, or Int64. -func (v Value) OverflowInt(x int64) bool { - switch v.Kind() { - case Int, Int8, Int16, Int32, Int64: - bitSize := v.typecode.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic(&ValueError{Method: "reflect.Value.OverflowInt", Kind: v.Kind()}) +func (iter *MapIter) Reset(v Value) { + (*reflectlite.MapIter)(iter).Reset(v.Value) } -// OverflowUint reports whether the uint64 x cannot be represented by v's type. -// It panics if v's Kind is not Uint, Uintptr, Uint8, Uint16, Uint32, or Uint64. -func (v Value) OverflowUint(x uint64) bool { - k := v.Kind() - switch k { - case Uint, Uintptr, Uint8, Uint16, Uint32, Uint64: - bitSize := v.typecode.Size() * 8 - trunc := (x << (64 - bitSize)) >> (64 - bitSize) - return x != trunc - } - panic(&ValueError{Method: "reflect.Value.OverflowUint", Kind: v.Kind()}) +func (v Value) Set(x Value) { + v.Value.Set(x.Value) } func (v Value) CanConvert(t Type) bool { - panic("unimplemented: (reflect.Value).CanConvert()") + return v.Value.CanConvert(toRawType(t)) } func (v Value) Convert(t Type) Value { - if v, ok := convertOp(v, t); ok { - return v - } - - panic("reflect.Value.Convert: value of type " + v.typecode.String() + " cannot be converted to type " + t.String()) + return Value{v.Value.Convert(toRawType(t))} } -func convertOp(src Value, typ Type) (Value, bool) { - - // Easy check first. Do we even need to do anything? - if src.typecode.underlying() == typ.(*rawType).underlying() { - return Value{ - typecode: typ.(*rawType), - value: src.value, - flags: src.flags, - }, true - } - - switch src.Kind() { - case Int, Int8, Int16, Int32, Int64: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtInt(src, rtype), true - case Float32, Float64: - return cvtIntFloat(src, rtype), true - case String: - return cvtIntString(src, rtype), true - } - - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtUint(src, rtype), true - case Float32, Float64: - return cvtUintFloat(src, rtype), true - case String: - return cvtUintString(src, rtype), true - } - - case Float32, Float64: - switch rtype := typ.(*rawType); rtype.Kind() { - case Int, Int8, Int16, Int32, Int64: - return cvtFloatInt(src, rtype), true - case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: - return cvtFloatUint(src, rtype), true - case Float32, Float64: - return cvtFloat(src, rtype), true - } - - /* - case Complex64, Complex128: - switch src.Kind() { - case Complex64, Complex128: - return cvtComplex - } - */ - - 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 - } - } - - case String: - rtype := typ.(*rawType) - if typ.Kind() == Slice && !rtype.elem().isNamed() { - switch typ.Elem().Kind() { - case Uint8: - return cvtStringBytes(src, rtype), true - case Int32: - return cvtStringRunes(src, rtype), true - } - } - } - - // TODO(dgryski): Unimplemented: - // Chan - // Non-defined pointers types with same underlying base type - // Interface <-> Type conversions - - return Value{}, false -} - -func cvtInt(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(v.Int()), t) -} - -func cvtUint(v Value, t *rawType) Value { - return makeInt(v.flags, v.Uint(), t) -} - -func cvtIntFloat(v Value, t *rawType) Value { - return makeFloat(v.flags, float64(v.Int()), t) -} - -func cvtUintFloat(v Value, t *rawType) Value { - return makeFloat(v.flags, float64(v.Uint()), t) -} - -func cvtFloatInt(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(int64(v.Float())), t) -} - -func cvtFloatUint(v Value, t *rawType) Value { - return makeInt(v.flags, uint64(v.Float()), t) -} - -func cvtFloat(v Value, t *rawType) Value { - if v.Type().Kind() == Float32 && t.Kind() == Float32 { - // Don't do any conversion if both types have underlying type float32. - // This avoids converting to float64 and back, which will - // convert a signaling NaN to a quiet NaN. See issue 36400. - return makeFloat32(v.flags, v.Float32(), t) - } - return makeFloat(v.flags, v.Float(), t) -} - -//go:linkname stringToBytes runtime.stringToBytes -func stringToBytes(x string) []byte - -func cvtStringBytes(v Value, t *rawType) Value { - b := stringToBytes(*(*string)(v.value)) - return Value{ - typecode: t, - value: unsafe.Pointer(&b), - flags: v.flags, - } -} - -//go:linkname stringFromBytes runtime.stringFromBytes -func stringFromBytes(x []byte) string - -func cvtBytesString(v Value, t *rawType) Value { - s := stringFromBytes(*(*[]byte)(v.value)) - return Value{ - typecode: t, - value: unsafe.Pointer(&s), - flags: v.flags, - } -} - -func makeInt(flags valueFlags, bits uint64, t *rawType) Value { - size := t.Size() - - v := Value{ - typecode: t, - flags: flags, - } - - ptr := unsafe.Pointer(&v.value) - if size > unsafe.Sizeof(uintptr(0)) { - ptr = alloc(size, nil) - v.value = ptr - } - - switch size { - case 1: - *(*uint8)(ptr) = uint8(bits) - case 2: - *(*uint16)(ptr) = uint16(bits) - case 4: - *(*uint32)(ptr) = uint32(bits) - case 8: - *(*uint64)(ptr) = bits - } - return v -} - -func makeFloat(flags valueFlags, f float64, t *rawType) Value { - size := t.Size() - - v := Value{ - typecode: t, - flags: flags, - } - - ptr := unsafe.Pointer(&v.value) - if size > unsafe.Sizeof(uintptr(0)) { - ptr = alloc(size, nil) - v.value = ptr - } - - switch size { - case 4: - *(*float32)(ptr) = float32(f) - case 8: - *(*float64)(ptr) = f - } - return v -} - -func makeFloat32(flags valueFlags, f float32, t *rawType) Value { - v := Value{ - typecode: t, - flags: flags, - } - *(*float32)(unsafe.Pointer(&v.value)) = float32(f) - return v -} - -func cvtIntString(src Value, t *rawType) Value { - panic("cvtUintString: unimplemented") -} - -func cvtUintString(src Value, t *rawType) Value { - panic("cvtUintString: unimplemented") -} - -func cvtStringRunes(src Value, t *rawType) Value { - panic("cvsStringRunes: unimplemented") -} - -func cvtRunesString(src Value, t *rawType) Value { - panic("cvsRunesString: unimplemented") -} - -//go:linkname slicePanic runtime.slicePanic -func slicePanic() - func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != Slice { - panic("reflect.MakeSlice of non-slice type") - } - - rtype := typ.(*rawType) - - ulen := uint(len) - ucap := uint(cap) - maxSize := (^uintptr(0)) / 2 - elementSize := rtype.elem().Size() - if elementSize > 1 { - maxSize /= uintptr(elementSize) - } - if ulen > ucap || ucap > uint(maxSize) { - slicePanic() - } - - // This can't overflow because of the above checks. - size := uintptr(ucap) * elementSize - - var slice sliceHeader - slice.cap = uintptr(ucap) - slice.len = uintptr(ulen) - slice.data = alloc(size, nil) - - return Value{ - typecode: rtype, - value: unsafe.Pointer(&slice), - flags: valueFlagExported, - } -} - -var zerobuffer unsafe.Pointer - -const zerobufferLen = 32 - -func init() { - // 32 characters of zero bytes - zerobufferStr := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - s := (*stringHeader)(unsafe.Pointer(&zerobufferStr)) - zerobuffer = s.data + return Value{reflectlite.MakeSlice(toRawType(typ), len, cap)} } func Zero(typ Type) Value { - size := typ.Size() - if size <= unsafe.Sizeof(uintptr(0)) { - return Value{ - typecode: typ.(*rawType), - value: nil, - flags: valueFlagExported | valueFlagRO, - } - } - - if size <= zerobufferLen { - return Value{ - typecode: typ.(*rawType), - value: unsafe.Pointer(zerobuffer), - flags: valueFlagExported | valueFlagRO, - } - } - - return Value{ - typecode: typ.(*rawType), - value: alloc(size, nil), - flags: valueFlagExported | valueFlagRO, - } + return Value{reflectlite.Zero(toRawType(typ))} } // New is the reflect equivalent of the new(T) keyword, returning a pointer to a // new value of the given type. func New(typ Type) Value { - return Value{ - typecode: pointerTo(typ.(*rawType)), - value: alloc(typ.Size(), nil), - flags: valueFlagExported, - } + return Value{reflectlite.New(toRawType(typ))} } -type funcHeader struct { - Context unsafe.Pointer - Code unsafe.Pointer -} - -type SliceHeader struct { - Data uintptr - Len uintptr - Cap uintptr -} - -// Slice header that matches the underlying structure. Used for when we switch -// to a precise GC, which needs to know exactly where pointers live. -type sliceHeader struct { - data unsafe.Pointer - len uintptr - cap uintptr -} - -type StringHeader struct { - Data uintptr - Len uintptr -} - -// Like sliceHeader, this type is used internally to make sure pointer and -// non-pointer fields match those of actual strings. -type stringHeader struct { - data unsafe.Pointer - len uintptr -} - -type ValueError struct { - Method string - Kind Kind -} - -func (e *ValueError) Error() string { - if e.Kind == 0 { - return "reflect: call of " + e.Method + " on zero Value" - } - return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" -} - -//go:linkname memcpy runtime.memcpy -func memcpy(dst, src unsafe.Pointer, size uintptr) - -//go:linkname memzero runtime.memzero -func memzero(ptr unsafe.Pointer, size uintptr) - -//go:linkname alloc runtime.alloc -func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer - -//go:linkname sliceAppend runtime.sliceAppend -func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) - -//go:linkname sliceCopy runtime.sliceCopy -func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int +type ValueError = reflectlite.ValueError // Copy copies the contents of src into dst until either // dst has been filled or src has been exhausted. func Copy(dst, src Value) int { - compatibleTypes := false || - // dst and src are both slices or arrays with equal types - ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && - (src.typecode.Kind() == Slice || src.typecode.Kind() == Array) && - (dst.typecode.elem() == src.typecode.elem())) || - // dst is array or slice of uint8 and src is string - ((dst.typecode.Kind() == Slice || dst.typecode.Kind() == Array) && - dst.typecode.elem().Kind() == Uint8 && - src.typecode.Kind() == String) - - if !compatibleTypes { - panic("Copy: type mismatch: " + dst.typecode.String() + "/" + src.typecode.String()) - } - - // Can read from an unaddressable array but not write to one. - if dst.typecode.Kind() == Array && !dst.isIndirect() { - panic("reflect.Copy: unaddressable array value") - } - - dstbuf, dstlen := buflen(dst) - srcbuf, srclen := buflen(src) - - if srclen > 0 { - dst.checkRO() - } - - return sliceCopy(dstbuf, srcbuf, dstlen, srclen, dst.typecode.elem().Size()) -} - -func buflen(v Value) (unsafe.Pointer, uintptr) { - var buf unsafe.Pointer - var len uintptr - switch v.typecode.Kind() { - case Slice: - hdr := (*sliceHeader)(v.value) - buf = hdr.data - len = hdr.len - case Array: - if v.isIndirect() { - buf = v.value - } else { - buf = unsafe.Pointer(&v.value) - } - len = uintptr(v.Len()) - case String: - hdr := (*stringHeader)(v.value) - buf = hdr.data - len = hdr.len - default: - // This shouldn't happen - panic("reflect.Copy: not slice or array or string") - } - - return buf, len -} - -//go:linkname sliceGrow runtime.sliceGrow -func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) - -// extend slice to hold n new elements -func extendSlice(v Value, n int) sliceHeader { - if v.Kind() != Slice { - panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) - } - - var old sliceHeader - if v.value != nil { - old = *(*sliceHeader)(v.value) - } - - var nbuf unsafe.Pointer - var nlen, ncap uintptr - - if old.len+uintptr(n) > old.cap { - // we need to grow the slice - nbuf, nlen, ncap = sliceGrow(old.data, old.len, old.cap, old.cap+uintptr(n), v.typecode.elem().Size()) - } else { - // we can reuse the slice we have - nbuf = old.data - nlen = old.len - ncap = old.cap - } - - return sliceHeader{ - data: nbuf, - len: nlen + uintptr(n), - cap: ncap, - } + return reflectlite.Copy(dst.Value, src.Value) } // Append appends the values x to a slice s and returns the resulting slice. // As in Go, each x's value must be assignable to the slice's element type. func Append(v Value, x ...Value) Value { - if v.Kind() != Slice { - panic(&ValueError{Method: "Append", Kind: v.Kind()}) - } - oldLen := v.Len() - newslice := extendSlice(v, len(x)) - v.flags = valueFlagExported - v.value = (unsafe.Pointer)(&newslice) - for i, xx := range x { - v.Index(oldLen + i).Set(xx) - } - return v + y := *(*[]reflectlite.Value)(unsafe.Pointer(&x)) + return Value{reflectlite.Append(v.Value, y...)} } // AppendSlice appends a slice t to a slice s and returns the resulting slice. // The slices s and t must have the same element type. func AppendSlice(s, t Value) Value { - if s.typecode.Kind() != Slice || t.typecode.Kind() != Slice || s.typecode != t.typecode { - // Not a very helpful error message, but shortened to just one error to - // keep code size down. - panic("reflect.AppendSlice: invalid types") - } - if !s.isExported() || !t.isExported() { - // One of the sides was not exported, so can't access the data. - panic("reflect.AppendSlice: unexported") - } - sSlice := (*sliceHeader)(s.value) - tSlice := (*sliceHeader)(t.value) - elemSize := s.typecode.elem().Size() - ptr, len, cap := sliceAppend(sSlice.data, tSlice.data, sSlice.len, sSlice.cap, tSlice.len, elemSize) - result := &sliceHeader{ - data: ptr, - len: len, - cap: cap, - } - return Value{ - typecode: s.typecode, - value: unsafe.Pointer(result), - flags: valueFlagExported, - } + return Value{reflectlite.AppendSlice(s.Value, t.Value)} } -// Grow increases the slice's capacity, if necessary, to guarantee space for -// another n elements. After Grow(n), at least n elements can be appended -// to the slice without another allocation. -// -// It panics if v's Kind is not a Slice or if n is negative or too large to -// allocate the memory. -func (v Value) Grow(n int) { - v.checkAddressable() - if n < 0 { - panic("reflect.Grow: negative length") - } - if v.Kind() != Slice { - panic(&ValueError{Method: "Grow", Kind: v.Kind()}) - } - slice := (*sliceHeader)(v.value) - newslice := extendSlice(v, n) - // Only copy the new data and cap: the len remains unchanged. - slice.data = newslice.data - slice.cap = newslice.cap -} - -//go:linkname hashmapStringSet runtime.hashmapStringSetUnsafePointer -func hashmapStringSet(m unsafe.Pointer, key string, value unsafe.Pointer) - -//go:linkname hashmapBinarySet runtime.hashmapBinarySetUnsafePointer -func hashmapBinarySet(m unsafe.Pointer, key, value unsafe.Pointer) - -//go:linkname hashmapInterfaceSet runtime.hashmapInterfaceSetUnsafePointer -func hashmapInterfaceSet(m unsafe.Pointer, key interface{}, value unsafe.Pointer) - -//go:linkname hashmapStringDelete runtime.hashmapStringDeleteUnsafePointer -func hashmapStringDelete(m unsafe.Pointer, key string) - -//go:linkname hashmapBinaryDelete runtime.hashmapBinaryDeleteUnsafePointer -func hashmapBinaryDelete(m unsafe.Pointer, key unsafe.Pointer) - -//go:linkname hashmapInterfaceDelete runtime.hashmapInterfaceDeleteUnsafePointer -func hashmapInterfaceDelete(m unsafe.Pointer, key interface{}) - func (v Value) SetMapIndex(key, elem Value) { - v.checkRO() - if v.Kind() != Map { - panic(&ValueError{Method: "SetMapIndex", Kind: v.Kind()}) - } - - vkey := v.typecode.key() - - // compare key type with actual key type of map - if !key.typecode.AssignableTo(vkey) { - panic("reflect.Value.SetMapIndex: incompatible types for key") - } - - // if elem is the zero Value, it means delete - del := elem == Value{} - - if !del && !elem.typecode.AssignableTo(v.typecode.elem()) { - panic("reflect.Value.SetMapIndex: incompatible types for value") - } - - // make elem an interface if it needs to be converted - if v.typecode.elem().Kind() == Interface && elem.typecode.Kind() != Interface { - intf := composeInterface(unsafe.Pointer(elem.typecode), elem.value) - elem = Value{ - typecode: v.typecode.elem(), - value: unsafe.Pointer(&intf), - } - } - - if key.Kind() == String { - if del { - hashmapStringDelete(v.pointer(), *(*string)(key.value)) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - hashmapStringSet(v.pointer(), *(*string)(key.value), elemptr) - } - - } else if key.typecode.isBinary() { - var keyptr unsafe.Pointer - if key.isIndirect() || key.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - keyptr = key.value - } else { - keyptr = unsafe.Pointer(&key.value) - } - - if del { - hashmapBinaryDelete(v.pointer(), keyptr) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - hashmapBinarySet(v.pointer(), keyptr, elemptr) - } - } else { - if del { - hashmapInterfaceDelete(v.pointer(), key.Interface()) - } else { - var elemptr unsafe.Pointer - if elem.isIndirect() || elem.typecode.Size() > unsafe.Sizeof(uintptr(0)) { - elemptr = elem.value - } else { - elemptr = unsafe.Pointer(&elem.value) - } - - hashmapInterfaceSet(v.pointer(), key.Interface(), elemptr) - } - } + v.Value.SetMapIndex(key.Value, elem.Value) } // FieldByIndex returns the nested field corresponding to index. func (v Value) FieldByIndex(index []int) Value { - if len(index) == 1 { - return v.Field(index[0]) - } - if v.Kind() != Struct { - panic(&ValueError{"FieldByIndex", v.Kind()}) - } - for i, x := range index { - if i > 0 { - if v.Kind() == Pointer && v.typecode.elem().Kind() == Struct { - if v.IsNil() { - panic("reflect: indirection through nil pointer to embedded struct") - } - v = v.Elem() - } - } - v = v.Field(x) - } - return v + return Value{v.Value.FieldByIndex(index)} } // FieldByIndexErr returns the nested field corresponding to index. func (v Value) FieldByIndexErr(index []int) (Value, error) { - return Value{}, &ValueError{Method: "FieldByIndexErr"} + out, err := v.Value.FieldByIndexErr(index) + return Value{out}, err } func (v Value) FieldByName(name string) Value { - if v.Kind() != Struct { - panic(&ValueError{"FieldByName", v.Kind()}) - } - - if field, ok := v.typecode.FieldByName(name); ok { - return v.FieldByIndex(field.Index) - } - return Value{} + return Value{v.Value.FieldByName(name)} } func (v Value) FieldByNameFunc(match func(string) bool) Value { - if v.Kind() != Struct { - panic(&ValueError{"FieldByName", v.Kind()}) - } - - if field, ok := v.typecode.FieldByNameFunc(match); ok { - return v.FieldByIndex(field.Index) - } - return Value{} -} - -//go:linkname hashmapMake runtime.hashmapMakeUnsafePointer -func hashmapMake(keySize, valueSize uintptr, sizeHint uintptr, alg uint8) unsafe.Pointer - -// MakeMapWithSize creates a new map with the specified type and initial space -// for approximately n elements. -func MakeMapWithSize(typ Type, n int) Value { - - // TODO(dgryski): deduplicate these? runtime and reflect both need them. - const ( - hashmapAlgorithmBinary uint8 = iota - hashmapAlgorithmString - hashmapAlgorithmInterface - ) - - if typ.Kind() != Map { - panic(&ValueError{Method: "MakeMap", Kind: typ.Kind()}) - } - - if n < 0 { - panic("reflect.MakeMapWithSize: negative size hint") - } - - key := typ.Key().(*rawType) - val := typ.Elem().(*rawType) - - var alg uint8 - - if key.Kind() == String { - alg = hashmapAlgorithmString - } else if key.isBinary() { - alg = hashmapAlgorithmBinary - } else { - alg = hashmapAlgorithmInterface - } - - m := hashmapMake(key.Size(), val.Size(), uintptr(n), alg) - - return Value{ - typecode: typ.(*rawType), - value: m, - flags: valueFlagExported, - } + return Value{v.Value.FieldByNameFunc(match)} } type SelectDir int @@ -2004,7 +184,13 @@ func (v Value) Close() { // MakeMap creates a new map with the specified type. func MakeMap(typ Type) Value { - return MakeMapWithSize(typ, 8) + return Value{reflectlite.MakeMap(toRawType(typ))} +} + +// MakeMapWithSize creates a new map with the specified type and initial space +// for approximately n elements. +func MakeMapWithSize(typ Type, n int) Value { + return Value{reflectlite.MakeMapWithSize(toRawType(typ), n)} } func (v Value) Call(in []Value) []Value { @@ -2015,6 +201,10 @@ func (v Value) CallSlice(in []Value) []Value { panic("unimplemented: (reflect.Value).CallSlice()") } +func (v Value) Equal(u Value) bool { + return v.Value.Equal(u.Value) +} + func (v Value) Method(i int) Value { panic("unimplemented: (reflect.Value).Method()") } @@ -2030,3 +220,24 @@ func (v Value) Recv() (x Value, ok bool) { func NewAt(typ Type, p unsafe.Pointer) Value { panic("unimplemented: reflect.New()") } + +// Deprecated: Use unsafe.Slice or unsafe.SliceData instead. +type SliceHeader struct { + Data uintptr + Len intw + Cap intw +} + +// Deprecated: Use unsafe.String or unsafe.StringData instead. +type StringHeader struct { + Data uintptr + Len intw +} + +// Verify SliceHeader and StringHeader sizes. +// See https://github.com/tinygo-org/tinygo/pull/4156 +// and https://github.com/tinygo-org/tinygo/issues/1284. +var ( + _ [unsafe.Sizeof([]byte{})]byte = [unsafe.Sizeof(SliceHeader{})]byte{} + _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(StringHeader{})]byte{} +) diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index 6641750b9b..4df9db4d19 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -1,8 +1,10 @@ package reflect_test import ( + "bytes" "encoding/base64" . "reflect" + "slices" "sort" "strings" "testing" @@ -164,7 +166,7 @@ func TestTinyMap(t *testing.T) { // make sure we can get it out v2 := refut.MapIndex(ValueOf(unmarshalerText{"x", "y"})) if !v2.IsValid() || !v2.Bool() { - t.Errorf("Failed to look up map struct key with refection") + t.Errorf("Failed to look up map struct key with reflection") } // put in a key with reflection @@ -489,7 +491,7 @@ func TestTinyStruct(t *testing.T) { func TestTinyZero(t *testing.T) { s := "hello, world" - var sptr *string = &s + sptr := &s v := ValueOf(&sptr).Elem() v.Set(Zero(v.Type())) @@ -535,7 +537,7 @@ func TestTinyAddr(t *testing.T) { } func TestTinyNilType(t *testing.T) { - var a any = nil + var a any typ := TypeOf(a) if typ != nil { t.Errorf("Type of any{nil} is not nil") @@ -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) diff --git a/src/reflect/visiblefields.go b/src/reflect/visiblefields.go index 9375faa110..ac722da070 100644 --- a/src/reflect/visiblefields.go +++ b/src/reflect/visiblefields.go @@ -1,105 +1,11 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - package reflect -// VisibleFields returns all the visible fields in t, which must be a -// struct type. A field is defined as visible if it's accessible -// directly with a FieldByName call. The returned fields include fields -// inside anonymous struct members and unexported fields. They follow -// the same order found in the struct, with anonymous fields followed -// immediately by their promoted fields. -// -// For each element e of the returned slice, the corresponding field -// can be retrieved from a value v of type t by calling v.FieldByIndex(e.Index). -func VisibleFields(t Type) []StructField { - if t == nil { - panic("reflect: VisibleFields(nil)") - } - if t.Kind() != Struct { - panic("reflect.VisibleFields of non-struct type") - } - w := &visibleFieldsWalker{ - byName: make(map[string]int), - visiting: make(map[Type]bool), - fields: make([]StructField, 0, t.NumField()), - index: make([]int, 0, 2), - } - w.walk(t) - // Remove all the fields that have been hidden. - // Use an in-place removal that avoids copying in - // the common case that there are no hidden fields. - j := 0 - for i := range w.fields { - f := &w.fields[i] - if f.Name == "" { - continue - } - if i != j { - // A field has been removed. We need to shuffle - // all the subsequent elements up. - w.fields[j] = *f - } - j++ - } - return w.fields[:j] -} - -type visibleFieldsWalker struct { - byName map[string]int - visiting map[Type]bool - fields []StructField - index []int -} +import ( + "internal/reflectlite" + "unsafe" +) -// walk walks all the fields in the struct type t, visiting -// fields in index preorder and appending them to w.fields -// (this maintains the required ordering). -// Fields that have been overridden have their -// Name field cleared. -func (w *visibleFieldsWalker) walk(t Type) { - if w.visiting[t] { - return - } - w.visiting[t] = true - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - w.index = append(w.index, i) - add := true - if oldIndex, ok := w.byName[f.Name]; ok { - old := &w.fields[oldIndex] - if len(w.index) == len(old.Index) { - // Fields with the same name at the same depth - // cancel one another out. Set the field name - // to empty to signify that has happened, and - // there's no need to add this field. - old.Name = "" - add = false - } else if len(w.index) < len(old.Index) { - // The old field loses because it's deeper than the new one. - old.Name = "" - } else { - // The old field wins because it's shallower than the new one. - add = false - } - } - if add { - // Copy the index so that it's not overwritten - // by the other appends. - f.Index = append([]int(nil), w.index...) - w.byName[f.Name] = len(w.fields) - w.fields = append(w.fields, f) - } - if f.Anonymous { - if f.Type.Kind() == Pointer { - f.Type = f.Type.Elem() - } - if f.Type.Kind() == Struct { - w.walk(f.Type) - } - } - w.index = w.index[:len(w.index)-1] - } - delete(w.visiting, t) +func VisibleFields(t Type) []StructField { + fields := reflectlite.VisibleFields(toRawType(t)) + return *(*[]StructField)(unsafe.Pointer(&fields)) } diff --git a/src/runtime/algorithm.go b/src/runtime/algorithm.go index 15ca2b7f5d..d8cd1ca43a 100644 --- a/src/runtime/algorithm.go +++ b/src/runtime/algorithm.go @@ -23,6 +23,12 @@ func fastrand() uint32 { return xorshift32State } +func initRand() { + r, _ := hardwareRand() + xorshift64State = uint64(r | 1) // protect against 0 + xorshift32State = uint32(xorshift64State) +} + var xorshift32State uint32 = 1 func xorshift32(x uint32) uint32 { diff --git a/src/runtime/arch_386.go b/src/runtime/arch_386.go index c4dbdf1661..90ec8e8baf 100644 --- a/src/runtime/arch_386.go +++ b/src/runtime/arch_386.go @@ -9,6 +9,13 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_amd64.go b/src/runtime/arch_amd64.go index 996476013b..436d6e3849 100644 --- a/src/runtime/arch_amd64.go +++ b/src/runtime/arch_amd64.go @@ -9,6 +9,13 @@ const deferExtraRegs = 0 const callInstSize = 5 // "call someFunction" is 5 bytes +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) + // Align a pointer. // Note that some amd64 instructions (like movaps) expect 16-byte aligned // memory, thus the result must be 16-byte aligned. diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 33a7513b0a..ea6b540d2a 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -11,6 +11,13 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) + // Align on the maximum alignment for this platform (double). func align(ptr uintptr) uintptr { return (ptr + 7) &^ 7 diff --git a/src/runtime/arch_arm64.go b/src/runtime/arch_arm64.go index ac28ab7c3d..6d3c856cf6 100644 --- a/src/runtime/arch_arm64.go +++ b/src/runtime/arch_arm64.go @@ -9,6 +9,13 @@ const deferExtraRegs = 0 const callInstSize = 4 // "bl someFunction" is 4 bytes +const ( + linux_MAP_ANONYMOUS = 0x20 + linux_SIGBUS = 7 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) + // Align on word boundary. func align(ptr uintptr) uintptr { return (ptr + 15) &^ 15 diff --git a/src/runtime/arch_mips.go b/src/runtime/arch_mips.go new file mode 100644 index 0000000000..5a7d05c898 --- /dev/null +++ b/src/runtime/arch_mips.go @@ -0,0 +1,26 @@ +package runtime + +const GOARCH = "mips" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +const deferExtraRegs = 0 + +const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot + +const ( + linux_MAP_ANONYMOUS = 0x800 + linux_SIGBUS = 10 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) + +// It appears that MIPS has a maximum alignment of 8 bytes. +func align(ptr uintptr) uintptr { + return (ptr + 7) &^ 7 +} + +func getCurrentStackPointer() uintptr { + return uintptr(stacksave()) +} diff --git a/src/runtime/arch_mipsle.go b/src/runtime/arch_mipsle.go new file mode 100644 index 0000000000..498cf862b7 --- /dev/null +++ b/src/runtime/arch_mipsle.go @@ -0,0 +1,26 @@ +package runtime + +const GOARCH = "mipsle" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +const deferExtraRegs = 0 + +const callInstSize = 8 // "jal someFunc" is 4 bytes, plus a MIPS delay slot + +const ( + linux_MAP_ANONYMOUS = 0x800 + linux_SIGBUS = 10 + linux_SIGILL = 4 + linux_SIGSEGV = 11 +) + +// It appears that MIPS has a maximum alignment of 8 bytes. +func align(ptr uintptr) uintptr { + return (ptr + 7) &^ 7 +} + +func getCurrentStackPointer() uintptr { + return uintptr(stacksave()) +} diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index 0d376c48b8..921c775a5e 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -36,7 +36,7 @@ func procUnpin() { func waitForEvents() { mask := riscv.DisableInterrupts() - if !runqueue.Empty() { + if runqueue := schedulerRunQueue(); runqueue == nil || !runqueue.Empty() { riscv.Asm("wfi") } riscv.EnableInterrupts(mask) diff --git a/src/runtime/asm_mipsx.S b/src/runtime/asm_mipsx.S new file mode 100644 index 0000000000..f2e81bd941 --- /dev/null +++ b/src/runtime/asm_mipsx.S @@ -0,0 +1,44 @@ +// Do not reorder instructions to insert a branch delay slot. +// We know what we're doing, and will manually fill the branch delay slot. +.set noreorder + +.section .text.tinygo_scanCurrentStack +.global tinygo_scanCurrentStack +.type tinygo_scanCurrentStack, %function +tinygo_scanCurrentStack: + // Push callee-saved registers onto the stack. + addiu $sp, $sp, -40 + sw $ra, 36($sp) + sw $s8, 32($sp) + sw $s7, 28($sp) + sw $s6, 24($sp) + sw $s5, 20($sp) + sw $s4, 16($sp) + sw $s3, 12($sp) + sw $s2, 8($sp) + sw $s1, 4($sp) + sw $s0, ($sp) + + // Scan the stack. + jal tinygo_scanstack + move $a0, $sp // in the branch delay slot + + // Restore return address. + lw $ra, 36($sp) + + // Restore stack state. + addiu $sp, $sp, 40 + + // Return to the caller. + jalr $ra + nop + +.section .text.tinygo_longjmp +.global tinygo_longjmp +tinygo_longjmp: + // Note: the code we jump to assumes a0 is non-zero, which is already the + // case because that's the defer frame pointer. + lw $sp, 0($a0) // jumpSP + lw $a1, 4($a0) // jumpPC + jr $a1 + nop diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index 173d0db25e..aecb189720 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -86,3 +86,16 @@ func AdjustTimeOffset(offset int64) { // TODO: do this atomically? timeOffset += offset } + +// Picolibc is not configured to define its own errno value, instead it calls +// __errno_location. +// TODO: a global works well enough for now (same as errno on Linux with +// -scheduler=tasks), but this should ideally be a thread-local variable stored +// in task.Task. +// Especially when we add multicore support for microcontrollers. +var errno int32 + +//export __errno_location +func libc_errno_location() *int32 { + return &errno +} diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 6253722467..e437798b09 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -1,27 +1,46 @@ package runtime // This file implements the 'chan' type and send/receive/select operations. - -// A channel can be in one of the following states: -// empty: -// No goroutine is waiting on a send or receive operation. The 'blocked' -// member is nil. -// recv: -// A goroutine tries to receive from the channel. This goroutine is stored -// in the 'blocked' member. -// send: -// The reverse of send. A goroutine tries to send to the channel. This -// goroutine is stored in the 'blocked' member. -// closed: -// The channel is closed. Sends will panic, receives will get a zero value -// plus optionally the indication that the channel is zero (with the -// comma-ok value in the task). // -// A send/recv transmission is completed by copying from the data element of the -// sending task to the data element of the receiving task, and setting -// the 'comma-ok' value to true. -// A receive operation on a closed channel is completed by zeroing the data -// element of the receiving task and setting the 'comma-ok' value to false. +// Every channel has a list of senders and a list of receivers, and possibly a +// queue. There is no 'channel state', the state is inferred from the available +// senders/receivers and values in the buffer. +// +// - A sender will first try to send the value to a waiting receiver if there is +// one, but only if there is nothing in the queue (to keep the values flowing +// in the correct order). If it can't, it will add the value in the queue and +// possibly wait as a sender if there's no space available. +// - A receiver will first try to read a value from the queue, but if there is +// none it will try to read from a sender in the list. It will block if it +// can't proceed. +// +// State is kept in various ways: +// +// - The sender value is stored in the sender 'channelOp', which is really a +// queue entry. This works for both senders and select operations: a select +// operation has a separate value to send for each case. +// - The receiver value is stored inside Task.Ptr. This works for receivers, and +// importantly also works for select which has a single buffer for every +// receive operation. +// - The `Task.Data` value stores how the channel operation proceeded. For +// normal send/receive operations, it starts at chanOperationWaiting and then +// is changed to chanOperationOk or chanOperationClosed depending on whether +// the send/receive proceeded normally or because it was closed. For a select +// operation, it also stores the 'case' index in the upper bits (zero for +// non-select operations) so that the select operation knows which case did +// proceed. +// The value is at the same time also a way that goroutines can be the first +// (and only) goroutine to 'take' a channel operation using an atomic CAS +// operation to change it from 'waiting' to any other value. This is important +// for the select statement because multiple goroutines could try to let +// different channels in the select statement proceed at the same time. By +// using Task.Data, only a single channel operation in the select statement +// can proceed. +// - It is possible for the channel queues to contain already-processed senders +// or receivers. This can happen when the select statement managed to proceed +// but the goroutine doing the select has not yet cleaned up the stale queue +// entries before returning. This should therefore only happen for a short +// period. import ( "internal/task" @@ -29,504 +48,296 @@ import ( "unsafe" ) -func chanDebug(ch *channel) { - if schedulerDebug { - if ch.bufSize > 0 { - println("--- channel update:", ch, ch.state.String(), ch.bufSize, ch.bufUsed) - } else { - println("--- channel update:", ch, ch.state.String()) - } - } +// The runtime implementation of the Go 'chan' type. +type channel struct { + closed bool + selectLocked bool + elementSize uintptr + bufCap uintptr // 'cap' + bufLen uintptr // 'len' + bufHead uintptr + bufTail uintptr + senders chanQueue + receivers chanQueue + lock task.PMutex + buf unsafe.Pointer } -// channelBlockedList is a list of channel operations on a specific channel which are currently blocked. -type channelBlockedList struct { - // next is a pointer to the next blocked channel operation on the same channel. - next *channelBlockedList - - // t is the task associated with this channel operation. - // If this channel operation is not part of a select, then the pointer field of the state holds the data buffer. - // If this channel operation is part of a select, then the pointer field of the state holds the receive buffer. - // If this channel operation is a receive, then the data field should be set to zero when resuming due to channel closure. - t *task.Task - - // s is a pointer to the channel select state corresponding to this operation. - // This will be nil if and only if this channel operation is not part of a select statement. - // If this is a send operation, then the send buffer can be found in this select state. - s *chanSelectState - - // allSelectOps is a slice containing all of the channel operations involved with this select statement. - // Before resuming the task, all other channel operations on this select statement should be canceled by removing them from their corresponding lists. - allSelectOps []channelBlockedList +const ( + chanOperationWaiting = 0b00 // waiting for a send/receive operation to continue + chanOperationOk = 0b01 // successfully sent or received (not closed) + chanOperationClosed = 0b10 // channel was closed, the value has been zeroed + chanOperationMask = 0b11 +) + +type chanQueue struct { + first *channelOp } -// remove takes the current list of blocked channel operations and removes the specified operation. -// This returns the resulting list, or nil if the resulting list is empty. -// A nil receiver is treated as an empty list. -func (b *channelBlockedList) remove(old *channelBlockedList) *channelBlockedList { - if b == old { - return b.next - } - c := b - for ; c != nil && c.next != old; c = c.next { - } - if c != nil { - c.next = old.next - } - return b +// Pus the next channel operation to the queue. All appropriate fields must have +// been initialized already. +// This function must be called with interrupts disabled and the channel lock +// held. +func (q *chanQueue) push(node *channelOp) { + node.next = q.first + q.first = node } -// detatch removes all other channel operations that are part of the same select statement. -// If the input is not part of a select statement, this is a no-op. -// This must be called before resuming any task blocked on a channel operation in order to ensure that it is not placed on the runqueue twice. -func (b *channelBlockedList) detach() { - if b.allSelectOps == nil { - // nothing to do - return - } - for i, v := range b.allSelectOps { - // cancel all other channel operations that are part of this select statement - switch { - case &b.allSelectOps[i] == b: - // This entry is the one that was already detatched. - continue - case v.t == nil: - // This entry is not used (nil channel). - continue +// Pop the next waiting channel from the queue. Channels that are no longer +// waiting (for example, when they're part of a select operation) will be +// skipped. +// This function must be called with interrupts disabled. +func (q *chanQueue) pop(chanOp uint32) *channelOp { + for { + if q.first == nil { + return nil } - v.s.ch.blocked = v.s.ch.blocked.remove(&b.allSelectOps[i]) - if v.s.ch.blocked == nil { - if v.s.value == nil { - // recv operation - if v.s.ch.state != chanStateClosed { - v.s.ch.state = chanStateEmpty - } - } else { - // send operation - if v.s.ch.bufUsed == 0 { - // unbuffered channel - v.s.ch.state = chanStateEmpty - } else { - // buffered channel - v.s.ch.state = chanStateBuf - } - } + + // Pop next from the queue. + popped := q.first + q.first = q.first.next + + // The new value for the 'data' field will be a combination of the + // channel operation and the select index. (The select index is 0 for + // non-select channel operations). + newDataValue := chanOp | popped.index<<2 + + // Try to be the first to proceed with this goroutine. + swapped := popped.task.DataAtomicUint32().CompareAndSwap(0, newDataValue) + if swapped { + return popped } - chanDebug(v.s.ch) } } -type channel struct { - elementSize uintptr // the size of one value in this channel - bufSize uintptr // size of buffer (in elements) - state chanState - blocked *channelBlockedList - bufHead uintptr // head index of buffer (next push index) - bufTail uintptr // tail index of buffer (next pop index) - bufUsed uintptr // number of elements currently in buffer - buf unsafe.Pointer // pointer to first element of buffer +// Remove the given to-be-removed node from the queue if it is part of the +// queue. If there are multiple, only one will be removed. +// This function must be called with interrupts disabled and the channel lock +// held. +func (q *chanQueue) remove(remove *channelOp) { + n := &q.first + for *n != nil { + if *n == remove { + *n = (*n).next + return + } + n = &((*n).next) + } +} + +type channelOp struct { + next *channelOp + task *task.Task + index uint32 // select index, 0 for non-select operation + value unsafe.Pointer // if this is a sender, this is the value to send +} + +type chanSelectState struct { + ch *channel + value unsafe.Pointer } -// chanMake creates a new channel with the given element size and buffer length in number of elements. -// This is a compiler intrinsic. func chanMake(elementSize uintptr, bufSize uintptr) *channel { return &channel{ elementSize: elementSize, - bufSize: bufSize, + bufCap: bufSize, buf: alloc(elementSize*bufSize, nil), } } // Return the number of entries in this chan, called from the len builtin. // A nil chan is defined as having length 0. -// -//go:inline func chanLen(c *channel) int { if c == nil { return 0 } - return int(c.bufUsed) -} - -// wrapper for use in reflect -func chanLenUnsafePointer(p unsafe.Pointer) int { - c := (*channel)(p) - return chanLen(c) + return int(c.bufLen) } // Return the capacity of this chan, called from the cap builtin. // A nil chan is defined as having capacity 0. -// -//go:inline func chanCap(c *channel) int { if c == nil { return 0 } - return int(c.bufSize) -} - -// wrapper for use in reflect -func chanCapUnsafePointer(p unsafe.Pointer) int { - c := (*channel)(p) - return chanCap(c) -} - -// resumeRX resumes the next receiver and returns the destination pointer. -// If the ok value is true, then the caller is expected to store a value into this pointer. -func (ch *channel) resumeRX(ok bool) unsafe.Pointer { - // pop a blocked goroutine off the stack - var b *channelBlockedList - b, ch.blocked = ch.blocked, ch.blocked.next - - // get destination pointer - dst := b.t.Ptr - - if !ok { - // the result value is zero - memzero(dst, ch.elementSize) - b.t.Data = 0 - } - - if b.s != nil { - // tell the select op which case resumed - b.t.Ptr = unsafe.Pointer(b.s) - - // detach associated operations - b.detach() - } - - // push task onto runqueue - runqueue.Push(b.t) - - return dst + return int(c.bufCap) } -// resumeTX resumes the next sender and returns the source pointer. -// The caller is expected to read from the value in this pointer before yielding. -func (ch *channel) resumeTX() unsafe.Pointer { - // pop a blocked goroutine off the stack - var b *channelBlockedList - b, ch.blocked = ch.blocked, ch.blocked.next - - // get source pointer - src := b.t.Ptr - - if b.s != nil { - // use state's source pointer - src = b.s.value - - // tell the select op which case resumed - b.t.Ptr = unsafe.Pointer(b.s) - - // detach associated operations - b.detach() - } - - // push task onto runqueue - runqueue.Push(b.t) - - return src -} - -// push value to end of channel if space is available -// returns whether there was space for the value in the buffer -func (ch *channel) push(value unsafe.Pointer) bool { - // immediately return false if the channel is not buffered - if ch.bufSize == 0 { - return false - } - - // ensure space is available - if ch.bufUsed == ch.bufSize { - return false - } - - // copy value to buffer - memcpy( - unsafe.Add(ch.buf, // pointer to the base of the buffer + offset = pointer to destination element - ch.elementSize*ch.bufHead), // element size * equivalent slice index = offset - value, - ch.elementSize, - ) - - // update buffer state - ch.bufUsed++ +// Push the value to the channel buffer array, for a send operation. +// This function may only be called when interrupts are disabled, the channel is +// locked and it is known there is space available in the buffer. +func (ch *channel) bufferPush(value unsafe.Pointer) { + elemAddr := unsafe.Add(ch.buf, ch.bufHead*ch.elementSize) + ch.bufLen++ ch.bufHead++ - if ch.bufHead == ch.bufSize { + if ch.bufHead == ch.bufCap { ch.bufHead = 0 } - return true + memcpy(elemAddr, value, ch.elementSize) } -// pop value from channel buffer if one is available -// returns whether a value was popped or not -// result is stored into value pointer -func (ch *channel) pop(value unsafe.Pointer) bool { - // channel is empty - if ch.bufUsed == 0 { - return false - } - - // compute address of source - addr := unsafe.Add(ch.buf, (ch.elementSize * ch.bufTail)) - - // copy value from buffer - memcpy( - value, - addr, - ch.elementSize, - ) - - // zero buffer element to allow garbage collection of value - memzero( - addr, - ch.elementSize, - ) - - // update buffer state - ch.bufUsed-- - - // move tail up +// Pop a value from the channel buffer and store it in the 'value' pointer, for +// a receive operation. +// This function may only be called when interrupts are disabled, the channel is +// locked and it is known there is at least one value available in the buffer. +func (ch *channel) bufferPop(value unsafe.Pointer) { + elemAddr := unsafe.Add(ch.buf, ch.bufTail*ch.elementSize) + ch.bufLen-- ch.bufTail++ - if ch.bufTail == ch.bufSize { + if ch.bufTail == ch.bufCap { ch.bufTail = 0 } - return true + memcpy(value, elemAddr, ch.elementSize) + + // Zero the value to allow the GC to collect it. + memzero(elemAddr, ch.elementSize) } -// try to send a value to a channel, without actually blocking -// returns whether the value was sent -// will panic if channel is closed +// Try to proceed with this send operation without blocking, and return whether +// the send succeeded. Interrupts must be disabled and the lock must be held +// when calling this function. func (ch *channel) trySend(value unsafe.Pointer) bool { - if ch == nil { - // send to nil channel blocks forever - // this is non-blocking, so just say no - return false + // To make sure we send values in the correct order, we can only send + // directly to a receiver when there are no values in the buffer. + + // Do not allow sending on a closed channel. + if ch.closed { + // Note: we cannot currently recover from this panic. + // There's some state in the select statement especially that would be + // corrupted if we allowed recovering from this panic. + runtimePanic("send on closed channel") } - i := interrupt.Disable() - - switch ch.state { - case chanStateEmpty, chanStateBuf: - // try to dump the value directly into the buffer - if ch.push(value) { - ch.state = chanStateBuf - interrupt.Restore(i) + // There is no value in the buffer and we have a receiver available. Copy + // the value directly into the receiver. + if ch.bufLen == 0 { + if receiver := ch.receivers.pop(chanOperationOk); receiver != nil { + memcpy(receiver.task.Ptr, value, ch.elementSize) + scheduleTask(receiver.task) return true } - interrupt.Restore(i) - return false - case chanStateRecv: - // unblock receiver - dst := ch.resumeRX(true) - - // copy value to receiver - memcpy(dst, value, ch.elementSize) - - // change state to empty if there are no more receivers - if ch.blocked == nil { - ch.state = chanStateEmpty - } + } - interrupt.Restore(i) + // If there is space in the buffer (if this is a buffered channel), we can + // store the value in the buffer and continue. + if ch.bufLen < ch.bufCap { + ch.bufferPush(value) return true - case chanStateSend: - // something else is already waiting to send - interrupt.Restore(i) - return false - case chanStateClosed: - interrupt.Restore(i) - runtimePanic("send on closed channel") - default: - interrupt.Restore(i) - runtimePanic("invalid channel state") } - - interrupt.Restore(i) return false } -// try to receive a value from a channel, without really blocking -// returns whether a value was received -// second return is the comma-ok value -func (ch *channel) tryRecv(value unsafe.Pointer) (bool, bool) { +func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) { if ch == nil { - // receive from nil channel blocks forever - // this is non-blocking, so just say no - return false, false + // A nil channel blocks forever. Do not schedule this goroutine again. + deadlock() } - i := interrupt.Disable() - - switch ch.state { - case chanStateBuf, chanStateSend: - // try to pop the value directly from the buffer - if ch.pop(value) { - // unblock next sender if applicable - if ch.blocked != nil { - src := ch.resumeTX() - - // push sender's value into buffer - ch.push(src) - - if ch.blocked == nil { - // last sender unblocked - update state - ch.state = chanStateBuf - } - } - - if ch.bufUsed == 0 { - // channel empty - update state - ch.state = chanStateEmpty - } + mask := interrupt.Disable() + ch.lock.Lock() - interrupt.Restore(i) - return true, true - } else if ch.blocked != nil { - // unblock next sender if applicable - src := ch.resumeTX() + // See whether we can proceed immediately, and if so, return early. + if ch.trySend(value) { + ch.lock.Unlock() + interrupt.Restore(mask) + return + } - // copy sender's value - memcpy(value, src, ch.elementSize) + // Can't proceed. Add us to the list of senders and wait until we're awoken. + t := task.Current() + t.SetDataUint32(chanOperationWaiting) + op.task = t + op.index = 0 + op.value = value + ch.senders.push(op) + ch.lock.Unlock() + interrupt.Restore(mask) + + // Wait until this goroutine is resumed. + // It might be resumed after Unlock() and before Pause(). In that case, + // because we use semaphores, the Pause() will continue immediately. + task.Pause() - if ch.blocked == nil { - // last sender unblocked - update state - ch.state = chanStateEmpty - } + // Check whether the sent happened normally (not because the channel was + // closed while sending). + if t.DataUint32() == chanOperationClosed { + // Oops, this channel was closed while sending! + runtimePanic("send on closed channel") + } +} - interrupt.Restore(i) - return true, true - } - interrupt.Restore(i) - return false, false - case chanStateRecv, chanStateEmpty: - // something else is already waiting to receive - interrupt.Restore(i) - return false, false - case chanStateClosed: - if ch.pop(value) { - interrupt.Restore(i) - return true, true +// Try to proceed with this receive operation without blocking, and return +// whether the receive operation succeeded. Interrupts must be disabled and the +// lock must be held when calling this function. +func (ch *channel) tryRecv(value unsafe.Pointer) (received, ok bool) { + // To make sure we keep the values in the channel in the correct order, we + // first have to read values from the buffer before we can look at the + // senders. + + // If there is a value available in the buffer, we can pull it out and + // proceed immediately. + if ch.bufLen > 0 { + ch.bufferPop(value) + + // Check for the next sender available and push it to the buffer. + if sender := ch.senders.pop(chanOperationOk); sender != nil { + ch.bufferPush(sender.value) + scheduleTask(sender.task) } - // channel closed - nothing to receive + return true, true + } + + if ch.closed { + // Channel is closed, so proceed immediately. memzero(value, ch.elementSize) - interrupt.Restore(i) return true, false - default: - runtimePanic("invalid channel state") } - runtimePanic("unreachable") - return false, false -} - -type chanState uint8 - -const ( - chanStateEmpty chanState = iota // nothing in channel, no senders/receivers - chanStateRecv // nothing in channel, receivers waiting - chanStateSend // senders waiting, buffer full if present - chanStateBuf // buffer not empty, no senders waiting - chanStateClosed // channel closed -) - -func (s chanState) String() string { - switch s { - case chanStateEmpty: - return "empty" - case chanStateRecv: - return "recv" - case chanStateSend: - return "send" - case chanStateBuf: - return "buffered" - case chanStateClosed: - return "closed" - default: - return "invalid" + // If there is a sender, we can proceed with the channel operation + // immediately. + if sender := ch.senders.pop(chanOperationOk); sender != nil { + memcpy(value, sender.value, ch.elementSize) + scheduleTask(sender.task) + return true, true } -} -// chanSelectState is a single channel operation (send/recv) in a select -// statement. The value pointer is either nil (for receives) or points to the -// value to send (for sends). -type chanSelectState struct { - ch *channel - value unsafe.Pointer + return false, false } -// chanSend sends a single value over the channel. -// This operation will block unless a value is immediately available. -// May panic if the channel is closed. -func chanSend(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) { - i := interrupt.Disable() - - if ch.trySend(value) { - // value immediately sent - chanDebug(ch) - interrupt.Restore(i) - return - } - +func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool { if ch == nil { // A nil channel blocks forever. Do not schedule this goroutine again. - interrupt.Restore(i) deadlock() } - // wait for receiver - sender := task.Current() - ch.state = chanStateSend - sender.Ptr = value - *blockedlist = channelBlockedList{ - next: ch.blocked, - t: sender, - } - ch.blocked = blockedlist - chanDebug(ch) - interrupt.Restore(i) - task.Pause() - sender.Ptr = nil -} + mask := interrupt.Disable() + ch.lock.Lock() -// chanRecv receives a single value over a channel. -// It blocks if there is no available value to receive. -// The received value is copied into the value pointer. -// Returns the comma-ok value. -func chanRecv(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) bool { - i := interrupt.Disable() - - if rx, ok := ch.tryRecv(value); rx { - // value immediately available - chanDebug(ch) - interrupt.Restore(i) + if received, ok := ch.tryRecv(value); received { + ch.lock.Unlock() + interrupt.Restore(mask) return ok } - if ch == nil { - // A nil channel blocks forever. Do not schedule this goroutine again. - interrupt.Restore(i) - deadlock() - } - - // wait for a value - receiver := task.Current() - ch.state = chanStateRecv - receiver.Ptr, receiver.Data = value, 1 - *blockedlist = channelBlockedList{ - next: ch.blocked, - t: receiver, - } - ch.blocked = blockedlist - chanDebug(ch) - interrupt.Restore(i) + // We can't proceed, so we add ourselves to the list of receivers and wait + // until we're awoken. + t := task.Current() + t.Ptr = value + t.SetDataUint32(chanOperationWaiting) + op.task = t + op.index = 0 + ch.receivers.push(op) + ch.lock.Unlock() + interrupt.Restore(mask) + + // Wait until the goroutine is resumed. task.Pause() - ok := receiver.Data == 1 - receiver.Ptr, receiver.Data = nil, 0 - return ok + + // Return whether the receive happened from a closed channel. + return t.DataUint32() != chanOperationClosed } // chanClose closes the given channel. If this channel has a receiver or is @@ -536,128 +347,187 @@ func chanClose(ch *channel) { // Not allowed by the language spec. runtimePanic("close of nil channel") } - i := interrupt.Disable() - switch ch.state { - case chanStateClosed: + + mask := interrupt.Disable() + ch.lock.Lock() + + if ch.closed { // Not allowed by the language spec. - interrupt.Restore(i) + ch.lock.Unlock() + interrupt.Restore(mask) runtimePanic("close of closed channel") - case chanStateSend: - // This panic should ideally on the sending side, not in this goroutine. - // But when a goroutine tries to send while the channel is being closed, - // that is clearly invalid: the send should have been completed already - // before the close. - interrupt.Restore(i) - runtimePanic("close channel during send") - case chanStateRecv: - // unblock all receivers with the zero value - ch.state = chanStateClosed - for ch.blocked != nil { - ch.resumeRX(false) + } + + // Proceed all receiving operations that are blocked. + for { + receiver := ch.receivers.pop(chanOperationClosed) + if receiver == nil { + // Processed all receivers. + break } - case chanStateEmpty, chanStateBuf: - // Easy case. No available sender or receiver. + + // Zero the value that the receiver is getting. + memzero(receiver.task.Ptr, ch.elementSize) + + // Wake up the receiving goroutine. + scheduleTask(receiver.task) } - ch.state = chanStateClosed - interrupt.Restore(i) - chanDebug(ch) + + // Let all senders panic. + for { + sender := ch.senders.pop(chanOperationClosed) + if sender == nil { + break // processed all senders + } + + // Wake up the sender. + scheduleTask(sender.task) + } + + ch.closed = true + + ch.lock.Unlock() + interrupt.Restore(mask) } -// chanSelect is the runtime implementation of the select statement. This is -// perhaps the most complicated statement in the Go spec. It returns the -// selected index and the 'comma-ok' value. +// We currently use a global select lock to avoid deadlocks while locking each +// individual channel in the select. Without this global lock, two select +// operations that have a different order of the same channels could end up in a +// deadlock. This global lock is inefficient if there are many select operations +// happening in parallel, but gets the job done. // -// TODO: do this in a round-robin fashion (as specified in the Go spec) instead -// of picking the first one that can proceed. -func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelBlockedList) (uintptr, bool) { - istate := interrupt.Disable() - - if selected, ok := tryChanSelect(recvbuf, states); selected != ^uintptr(0) { - // one channel was immediately ready - interrupt.Restore(istate) - return selected, ok +// If this becomes a performance issue, we can see how the Go runtime does this. +// I think it does this by sorting all states by channel address and then +// locking them in that order to avoid this deadlock. +var chanSelectLock task.PMutex + +// Lock all channels (taking care to skip duplicate channels). +func lockAllStates(states []chanSelectState) { + if !hasParallelism { + return } + for _, state := range states { + if state.ch != nil && !state.ch.selectLocked { + state.ch.lock.Lock() + state.ch.selectLocked = true + } + } +} - // construct blocked operations - for i, v := range states { - if v.ch == nil { - // A nil channel receive will never complete. - // A nil channel send would have panicked during tryChanSelect. - ops[i] = channelBlockedList{} - continue +// Unlock all channels (taking care to skip duplicate channels). +func unlockAllStates(states []chanSelectState) { + if !hasParallelism { + return + } + for _, state := range states { + if state.ch != nil && state.ch.selectLocked { + state.ch.lock.Unlock() + state.ch.selectLocked = false } + } +} + +// chanSelect implements blocking or non-blocking select operations. +// The 'ops' slice must be set if (and only if) this is a blocking select. +func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uint32, bool) { + mask := interrupt.Disable() + + // Lock everything. + chanSelectLock.Lock() + lockAllStates(states) - ops[i] = channelBlockedList{ - next: v.ch.blocked, - t: task.Current(), - s: &states[i], - allSelectOps: ops, + const selectNoIndex = ^uint32(0) + selectIndex := selectNoIndex + selectOk := true + + // Iterate over each state, and see if it can proceed. + // TODO: start from a random index. + for i, state := range states { + if state.ch == nil { + // A nil channel blocks forever, so it won't take part of the select + // operation. + continue } - v.ch.blocked = &ops[i] - if v.value == nil { - // recv - switch v.ch.state { - case chanStateEmpty: - v.ch.state = chanStateRecv - case chanStateRecv: - // already in correct state - default: - interrupt.Restore(istate) - runtimePanic("invalid channel state") + + if state.value == nil { // chan receive + if received, ok := state.ch.tryRecv(recvbuf); received { + selectIndex = uint32(i) + selectOk = ok + break } - } else { - // send - switch v.ch.state { - case chanStateEmpty: - v.ch.state = chanStateSend - case chanStateSend: - // already in correct state - case chanStateBuf: - // already in correct state - default: - interrupt.Restore(istate) - runtimePanic("invalid channel state") + } else { // chan send + if state.ch.trySend(state.value) { + selectIndex = uint32(i) + break } } - chanDebug(v.ch) } - // expose rx buffer + // If this select can immediately proceed, or is a non-blocking select, + // return early. + blocking := len(ops) != 0 + if selectIndex != selectNoIndex || !blocking { + unlockAllStates(states) + chanSelectLock.Unlock() + interrupt.Restore(mask) + return selectIndex, selectOk + } + + // The select is blocking and no channel operation can proceed, so things + // become more complicated. + // We add ourselves as a sender/receiver to every channel, and wait for the + // first one to complete. Only one will successfully complete, because + // senders and receivers use a compare-and-exchange atomic operation on + // t.Data so that only one will be able to "take" this select operation. t := task.Current() t.Ptr = recvbuf - t.Data = 1 + t.SetDataUint32(chanOperationWaiting) + for i, state := range states { + if state.ch == nil { + continue + } + op := &ops[i] + op.task = t + op.index = uint32(i) + if state.value == nil { // chan receive + state.ch.receivers.push(op) + } else { // chan send + op.value = state.value + state.ch.senders.push(op) + } + } - // wait for one case to fire - interrupt.Restore(istate) + // Now we wait until one of the send/receive operations can proceed. + unlockAllStates(states) + chanSelectLock.Unlock() + interrupt.Restore(mask) task.Pause() - // figure out which one fired and return the ok value - return (uintptr(t.Ptr) - uintptr(unsafe.Pointer(&states[0]))) / unsafe.Sizeof(chanSelectState{}), t.Data != 0 -} + // Resumed, so one channel operation must have progressed. -// tryChanSelect is like chanSelect, but it does a non-blocking select operation. -func tryChanSelect(recvbuf unsafe.Pointer, states []chanSelectState) (uintptr, bool) { - istate := interrupt.Disable() - - // See whether we can receive from one of the channels. + // Make sure all channel ops are removed from the senders/receivers + // queue before we return and the memory of them becomes invalid. + chanSelectLock.Lock() + lockAllStates(states) for i, state := range states { + if state.ch == nil { + continue + } + op := &ops[i] + mask := interrupt.Disable() if state.value == nil { - // A receive operation. - if rx, ok := state.ch.tryRecv(recvbuf); rx { - chanDebug(state.ch) - interrupt.Restore(istate) - return uintptr(i), ok - } + state.ch.receivers.remove(op) } else { - // A send operation: state.value is not nil. - if state.ch.trySend(state.value) { - chanDebug(state.ch) - interrupt.Restore(istate) - return uintptr(i), true - } + state.ch.senders.remove(op) } + interrupt.Restore(mask) } + unlockAllStates(states) + chanSelectLock.Unlock() + + // Pull the return values out of t.Data (which contains two bitfields). + selectIndex = t.DataUint32() >> 2 + selectOk = t.DataUint32()&chanOperationMask != chanOperationClosed - interrupt.Restore(istate) - return ^uintptr(0), false + return selectIndex, selectOk } diff --git a/src/runtime/cond.go b/src/runtime/cond.go deleted file mode 100644 index 00e89932a5..0000000000 --- a/src/runtime/cond.go +++ /dev/null @@ -1,90 +0,0 @@ -//go:build !scheduler.none - -package runtime - -import ( - "internal/task" - "sync/atomic" - "unsafe" -) - -// notifiedPlaceholder is a placeholder task which is used to indicate that the condition variable has been notified. -var notifiedPlaceholder task.Task - -// Cond is a simplified condition variable, useful for notifying goroutines of interrupts. -type Cond struct { - t *task.Task -} - -// Notify sends a notification. -// If the condition variable already has a pending notification, this returns false. -func (c *Cond) Notify() bool { - for { - t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)))) - switch t { - case nil: - // Nothing is waiting yet. - // Apply the notification placeholder. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), unsafe.Pointer(¬ifiedPlaceholder)) { - return true - } - case ¬ifiedPlaceholder: - // The condition variable has already been notified. - return false - default: - // Unblock the waiting task. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) { - runqueuePushBack(t) - return true - } - } - } -} - -// Poll checks for a notification. -// If a notification is found, it is cleared and this returns true. -func (c *Cond) Poll() bool { - for { - t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)))) - switch t { - case nil: - // No notifications are present. - return false - case ¬ifiedPlaceholder: - // A notification arrived and there is no waiting goroutine. - // Clear the notification and return. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) { - return true - } - default: - // A task is blocked on the condition variable, which means it has not been notified. - return false - } - } -} - -// Wait for a notification. -// If the condition variable was previously notified, this returns immediately. -func (c *Cond) Wait() { - cur := task.Current() - for { - t := (*task.Task)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)))) - switch t { - case nil: - // Condition variable has not been notified. - // Block the current task on the condition variable. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), nil, unsafe.Pointer(cur)) { - task.Pause() - return - } - case ¬ifiedPlaceholder: - // A notification arrived and there is no waiting goroutine. - // Clear the notification and return. - if atomic.CompareAndSwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.t)), unsafe.Pointer(t), nil) { - return - } - default: - panic("interrupt.Cond: condition variable in use by another goroutine") - } - } -} diff --git a/src/runtime/cond_nosched.go b/src/runtime/cond_nosched.go deleted file mode 100644 index ff57f41468..0000000000 --- a/src/runtime/cond_nosched.go +++ /dev/null @@ -1,38 +0,0 @@ -//go:build scheduler.none - -package runtime - -import "runtime/interrupt" - -// Cond is a simplified condition variable, useful for notifying goroutines of interrupts. -type Cond struct { - notified bool -} - -// Notify sends a notification. -// If the condition variable already has a pending notification, this returns false. -func (c *Cond) Notify() bool { - i := interrupt.Disable() - prev := c.notified - c.notified = true - interrupt.Restore(i) - return !prev -} - -// Poll checks for a notification. -// If a notification is found, it is cleared and this returns true. -func (c *Cond) Poll() bool { - i := interrupt.Disable() - notified := c.notified - c.notified = false - interrupt.Restore(i) - return notified -} - -// Wait for a notification. -// If the condition variable was previously notified, this returns immediately. -func (c *Cond) Wait() { - for !c.Poll() { - waitForEvents() - } -} diff --git a/src/runtime/coro.go b/src/runtime/coro.go new file mode 100644 index 0000000000..204a5c2bed --- /dev/null +++ b/src/runtime/coro.go @@ -0,0 +1,31 @@ +package runtime + +// A naive implementation of coroutines that supports +// package iter. + +type coro struct { + f func(*coro) + ch chan struct{} +} + +//go:linkname newcoro + +func newcoro(f func(*coro)) *coro { + c := &coro{ + ch: make(chan struct{}), + f: f, + } + go func() { + defer close(c.ch) + <-c.ch + f(c) + }() + return c +} + +//go:linkname coroswitch + +func coroswitch(c *coro) { + c.ch <- struct{}{} + <-c.ch +} diff --git a/src/runtime/debug/debug.go b/src/runtime/debug/debug.go index 7631173133..38e6ab763b 100644 --- a/src/runtime/debug/debug.go +++ b/src/runtime/debug/debug.go @@ -1,6 +1,17 @@ -// Package debug is a dummy package that is not yet implemented. +// Portions 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. + +// Package debug is a very partially implemented package to allow compilation. package debug +import ( + "fmt" + "runtime" + "strconv" + "strings" +) + // SetMaxStack sets the maximum amount of memory that can be used by a single // goroutine stack. // @@ -27,16 +38,17 @@ func Stack() []byte { // // Not implemented. func ReadBuildInfo() (info *BuildInfo, ok bool) { - return nil, false + return &BuildInfo{GoVersion: runtime.Compiler + runtime.Version()}, true } // BuildInfo represents the build information read from // the running binary. type BuildInfo struct { - Path string // The main package path - Main Module // The module containing the main package - Deps []*Module // Module dependencies - Settings []BuildSetting + GoVersion string // version of the Go toolchain that built the binary, e.g. "go1.19.2" + Path string // The main package path + Main Module // The module containing the main package + Deps []*Module // Module dependencies + Settings []BuildSetting } type BuildSetting struct { @@ -58,3 +70,60 @@ type Module struct { func SetGCPercent(n int) int { return n } + +// Start of stolen from big go. TODO: import/reuse without copy pasta. + +// quoteKey reports whether key is required to be quoted. +func quoteKey(key string) bool { + return len(key) == 0 || strings.ContainsAny(key, "= \t\r\n\"`") +} + +// quoteValue reports whether value is required to be quoted. +func quoteValue(value string) bool { + return strings.ContainsAny(value, " \t\r\n\"`") +} + +func (bi *BuildInfo) String() string { + buf := new(strings.Builder) + if bi.GoVersion != "" { + fmt.Fprintf(buf, "go\t%s\n", bi.GoVersion) + } + if bi.Path != "" { + fmt.Fprintf(buf, "path\t%s\n", bi.Path) + } + var formatMod func(string, Module) + formatMod = func(word string, m Module) { + buf.WriteString(word) + buf.WriteByte('\t') + buf.WriteString(m.Path) + buf.WriteByte('\t') + buf.WriteString(m.Version) + if m.Replace == nil { + buf.WriteByte('\t') + buf.WriteString(m.Sum) + } else { + buf.WriteByte('\n') + formatMod("=>", *m.Replace) + } + buf.WriteByte('\n') + } + if bi.Main != (Module{}) { + formatMod("mod", bi.Main) + } + for _, dep := range bi.Deps { + formatMod("dep", *dep) + } + for _, s := range bi.Settings { + key := s.Key + if quoteKey(key) { + key = strconv.Quote(key) + } + value := s.Value + if quoteValue(value) { + value = strconv.Quote(value) + } + fmt.Fprintf(buf, "build\t%s=%s\n", key, value) + } + + return buf.String() +} diff --git a/src/runtime/func.go b/src/runtime/func.go deleted file mode 100644 index 879424b5d7..0000000000 --- a/src/runtime/func.go +++ /dev/null @@ -1,28 +0,0 @@ -package runtime - -// This file implements some data types that may be useful for some -// implementations of func values. - -import ( - "unsafe" -) - -// funcValue is the underlying type of func values, depending on which func -// value representation was used. -type funcValue struct { - context unsafe.Pointer // function context, for closures and bound methods - id uintptr // ptrtoint of *funcValueWithSignature before lowering, opaque index (non-0) after lowering -} - -// funcValueWithSignature is used before the func lowering pass. -type funcValueWithSignature struct { - funcPtr uintptr // ptrtoint of the actual function pointer - signature *uint8 // external *i8 with a name identifying the function signature -} - -// getFuncPtr is a dummy function that may be used if the func lowering pass is -// not used. It is generally too slow but may be a useful fallback to debug the -// func lowering pass. -func getFuncPtr(val funcValue, signature *uint8) uintptr { - return (*funcValueWithSignature)(unsafe.Pointer(val.id)).funcPtr -} diff --git a/src/runtime/gc_blocks.go b/src/runtime/gc_blocks.go index 02a8277300..62e38dcd7a 100644 --- a/src/runtime/gc_blocks.go +++ b/src/runtime/gc_blocks.go @@ -37,6 +37,7 @@ import ( ) const gcDebug = false +const needsStaticHeap = true // Some globals + constants for the entire GC. @@ -45,7 +46,7 @@ const ( bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart) stateBits = 2 // how many bits a block state takes (see blockState type) blocksPerStateByte = 8 / stateBits - markStackSize = 4 * unsafe.Sizeof((*int)(nil)) // number of to-be-marked blocks to queue before forcing a rescan + markStackSize = 8 * unsafe.Sizeof((*int)(nil)) // number of to-be-marked blocks to queue before forcing a rescan ) var ( @@ -53,8 +54,10 @@ var ( nextAlloc gcBlock // the next block that should be tried by the allocator endBlock gcBlock // the block just past the end of the available space gcTotalAlloc uint64 // total number of bytes allocated + gcTotalBlocks uint64 // total number of allocated blocks gcMallocs uint64 // total number of allocations gcFrees uint64 // total number of objects freed + gcFreedBlocks uint64 // total number of freed blocks ) // zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. @@ -74,6 +77,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 { @@ -121,7 +131,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 { @@ -144,10 +172,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 @@ -285,6 +322,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { gcMallocs++ neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock + gcTotalBlocks += uint64(neededBlocks) // Continue looping until a run of free blocks has been found that fits the // requested size. @@ -410,7 +448,7 @@ func GC() { runGC() } -// runGC performs a garbage colleciton cycle. It is the internal implementation +// runGC performs a garbage collection cycle. It is the internal implementation // of the runtime.GC() function. The difference is that it returns the number of // free bytes in the heap after the GC is finished. func runGC() (freeBytes uintptr) { @@ -424,15 +462,16 @@ func runGC() (freeBytes uintptr) { if baremetal && hasScheduler { // Channel operations in interrupts may move task pointers around while we are marking. - // Therefore we need to scan the runqueue seperately. + // Therefore we need to scan the runqueue separately. var markedTaskQueue task.Queue runqueueScan: + runqueue := schedulerRunQueue() for !runqueue.Empty() { // Pop the next task off of the runqueue. t := runqueue.Pop() // Mark the task if it has not already been marked. - markRoot(uintptr(unsafe.Pointer(&runqueue)), uintptr(unsafe.Pointer(t))) + markRoot(uintptr(unsafe.Pointer(runqueue)), uintptr(unsafe.Pointer(t))) // Push the task onto our temporary queue. markedTaskQueue.Push(t) @@ -447,7 +486,7 @@ func runGC() (freeBytes uintptr) { interrupt.Restore(i) goto runqueueScan } - runqueue = markedTaskQueue + *runqueue = markedTaskQueue interrupt.Restore(i) } else { finishMark() @@ -495,6 +534,12 @@ func markRoots(start, end uintptr) { } } +func markCurrentGoroutineStack(sp uintptr) { + // This could be optimized by only marking the stack area that's currently + // in use. + markRoot(0, sp) +} + // stackOverflow is a flag which is set when the GC scans too deep while marking. // After it is set, all marked allocations must be re-scanned. var stackOverflow bool @@ -619,6 +664,7 @@ func markRoot(addr, root uintptr) { // It returns how many bytes are free in the heap after the sweep. func sweep() (freeBytes uintptr) { freeCurrentObject := false + var freed uint64 for block := gcBlock(0); block < endBlock; block++ { switch block.state() { case blockStateHead: @@ -626,13 +672,13 @@ func sweep() (freeBytes uintptr) { block.markFree() freeCurrentObject = true gcFrees++ - freeBytes += bytesPerBlock + freed++ case blockStateTail: if freeCurrentObject { // This is a tail object following an unmarked head. // Free it now. block.markFree() - freeBytes += bytesPerBlock + freed++ } case blockStateMark: // This is a marked object. The next tail blocks must not be freed, @@ -644,6 +690,8 @@ func sweep() (freeBytes uintptr) { freeBytes += bytesPerBlock } } + gcFreedBlocks += freed + freeBytes += uintptr(freed) * bytesPerBlock return } @@ -690,6 +738,8 @@ func ReadMemStats(m *MemStats) { m.Mallocs = gcMallocs m.Frees = gcFrees m.Sys = uint64(heapEnd - heapStart) + m.HeapAlloc = (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock) + m.Alloc = m.HeapAlloc } func SetFinalizer(obj interface{}, finalizer interface{}) { diff --git a/src/runtime/gc_boehm.go b/src/runtime/gc_boehm.go new file mode 100644 index 0000000000..0955f3c224 --- /dev/null +++ b/src/runtime/gc_boehm.go @@ -0,0 +1,172 @@ +//go:build gc.boehm + +package runtime + +import ( + "internal/gclayout" + "internal/reflectlite" + "internal/task" + "unsafe" +) + +const needsStaticHeap = false + +// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. +var zeroSizedAlloc uint8 + +var gcLock task.PMutex + +func initHeap() { + libgc_init() + + libgc_set_push_other_roots(gcCallbackPtr) +} + +var gcCallbackPtr = reflectlite.ValueOf(gcCallback).UnsafePointer() + +func gcCallback() { + // Mark the system stack and (if we're on a goroutine stack) also the + // current goroutine stack. + markStack() + + findGlobals(func(start, end uintptr) { + libgc_push_all(start, end) + }) +} + +func markRoots(start, end uintptr) { + libgc_push_all(start, end) +} + +func markCurrentGoroutineStack(sp uintptr) { + // Only mark the area of the stack that is currently in use. + // (This doesn't work for other goroutines, but at least it doesn't keep + // more pointers alive than needed on the current stack). + base := libgc_base(sp) + if base == 0 { // && asserts + runtimePanic("goroutine stack not in a heap allocation?") + } + stackBottom := base + libgc_size(base) + libgc_push_all_stack(sp, stackBottom) +} + +//go:noinline +func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { + if size == 0 { + return unsafe.Pointer(&zeroSizedAlloc) + } + + gcLock.Lock() + var ptr unsafe.Pointer + if layout == gclayout.NoPtrs { + // This object is entirely pointer free, for example make([]int, ...). + // Make sure the GC knows this so it doesn't scan the object + // unnecessarily to improve performance. + ptr = libgc_malloc_atomic(size) + // Memory returned from libgc_malloc_atomic has not been zeroed so we + // have to do that manually. + memzero(ptr, size) + } else { + // TODO: bdwgc supports typed allocations, which could be useful to + // implement a mostly-precise GC. + ptr = libgc_malloc(size) + // Memory returned from libgc_malloc has already been zeroed, so nothing + // to do here. + } + gcLock.Unlock() + if ptr == nil { + runtimePanic("gc: out of memory") + } + + return ptr +} + +func free(ptr unsafe.Pointer) { + libgc_free(ptr) +} + +func GC() { + libgc_gcollect() +} + +// This should be stack-allocated, but we don't currently have a good way of +// ensuring that happens. +var gcMemStats libgc_prof_stats + +func ReadMemStats(m *MemStats) { + gcLock.Lock() + + libgc_get_prof_stats(&gcMemStats, unsafe.Sizeof(gcMemStats)) + + // Fill in MemStats as well as we can, given the information that bdwgc + // provides to us. + m.HeapIdle = uint64(gcMemStats.free_bytes_full - gcMemStats.unmapped_bytes) + m.HeapInuse = uint64(gcMemStats.heapsize_full - gcMemStats.unmapped_bytes) + m.HeapReleased = uint64(gcMemStats.unmapped_bytes) + m.HeapSys = uint64(m.HeapInuse + m.HeapIdle) + m.GCSys = 0 // not provided by bdwgc + m.TotalAlloc = uint64(gcMemStats.allocd_bytes_before_gc + gcMemStats.bytes_allocd_since_gc) + m.Mallocs = 0 // not provided by bdwgc + m.Frees = 0 // not provided by bdwgc + m.Sys = uint64(gcMemStats.obtained_from_os_bytes) + + gcLock.Unlock() +} + +func setHeapEnd(newHeapEnd uintptr) { + runtimePanic("gc: did not expect setHeapEnd call") +} + +func SetFinalizer(obj interface{}, finalizer interface{}) { + // Unimplemented. + // The GC *does* support finalization, so this could be added relatively + // easily I think. +} + +//export GC_init +func libgc_init() + +//export GC_malloc +func libgc_malloc(uintptr) unsafe.Pointer + +//export GC_malloc_atomic +func libgc_malloc_atomic(uintptr) unsafe.Pointer + +//export GC_free +func libgc_free(unsafe.Pointer) + +//export GC_base +func libgc_base(ptr uintptr) uintptr + +//export GC_size +func libgc_size(ptr uintptr) uintptr + +//export GC_push_all +func libgc_push_all(bottom, top uintptr) + +//export GC_push_all_stack +func libgc_push_all_stack(bottom, top uintptr) + +//export GC_gcollect +func libgc_gcollect() + +//export GC_get_prof_stats +func libgc_get_prof_stats(*libgc_prof_stats, uintptr) uintptr + +//export GC_set_push_other_roots +func libgc_set_push_other_roots(unsafe.Pointer) + +type libgc_prof_stats struct { + heapsize_full uintptr + free_bytes_full uintptr + unmapped_bytes uintptr + bytes_allocd_since_gc uintptr + allocd_bytes_before_gc uintptr + non_gc_bytes uintptr + gc_no uintptr + markers_m1 uintptr + bytes_reclaimed_since_gc uintptr + reclaimed_bytes_before_gc uintptr + expl_freed_bytes_since_gc uintptr + obtained_from_os_bytes uintptr +} diff --git a/src/runtime/gc_custom.go b/src/runtime/gc_custom.go index a34b7dce69..0125f1688b 100644 --- a/src/runtime/gc_custom.go +++ b/src/runtime/gc_custom.go @@ -36,6 +36,8 @@ import ( "unsafe" ) +const needsStaticHeap = false + // initHeap is called when the heap is first initialized at program start. func initHeap() diff --git a/src/runtime/gc_globals.go b/src/runtime/gc_globals.go index f27911ec51..3e8f857618 100644 --- a/src/runtime/gc_globals.go +++ b/src/runtime/gc_globals.go @@ -1,4 +1,4 @@ -//go:build (gc.conservative || gc.precise) && (baremetal || tinygo.wasm) +//go:build baremetal || tinygo.wasm package runtime diff --git a/src/runtime/gc_leaking.go b/src/runtime/gc_leaking.go index 26b012bdbb..bdc7efe810 100644 --- a/src/runtime/gc_leaking.go +++ b/src/runtime/gc_leaking.go @@ -7,11 +7,14 @@ package runtime // may be the only memory allocator possible. import ( + "internal/task" "unsafe" ) +const needsStaticHeap = true + // Ever-incrementing pointer: no memory is freed. -var heapptr = heapStart +var heapptr uintptr // Total amount allocated for runtime.MemStats var gcTotalAlloc uint64 @@ -19,6 +22,9 @@ var gcTotalAlloc uint64 // Total number of calls to alloc() var gcMallocs uint64 +// Heap lock for parallel goroutines. No-op when single threaded. +var gcLock task.PMutex + // Total number of objected freed; for leaking collector this stays 0 const gcFrees = 0 @@ -30,6 +36,7 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { // TODO: this can be optimized by not casting between pointers and ints so // much. And by using platform-native data types (e.g. *uint8 for 8-bit // systems). + gcLock.Lock() size = align(size) addr := heapptr gcTotalAlloc += uint64(size) @@ -43,8 +50,10 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer { // Failed to make the heap bigger, so we must really be out of memory. runtimePanic("out of memory") } + gcLock.Unlock() + pointer := unsafe.Pointer(addr) - memzero(pointer, size) + zero_new_alloc(pointer, size) return pointer } @@ -69,6 +78,8 @@ func free(ptr unsafe.Pointer) { // The returned memory statistics are up to date as of the // call to ReadMemStats. This would not do GC implicitly for you. func ReadMemStats(m *MemStats) { + gcLock.Lock() + m.HeapIdle = 0 m.HeapInuse = gcTotalAlloc m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS. @@ -79,6 +90,11 @@ func ReadMemStats(m *MemStats) { m.Mallocs = gcMallocs m.Frees = gcFrees m.Sys = uint64(heapEnd - heapStart) + // no free -- current in use heap is the total allocated + m.HeapAlloc = gcTotalAlloc + m.Alloc = m.HeapAlloc + + gcLock.Unlock() } func GC() { @@ -90,7 +106,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 } diff --git a/src/runtime/gc_none.go b/src/runtime/gc_none.go index 43d3c4904b..173c1f6439 100644 --- a/src/runtime/gc_none.go +++ b/src/runtime/gc_none.go @@ -10,6 +10,8 @@ import ( "unsafe" ) +const needsStaticHeap = false + var gcTotalAlloc uint64 // for runtime.MemStats var gcMallocs uint64 var gcFrees uint64 diff --git a/src/runtime/gc_precise.go b/src/runtime/gc_precise.go index 4c7463b8ec..aa716585c8 100644 --- a/src/runtime/gc_precise.go +++ b/src/runtime/gc_precise.go @@ -7,7 +7,7 @@ // however use a bit more RAM to store the layout of each object. // // The pointer/non-pointer information for objects is stored in the first word -// of the object. It is described below but in essense it contains a bitstring +// of the object. It is described below but in essence it contains a bitstring // of a particular size. This size does not indicate the size of the object: // instead the allocated object is a multiple of the bitstring size. This is so // that arrays and slices can store the size of the object efficiently. The diff --git a/src/runtime/gc_stack_portable.go b/src/runtime/gc_stack_portable.go index f822d8482e..d35e16e30c 100644 --- a/src/runtime/gc_stack_portable.go +++ b/src/runtime/gc_stack_portable.go @@ -26,7 +26,7 @@ type stackChainObject struct { // // Therefore, we only need to scan the system stack. // It is relatively easy to scan the system stack while we're on it: we can -// simply read __stack_pointer and __global_base and scan the area inbetween. +// simply read __stack_pointer and __global_base and scan the area in between. // Unfortunately, it's hard to get the system stack pointer while we're on a // goroutine stack. But when we're on a goroutine stack, the system stack is in // the scheduler which means there shouldn't be anything on the system stack diff --git a/src/runtime/gc_stack_raw.go b/src/runtime/gc_stack_raw.go index 5ee18622db..01ec0208c2 100644 --- a/src/runtime/gc_stack_raw.go +++ b/src/runtime/gc_stack_raw.go @@ -1,4 +1,4 @@ -//go:build (gc.conservative || gc.precise) && !tinygo.wasm +//go:build (gc.conservative || gc.precise || gc.boehm) && !tinygo.wasm package runtime @@ -33,7 +33,6 @@ func scanstack(sp uintptr) { markRoots(sp, stackTop) } else { // This is a goroutine stack. - // It is an allocation, so scan it as if it were a value in a global. - markRoot(0, sp) + markCurrentGoroutineStack(sp) } } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index dfbec300ed..894d92a1ba 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -6,7 +6,8 @@ package runtime // https://golang.org/src/runtime/map.go import ( - "reflect" + "internal/reflectlite" + "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 @@ -45,8 +38,11 @@ type hashmapIterator struct { buckets unsafe.Pointer // pointer to array of hashapBuckets numBuckets uintptr // length of buckets array bucketNumber uintptr // current index into buckets array + startBucket uintptr // starting location for iterator bucket *hashmapBucket // current bucket in chain bucketIndex uint8 // current index into bucket + startIndex uint8 // starting bucket index for iterator + wrapped bool // true if the iterator has wrapped } func hashmapNewIterator() unsafe.Pointer { @@ -73,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<= 8 { // end of bucket, move to the next in the chain it.bucketIndex = 0 it.bucket = it.bucket.next } + if it.bucket == nil { + it.bucketNumber++ // next bucket if it.bucketNumber >= it.numBuckets { - // went through all buckets - return false + // went through all buckets -- wrap around + it.bucketNumber = 0 + it.wrapped = true } it.bucket = hashmapBucketAddr(m, it.buckets, it.bucketNumber) - it.bucketNumber++ // next bucket + continue } + if it.bucket.tophash[it.bucketIndex] == 0 { // slot is empty - move on it.bucketIndex++ continue } + // Found a key. slotKey := hashmapSlotKey(m, it.bucket, it.bucketIndex) memcpy(key, slotKey, m.keySize) @@ -456,10 +465,6 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo } } -func hashmapNextUnsafePointer(m unsafe.Pointer, it unsafe.Pointer, key, value unsafe.Pointer) bool { - return hashmapNext((*hashmap)(m), (*hashmapIterator)(it), key, value) -} - // Hashmap with plain binary data keys (not containing strings etc.). func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) { if m == nil { @@ -469,10 +474,6 @@ func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) { hashmapSet(m, key, value, hash) } -func hashmapBinarySetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer) { - hashmapBinarySet((*hashmap)(m), key, value) -} - func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) @@ -482,10 +483,6 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) return hashmapGet(m, key, value, valueSize, hash) } -func hashmapBinaryGetUnsafePointer(m unsafe.Pointer, key, value unsafe.Pointer, valueSize uintptr) bool { - return hashmapBinaryGet((*hashmap)(m), key, value, valueSize) -} - func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) { if m == nil { return @@ -494,10 +491,6 @@ func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) { hashmapDelete(m, key, hash) } -func hashmapBinaryDeleteUnsafePointer(m unsafe.Pointer, key unsafe.Pointer) { - hashmapBinaryDelete((*hashmap)(m), key) -} - // Hashmap with string keys (a common case). func hashmapStringEqual(x, y unsafe.Pointer, n uintptr) bool { @@ -522,10 +515,6 @@ func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) { hashmapSet(m, unsafe.Pointer(&key), value, hash) } -func hashmapStringSetUnsafePointer(m unsafe.Pointer, key string, value unsafe.Pointer) { - hashmapStringSet((*hashmap)(m), key, value) -} - func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) @@ -535,10 +524,6 @@ func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize ui return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash) } -func hashmapStringGetUnsafePointer(m unsafe.Pointer, key string, value unsafe.Pointer, valueSize uintptr) bool { - return hashmapStringGet((*hashmap)(m), key, value, valueSize) -} - func hashmapStringDelete(m *hashmap, key string) { if m == nil { return @@ -547,10 +532,6 @@ func hashmapStringDelete(m *hashmap, key string) { hashmapDelete(m, unsafe.Pointer(&key), hash) } -func hashmapStringDeleteUnsafePointer(m unsafe.Pointer, key string) { - hashmapStringDelete((*hashmap)(m), key) -} - // Hashmap with interface keys (for everything else). // This is a method that is intentionally unexported in the reflect package. It @@ -558,8 +539,8 @@ func hashmapStringDeleteUnsafePointer(m unsafe.Pointer, key string) { // a field is exported and thus allows circumventing the type system. // The hash function needs it as it also needs to hash unexported struct fields. // -//go:linkname valueInterfaceUnsafe reflect.valueInterfaceUnsafe -func valueInterfaceUnsafe(v reflect.Value) interface{} +//go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe +func valueInterfaceUnsafe(v reflectlite.Value) interface{} func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 { f := *(*uint32)(ptr) @@ -580,7 +561,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 { } func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { - x := reflect.ValueOf(itf) + x := reflectlite.ValueOf(itf) if x.RawType() == nil { return 0 // nil interface } @@ -593,39 +574,39 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { } switch x.RawType().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Bool, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflectlite.Bool, reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Float32: + case reflectlite.Float32: // It should be possible to just has the contents. However, NaN != NaN // so if you're using lots of NaNs as map keys (you shouldn't) then hash // time may become exponential. To fix that, it would be better to // return a random number instead: // https://research.swtch.com/randhash return hashmapFloat32Hash(ptr, seed) - case reflect.Float64: + case reflectlite.Float64: return hashmapFloat64Hash(ptr, seed) - case reflect.Complex64: + case reflectlite.Complex64: rptr, iptr := ptr, unsafe.Add(ptr, 4) return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed) - case reflect.Complex128: + case reflectlite.Complex128: rptr, iptr := ptr, unsafe.Add(ptr, 8) return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed) - case reflect.String: + case reflectlite.String: return hashmapStringHash(x.String(), seed) - case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: + case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: // It might seem better to just return the pointer, but that won't // result in an evenly distributed hashmap. Instead, hash the pointer // like most other types. return hash32(ptr, x.RawType().Size(), seed) - case reflect.Array: + case reflectlite.Array: var hash uint32 for i := 0; i < x.Len(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed) } return hash - case reflect.Struct: + case reflectlite.Struct: var hash uint32 for i := 0; i < x.NumField(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed) @@ -654,10 +635,6 @@ func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) { hashmapSet(m, unsafe.Pointer(&key), value, hash) } -func hashmapInterfaceSetUnsafePointer(m unsafe.Pointer, key interface{}, value unsafe.Pointer) { - hashmapInterfaceSet((*hashmap)(m), key, value) -} - func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool { if m == nil { memzero(value, uintptr(valueSize)) @@ -667,10 +644,6 @@ func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valu return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash) } -func hashmapInterfaceGetUnsafePointer(m unsafe.Pointer, key interface{}, value unsafe.Pointer, valueSize uintptr) bool { - return hashmapInterfaceGet((*hashmap)(m), key, value, valueSize) -} - func hashmapInterfaceDelete(m *hashmap, key interface{}) { if m == nil { return @@ -678,7 +651,3 @@ func hashmapInterfaceDelete(m *hashmap, key interface{}) { hash := hashmapInterfaceHash(key, m.seed) hashmapDelete(m, unsafe.Pointer(&key), hash) } - -func hashmapInterfaceDeleteUnsafePointer(m unsafe.Pointer, key interface{}) { - hashmapInterfaceDelete((*hashmap)(m), key) -} diff --git a/src/runtime/interface.go b/src/runtime/interface.go index b9813225f2..e1d263a7e3 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -6,7 +6,7 @@ package runtime // anything (including non-pointers). import ( - "reflect" + "internal/reflectlite" "unsafe" ) @@ -27,12 +27,12 @@ func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) { // Return true iff both interfaces are equal. func interfaceEqual(x, y interface{}) bool { - return reflectValueEqual(reflect.ValueOf(x), reflect.ValueOf(y)) + return reflectValueEqual(reflectlite.ValueOf(x), reflectlite.ValueOf(y)) } -func reflectValueEqual(x, y reflect.Value) bool { +func reflectValueEqual(x, y reflectlite.Value) bool { // Note: doing a x.Type() == y.Type() comparison would not work here as that - // would introduce an infinite recursion: comparing two reflect.Type values + // would introduce an infinite recursion: comparing two reflectlite.Type values // is done with this reflectValueEqual runtime call. if x.RawType() == nil || y.RawType() == nil { // One of them is nil. @@ -46,35 +46,35 @@ func reflectValueEqual(x, y reflect.Value) bool { } switch x.RawType().Kind() { - case reflect.Bool: + case reflectlite.Bool: return x.Bool() == y.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return x.Int() == y.Int() - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return x.Uint() == y.Uint() - case reflect.Float32, reflect.Float64: + case reflectlite.Float32, reflectlite.Float64: return x.Float() == y.Float() - case reflect.Complex64, reflect.Complex128: + case reflectlite.Complex64, reflectlite.Complex128: return x.Complex() == y.Complex() - case reflect.String: + case reflectlite.String: return x.String() == y.String() - case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: + case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: return x.UnsafePointer() == y.UnsafePointer() - case reflect.Array: + case reflectlite.Array: for i := 0; i < x.Len(); i++ { if !reflectValueEqual(x.Index(i), y.Index(i)) { return false } } return true - case reflect.Struct: + case reflectlite.Struct: for i := 0; i < x.NumField(); i++ { if !reflectValueEqual(x.Field(i), y.Field(i)) { return false } } return true - case reflect.Interface: + case reflectlite.Interface: return reflectValueEqual(x.Elem(), y.Elem()) default: runtimePanic("comparing un-comparable type") diff --git a/src/runtime/interrupt/interrupt.go b/src/runtime/interrupt/interrupt.go index a8cf6f4e98..e0376a52f9 100644 --- a/src/runtime/interrupt/interrupt.go +++ b/src/runtime/interrupt/interrupt.go @@ -26,7 +26,7 @@ func New(id int, handler func(Interrupt)) Interrupt // and use that in an Interrupt object. That way the compiler will be able to // optimize away all interrupt handles that are never used in a program. // This system only works when interrupts need to be enabled before use and this -// is done only through calling Enable() on this object. If interrups cannot +// is done only through calling Enable() on this object. If interrupts cannot // individually be enabled/disabled, the compiler should create a pseudo-call // (like runtime/interrupt.use()) that keeps the interrupt alive. type handle struct { diff --git a/src/runtime/interrupt/interrupt_avr.go b/src/runtime/interrupt/interrupt_avr.go index 0af71a89e5..f27da8a048 100644 --- a/src/runtime/interrupt/interrupt_avr.go +++ b/src/runtime/interrupt/interrupt_avr.go @@ -27,7 +27,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { // SREG is at I/O address 0x3f. device.AsmFull("out 0x3f, {state}", map[string]interface{}{ diff --git a/src/runtime/interrupt/interrupt_cortexm.go b/src/runtime/interrupt/interrupt_cortexm.go index a34dff7879..708d486bdf 100644 --- a/src/runtime/interrupt/interrupt_cortexm.go +++ b/src/runtime/interrupt/interrupt_cortexm.go @@ -46,7 +46,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { arm.EnableInterrupts(uintptr(state)) } diff --git a/src/runtime/interrupt/interrupt_esp32c3.go b/src/runtime/interrupt/interrupt_esp32c3.go index 7d9be3937e..4e3e3dccfc 100644 --- a/src/runtime/interrupt/interrupt_esp32c3.go +++ b/src/runtime/interrupt/interrupt_esp32c3.go @@ -169,7 +169,7 @@ func handleInterrupt() { // save MSTATUS & MEPC, which could be overwritten by another CPU interrupt mstatus := riscv.MSTATUS.Get() mepc := riscv.MEPC.Get() - // Useing threshold to temporary disable this interrupts. + // Using threshold to temporary disable this interrupts. // FYI: using CPU interrupt enable bit make runtime to loose interrupts. reg := (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.INTERRUPT_CORE0.CPU_INT_PRI_0), interruptNumber*4)) thresholdSave := reg.Get() @@ -222,7 +222,7 @@ func handleException(mcause uintptr) { println("*** Exception: mcause:", mcause) switch uint32(mcause & 0x1f) { case 1: - println("*** virtual addess:", riscv.MTVAL.Get()) + println("*** virtual address:", riscv.MTVAL.Get()) case 2: println("*** opcode:", riscv.MTVAL.Get()) case 5: diff --git a/src/runtime/interrupt/interrupt_gameboyadvance.go b/src/runtime/interrupt/interrupt_gameboyadvance.go index 13f5fbe09d..2224066ed7 100644 --- a/src/runtime/interrupt/interrupt_gameboyadvance.go +++ b/src/runtime/interrupt/interrupt_gameboyadvance.go @@ -92,7 +92,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { // Restore interrupts to the previous state. gba.INTERRUPT.PAUSE.Set(uint16(state)) diff --git a/src/runtime/interrupt/interrupt_none.go b/src/runtime/interrupt/interrupt_none.go index 255fca9ea8..ea8bdb68c6 100644 --- a/src/runtime/interrupt/interrupt_none.go +++ b/src/runtime/interrupt/interrupt_none.go @@ -1,4 +1,4 @@ -//go:build !baremetal +//go:build !baremetal || tkey package interrupt @@ -21,7 +21,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) {} // In returns whether the system is currently in an interrupt. diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go index 2b97a2f11a..558e67150c 100644 --- a/src/runtime/interrupt/interrupt_tinygoriscv.go +++ b/src/runtime/interrupt/interrupt_tinygoriscv.go @@ -1,4 +1,4 @@ -//go:build tinygo.riscv +//go:build tinygo.riscv && !tkey package interrupt @@ -23,7 +23,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { riscv.EnableInterrupts(uintptr(state)) } diff --git a/src/runtime/interrupt/interrupt_xtensa.go b/src/runtime/interrupt/interrupt_xtensa.go index fe962f0451..69dc711836 100644 --- a/src/runtime/interrupt/interrupt_xtensa.go +++ b/src/runtime/interrupt/interrupt_xtensa.go @@ -23,7 +23,7 @@ func Disable() (state State) { // Restore restores interrupts to what they were before. Give the previous state // returned by Disable as a parameter. If interrupts were disabled before // calling Disable, this will not re-enable interrupts, allowing for nested -// cricital sections. +// critical sections. func Restore(state State) { device.AsmFull("wsr {state}, PS", map[string]interface{}{ "state": state, diff --git a/src/runtime/memhash_fnv.go b/src/runtime/memhash_fnv.go index 4d82971d0b..69802e0535 100644 --- a/src/runtime/memhash_fnv.go +++ b/src/runtime/memhash_fnv.go @@ -1,4 +1,4 @@ -//go:build (!wasi && !runtime_memhash_tsip && !runtime_memhash_leveldb) || (wasi && runtime_memhash_fnv) +//go:build (!wasip1 && !runtime_memhash_tsip && !runtime_memhash_leveldb) || (wasip1 && runtime_memhash_fnv) // This is the default for all targets except WASI, unless a more specific build // tag is set. diff --git a/src/runtime/memhash_leveldb.go b/src/runtime/memhash_leveldb.go index 22ff829e41..2e7557bb3f 100644 --- a/src/runtime/memhash_leveldb.go +++ b/src/runtime/memhash_leveldb.go @@ -1,4 +1,4 @@ -//go:build runtime_memhash_leveldb || (wasi && !runtime_memhash_fnv && !runtime_memhash_tsip) +//go:build runtime_memhash_leveldb || (wasip1 && !runtime_memhash_fnv && !runtime_memhash_tsip) // This is the default for WASI, but can also be used on other targets with the // right build tag. @@ -16,23 +16,6 @@ import ( "unsafe" ) -func ptrToSlice(ptr unsafe.Pointer, n uintptr) []byte { - var p []byte - - type _bslice struct { - ptr *byte - len uintptr - cap uintptr - } - - pslice := (*_bslice)(unsafe.Pointer(&p)) - pslice.ptr = (*byte)(ptr) - pslice.cap = n - pslice.len = n - - return p -} - // leveldb hash func hash32(ptr unsafe.Pointer, n, seed uintptr) uint32 { @@ -41,7 +24,7 @@ func hash32(ptr unsafe.Pointer, n, seed uintptr) uint32 { m = 0xc6a4a793 ) - b := ptrToSlice(ptr, n) + b := unsafe.Slice((*byte)(ptr), n) h := uint32(lseed^seed) ^ uint32(uint(len(b))*uint(m)) diff --git a/src/runtime/memhash_tsip.go b/src/runtime/memhash_tsip.go index e05bf15dc2..607055bd50 100644 --- a/src/runtime/memhash_tsip.go +++ b/src/runtime/memhash_tsip.go @@ -10,28 +10,11 @@ package runtime import ( - "encoding/binary" + "internal/binary" "math/bits" "unsafe" ) -func ptrToSlice(ptr unsafe.Pointer, n uintptr) []byte { - var p []byte - - type _bslice struct { - ptr *byte - len uintptr - cap uintptr - } - - pslice := (*_bslice)(unsafe.Pointer(&p)) - pslice.ptr = (*byte)(ptr) - pslice.cap = n - pslice.len = n - - return p -} - type sip struct { v0, v1 uint64 } @@ -45,8 +28,7 @@ func (s *sip) round() { } func hash64(ptr unsafe.Pointer, n uintptr, seed uintptr) uint64 { - - p := ptrToSlice(ptr, n) + p := unsafe.Slice((*byte)(ptr), n) k0 := uint64(seed) k1 := uint64(0) @@ -117,9 +99,7 @@ func (s *sip32) round() { } func hash32(ptr unsafe.Pointer, n uintptr, seed uintptr) uint32 { - // TODO(dgryski): replace this messiness with unsafe.Slice when we can use 1.17 features - - p := ptrToSlice(ptr, n) + p := unsafe.Slice((*byte)(ptr), n) k0 := uint32(seed) k1 := uint32(seed >> 32) diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go index 2699e08b3f..7a6f8e637f 100644 --- a/src/runtime/mstats.go +++ b/src/runtime/mstats.go @@ -9,6 +9,11 @@ package runtime type MemStats struct { // General statistics. + // Alloc is bytes of allocated heap objects. + // + // This is the same as HeapAlloc (see below). + Alloc uint64 + // Sys is the total bytes of memory obtained from the OS. // // Sys is the sum of the XSys fields below. Sys measures the @@ -18,6 +23,19 @@ type MemStats struct { // Heap memory statistics. + // HeapAlloc is bytes of allocated heap objects. + // + // "Allocated" heap objects include all reachable objects, as + // well as unreachable objects that the garbage collector has + // not yet freed. Specifically, HeapAlloc increases as heap + // objects are allocated and decreases as the heap is swept + // and unreachable objects are freed. Sweeping occurs + // incrementally between GC cycles, so these two processes + // occur simultaneously, and as a result HeapAlloc tends to + // change smoothly (in contrast with the sawtooth that is + // typical of stop-the-world garbage collectors). + HeapAlloc uint64 + // HeapSys is bytes of heap memory, total. // // In TinyGo unlike upstream Go, we make no distinction between diff --git a/src/runtime/nonhosted.go b/src/runtime/nonhosted.go index 6b47ba8b0c..9f01a7621a 100644 --- a/src/runtime/nonhosted.go +++ b/src/runtime/nonhosted.go @@ -1,4 +1,4 @@ -//go:build baremetal || js +//go:build baremetal || js || wasm_unknown || nintendoswitch package runtime diff --git a/src/runtime/os_darwin.c b/src/runtime/os_darwin.c index d5f6c807a7..5d7cd7c71d 100644 --- a/src/runtime/os_darwin.c +++ b/src/runtime/os_darwin.c @@ -1,7 +1,32 @@ -// Wrapper function because 'open' is a variadic function and variadic functions -// use a different (incompatible) calling convention on darwin/arm64. +//go:build none + +// This file is included in the build, despite the //go:build line above. #include + +// Wrapper function because 'open' is a variadic function and variadic functions +// use a different (incompatible) calling convention on darwin/arm64. +// This function is referenced from the compiler, when it sees a +// syscall.libc_open_trampoline function. int syscall_libc_open(const char *pathname, int flags, mode_t mode) { return open(pathname, flags, mode); } + +// The following functions are called by the runtime because Go can't call +// function pointers directly. + +int tinygo_syscall(int (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3), uintptr_t a1, uintptr_t a2, uintptr_t a3) { + return fn(a1, a2, a3); +} + +uintptr_t tinygo_syscallX(uintptr_t (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3), uintptr_t a1, uintptr_t a2, uintptr_t a3) { + return fn(a1, a2, a3); +} + +int tinygo_syscall6(int (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6), uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6) { + return fn(a1, a2, a3, a4, a5, a6); +} + +uintptr_t tinygo_syscall6X(uintptr_t (*fn)(uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6), uintptr_t a1, uintptr_t a2, uintptr_t a3, uintptr_t a4, uintptr_t a5, uintptr_t a6) { + return fn(a1, a2, a3, a4, a5, a6); +} diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index d9894bc1a4..e7f7b368fb 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -4,8 +4,6 @@ package runtime import "unsafe" -import "C" // dummy import so that os_darwin.c works - const GOOS = "darwin" const ( @@ -22,6 +20,14 @@ const ( clock_MONOTONIC_RAW = 4 ) +// Source: +// https://opensource.apple.com/source/xnu/xnu-7195.141.2/bsd/sys/signal.h.auto.html +const ( + sig_SIGBUS = 10 + sig_SIGILL = 4 + sig_SIGSEGV = 11 +) + // https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html type machHeader struct { magic uint32 @@ -107,7 +113,7 @@ func findGlobals(found func(start, end uintptr)) { } } - // Move on to the next load command (wich may or may not be a + // Move on to the next load command (which may or may not be a // LC_SEGMENT_64). cmd = (*segmentLoadCommand)(unsafe.Add(unsafe.Pointer(cmd), cmd.cmdsize)) } @@ -119,7 +125,107 @@ func hardwareRand() (n uint64, ok bool) { return n, true } +//go:linkname syscall_Getpagesize syscall.Getpagesize +func syscall_Getpagesize() int { + return int(libc_getpagesize()) +} + +// Call "system calls" (actually: libc functions) in a special way. +// - Most calls calls return a C int (which is 32-bits), and -1 on failure. +// - syscallX* is for functions that return a 64-bit integer (and also return +// -1 on failure). +// - syscallPtr is for functions that return a pointer on success or NULL on +// failure. +// - rawSyscall seems to avoid some stack modifications, which isn't relevant +// to TinyGo. + +//go:linkname syscall_syscall syscall.syscall +func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + // For TinyGo we don't need to do anything special to call C functions. + return syscall_rawSyscall(fn, a1, a2, a3) +} + +//go:linkname syscall_rawSyscall syscall.rawSyscall +func syscall_rawSyscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + result := call_syscall(fn, a1, a2, a3) + r1 = uintptr(result) + if result == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc_errno_location()) + } + return +} + +//go:linkname syscall_syscallX syscall.syscallX +func syscall_syscallX(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + r1 = call_syscallX(fn, a1, a2, a3) + if int64(r1) == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc_errno_location()) + } + return +} + +//go:linkname syscall_syscallPtr syscall.syscallPtr +func syscall_syscallPtr(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { + r1 = call_syscallX(fn, a1, a2, a3) + if r1 == 0 { + // Syscall returns a pointer on success, or NULL on failure. + err = uintptr(*libc_errno_location()) + } + return +} + +//go:linkname syscall_syscall6 syscall.syscall6 +func syscall_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { + result := call_syscall6(fn, a1, a2, a3, a4, a5, a6) + r1 = uintptr(result) + if result == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc_errno_location()) + } + return +} + +//go:linkname syscall_syscall6X syscall.syscall6X +func syscall_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) { + r1 = call_syscall6X(fn, a1, a2, a3, a4, a5, a6) + if int64(r1) == -1 { + // Syscall returns -1 on failure. + err = uintptr(*libc_errno_location()) + } + return +} + // uint32_t arc4random(void); // //export arc4random func libc_arc4random() uint32 + +// int getpagesize(void); +// +//export getpagesize +func libc_getpagesize() int32 + +// This function returns the error location in the darwin ABI. +// Discovered by compiling the following code using Clang: +// +// #include +// int getErrno() { +// return errno; +// } +// +//export __error +func libc_errno_location() *int32 + +//export tinygo_syscall +func call_syscall(fn, a1, a2, a3 uintptr) int32 + +//export tinygo_syscallX +func call_syscallX(fn, a1, a2, a3 uintptr) uintptr + +//export tinygo_syscall6 +func call_syscall6(fn, a1, a2, a3, a4, a5, a6 uintptr) int32 + +//export tinygo_syscall6X +func call_syscall6X(fn, a1, a2, a3, a4, a5, a6 uintptr) uintptr diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 3f27dfd16c..0ae105c5fc 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -1,11 +1,13 @@ -//go:build linux && !baremetal && !nintendoswitch && !wasi && !wasm_unknown +//go:build linux && !baremetal && !nintendoswitch && !wasip1 && !wasm_unknown && !wasip2 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" @@ -14,7 +16,7 @@ const ( flag_PROT_READ = 0x1 flag_PROT_WRITE = 0x2 flag_MAP_PRIVATE = 0x2 - flag_MAP_ANONYMOUS = 0x20 + flag_MAP_ANONYMOUS = linux_MAP_ANONYMOUS // different on alpha, hppa, mips, xtensa ) // Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h @@ -23,6 +25,12 @@ const ( clock_MONOTONIC_RAW = 4 ) +const ( + sig_SIGBUS = linux_SIGBUS + sig_SIGILL = linux_SIGILL + sig_SIGSEGV = linux_SIGSEGV +) + // For the definition of the various header structs, see: // https://refspecs.linuxfoundation.org/elf/elf.pdf // Also useful: @@ -77,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)) { @@ -133,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 +} diff --git a/src/runtime/os_other.go b/src/runtime/os_other.go index c8330bafc1..33814c4368 100644 --- a/src/runtime/os_other.go +++ b/src/runtime/os_other.go @@ -1,4 +1,4 @@ -//go:build linux && (baremetal || nintendoswitch || wasi || wasm_unknown) +//go:build linux && (baremetal || nintendoswitch || wasm_unknown) // Other systems that aren't operating systems supported by the Go toolchain // need to pretend to be an existing operating system. Linux seems like a good diff --git a/src/runtime/os_wasip2.go b/src/runtime/os_wasip2.go new file mode 100644 index 0000000000..baecfb3ab9 --- /dev/null +++ b/src/runtime/os_wasip2.go @@ -0,0 +1,5 @@ +//go:build wasip2 + +package runtime + +const GOOS = "wasip2" diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index 3750d51940..a124e7ab14 100644 --- a/src/runtime/os_windows.go +++ b/src/runtime/os_windows.go @@ -113,3 +113,6 @@ func syscall_Getpagesize() int { _GetSystemInfo(unsafe.Pointer(&info)) return int(info.dwpagesize) } + +//export _errno +func libc_errno_location() *int32 diff --git a/src/runtime/panic.go b/src/runtime/panic.go index e8a67e4b64..9ae1f982b9 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -2,6 +2,8 @@ package runtime import ( "internal/task" + "runtime/interrupt" + "tinygo" "unsafe" ) @@ -21,6 +23,11 @@ func tinygo_longjmp(frame *deferFrame) // Returns whether recover is supported on the current architecture. func supportsRecover() bool +// Compile intrinsic. +// Returns which strategy is used. This is usually "print" but can be changed +// using the -panic= compiler flag. +func panicStrategy() uint8 + // DeferFrame is a stack allocated object that stores information for the // current "defer frame", which is used in functions that use the `defer` // keyword. @@ -31,21 +38,43 @@ type deferFrame struct { JumpPC unsafe.Pointer // pc to return to ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture) Previous *deferFrame // previous recover buffer pointer - Panicking bool // true iff this defer frame is panicking + Panicking panicState // not panicking, panicking, or in Goexit PanicValue interface{} // panic value, might be nil for panic(nil) for example } +type panicState uint8 + +const ( + panicFalse panicState = iota + panicTrue + panicGoexit +) + // Builtin function panic(msg), used as a compiler intrinsic. func _panic(message interface{}) { - if supportsRecover() { + panicOrGoexit(message, panicTrue) +} + +func panicOrGoexit(message interface{}, panicking panicState) { + if panicStrategy() == tinygo.PanicStrategyTrap { + trap() + } + // Note: recover is not supported inside interrupts. + // (This could be supported, like defer, but we currently don't). + if supportsRecover() && !interrupt.In() { frame := (*deferFrame)(task.Current().DeferFrame) if frame != nil { frame.PanicValue = message - frame.Panicking = true + frame.Panicking = panicking tinygo_longjmp(frame) // unreachable } } + if panicking == panicGoexit { + // Call to Goexit() instead of a panic. + // Exit the goroutine instead of printing a panic message. + deadlock() + } printstring("panic: ") printitf(message) printnl() @@ -60,6 +89,9 @@ func runtimePanic(msg string) { } func runtimePanicAt(addr unsafe.Pointer, msg string) { + if panicStrategy() == tinygo.PanicStrategyTrap { + trap() + } if hasReturnAddr { printstring("panic: runtime error at ") printptr(uintptr(addr) - callInstSize) @@ -79,10 +111,16 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) { //go:inline //go:nobounds func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { + if interrupt.In() { + // Defer is not currently allowed in interrupts. + // We could add support for this, but since defer might also allocate + // (especially in loops) it might not be a good idea anyway. + runtimePanicAt(returnAddress(0), "defer in interrupt") + } currentTask := task.Current() frame.Previous = (*deferFrame)(currentTask.DeferFrame) frame.JumpSP = jumpSP - frame.Panicking = false + frame.Panicking = panicFalse currentTask.DeferFrame = unsafe.Pointer(frame) } @@ -94,10 +132,10 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { //go:nobounds func destroyDeferFrame(frame *deferFrame) { task.Current().DeferFrame = unsafe.Pointer(frame.Previous) - if frame.Panicking { + if frame.Panicking != panicFalse { // We're still panicking! // Re-raise the panic now. - _panic(frame.PanicValue) + panicOrGoexit(frame.PanicValue, frame.Panicking) } } @@ -106,8 +144,11 @@ func destroyDeferFrame(frame *deferFrame) { // useParentFrame is set when the caller of runtime._recover has a defer frame // itself. In that case, recover() shouldn't check that frame but one frame up. func _recover(useParentFrame bool) interface{} { - if !supportsRecover() { - // Compiling without stack unwinding support, so make this a no-op. + if !supportsRecover() || interrupt.In() { + // Either we're compiling without stack unwinding support, or we're + // inside an interrupt where panic/recover is not supported. Either way, + // make this a no-op since panic() won't do any long jumps to a deferred + // function. return nil } // TODO: somehow check that recover() is called directly by a deferred @@ -119,10 +160,15 @@ func _recover(useParentFrame bool) interface{} { // already), but instead from the previous frame. frame = frame.Previous } - if frame != nil && frame.Panicking { + if frame != nil && frame.Panicking != panicFalse { + if frame.Panicking == panicGoexit { + // Special value that indicates we're exiting the goroutine using + // Goexit(). Therefore, make this recover call a no-op. + return nil + } // Only the first call to recover returns the panic value. It also stops // the panicking sequence, hence setting panicking to false. - frame.Panicking = false + frame.Panicking = panicFalse return frame.PanicValue } // Not panicking, so return a nil interface. @@ -139,7 +185,7 @@ func nilMapPanic() { runtimePanicAt(returnAddress(0), "assignment to entry in nil map") } -// Panic when trying to acces an array or slice out of bounds. +// Panic when trying to access an array or slice out of bounds. func lookupPanic() { runtimePanicAt(returnAddress(0), "index out of range") } @@ -180,3 +226,8 @@ func divideByZeroPanic() { func blockingPanic() { runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function") } + +//go:linkname fips_fatal crypto/internal/fips140.fatal +func fips_fatal(msg string) { + runtimePanic(msg) +} diff --git a/src/runtime/print.go b/src/runtime/print.go index ef9117ff3e..a4de460253 100644 --- a/src/runtime/print.go +++ b/src/runtime/print.go @@ -1,6 +1,7 @@ package runtime import ( + "internal/task" "unsafe" ) @@ -8,6 +9,18 @@ type stringer interface { String() string } +// Lock to make sure print calls do not interleave. +// This is a no-op lock on systems that do not have parallelism. +var printLock task.PMutex + +func printlock() { + printLock.Lock() +} + +func printunlock() { + printLock.Unlock() +} + //go:nobounds func printstring(s string) { for i := 0; i < len(s); i++ { @@ -293,67 +306,84 @@ func printnl() { func printitf(msg interface{}) { switch msg := msg.(type) { case bool: - print(msg) + printbool(msg) case int: - print(msg) + switch unsafe.Sizeof(msg) { + case 8: + printint64(int64(msg)) + case 4: + printint32(int32(msg)) + } case int8: - print(msg) + printint8(msg) case int16: - print(msg) + printint16(msg) case int32: - print(msg) + printint32(msg) case int64: - print(msg) + printint64(msg) case uint: - print(msg) + switch unsafe.Sizeof(msg) { + case 8: + printuint64(uint64(msg)) + case 4: + printuint32(uint32(msg)) + } case uint8: - print(msg) + printuint8(msg) case uint16: - print(msg) + printuint16(msg) case uint32: - print(msg) + printuint32(msg) case uint64: - print(msg) + printuint64(msg) case uintptr: - print(msg) + printuintptr(msg) case float32: - print(msg) + printfloat32(msg) case float64: - print(msg) + printfloat64(msg) case complex64: - print(msg) + printcomplex64(msg) case complex128: - print(msg) + printcomplex128(msg) case string: - print(msg) + printstring(msg) case error: - print(msg.Error()) + printstring(msg.Error()) case stringer: - print(msg.String()) + printstring(msg.String()) default: // cast to underlying type itf := *(*_interface)(unsafe.Pointer(&msg)) putchar('(') printuintptr(uintptr(itf.typecode)) putchar(':') - print(itf.value) + printptr(uintptr(itf.value)) putchar(')') } } func printmap(m *hashmap) { - print("map[") + printstring("map[") if m == nil { - print("nil") + printstring("nil") } else { - print(uint(m.count)) + switch unsafe.Sizeof(m.count) { + case 8: + printuint64(uint64(m.count)) + case 4: + printuint32(uint32(m.count)) + case 2: + printuint16(uint16(m.count)) + } } putchar(']') } func printptr(ptr uintptr) { if ptr == 0 { - print("nil") + printstring("nil") return } putchar('0') diff --git a/src/runtime/proc.go b/src/runtime/proc.go new file mode 100644 index 0000000000..e029ffe95f --- /dev/null +++ b/src/runtime/proc.go @@ -0,0 +1,19 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runtime + +// Called from syscall package before Exec. +// +//go:linkname syscall_runtime_BeforeExec syscall.runtime_BeforeExec +func syscall_runtime_BeforeExec() { + // Used in BigGo to serialize exec / thread creation. Stubbing to + // satisfy link. +} + +// Called from syscall package after Exec. +// +//go:linkname syscall_runtime_AfterExec syscall.runtime_AfterExec +func syscall_runtime_AfterExec() { +} diff --git a/src/runtime/rand_hwrng.go b/src/runtime/rand_hwrng.go index 7154ffd79c..9f1a152d7f 100644 --- a/src/runtime/rand_hwrng.go +++ b/src/runtime/rand_hwrng.go @@ -1,4 +1,4 @@ -//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3) +//go:build baremetal && (nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) // If you update the above build constraint, you'll probably also need to update // src/crypto/rand/rand_baremetal.go. diff --git a/src/runtime/rand_norng.go b/src/runtime/rand_norng.go index e6045008e4..aa79c500dc 100644 --- a/src/runtime/rand_norng.go +++ b/src/runtime/rand_norng.go @@ -1,4 +1,4 @@ -//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3) +//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) package runtime diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index ac7cd25c93..99ca34f2c8 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -41,11 +41,9 @@ func memmove(dst, src unsafe.Pointer, size uintptr) // like llvm.memset.p0.i32(ptr, 0, size, false). func memzero(ptr unsafe.Pointer, size uintptr) -// This intrinsic returns the current stack pointer. -// It is normally used together with llvm.stackrestore but also works to get the -// current stack pointer in a platform-independent way. -// -//export llvm.stacksave +// Return the current stack pointer using the llvm.stacksave.p0 intrinsic. +// It is normally used together with llvm.stackrestore.p0 but also works to get +// the current stack pointer in a platform-independent way. func stacksave() unsafe.Pointer //export strlen @@ -124,3 +122,13 @@ func write(fd uintptr, p unsafe.Pointer, n int32) int32 { } return 0 } + +// getAuxv is linknamed from golang.org/x/sys/cpu. +func getAuxv() []uintptr { + return nil +} + +// Called from cgo to obtain the errno value. +func cgo_errno() uintptr { + return uintptr(*libc_errno_location()) +} diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go index 166df214fa..d30fc7f6f0 100644 --- a/src/runtime/runtime_atsamd21.go +++ b/src/runtime/runtime_atsamd21.go @@ -318,7 +318,7 @@ func readRTC() uint32 { // ticks are in microseconds // Returns true if the timer completed. -// Returns false if another interrupt occured which requires an early return to scheduler. +// Returns false if another interrupt occurred which requires an early return to scheduler. func timerSleep(ticks uint32) bool { timerWakeup.Set(0) if ticks < 7 { diff --git a/src/runtime/runtime_atsamd51.go b/src/runtime/runtime_atsamd51.go index 586ab00911..151f815813 100644 --- a/src/runtime/runtime_atsamd51.go +++ b/src/runtime/runtime_atsamd51.go @@ -307,7 +307,7 @@ func readRTC() uint32 { // ticks are in microseconds // Returns true if the timer completed. -// Returns false if another interrupt occured which requires an early return to scheduler. +// Returns false if another interrupt occurred which requires an early return to scheduler. func timerSleep(ticks uint32) bool { timerWakeup.Set(0) if ticks < 8 { diff --git a/src/runtime/runtime_avr.go b/src/runtime/runtime_avr.go index a2e11104e0..43d35d7b9c 100644 --- a/src/runtime/runtime_avr.go +++ b/src/runtime/runtime_avr.go @@ -104,7 +104,7 @@ func exit(code int) { func abort() { // Disable interrupts and go to sleep. - // This can never be awoken except for reset, and is recogized as termination by simavr. + // This can never be awoken except for reset, and is recognized as termination by simavr. avr.Asm("cli") for { avr.Asm("sleep") diff --git a/src/runtime/runtime_avrtiny.go b/src/runtime/runtime_avrtiny.go index 63eb5943a2..8ce324938c 100644 --- a/src/runtime/runtime_avrtiny.go +++ b/src/runtime/runtime_avrtiny.go @@ -115,7 +115,7 @@ func sleepTicks(d timeUnit) { // Sleep until the next interrupt happens. avr.Asm("sei\nsleep\ncli") if cmpMatch.Get() != 0 { - // The CMP interrupt occured, so we have slept long enough. + // The CMP interrupt occurred, so we have slept long enough. cmpMatch.Set(0) break } diff --git a/src/runtime/runtime_cortexm_hardfault.go b/src/runtime/runtime_cortexm_hardfault.go index 1e264c286a..b2449ed910 100644 --- a/src/runtime/runtime_cortexm_hardfault.go +++ b/src/runtime/runtime_cortexm_hardfault.go @@ -8,7 +8,7 @@ import ( // This function is called at HardFault. // Before this function is called, the stack pointer is reset to the initial -// stack pointer (loaded from addres 0x0) and the previous stack pointer is +// stack pointer (loaded from address 0x0) and the previous stack pointer is // passed as an argument to this function. This allows for easy inspection of // the stack the moment a HardFault occurs, but it means that the stack will be // corrupted by this function and thus this handler must not attempt to recover. diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go index 01cc3ba119..f65c39a4df 100644 --- a/src/runtime/runtime_fe310.go +++ b/src/runtime/runtime_fe310.go @@ -85,7 +85,7 @@ func handleInterrupt() { riscv.MCAUSE.Set(0) } -// initPeripherals configures periperhals the way the runtime expects them. +// initPeripherals configures peripherals the way the runtime expects them. func initPeripherals() { // Configure PLL to output 320MHz. // R=2: divide 16MHz to 8MHz diff --git a/src/runtime/runtime_k210.go b/src/runtime/runtime_k210.go index 5998de69db..96cdd63408 100644 --- a/src/runtime/runtime_k210.go +++ b/src/runtime/runtime_k210.go @@ -105,7 +105,7 @@ func handleInterrupt() { riscv.MCAUSE.Set(0) } -// initPeripherals configures periperhals the way the runtime expects them. +// initPeripherals configures peripherals the way the runtime expects them. func initPeripherals() { // Enable APB0 clock. kendryte.SYSCTL.CLK_EN_CENT.SetBits(kendryte.SYSCTL_CLK_EN_CENT_APB0_CLK_EN) diff --git a/src/runtime/runtime_mimxrt1062_clock.go b/src/runtime/runtime_mimxrt1062_clock.go index 23d6c3cafd..707308b9fe 100644 --- a/src/runtime/runtime_mimxrt1062_clock.go +++ b/src/runtime/runtime_mimxrt1062_clock.go @@ -14,7 +14,7 @@ const ( // Note from Teensyduino (cores/teensy4/startup.c): // -// | ARM SysTick is used for most Ardiuno timing functions, delay(), millis(), +// | ARM SysTick is used for most Arduino timing functions, delay(), millis(), // | micros(). SysTick can run from either the ARM core clock, or from an // | "external" clock. NXP documents it as "24 MHz XTALOSC can be the external // | clock source of SYSTICK" (RT1052 ref manual, rev 1, page 411). However, diff --git a/src/runtime/runtime_mimxrt1062_time.go b/src/runtime/runtime_mimxrt1062_time.go index e3779052ff..feaf68dbd4 100644 --- a/src/runtime/runtime_mimxrt1062_time.go +++ b/src/runtime/runtime_mimxrt1062_time.go @@ -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() } } diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index 10724e40f6..2d3677bf05 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -84,6 +84,19 @@ func ticks() timeUnit { return timeUnit(ticksToNanoseconds(timeUnit(getArmSystemTick()))) } +// timeOffset is how long the monotonic clock started after the Unix epoch. It +// should be a positive integer under normal operation or zero when it has not +// been set. +var timeOffset int64 + +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + mono = nanotime() + sec = (mono + timeOffset) / (1000 * 1000 * 1000) + nsec = int32((mono + timeOffset) - sec*(1000*1000*1000)) + return +} + var stdoutBuffer = make([]byte, 120) var position = 0 @@ -98,6 +111,14 @@ func putchar(c byte) { position++ } +func buffered() int { + return 0 +} + +func getchar() byte { + return 0 +} + func abort() { for { exit(1) @@ -172,9 +193,9 @@ func setupEnv() { func setupHeap() { if heapStart != 0 { if debugInit { - print("Heap already overrided by hblauncher") + print("Heap already overridden by hblauncher") } - // Already overrided + // Already overridden return } @@ -313,3 +334,9 @@ func hardwareRand() (n uint64, ok bool) { // TODO: see whether there is a RNG and use it. return 0, false } + +func libc_errno_location() *int32 { + // CGo is unavailable, so this function should be unreachable. + runtimePanic("runtime: no cgo errno") + return nil +} diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go index 2f58c3518c..729c6bb20f 100644 --- a/src/runtime/runtime_nrf.go +++ b/src/runtime/runtime_nrf.go @@ -103,7 +103,7 @@ func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns * 64 / 1953125) } -// Monotonically increasing numer of ticks since start. +// Monotonically increasing number of ticks since start. func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 diff --git a/src/runtime/runtime_nrf52840.go b/src/runtime/runtime_nrf52840.go index 47f9a8db1e..41c36fe5f0 100644 --- a/src/runtime/runtime_nrf52840.go +++ b/src/runtime/runtime_nrf52840.go @@ -106,7 +106,7 @@ func nanosecondsToTicks(ns int64) timeUnit { return timeUnit(ns * 64 / 1953125) } -// Monotonically increasing numer of ticks since start. +// Monotonically increasing number of ticks since start. func ticks() timeUnit { // For some ways of capturing the time atomically, see this thread: // https://www.eevblog.com/forum/microcontrollers/correct-timing-by-timer-overflow-count/msg749617/#msg749617 diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index e5659d625d..1d36a771e5 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -14,7 +14,7 @@ func machineTicks() uint64 // machineLightSleep is provided by package machine. func machineLightSleep(uint64) -type timeUnit uint64 +type timeUnit int64 // ticks returns the number of ticks (microseconds) elapsed since power up. func ticks() timeUnit { @@ -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 diff --git a/src/runtime/runtime_rp2350.go b/src/runtime/runtime_rp2350.go new file mode 100644 index 0000000000..f70ec413cb --- /dev/null +++ b/src/runtime/runtime_rp2350.go @@ -0,0 +1,88 @@ +//go:build rp2350 + +package runtime + +import ( + "device/arm" + "machine" + "machine/usb/cdc" +) + +// machineTicks is provided by package machine. +func machineTicks() uint64 + +// machineLightSleep is provided by package machine. +func machineLightSleep(uint64) + +type timeUnit int64 + +// ticks returns the number of ticks (microseconds) elapsed since power up. +func ticks() timeUnit { + t := machineTicks() + return timeUnit(t) +} + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 1000 +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 1000) +} + +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 + // eligible to run + machineLightSleep(uint64(d)) + return + } + + // Busy loop + sleepUntil := ticks() + d + for ticks() < sleepUntil { + } +} + +func waitForEvents() { + arm.Asm("wfe") +} + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} + +// machineInit is provided by package machine. +func machineInit() + +func init() { + machineInit() + + cdc.EnableUSBCDC() + machine.USBDev.Configure(machine.UARTConfig{}) + machine.InitSerial() +} + +//export Reset_Handler +func main() { + preinit() + run() + exit(0) +} diff --git a/src/runtime/runtime_stm32f103.go b/src/runtime/runtime_stm32f103.go index ac98de674c..702d773897 100644 --- a/src/runtime/runtime_stm32f103.go +++ b/src/runtime/runtime_stm32f103.go @@ -33,10 +33,11 @@ func buffered() int { // initCLK sets clock to 72MHz using HSE 8MHz crystal w/ PLL X 9 (8MHz x 9 = 72MHz). func initCLK() { - stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_LATENCY_WS2) // Two wait states, per datasheet - stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos) // prescale PCLK1 = HCLK/2 - stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div1 << stm32.RCC_CFGR_PPRE2_Pos) // prescale PCLK2 = HCLK/1 - stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON) // enable HSE clock + stm32.FLASH.ACR.SetBits(stm32.FLASH_ACR_LATENCY_WS2) // Two wait states, per datasheet + stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE1_Div2 << stm32.RCC_CFGR_PPRE1_Pos) // prescale PCLK1 = HCLK/2 + stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_PPRE2_Div1 << stm32.RCC_CFGR_PPRE2_Pos) // prescale PCLK2 = HCLK/1 + stm32.RCC.CFGR.SetBits(stm32.RCC_CFGR_ADCPRE_Div6 << stm32.RCC_CFGR_ADCPRE_Pos) // prescale ADCCLK = PCLK2/6 + stm32.RCC.CR.SetBits(stm32.RCC_CR_HSEON) // enable HSE clock // wait for the HSEREADY flag for !stm32.RCC.CR.HasBits(stm32.RCC_CR_HSERDY) { diff --git a/src/runtime/runtime_stm32l4x6.go b/src/runtime/runtime_stm32l4x6.go new file mode 100644 index 0000000000..6a56d8bd18 --- /dev/null +++ b/src/runtime/runtime_stm32l4x6.go @@ -0,0 +1,29 @@ +//go:build stm32 && stm32l4x6 + +package runtime + +import ( + "device/stm32" +) + +/* +clock settings + + +-------------+-----------+ + | LSE | 32.768khz | + | SYSCLK | 80mhz | + | HCLK | 80mhz | + | APB1(PCLK1) | 80mhz | + | APB2(PCLK2) | 80mhz | + +-------------+-----------+ +*/ +const ( + HSE_STARTUP_TIMEOUT = 0x0500 + PLL_M = 1 + PLL_N = 40 + PLL_P = RCC_PLLP_DIV7 + PLL_Q = RCC_PLLQ_DIV2 + PLL_R = RCC_PLLR_DIV2 + + MSIRANGE = stm32.RCC_CR_MSIRANGE_Range4M +) diff --git a/src/runtime/runtime_tinygowasm.go b/src/runtime/runtime_tinygowasm.go index 3998603539..67367b479e 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -1,4 +1,7 @@ -//go:build tinygo.wasm && !wasm_unknown +//go:build tinygo.wasm && !wasm_unknown && !wasip2 + +// This file is for wasm/wasip1 and for wasm/js, which both use much of the +// WASIp1 API. package runtime @@ -21,6 +24,11 @@ func fd_write(id uint32, iovs *__wasi_iovec_t, iovs_len uint, nwritten *uint) (e //go:wasmimport wasi_snapshot_preview1 proc_exit func proc_exit(exitcode uint32) +// Flush stdio on exit. +// +//export __stdio_exit +func __stdio_exit() + const ( putcharBufferSize = 120 stdout = 1 @@ -72,9 +80,17 @@ func abort() { //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { + // 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. @@ -96,3 +112,8 @@ func hardwareRand() (n uint64, ok bool) { // //export arc4random func libc_arc4random() uint32 + +// int *__errno_location(void); +// +//export __errno_location +func libc_errno_location() *int32 diff --git a/src/runtime/runtime_tinygowasm_unknown.go b/src/runtime/runtime_tinygowasm_unknown.go index 39caa245a2..b67d70aeab 100644 --- a/src/runtime/runtime_tinygowasm_unknown.go +++ b/src/runtime/runtime_tinygowasm_unknown.go @@ -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 @@ -47,3 +51,9 @@ func procUnpin() { func hardwareRand() (n uint64, ok bool) { return 0, false } + +func libc_errno_location() *int32 { + // CGo is unavailable, so this function should be unreachable. + runtimePanic("runtime: no cgo errno") + return nil +} diff --git a/src/runtime/runtime_tinygowasmp2.go b/src/runtime/runtime_tinygowasmp2.go new file mode 100644 index 0000000000..5565cb4ee6 --- /dev/null +++ b/src/runtime/runtime_tinygowasmp2.go @@ -0,0 +1,89 @@ +//go:build wasip2 + +package runtime + +import ( + "internal/cm" + + exit "internal/wasi/cli/v0.2.0/exit" + stdout "internal/wasi/cli/v0.2.0/stdout" + monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + random "internal/wasi/random/v0.2.0/random" +) + +const putcharBufferSize = 120 + +// Using global variables to avoid heap allocation. +var ( + putcharStdout = stdout.GetStdout() + putcharBuffer = [putcharBufferSize]byte{} + putcharPosition uint = 0 +) + +func putchar(c byte) { + putcharBuffer[putcharPosition] = c + putcharPosition++ + if c == '\n' || putcharPosition >= putcharBufferSize { + list := cm.NewList(&putcharBuffer[0], putcharPosition) + putcharStdout.BlockingWriteAndFlush(list) // error return ignored; can't do anything anyways + putcharPosition = 0 + } +} + +func getchar() byte { + // dummy, TODO + return 0 +} + +func buffered() int { + // dummy, TODO + return 0 +} + +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + now := wallclock.Now() + sec = int64(now.Seconds) + nsec = int32(now.Nanoseconds) + mono = int64(monotonicclock.Now()) + return +} + +// Abort executes the wasm 'unreachable' instruction. +func abort() { + trap() +} + +//go:linkname syscall_Exit syscall.Exit +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. + +//go:linkname procPin sync/atomic.runtime_procPin +func procPin() { +} + +//go:linkname procUnpin sync/atomic.runtime_procUnpin +func procUnpin() { +} + +func hardwareRand() (n uint64, ok bool) { + return random.GetRandomU64(), true +} + +func libc_errno_location() *int32 { + // CGo is unavailable, so this function should be unreachable. + runtimePanic("runtime: no cgo errno") + return nil +} diff --git a/src/runtime/runtime_tkey.go b/src/runtime/runtime_tkey.go new file mode 100644 index 0000000000..ba8c5e944f --- /dev/null +++ b/src/runtime/runtime_tkey.go @@ -0,0 +1,64 @@ +//go:build tkey + +// This file implements target-specific things for the TKey. + +package runtime + +import ( + "device/tkey" + "machine" + "runtime/volatile" +) + +type timeUnit int64 + +//export main +func main() { + preinit() + initPeripherals() + run() + exit(0) +} + +// initPeripherals configures peripherals the way the runtime expects them. +func initPeripherals() { + // prescaler value that results in 0.00001-second timer-ticks. + // given an 18 MHz processor, a millisecond is about 18,000 cycles. + tkey.TIMER.PRESCALER.Set(18 * machine.MHz / 100000) + + machine.InitSerial() +} + +func putchar(c byte) { + machine.Serial.WriteByte(c) +} + +func getchar() byte { + for machine.Serial.Buffered() == 0 { + Gosched() + } + v, _ := machine.Serial.ReadByte() + return v +} + +func buffered() int { + return machine.Serial.Buffered() +} + +var timestamp volatile.Register32 + +// ticks returns the current value of the timer in ticks. +func ticks() timeUnit { + return timeUnit(timestamp.Get()) +} + +// sleepTicks sleeps for at least the duration d. +func sleepTicks(d timeUnit) { + target := uint32(ticks() + d) + + tkey.TIMER.TIMER.Set(uint32(d)) + tkey.TIMER.CTRL.SetBits(tkey.TK1_MMIO_TIMER_CTRL_START) + for tkey.TIMER.STATUS.Get() != 0 { + } + timestamp.Set(target) +} diff --git a/src/runtime/runtime_tkey_baremetal.go b/src/runtime/runtime_tkey_baremetal.go new file mode 100644 index 0000000000..a83bd4408d --- /dev/null +++ b/src/runtime/runtime_tkey_baremetal.go @@ -0,0 +1,24 @@ +//go:build tkey && !qemu + +package runtime + +import "device/riscv" + +// ticksToNanoseconds converts ticks (at 18MHz) to 10 µs. +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 10000 +} + +// nanosecondsToTicks converts 10 µs to ticks (at 18MHz). +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 10000) +} + +func exit(code int) { + abort() +} + +func abort() { + // Force illegal instruction to halt CPU + riscv.Asm("unimp") +} diff --git a/src/runtime/runtime_unix.c b/src/runtime/runtime_unix.c new file mode 100644 index 0000000000..79dd7ce915 --- /dev/null +++ b/src/runtime/runtime_unix.c @@ -0,0 +1,56 @@ +//go:build none + +// This file is included on Darwin and Linux (despite the //go:build line above). + +#define _GNU_SOURCE +#define _XOPEN_SOURCE +#include +#include +#include +#include +#include + +void tinygo_handle_fatal_signal(int sig, uintptr_t addr); + +static void signal_handler(int sig, siginfo_t *info, void *context) { + ucontext_t* uctx = context; + uintptr_t addr = 0; + #if __APPLE__ + #if __arm64__ + addr = uctx->uc_mcontext->__ss.__pc; + #elif __x86_64__ + addr = uctx->uc_mcontext->__ss.__rip; + #else + #error unknown architecture + #endif + #elif __linux__ + // Note: this can probably be simplified using the MC_PC macro in musl, + // but this works for now. + #if __arm__ + addr = uctx->uc_mcontext.arm_pc; + #elif __i386__ + addr = uctx->uc_mcontext.gregs[REG_EIP]; + #elif __x86_64__ + addr = uctx->uc_mcontext.gregs[REG_RIP]; + #else // aarch64, mips, maybe others + addr = uctx->uc_mcontext.pc; + #endif + #else + #error unknown platform + #endif + tinygo_handle_fatal_signal(sig, addr); +} + +void tinygo_register_fatal_signals(void) { + struct sigaction act = { 0 }; + // SA_SIGINFO: we want the 2 extra parameters + // SA_RESETHAND: only catch the signal once (the handler will re-raise the signal) + act.sa_flags = SA_SIGINFO | SA_RESETHAND; + act.sa_sigaction = &signal_handler; + + // Register the signal handler for common issues. There are more signals, + // which can be added if needed. + sigaction(SIGBUS, &act, NULL); + sigaction(SIGILL, &act, NULL); + sigaction(SIGSEGV, &act, NULL); +} diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index f1f1c4df70..6add9157d3 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -1,8 +1,13 @@ -//go:build (darwin || (linux && !baremetal && !wasi && !wasm_unknown)) && !nintendoswitch +//go:build darwin || (linux && !baremetal && !wasip1 && !wasm_unknown && !wasip2 && !nintendoswitch) package runtime import ( + "internal/futex" + "internal/task" + "math/bits" + "sync/atomic" + "tinygo" "unsafe" ) @@ -12,6 +17,9 @@ func libc_write(fd int32, buf unsafe.Pointer, count uint) int //export usleep func usleep(usec uint) int +//export pause +func pause() int32 + // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); // Note: off_t is defined as int64 because: // - musl (used on Linux) always defines it as int64 @@ -26,6 +34,9 @@ func abort() //export exit func exit(code int) +//export raise +func raise(sig int32) + //export clock_gettime func libc_clock_gettime(clk_id int32, ts *timespec) @@ -68,12 +79,19 @@ var stackTop uintptr // //export main func main(argc int32, argv *unsafe.Pointer) int { - preinit() + if needsStaticHeap { + // Allocate area for the heap if the GC needs it. + allocateHeap() + } // Store argc and argv for later use. main_argc = argc main_argv = argv + // Register some fatal signals, so that we can print slightly better error + // messages. + tinygo_register_fatal_signals() + // Obtain the initial stack pointer right before calling the run() function. // The run function has been moved to a separate (non-inlined) function so // that the correct stack pointer is read. @@ -119,6 +137,50 @@ func runMain() { run() } +//export tinygo_register_fatal_signals +func tinygo_register_fatal_signals() + +// Print fatal errors when they happen, including the instruction location. +// With the particular formatting below, `tinygo run` can extract the location +// where the signal happened and try to show the source location based on DWARF +// information. +// +//export tinygo_handle_fatal_signal +func tinygo_handle_fatal_signal(sig int32, addr uintptr) { + if panicStrategy() == tinygo.PanicStrategyTrap { + trap() + } + + // Print signal including the faulting instruction. + if addr != 0 { + printstring("panic: runtime error at ") + printptr(addr) + } else { + printstring("panic: runtime error") + } + printstring(": caught signal ") + switch sig { + case sig_SIGBUS: + println("SIGBUS") + case sig_SIGILL: + println("SIGILL") + case sig_SIGSEGV: + println("SIGSEGV") + default: + println(sig) + } + + // TODO: it might be interesting to also print the invalid address for + // SIGSEGV and SIGBUS. + + // Do *not* abort here, instead raise the same signal again. The signal is + // registered with SA_RESETHAND which means it executes only once. So when + // we raise the signal again below, the signal isn't handled specially but + // is handled in the default way (probably exiting the process, maybe with a + // core dump). + raise(sig) +} + //go:extern environ var environ *unsafe.Pointer @@ -166,8 +228,31 @@ func nanosecondsToTicks(ns int64) timeUnit { } func sleepTicks(d timeUnit) { - // timeUnit is in nanoseconds, so need to convert to microseconds here. - usleep(uint(d) / 1000) + until := ticks() + d + + for { + // Sleep for the given amount of time. + // If a signal arrived before going to sleep, or during the sleep, the + // sleep will exit early. + signalFutex.WaitUntil(0, uint64(ticksToNanoseconds(d))) + + // Check whether there was a signal before or during the call to + // WaitUntil. + if signalFutex.Swap(0) != 0 { + if checkSignals() && hasScheduler { + // We got a signal, so return to the scheduler. + // (If there is no scheduler, there is no other goroutine that + // might need to run now). + return + } + } + + // Set duration (in next loop iteration) to the remaining time. + d = until - ticks() + if d <= 0 { + return + } + } } func getTime(clock int32) uint64 { @@ -216,7 +301,7 @@ var heapMaxSize uintptr var heapStart, heapEnd uintptr -func preinit() { +func allocateHeap() { // Allocate a large chunk of virtual memory. Because it is virtual, it won't // really be allocated in RAM. Memory will only be allocated when it is // first touched. @@ -229,6 +314,9 @@ func preinit() { // heap size. // This can happen on 32-bit systems. heapMaxSize /= 2 + if heapMaxSize < 4096 { + runtimePanic("cannot allocate heap memory") + } continue } heapStart = uintptr(addr) @@ -253,3 +341,177 @@ func growHeap() bool { setHeapEnd(heapStart + heapSize) return true } + +// Indicate whether signals have been registered. +var hasSignals bool + +// Futex for the signal handler. +// The value is 0 when there are no new signals, or 1 when there are unhandled +// signals and the main thread doesn't know about it yet. +// When a signal arrives, the futex value is changed to 1 and if it was 0 +// before, all waiters are awoken. +// When a wait exits, the value is changed to 0 and if it wasn't 0 before, the +// signals are checked. +var signalFutex futex.Futex + +// Mask of signals that have been received. The signal handler atomically ORs +// signals into this value. +var receivedSignals atomic.Uint32 + +//go:linkname signal_enable os/signal.signal_enable +func signal_enable(s uint32) { + if s >= 32 { + // TODO: to support higher signal numbers, we need to turn + // receivedSignals into a uint32 array. + runtimePanicAt(returnAddress(0), "unsupported signal number") + } + + // This is intentonally a non-atomic store. This is safe, since hasSignals + // is only used in waitForEvents which is only called when there's a + // scheduler (and therefore there is no parallelism). + hasSignals = true + + // It's easier to implement this function in C. + tinygo_signal_enable(s) +} + +//go:linkname signal_ignore os/signal.signal_ignore +func signal_ignore(s uint32) { + if s >= 32 { + // TODO: to support higher signal numbers, we need to turn + // receivedSignals into a uint32 array. + runtimePanicAt(returnAddress(0), "unsupported signal number") + } + tinygo_signal_ignore(s) +} + +//go:linkname signal_disable os/signal.signal_disable +func signal_disable(s uint32) { + if s >= 32 { + // TODO: to support higher signal numbers, we need to turn + // receivedSignals into a uint32 array. + runtimePanicAt(returnAddress(0), "unsupported signal number") + } + tinygo_signal_disable(s) +} + +//go:linkname signal_waitUntilIdle os/signal.signalWaitUntilIdle +func signal_waitUntilIdle() { + // Wait until signal_recv has processed all signals. + for receivedSignals.Load() != 0 { + // TODO: this becomes a busy loop when using threads. + // We might want to pause until signal_recv has no more incoming signals + // to process. + Gosched() + } +} + +//export tinygo_signal_enable +func tinygo_signal_enable(s uint32) + +//export tinygo_signal_ignore +func tinygo_signal_ignore(s uint32) + +//export tinygo_signal_disable +func tinygo_signal_disable(s uint32) + +// void tinygo_signal_handler(int sig); +// +//export tinygo_signal_handler +func tinygo_signal_handler(s int32) { + // The following loop is equivalent to the following: + // + // receivedSignals.Or(uint32(1) << uint32(s)) + // + // TODO: use this instead of a loop once we drop support for Go 1.22. + for { + mask := uint32(1) << uint32(s) + val := receivedSignals.Load() + swapped := receivedSignals.CompareAndSwap(val, val|mask) + if swapped { + break + } + } + + // Notify the main thread that there was a signal. + // This will exit the call to Wait or WaitUntil early. + if signalFutex.Swap(1) == 0 { + // Changed from 0 to 1, so there may have been a waiting goroutine. + // This could be optimized to avoid a syscall when there are no waiting + // goroutines. + signalFutex.WakeAll() + } +} + +// Task waiting for a signal to arrive, or nil if it is running or there are no +// signals. +var signalRecvWaiter atomic.Pointer[task.Task] + +//go:linkname signal_recv os/signal.signal_recv +func signal_recv() uint32 { + // Function called from os/signal to get the next received signal. + for { + val := receivedSignals.Load() + if val == 0 { + // There are no signals to receive. Sleep until there are. + if signalRecvWaiter.Swap(task.Current()) != nil { + // We expect only a single goroutine to call signal_recv. + runtimePanic("signal_recv called concurrently") + } + task.Pause() + continue + } + + // Extract the lowest numbered signal number from receivedSignals. + num := uint32(bits.TrailingZeros32(val)) + + // Atomically clear the signal number from receivedSignals. + // TODO: use atomic.Uint32.And once we drop support for Go 1.22 instead + // of this loop, like so: + // + // receivedSignals.And(^(uint32(1) << num)) + // + for { + newVal := val &^ (1 << num) + swapped := receivedSignals.CompareAndSwap(val, newVal) + if swapped { + break + } + val = receivedSignals.Load() + } + + return num + } +} + +// Reactivate the goroutine waiting for signals, if there are any. +// Return true if it was reactivated (and therefore the scheduler should run +// again), and false otherwise. +func checkSignals() bool { + if receivedSignals.Load() != 0 { + if waiter := signalRecvWaiter.Swap(nil); waiter != nil { + scheduleTask(waiter) + return true + } + } + return false +} + +func waitForEvents() { + if hasSignals { + // Wait as long as the futex value is 0. + // This can happen either before or during the call to Wait. + // This can be optimized: if the value is nonzero we don't need to do a + // futex wait syscall and can instead immediately call checkSignals. + signalFutex.Wait(0) + + // Check for signals that arrived before or during the call to Wait. + // If there are any signals, the value is 0. + if signalFutex.Swap(0) != 0 { + checkSignals() + } + } else { + // The program doesn't use signals, so this is a deadlock. + runtimePanic("deadlocked: no event source") + } +} diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasip1.go similarity index 93% rename from src/runtime/runtime_wasm_wasi.go rename to src/runtime/runtime_wasip1.go index f258039ae6..92adb9bef6 100644 --- a/src/runtime/runtime_wasm_wasi.go +++ b/src/runtime/runtime_wasip1.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && (wasi || wasip1) +//go:build wasip1 package runtime @@ -13,14 +13,6 @@ type timeUnit int64 //export __wasm_call_ctors func __wasm_call_ctors() -//export _start -func _start() { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - run() -} - // Read the command line arguments from WASI. // For example, they can be passed to a program with wasmtime like this: // diff --git a/src/runtime/runtime_wasip2.go b/src/runtime/runtime_wasip2.go new file mode 100644 index 0000000000..296f4a45bd --- /dev/null +++ b/src/runtime/runtime_wasip2.go @@ -0,0 +1,54 @@ +//go:build wasip2 + +package runtime + +import ( + "unsafe" + + "internal/wasi/cli/v0.2.0/environment" + wasiclirun "internal/wasi/cli/v0.2.0/run" + monotonicclock "internal/wasi/clocks/v0.2.0/monotonic-clock" + + "internal/cm" +) + +type timeUnit int64 + +func init() { + wasiclirun.Exports.Run = func() cm.BoolResult { + callMain() + return false + } +} + +var args []string + +//go:linkname os_runtime_args os.runtime_args +func os_runtime_args() []string { + if args == nil { + args = environment.GetArguments().Slice() + } + return args +} + +//export cabi_realloc +func cabi_realloc(ptr, oldsize, align, newsize unsafe.Pointer) unsafe.Pointer { + return realloc(ptr, uintptr(newsize)) +} + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + +func sleepTicks(d timeUnit) { + p := monotonicclock.SubscribeDuration(monotonicclock.Duration(d)) + p.Block() +} + +func ticks() timeUnit { + return timeUnit(monotonicclock.Now()) +} diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index 18ca44abec..21a0bc1055 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -1,26 +1,9 @@ -//go:build wasm && !wasi && !wasip1 +//go:build wasm && !wasip1 package runtime -import "unsafe" - type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript -// wasmNested is used to detect scheduler nesting (WASM calls into JS calls back into WASM). -// When this happens, we need to use a reduced version of the scheduler. -var wasmNested bool - -//export _start -func _start() { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - - wasmNested = true - run() - wasmNested = false -} - var handleEvent func() //go:linkname setEventHandler syscall/js.setEventHandler diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go index d5e1012de4..9fd8c45541 100644 --- a/src/runtime/runtime_wasm_js_scheduler.go +++ b/src/runtime/runtime_wasm_js_scheduler.go @@ -1,4 +1,4 @@ -//go:build wasm && !wasi && !scheduler.none && !wasip1 && !wasm_unknown +//go:build wasm && !wasi && !scheduler.none && !wasip1 && !wasip2 && !wasm_unknown package runtime @@ -8,24 +8,10 @@ func resume() { handleEvent() }() - if wasmNested { - minSched() - return - } - - wasmNested = true - scheduler() - wasmNested = false + scheduler(false) } //export go_scheduler func go_scheduler() { - if wasmNested { - minSched() - return - } - - wasmNested = true - scheduler() - wasmNested = false + scheduler(false) } diff --git a/src/runtime/runtime_wasm_unknown.go b/src/runtime/runtime_wasm_unknown.go index 111bddf085..27e2485791 100644 --- a/src/runtime/runtime_wasm_unknown.go +++ b/src/runtime/runtime_wasm_unknown.go @@ -2,7 +2,8 @@ package runtime -import "unsafe" +// TODO: this is essentially reactor mode wasm. So we might want to support +// -buildmode=c-shared (and default to it). type timeUnit int64 @@ -11,20 +12,10 @@ type timeUnit int64 //export __wasm_call_ctors func __wasm_call_ctors() -//export _initialize -func _initialize() { - // These need to be initialized early so that the heap can be initialized. - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - initAll() -} - func init() { __wasm_call_ctors() } -var args []string - func ticksToNanoseconds(ticks timeUnit) int64 { return int64(ticks) } @@ -42,3 +33,9 @@ func sleepTicks(d timeUnit) { func ticks() timeUnit { return timeUnit(0) } + +func mainReturnExit() { + // Don't exit explicitly here. We can't (there is no environment with an + // exit call) but also it's not needed. We can just let _start and main.main + // return to the caller. +} diff --git a/src/runtime/runtime_wasmentry.go b/src/runtime/runtime_wasmentry.go new file mode 100644 index 0000000000..b33a19d40b --- /dev/null +++ b/src/runtime/runtime_wasmentry.go @@ -0,0 +1,99 @@ +//go:build tinygo.wasm + +package runtime + +// Entry points for WebAssembly modules, and runtime support for +// //go:wasmexport: runtime.wasmExport* function calls are inserted by the +// compiler for //go:wasmexport support. + +import ( + "internal/task" + "unsafe" +) + +// 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) + run() + 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. +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) + initHeap() + initRand() + + if hasScheduler { + // A package initializer might do funky stuff like start a goroutine and + // wait until it completes, so we have to run package initializers in a + // goroutine. + go func() { + initAll() + }() + scheduler(true) + } 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() + } +} + +// This is the _start entry point, when using -buildmode=wasi-legacy. +func wasmEntryLegacy() { + // 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) + run() +} + +// Whether the runtime was initialized by a call to _initialize or _start. +var initializeCalled bool + +func wasmExportCheckRun() { + switch { + case !initializeCalled: + runtimePanic("//go:wasmexport function called before runtime initialization") + case mainExited: + runtimePanic("//go:wasmexport function called after main.main returned") + } +} + +// Called from within a //go:wasmexport wrapper (the one that's exported from +// the wasm module) after the goroutine has been queued. Just run the scheduler, +// and check that the goroutine finished when the scheduler is idle (as required +// by the //go:wasmexport proposal). +// +// This function is not called when the scheduler is disabled. +func wasmExportRun(done *bool) { + scheduler(true) + if !*done { + runtimePanic("//go:wasmexport function did not finish") + } +} + +// Called from the goroutine wrapper for the //go:wasmexport function. It just +// signals to the runtime that the //go:wasmexport call has finished, and can +// switch back to the wasmExportRun function. +// +// This function is not called when the scheduler is disabled. +func wasmExportExit() { + task.Pause() + + // TODO: we could cache the allocated stack so we don't have to keep + // allocating a new stack on every //go:wasmexport call. +} diff --git a/src/runtime/runtime_windows.go b/src/runtime/runtime_windows.go index 97485294c5..88857fc3a5 100644 --- a/src/runtime/runtime_windows.go +++ b/src/runtime/runtime_windows.go @@ -52,7 +52,16 @@ 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. + // Exiting this way (instead of returning) also fixes an issue where not all + // output would be sent to stdout before exit. + // See: https://github.com/tinygo-org/tinygo/pull/4589 + libc_exit(0) + + // Unreachable, since we've already exited. But we need to return something + // here to make this valid Go code. return 0 } @@ -129,7 +138,7 @@ func nanosecondsToTicks(ns int64) timeUnit { } func sleepTicks(d timeUnit) { - // Calcluate milliseconds from ticks (which have a resolution of 100ns), + // Calculate milliseconds from ticks (which have a resolution of 100ns), // rounding up. milliseconds := int64(d+9_999) / 10_000 for milliseconds != 0 { diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 7367eed353..40740da310 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -1,35 +1,10 @@ package runtime -// This file implements the TinyGo scheduler. This scheduler is a very simple -// cooperative round robin scheduler, with a runqueue that contains a linked -// list of goroutines (tasks) that should be run next, in order of when they -// were added to the queue (first-in, first-out). It also contains a sleep queue -// with sleeping goroutines in order of when they should be re-activated. -// -// The scheduler is used both for the asyncify based scheduler and for the task -// based scheduler. In both cases, the 'internal/task.Task' type is used to represent one -// goroutine. - -import ( - "internal/task" - "runtime/interrupt" -) +import "internal/task" const schedulerDebug = false -// On JavaScript, we can't do a blocking sleep. Instead we have to return and -// queue a new scheduler invocation using setTimeout. -const asyncScheduler = GOOS == "js" - -var schedulerDone bool - -// Queues used by the scheduler. -var ( - runqueue task.Queue - sleepQueue *task.Task - sleepQueueBaseTime timeUnit - timerQueue *timerNode -) +var mainExited bool // Simple logging, for debugging. func scheduleLog(msg string) { @@ -52,210 +27,18 @@ func scheduleLogChan(msg string, ch *channel, t *task.Task) { } } -// deadlock is called when a goroutine cannot proceed any more, but is in theory -// not exited (so deferred calls won't run). This can happen for example in code -// like this, that blocks forever: -// -// select{} -// -//go:noinline -func deadlock() { - // call yield without requesting a wakeup - task.Pause() - panic("unreachable") -} - // Goexit terminates the currently running goroutine. No other goroutines are affected. -// -// Unlike the main Go implementation, no deffered calls will be run. -// -//go:inline func Goexit() { - // its really just a deadlock - deadlock() -} - -// Add this task to the end of the run queue. -func runqueuePushBack(t *task.Task) { - runqueue.Push(t) -} - -// Add this task to the sleep queue, assuming its state is set to sleeping. -func addSleepTask(t *task.Task, duration timeUnit) { - if schedulerDebug { - println(" set sleep:", t, duration) - if t.Next != nil { - panic("runtime: addSleepTask: expected next task to be nil") - } - } - t.Data = uint64(duration) - now := ticks() - if sleepQueue == nil { - scheduleLog(" -> sleep new queue") - - // set new base time - sleepQueueBaseTime = now - } - - // Add to sleep queue. - q := &sleepQueue - for ; *q != nil; q = &(*q).Next { - if t.Data < (*q).Data { - // this will finish earlier than the next - insert here - break - } else { - // this will finish later - adjust delay - t.Data -= (*q).Data - } - } - if *q != nil { - // cut delay time between this sleep task and the next - (*q).Data -= t.Data - } - t.Next = *q - *q = t + panicOrGoexit(nil, panicGoexit) } -// addTimer adds the given timer node to the timer queue. It must not be in the -// queue already. -// This function is very similar to addSleepTask but for timerQueue instead of -// sleepQueue. -func addTimer(tim *timerNode) { - mask := interrupt.Disable() - - // Add to timer queue. - q := &timerQueue - for ; *q != nil; q = &(*q).next { - if tim.whenTicks() < (*q).whenTicks() { - // this will finish earlier than the next - insert here - break - } - } - tim.next = *q - *q = tim - interrupt.Restore(mask) -} - -// removeTimer is the implementation of time.stopTimer. It removes a timer from -// the timer queue, returning true if the timer is present in the timer queue. -func removeTimer(tim *timer) bool { - removedTimer := false - mask := interrupt.Disable() - for t := &timerQueue; *t != nil; t = &(*t).next { - if (*t).timer == tim { - scheduleLog("removed timer") - *t = (*t).next - removedTimer = true - break - } - } - if !removedTimer { - scheduleLog("did not remove timer") - } - interrupt.Restore(mask) - return removedTimer -} - -// Run the scheduler until all tasks have finished. -func scheduler() { - // Main scheduler loop. - var now timeUnit - for !schedulerDone { - scheduleLog("") - scheduleLog(" schedule") - if sleepQueue != nil || timerQueue != nil { - now = ticks() - } - - // Add tasks that are done sleeping to the end of the runqueue so they - // will be executed soon. - if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) { - t := sleepQueue - scheduleLogTask(" awake:", t) - sleepQueueBaseTime += timeUnit(t.Data) - sleepQueue = t.Next - t.Next = nil - runqueue.Push(t) - } - - // Check for expired timers to trigger. - if timerQueue != nil && now >= timerQueue.whenTicks() { - scheduleLog("--- timer awoke") - // Pop timer from queue. - tn := timerQueue - timerQueue = tn.next - tn.next = nil - // Run the callback stored in this timer node. - tn.callback(tn) - } - - t := runqueue.Pop() - if t == nil { - if sleepQueue == nil && timerQueue == nil { - if asyncScheduler { - // JavaScript is treated specially, see below. - return - } - waitForEvents() - continue - } - - var timeLeft timeUnit - if sleepQueue != nil { - timeLeft = timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime) - } - if timerQueue != nil { - timeLeftForTimer := timerQueue.whenTicks() - now - if sleepQueue == nil || timeLeftForTimer < timeLeft { - timeLeft = timeLeftForTimer - } - } - - if schedulerDebug { - println(" sleeping...", sleepQueue, uint(timeLeft)) - for t := sleepQueue; t != nil; t = t.Next { - println(" task sleeping:", t, timeUnit(t.Data)) - } - for tim := timerQueue; tim != nil; tim = tim.next { - 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 - } - continue - } - - // Run the given task. - scheduleLogTask(" run:", t) - t.Resume() - } -} - -// This horrible hack exists to make WASM work properly. -// When a WASM program calls into JS which calls back into WASM, the event with which we called back in needs to be handled before returning. -// Thus there are two copies of the scheduler running at once. -// This is a reduced version of the scheduler which does not deal with the timer queue (that is a problem for the outer scheduler). -func minSched() { - scheduleLog("start nested scheduler") - for !schedulerDone { - t := runqueue.Pop() - if t == nil { - break - } - - scheduleLogTask(" run:", t) - t.Resume() - } - scheduleLog("stop nested scheduler") +//go:linkname fips_getIndicator crypto/internal/fips140.getIndicator +func fips_getIndicator() uint8 { + return task.Current().FipsIndicator } -func Gosched() { - runqueue.Push(task.Current()) - task.Pause() +//go:linkname fips_setIndicator crypto/internal/fips140.setIndicator +func fips_setIndicator(indicator uint8) { + // This indicator is stored per goroutine. + task.Current().FipsIndicator = indicator } diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go deleted file mode 100644 index 0911a2dc73..0000000000 --- a/src/runtime/scheduler_any.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build !scheduler.none - -package runtime - -import "internal/task" - -// Pause the current task for a given time. -// -//go:linkname sleep time.Sleep -func sleep(duration int64) { - if duration <= 0 { - return - } - - addSleepTask(task.Current(), nanosecondsToTicks(duration)) - task.Pause() -} - -// run is called by the program entry point to execute the go program. -// With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. -func run() { - initHeap() - go func() { - initAll() - callMain() - schedulerDone = true - }() - scheduler() -} - -const hasScheduler = true diff --git a/src/runtime/scheduler_cooperative.go b/src/runtime/scheduler_cooperative.go new file mode 100644 index 0000000000..9f80e060ce --- /dev/null +++ b/src/runtime/scheduler_cooperative.go @@ -0,0 +1,257 @@ +//go:build scheduler.tasks || scheduler.asyncify + +package runtime + +// This file implements the TinyGo scheduler. This scheduler is a very simple +// cooperative round robin scheduler, with a runqueue that contains a linked +// list of goroutines (tasks) that should be run next, in order of when they +// were added to the queue (first-in, first-out). It also contains a sleep queue +// with sleeping goroutines in order of when they should be re-activated. +// +// The scheduler is used both for the asyncify based scheduler and for the task +// based scheduler. In both cases, the 'internal/task.Task' type is used to represent one +// goroutine. + +import ( + "internal/task" + "runtime/interrupt" +) + +// On JavaScript, we can't do a blocking sleep. Instead we have to return and +// queue a new scheduler invocation using setTimeout. +const asyncScheduler = GOOS == "js" + +const hasScheduler = true + +// Concurrency is not parallelism. While the cooperative scheduler has +// concurrency, it does not have parallelism. +const hasParallelism = false + +// Queues used by the scheduler. +var ( + runqueue task.Queue + sleepQueue *task.Task + sleepQueueBaseTime timeUnit + timerQueue *timerNode +) + +// deadlock is called when a goroutine cannot proceed any more, but is in theory +// not exited (so deferred calls won't run). This can happen for example in code +// like this, that blocks forever: +// +// select{} +// +//go:noinline +func deadlock() { + // call yield without requesting a wakeup + task.Pause() + panic("unreachable") +} + +// Add this task to the end of the run queue. +func scheduleTask(t *task.Task) { + runqueue.Push(t) +} + +func Gosched() { + runqueue.Push(task.Current()) + task.Pause() +} + +// Add this task to the sleep queue, assuming its state is set to sleeping. +func addSleepTask(t *task.Task, duration timeUnit) { + if schedulerDebug { + println(" set sleep:", t, duration) + if t.Next != nil { + panic("runtime: addSleepTask: expected next task to be nil") + } + } + t.Data = uint64(duration) + now := ticks() + if sleepQueue == nil { + scheduleLog(" -> sleep new queue") + + // set new base time + sleepQueueBaseTime = now + } + + // Add to sleep queue. + q := &sleepQueue + for ; *q != nil; q = &(*q).Next { + if t.Data < (*q).Data { + // this will finish earlier than the next - insert here + break + } else { + // this will finish later - adjust delay + t.Data -= (*q).Data + } + } + if *q != nil { + // cut delay time between this sleep task and the next + (*q).Data -= t.Data + } + t.Next = *q + *q = t +} + +// addTimer adds the given timer node to the timer queue. It must not be in the +// queue already. +// This function is very similar to addSleepTask but for timerQueue instead of +// sleepQueue. +func addTimer(tim *timerNode) { + mask := interrupt.Disable() + + // Add to timer queue. + q := &timerQueue + for ; *q != nil; q = &(*q).next { + if tim.whenTicks() < (*q).whenTicks() { + // this will finish earlier than the next - insert here + break + } + } + tim.next = *q + *q = tim + interrupt.Restore(mask) +} + +// removeTimer is the implementation of time.stopTimer. It removes a timer from +// the timer queue, returning true if the timer is present in the timer queue. +func removeTimer(tim *timer) bool { + removedTimer := false + mask := interrupt.Disable() + for t := &timerQueue; *t != nil; t = &(*t).next { + if (*t).timer == tim { + scheduleLog("removed timer") + *t = (*t).next + removedTimer = true + break + } + } + if !removedTimer { + scheduleLog("did not remove timer") + } + interrupt.Restore(mask) + return removedTimer +} + +func schedulerRunQueue() *task.Queue { + return &runqueue +} + +// Run the scheduler until all tasks have finished. +// There are a few special cases: +// - When returnAtDeadlock is true, it also returns when there are no more +// runnable goroutines. +// - When using the asyncify scheduler, it returns when it has to wait +// (JavaScript uses setTimeout so the scheduler must return to the JS +// environment). +func scheduler(returnAtDeadlock bool) { + // Main scheduler loop. + var now timeUnit + for !mainExited { + scheduleLog("") + scheduleLog(" schedule") + if sleepQueue != nil || timerQueue != nil { + now = ticks() + } + + // Add tasks that are done sleeping to the end of the runqueue so they + // will be executed soon. + if sleepQueue != nil && now-sleepQueueBaseTime >= timeUnit(sleepQueue.Data) { + t := sleepQueue + scheduleLogTask(" awake:", t) + sleepQueueBaseTime += timeUnit(t.Data) + sleepQueue = t.Next + t.Next = nil + runqueue.Push(t) + } + + // Check for expired timers to trigger. + if timerQueue != nil && now >= timerQueue.whenTicks() { + scheduleLog("--- timer awoke") + delay := ticksToNanoseconds(now - timerQueue.whenTicks()) + // Pop timer from queue. + tn := timerQueue + timerQueue = tn.next + tn.next = nil + // Run the callback stored in this timer node. + tn.callback(tn, delay) + } + + t := runqueue.Pop() + if t == nil { + if sleepQueue == nil && timerQueue == nil { + if returnAtDeadlock { + return + } + if asyncScheduler { + // JavaScript is treated specially, see below. + return + } + waitForEvents() + continue + } + + var timeLeft timeUnit + if sleepQueue != nil { + timeLeft = timeUnit(sleepQueue.Data) - (now - sleepQueueBaseTime) + } + if timerQueue != nil { + timeLeftForTimer := timerQueue.whenTicks() - now + if sleepQueue == nil || timeLeftForTimer < timeLeft { + timeLeft = timeLeftForTimer + } + } + + if schedulerDebug { + println(" sleeping...", sleepQueue, uint(timeLeft)) + for t := sleepQueue; t != nil; t = t.Next { + println(" task sleeping:", t, timeUnit(t.Data)) + } + for tim := timerQueue; tim != nil; tim = tim.next { + println("--- timer waiting:", tim, tim.whenTicks()) + } + } + if timeLeft > 0 { + sleepTicks(timeLeft) + if asyncScheduler { + // The sleepTicks function above only sets a timeout at + // which point the scheduler will be called again. It does + // not really sleep. So instead of sleeping, we return and + // expect to be called again. + break + } + } + continue + } + + // Run the given task. + scheduleLogTask(" run:", t) + t.Resume() + } +} + +// Pause the current task for a given time. +// +//go:linkname sleep time.Sleep +func sleep(duration int64) { + if duration <= 0 { + return + } + + addSleepTask(task.Current(), nanosecondsToTicks(duration)) + task.Pause() +} + +// run is called by the program entry point to execute the go program. +// With a scheduler, init and the main function are invoked in a goroutine before starting the scheduler. +func run() { + initHeap() + initRand() + go func() { + initAll() + callMain() + mainExited = true + }() + scheduler(false) +} diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index 5079a80853..25bd7eb9c1 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -2,6 +2,23 @@ package runtime +import "internal/task" + +const hasScheduler = false + +// No goroutines are allowed, so there's no parallelism anywhere. +const hasParallelism = false + +// run is called by the program entry point to execute the go program. +// With the "none" scheduler, init and the main function are invoked directly. +func run() { + initHeap() + initRand() + initAll() + callMain() + mainExited = true +} + //go:linkname sleep time.Sleep func sleep(duration int64) { if duration <= 0 { @@ -11,18 +28,43 @@ func sleep(duration int64) { sleepTicks(nanosecondsToTicks(duration)) } +func deadlock() { + // The only goroutine available is deadlocked. + runtimePanic("all goroutines are asleep - deadlock!") +} + +func scheduleTask(t *task.Task) { + // Pause() will panic, so this should not be reachable. +} + +func Gosched() { + // There are no other goroutines, so there's nothing to schedule. +} + +func addTimer(tim *timerNode) { + runtimePanic("timers not supported without a scheduler") +} + +func removeTimer(tim *timer) bool { + runtimePanic("timers not supported without a scheduler") + return false +} + +func schedulerRunQueue() *task.Queue { + // This function is not actually used, it is only called when hasScheduler + // is true. + runtimePanic("unreachable: no runqueue without a scheduler") + return nil +} + +func scheduler(returnAtDeadlock bool) { + // The scheduler should never be run when using -scheduler=none. Meaning, + // this code should be unreachable. + runtimePanic("unreachable: scheduler must not be called with the 'none' scheduler") +} + // getSystemStackPointer returns the current stack pointer of the system stack. // This is always the current stack pointer. func getSystemStackPointer() uintptr { return getCurrentStackPointer() } - -// run is called by the program entry point to execute the go program. -// With the "none" scheduler, init and the main function are invoked directly. -func run() { - initHeap() - initAll() - callMain() -} - -const hasScheduler = false diff --git a/src/runtime/signal.c b/src/runtime/signal.c new file mode 100644 index 0000000000..87af43011e --- /dev/null +++ b/src/runtime/signal.c @@ -0,0 +1,32 @@ +//go:build none + +// Ignore the //go:build above. This file is manually included on Linux and +// MacOS to provide os/signal support. + +#include +#include +#include +#include + +// Signal handler in the runtime. +void tinygo_signal_handler(int sig); + +// Enable a signal from the runtime. +void tinygo_signal_enable(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_handler = &tinygo_signal_handler; + act.sa_flags = SA_RESTART; + sigaction(sig, &act, NULL); +} + +void tinygo_signal_ignore(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_handler = SIG_IGN; + sigaction(sig, &act, NULL); +} + +void tinygo_signal_disable(uint32_t sig) { + struct sigaction act = { 0 }; + act.sa_handler = SIG_DFL; + sigaction(sig, &act, NULL); +} diff --git a/src/runtime/slice.go b/src/runtime/slice.go index 2269047a8c..c9603643a7 100644 --- a/src/runtime/slice.go +++ b/src/runtime/slice.go @@ -3,42 +3,25 @@ package runtime // This file implements compiler builtins for slices: append() and copy(). import ( + "internal/gclayout" + "math/bits" "unsafe" ) // Builtin append(src, elements...) function: append elements to src and return // the modified (possibly expanded) slice. -func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen uintptr, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { - if elemsLen == 0 { - // Nothing to append, return the input slice. - return srcBuf, srcLen, srcCap - } - - if srcLen+elemsLen > srcCap { - // Slice does not fit, allocate a new buffer that's large enough. - srcCap = srcCap * 2 - if srcCap == 0 { // e.g. zero slice - srcCap = 1 - } - for srcLen+elemsLen > srcCap { - // This algorithm may be made more memory-efficient: don't multiply - // by two but by 1.5 or something. As far as I can see, that's - // allowed by the Go language specification (but may be observed by - // programs). - srcCap *= 2 - } - buf := alloc(srcCap*elemSize, nil) +func sliceAppend(srcBuf, elemsBuf unsafe.Pointer, srcLen, srcCap, elemsLen, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { + newLen := srcLen + elemsLen + if elemsLen > 0 { + // Allocate a new slice with capacity for elemsLen more elements, if necessary; + // otherwise, reuse the passed slice. + srcBuf, _, srcCap = sliceGrow(srcBuf, srcLen, srcCap, newLen, elemSize) - // Copy the old slice to the new slice. - if srcLen != 0 { - memmove(buf, srcBuf, srcLen*elemSize) - } - srcBuf = buf + // Append the new elements in-place. + memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize) } - // The slice fits (after possibly allocating a new one), append it in-place. - memmove(unsafe.Add(srcBuf, srcLen*elemSize), elemsBuf, elemsLen*elemSize) - return srcBuf, srcLen + elemsLen, srcCap + return srcBuf, newLen, srcCap } // Builtin copy(dst, src) function: copy bytes from dst to src. @@ -54,29 +37,28 @@ func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr // sliceGrow returns a new slice with space for at least newCap elements func sliceGrow(oldBuf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) { - - // TODO(dgryski): sliceGrow() and sliceAppend() should be refactored to share the base growth code. - if oldCap >= newCap { // No need to grow, return the input slice. return oldBuf, oldLen, oldCap } - // allow nil slice - if oldCap == 0 { - oldCap++ - } + // This can be made more memory-efficient by multiplying by some other constant, such as 1.5, + // which seems to be allowed by the Go language specification (but this can be observed by + // programs); however, due to memory fragmentation and the current state of the TinyGo + // memory allocators, this causes some difficult to debug issues. + newCap = 1 << bits.Len(uint(newCap)) - // grow capacity - for oldCap < newCap { - oldCap *= 2 + var layout unsafe.Pointer + // less type info here; can only go off element size + if elemSize < unsafe.Sizeof(uintptr(0)) { + layout = gclayout.NoPtrs } - buf := alloc(oldCap*elemSize, nil) + buf := alloc(newCap*elemSize, layout) if oldLen > 0 { // copy any data to new slice memmove(buf, oldBuf, oldLen*elemSize) } - return buf, oldLen, oldCap + return buf, oldLen, newCap } diff --git a/src/runtime/string.go b/src/runtime/string.go index 13bfcd0ed2..54485dfc29 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -3,6 +3,7 @@ package runtime // This file implements functions related to Go strings. import ( + "internal/gclayout" "unsafe" ) @@ -59,7 +60,7 @@ func stringConcat(x, y _string) _string { return x } else { length := x.length + y.length - buf := alloc(length, nil) + buf := alloc(length, gclayout.NoPtrs) memcpy(buf, unsafe.Pointer(x.ptr), x.length) memcpy(unsafe.Add(buf, x.length), unsafe.Pointer(y.ptr), y.length) return _string{ptr: (*byte)(buf), length: length} @@ -72,7 +73,7 @@ func stringFromBytes(x struct { len uintptr cap uintptr }) _string { - buf := alloc(x.len, nil) + buf := alloc(x.len, gclayout.NoPtrs) memcpy(buf, unsafe.Pointer(x.ptr), x.len) return _string{ptr: (*byte)(buf), length: x.len} } @@ -83,7 +84,7 @@ func stringToBytes(x _string) (slice struct { len uintptr cap uintptr }) { - buf := alloc(x.length, nil) + buf := alloc(x.length, gclayout.NoPtrs) memcpy(buf, unsafe.Pointer(x.ptr), x.length) slice.ptr = (*byte)(buf) slice.len = x.length @@ -100,7 +101,7 @@ func stringFromRunes(runeSlice []rune) (s _string) { } // Allocate memory for the string. - s.ptr = (*byte)(alloc(s.length, nil)) + s.ptr = (*byte)(alloc(s.length, gclayout.NoPtrs)) // Encode runes to UTF-8 and store the resulting bytes in the string. index := uintptr(0) @@ -283,3 +284,10 @@ func cgo_GoBytes(ptr unsafe.Pointer, length uintptr) []byte { } return buf } + +func cgo_CBytes(b []byte) unsafe.Pointer { + p := malloc(uintptr(len(b))) + s := unsafe.Slice((*byte)(p), len(b)) + copy(s, b) + return p +} diff --git a/src/runtime/synctest.go b/src/runtime/synctest.go new file mode 100644 index 0000000000..fa11c991fc --- /dev/null +++ b/src/runtime/synctest.go @@ -0,0 +1,15 @@ +package runtime + +// Dummy implementation of synctest functions (we don't support synctest at the +// moment). + +//go:linkname synctest_acquire internal/synctest.acquire +func synctest_acquire() any { + // Dummy: we don't support synctest. + return nil +} + +//go:linkname synctest_release internal/synctest.release +func synctest_release(sg any) { + // Dummy: we don't support synctest. +} diff --git a/src/runtime/time.go b/src/runtime/time.go index 4fa3a418b5..3935b4486e 100644 --- a/src/runtime/time.go +++ b/src/runtime/time.go @@ -1,10 +1,23 @@ package runtime +//go:linkname time_runtimeNano time.runtimeNano +func time_runtimeNano() int64 { + // Note: we're ignoring sync groups here (package testing/synctest). + // See: https://github.com/golang/go/issues/67434 + return nanotime() +} + +//go:linkname time_runtimeNow time.runtimeNow +func time_runtimeNow() (sec int64, nsec int32, mono int64) { + // Also ignoring the sync group here, like time_runtimeNano above. + return now() +} + // timerNode is an element in a linked list of timers. type timerNode struct { next *timerNode timer *timer - callback func(*timerNode) + callback func(node *timerNode, delta int64) } // whenTicks returns the (absolute) time when this timer should trigger next. @@ -12,17 +25,6 @@ func (t *timerNode) whenTicks() timeUnit { return nanosecondsToTicks(t.timer.when) } -// Defined in the time package, implemented here in the runtime. -// -//go:linkname startTimer time.startTimer -func startTimer(tim *timer) { - addTimer(&timerNode{ - timer: tim, - callback: timerCallback, - }) - scheduleLog("adding timer") -} - // timerCallback is called when a timer expires. It makes sure to call the // callback in the time package and to re-add the timer to the queue if this is // a ticker (repeating timer). @@ -32,11 +34,11 @@ func startTimer(tim *timer) { // dependency causes timerQueue not to get optimized away. // If timerQueue doesn't get optimized away, small programs (that don't call // time.NewTimer etc) would still pay the cost of these timers. -func timerCallback(tn *timerNode) { +func timerCallback(tn *timerNode, delta int64) { // Run timer function (implemented in the time package). // The seq parameter to the f function is not used in the time // package so is left zero. - tn.timer.f(tn.timer.arg, 0) + tn.timer.callCallback(delta) // If this is a periodic timer (a ticker), re-add it to the queue. if tn.timer.period != 0 { @@ -44,16 +46,3 @@ func timerCallback(tn *timerNode) { addTimer(tn) } } - -//go:linkname stopTimer time.stopTimer -func stopTimer(tim *timer) bool { - return removeTimer(tim) -} - -//go:linkname resetTimer time.resetTimer -func resetTimer(tim *timer, when int64) bool { - tim.when = when - removed := removeTimer(tim) - startTimer(tim) - return removed -} diff --git a/src/runtime/timer.go b/src/runtime/time_go122.go similarity index 60% rename from src/runtime/timer.go rename to src/runtime/time_go122.go index 134f9b9ac9..2994c27220 100644 --- a/src/runtime/timer.go +++ b/src/runtime/time_go122.go @@ -1,9 +1,13 @@ +//go:build !go1.23 + // Portions 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. package runtime +// Time functions for Go 1.22 and below. + type puintptr uintptr // Package time knows the layout of this structure. @@ -31,3 +35,31 @@ type timer struct { // The status field holds one of the values below. status uint32 } + +func (tim *timer) callCallback(delta int64) { + tim.f(tim.arg, 0) +} + +// Defined in the time package, implemented here in the runtime. +// +//go:linkname startTimer time.startTimer +func startTimer(tim *timer) { + addTimer(&timerNode{ + timer: tim, + callback: timerCallback, + }) + scheduleLog("adding timer") +} + +//go:linkname stopTimer time.stopTimer +func stopTimer(tim *timer) bool { + return removeTimer(tim) +} + +//go:linkname resetTimer time.resetTimer +func resetTimer(tim *timer, when int64) bool { + tim.when = when + removed := removeTimer(tim) + startTimer(tim) + return removed +} diff --git a/src/runtime/time_go123.go b/src/runtime/time_go123.go new file mode 100644 index 0000000000..cfef4d3934 --- /dev/null +++ b/src/runtime/time_go123.go @@ -0,0 +1,68 @@ +//go:build go1.23 + +package runtime + +import "unsafe" + +// Time functions for Go 1.23 and above. + +// This is the timer that's used internally inside the runtime. +type timer struct { + // When to call the timer, and the interval for the ticker. + when int64 + period int64 + + // Callback from the time package. + f func(arg any, seq uintptr, delta int64) + arg any +} + +func (tim *timer) callCallback(delta int64) { + tim.f(tim.arg, 0, delta) +} + +// This is the struct used internally in the runtime. The first two fields are +// the same as time.Timer and time.Ticker so it can be used as-is in the time +// package. +type timeTimer struct { + c unsafe.Pointer // <-chan time.Time + init bool + timer +} + +//go:linkname newTimer time.newTimer +func newTimer(when, period int64, f func(arg any, seq uintptr, delta int64), arg any, c unsafe.Pointer) *timeTimer { + tim := &timeTimer{ + c: c, + init: true, + timer: timer{ + when: when, + period: period, + f: f, + arg: arg, + }, + } + scheduleLog("new timer") + addTimer(&timerNode{ + timer: &tim.timer, + callback: timerCallback, + }) + return tim +} + +//go:linkname stopTimer time.stopTimer +func stopTimer(tim *timeTimer) bool { + return removeTimer(&tim.timer) +} + +//go:linkname resetTimer time.resetTimer +func resetTimer(t *timeTimer, when, period int64) bool { + t.timer.when = when + t.timer.period = period + removed := removeTimer(&t.timer) + addTimer(&timerNode{ + timer: &t.timer, + callback: timerCallback, + }) + return removed +} diff --git a/src/runtime/trace/trace.go b/src/runtime/trace/trace.go index 31cdbc33cb..9d5edad866 100644 --- a/src/runtime/trace/trace.go +++ b/src/runtime/trace/trace.go @@ -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 +} diff --git a/src/runtime/wait_other.go b/src/runtime/wait_other.go index b51d4b64b6..f1487e3969 100644 --- a/src/runtime/wait_other.go +++ b/src/runtime/wait_other.go @@ -1,4 +1,4 @@ -//go:build !tinygo.riscv && !cortexm +//go:build !tinygo.riscv && !cortexm && !(linux && !baremetal && !tinygo.wasm && !nintendoswitch) && !darwin package runtime diff --git a/src/runtime/zero_new_alloc.go b/src/runtime/zero_new_alloc.go new file mode 100644 index 0000000000..b94190ae0a --- /dev/null +++ b/src/runtime/zero_new_alloc.go @@ -0,0 +1,12 @@ +//go:build gc.leaking && (baremetal || nintendoswitch) + +package runtime + +import ( + "unsafe" +) + +//go:inline +func zero_new_alloc(ptr unsafe.Pointer, size uintptr) { + memzero(ptr, size) +} diff --git a/src/runtime/zero_new_alloc_noop.go b/src/runtime/zero_new_alloc_noop.go new file mode 100644 index 0000000000..79fefc199b --- /dev/null +++ b/src/runtime/zero_new_alloc_noop.go @@ -0,0 +1,14 @@ +//go:build gc.leaking && !baremetal && !nintendoswitch + +package runtime + +import ( + "unsafe" +) + +//go:inline +func zero_new_alloc(ptr unsafe.Pointer, size uintptr) { + // Wasm linear memory is initialized to zero by default, so + // there's no need to do anything. This is also the case for + // fresh-allocated memory from an mmap() system call. +} diff --git a/src/sync/cond.go b/src/sync/cond.go index e65e86ed1b..139d8e0229 100644 --- a/src/sync/cond.go +++ b/src/sync/cond.go @@ -1,19 +1,26 @@ package sync -import "internal/task" +import ( + "internal/task" + "unsafe" +) + +// Condition variable. +// A goroutine that called Wait() can be in one of a few states depending on the +// Task.Data field: +// - When entering Wait, and before going to sleep, the data field is 0. +// - When the goroutine that calls Wait changes its data value from 0 to 1, it +// is going to sleep. It has not been awoken early. +// - When instead a call to Signal or Broadcast can change the data field from 0 +// to 1, it will _not_ go to sleep but be signalled early. +// This can happen when a concurrent call to Signal happens, or the Unlock +// function calls Signal for some reason. type Cond struct { L Locker - unlocking *earlySignal - blocked task.Stack -} - -// earlySignal is a type used to implement a stack for signalling waiters while they are unlocking. -type earlySignal struct { - next *earlySignal - - signaled bool + blocked task.Stack + lock task.PMutex } func NewCond(l Locker) *Cond { @@ -24,14 +31,14 @@ func (c *Cond) trySignal() bool { // Pop a blocked task off of the stack, and schedule it if applicable. t := c.blocked.Pop() if t != nil { - scheduleTask(t) - return true - } - - // If there any tasks which are currently unlocking, signal one. - if c.unlocking != nil { - c.unlocking.signaled = true - c.unlocking = c.unlocking.next + dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data)) + + // The data value is 0 when the task is not yet sleeping, and 1 when it is. + if dataPtr.Swap(1) != 0 { + // The value was already 1, so the task went to sleep (or is about to go + // to sleep). Schedule the task to be resumed. + scheduleTask(t) + } return true } @@ -40,21 +47,29 @@ func (c *Cond) trySignal() bool { } func (c *Cond) Signal() { + c.lock.Lock() c.trySignal() + c.lock.Unlock() } func (c *Cond) Broadcast() { // Signal everything. + c.lock.Lock() for c.trySignal() { } + c.lock.Unlock() } func (c *Cond) Wait() { - // Add an earlySignal frame to the stack so we can be signalled while unlocking. - early := earlySignal{ - next: c.unlocking, - } - c.unlocking = &early + // Mark us as not yet signalled or sleeping. + t := task.Current() + dataPtr := (*task.Uint32)(unsafe.Pointer(&t.Data)) + dataPtr.Store(0) + + // Add us to the list of waiting goroutines. + c.lock.Lock() + c.blocked.Push(t) + c.lock.Unlock() // Temporarily unlock L. c.L.Unlock() @@ -63,22 +78,17 @@ func (c *Cond) Wait() { defer c.L.Lock() // If we were signaled while unlocking, immediately complete. - if early.signaled { + if dataPtr.Swap(1) != 0 { + // The data value was already 1, so we got a signal already (and weren't + // scheduled because trySignal was the first to change the value). return } - // Remove the earlySignal frame. - prev := c.unlocking - for prev != nil && prev.next != &early { - prev = prev.next - } - if prev != nil { - prev.next = early.next - } else { - c.unlocking = early.next - } - - // Wait for a signal. - c.blocked.Push(task.Current()) + // We were the first to change the value from 0 to 1, meaning we did not get + // a signal during the call to Unlock(). So we wait until we do get a + // signal. task.Pause() } + +//go:linkname scheduleTask runtime.scheduleTask +func scheduleTask(*task.Task) diff --git a/src/sync/map.go b/src/sync/map.go index f27450ce5e..cd8a1967d6 100644 --- a/src/sync/map.go +++ b/src/sync/map.go @@ -1,10 +1,12 @@ package sync +import "internal/task" + // This file implements just enough of sync.Map to get packages to compile. It // is no more efficient than a map with a lock. type Map struct { - lock Mutex + lock task.PMutex m map[interface{}]interface{} } diff --git a/src/sync/map_go123.go b/src/sync/map_go123.go new file mode 100644 index 0000000000..b7bd61e103 --- /dev/null +++ b/src/sync/map_go123.go @@ -0,0 +1,13 @@ +//go:build go1.23 + +package sync + +// Go 1.23 added the Clear() method. The clear() function is added in Go 1.21, +// so this method can be moved to map.go once we drop support for Go 1.20 and +// below. + +func (m *Map) Clear() { + m.lock.Lock() + defer m.lock.Unlock() + clear(m.m) +} diff --git a/src/sync/mutex.go b/src/sync/mutex.go index 59f320d5d7..08c674d7ea 100644 --- a/src/sync/mutex.go +++ b/src/sync/mutex.go @@ -2,70 +2,9 @@ package sync import ( "internal/task" - _ "unsafe" - - "runtime/volatile" ) -type Mutex struct { - state uint8 // Set to non-zero if locked. - blocked task.Stack -} - -//go:linkname scheduleTask runtime.runqueuePushBack -func scheduleTask(*task.Task) - -func (m *Mutex) Lock() { - if m.islocked() { - // Push self onto stack of blocked tasks, and wait to be resumed. - m.blocked.Push(task.Current()) - task.Pause() - return - } - - m.setlock(true) -} - -func (m *Mutex) Unlock() { - if !m.islocked() { - panic("sync: unlock of unlocked Mutex") - } - - // Wake up a blocked task, if applicable. - if t := m.blocked.Pop(); t != nil { - scheduleTask(t) - } else { - m.setlock(false) - } -} - -// TryLock tries to lock m and reports whether it succeeded. -// -// Note that while correct uses of TryLock do exist, they are rare, -// and use of TryLock is often a sign of a deeper problem -// in a particular use of mutexes. -func (m *Mutex) TryLock() bool { - if m.islocked() { - return false - } - m.Lock() - return true -} - -func (m *Mutex) islocked() bool { - return volatile.LoadUint8(&m.state) != 0 -} - -func (m *Mutex) setlock(b bool) { - volatile.StoreUint8(&m.state, boolToU8(b)) -} - -func boolToU8(b bool) uint8 { - if b { - return 1 - } - return 0 -} +type Mutex = task.Mutex type RWMutex struct { // waitingWriters are all of the tasks waiting for write locks. diff --git a/src/sync/mutex_test.go b/src/sync/mutex_test.go index accb01c975..be24d93031 100644 --- a/src/sync/mutex_test.go +++ b/src/sync/mutex_test.go @@ -120,7 +120,7 @@ func TestRWMutexUncontended(t *testing.T) { mu.Lock() mu.Unlock() - // Acuire several read locks. + // Acquire several read locks. const n = 5 for i := 0; i < n; i++ { mu.RLock() @@ -196,7 +196,7 @@ func TestRWMutexWriteToRead(t *testing.T) { } } -// TestRWMutexWriteToRead tests the transition from a read lock to a write lock while contended. +// TestRWMutexReadToWrite tests the transition from a read lock to a write lock while contended. func TestRWMutexReadToWrite(t *testing.T) { // Create a new RWMutex and read-lock it several times. const n = 3 diff --git a/src/sync/pool.go b/src/sync/pool.go index 41f06a65ce..5a29afebda 100644 --- a/src/sync/pool.go +++ b/src/sync/pool.go @@ -1,18 +1,24 @@ package sync +import "internal/task" + // Pool is a very simple implementation of sync.Pool. type Pool struct { + lock task.PMutex New func() interface{} items []interface{} } // Get returns an item in the pool, or the value of calling Pool.New() if there are no items. func (p *Pool) Get() interface{} { + p.lock.Lock() if len(p.items) > 0 { x := p.items[len(p.items)-1] p.items = p.items[:len(p.items)-1] + p.lock.Unlock() return x } + p.lock.Unlock() if p.New == nil { return nil } @@ -21,5 +27,7 @@ func (p *Pool) Get() interface{} { // Put adds a value back into the pool. func (p *Pool) Put(x interface{}) { + p.lock.Lock() p.items = append(p.items, x) + p.lock.Unlock() } diff --git a/src/sync/waitgroup.go b/src/sync/waitgroup.go index 72ef24c809..40306d9327 100644 --- a/src/sync/waitgroup.go +++ b/src/sync/waitgroup.go @@ -3,35 +3,65 @@ package sync import "internal/task" type WaitGroup struct { - counter uint - waiters task.Stack + futex task.Futex } func (wg *WaitGroup) Add(delta int) { - if delta > 0 { - // Check for overflow. - if uint(delta) > (^uint(0))-wg.counter { - panic("sync: WaitGroup counter overflowed") - } + switch { + case delta > 0: + // Delta is positive. + for { + // Check for overflow. + counter := wg.futex.Load() + if uint32(delta) > (^uint32(0))-counter { + panic("sync: WaitGroup counter overflowed") + } - // Add to the counter. - wg.counter += uint(delta) - } else { - // Check for underflow. - if uint(-delta) > wg.counter { - panic("sync: negative WaitGroup counter") + // Add to the counter. + if wg.futex.CompareAndSwap(counter, counter+uint32(delta)) { + // Successfully added. + return + } } + default: + // Delta is negative (or zero). + for { + counter := wg.futex.Load() - // Subtract from the counter. - wg.counter -= uint(-delta) + // Check for underflow. + if uint32(-delta) > counter { + panic("sync: negative WaitGroup counter") + } + + // Subtract from the counter. + if !wg.futex.CompareAndSwap(counter, counter-uint32(-delta)) { + // Could not swap, trying again. + continue + } - // If the counter is zero, everything is done and the waiters should be resumed. - // This code assumes that the waiters cannot wake up until after this function returns. - // In the current implementation, this is always correct. - if wg.counter == 0 { - for t := wg.waiters.Pop(); t != nil; t = wg.waiters.Pop() { - scheduleTask(t) + // If the counter is zero, everything is done and the waiters should + // be resumed. + // When there are multiple thread, there is a chance for the counter + // to go to zero, WakeAll to be called, and then the counter to be + // incremented again before a waiting goroutine has a chance to + // check the new (zero) value. However the last increment is + // explicitly given in the docs as something that should not be + // done: + // + // > Note that calls with a positive delta that occur when the + // > counter is zero must happen before a Wait. + // + // So we're fine here. + if counter-uint32(-delta) == 0 { + // TODO: this is not the most efficient implementation possible + // because we wake up all waiters unconditionally, even if there + // might be none. Though since the common usage is for this to + // be called with at least one waiter, it's probably fine. + wg.futex.WakeAll() } + + // Successfully swapped (and woken all waiting tasks if needed). + return } } } @@ -41,14 +71,15 @@ func (wg *WaitGroup) Done() { } func (wg *WaitGroup) Wait() { - if wg.counter == 0 { - // Everything already finished. - return - } - - // Push the current goroutine onto the waiter stack. - wg.waiters.Push(task.Current()) + for { + counter := wg.futex.Load() + if counter == 0 { + return // everything already finished + } - // Pause until the waiters are awoken by Add/Done. - task.Pause() + if wg.futex.Wait(counter) { + // Successfully woken by WakeAll (in wg.Add). + break + } + } } diff --git a/src/syscall/env_libc.go b/src/syscall/env_libc.go new file mode 100644 index 0000000000..fbf7d04e0e --- /dev/null +++ b/src/syscall/env_libc.go @@ -0,0 +1,111 @@ +//go:build nintendoswitch || wasip1 + +package syscall + +import ( + "unsafe" +) + +func Environ() []string { + + // This function combines all the environment into a single allocation. + // While this optimizes for memory usage and garbage collector + // overhead, it does run the risk of potentially pinning a "large" + // allocation if a user holds onto a single environment variable or + // value. Having each variable be its own allocation would make the + // trade-off in the other direction. + + // calculate total memory required + var length uintptr + var vars int + for environ := libc_environ; *environ != nil; { + length += libc_strlen(*environ) + vars++ + environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) + } + + // allocate our backing slice for the strings + b := make([]byte, length) + // and the slice we're going to return + envs := make([]string, 0, vars) + + // loop over the environment again, this time copying over the data to the backing slice + for environ := libc_environ; *environ != nil; { + length = libc_strlen(*environ) + // construct a Go string pointing at the libc-allocated environment variable data + var envVar string + rawEnvVar := (*struct { + ptr unsafe.Pointer + length uintptr + })(unsafe.Pointer(&envVar)) + rawEnvVar.ptr = *environ + rawEnvVar.length = length + // pull off the number of bytes we need for this environment variable + var bs []byte + bs, b = b[:length], b[length:] + // copy over the bytes to the Go heap + copy(bs, envVar) + // convert trimmed slice to string + s := *(*string)(unsafe.Pointer(&bs)) + // add s to our list of environment variables + envs = append(envs, s) + // environ++ + environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) + } + return envs +} + +func Getenv(key string) (value string, found bool) { + data := cstring(key) + raw := libc_getenv(&data[0]) + if raw == nil { + return "", false + } + + ptr := uintptr(unsafe.Pointer(raw)) + for size := uintptr(0); ; size++ { + v := *(*byte)(unsafe.Pointer(ptr)) + if v == 0 { + src := *(*[]byte)(unsafe.Pointer(&sliceHeader{buf: raw, len: size, cap: size})) + return string(src), true + } + ptr += unsafe.Sizeof(byte(0)) + } +} + +func Setenv(key, val string) (err error) { + if len(key) == 0 { + return EINVAL + } + for i := 0; i < len(key); i++ { + if key[i] == '=' || key[i] == 0 { + return EINVAL + } + } + for i := 0; i < len(val); i++ { + if val[i] == 0 { + return EINVAL + } + } + runtimeSetenv(key, val) + return +} + +func Unsetenv(key string) (err error) { + runtimeUnsetenv(key) + return +} + +func Clearenv() { + for _, s := range Environ() { + for j := 0; j < len(s); j++ { + if s[j] == '=' { + Unsetenv(s[0:j]) + break + } + } + } +} + +//go:extern environ +var libc_environ *unsafe.Pointer diff --git a/src/syscall/env_nonhosted.go b/src/syscall/env_nonhosted.go new file mode 100644 index 0000000000..446ba55d2d --- /dev/null +++ b/src/syscall/env_nonhosted.go @@ -0,0 +1,45 @@ +//go:build baremetal || js || wasm_unknown + +package syscall + +func Environ() []string { + env := runtime_envs() + envCopy := make([]string, len(env)) + copy(envCopy, env) + return envCopy +} + +func Getenv(key string) (value string, found bool) { + env := runtime_envs() + for _, keyval := range env { + // Split at '=' character. + var k, v string + for i := 0; i < len(keyval); i++ { + if keyval[i] == '=' { + k = keyval[:i] + v = keyval[i+1:] + } + } + if k == key { + return v, true + } + } + return "", false +} + +func Setenv(key, val string) (err error) { + // stub for now + return ENOSYS +} + +func Unsetenv(key string) (err error) { + // stub for now + return ENOSYS +} + +func Clearenv() (err error) { + // stub for now + return ENOSYS +} + +func runtime_envs() []string diff --git a/src/syscall/env_wasip2.go b/src/syscall/env_wasip2.go new file mode 100644 index 0000000000..8064d0d281 --- /dev/null +++ b/src/syscall/env_wasip2.go @@ -0,0 +1,56 @@ +//go:build wasip2 + +package syscall + +import ( + "internal/wasi/cli/v0.2.0/environment" +) + +var libc_envs map[string]string + +func populateEnvironment() { + libc_envs = make(map[string]string) + for _, kv := range environment.GetEnvironment().Slice() { + libc_envs[kv[0]] = kv[1] + } +} + +func Environ() []string { + var env []string + for k, v := range libc_envs { + env = append(env, k+"="+v) + } + return env +} + +func Getenv(key string) (value string, found bool) { + value, found = libc_envs[key] + return +} + +func Setenv(key, val string) (err error) { + if len(key) == 0 { + return EINVAL + } + for i := 0; i < len(key); i++ { + if key[i] == '=' || key[i] == 0 { + return EINVAL + } + } + for i := 0; i < len(val); i++ { + if val[i] == 0 { + return EINVAL + } + } + libc_envs[key] = val + return nil +} + +func Unsetenv(key string) (err error) { + delete(libc_envs, key) + return nil +} + +func Clearenv() { + clear(libc_envs) +} diff --git a/src/syscall/errno_other.go b/src/syscall/errno_other.go index a001096525..8c58f5f01a 100644 --- a/src/syscall/errno_other.go +++ b/src/syscall/errno_other.go @@ -1,4 +1,4 @@ -//go:build !wasi && !wasip1 && !darwin +//go:build !js && !wasip1 && !wasip2 package syscall diff --git a/src/syscall/errno_wasilibc.go b/src/syscall/errno_wasilibc.go new file mode 100644 index 0000000000..efb97260f5 --- /dev/null +++ b/src/syscall/errno_wasilibc.go @@ -0,0 +1,8 @@ +//go:build wasip1 || js + +package syscall + +// Use a go:extern definition to access the errno from wasi-libc +// +//go:extern errno +var libcErrno Errno diff --git a/src/syscall/errno_wasip2.go b/src/syscall/errno_wasip2.go new file mode 100644 index 0000000000..39f1f8b403 --- /dev/null +++ b/src/syscall/errno_wasip2.go @@ -0,0 +1,7 @@ +//go:build wasip2 + +package syscall + +// The errno for libc_wasip2.go + +var libcErrno Errno diff --git a/src/syscall/file_emulated.go b/src/syscall/file_emulated.go index 8dd3bc14fc..7b50d4f7e8 100644 --- a/src/syscall/file_emulated.go +++ b/src/syscall/file_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasip1) || wasm_unknown +//go:build baremetal || (wasm && !wasip1 && !wasip2) || wasm_unknown // This file emulates some file-related functions that are only available // under a real operating system. diff --git a/src/syscall/file_hosted.go b/src/syscall/file_hosted.go index f448f018d7..a079f400fb 100644 --- a/src/syscall/file_hosted.go +++ b/src/syscall/file_hosted.go @@ -1,4 +1,4 @@ -//go:build !(baremetal || (wasm && !wasip1) || wasm_unknown) +//go:build !(baremetal || (wasm && !wasip1 && !wasip2) || wasm_unknown) // This file assumes there is a libc available that runs on a real operating // system. diff --git a/src/syscall/libc_wasip2.go b/src/syscall/libc_wasip2.go new file mode 100644 index 0000000000..5621c1a683 --- /dev/null +++ b/src/syscall/libc_wasip2.go @@ -0,0 +1,1307 @@ +//go:build wasip2 + +// mini libc wrapping wasi preview2 calls in a libc api + +package syscall + +import ( + "unsafe" + + "internal/cm" + + "internal/wasi/cli/v0.2.0/environment" + "internal/wasi/cli/v0.2.0/stderr" + "internal/wasi/cli/v0.2.0/stdin" + "internal/wasi/cli/v0.2.0/stdout" + wallclock "internal/wasi/clocks/v0.2.0/wall-clock" + "internal/wasi/filesystem/v0.2.0/preopens" + "internal/wasi/filesystem/v0.2.0/types" + ioerror "internal/wasi/io/v0.2.0/error" + "internal/wasi/io/v0.2.0/streams" + "internal/wasi/random/v0.2.0/random" +) + +func goString(cstr *byte) string { + return unsafe.String(cstr, strlen(cstr)) +} + +//export strlen +func strlen(cstr *byte) uintptr { + if cstr == nil { + return 0 + } + ptr := unsafe.Pointer(cstr) + var i uintptr + for p := (*byte)(ptr); *p != 0; p = (*byte)(unsafe.Add(unsafe.Pointer(p), 1)) { + i++ + } + return i +} + +// ssize_t write(int fd, const void *buf, size_t count) +// +//export write +func write(fd int32, buf *byte, count uint) int { + if stream, ok := wasiStreams[fd]; ok { + return writeStream(stream, buf, count, 0) + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + + n := pwrite(fd, buf, count, int64(stream.offset)) + if n == -1 { + return -1 + } + stream.offset += int64(n) + return int(n) +} + +// ssize_t read(int fd, void *buf, size_t count); +// +//export read +func read(fd int32, buf *byte, count uint) int { + if stream, ok := wasiStreams[fd]; ok { + return readStream(stream, buf, count, 0) + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + + n := pread(fd, buf, count, int64(stream.offset)) + if n == -1 { + // error during pread + return -1 + } + stream.offset += int64(n) + return int(n) +} + +// At the moment, each time we have a file read or write we create a new stream. Future implementations +// could change the current in or out file stream lazily. We could do this by tracking input and output +// offsets individually, and if they don't match the current main offset, reopen the file stream at that location. + +type wasiFile struct { + d types.Descriptor + oflag int32 // original open flags: O_RDONLY, O_WRONLY, O_RDWR + offset int64 // current fd offset; updated with each read/write + refs int +} + +// Need to figure out which system calls we're using: +// stdin/stdout/stderr want streams, so we use stream read/write +// but for regular files we can use the descriptor and explicitly write a buffer to the offset? +// The mismatch comes from trying to combine these. + +var wasiFiles map[int32]*wasiFile = make(map[int32]*wasiFile) + +func findFreeFD() int32 { + var newfd int32 + for wasiStreams[newfd] != nil || wasiFiles[newfd] != nil { + newfd++ + } + return newfd +} + +var wasiErrno ioerror.Error + +type wasiStream struct { + in *streams.InputStream + out *streams.OutputStream + refs int +} + +// This holds entries for stdin/stdout/stderr. + +var wasiStreams map[int32]*wasiStream + +func init() { + sin := stdin.GetStdin() + sout := stdout.GetStdout() + serr := stderr.GetStderr() + wasiStreams = map[int32]*wasiStream{ + 0: &wasiStream{ + in: &sin, + refs: 1, + }, + 1: &wasiStream{ + out: &sout, + refs: 1, + }, + 2: &wasiStream{ + out: &serr, + refs: 1, + }, + } +} + +func readStream(stream *wasiStream, buf *byte, count uint, offset int64) int { + if stream.in == nil { + // not a stream we can read from + libcErrno = EBADF + return -1 + } + + if offset != 0 { + libcErrno = EINVAL + return -1 + } + + libcErrno = 0 + list, err, isErr := stream.in.BlockingRead(uint64(count)).Result() + if isErr { + if err.Closed() { + libcErrno = 0 + return 0 + } else if err := err.LastOperationFailed(); err != nil { + wasiErrno = *err + libcErrno = EWASIERROR + } + return -1 + } + + copy(unsafe.Slice(buf, count), list.Slice()) + return int(list.Len()) +} + +func writeStream(stream *wasiStream, buf *byte, count uint, offset int64) int { + if stream.out == nil { + // not a stream we can write to + libcErrno = EBADF + return -1 + } + + if offset != 0 { + libcErrno = EINVAL + return -1 + } + + src := unsafe.Slice(buf, count) + var remaining = count + + // The blocking-write-and-flush call allows a maximum of 4096 bytes at a time. + // We loop here by instead of doing subscribe/check-write/poll-one/write by hand. + for remaining > 0 { + len := uint(4096) + if len > remaining { + len = remaining + } + _, err, isErr := stream.out.BlockingWriteAndFlush(cm.ToList(src[:len])).Result() + if isErr { + if err.Closed() { + libcErrno = 0 + return 0 + } else if err := err.LastOperationFailed(); err != nil { + wasiErrno = *err + libcErrno = EWASIERROR + } + return -1 + } + remaining -= len + } + + return int(count) +} + +//go:linkname memcpy runtime.memcpy +func memcpy(dst, src unsafe.Pointer, size uintptr) + +// ssize_t pread(int fd, void *buf, size_t count, off_t offset); +// +//export pread +func pread(fd int32, buf *byte, count uint, offset int64) int { + // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? + + if stream, ok := wasiStreams[fd]; ok { + return readStream(stream, buf, count, offset) + + } + + streams, ok := wasiFiles[fd] + if !ok { + // TODO(dgryski): EINVAL? + libcErrno = EBADF + return -1 + } + if streams.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + if streams.oflag&O_RDONLY == 0 { + libcErrno = EBADF + return -1 + } + + listEOF, err, isErr := streams.d.Read(types.FileSize(count), types.FileSize(offset)).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + list := listEOF.F0 + copy(unsafe.Slice(buf, count), list.Slice()) + + // TODO(dgryski): EOF bool is ignored? + return int(list.Len()) +} + +// ssize_t pwrite(int fd, void *buf, size_t count, off_t offset); +// +//export pwrite +func pwrite(fd int32, buf *byte, count uint, offset int64) int { + // TODO(dgryski): Need to be consistent about all these checks; EBADF/EINVAL/... ? + if stream, ok := wasiStreams[fd]; ok { + return writeStream(stream, buf, count, 0) + } + + streams, ok := wasiFiles[fd] + if !ok { + // TODO(dgryski): EINVAL? + libcErrno = EBADF + return -1 + } + if streams.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + if streams.oflag&O_WRONLY == 0 { + libcErrno = EBADF + return -1 + } + + n, err, isErr := streams.d.Write(cm.NewList(buf, count), types.FileSize(offset)).Result() + if isErr { + // TODO(dgryski): + libcErrno = errorCodeToErrno(err) + return -1 + } + + return int(n) +} + +// ssize_t lseek(int fd, off_t offset, int whence); +// +//export lseek +func lseek(fd int32, offset int64, whence int) int64 { + if _, ok := wasiStreams[fd]; ok { + // can't lseek a stream + libcErrno = EBADF + return -1 + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + + switch whence { + case 0: // SEEK_SET + stream.offset = offset + case 1: // SEEK_CUR + stream.offset += offset + case 2: // SEEK_END + stat, err, isErr := stream.d.Stat().Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + stream.offset = int64(stat.Size) + offset + } + + return int64(stream.offset) +} + +// int close(int fd) +// +//export close +func close(fd int32) int32 { + if streams, ok := wasiStreams[fd]; ok { + if streams.out != nil { + // ignore any error + streams.out.BlockingFlush() + } + + if streams.refs--; streams.refs == 0 { + if streams.out != nil { + streams.out.ResourceDrop() + streams.out = nil + } + if streams.in != nil { + streams.in.ResourceDrop() + streams.in = nil + } + } + + delete(wasiStreams, fd) + return 0 + } + + streams, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if streams.refs--; streams.refs == 0 && streams.d != cm.ResourceNone { + streams.d.ResourceDrop() + streams.d = 0 + } + delete(wasiFiles, fd) + + return 0 +} + +// int dup(int fd) +// +//export dup +func dup(fd int32) int32 { + // is fd a stream? + if stream, ok := wasiStreams[fd]; ok { + newfd := findFreeFD() + stream.refs++ + wasiStreams[newfd] = stream + return newfd + } + + // is fd a file? + if file, ok := wasiFiles[fd]; ok { + // scan for first free file descriptor + newfd := findFreeFD() + file.refs++ + wasiFiles[newfd] = file + return newfd + } + + // unknown file descriptor + libcErrno = EBADF + return -1 +} + +// void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); +// +//export mmap +func mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offset uintptr) unsafe.Pointer { + libcErrno = ENOSYS + return unsafe.Pointer(^uintptr(0)) +} + +// int munmap(void *addr, size_t length); +// +//export munmap +func munmap(addr unsafe.Pointer, length uintptr) int32 { + libcErrno = ENOSYS + return -1 +} + +// int mprotect(void *addr, size_t len, int prot); +// +//export mprotect +func mprotect(addr unsafe.Pointer, len uintptr, prot int32) int32 { + libcErrno = ENOSYS + return -1 +} + +// int chmod(const char *pathname, mode_t mode); +// +//export chmod +func chmod(pathname *byte, mode uint32) int32 { + return 0 +} + +// int mkdir(const char *pathname, mode_t mode); +// +//export mkdir +func mkdir(pathname *byte, mode uint32) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + _, err, isErr := dir.d.CreateDirectoryAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + return 0 +} + +// int rmdir(const char *pathname); +// +//export rmdir +func rmdir(pathname *byte) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + _, err, isErr := dir.d.RemoveDirectoryAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + return 0 +} + +// int rename(const char *from, *to); +// +//export rename +func rename(from, to *byte) int32 { + fromPath := goString(from) + fromDir, fromRelPath := findPreopenForPath(fromPath) + if fromDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + toPath := goString(to) + toDir, toRelPath := findPreopenForPath(toPath) + if toDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + _, err, isErr := fromDir.d.RenameAt(fromRelPath, toDir.d, toRelPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + return 0 +} + +// int symlink(const char *from, *to); +// +//export symlink +func symlink(from, to *byte) int32 { + fromPath := goString(from) + fromDir, fromRelPath := findPreopenForPath(fromPath) + if fromDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + toPath := goString(to) + toDir, toRelPath := findPreopenForPath(toPath) + if toDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + if fromDir.d != toDir.d { + libcErrno = EACCES + return -1 + } + + // TODO(dgryski): check fromDir == toDir? + + _, err, isErr := fromDir.d.SymlinkAt(fromRelPath, toRelPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + return 0 +} + +// int link(const char *from, *to); +// +//export link +func link(from, to *byte) int32 { + fromPath := goString(from) + fromDir, fromRelPath := findPreopenForPath(fromPath) + if fromDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + toPath := goString(to) + toDir, toRelPath := findPreopenForPath(toPath) + if toDir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + if fromDir.d != toDir.d { + libcErrno = EACCES + return -1 + } + + // TODO(dgryski): check fromDir == toDir? + + _, err, isErr := fromDir.d.LinkAt(0, fromRelPath, toDir.d, toRelPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + return 0 +} + +// int fsync(int fd); +// +//export fsync +func fsync(fd int32) int32 { + if _, ok := wasiStreams[fd]; ok { + // can't sync a stream + libcErrno = EBADF + return -1 + } + + streams, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if streams.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + if streams.oflag&O_WRONLY == 0 { + libcErrno = EBADF + return -1 + } + + _, err, isErr := streams.d.SyncData().Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + return 0 +} + +// ssize_t readlink(const char *path, void *buf, size_t count); +// +//export readlink +func readlink(pathname *byte, buf *byte, count uint) int { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + s, err, isErr := dir.d.ReadLinkAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + size := uintptr(count) + if size > uintptr(len(s)) { + size = uintptr(len(s)) + } + + memcpy(unsafe.Pointer(buf), unsafe.Pointer(unsafe.StringData(s)), size) + return int(size) +} + +// int unlink(const char *pathname); +// +//export unlink +func unlink(pathname *byte) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + _, err, isErr := dir.d.UnlinkFileAt(relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + return 0 +} + +// int getpagesize(void); +// +//export getpagesize +func getpagesize() int { + return 65536 +} + +// int stat(const char *path, struct stat * buf); +// +//export stat +func stat(pathname *byte, dst *Stat_t) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + stat, err, isErr := dir.d.StatAt(0, relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + setStatFromWASIStat(dst, &stat) + + return 0 +} + +// int fstat(int fd, struct stat * buf); +// +//export fstat +func fstat(fd int32, dst *Stat_t) int32 { + if _, ok := wasiStreams[fd]; ok { + // TODO(dgryski): fill in stat buffer for stdin etc + return -1 + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return -1 + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return -1 + } + stat, err, isErr := stream.d.Stat().Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + setStatFromWASIStat(dst, &stat) + + return 0 +} + +func setStatFromWASIStat(sstat *Stat_t, wstat *types.DescriptorStat) { + // This will cause problems for people who want to compare inodes + sstat.Dev = 0 + sstat.Ino = 0 + sstat.Rdev = 0 + + sstat.Nlink = uint64(wstat.LinkCount) + + sstat.Mode = p2fileTypeToStatType(wstat.Type) + + // No uid/gid + sstat.Uid = 0 + sstat.Gid = 0 + sstat.Size = int64(wstat.Size) + + // made up numbers + sstat.Blksize = 512 + sstat.Blocks = (sstat.Size + 511) / int64(sstat.Blksize) + + setOptTime := func(t *Timespec, o *wallclock.DateTime) { + t.Sec = 0 + t.Nsec = 0 + if o != nil { + t.Sec = int32(o.Seconds) + t.Nsec = int64(o.Nanoseconds) + } + } + + setOptTime(&sstat.Atim, wstat.DataAccessTimestamp.Some()) + setOptTime(&sstat.Mtim, wstat.DataModificationTimestamp.Some()) + setOptTime(&sstat.Ctim, wstat.StatusChangeTimestamp.Some()) +} + +// int lstat(const char *path, struct stat * buf); +// +//export lstat +func lstat(pathname *byte, dst *Stat_t) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + stat, err, isErr := dir.d.StatAt(0, relPath).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + setStatFromWASIStat(dst, &stat) + + return 0 +} + +func init() { + populateEnvironment() + populatePreopens() +} + +type wasiDir struct { + d types.Descriptor // wasip2 descriptor + root string // root path for this descriptor + rel string // relative path under root +} + +var libcCWD wasiDir + +var wasiPreopens map[string]types.Descriptor + +func populatePreopens() { + var cwd string + + // find CWD + result := environment.InitialCWD() + if s := result.Some(); s != nil { + cwd = *s + } else if s, _ := Getenv("PWD"); s != "" { + cwd = s + } + + dirs := preopens.GetDirectories().Slice() + preopens := make(map[string]types.Descriptor, len(dirs)) + for _, tup := range dirs { + desc, path := tup.F0, tup.F1 + if path == cwd { + libcCWD.d = desc + libcCWD.root = path + libcCWD.rel = "" + } + preopens[path] = desc + } + wasiPreopens = preopens +} + +// -- BEGIN fs_wasip1.go -- +// The following section has been taken from upstream Go with the following copyright: +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:nosplit +func appendCleanPath(buf []byte, path string, lookupParent bool) ([]byte, bool) { + i := 0 + for i < len(path) { + for i < len(path) && path[i] == '/' { + i++ + } + + j := i + for j < len(path) && path[j] != '/' { + j++ + } + + s := path[i:j] + i = j + + switch s { + case "": + continue + case ".": + continue + case "..": + if !lookupParent { + k := len(buf) + for k > 0 && buf[k-1] != '/' { + k-- + } + for k > 1 && buf[k-1] == '/' { + k-- + } + buf = buf[:k] + if k == 0 { + lookupParent = true + } else { + s = "" + continue + } + } + default: + lookupParent = false + } + + if len(buf) > 0 && buf[len(buf)-1] != '/' { + buf = append(buf, '/') + } + buf = append(buf, s...) + } + return buf, lookupParent +} + +// joinPath concatenates dir and file paths, producing a cleaned path where +// "." and ".." have been removed, unless dir is relative and the references +// to parent directories in file represented a location relative to a parent +// of dir. +// +// This function is used for path resolution of all wasi functions expecting +// a path argument; the returned string is heap allocated, which we may want +// to optimize in the future. Instead of returning a string, the function +// could append the result to an output buffer that the functions in this +// file can manage to have allocated on the stack (e.g. initializing to a +// fixed capacity). Since it will significantly increase code complexity, +// we prefer to optimize for readability and maintainability at this time. +func joinPath(dir, file string) string { + buf := make([]byte, 0, len(dir)+len(file)+1) + if isAbs(dir) { + buf = append(buf, '/') + } + + buf, lookupParent := appendCleanPath(buf, dir, true) + buf, _ = appendCleanPath(buf, file, lookupParent) + // The appendCleanPath function cleans the path so it does not inject + // references to the current directory. If both the dir and file args + // were ".", this results in the output buffer being empty so we handle + // this condition here. + if len(buf) == 0 { + buf = append(buf, '.') + } + // If the file ended with a '/' we make sure that the output also ends + // with a '/'. This is needed to ensure that programs have a mechanism + // to represent dereferencing symbolic links pointing to directories. + if buf[len(buf)-1] != '/' && isDir(file) { + buf = append(buf, '/') + } + return unsafe.String(&buf[0], len(buf)) +} + +func isAbs(path string) bool { + return hasPrefix(path, "/") +} + +func isDir(path string) bool { + return hasSuffix(path, "/") +} + +func hasPrefix(s, p string) bool { + return len(s) >= len(p) && s[:len(p)] == p +} + +func hasSuffix(s, x string) bool { + return len(s) >= len(x) && s[len(s)-len(x):] == x +} + +// findPreopenForPath finds which preopen it relates to and return that descriptor/root and the path relative to that directory descriptor/root +func findPreopenForPath(path string) (wasiDir, string) { + dir := "/" + var wasidir wasiDir + + if !isAbs(path) { + dir = libcCWD.root + wasidir = libcCWD + if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { + path = libcCWD.rel + "/" + path + } + } + path = joinPath(dir, path) + + var best string + for k, v := range wasiPreopens { + if len(k) > len(best) && hasPrefix(path, k) { + wasidir = wasiDir{d: v, root: k} + best = wasidir.root + } + } + + if hasPrefix(path, wasidir.root) { + path = path[len(wasidir.root):] + } + for isAbs(path) { + path = path[1:] + } + if len(path) == 0 { + path = "." + } + + return wasidir, path +} + +// -- END fs_wasip1.go -- + +// int open(const char *pathname, int flags, mode_t mode); +// +//export open +func open(pathname *byte, flags int32, mode uint32) int32 { + path := goString(pathname) + dir, relPath := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + var dflags types.DescriptorFlags + if (flags & O_RDONLY) == O_RDONLY { + dflags |= types.DescriptorFlagsRead + } + if (flags & O_WRONLY) == O_WRONLY { + dflags |= types.DescriptorFlagsWrite + } + + var oflags types.OpenFlags + if flags&O_CREAT == O_CREAT { + oflags |= types.OpenFlagsCreate + } + if flags&O_DIRECTORY == O_DIRECTORY { + oflags |= types.OpenFlagsDirectory + } + if flags&O_EXCL == O_EXCL { + oflags |= types.OpenFlagsExclusive + } + if flags&O_TRUNC == O_TRUNC { + oflags |= types.OpenFlagsTruncate + } + + // By default, follow symlinks for open() unless O_NOFOLLOW was passed + var pflags types.PathFlags = types.PathFlagsSymlinkFollow + if flags&O_NOFOLLOW == O_NOFOLLOW { + // O_NOFOLLOW was passed, so turn off SymlinkFollow + pflags &^= types.PathFlagsSymlinkFollow + } + + descriptor, err, isErr := dir.d.OpenAt(pflags, relPath, oflags, dflags).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + stream := wasiFile{ + d: descriptor, + oflag: flags, + refs: 1, + } + + if flags&(O_WRONLY|O_APPEND) == (O_WRONLY | O_APPEND) { + stat, err, isErr := stream.d.Stat().Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + stream.offset = int64(stat.Size) + } + + libcfd := findFreeFD() + + wasiFiles[libcfd] = &stream + + return int32(libcfd) +} + +func errorCodeToErrno(err types.ErrorCode) Errno { + switch err { + case types.ErrorCodeAccess: + return EACCES + case types.ErrorCodeWouldBlock: + return EAGAIN + case types.ErrorCodeAlready: + return EALREADY + case types.ErrorCodeBadDescriptor: + return EBADF + case types.ErrorCodeBusy: + return EBUSY + case types.ErrorCodeDeadlock: + return EDEADLK + case types.ErrorCodeQuota: + return EDQUOT + case types.ErrorCodeExist: + return EEXIST + case types.ErrorCodeFileTooLarge: + return EFBIG + case types.ErrorCodeIllegalByteSequence: + return EILSEQ + case types.ErrorCodeInProgress: + return EINPROGRESS + case types.ErrorCodeInterrupted: + return EINTR + case types.ErrorCodeInvalid: + return EINVAL + case types.ErrorCodeIO: + return EIO + case types.ErrorCodeIsDirectory: + return EISDIR + case types.ErrorCodeLoop: + return ELOOP + case types.ErrorCodeTooManyLinks: + return EMLINK + case types.ErrorCodeMessageSize: + return EMSGSIZE + case types.ErrorCodeNameTooLong: + return ENAMETOOLONG + case types.ErrorCodeNoDevice: + return ENODEV + case types.ErrorCodeNoEntry: + return ENOENT + case types.ErrorCodeNoLock: + return ENOLCK + case types.ErrorCodeInsufficientMemory: + return ENOMEM + case types.ErrorCodeInsufficientSpace: + return ENOSPC + case types.ErrorCodeNotDirectory: + return ENOTDIR + case types.ErrorCodeNotEmpty: + return ENOTEMPTY + case types.ErrorCodeNotRecoverable: + return ENOTRECOVERABLE + case types.ErrorCodeUnsupported: + return ENOSYS + case types.ErrorCodeNoTTY: + return ENOTTY + case types.ErrorCodeNoSuchDevice: + return ENXIO + case types.ErrorCodeOverflow: + return EOVERFLOW + case types.ErrorCodeNotPermitted: + return EPERM + case types.ErrorCodePipe: + return EPIPE + case types.ErrorCodeReadOnly: + return EROFS + case types.ErrorCodeInvalidSeek: + return ESPIPE + case types.ErrorCodeTextFileBusy: + return ETXTBSY + case types.ErrorCodeCrossDevice: + return EXDEV + } + return Errno(err) +} + +type libc_DIR struct { + d types.DirectoryEntryStream +} + +// DIR *fdopendir(int); +// +//export fdopendir +func fdopendir(fd int32) unsafe.Pointer { + if _, ok := wasiStreams[fd]; ok { + libcErrno = EBADF + return nil + } + + stream, ok := wasiFiles[fd] + if !ok { + libcErrno = EBADF + return nil + } + if stream.d == cm.ResourceNone { + libcErrno = EBADF + return nil + } + + dir, err, isErr := stream.d.ReadDirectory().Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return nil + } + + return unsafe.Pointer(&libc_DIR{d: dir}) +} + +// int fdclosedir(DIR *); +// +//export fdclosedir +func fdclosedir(dirp unsafe.Pointer) int32 { + if dirp == nil { + return 0 + + } + dir := (*libc_DIR)(dirp) + if dir.d == cm.ResourceNone { + return 0 + } + + dir.d.ResourceDrop() + dir.d = cm.ResourceNone + + return 0 +} + +// struct dirent *readdir(DIR *); +// +//export readdir +func readdir(dirp unsafe.Pointer) *Dirent { + if dirp == nil { + return nil + + } + dir := (*libc_DIR)(dirp) + if dir.d == cm.ResourceNone { + return nil + } + + someEntry, err, isErr := dir.d.ReadDirectoryEntry().Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return nil + } + + entry := someEntry.Some() + if entry == nil { + libcErrno = 0 + return nil + } + + // The dirent C struct uses a flexible array member to indicate that the + // directory name is laid out in memory right after the struct data: + // + // struct dirent { + // ino_t d_ino; + // unsigned char d_type; + // char d_name[]; + // }; + buf := make([]byte, unsafe.Sizeof(Dirent{})+uintptr(len(entry.Name))) + dirent := (*Dirent)((unsafe.Pointer)(&buf[0])) + + // No inodes in wasi + dirent.Ino = 0 + dirent.Type = p2fileTypeToDirentType(entry.Type) + copy(buf[unsafe.Offsetof(dirent.Type)+1:], entry.Name) + + return dirent +} + +func p2fileTypeToDirentType(t types.DescriptorType) uint8 { + switch t { + case types.DescriptorTypeUnknown: + return DT_UNKNOWN + case types.DescriptorTypeBlockDevice: + return DT_BLK + case types.DescriptorTypeCharacterDevice: + return DT_CHR + case types.DescriptorTypeDirectory: + return DT_DIR + case types.DescriptorTypeFIFO: + return DT_FIFO + case types.DescriptorTypeSymbolicLink: + return DT_LNK + case types.DescriptorTypeRegularFile: + return DT_REG + case types.DescriptorTypeSocket: + return DT_FIFO + } + + return DT_UNKNOWN +} + +func p2fileTypeToStatType(t types.DescriptorType) uint32 { + switch t { + case types.DescriptorTypeUnknown: + return 0 + case types.DescriptorTypeBlockDevice: + return S_IFBLK + case types.DescriptorTypeCharacterDevice: + return S_IFCHR + case types.DescriptorTypeDirectory: + return S_IFDIR + case types.DescriptorTypeFIFO: + return S_IFIFO + case types.DescriptorTypeSymbolicLink: + return S_IFLNK + case types.DescriptorTypeRegularFile: + return S_IFREG + case types.DescriptorTypeSocket: + return S_IFSOCK + } + + return 0 +} + +// void arc4random_buf (void *, size_t); +// +//export arc4random_buf +func arc4random_buf(p unsafe.Pointer, l uint) { + result := random.GetRandomBytes(uint64(l)) + s := result.Slice() + memcpy(unsafe.Pointer(p), unsafe.Pointer(unsafe.SliceData(s)), uintptr(l)) +} + +// int chdir(char *name) +// +//export chdir +func chdir(name *byte) int { + path := goString(name) + "/" + + if !isAbs(path) { + path = joinPath(libcCWD.root+"/"+libcCWD.rel+"/", path) + } + + if path == "." { + return 0 + } + + dir, rel := findPreopenForPath(path) + if dir.d == cm.ResourceNone { + libcErrno = EACCES + return -1 + } + + _, err, isErr := dir.d.OpenAt(types.PathFlagsSymlinkFollow, rel, types.OpenFlagsDirectory, types.DescriptorFlagsRead).Result() + if isErr { + libcErrno = errorCodeToErrno(err) + return -1 + } + + libcCWD = dir + // keep the same cwd base but update "rel" to point to new base path + libcCWD.rel = rel + + return 0 +} + +// char *getcwd(char *buf, size_t size) +// +//export getcwd +func getcwd(buf *byte, size uint) *byte { + + cwd := libcCWD.root + if libcCWD.rel != "" && libcCWD.rel != "." && libcCWD.rel != "./" { + cwd += libcCWD.rel + } + + if buf == nil { + b := make([]byte, len(cwd)+1) + buf = unsafe.SliceData(b) + } else if size == 0 { + libcErrno = EINVAL + return nil + } + + if size < uint(len(cwd)+1) { + libcErrno = ERANGE + return nil + } + + s := unsafe.Slice(buf, size) + s[size-1] = 0 // Enforce NULL termination + copy(s, cwd) + return buf +} + +// int truncate(const char *path, off_t length); +// +//export truncate +func truncate(path *byte, length int64) int32 { + libcErrno = ENOSYS + return -1 +} diff --git a/src/syscall/mmap_unix_test.go b/src/syscall/mmap_unix_test.go index 1946265d8c..62748a9395 100644 --- a/src/syscall/mmap_unix_test.go +++ b/src/syscall/mmap_unix_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin || linux +//go:build linux package syscall_test diff --git a/src/syscall/proc_emulated.go b/src/syscall/proc_emulated.go index d8e7ff7a92..bcf8eabe94 100644 --- a/src/syscall/proc_emulated.go +++ b/src/syscall/proc_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || tinygo.wasm +//go:build baremetal || tinygo.wasm || nintendoswitch // This file emulates some process-related functions that are only available // under a real operating system. diff --git a/src/syscall/proc_hosted.go b/src/syscall/proc_hosted.go index 05c509e6ff..8b8f1fafd5 100644 --- a/src/syscall/proc_hosted.go +++ b/src/syscall/proc_hosted.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !tinygo.wasm +//go:build !baremetal && !tinygo.wasm && !nintendoswitch // This file assumes there is a libc available that runs on a real operating // system. diff --git a/src/syscall/syscall.go b/src/syscall/syscall.go index dd36800c9a..f22289c5ad 100644 --- a/src/syscall/syscall.go +++ b/src/syscall/syscall.go @@ -2,7 +2,6 @@ package syscall import ( "errors" - "sync/atomic" ) const ( @@ -18,11 +17,6 @@ type Rlimit struct { Max uint64 } -// origRlimitNofile, if not {0, 0}, is the original soft RLIMIT_NOFILE. -// When we can assume that we are bootstrapping with Go 1.19, -// this can be atomic.Pointer[Rlimit]. -var origRlimitNofile atomic.Value // of Rlimit - func Setrlimit(resource int, rlim *Rlimit) error { return errors.New("Setrlimit not implemented") } diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 7a13245b6d..86c756383e 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -1,4 +1,4 @@ -//go:build darwin || nintendoswitch || wasi || wasip1 +//go:build js || nintendoswitch || wasip1 || wasip2 package syscall @@ -134,6 +134,16 @@ func Rename(from, to string) (err error) { return } +func Link(oldname, newname string) (err error) { + fromdata := cstring(oldname) + todata := cstring(newname) + fail := int(libc_link(&fromdata[0], &todata[0])) + if fail < 0 { + err = getErrno() + } + return +} + func Symlink(from, to string) (err error) { fromdata := cstring(from) todata := cstring(to) @@ -153,6 +163,55 @@ func Unlink(path string) (err error) { return } +func Chown(path string, uid, gid int) (err error) { + data := cstring(path) + fail := int(libc_chown(&data[0], uid, gid)) + if fail < 0 { + err = getErrno() + } + return +} + +func Fork() (err error) { + fail := int(libc_fork()) + if fail < 0 { + err = getErrno() + } + return +} + +func Execve(pathname string, argv []string, envv []string) (err 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 + + fail := int(libc_execve(&argv0[0], &argv1[0], &env1[0])) + if fail < 0 { + err = getErrno() + } + return +} + +func Truncate(path string, length int64) (err error) { + data := cstring(path) + fail := int(libc_truncate(&data[0], length)) + if fail < 0 { + err = getErrno() + } + return +} + func Faccessat(dirfd int, path string, mode uint32, flags int) (err error) func Kill(pid int, sig Signal) (err error) { @@ -174,56 +233,13 @@ func (w WaitStatus) Continued() bool { return false } func (w WaitStatus) StopSignal() Signal { return 0 } func (w WaitStatus) TrapCause() int { return 0 } -func Getenv(key string) (value string, found bool) { - data := cstring(key) - raw := libc_getenv(&data[0]) - if raw == nil { - return "", false - } - - ptr := uintptr(unsafe.Pointer(raw)) - for size := uintptr(0); ; size++ { - v := *(*byte)(unsafe.Pointer(ptr)) - if v == 0 { - src := *(*[]byte)(unsafe.Pointer(&sliceHeader{buf: raw, len: size, cap: size})) - return string(src), true - } - ptr += unsafe.Sizeof(byte(0)) - } -} - -func Setenv(key, val string) (err error) { - if len(key) == 0 { - return EINVAL - } - for i := 0; i < len(key); i++ { - if key[i] == '=' || key[i] == 0 { - return EINVAL - } - } - for i := 0; i < len(val); i++ { - if val[i] == 0 { - return EINVAL - } - } - runtimeSetenv(key, val) - return -} - -func Unsetenv(key string) (err error) { - runtimeUnsetenv(key) - return +// Purely here for compatibility. +type Rusage struct { } -func Clearenv() { - for _, s := range Environ() { - for j := 0; j < len(s); j++ { - if s[j] == '=' { - Unsetenv(s[0:j]) - break - } - } - } +// 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 Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) { @@ -250,55 +266,6 @@ func Mprotect(b []byte, prot int) (err error) { return } -func Environ() []string { - - // This function combines all the environment into a single allocation. - // While this optimizes for memory usage and garbage collector - // overhead, it does run the risk of potentially pinning a "large" - // allocation if a user holds onto a single environment variable or - // value. Having each variable be its own allocation would make the - // trade-off in the other direction. - - // calculate total memory required - var length uintptr - var vars int - for environ := libc_environ; *environ != nil; { - length += libc_strlen(*environ) - vars++ - environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) - } - - // allocate our backing slice for the strings - b := make([]byte, length) - // and the slice we're going to return - envs := make([]string, 0, vars) - - // loop over the environment again, this time copying over the data to the backing slice - for environ := libc_environ; *environ != nil; { - length = libc_strlen(*environ) - // construct a Go string pointing at the libc-allocated environment variable data - var envVar string - rawEnvVar := (*struct { - ptr unsafe.Pointer - length uintptr - })(unsafe.Pointer(&envVar)) - rawEnvVar.ptr = *environ - rawEnvVar.length = length - // pull off the number of bytes we need for this environment variable - var bs []byte - bs, b = b[:length], b[length:] - // copy over the bytes to the Go heap - copy(bs, envVar) - // convert trimmed slice to string - s := *(*string)(unsafe.Pointer(&bs)) - // add s to our list of environment variables - envs = append(envs, s) - // environ++ - environ = (*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(environ), unsafe.Sizeof(environ))) - } - return envs -} - // BytePtrFromString returns a pointer to a NUL-terminated array of // bytes containing the text of s. If s contains a NUL byte at any // location, it returns (nil, EINVAL). @@ -396,6 +363,11 @@ func libc_chdir(pathname *byte) int32 //export chmod func libc_chmod(pathname *byte, mode uint32) int32 +// int chown(const char *pathname, uid_t owner, gid_t group); +// +//export chown +func libc_chown(pathname *byte, owner, group int) int32 + // int mkdir(const char *pathname, mode_t mode); // //export mkdir @@ -416,6 +388,11 @@ func libc_rename(from, to *byte) int32 //export symlink func libc_symlink(from, to *byte) int32 +// int link(const char *oldname, *newname); +// +//export link +func libc_link(oldname, newname *byte) int32 + // int fsync(int fd); // //export fsync @@ -431,5 +408,17 @@ func libc_readlink(path *byte, buf *byte, count uint) int //export unlink func libc_unlink(pathname *byte) int32 -//go:extern environ -var libc_environ *unsafe.Pointer +// pid_t fork(void); +// +//export fork +func libc_fork() int32 + +// int execve(const char *filename, char *const argv[], char *const envp[]); +// +//export execve +func libc_execve(filename *byte, argv **byte, envp **byte) int + +// int truncate(const char *path, off_t length); +// +//export truncate +func libc_truncate(path *byte, length int64) int32 diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go deleted file mode 100644 index d64f1061f3..0000000000 --- a/src/syscall/syscall_libc_darwin.go +++ /dev/null @@ -1,339 +0,0 @@ -//go:build darwin - -package syscall - -import ( - "internal/itoa" - "unsafe" -) - -// This file defines errno and constants to match the darwin libsystem ABI. -// Values have been copied from src/syscall/zerrors_darwin_amd64.go. - -// This function returns the error location in the darwin ABI. -// Discovered by compiling the following code using Clang: -// -// #include -// int getErrno() { -// return errno; -// } -// -//export __error -func libc___error() *int32 - -// getErrno returns the current C errno. It may not have been caused by the last -// call, so it should only be relied upon when the last call indicates an error -// (for example, by returning -1). -func getErrno() Errno { - errptr := libc___error() - return Errno(uintptr(*errptr)) -} - -func (e Errno) Is(target error) bool { - switch target.Error() { - case "permission denied": - return e == EACCES || e == EPERM - case "file already exists": - return e == EEXIST - case "file does not exist": - return e == ENOENT - } - return false -} - -// Source: upstream zerrors_darwin_amd64.go -const ( - DT_BLK = 0x6 - DT_CHR = 0x2 - DT_DIR = 0x4 - DT_FIFO = 0x1 - DT_LNK = 0xa - DT_REG = 0x8 - DT_SOCK = 0xc - DT_UNKNOWN = 0x0 - DT_WHT = 0xe - F_GETFL = 0x3 - F_SETFL = 0x4 - O_NONBLOCK = 0x4 -) - -// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/errno.h.auto.html -const ( - EPERM Errno = 1 - ENOENT Errno = 2 - EACCES Errno = 13 - EEXIST Errno = 17 - EINTR Errno = 4 - ENOTDIR Errno = 20 - EISDIR Errno = 21 - EINVAL Errno = 22 - EMFILE Errno = 24 - EROFS Errno = 30 - EPIPE Errno = 32 - EAGAIN Errno = 35 - ENOTCONN Errno = 57 - ETIMEDOUT Errno = 60 - ENOSYS Errno = 78 - EWOULDBLOCK Errno = EAGAIN -) - -type Signal int - -// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/signal.h -const ( - SIGINT Signal = 2 /* interrupt */ - SIGQUIT Signal = 3 /* quit */ - SIGILL Signal = 4 /* illegal instruction (not reset when caught) */ - SIGTRAP Signal = 5 /* trace trap (not reset when caught) */ - SIGABRT Signal = 6 /* abort() */ - SIGFPE Signal = 8 /* floating point exception */ - SIGKILL Signal = 9 /* kill (cannot be caught or ignored) */ - SIGBUS Signal = 10 /* bus error */ - SIGSEGV Signal = 11 /* segmentation violation */ - SIGPIPE Signal = 13 /* write on a pipe with no one to read it */ - SIGTERM Signal = 15 /* software termination signal from kill */ - SIGCHLD Signal = 20 /* to parent on child stop or exit */ -) - -func (s Signal) Signal() {} - -func (s Signal) String() string { - if 0 <= s && int(s) < len(signals) { - str := signals[s] - if str != "" { - return str - } - } - return "signal " + itoa.Itoa(int(s)) -} - -var signals = [...]string{} - -const ( - Stdin = 0 - Stdout = 1 - Stderr = 2 -) - -const ( - O_RDONLY = 0x0 - O_WRONLY = 0x1 - O_RDWR = 0x2 - O_APPEND = 0x8 - O_SYNC = 0x80 - O_CREAT = 0x200 - O_TRUNC = 0x400 - O_EXCL = 0x800 - - O_CLOEXEC = 0x01000000 -) - -// Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/mman.h.auto.html -const ( - PROT_NONE = 0x00 // no permissions - PROT_READ = 0x01 // pages can be read - PROT_WRITE = 0x02 // pages can be written - PROT_EXEC = 0x04 // pages can be executed - - MAP_SHARED = 0x0001 // share changes - MAP_PRIVATE = 0x0002 // changes are private - - MAP_FILE = 0x0000 // map from file (default) - MAP_ANON = 0x1000 // allocated from memory, swap space - MAP_ANONYMOUS = MAP_ANON -) - -type Timespec struct { - Sec int64 - Nsec int64 -} - -// Unix returns the time stored in ts as seconds plus nanoseconds. -func (ts *Timespec) Unix() (sec int64, nsec int64) { - return int64(ts.Sec), int64(ts.Nsec) -} - -// Source: upstream ztypes_darwin_amd64.go -type Dirent struct { - Ino uint64 - Seekoff uint64 - Reclen uint16 - Namlen uint16 - Type uint8 - Name [1024]int8 - Pad_cgo_0 [3]byte -} - -type Stat_t struct { - Dev int32 - Mode uint16 - Nlink uint16 - Ino uint64 - Uid uint32 - Gid uint32 - Rdev int32 - Pad_cgo_0 [4]byte - Atimespec Timespec - Mtimespec Timespec - Ctimespec Timespec - Btimespec Timespec - Size int64 - Blocks int64 - Blksize int32 - Flags uint32 - Gen uint32 - Lspare int32 - Qspare [2]int64 -} - -// Source: https://github.com/apple/darwin-xnu/blob/main/bsd/sys/_types/_s_ifmt.h -const ( - S_IEXEC = 0x40 - S_IFBLK = 0x6000 - S_IFCHR = 0x2000 - S_IFDIR = 0x4000 - S_IFIFO = 0x1000 - S_IFLNK = 0xa000 - S_IFMT = 0xf000 - S_IFREG = 0x8000 - S_IFSOCK = 0xc000 - S_IFWHT = 0xe000 - S_IREAD = 0x100 - S_IRGRP = 0x20 - S_IROTH = 0x4 - S_IRUSR = 0x100 - S_IRWXG = 0x38 - S_IRWXO = 0x7 - S_IRWXU = 0x1c0 - S_ISGID = 0x400 - S_ISTXT = 0x200 - S_ISUID = 0x800 - S_ISVTX = 0x200 - S_IWGRP = 0x10 - S_IWOTH = 0x2 - S_IWRITE = 0x80 - S_IWUSR = 0x80 - S_IXGRP = 0x8 - S_IXOTH = 0x1 - S_IXUSR = 0x40 -) - -func Stat(path string, p *Stat_t) (err error) { - data := cstring(path) - n := libc_stat(&data[0], unsafe.Pointer(p)) - - if n < 0 { - err = getErrno() - } - return -} - -func Fstat(fd int, p *Stat_t) (err error) { - n := libc_fstat(int32(fd), unsafe.Pointer(p)) - - if n < 0 { - err = getErrno() - } - return -} - -func Lstat(path string, p *Stat_t) (err error) { - data := cstring(path) - n := libc_lstat(&data[0], unsafe.Pointer(p)) - if n < 0 { - err = getErrno() - } - return -} - -func Fdopendir(fd int) (dir uintptr, err error) { - r0 := libc_fdopendir(int32(fd)) - dir = uintptr(r0) - if dir == 0 { - err = getErrno() - } - return -} - -func Pipe2(fds []int, flags int) (err error) { - // Mac only has Pipe, which ignores the flags argument - buf := make([]int32, 2) - fail := int(libc_pipe(&buf[0])) - if fail < 0 { - err = getErrno() - } else { - fds[0] = int(buf[0]) - fds[1] = int(buf[1]) - } - return -} - -func Chmod(path string, mode uint32) (err error) { - data := cstring(path) - fail := int(libc_chmod(&data[0], mode)) - if fail < 0 { - err = getErrno() - } - return -} - -func closedir(dir uintptr) (err error) { - e := libc_closedir(unsafe.Pointer(dir)) - if e != 0 { - err = getErrno() - } - return -} - -func readdir_r(dir uintptr, entry *Dirent, result **Dirent) (err error) { - e1 := libc_readdir_r(unsafe.Pointer(dir), unsafe.Pointer(entry), unsafe.Pointer(result)) - if e1 != 0 { - err = getErrno() - } - return -} - -func Getpagesize() int { - return int(libc_getpagesize()) -} - -// The following RawSockAddr* types have been copied from the Go source tree and -// are here purely to fix build errors. - -type RawSockaddr struct { - Len uint8 - Family uint8 - Data [14]int8 -} - -type RawSockaddrInet4 struct { - Len uint8 - Family uint8 - Port uint16 - Addr [4]byte /* in_addr */ - Zero [8]int8 -} - -type RawSockaddrInet6 struct { - Len uint8 - Family uint8 - Port uint16 - Flowinfo uint32 - Addr [16]byte /* in6_addr */ - Scope_id uint32 -} - -// int pipe(int32 *fds); -// -//export pipe -func libc_pipe(fds *int32) int32 - -// int getpagesize(); -// -//export getpagesize -func libc_getpagesize() int32 - -// int open(const char *pathname, int flags, mode_t mode); -// -//export syscall_libc_open -func libc_open(pathname *byte, flags int32, mode uint32) int32 diff --git a/src/syscall/syscall_libc_darwin_amd64.go b/src/syscall/syscall_libc_darwin_amd64.go deleted file mode 100644 index 1f5528ec54..0000000000 --- a/src/syscall/syscall_libc_darwin_amd64.go +++ /dev/null @@ -1,43 +0,0 @@ -//go:build darwin - -package syscall - -import ( - "unsafe" -) - -// The odd $INODE64 suffix is an Apple compatibility feature, see -// __DARWIN_INODE64 in /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h -// and https://assert.cc/posts/darwin_use_64_bit_inode_vs_ctypes/ -// Without it, you get the old, smaller struct stat from mac os 10.2 or so. -// It not needed on arm64. - -// struct DIR * buf fdopendir(int fd); -// -//export fdopendir$INODE64 -func libc_fdopendir(fd int32) unsafe.Pointer - -// int closedir(struct DIR * buf); -// -//export closedir -func libc_closedir(unsafe.Pointer) int32 - -// int readdir_r(struct DIR * buf, struct dirent *entry, struct dirent **result); -// -//export readdir_r$INODE64 -func libc_readdir_r(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer) int32 - -// int stat(const char *path, struct stat * buf); -// -//export stat$INODE64 -func libc_stat(pathname *byte, ptr unsafe.Pointer) int32 - -// int fstat(int fd, struct stat * buf); -// -//export fstat$INODE64 -func libc_fstat(fd int32, ptr unsafe.Pointer) int32 - -// int lstat(const char *path, struct stat * buf); -// -//export lstat$INODE64 -func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 diff --git a/src/syscall/syscall_libc_darwin_arm64.go b/src/syscall/syscall_libc_darwin_arm64.go deleted file mode 100644 index f9ce3c4e39..0000000000 --- a/src/syscall/syscall_libc_darwin_arm64.go +++ /dev/null @@ -1,37 +0,0 @@ -//go:build darwin - -package syscall - -import ( - "unsafe" -) - -// struct DIR * buf fdopendir(int fd); -// -//export fdopendir -func libc_fdopendir(fd int32) unsafe.Pointer - -// int closedir(struct DIR * buf); -// -//export closedir -func libc_closedir(unsafe.Pointer) int32 - -// int readdir_r(struct DIR * buf, struct dirent *entry, struct dirent **result); -// -//export readdir_r -func libc_readdir_r(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer) int32 - -// int stat(const char *path, struct stat * buf); -// -//export stat -func libc_stat(pathname *byte, ptr unsafe.Pointer) int32 - -// int fstat(int fd, struct stat * buf); -// -//export fstat -func libc_fstat(fd int32, ptr unsafe.Pointer) int32 - -// int lstat(const char *path, struct stat * buf); -// -//export lstat -func libc_lstat(pathname *byte, ptr unsafe.Pointer) int32 diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index 29d79b50c1..479377877b 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -1,4 +1,6 @@ -//go:build wasi || wasip1 +//go:build js || wasip1 || wasip2 + +// Note: also including js in here because it also uses wasi-libc. package syscall @@ -70,9 +72,10 @@ const ( __WASI_FILETYPE_SYMBOLIC_LINK = 7 // ../../lib/wasi-libc/libc-bottom-half/headers/public/__header_fcntl.h - O_RDONLY = 0x04000000 - O_WRONLY = 0x10000000 - O_RDWR = O_RDONLY | O_WRONLY + O_NOFOLLOW = 0x01000000 + O_RDONLY = 0x04000000 + O_WRONLY = 0x10000000 + O_RDWR = O_RDONLY | O_WRONLY O_CREAT = __WASI_OFLAGS_CREAT << 12 O_TRUNC = __WASI_OFLAGS_TRUNC << 12 @@ -103,6 +106,9 @@ const ( // ../../lib/wasi-libc/expected/wasm32-wasi/predefined-macros.txt F_GETFL = 3 F_SETFL = 4 + + // ../../lib/wasi-libc/libc-top-half/musl/arch/generic/bits/ioctl.h + TIOCSPGRP = 0x5410 ) // These values are needed as a stub until Go supports WASI as a full target. @@ -113,16 +119,18 @@ const ( SYS_FCNTL SYS_FCNTL64 SYS_FSTATAT64 + SYS_IOCTL + SYS_MKDIRAT SYS_OPENAT + SYS_READLINKAT SYS_UNLINKAT + SYS_WAITID PATH_MAX = 4096 ) -//go:extern errno -var libcErrno uintptr - func getErrno() error { - return Errno(libcErrno) + // libcErrno is the errno from wasi-libc for wasip1 and the errno for libc_wasip2 for wasip2 + return libcErrno } func (e Errno) Is(target error) bool { @@ -195,6 +203,7 @@ const ( ENOTCONN Errno = 53 /* Socket is not connected */ ENOTDIR Errno = 54 /* Not a directory */ ENOTEMPTY Errno = 55 /* Directory not empty */ + ENOTRECOVERABLE Errno = 56 /* State not recoverable */ ENOTSOCK Errno = 57 /* Socket operation on non-socket */ ESOCKTNOSUPPORT Errno = 58 /* Socket type not supported */ EOPNOTSUPP Errno = 58 /* Operation not supported on transport endpoint */ @@ -213,10 +222,15 @@ const ( ESRCH Errno = 71 /* No such process */ ESTALE Errno = 72 ETIMEDOUT Errno = 73 /* Connection timed out */ + ETXTBSY Errno = 74 /* Text file busy */ EXDEV Errno = 75 /* Cross-device link */ ENOTCAPABLE Errno = 76 /* Extension: Capabilities insufficient. */ + + EWASIERROR Errno = 255 /* Unknown WASI error */ ) +// TODO(ydnar): remove Timespec for WASI Preview 2 (seconds is uint64). +// // https://github.com/WebAssembly/wasi-libc/blob/main/libc-bottom-half/headers/public/__struct_timespec.h type Timespec struct { Sec int32 @@ -344,7 +358,7 @@ func Fdclosedir(dir uintptr) (err error) { func Readdir(dir uintptr) (dirent *Dirent, err error) { // There might be a leftover errno value in the global variable, so we have // to clear it before calling readdir because we cannot know whether a nil - // return means that we reached EOF or that an error occured. + // return means that we reached EOF or that an error occurred. libcErrno = 0 dirent = libc_readdir(unsafe.Pointer(dir)) @@ -397,6 +411,7 @@ func Chmod(path string, mode uint32) (err error) { return Lstat(path, &stat) } +// TODO: should this return runtime.wasmPageSize? func Getpagesize() int { return libc_getpagesize() } @@ -410,6 +425,11 @@ type Utsname struct { Domainname [65]int8 } +//go:linkname faccessat syscall.Faccessat +func faccessat(dirfd int, path string, mode uint32, flags int) (err error) { + return ENOSYS +} + // Stub Utsname, needed because WASI pretends to be linux/arm. func Uname(buf *Utsname) (err error) @@ -421,6 +441,13 @@ type RawSockaddrInet6 struct { // stub } +func RandomGet(b []byte) error { + if len(b) > 0 { + libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b))) + } + return nil +} + // This is a stub, it is not functional. func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) @@ -466,3 +493,8 @@ func libc_fdclosedir(unsafe.Pointer) int32 // //export readdir func libc_readdir(unsafe.Pointer) *Dirent + +// void arc4random_buf(void *buf, size_t buflen); +// +//export arc4random_buf +func libc_arc4random_buf(buf unsafe.Pointer, buflen uint) diff --git a/src/syscall/syscall_nonhosted.go b/src/syscall/syscall_nonhosted.go index b56d71af64..a0965692f4 100644 --- a/src/syscall/syscall_nonhosted.go +++ b/src/syscall/syscall_nonhosted.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasm_unknown +//go:build baremetal || wasm_unknown package syscall @@ -87,48 +87,6 @@ const ( MAP_ANONYMOUS = MAP_ANON ) -func runtime_envs() []string - -func Getenv(key string) (value string, found bool) { - env := runtime_envs() - for _, keyval := range env { - // Split at '=' character. - var k, v string - for i := 0; i < len(keyval); i++ { - if keyval[i] == '=' { - k = keyval[:i] - v = keyval[i+1:] - } - } - if k == key { - return v, true - } - } - return "", false -} - -func Setenv(key, val string) (err error) { - // stub for now - return ENOSYS -} - -func Unsetenv(key string) (err error) { - // stub for now - return ENOSYS -} - -func Clearenv() (err error) { - // stub for now - return ENOSYS -} - -func Environ() []string { - env := runtime_envs() - envCopy := make([]string, len(env)) - copy(envCopy, env) - return envCopy -} - func Open(path string, mode int, perm uint32) (fd int, err error) { return 0, ENOSYS } diff --git a/src/syscall/syscall_unix.go b/src/syscall/syscall_unix.go index 23d81fb891..b5b8f4eb78 100644 --- a/src/syscall/syscall_unix.go +++ b/src/syscall/syscall_unix.go @@ -1,3 +1,5 @@ +//go:build linux || unix + package syscall func Exec(argv0 string, argv []string, envv []string) (err error) diff --git a/src/syscall/tables_nonhosted.go b/src/syscall/tables_nonhosted.go index e66620cbf4..a45834827e 100644 --- a/src/syscall/tables_nonhosted.go +++ b/src/syscall/tables_nonhosted.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build baremetal || nintendoswitch || js || wasm_unknown +//go:build baremetal || nintendoswitch || wasm_unknown package syscall diff --git a/src/testing/is_wasi_no_test.go b/src/testing/is_wasi_no_test.go deleted file mode 100644 index 630467ec0b..0000000000 --- a/src/testing/is_wasi_no_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 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 !wasi && !wasip1 - -package testing_test - -const isWASI = false diff --git a/src/testing/is_wasi_test.go b/src/testing/is_wasi_test.go deleted file mode 100644 index e20e15fc04..0000000000 --- a/src/testing/is_wasi_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2016 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 wasi || wasip1 - -package testing_test - -const isWASI = true diff --git a/src/testing/testing.go b/src/testing/testing.go index 8429e92212..c4449cbb0a 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -17,6 +17,8 @@ import ( "io/fs" "math/rand" "os" + "path/filepath" + "runtime" "strconv" "strings" "time" @@ -390,6 +392,49 @@ func (c *common) Setenv(key, value string) { } } +// Chdir calls os.Chdir(dir) and uses Cleanup to restore the current +// working directory to its original value after the test. On Unix, it +// also sets PWD environment variable for the duration of the test. +// +// Because Chdir affects the whole process, it cannot be used +// in parallel tests or tests with parallel ancestors. +func (c *common) Chdir(dir string) { + // Note: function copied from the Go 1.24.0 source tree. + + oldwd, err := os.Open(".") + if err != nil { + c.Fatal(err) + } + if err := os.Chdir(dir); err != nil { + c.Fatal(err) + } + // On POSIX platforms, PWD represents “an absolute pathname of the + // current working directory.” Since we are changing the working + // directory, we should also set or update PWD to reflect that. + switch runtime.GOOS { + case "windows", "plan9": + // Windows and Plan 9 do not use the PWD variable. + default: + if !filepath.IsAbs(dir) { + dir, err = os.Getwd() + if err != nil { + c.Fatal(err) + } + } + c.Setenv("PWD", dir) + } + c.Cleanup(func() { + err := oldwd.Chdir() + oldwd.Close() + if err != nil { + // It's not safe to continue with tests if we can't + // get back to the original working directory. Since + // we are holding a dirfd, this is highly unlikely. + panic("testing.Chdir: " + err.Error()) + } + }) +} + // runCleanup is called at the end of the test. func (c *common) runCleanup() { for { @@ -463,10 +508,23 @@ func (t *T) Run(name string, f func(t *T)) bool { return !sub.failed } +// Deadline reports the time at which the test binary will have +// exceeded the timeout specified by the -timeout flag. +// +// The ok result is false if the -timeout flag indicates “no timeout” (0). +// For now tinygo always return 0, false. +// +// Not Implemented. +func (t *T) Deadline() (deadline time.Time, ok bool) { + deadline = t.context.deadline + return deadline, !deadline.IsZero() +} + // testContext holds all fields that are common to all tests. This includes // synchronization primitives to run at most *parallel tests. type testContext struct { - match *matcher + match *matcher + deadline time.Time } func newTestContext(m *matcher) *testContext { diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 631a313414..eecba519af 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -13,6 +13,7 @@ import ( "io/fs" "os" "path/filepath" + "runtime" "testing" ) @@ -25,7 +26,7 @@ func TestMain(m *testing.M) { } func TestTempDirInCleanup(t *testing.T) { - if isWASI { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } @@ -62,7 +63,7 @@ func TestTempDirInBenchmark(t *testing.T) { } func TestTempDir(t *testing.T) { - if isWASI { + if runtime.GOOS == "wasip1" || runtime.GOOS == "wasip2" { t.Log("Skipping. TODO: implement RemoveAll for wasi") return } diff --git a/src/tinygo/runtime.go b/src/tinygo/runtime.go new file mode 100644 index 0000000000..c92417ebc6 --- /dev/null +++ b/src/tinygo/runtime.go @@ -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 +) diff --git a/src/unique/handle.go b/src/unique/handle.go new file mode 100644 index 0000000000..67c925d2de --- /dev/null +++ b/src/unique/handle.go @@ -0,0 +1,74 @@ +// Package unique implements the upstream Go unique package for TinyGo. +// +// It is not a full implementation: while it should behave the same way, it +// doesn't free unreferenced uniqued objects. +package unique + +import ( + "sync" + "unsafe" +) + +var ( + // We use a two-level map because that way it's easier to store and retrieve + // values. + globalMap map[unsafe.Pointer]any // map value type is always map[T]Handle[T] + + globalMapMutex sync.Mutex +) + +// Unique handle for the given value. Comparing two handles is cheap. +type Handle[T comparable] struct { + value *T +} + +// Value returns a shallow copy of the T value that produced the Handle. +func (h Handle[T]) Value() T { + return *h.value +} + +// Make a new unqique handle for the given value. +func Make[T comparable](value T) Handle[T] { + // Very simple implementation of the unique package. This is much, *much* + // simpler than the upstream implementation. Sadly it's not possible to + // reuse the upstream version because it relies on implementation details of + // the upstream runtime. + // It probably isn't as efficient as the upstream version, but the first + // goal here is compatibility. If the performance is a problem, it can be + // optimized later. + + globalMapMutex.Lock() + + // The map isn't initialized at program startup (and after a test run), so + // create it. + if globalMap == nil { + globalMap = make(map[unsafe.Pointer]any) + } + + // Retrieve the type-specific map, creating it if not yet present. + typeptr, _ := decomposeInterface(value) + var typeSpecificMap map[T]Handle[T] + if typeSpecificMapValue, ok := globalMap[typeptr]; !ok { + typeSpecificMap = make(map[T]Handle[T]) + globalMap[typeptr] = typeSpecificMap + } else { + typeSpecificMap = typeSpecificMapValue.(map[T]Handle[T]) + } + + // Retrieve the handle for the value, creating it if it isn't created yet. + var handle Handle[T] + if h, ok := typeSpecificMap[value]; !ok { + var clone T = value + handle.value = &clone + typeSpecificMap[value] = handle + } else { + handle = h + } + + globalMapMutex.Unlock() + + return handle +} + +//go:linkname decomposeInterface runtime.decomposeInterface +func decomposeInterface(i interface{}) (unsafe.Pointer, unsafe.Pointer) diff --git a/src/unique/handle_test.go b/src/unique/handle_test.go new file mode 100644 index 0000000000..864b11f232 --- /dev/null +++ b/src/unique/handle_test.go @@ -0,0 +1,76 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file is a copy of src/unique/handle_test.go in upstream Go, but with +// some parts removed that rely on Go runtime implementation details. + +package unique + +import ( + "fmt" + "reflect" + "testing" +) + +// Set up special types. Because the internal maps are sharded by type, +// this will ensure that we're not overlapping with other tests. +type testString string +type testIntArray [4]int +type testEface any +type testStringArray [3]string +type testStringStruct struct { + a string +} +type testStringStructArrayStruct struct { + s [2]testStringStruct +} +type testStruct struct { + z float64 + b string +} + +func TestHandle(t *testing.T) { + testHandle[testString](t, "foo") + testHandle[testString](t, "bar") + testHandle[testString](t, "") + testHandle[testIntArray](t, [4]int{7, 77, 777, 7777}) + //testHandle[testEface](t, nil) // requires Go 1.20 + testHandle[testStringArray](t, [3]string{"a", "b", "c"}) + testHandle[testStringStruct](t, testStringStruct{"x"}) + testHandle[testStringStructArrayStruct](t, testStringStructArrayStruct{ + s: [2]testStringStruct{testStringStruct{"y"}, testStringStruct{"z"}}, + }) + testHandle[testStruct](t, testStruct{0.5, "184"}) +} + +func testHandle[T comparable](t *testing.T, value T) { + name := reflect.TypeFor[T]().Name() + t.Run(fmt.Sprintf("%s/%#v", name, value), func(t *testing.T) { + t.Parallel() + + v0 := Make(value) + v1 := Make(value) + + if v0.Value() != v1.Value() { + t.Error("v0.Value != v1.Value") + } + if v0.Value() != value { + t.Errorf("v0.Value not %#v", value) + } + if v0 != v1 { + t.Error("v0 != v1") + } + + drainMaps(t) + }) +} + +// drainMaps ensures that the internal maps are drained. +func drainMaps(t *testing.T) { + t.Helper() + + globalMapMutex.Lock() + globalMap = nil + globalMapMutex.Unlock() +} diff --git a/stacksize/stacksize.go b/stacksize/stacksize.go index 8cccbaec61..2cc099f2ac 100644 --- a/stacksize/stacksize.go +++ b/stacksize/stacksize.go @@ -224,7 +224,7 @@ func CallGraph(f *elf.File, callsIndirectFunction []string) (map[string][]*CallN for name, size := range knownFrameSizes { if sym, ok := symbolNames[name]; ok { if len(sym) > 1 { - return nil, fmt.Errorf("expected zero or one occurence of the symbol %s, found %d", name, len(sym)) + return nil, fmt.Errorf("expected zero or one occurrence of the symbol %s, found %d", name, len(sym)) } sym[0].FrameSize = size sym[0].FrameSizeType = Bounded diff --git a/targets/adafruit-esp32-feather-v2.json b/targets/adafruit-esp32-feather-v2.json new file mode 100644 index 0000000000..9db914dbde --- /dev/null +++ b/targets/adafruit-esp32-feather-v2.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32"], + "build-tags": ["adafruit_esp32_feather_v2"] +} diff --git a/targets/arm.ld b/targets/arm.ld index 39b5c75ddb..cdf5b1dd43 100644 --- a/targets/arm.ld +++ b/targets/arm.ld @@ -9,6 +9,7 @@ SECTIONS .text : { KEEP(*(.isr_vector)) + KEEP(*(.after_isr_vector)) /* for the RP2350 */ *(.text) *(.text.*) *(.rodata) diff --git a/targets/badger2040-w.json b/targets/badger2040-w.json new file mode 100644 index 0000000000..7b7e729d17 --- /dev/null +++ b/targets/badger2040-w.json @@ -0,0 +1,13 @@ +{ + "inherits": [ + "rp2040" + ], + "serial-port": ["2e8a:0003"], + "build-tags": ["badger2040_w", "cyw43439"], + "ldflags": [ + "--defsym=__flash_size=1020K" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} diff --git a/targets/cortex-m3.json b/targets/cortex-m3.json index bb11efea5d..44d992a91f 100644 --- a/targets/cortex-m3.json +++ b/targets/cortex-m3.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv7m-unknown-unknown-eabi", "cpu": "cortex-m3", - "features": "+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/cortex-m33.json b/targets/cortex-m33.json index 556b1e2af1..a5582c0823 100644 --- a/targets/cortex-m33.json +++ b/targets/cortex-m33.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv8m.main-unknown-unknown-eabi", "cpu": "cortex-m33", - "features": "+armv8-m.main,+dsp,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv8-m.main,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/cortex-m4.json b/targets/cortex-m4.json index 58b1673647..80ed66d407 100644 --- a/targets/cortex-m4.json +++ b/targets/cortex-m4.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv7em-unknown-unknown-eabi", "cpu": "cortex-m4", - "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/cortex-m7.json b/targets/cortex-m7.json index e9abf1de41..1a39c6a9ec 100644 --- a/targets/cortex-m7.json +++ b/targets/cortex-m7.json @@ -2,5 +2,5 @@ "inherits": ["cortex-m"], "llvm-target": "thumbv7em-unknown-unknown-eabi", "cpu": "cortex-m7", - "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + "features": "+armv7e-m,+dsp,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } diff --git a/targets/elecrow-rp2040.json b/targets/elecrow-rp2040.json new file mode 100644 index 0000000000..46feadc4de --- /dev/null +++ b/targets/elecrow-rp2040.json @@ -0,0 +1,12 @@ +{ + "inherits": ["rp2040"], + "build-tags": ["elecrow_rp2040", "comboat_fw"], + "serial-port": ["2e8a:000a"], + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=8M" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} diff --git a/targets/elecrow-rp2350.json b/targets/elecrow-rp2350.json new file mode 100644 index 0000000000..75876ed5ff --- /dev/null +++ b/targets/elecrow-rp2350.json @@ -0,0 +1,12 @@ +{ + "inherits": ["rp2350"], + "build-tags": ["elecrow_rp2350", "comboat_fw"], + "serial-port": ["2e8a:000f"], + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=8M" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} diff --git a/targets/esp-c3-32s-kit.json b/targets/esp-c3-32s-kit.json new file mode 100644 index 0000000000..6f787e7dbb --- /dev/null +++ b/targets/esp-c3-32s-kit.json @@ -0,0 +1,5 @@ +{ + "inherits": ["esp32c3"], + "build-tags": ["esp_c3_32s_kit"], + "serial-port": ["1a86:7523"] +} diff --git a/targets/esp32.ld b/targets/esp32.ld index a8d161288e..6818ce3190 100644 --- a/targets/esp32.ld +++ b/targets/esp32.ld @@ -29,7 +29,7 @@ SECTIONS */ .text : ALIGN(4) { - *(.literal.text.call_start_cpu0) + *(.literal.call_start_cpu0) *(.text.call_start_cpu0) *(.literal .text) *(.literal.* .text.*) diff --git a/targets/esp32c3-supermini.json b/targets/esp32c3-supermini.json new file mode 100644 index 0000000000..3e4e40895e --- /dev/null +++ b/targets/esp32c3-supermini.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32c3"], + "build-tags": ["esp32c3_supermini"] +} diff --git a/targets/esp32c3.json b/targets/esp32c3.json index 5a1e70626d..900c4845eb 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], - "features": "+32bit,+c,+m,-a,-d,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zicsr,-zifencei,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+c,+m,+zmmul,-a,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["esp32c3", "esp"], "serial": "usb", "rtlib": "compiler-rt", diff --git a/targets/fe310.json b/targets/fe310.json index a51488358d..cd92c4fb1b 100644 --- a/targets/fe310.json +++ b/targets/fe310.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], "cpu": "sifive-e31", - "features": "+32bit,+a,+c,+m,-d,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zicsr,-zifencei,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+a,+c,+m,+zmmul,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["fe310", "sifive"] } diff --git a/targets/hw-651-s110v8.json b/targets/hw-651-s110v8.json new file mode 100644 index 0000000000..50030cecad --- /dev/null +++ b/targets/hw-651-s110v8.json @@ -0,0 +1,3 @@ +{ + "inherits": ["hw-651", "nrf51-s110v8"] +} diff --git a/targets/hw-651.json b/targets/hw-651.json new file mode 100644 index 0000000000..a5ed5eebc4 --- /dev/null +++ b/targets/hw-651.json @@ -0,0 +1,7 @@ +{ + "inherits": ["nrf51"], + "build-tags": ["hw_651"], + "serial": "uart", + "flash-method": "openocd", + "openocd-interface": "cmsis-dap" +} diff --git a/targets/k210.json b/targets/k210.json index 778e403c67..2140f459e4 100644 --- a/targets/k210.json +++ b/targets/k210.json @@ -1,6 +1,6 @@ { "inherits": ["riscv64"], - "features": "+64bit,+a,+c,+d,+f,+m,+zicsr,+zifencei,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+64bit,+a,+c,+d,+f,+m,+zicsr,+zifencei,+zmmul,-b,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["k210", "kendryte"], "code-model": "medium" } diff --git a/targets/m5paper.json b/targets/m5paper.json new file mode 100644 index 0000000000..9ee5d43b4c --- /dev/null +++ b/targets/m5paper.json @@ -0,0 +1,5 @@ +{ + "inherits": ["esp32"], + "build-tags": ["m5paper"], + "serial-port": ["1a86:55d4"] +} diff --git a/targets/mksnanov3.json b/targets/mksnanov3.json index 9b202d3abc..fc57a71d9b 100644 --- a/targets/mksnanov3.json +++ b/targets/mksnanov3.json @@ -7,6 +7,7 @@ "src/device/stm32/stm32f407.s" ], "flash-method": "openocd", - "openocd-interface": "stlink-v2", - "openocd-target": "stm32f4x" + "openocd-interface": "stlink", + "openocd-target": "stm32f4x", + "openocd-commands": ["stm32f4x.cpu configure -event reset-init { adapter speed 1800 }"] } diff --git a/targets/nintendoswitch.json b/targets/nintendoswitch.json index e86cfc1712..f83f8fcc10 100644 --- a/targets/nintendoswitch.json +++ b/targets/nintendoswitch.json @@ -1,7 +1,7 @@ { "llvm-target": "aarch64", "cpu": "cortex-a57", - "features": "+aes,+crc,+fp-armv8,+neon,+sha2,+v8a,-fmv", + "features": "+aes,+crc,+fp-armv8,+neon,+perfmon,+sha2,+v8a,-fmv", "build-tags": ["nintendoswitch", "arm64"], "scheduler": "tasks", "goos": "linux", diff --git a/targets/nucleo-l476rg.json b/targets/nucleo-l476rg.json new file mode 100644 index 0000000000..73eddee813 --- /dev/null +++ b/targets/nucleo-l476rg.json @@ -0,0 +1,12 @@ +{ + "inherits": ["cortex-m4"], + "build-tags": ["nucleol476rg", "stm32l476", "stm32l4x6", "stm32l4", "stm32"], + "serial": "uart", + "linkerscript": "targets/stm32l4x6.ld", + "extra-files": [ + "src/device/stm32/stm32l4x6.s" + ], + "flash-method": "openocd", + "openocd-interface": "stlink-v2-1", + "openocd-target": "stm32l4x" + } diff --git a/targets/pca10059-s140v7.json b/targets/pca10059-s140v7.json new file mode 100644 index 0000000000..ca302c3734 --- /dev/null +++ b/targets/pca10059-s140v7.json @@ -0,0 +1,3 @@ +{ + "inherits": ["pca10059", "nrf52840-s140v7"] +} diff --git a/targets/pga2350.json b/targets/pga2350.json new file mode 100644 index 0000000000..5afe89ec07 --- /dev/null +++ b/targets/pga2350.json @@ -0,0 +1,7 @@ +{ + "inherits": ["rp2350b"], + "build-tags": ["pga2350"], + "ldflags": [ + "--defsym=__flash_size=16M" + ] +} \ No newline at end of file diff --git a/targets/pico-plus2.json b/targets/pico-plus2.json new file mode 100644 index 0000000000..307c1b72da --- /dev/null +++ b/targets/pico-plus2.json @@ -0,0 +1,11 @@ +{ + "inherits": [ + "rp2350b" + ], + "build-tags": ["pico_plus2"], + "ldflags": [ + "--defsym=__flash_size=16M" + ], + "serial-port": ["2e8a:000F"], + "default-stack-size": 8192 +} diff --git a/targets/pico-w.json b/targets/pico-w.json new file mode 100644 index 0000000000..0eff1afca5 --- /dev/null +++ b/targets/pico-w.json @@ -0,0 +1,4 @@ +{ + "inherits": ["pico"], + "build-tags": ["pico-w", "cyw43439"] +} diff --git a/targets/pico2-w.json b/targets/pico2-w.json new file mode 100644 index 0000000000..0f1349645d --- /dev/null +++ b/targets/pico2-w.json @@ -0,0 +1,4 @@ +{ + "inherits": ["pico2"], + "build-tags": ["pico2-w", "cyw43439"] +} diff --git a/targets/pico2.json b/targets/pico2.json new file mode 100644 index 0000000000..af156d54d8 --- /dev/null +++ b/targets/pico2.json @@ -0,0 +1,11 @@ +{ + "inherits": [ + "rp2350" + ], + "build-tags": ["pico2"], + "serial-port": ["2e8a:000A"], + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=4M" + ] +} diff --git a/targets/rak4631.json b/targets/rak4631.json new file mode 100644 index 0000000000..cbd8221300 --- /dev/null +++ b/targets/rak4631.json @@ -0,0 +1,6 @@ +{ + "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], + "build-tags": ["rak4631"], + "serial-port": ["239a:8029"], + "msd-volume-name": ["RAK4631"] +} diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index 7f9c5e395d..90f1c312fc 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -1,8 +1,8 @@ { "inherits": ["riscv32"], - "features": "+32bit,+a,+c,+m,-d,-e,-experimental-smaia,-experimental-ssaia,-experimental-zacas,-experimental-zfa,-experimental-zfbfmin,-experimental-zicond,-experimental-zihintntl,-experimental-ztso,-experimental-zvbb,-experimental-zvbc,-experimental-zvfbfmin,-experimental-zvfbfwma,-experimental-zvkg,-experimental-zvkn,-experimental-zvknc,-experimental-zvkned,-experimental-zvkng,-experimental-zvknha,-experimental-zvknhb,-experimental-zvks,-experimental-zvksc,-experimental-zvksed,-experimental-zvksg,-experimental-zvksh,-experimental-zvkt,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xcvbitmanip,-xcvmac,-xsfcie,-xsfvcp,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmp,-zcmt,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zicntr,-zicsr,-zifencei,-zihintpause,-zihpm,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfh,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "features": "+32bit,+a,+c,+m,+zmmul,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", "build-tags": ["virt", "qemu"], - "default-stack-size": 4096, + "default-stack-size": 8192, "linkerscript": "targets/riscv-qemu.ld", - "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -kernel {}" + "emulator": "qemu-system-riscv32 -machine virt -nographic -bios none -device virtio-rng-device -kernel {}" } diff --git a/targets/riscv-qemu.ld b/targets/riscv-qemu.ld index ab344571e2..822c00d10b 100644 --- a/targets/riscv-qemu.ld +++ b/targets/riscv-qemu.ld @@ -1,16 +1,16 @@ /* Memory map: * https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c - * RAM and flash are set to 1MB each. That should be enough for the foreseeable - * future. QEMU does not seem to limit the flash/RAM size and in fact doesn't - * seem to differentiate between it. + * Looks like we can use any address starting from 0x80000000 (so 2GB of space). + * However, using a large space slows down tests. */ MEMORY { - FLASH_TEXT (rw) : ORIGIN = 0x80000000, LENGTH = 0x100000 - RAM (xrw) : ORIGIN = 0x80100000, LENGTH = 0x100000 + RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 100M } +REGION_ALIAS("FLASH_TEXT", RAM) + _stack_size = 2K; INCLUDE "targets/riscv.ld" diff --git a/targets/rp2350.json b/targets/rp2350.json new file mode 100644 index 0000000000..0487aa14d9 --- /dev/null +++ b/targets/rp2350.json @@ -0,0 +1,22 @@ +{ + "inherits": ["cortex-m33"], + "build-tags": ["rp2350", "rp"], + "flash-1200-bps-reset": "true", + "flash-method": "msd", + "serial": "usb", + "msd-volume-name": ["RP2350"], + "msd-firmware-name": "firmware.uf2", + "binary-format": "uf2", + "uf2-family-id": "0xe48bff59","comment":"See page 393 of RP2350 datasheet: RP2350 Arm Secure image (i.e. one intended to be booted directly by the bootrom)", + "extra-files": [ + "src/device/rp/rp2350.s", + "targets/rp2350_embedded_block.s" + ], + "ldflags": [ + "--defsym=__flash_size=2M" + ], + "linkerscript": "targets/rp2350.ld", + "openocd-interface": "picoprobe", + "openocd-transport": "swd", + "openocd-target": "rp2350" +} diff --git a/targets/rp2350.ld b/targets/rp2350.ld new file mode 100644 index 0000000000..5296a1fb12 --- /dev/null +++ b/targets/rp2350.ld @@ -0,0 +1,23 @@ +/* See Rust for a more complete reference: https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal-examples/memory.x */ +MEMORY +{ + /* 2MiB safe default. */ + FLASH : ORIGIN = 0x10000000, LENGTH = __flash_size + /* RAM consists of 8 banks, SRAM0..SRAM7 with striped mapping. */ + SRAM : ORIGIN = 0x20000000, LENGTH = 512k + /* Banks 8 and 9 use direct mapping which can be + specailized for applications where predictable access time is beneficial. + i.e: Separate stacks for core0 and core1. */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4k + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4k + FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = __flash_size + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 512k +} + +_stack_size = 2K; + +SECTIONS +{ +} + +INCLUDE "targets/arm.ld" diff --git a/targets/rp2350_embedded_block.s b/targets/rp2350_embedded_block.s new file mode 100644 index 0000000000..f6202ed859 --- /dev/null +++ b/targets/rp2350_embedded_block.s @@ -0,0 +1,10 @@ +// Minimum viable block image from datasheet section 5.9.5.1, "Minimum Arm IMAGE_DEF" +.section .after_isr_vector, "a" +.p2align 2 +embedded_block: +.word 0xffffded3 +.word 0x10210142 +.word 0x000001ff +.word 0x00000000 +.word 0xab123579 +embedded_block_end: diff --git a/targets/rp2350b.json b/targets/rp2350b.json new file mode 100644 index 0000000000..5db4d48492 --- /dev/null +++ b/targets/rp2350b.json @@ -0,0 +1,5 @@ +{ + "inherits": ["rp2350"], + "build-tags": ["rp2350b"], + "serial-port": ["2e8a:000f"] +} \ No newline at end of file diff --git a/targets/stm32l4x6.ld b/targets/stm32l4x6.ld new file mode 100644 index 0000000000..4f1ed77322 --- /dev/null +++ b/targets/stm32l4x6.ld @@ -0,0 +1,11 @@ + +MEMORY +{ + FLASH_TEXT (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K + RAM2 (xrw) : ORIGIN = 0x10000000, LENGTH = 32K +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" diff --git a/targets/tiny2350.json b/targets/tiny2350.json new file mode 100644 index 0000000000..660e096468 --- /dev/null +++ b/targets/tiny2350.json @@ -0,0 +1,10 @@ +{ + "inherits": [ + "rp2350" + ], + "build-tags": ["tiny2350"], + "ldflags": [ + "--defsym=__flash_size=4M" + ], + "serial-port": ["2e8a:000f"] +} diff --git a/targets/tkey.json b/targets/tkey.json new file mode 100644 index 0000000000..3a52cae284 --- /dev/null +++ b/targets/tkey.json @@ -0,0 +1,13 @@ +{ + "inherits": ["riscv32"], + "build-tags": ["tkey"], + "features": "+32bit,+c,+zmmul,-a,-b,-d,-e,-experimental-smmpm,-experimental-smnpm,-experimental-ssnpm,-experimental-sspm,-experimental-ssqosid,-experimental-supm,-experimental-zacas,-experimental-zalasr,-experimental-zicfilp,-experimental-zicfiss,-f,-h,-m,-relax,-shcounterenw,-shgatpa,-shtvala,-shvsatpa,-shvstvala,-shvstvecd,-smaia,-smcdeleg,-smcsrind,-smepmp,-smstateen,-ssaia,-ssccfg,-ssccptr,-sscofpmf,-sscounterenw,-sscsrind,-ssstateen,-ssstrict,-sstc,-sstvala,-sstvecd,-ssu64xl,-svade,-svadu,-svbare,-svinval,-svnapot,-svpbmt,-v,-xcvalu,-xcvbi,-xcvbitmanip,-xcvelw,-xcvmac,-xcvmem,-xcvsimd,-xesppie,-xsfcease,-xsfvcp,-xsfvfnrclipxfqf,-xsfvfwmaccqqq,-xsfvqmaccdod,-xsfvqmaccqoq,-xsifivecdiscarddlone,-xsifivecflushdlone,-xtheadba,-xtheadbb,-xtheadbs,-xtheadcmo,-xtheadcondmov,-xtheadfmemidx,-xtheadmac,-xtheadmemidx,-xtheadmempair,-xtheadsync,-xtheadvdot,-xventanacondops,-xwchc,-za128rs,-za64rs,-zaamo,-zabha,-zalrsc,-zama16b,-zawrs,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zca,-zcb,-zcd,-zce,-zcf,-zcmop,-zcmp,-zcmt,-zdinx,-zfa,-zfbfmin,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zic64b,-zicbom,-zicbop,-zicboz,-ziccamoa,-ziccif,-zicclsm,-ziccrse,-zicntr,-zicond,-zicsr,-zifencei,-zihintntl,-zihintpause,-zihpm,-zimop,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-ztso,-zvbb,-zvbc,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-zvfbfmin,-zvfbfwma,-zvfh,-zvfhmin,-zvkb,-zvkg,-zvkn,-zvknc,-zvkned,-zvkng,-zvknha,-zvknhb,-zvks,-zvksc,-zvksed,-zvksg,-zvksh,-zvkt,-zvl1024b,-zvl128b,-zvl16384b,-zvl2048b,-zvl256b,-zvl32768b,-zvl32b,-zvl4096b,-zvl512b,-zvl64b,-zvl65536b,-zvl8192b", + "cflags": [ + "-march=rv32iczmmul" + ], + "linkerscript": "targets/tkey.ld", + "scheduler": "none", + "gc": "conservative", + "flash-command": "tkey-runapp {bin}", + "serial": "uart" +} diff --git a/targets/tkey.ld b/targets/tkey.ld new file mode 100644 index 0000000000..09becf4036 --- /dev/null +++ b/targets/tkey.ld @@ -0,0 +1,11 @@ + +MEMORY +{ + RAM (rwx) : ORIGIN = 0x40000000, LENGTH = 0x20000 /* 128 KB */ +} + +REGION_ALIAS("FLASH_TEXT", RAM); + +_stack_size = 2K; + +INCLUDE "targets/riscv.ld" diff --git a/targets/wasi.json b/targets/wasi.json index 1e1fff415f..21d5694178 100644 --- a/targets/wasi.json +++ b/targets/wasi.json @@ -1,26 +1,3 @@ { - "llvm-target": "wasm32-unknown-wasi", - "cpu": "generic", - "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", - "build-tags": ["tinygo.wasm", "wasi"], - "goos": "linux", - "goarch": "arm", - "linker": "wasm-ld", - "libc": "wasi-libc", - "rtlib": "compiler-rt", - "scheduler": "asyncify", - "default-stack-size": 65536, - "cflags": [ - "-mbulk-memory", - "-mnontrapping-fptoint", - "-msign-ext" - ], - "ldflags": [ - "--stack-first", - "--no-demangle" - ], - "extra-files": [ - "src/runtime/asm_tinygowasm.S" - ], - "emulator": "wasmtime --dir={tmpDir}::/tmp {}" + "inherits": ["wasip1"] } diff --git a/targets/wasip1.json b/targets/wasip1.json new file mode 100644 index 0000000000..25ac7c3a6c --- /dev/null +++ b/targets/wasip1.json @@ -0,0 +1,29 @@ +{ + "llvm-target": "wasm32-unknown-wasi", + "cpu": "generic", + "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", + "build-tags": ["tinygo.wasm"], + "goos": "wasip1", + "goarch": "wasm", + "linker": "wasm-ld", + "libc": "wasi-libc", + "rtlib": "compiler-rt", + "gc": "precise", + "scheduler": "asyncify", + "default-stack-size": 65536, + "cflags": [ + "-mbulk-memory", + "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", + "-msign-ext" + ], + "ldflags": [ + "--stack-first", + "--no-demangle" + ], + "extra-files": [ + "src/runtime/asm_tinygowasm.S" + ], + "emulator": "wasmtime run --dir={tmpDir}::/tmp {}" +} diff --git a/targets/wasip2.json b/targets/wasip2.json new file mode 100644 index 0000000000..66e79eda5a --- /dev/null +++ b/targets/wasip2.json @@ -0,0 +1,33 @@ +{ + "llvm-target": "wasm32-unknown-wasi", + "cpu": "generic", + "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", + "build-tags": ["tinygo.wasm", "wasip2"], + "buildmode": "c-shared", + "goos": "linux", + "goarch": "arm", + "linker": "wasm-ld", + "libc": "wasmbuiltins", + "rtlib": "compiler-rt", + "gc": "precise", + "scheduler": "asyncify", + "default-stack-size": 65536, + "cflags": [ + "-mbulk-memory", + "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", + "-msign-ext" + ], + "ldflags": [ + "--stack-first", + "--no-demangle", + "--no-entry" + ], + "extra-files": [ + "src/runtime/asm_tinygowasm.S" + ], + "emulator": "wasmtime run --wasm component-model -Sinherit-network -Sallow-ip-name-lookup --dir={tmpDir}::/tmp {}", + "wit-package": "{root}/lib/wasi-cli/wit/", + "wit-world": "wasi:cli/command" +} diff --git a/targets/wasm-unknown.json b/targets/wasm-unknown.json index 903afa49e8..d64fa575cb 100644 --- a/targets/wasm-unknown.json +++ b/targets/wasm-unknown.json @@ -1,28 +1,30 @@ { "llvm-target": "wasm32-unknown-unknown", "cpu": "generic", - "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,-bulk-memory", + "features": "+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm", "wasm_unknown"], + "buildmode": "c-shared", "goos": "linux", "goarch": "arm", "linker": "wasm-ld", "rtlib": "compiler-rt", + "libc": "wasmbuiltins", "scheduler": "none", "gc": "leaking", "default-stack-size": 4096, "cflags": [ - "-mno-bulk-memory", "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", "-msign-ext" ], "ldflags": [ "--stack-first", "--no-demangle", - "--no-entry", - "--import-memory" + "--no-entry" ], "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --dir={tmpDir}::/tmp {}" + "emulator": "wasmtime run --dir={tmpDir}::/tmp {}" } diff --git a/targets/wasm.json b/targets/wasm.json index 050ee105e0..1333647e98 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -1,18 +1,21 @@ { "llvm-target": "wasm32-unknown-wasi", "cpu": "generic", - "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext", + "features": "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types", "build-tags": ["tinygo.wasm"], "goos": "js", "goarch": "wasm", "linker": "wasm-ld", "libc": "wasi-libc", "rtlib": "compiler-rt", + "gc": "precise", "scheduler": "asyncify", "default-stack-size": 65536, "cflags": [ "-mbulk-memory", "-mnontrapping-fptoint", + "-mno-multivalue", + "-mno-reference-types", "-msign-ext" ], "ldflags": [ diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index a7f8c31b12..53ea75fd42 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -29,7 +29,7 @@ } if (!global.fs && global.require) { - global.fs = require("fs"); + global.fs = require("node:fs"); } const enosys = () => { @@ -101,7 +101,7 @@ } if (!global.crypto) { - const nodeCrypto = require("crypto"); + const nodeCrypto = require("node:crypto"); global.crypto = { getRandomValues(b) { nodeCrypto.randomFillSync(b); @@ -119,11 +119,11 @@ } if (!global.TextEncoder) { - global.TextEncoder = require("util").TextEncoder; + global.TextEncoder = require("node:util").TextEncoder; } if (!global.TextDecoder) { - global.TextDecoder = require("util").TextDecoder; + global.TextDecoder = require("node:util").TextDecoder; } // End of polyfills for common API. @@ -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,18 +291,37 @@ // 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) "syscall/js.finalizeRef": (v_ref) => { - // Note: TinyGo does not support finalizers so this should never be - // called. - console.error('syscall/js.finalizeRef not implemented'); + // Note: TinyGo does not support finalizers so this is only called + // for one specific case, by js.go:jsString. and can/might leak memory. + const id = v_ref & 0xffffffffn; + if (this._goRefCounts?.[id] !== undefined) { + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + } else { + console.error("syscall/js.finalizeRef: unknown id", id); + } }, // func stringVal(value string) ref "syscall/js.stringVal": (value_ptr, value_len) => { + value_ptr >>>= 0; const s = loadString(value_ptr, value_len); return boxValue(s); }, @@ -465,21 +482,25 @@ 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; - while (true) { - const callbackPromise = new Promise((resolve) => { - this._resolveCallbackPromise = () => { - if (this.exited) { - throw new Error("bad callback: Go program has already exited"); - } - setTimeout(resolve, 0); // make sure it is asynchronous - }; + if (this._inst.exports._start) { + let exitPromise = new Promise((resolve, reject) => { + this._resolveExitPromise = resolve; }); - this._inst.exports._start(); - if (this.exited) { - break; + + // 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 callbackPromise; + + await exitPromise; + return this.exitCode; + } else { + this._inst.exports._initialize(); } } @@ -487,7 +508,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(); } @@ -517,8 +542,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); diff --git a/targets/waveshare-rp2040-tiny.json b/targets/waveshare-rp2040-tiny.json new file mode 100644 index 0000000000..74b651b51b --- /dev/null +++ b/targets/waveshare-rp2040-tiny.json @@ -0,0 +1,13 @@ +{ + "inherits": [ + "rp2040" + ], + "serial-port": ["2e8a:0003"], + "build-tags": ["waveshare_rp2040_tiny"], + "ldflags": [ + "--defsym=__flash_size=1020K" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} diff --git a/testdata/cgo/main.c b/testdata/cgo/main.c index 31b60704f5..94b338dda2 100644 --- a/testdata/cgo/main.c +++ b/testdata/cgo/main.c @@ -1,4 +1,6 @@ +#include #include "main.h" +#include int global = 3; bool globalBool = 1; @@ -67,3 +69,16 @@ void unionSetData(short f0, short f1, short f2) { void arraydecay(int buf1[5], int buf2[3][8], int buf3[4][7][2]) { // Do nothing. } + +double doSqrt(double x) { + return sqrt(x); +} + +void printf_single_int(char *format, int arg) { + printf(format, arg); +} + +int set_errno(int err) { + errno = err; + return -1; +} diff --git a/testdata/cgo/main.go b/testdata/cgo/main.go index aac5221f42..38d11386a9 100644 --- a/testdata/cgo/main.go +++ b/testdata/cgo/main.go @@ -2,6 +2,7 @@ package main /* #include +#include int fortytwo(void); #include "main.h" #include "test.h" @@ -17,7 +18,10 @@ import "C" // static int headerfunc_static(int a) { return a - 1; } import "C" -import "unsafe" +import ( + "syscall" + "unsafe" +) func main() { println("fortytwo:", C.fortytwo()) @@ -164,6 +168,15 @@ func main() { println("C.GoString(nil):", C.GoString(nil)) println("len(C.GoStringN(nil, 0)):", len(C.GoStringN(nil, 0))) println("len(C.GoBytes(nil, 0)):", len(C.GoBytes(nil, 0))) + println("len(C.GoBytes(C.CBytes(nil),0)):", len(C.GoBytes(C.CBytes(nil), 0))) + println(`rountrip CBytes:`, C.GoString((*C.char)(C.CBytes([]byte("hello\000"))))) + + // Check that errno is returned from the second return value, and that it + // matches the errno value that was just set. + _, errno := C.set_errno(C.EINVAL) + println("EINVAL:", errno == syscall.EINVAL) + _, errno = C.set_errno(C.EAGAIN) + println("EAGAIN:", errno == syscall.EAGAIN) // libc: test whether C functions work at all. buf1 := []byte("foobar\x00") @@ -171,9 +184,17 @@ func main() { C.strcpy((*C.char)(unsafe.Pointer(&buf2[0])), (*C.char)(unsafe.Pointer(&buf1[0]))) println("copied string:", string(buf2[:C.strlen((*C.char)(unsafe.Pointer(&buf2[0])))])) + // libc: test libm functions (normally bundled in libc) + println("CGo sqrt(3):", C.sqrt(3)) + println("C sqrt(3):", C.doSqrt(3)) + // libc: test basic stdio functionality putsBuf := []byte("line written using C puts\x00") C.puts((*C.char)(unsafe.Pointer(&putsBuf[0]))) + + // libc: test whether printf works in C. + printfBuf := []byte("line written using C printf with value=%d\n\x00") + C.printf_single_int((*C.char)(unsafe.Pointer(&printfBuf[0])), -21) } func printUnion(union C.joined_t) C.joined_t { diff --git a/testdata/cgo/main.h b/testdata/cgo/main.h index 702dab0c00..3942497f23 100644 --- a/testdata/cgo/main.h +++ b/testdata/cgo/main.h @@ -1,5 +1,6 @@ #include #include +#include typedef short myint; typedef short unusedTypedef; @@ -150,3 +151,9 @@ extern int global; // Test array decaying into a pointer. typedef int arraydecay_buf3[4][7][2]; void arraydecay(int buf1[5], int buf2[3][8], arraydecay_buf3 buf3); + +double doSqrt(double); + +void printf_single_int(char *format, int arg); + +int set_errno(int err); diff --git a/testdata/cgo/out.txt b/testdata/cgo/out.txt index 781187b50c..1d63f5e82f 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -73,5 +73,12 @@ C.CStringN: 4 2 0 4 8 C.GoString(nil): len(C.GoStringN(nil, 0)): 0 len(C.GoBytes(nil, 0)): 0 +len(C.GoBytes(C.CBytes(nil),0)): 0 +rountrip CBytes: hello +EINVAL: true +EAGAIN: true copied string: foobar +CGo sqrt(3): +1.732051e+000 +C sqrt(3): +1.732051e+000 line written using C puts +line written using C printf with value=-21 diff --git a/testdata/channel.go b/testdata/channel.go index a7d0e99e4b..9c0fee5b73 100644 --- a/testdata/channel.go +++ b/testdata/channel.go @@ -3,6 +3,7 @@ package main import ( "runtime" "sync" + "sync/atomic" "time" ) @@ -70,11 +71,13 @@ func main() { // Test multi-receiver. ch = make(chan int) wg.Add(3) - go fastreceiver(ch) - go fastreceiver(ch) - go fastreceiver(ch) + var result atomic.Uint32 + go fastreceiveradd(ch, &result) + go fastreceiveradd(ch, &result) + go fastreceiveradd(ch, &result) slowsender(ch) wg.Wait() + println("sum of sums:", result.Load()) // Test iterator style channel. ch = make(chan int) @@ -88,7 +91,10 @@ func main() { println("sum(100):", sum) // Test simple selects. - go selectDeadlock() // cannot use waitGroup here - never terminates + wg.Add(1) + go selectDeadlock() + wg.Wait() + wg.Add(1) go selectNoOp() wg.Wait() @@ -244,7 +250,7 @@ func receive(ch <-chan int) { func sender(ch chan int) { for i := 1; i <= 8; i++ { if i == 4 { - time.Sleep(time.Microsecond) + time.Sleep(time.Millisecond) println("slept") } ch <- i @@ -290,6 +296,16 @@ func fastreceiver(ch chan int) { wg.Done() } +func fastreceiveradd(ch chan int, result *atomic.Uint32) { + sum := 0 + for i := 0; i < 2; i++ { + n := <-ch + sum += n + } + result.Add(uint32(sum)) + wg.Done() +} + func iterator(ch chan int, top int) { for i := 0; i < top; i++ { ch <- i @@ -300,6 +316,7 @@ func iterator(ch chan int, top int) { func selectDeadlock() { println("deadlocking") + wg.Done() select {} println("unreachable") } diff --git a/testdata/channel.txt b/testdata/channel.txt index bd3a4419d2..44cda5ef7b 100644 --- a/testdata/channel.txt +++ b/testdata/channel.txt @@ -12,9 +12,7 @@ received num: 8 recv from closed channel: 0 false complex128: (+7.000000e+000+1.050000e+001i) sum of n: 149 -sum: 25 -sum: 29 -sum: 33 +sum of sums: 87 sum(100): 4950 deadlocking select no-op diff --git a/testdata/errors/cgo.go b/testdata/errors/cgo.go new file mode 100644 index 0000000000..ce18278a94 --- /dev/null +++ b/testdata/errors/cgo.go @@ -0,0 +1,12 @@ +package main + +// #error hello +// ))) +import "C" + +func main() { +} + +// ERROR: # command-line-arguments +// ERROR: cgo.go:3:5: error: hello +// ERROR: cgo.go:4:4: error: expected identifier or '(' diff --git a/testdata/errors/compiler.go b/testdata/errors/compiler.go new file mode 100644 index 0000000000..88559103fa --- /dev/null +++ b/testdata/errors/compiler.go @@ -0,0 +1,23 @@ +package main + +//go:wasmimport foo bar +func foo() { +} + +//go:align 7 +var global int + +// Test for https://github.com/tinygo-org/tinygo/issues/4486 +type genericType[T any] struct{} + +func (genericType[T]) methodWithoutBody() + +func callMethodWithoutBody() { + msg := &genericType[int]{} + msg.methodWithoutBody() +} + +// ERROR: # command-line-arguments +// ERROR: compiler.go:4:6: can only use //go:wasmimport on declarations +// ERROR: compiler.go:8:5: global variable alignment must be a positive power of two +// ERROR: compiler.go:13:23: missing function body diff --git a/testdata/errors/importcycle/cycle.go b/testdata/errors/importcycle/cycle.go new file mode 100644 index 0000000000..40ecf5e235 --- /dev/null +++ b/testdata/errors/importcycle/cycle.go @@ -0,0 +1,3 @@ +package importcycle + +import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle" diff --git a/testdata/errors/interp.go b/testdata/errors/interp.go new file mode 100644 index 0000000000..a3f5cee78e --- /dev/null +++ b/testdata/errors/interp.go @@ -0,0 +1,31 @@ +package main + +import _ "unsafe" + +func init() { + foo() +} + +func foo() { + interp_test_error() +} + +// This is a function that always causes an error in interp, for testing. +// +//go:linkname interp_test_error __tinygo_interp_raise_test_error +func interp_test_error() + +func main() { +} + +// ERROR: # main +// ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:10:19: test error +// ERROR: call void @__tinygo_interp_raise_test_error{{.*}} +// ERROR: {{}} +// ERROR: traceback: +// ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:10:19: +// ERROR: call void @__tinygo_interp_raise_test_error{{.*}} +// ERROR: {{.*testdata[\\/]errors[\\/]interp\.go}}:6:5: +// ERROR: call void @main.foo{{.*}} +// ERROR: {{.*testdata[\\/]errors}}: +// ERROR: call void @"main.init#1"{{.*}} diff --git a/testdata/errors/invaliddep/invaliddep.go b/testdata/errors/invaliddep/invaliddep.go new file mode 100644 index 0000000000..9b0b1c577f --- /dev/null +++ b/testdata/errors/invaliddep/invaliddep.go @@ -0,0 +1 @@ +ppackage // syntax error diff --git a/testdata/errors/invalidmain.go b/testdata/errors/invalidmain.go new file mode 100644 index 0000000000..a86e32c8dd --- /dev/null +++ b/testdata/errors/invalidmain.go @@ -0,0 +1,6 @@ +// some comment to move the first line + +package foobar + +// ERROR: # command-line-arguments +// ERROR: invalidmain.go:3:9: expected main package to have name "main", not "foobar" diff --git a/testdata/errors/invalidname.go b/testdata/errors/invalidname.go new file mode 100644 index 0000000000..9a470b0d8d --- /dev/null +++ b/testdata/errors/invalidname.go @@ -0,0 +1,6 @@ +package main + +import _ "github.com/tinygo-org/tinygo/testdata/errors/invalidname" + +// ERROR: # github.com/tinygo-org/tinygo/testdata/errors/invalidname +// ERROR: invalidname{{[\\/]}}invalidname.go:3:9: invalid package name _ diff --git a/testdata/errors/invalidname/invalidname.go b/testdata/errors/invalidname/invalidname.go new file mode 100644 index 0000000000..c75242244e --- /dev/null +++ b/testdata/errors/invalidname/invalidname.go @@ -0,0 +1,3 @@ +// some comment to move the 'package' line + +package _ diff --git a/testdata/errors/linker-flashoverflow.go b/testdata/errors/linker-flashoverflow.go new file mode 100644 index 0000000000..46e7d9858e --- /dev/null +++ b/testdata/errors/linker-flashoverflow.go @@ -0,0 +1,21 @@ +package main + +import "unsafe" + +const ( + a = "0123456789abcdef" // 16 bytes + b = a + a + a + a + a + a + a + a // 128 bytes + c = b + b + b + b + b + b + b + b // 1024 bytes + d = c + c + c + c + c + c + c + c // 8192 bytes + e = d + d + d + d + d + d + d + d // 65536 bytes + f = e + e + e + e + e + e + e + e // 524288 bytes +) + +var s = f + +func main() { + println(unsafe.StringData(s)) +} + +// ERROR: program too large for this chip (flash overflowed by {{[0-9]+}} bytes) +// ERROR: optimization guide: https://tinygo.org/docs/guides/optimizing-binaries/ diff --git a/testdata/errors/linker-ramoverflow.go b/testdata/errors/linker-ramoverflow.go new file mode 100644 index 0000000000..866f984ad0 --- /dev/null +++ b/testdata/errors/linker-ramoverflow.go @@ -0,0 +1,9 @@ +package main + +var b [64 << 10]byte // 64kB + +func main() { + println("ptr:", &b[0]) +} + +// ERROR: program uses too much static RAM on this chip (RAM overflowed by {{[0-9]+}} bytes) diff --git a/testdata/errors/linker-undefined.go b/testdata/errors/linker-undefined.go new file mode 100644 index 0000000000..fda2b623d7 --- /dev/null +++ b/testdata/errors/linker-undefined.go @@ -0,0 +1,11 @@ +package main + +func foo() + +func main() { + foo() + foo() +} + +// ERROR: linker-undefined.go:6: linker could not find symbol {{_?}}main.foo +// ERROR: linker-undefined.go:7: linker could not find symbol {{_?}}main.foo diff --git a/testdata/errors/loader-importcycle.go b/testdata/errors/loader-importcycle.go new file mode 100644 index 0000000000..4571bdb4de --- /dev/null +++ b/testdata/errors/loader-importcycle.go @@ -0,0 +1,10 @@ +package main + +import _ "github.com/tinygo-org/tinygo/testdata/errors/importcycle" + +func main() { +} + +// ERROR: package command-line-arguments +// ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle +// ERROR: imports github.com/tinygo-org/tinygo/testdata/errors/importcycle: import cycle not allowed diff --git a/testdata/errors/loader-invaliddep.go b/testdata/errors/loader-invaliddep.go new file mode 100644 index 0000000000..05c2f2d5b2 --- /dev/null +++ b/testdata/errors/loader-invaliddep.go @@ -0,0 +1,8 @@ +package main + +import _ "github.com/tinygo-org/tinygo/testdata/errors/invaliddep" + +func main() { +} + +// ERROR: invaliddep{{[\\/]}}invaliddep.go:1:1: expected 'package', found ppackage diff --git a/testdata/errors/loader-invalidpackage.go b/testdata/errors/loader-invalidpackage.go new file mode 100644 index 0000000000..6d78810474 --- /dev/null +++ b/testdata/errors/loader-invalidpackage.go @@ -0,0 +1,3 @@ +ppackage // syntax error + +// ERROR: loader-invalidpackage.go:1:1: expected 'package', found ppackage diff --git a/testdata/errors/loader-nopackage.go b/testdata/errors/loader-nopackage.go new file mode 100644 index 0000000000..c0087fc0b6 --- /dev/null +++ b/testdata/errors/loader-nopackage.go @@ -0,0 +1,14 @@ +package main + +import ( + _ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package" + _ "github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2" +) + +func main() { +} + +// ERROR: loader-nopackage.go:4:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package; to add it: +// ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package +// ERROR: loader-nopackage.go:5:2: no required module provides package github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2; to add it: +// ERROR: go get github.com/tinygo-org/tinygo/testdata/errors/non-existing-package-2 diff --git a/testdata/errors/optimizer.go b/testdata/errors/optimizer.go new file mode 100644 index 0000000000..c2e0b9c4bd --- /dev/null +++ b/testdata/errors/optimizer.go @@ -0,0 +1,19 @@ +package main + +import "runtime/interrupt" + +var num = 5 + +func main() { + // Error coming from LowerInterrupts. + interrupt.New(num, func(interrupt.Interrupt) { + }) + + // 2nd error + interrupt.New(num, func(interrupt.Interrupt) { + }) +} + +// ERROR: # command-line-arguments +// ERROR: optimizer.go:9:15: interrupt ID is not a constant +// ERROR: optimizer.go:13:15: interrupt ID is not a constant diff --git a/testdata/errors/syntax.go b/testdata/errors/syntax.go new file mode 100644 index 0000000000..48d9e732f8 --- /dev/null +++ b/testdata/errors/syntax.go @@ -0,0 +1,7 @@ +package main + +func main(var) { // syntax error +} + +// ERROR: # command-line-arguments +// ERROR: syntax.go:3:11: expected ')', found 'var' diff --git a/testdata/errors/types.go b/testdata/errors/types.go new file mode 100644 index 0000000000..a74fb4a335 --- /dev/null +++ b/testdata/errors/types.go @@ -0,0 +1,12 @@ +package main + +func main() { + var a int + a = "foobar" + nonexisting() +} + +// ERROR: # command-line-arguments +// ERROR: types.go:4:6: declared and not used: a +// ERROR: types.go:5:6: cannot use "foobar" (untyped string constant) as int value in assignment +// ERROR: types.go:6:2: undefined: nonexisting diff --git a/testdata/go1.23/go.mod b/testdata/go1.23/go.mod new file mode 100644 index 0000000000..c0ad79b6d5 --- /dev/null +++ b/testdata/go1.23/go.mod @@ -0,0 +1,3 @@ +module github.com/tinygo-org/tinygo/testdata/go1.23 + +go 1.23 diff --git a/testdata/go1.23/main.go b/testdata/go1.23/main.go new file mode 100644 index 0000000000..2737f68be6 --- /dev/null +++ b/testdata/go1.23/main.go @@ -0,0 +1,33 @@ +package main + +import "iter" + +func main() { + testFuncRange(counter) + testIterPull(counter) + println("go1.23 has lift-off!") +} + +func testFuncRange(it iter.Seq[int]) { + for i := range it { + println(i) + } +} + +func testIterPull(it iter.Seq[int]) { + next, stop := iter.Pull(it) + defer stop() + for { + i, ok := next() + if !ok { + break + } + println(i) + } +} + +func counter(yield func(int) bool) { + for i := 10; i >= 1; i-- { + yield(i) + } +} diff --git a/testdata/go1.23/out.txt b/testdata/go1.23/out.txt new file mode 100644 index 0000000000..78ac56642a --- /dev/null +++ b/testdata/go1.23/out.txt @@ -0,0 +1,21 @@ +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +go1.23 has lift-off! diff --git a/testdata/goroutines.go b/testdata/goroutines.go index 66abc54fde..cf19cc3ca0 100644 --- a/testdata/goroutines.go +++ b/testdata/goroutines.go @@ -1,7 +1,6 @@ package main import ( - "runtime" "sync" "time" ) @@ -83,17 +82,19 @@ func main() { testGoOnInterface(Foo(0)) - testCond() - testIssue1790() + + done := make(chan int) + go testPaddedParameters(paddedStruct{x: 5, y: 7}, done) + <-done } func acquire(m *sync.Mutex) { m.Lock() println("acquired mutex from goroutine") time.Sleep(2 * time.Millisecond) + println("releasing mutex from goroutine") m.Unlock() - println("released mutex from goroutine") } func sub() { @@ -168,47 +169,6 @@ func testGoOnBuiltins() { } } -func testCond() { - var cond runtime.Cond - go func() { - // Wait for the caller to wait on the cond. - time.Sleep(time.Millisecond) - - // Notify the caller. - ok := cond.Notify() - if !ok { - panic("notification not sent") - } - - // This notification will be buffered inside the cond. - ok = cond.Notify() - if !ok { - panic("notification not queued") - } - - // This notification should fail, since there is already one buffered. - ok = cond.Notify() - if ok { - panic("notification double-sent") - } - }() - - // Verify that the cond has no pending notifications. - ok := cond.Poll() - if ok { - panic("unexpected early notification") - } - - // Wait for the goroutine spawned earlier to send a notification. - cond.Wait() - - // The goroutine should have also queued a notification in the cond. - ok = cond.Poll() - if !ok { - panic("missing queued notification") - } -} - var once sync.Once func testGoOnInterface(f Itf) { @@ -243,3 +203,15 @@ func (f Foo) Wait() { time.Sleep(time.Microsecond) println(" ...waited") } + +type paddedStruct struct { + x uint8 + _ [0]int64 + y uint8 +} + +// Structs with interesting padding used to crash. +func testPaddedParameters(s paddedStruct, done chan int) { + println("paddedStruct:", s.x, s.y) + close(done) +} diff --git a/testdata/goroutines.txt b/testdata/goroutines.txt index 35c0cd44dd..f1e4fc1e76 100644 --- a/testdata/goroutines.txt +++ b/testdata/goroutines.txt @@ -19,10 +19,11 @@ closure go call result: 1 pre-acquired mutex releasing mutex acquired mutex from goroutine -released mutex from goroutine +releasing mutex from goroutine re-acquired mutex done called: Foo.Nowait called: Foo.Wait ...waited done with 'go on interface' +paddedStruct: 5 7 diff --git a/testdata/init.go b/testdata/init.go index fa470fd54f..8b34db3f38 100644 --- a/testdata/init.go +++ b/testdata/init.go @@ -109,3 +109,14 @@ func sliceString(s string, start, end int) string { func sliceSlice(s []int, start, end int) []int { return s[start:end] } + +type outside struct{} + +func init() { + _, _ = any(0).(interface{ DoesNotExist() }) + _, _ = any("").(interface{ DoesNotExist() }) + _, _ = any(outside{}).(interface{ DoesNotExist() }) + + type inside struct{} + _, _ = any(inside{}).(interface{ DoesNotExist() }) +} diff --git a/testdata/print.go b/testdata/print.go index 7f7f843c4c..5156ad58e0 100644 --- a/testdata/print.go +++ b/testdata/print.go @@ -37,6 +37,12 @@ func main() { // print interface println(interface{}(nil)) + println(interface{}(true)) + println(interface{}("foobar")) + println(interface{}(int64(-3))) + println(interface{}(uint64(3))) + println(interface{}(int(-3))) + println(interface{}(uint(3))) // print map println(map[string]int{"three": 3, "five": 5}) diff --git a/testdata/print.txt b/testdata/print.txt index 116de945df..3a88cf91e0 100644 --- a/testdata/print.txt +++ b/testdata/print.txt @@ -19,6 +19,12 @@ a b c +3.140000e+000 (+5.000000e+000+1.234500e+000i) (0:nil) +true +foobar +-3 +3 +-3 +3 map[2] true false [0/0]nil diff --git a/testdata/recover.go b/testdata/recover.go index ced90cfaee..6fdf282e7b 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -1,5 +1,12 @@ package main +import ( + "runtime" + "sync" +) + +var wg sync.WaitGroup + func main() { println("# simple recover") recoverSimple() @@ -19,6 +26,12 @@ func main() { println("\n# panic replace") panicReplace() + + println("\n# defer panic") + deferPanic() + + println("\n# runtime.Goexit") + runtimeGoexit() } func recoverSimple() { @@ -89,6 +102,31 @@ 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 runtimeGoexit() { + wg.Add(1) + go func() { + defer func() { + println("Goexit deferred function, recover is nil:", recover() == nil) + wg.Done() + }() + + runtime.Goexit() + }() + wg.Wait() +} + func printitf(msg string, itf interface{}) { switch itf := itf.(type) { case string: diff --git a/testdata/recover.txt b/testdata/recover.txt index d276498550..87e4ba5d17 100644 --- a/testdata/recover.txt +++ b/testdata/recover.txt @@ -23,3 +23,10 @@ recovered: panic panic 1 panic 2 recovered: panic 2 + +# defer panic +defer panic +recovered from deferred call: deferred panic + +# runtime.Goexit +Goexit deferred function, recover is nil: true diff --git a/testdata/reflect.go b/testdata/reflect.go index 1a92e47ab7..6971866dbd 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -423,6 +423,9 @@ func showValue(rv reflect.Value, indent string) { if !rt.Comparable() { print(" comparable=false") } + if name := rt.Name(); name != "" { + print(" name=", name) + } println() switch rt.Kind() { case reflect.Bool: @@ -456,6 +459,7 @@ func showValue(rv reflect.Value, indent string) { case reflect.Interface: println(indent + " interface") println(indent+" nil:", rv.IsNil()) + println(indent+" NumMethod:", rv.NumMethod()) if !rv.IsNil() { showValue(rv.Elem(), indent+" ") } diff --git a/testdata/reflect.txt b/testdata/reflect.txt index e4a92a5e1c..90ac42ac98 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -7,139 +7,140 @@ false false values of interfaces -reflect type: bool +reflect type: bool name=bool bool: true -reflect type: bool +reflect type: bool name=bool bool: false -reflect type: int +reflect type: int name=int int: 2000 -reflect type: int +reflect type: int name=int int: -2000 -reflect type: uint +reflect type: uint name=uint uint: 2000 -reflect type: int8 +reflect type: int8 name=int8 int: -3 -reflect type: int8 +reflect type: int8 name=int8 int: 3 -reflect type: uint8 +reflect type: uint8 name=uint8 uint: 200 -reflect type: int16 +reflect type: int16 name=int16 int: -300 -reflect type: int16 +reflect type: int16 name=int16 int: 300 -reflect type: uint16 +reflect type: uint16 name=uint16 uint: 50000 -reflect type: int32 +reflect type: int32 name=int32 int: 7340032 -reflect type: int32 +reflect type: int32 name=int32 int: -7340032 -reflect type: uint32 +reflect type: uint32 name=uint32 uint: 7340032 -reflect type: int64 +reflect type: int64 name=int64 int: 9895604649984 -reflect type: int64 +reflect type: int64 name=int64 int: -9895604649984 -reflect type: uint64 +reflect type: uint64 name=uint64 uint: 9895604649984 -reflect type: uintptr +reflect type: uintptr name=uintptr uint: 12345 -reflect type: float32 +reflect type: float32 name=float32 float: +3.140000e+000 -reflect type: float64 +reflect type: float64 name=float64 float: +3.140000e+000 -reflect type: complex64 +reflect type: complex64 name=complex64 complex: (+1.200000e+000+3.000000e-001i) -reflect type: complex128 +reflect type: complex128 name=complex128 complex: (+1.300000e+000+4.000000e-001i) -reflect type: int +reflect type: int name=myint int: 32 -reflect type: string +reflect type: string name=string string: foo 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 102 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 111 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 111 -reflect type: unsafe.Pointer +reflect type: unsafe.Pointer name=Pointer pointer: true reflect type: chan chan: int nil: true -reflect type: chan +reflect type: chan name=mychan chan: int nil: true reflect type: ptr pointer: true int nil: false - reflect type: int settable=true addrable=true + reflect type: int settable=true addrable=true name=int int: 0 reflect type: ptr pointer: true interface nil: false - reflect type: interface settable=true addrable=true + reflect type: interface settable=true addrable=true name=error interface nil: true + NumMethod: 1 reflect type: ptr pointer: true int nil: false - reflect type: int settable=true addrable=true + reflect type: int settable=true addrable=true name=int int: 42 -reflect type: ptr +reflect type: ptr name=myptr pointer: true int nil: false - reflect type: int settable=true addrable=true + reflect type: int settable=true addrable=true name=int int: 0 reflect type: slice comparable=false slice: uint8 3 3 pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 1 indexing: 1 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 2 indexing: 2 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 3 reflect type: slice comparable=false slice: uint8 2 5 pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 0 indexing: 1 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 0 reflect type: slice comparable=false slice: int32 2 2 pointer: true nil: false indexing: 0 - reflect type: int32 settable=true addrable=true + reflect type: int32 settable=true addrable=true name=int32 int: 3 indexing: 1 - reflect type: int32 settable=true addrable=true + reflect type: int32 settable=true addrable=true name=int32 int: 5 reflect type: slice comparable=false slice: string 2 2 pointer: true nil: false indexing: 0 - reflect type: string settable=true addrable=true + reflect type: string settable=true addrable=true name=string string: xyz 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 120 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 121 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 122 indexing: 1 - reflect type: string settable=true addrable=true + reflect type: string settable=true addrable=true name=string string: Z 1 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 90 reflect type: slice comparable=false slice: uint8 0 0 @@ -154,67 +155,67 @@ reflect type: slice comparable=false pointer: true nil: false indexing: 0 - reflect type: float32 settable=true addrable=true + reflect type: float32 settable=true addrable=true name=float32 float: +1.000000e+000 indexing: 1 - reflect type: float32 settable=true addrable=true + reflect type: float32 settable=true addrable=true name=float32 float: +1.320000e+000 reflect type: slice comparable=false slice: float64 2 2 pointer: true nil: false indexing: 0 - reflect type: float64 settable=true addrable=true + reflect type: float64 settable=true addrable=true name=float64 float: +1.000000e+000 indexing: 1 - reflect type: float64 settable=true addrable=true + reflect type: float64 settable=true addrable=true name=float64 float: +1.640000e+000 reflect type: slice comparable=false slice: complex64 2 2 pointer: true nil: false indexing: 0 - reflect type: complex64 settable=true addrable=true + reflect type: complex64 settable=true addrable=true name=complex64 complex: (+1.000000e+000+0.000000e+000i) indexing: 1 - reflect type: complex64 settable=true addrable=true + reflect type: complex64 settable=true addrable=true name=complex64 complex: (+1.640000e+000+3.000000e-001i) reflect type: slice comparable=false slice: complex128 2 2 pointer: true nil: false indexing: 0 - reflect type: complex128 settable=true addrable=true + reflect type: complex128 settable=true addrable=true name=complex128 complex: (+1.000000e+000+0.000000e+000i) indexing: 1 - reflect type: complex128 settable=true addrable=true + reflect type: complex128 settable=true addrable=true name=complex128 complex: (+1.128000e+000+4.000000e-001i) -reflect type: slice comparable=false +reflect type: slice comparable=false name=myslice slice: uint8 3 3 pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 5 indexing: 1 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 3 indexing: 2 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 11 reflect type: array array: 3 int64 24 - reflect type: int64 + reflect type: int64 name=int64 int: 5 - reflect type: int64 + reflect type: int64 name=int64 int: 8 - reflect type: int64 + reflect type: int64 name=int64 int: 2 reflect type: array array: 2 uint8 2 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 5 reflect type: func comparable=false func @@ -237,9 +238,10 @@ reflect type: struct tag: "" embedded: true exported: false - reflect type: interface caninterface=false + reflect type: interface caninterface=false name=error interface nil: true + NumMethod: 1 reflect type: struct struct: 3 field: 0 a @@ -247,51 +249,51 @@ reflect type: struct tag: "" embedded: false exported: false - reflect type: uint8 caninterface=false + reflect type: uint8 caninterface=false name=uint8 uint: 42 field: 1 b pkg: main tag: "" embedded: false exported: false - reflect type: int16 caninterface=false + reflect type: int16 caninterface=false name=int16 int: 321 field: 2 c pkg: main tag: "" embedded: false exported: false - reflect type: int8 caninterface=false + reflect type: int8 caninterface=false name=int8 int: 123 -reflect type: struct comparable=false +reflect type: struct comparable=false name=mystruct struct: 5 field: 0 n pkg: main tag: "foo:\"bar\"" embedded: false exported: false - reflect type: int caninterface=false + reflect type: int caninterface=false name=int int: 5 field: 1 some pkg: main tag: "some\x00tag" embedded: false exported: false - reflect type: struct caninterface=false + reflect type: struct caninterface=false name=point struct: 2 field: 0 X pkg: tag: "" embedded: false exported: true - reflect type: int16 caninterface=false + reflect type: int16 caninterface=false name=int16 int: -5 field: 1 Y pkg: tag: "" embedded: false exported: true - reflect type: int16 caninterface=false + reflect type: int16 caninterface=false name=int16 int: 3 field: 2 zero pkg: main @@ -310,10 +312,10 @@ reflect type: struct comparable=false pointer: true nil: false indexing: 0 - reflect type: uint8 addrable=true caninterface=false + reflect type: uint8 addrable=true caninterface=false name=uint8 uint: 71 indexing: 1 - reflect type: uint8 addrable=true caninterface=false + reflect type: uint8 addrable=true caninterface=false name=uint8 uint: 111 field: 4 Buf pkg: @@ -325,12 +327,12 @@ reflect type: struct comparable=false pointer: true nil: false indexing: 0 - reflect type: uint8 settable=true addrable=true + reflect type: uint8 settable=true addrable=true name=uint8 uint: 88 reflect type: ptr pointer: true struct nil: false - reflect type: struct settable=true addrable=true + reflect type: struct settable=true addrable=true name=linkedList struct: 2 field: 0 next pkg: main @@ -345,7 +347,7 @@ reflect type: ptr tag: "" embedded: false exported: false - reflect type: int addrable=true caninterface=false + reflect type: int addrable=true caninterface=false name=int int: 42 reflect type: struct struct: 2 @@ -354,14 +356,14 @@ reflect type: struct tag: "" embedded: false exported: true - reflect type: uintptr + reflect type: uintptr name=uintptr uint: 2 field: 1 B pkg: tag: "" embedded: false exported: true - reflect type: uintptr + reflect type: uintptr name=uintptr uint: 3 reflect type: slice comparable=false slice: interface 3 3 @@ -371,50 +373,53 @@ reflect type: slice comparable=false reflect type: interface settable=true addrable=true interface nil: false - reflect type: int + NumMethod: 0 + reflect type: int name=int int: 3 indexing: 1 reflect type: interface settable=true addrable=true interface nil: false - reflect type: string + NumMethod: 0 + reflect type: string name=string string: str 3 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 115 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 116 - reflect type: uint8 + reflect type: uint8 name=uint8 uint: 114 indexing: 2 reflect type: interface settable=true addrable=true interface nil: false - reflect type: complex128 + NumMethod: 0 + reflect type: complex128 name=complex128 complex: (-4.000000e+000+2.500000e+000i) reflect type: ptr pointer: true int8 nil: false - reflect type: int8 settable=true addrable=true + reflect type: int8 settable=true addrable=true name=int8 int: 5 reflect type: ptr pointer: true int16 nil: false - reflect type: int16 settable=true addrable=true + reflect type: int16 settable=true addrable=true name=int16 int: -800 reflect type: ptr pointer: true int32 nil: false - reflect type: int32 settable=true addrable=true + reflect type: int32 settable=true addrable=true name=int32 int: 100000000 reflect type: ptr pointer: true int64 nil: false - reflect type: int64 settable=true addrable=true + reflect type: int64 settable=true addrable=true name=int64 int: -1000000000000 reflect type: ptr pointer: true complex128 nil: false - reflect type: complex128 settable=true addrable=true + reflect type: complex128 settable=true addrable=true name=complex128 complex: (-8.000000e+000-2.000000e+006i) sizes: diff --git a/testdata/signal.go b/testdata/signal.go new file mode 100644 index 0000000000..a82991f086 --- /dev/null +++ b/testdata/signal.go @@ -0,0 +1,42 @@ +package main + +// Test POSIX signals. +// TODO: run `tinygo test os/signal` instead, once CGo errno return values are +// supported. + +import ( + "os" + "os/signal" + "syscall" + "time" +) + +func main() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGUSR1) + + // Wait for signals to arrive. + go func() { + for sig := range c { + if sig == syscall.SIGUSR1 { + println("got expected signal") + } else { + println("got signal:", sig.String()) + } + } + }() + + // Send the signal. + syscall.Kill(syscall.Getpid(), syscall.SIGUSR1) + + time.Sleep(time.Millisecond * 100) + + // Stop notifying. + // (This is just a smoke test, it's difficult to test the default behavior + // in a unit test). + signal.Ignore(syscall.SIGUSR1) + + signal.Stop(c) + + println("exiting signal program") +} diff --git a/testdata/signal.txt b/testdata/signal.txt new file mode 100644 index 0000000000..c4726d7174 --- /dev/null +++ b/testdata/signal.txt @@ -0,0 +1,2 @@ +got expected signal +exiting signal program diff --git a/testdata/slice.txt b/testdata/slice.txt index d16a0bda9f..488170b52b 100644 --- a/testdata/slice.txt +++ b/testdata/slice.txt @@ -7,12 +7,12 @@ copy foo -> bar: 3 bar: len=3 cap=5 data: 1 2 4 slice is nil? true true grow: len=0 cap=0 data: -grow: len=1 cap=1 data: 42 +grow: len=1 cap=2 data: 42 grow: len=3 cap=4 data: 42 -1 -2 grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 grow: len=7 cap=8 data: 42 -1 -2 1 2 4 5 grow: len=14 cap=16 data: 42 -1 -2 1 2 4 5 42 -1 -2 1 2 4 5 -bytes: len=6 cap=6 data: 1 2 3 102 111 111 +bytes: len=6 cap=8 data: 1 2 3 102 111 111 slice to array pointer: 1 -2 20 4 unsafe.Add array: 1 5 8 4 unsafe.Slice array: 3 3 9 15 4 diff --git a/testdata/wasmexit.go b/testdata/wasmexit.go new file mode 100644 index 0000000000..cbf5878450 --- /dev/null +++ b/testdata/wasmexit.go @@ -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") +} diff --git a/testdata/wasmexit.js b/testdata/wasmexit.js new file mode 100644 index 0000000000..b41991e3a7 --- /dev/null +++ b/testdata/wasmexit.js @@ -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); +}); diff --git a/testdata/wasmexport-noscheduler.go b/testdata/wasmexport-noscheduler.go new file mode 100644 index 0000000000..cc99d7136a --- /dev/null +++ b/testdata/wasmexport-noscheduler.go @@ -0,0 +1,41 @@ +package main + +import "time" + +func init() { + println("called init") +} + +//go:wasmimport tester callTestMain +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() +} + +//go:wasmexport hello +func hello() { + println("hello!") +} + +//go:wasmexport add +func add(a, b int32) int32 { + println("called add:", a, b) + return a + b +} + +//go:wasmimport tester callOutside +func callOutside(a, b int32) int32 + +//go:wasmexport reentrantCall +func reentrantCall(a, b int32) int32 { + println("reentrantCall:", a, b) + result := callOutside(a, b) + println("reentrantCall result:", result) + return result +} diff --git a/testdata/wasmexport.go b/testdata/wasmexport.go new file mode 100644 index 0000000000..a215761273 --- /dev/null +++ b/testdata/wasmexport.go @@ -0,0 +1,52 @@ +package main + +import "time" + +func init() { + println("called init") + go adder() +} + +//go:wasmimport tester callTestMain +func callTestMain() + +func main() { + // main.main is not used when using -buildmode=c-shared. + callTestMain() +} + +//go:wasmexport hello +func hello() { + println("hello!") +} + +//go:wasmexport add +func add(a, b int32) int32 { + println("called add:", a, b) + addInputs <- a + addInputs <- b + return <-addOutput +} + +var addInputs = make(chan int32) +var addOutput = make(chan int32) + +func adder() { + for { + a := <-addInputs + b := <-addInputs + time.Sleep(time.Millisecond) + addOutput <- a + b + } +} + +//go:wasmimport tester callOutside +func callOutside(a, b int32) int32 + +//go:wasmexport reentrantCall +func reentrantCall(a, b int32) int32 { + println("reentrantCall:", a, b) + result := callOutside(a, b) + println("reentrantCall result:", result) + return result +} diff --git a/testdata/wasmexport.js b/testdata/wasmexport.js new file mode 100644 index 0000000000..c4a065125a --- /dev/null +++ b/testdata/wasmexport.js @@ -0,0 +1,40 @@ +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((result) => { + let buildMode = process.argv[3]; + if (buildMode === 'default') { + go.run(result.instance); + } else if (buildMode === 'c-shared') { + go.run(result.instance); + runTests(); + } +}).catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/testdata/wasmexport.txt b/testdata/wasmexport.txt new file mode 100644 index 0000000000..484a0ce8d8 --- /dev/null +++ b/testdata/wasmexport.txt @@ -0,0 +1,11 @@ +called init +hello! +called add: 3 5 +called add: 7 9 +called add: 6 1 +reentrantCall: 2 3 +called add: 2 3 +reentrantCall result: 5 +reentrantCall: 1 8 +called add: 1 8 +reentrantCall result: 9 diff --git a/testdata/wasmfunc.go b/testdata/wasmfunc.go new file mode 100644 index 0000000000..9d0d690e4f --- /dev/null +++ b/testdata/wasmfunc.go @@ -0,0 +1,17 @@ +package main + +import "syscall/js" + +func main() { + js.Global().Call("setCallback", js.FuncOf(func(this js.Value, args []js.Value) any { + println("inside callback! parameters:") + sum := 0 + for _, value := range args { + n := value.Int() + println(" parameter:", n) + sum += n + } + return sum + })) + js.Global().Call("callCallback") +} diff --git a/testdata/wasmfunc.js b/testdata/wasmfunc.js new file mode 100644 index 0000000000..3b1831ee4c --- /dev/null +++ b/testdata/wasmfunc.js @@ -0,0 +1,21 @@ +require('../targets/wasm_exec.js'); + +var callback; + +global.setCallback = (cb) => { + callback = cb; +}; + +global.callCallback = () => { + console.log('calling callback!'); + let result = callback(1, 2, 3, 4); + console.log('result from callback:', result); +}; + +let go = new Go(); +WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { + go.run(result.instance); +}).catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/testdata/wasmfunc.txt b/testdata/wasmfunc.txt new file mode 100644 index 0000000000..be41eba3c6 --- /dev/null +++ b/testdata/wasmfunc.txt @@ -0,0 +1,7 @@ +calling callback! +inside callback! parameters: + parameter: 1 + parameter: 2 + parameter: 3 + parameter: 4 +result from callback: 10 diff --git a/tests/runtime_wasi/malloc_test.go b/tests/runtime_wasi/malloc_test.go index e5bbb4ebb3..465e662a45 100644 --- a/tests/runtime_wasi/malloc_test.go +++ b/tests/runtime_wasi/malloc_test.go @@ -67,7 +67,7 @@ func checkFilledBuffer(t *testing.T, ptr uintptr, content string) { t.Helper() buf := *(*string)(unsafe.Pointer(&reflect.StringHeader{ Data: ptr, - Len: uintptr(len(content)), + Len: len(content), })) if buf != content { t.Errorf("expected %q, got %q", content, buf) diff --git a/tests/testing/chdir/chdir.go b/tests/testing/chdir/chdir.go new file mode 100644 index 0000000000..75281c21f0 --- /dev/null +++ b/tests/testing/chdir/chdir.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "os" + "path/filepath" + "runtime" +) + +/* +Test that this program is 'run' in expected directory. 'run' with expected +working-directory in 'EXPECT_DIR' environment variable' with{,out} a -C +argument. +*/ +func main() { + expectDir := os.Getenv("EXPECT_DIR") + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + if runtime.GOOS == "windows" { + cwd = filepath.ToSlash(cwd) + } + if cwd != expectDir { + log.Fatalf("expected:\"%v\" != os.Getwd():\"%v\"", expectDir, cwd) + } +} diff --git a/tests/wasm/setup_test.go b/tests/wasm/setup_test.go index b56b6f3363..f282aa134e 100644 --- a/tests/wasm/setup_test.go +++ b/tests/wasm/setup_test.go @@ -33,8 +33,16 @@ func runargs(t *testing.T, args ...string) error { } func chromectx(t *testing.T) context.Context { + // see https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md + opts := append(chromedp.DefaultExecAllocatorOptions[:], + chromedp.NoSandbox, + ) + + allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) + t.Cleanup(cancel) + // looks for locally installed Chrome - ctx, ccancel := chromedp.NewContext(context.Background(), chromedp.WithErrorf(t.Errorf), chromedp.WithDebugf(t.Logf), chromedp.WithLogf(t.Logf)) + ctx, ccancel := chromedp.NewContext(allocCtx, chromedp.WithErrorf(t.Errorf), chromedp.WithDebugf(t.Logf), chromedp.WithLogf(t.Logf)) t.Cleanup(ccancel) // Wait for browser to be ready. diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 0c49986ab9..1a3d539621 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -445,6 +445,13 @@ func readSVD(path, sourceURL string) (*Device, error) { return interruptList[i].PeripheralIndex < interruptList[j].PeripheralIndex }) + // Properly format the description, with comments. + description := "" + if text := device.Description; text != "" { + description = "// " + strings.ReplaceAll(text, "\n", "\n// ") + description = regexp.MustCompile(`\s+\n`).ReplaceAllString(description, "\n") + } + // Properly format the license block, with comments. licenseBlock := "" if text := formatText(device.LicenseText); text != "" { @@ -460,7 +467,7 @@ func readSVD(path, sourceURL string) (*Device, error) { DescriptorSource: sourceURL, Name: device.Name, NameLower: nameLower, - Description: strings.TrimSpace(device.Description), + Description: description, LicenseBlock: licenseBlock, } if device.CPU != nil { @@ -902,8 +909,9 @@ func writeGo(outdir string, device *Device, interruptSystem string) error { //go:build {{.pkgName}} && {{.device.Metadata.NameLower}} -// {{.device.Metadata.Description}} -// +/* +{{.device.Metadata.Description}} +*/ {{.device.Metadata.LicenseBlock}} package {{.pkgName}} @@ -1350,8 +1358,10 @@ func writeAsm(outdir string, device *Device) error { t := template.Must(template.New("go").Parse(`// Automatically generated file. DO NOT EDIT. // Generated by gen-device-svd.go from {{.File}}, see {{.DescriptorSource}} -// {{.Description}} -// +/* +{{.Description}} +*/ + {{.LicenseBlock}} .syntax unified diff --git a/tools/tgtestjson.sh b/tools/tgtestjson.sh new file mode 100755 index 0000000000..169da5852c --- /dev/null +++ b/tools/tgtestjson.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# Run tests and convert output to json with go tool test2json. +# This is a workaround for the lack of -json output in tinygo test. +# Some variables must be set in the environment beforehand. +# TODO: let's just add -json support to tinygo test. + +TINYGO="${TINYGO:-tinygo}" +PACKAGES="${PACKAGES:-"./tests"}" +TARGET="${TARGET:-wasip2}" +TESTOPTS="${TESTOPTS:-"-x -work"}" + +# go clean -testcache +for pkg in $PACKAGES; do + # Example invocation with test2json in BigGo: + # go test -test.v=test2json ./$pkg 2>&1 | go tool test2json -p $pkg + + # Uncomment to see resolved commands in output + # >&2 echo "${TINYGO} test -v -target $TARGET $TESTOPTS $pkg 2>&1 | go tool test2json -p $pkg" + "${TINYGO}" test -v -target $TARGET $TESTOPTS $pkg 2>&1 | go tool test2json -p $pkg + +done diff --git a/transform/allocs.go b/transform/allocs.go index 67ca1ea435..870faa5b75 100644 --- a/transform/allocs.go +++ b/transform/allocs.go @@ -29,10 +29,14 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc u targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() - ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) - builder := mod.Context().NewBuilder() + ctx := mod.Context() + builder := ctx.NewBuilder() defer builder.Dispose() + // Determine the maximum alignment on this platform. + complex128Type := ctx.StructType([]llvm.Type{ctx.DoubleType(), ctx.DoubleType()}, false) + maxAlign := int64(targetData.ABITypeAlignment(complex128Type)) + for _, heapalloc := range getUses(allocator) { logAllocs := printAllocs != nil && printAllocs.MatchString(heapalloc.InstructionParent().Parent().Name()) if heapalloc.Operand(0).IsAConstantInt().IsNil() { @@ -90,21 +94,14 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc u } // The pointer value does not escape. - // Determine the appropriate alignment of the alloca. The size of the - // allocation gives us a hint what the alignment should be. - var alignment int - if size%2 != 0 { - alignment = 1 - } else if size%4 != 0 { - alignment = 2 - } else if size%8 != 0 { - alignment = 4 - } else { - alignment = 8 - } - if pointerAlignment := targetData.ABITypeAlignment(ptrType); pointerAlignment < alignment { - // Use min(alignment, alignof(void*)) as the alignment. - alignment = pointerAlignment + // Determine the appropriate alignment of the alloca. + attr := heapalloc.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align")) + alignment := int(maxAlign) + if !attr.IsNil() { + // 'align' return value attribute is set, so use it. + // This is basically always the case, but to be sure we'll default + // to maxAlign if it isn't. + alignment = int(attr.GetEnumValue()) } // Insert alloca in the entry block. Do it here so that mem2reg can diff --git a/transform/interrupt.go b/transform/interrupt.go index 043eebb84c..8c3ed4b510 100644 --- a/transform/interrupt.go +++ b/transform/interrupt.go @@ -137,7 +137,7 @@ func LowerInterrupts(mod llvm.Module) []error { user.ReplaceAllUsesWith(llvm.ConstInt(user.Type(), uint64(num), true)) } - // The runtime/interrput.handle struct can finally be removed. + // The runtime/interrupt.handle struct can finally be removed. // It would probably be eliminated anyway by a globaldce pass but it's // better to do it now to be sure. handler.EraseFromParentAsGlobal() diff --git a/transform/optimizer.go b/transform/optimizer.go index e73e976006..54f9762bc4 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -8,6 +8,7 @@ import ( "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compiler/ircheck" + "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -27,7 +28,7 @@ func OptimizePackage(mod llvm.Module, config *compileopts.Config) { // passes. // // Please note that some optimizations are not optional, thus Optimize must -// alwasy be run before emitting machine code. +// always be run before emitting machine code. func Optimize(mod llvm.Module, config *compileopts.Config) []error { optLevel, speedLevel, _ := config.OptLevel() @@ -40,10 +41,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { fn.SetLinkage(llvm.ExternalLinkage) } - if config.PanicStrategy() == "trap" { - ReplacePanicsWithTrap(mod) // -panic=trap - } - // run a check of all of our code if config.VerifyIR() { errs := ircheck.Module(mod) @@ -56,7 +53,12 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // Run some preparatory passes for the Go optimizer. po := llvm.NewPassBuilderOptions() defer po.Dispose() - err := mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) + optPasses := "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs" + if llvmutil.Version() < 18 { + // LLVM 17 doesn't have the no-verify-fixpoint flag. + optPasses = "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs" + } + err := mod.RunPasses(optPasses, llvm.TargetMachine{}, po) if err != nil { return []error{fmt.Errorf("could not build pass pipeline: %w", err)} } @@ -79,7 +81,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // After interfaces are lowered, there are many more opportunities for // interprocedural optimizations. To get them to work, function // attributes have to be updated first. - err = mod.RunPasses("globaldce,globalopt,ipsccp,instcombine,adce,function-attrs", llvm.TargetMachine{}, po) + err = mod.RunPasses(optPasses, llvm.TargetMachine{}, po) if err != nil { return []error{fmt.Errorf("could not build pass pipeline: %w", err)} } @@ -140,11 +142,13 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { fn.SetLinkage(llvm.InternalLinkage) } - // Run the default pass pipeline. - // TODO: set the PrepareForThinLTO flag somehow. + // Run the ThinLTO pre-link passes, meant to be run on each individual + // module. This saves compilation time compared to "default<#>" and is meant + // to better match the optimization passes that are happening during + // ThinLTO. po := llvm.NewPassBuilderOptions() defer po.Dispose() - passes := fmt.Sprintf("default<%s>", optLevel) + passes := fmt.Sprintf("thinlto-pre-link<%s>", optLevel) err := mod.RunPasses(passes, llvm.TargetMachine{}, po) if err != nil { return []error{fmt.Errorf("could not build pass pipeline: %w", err)} diff --git a/transform/panic.go b/transform/panic.go deleted file mode 100644 index dee3bae06d..0000000000 --- a/transform/panic.go +++ /dev/null @@ -1,33 +0,0 @@ -package transform - -import ( - "tinygo.org/x/go-llvm" -) - -// ReplacePanicsWithTrap replaces each call to panic (or similar functions) with -// calls to llvm.trap, to reduce code size. This is the -panic=trap command-line -// option. -func ReplacePanicsWithTrap(mod llvm.Module) { - ctx := mod.Context() - builder := ctx.NewBuilder() - defer builder.Dispose() - - trap := mod.NamedFunction("llvm.trap") - if trap.IsNil() { - trapType := llvm.FunctionType(ctx.VoidType(), nil, false) - trap = llvm.AddFunction(mod, "llvm.trap", trapType) - } - for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} { - fn := mod.NamedFunction(name) - if fn.IsNil() { - continue - } - for _, use := range getUses(fn) { - if use.IsACallInst().IsNil() || use.CalledValue() != fn { - panic("expected use of a panic function to be a call") - } - builder.SetInsertPointBefore(use) - builder.CreateCall(trap.GlobalValueType(), trap, nil, "") - } - } -} diff --git a/transform/panic_test.go b/transform/panic_test.go deleted file mode 100644 index ea4efd0e7d..0000000000 --- a/transform/panic_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package transform_test - -import ( - "testing" - - "github.com/tinygo-org/tinygo/transform" -) - -func TestReplacePanicsWithTrap(t *testing.T) { - t.Parallel() - testTransform(t, "testdata/panic", transform.ReplacePanicsWithTrap) -} diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 0b6feff21f..3abc1d3952 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -34,7 +34,7 @@ func OptimizeStringToBytes(mod llvm.Module) { if use.IsAExtractValueInst().IsNil() { // Expected an extractvalue, but this is something else. canConvertPointer = false - continue + break } switch use.Type().TypeKind() { case llvm.IntegerTypeKind: @@ -49,7 +49,7 @@ func OptimizeStringToBytes(mod llvm.Module) { // There is a store to the byte slice. This means that none // of the pointer uses can't be propagated. canConvertPointer = false - continue + break } // It may be that the pointer value can be propagated, if all of // the pointer uses are readonly. @@ -117,8 +117,9 @@ func OptimizeStringEqual(mod llvm.Module) { // As of this writing, the (reflect.Type).Interface method has not yet been // implemented so this optimization is critical for the encoding/json package. func OptimizeReflectImplements(mod llvm.Module) { - implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") - if implementsSignature.IsNil() { + implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") + implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool") + if implementsSignature1.IsNil() && implementsSignature2.IsNil() { return } @@ -132,7 +133,8 @@ func OptimizeReflectImplements(mod llvm.Module) { if attr.IsNil() { continue } - if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" { + val := attr.GetStringValue() + if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.Type) bool" { implementsFunc = fn break } diff --git a/transform/testdata/allocs.ll b/transform/testdata/allocs.ll index 1c2fdd5aa4..4f6960ef99 100644 --- a/transform/testdata/allocs.ll +++ b/transform/testdata/allocs.ll @@ -7,7 +7,7 @@ declare nonnull ptr @runtime.alloc(i32, ptr) ; Test allocating a single int (i32) that should be allocated on the stack. define void @testInt() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) store i32 5, ptr %alloc ret void } @@ -15,7 +15,7 @@ define void @testInt() { ; Test allocating an array of 3 i16 values that should be allocated on the ; stack. define i16 @testArray() { - %alloc = call ptr @runtime.alloc(i32 6, ptr null) + %alloc = call align 2 ptr @runtime.alloc(i32 6, ptr null) %alloc.1 = getelementptr i16, ptr %alloc, i32 1 store i16 5, ptr %alloc.1 %alloc.2 = getelementptr i16, ptr %alloc, i32 2 @@ -23,30 +23,45 @@ define i16 @testArray() { ret i16 %val } +; Test allocating objects with an unknown alignment. +define void @testUnknownAlign() { + %alloc32 = call ptr @runtime.alloc(i32 32, ptr null) + store i8 5, ptr %alloc32 + %alloc24 = call ptr @runtime.alloc(i32 24, ptr null) + store i16 5, ptr %alloc24 + %alloc12 = call ptr @runtime.alloc(i32 12, ptr null) + store i16 5, ptr %alloc12 + %alloc6 = call ptr @runtime.alloc(i32 6, ptr null) + store i16 5, ptr %alloc6 + %alloc3 = call ptr @runtime.alloc(i32 3, ptr null) + store i16 5, ptr %alloc3 + ret void +} + ; Call a function that will let the pointer escape, so the heap-to-stack ; transform shouldn't be applied. define void @testEscapingCall() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtr(ptr %alloc) ret void } define void @testEscapingCall2() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtrSometimes(ptr %alloc, ptr %alloc) ret void } ; Call a function that doesn't let the pointer escape. define void @testNonEscapingCall() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @noescapeIntPtr(ptr %alloc) ret void } ; Return the allocated value, which lets it escape. define ptr @testEscapingReturn() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) ret ptr %alloc } @@ -55,7 +70,7 @@ define void @testNonEscapingLoop() { entry: br label %loop loop: - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %ptr = call ptr @noescapeIntPtr(ptr %alloc) %result = icmp eq ptr null, %ptr br i1 %result, label %loop, label %end @@ -65,7 +80,7 @@ end: ; Test a zero-sized allocation. define void @testZeroSizedAlloc() { - %alloc = call ptr @runtime.alloc(i32 0, ptr null) + %alloc = call align 1 ptr @runtime.alloc(i32 0, ptr null) %ptr = call ptr @noescapeIntPtr(ptr %alloc) ret void } diff --git a/transform/testdata/allocs.out.ll b/transform/testdata/allocs.out.ll index b3c8bf8f39..e4a5e4f7e9 100644 --- a/transform/testdata/allocs.out.ll +++ b/transform/testdata/allocs.out.ll @@ -22,14 +22,33 @@ define i16 @testArray() { ret i16 %val } +define void @testUnknownAlign() { + %stackalloc4 = alloca [32 x i8], align 8 + %stackalloc3 = alloca [24 x i8], align 8 + %stackalloc2 = alloca [12 x i8], align 8 + %stackalloc1 = alloca [6 x i8], align 8 + %stackalloc = alloca [3 x i8], align 8 + store [32 x i8] zeroinitializer, ptr %stackalloc4, align 8 + store i8 5, ptr %stackalloc4, align 1 + store [24 x i8] zeroinitializer, ptr %stackalloc3, align 8 + store i16 5, ptr %stackalloc3, align 2 + store [12 x i8] zeroinitializer, ptr %stackalloc2, align 8 + store i16 5, ptr %stackalloc2, align 2 + store [6 x i8] zeroinitializer, ptr %stackalloc1, align 8 + store i16 5, ptr %stackalloc1, align 2 + store [3 x i8] zeroinitializer, ptr %stackalloc, align 8 + store i16 5, ptr %stackalloc, align 2 + ret void +} + define void @testEscapingCall() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtr(ptr %alloc) ret void } define void @testEscapingCall2() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) %val = call ptr @escapeIntPtrSometimes(ptr %alloc, ptr %alloc) ret void } @@ -42,7 +61,7 @@ define void @testNonEscapingCall() { } define ptr @testEscapingReturn() { - %alloc = call ptr @runtime.alloc(i32 4, ptr null) + %alloc = call align 4 ptr @runtime.alloc(i32 4, ptr null) ret ptr %alloc } diff --git a/transform/testdata/allocs2.go b/transform/testdata/allocs2.go index 3b08fbc9e4..299df5b213 100644 --- a/transform/testdata/allocs2.go +++ b/transform/testdata/allocs2.go @@ -49,6 +49,15 @@ func main() { n4 = n5 }() println(n4, n5) + + // This shouldn't escape. + var buf [32]byte + s := string(buf[:]) + println(len(s)) + + var rbuf [5]rune + s = string(rbuf[:]) + println(s) } func derefInt(x *int) int { diff --git a/transform/testdata/panic.ll b/transform/testdata/panic.ll deleted file mode 100644 index 660e30f2f5..0000000000 --- a/transform/testdata/panic.ll +++ /dev/null @@ -1,22 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" -target triple = "armv7m-none-eabi" - -@"runtime.lookupPanic$string" = constant [18 x i8] c"index out of range" - -declare void @runtime.runtimePanic(ptr, i32) - -declare void @runtime._panic(i32, ptr) - -define void @runtime.lookupPanic() { - call void @runtime.runtimePanic(ptr @"runtime.lookupPanic$string", i32 18) - ret void -} - -; This is equivalent to the following code: -; func someFunc(x interface{}) { -; panic(x) -; } -define void @someFunc(i32 %typecode, ptr %value) { - call void @runtime._panic(i32 %typecode, ptr %value) - unreachable -} diff --git a/transform/testdata/panic.out.ll b/transform/testdata/panic.out.ll deleted file mode 100644 index 458e4c2477..0000000000 --- a/transform/testdata/panic.out.ll +++ /dev/null @@ -1,25 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" -target triple = "armv7m-none-eabi" - -@"runtime.lookupPanic$string" = constant [18 x i8] c"index out of range" - -declare void @runtime.runtimePanic(ptr, i32) - -declare void @runtime._panic(i32, ptr) - -define void @runtime.lookupPanic() { - call void @llvm.trap() - call void @runtime.runtimePanic(ptr @"runtime.lookupPanic$string", i32 18) - ret void -} - -define void @someFunc(i32 %typecode, ptr %value) { - call void @llvm.trap() - call void @runtime._panic(i32 %typecode, ptr %value) - unreachable -} - -; Function Attrs: cold noreturn nounwind -declare void @llvm.trap() #0 - -attributes #0 = { cold noreturn nounwind } diff --git a/transform/testdata/stringtobytes.ll b/transform/testdata/stringtobytes.ll index 06373a51ba..6d008d023b 100644 --- a/transform/testdata/stringtobytes.ll +++ b/transform/testdata/stringtobytes.ll @@ -5,6 +5,8 @@ target triple = "x86_64--linux" declare { ptr, i64, i64 } @runtime.stringToBytes(ptr, i64) +declare void @printByte(i8) + declare void @printSlice(ptr nocapture readonly, i64, i64) declare void @writeToSlice(ptr nocapture, i64, i64) @@ -17,6 +19,12 @@ entry: %2 = extractvalue { ptr, i64, i64 } %0, 1 %3 = extractvalue { ptr, i64, i64 } %0, 2 call fastcc void @printSlice(ptr %1, i64 %2, i64 %3) + + ; print(slice[0]) + %indexaddr.ptr1 = extractvalue { ptr, i64, i64 } %0, 0 + %4 = getelementptr inbounds i8, ptr %indexaddr.ptr1, i64 0 + %5 = load i8, ptr %4, align 1 + call fastcc void @printByte(i8 %5) ret void } diff --git a/transform/testdata/stringtobytes.out.ll b/transform/testdata/stringtobytes.out.ll index b33a17553b..b8909755aa 100644 --- a/transform/testdata/stringtobytes.out.ll +++ b/transform/testdata/stringtobytes.out.ll @@ -5,6 +5,8 @@ target triple = "x86_64--linux" declare { ptr, i64, i64 } @runtime.stringToBytes(ptr, i64) +declare void @printByte(i8) + declare void @printSlice(ptr nocapture readonly, i64, i64) declare void @writeToSlice(ptr nocapture, i64, i64) @@ -12,6 +14,9 @@ declare void @writeToSlice(ptr nocapture, i64, i64) define void @testReadOnly() { entry: call fastcc void @printSlice(ptr @str, i64 6, i64 6) + %0 = getelementptr inbounds i8, ptr @str, i64 0 + %1 = load i8, ptr %0, align 1 + call fastcc void @printByte(i8 %1) ret void } diff --git a/transform/transform_test.go b/transform/transform_test.go index f23a480a90..40769dbe86 100644 --- a/transform/transform_test.go +++ b/transform/transform_test.go @@ -137,6 +137,7 @@ func compileGoFileForTesting(t *testing.T, filename string) llvm.Module { Scheduler: config.Scheduler(), AutomaticStackSize: config.AutomaticStackSize(), Debug: true, + PanicStrategy: config.PanicStrategy(), } machine, err := compiler.NewTargetMachine(compilerConfig) if err != nil { diff --git a/transform/util.go b/transform/util.go index 9923669d17..7a574d2fcb 100644 --- a/transform/util.go +++ b/transform/util.go @@ -38,15 +38,18 @@ func hasFlag(call, param llvm.Value, kind string) bool { func isReadOnly(value llvm.Value) bool { uses := getUses(value) for _, use := range uses { - if !use.IsAGetElementPtrInst().IsNil() { + switch { + case !use.IsAGetElementPtrInst().IsNil(): if !isReadOnly(use) { return false } - } else if !use.IsACallInst().IsNil() { + case !use.IsACallInst().IsNil(): if !hasFlag(use, value, "readonly") { return false } - } else { + case !use.IsALoadInst().IsNil(): + // Loads are read-only. + default: // Unknown instruction, might not be readonly. return false }