diff --git a/.circleci/config.yml b/.circleci/config.yml index 1dff3ba719..a3a052c28c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,12 +10,12 @@ commands: steps: - restore_cache: keys: - - llvm-source-16-v3 + - llvm-source-19-v1 - run: name: "Fetch LLVM source" command: make llvm-source - save_cache: - key: llvm-source-16-v3 + 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: @@ -55,7 +55,7 @@ commands: - run: name: "Install apt dependencies" command: | - echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<> main' > /etc/apt/sources.list.d/llvm.list + echo 'deb https://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-<> main' > /etc/apt/sources.list.d/llvm.list wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - apt-get update apt-get install --no-install-recommends -y \ @@ -69,16 +69,16 @@ commands: - build-binaryen-linux - restore_cache: keys: - - go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - - go-cache-v3-{{ checksum "go.mod" }} + - go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} + - go-cache-v4-{{ checksum "go.mod" }} - llvm-source-linux - run: go install -tags=llvm<> . - restore_cache: keys: - - wasi-libc-sysroot-systemclang-v6 + - wasi-libc-sysroot-systemclang-v7 - run: make wasi-libc - save_cache: - key: wasi-libc-sysroot-systemclang-v6 + key: wasi-libc-sysroot-systemclang-v7 paths: - lib/wasi-libc/sysroot - when: @@ -88,22 +88,33 @@ 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-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} + key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: - ~/.cache/go-build - /go/pkg/mod jobs: - test-llvm14-go118: + test-llvm15-go119: docker: - - image: golang:1.18-buster + - image: golang:1.19-bullseye steps: - test-linux: - llvm: "14" + 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-llvm19-go124: + docker: + - image: golang:1.24-bullseye + steps: + - test-linux: + llvm: "19" resource_class: large workflows: @@ -111,4 +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-llvm14-go118 + - 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 1284a4edd3..25b5971783 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -14,26 +14,38 @@ concurrency: jobs: build-macos: name: build-macos - runs-on: macos-11 + strategy: + matrix: + # macos-13: amd64 (oldest supported version as of 18-10-2024) + # macos-14: arm64 (oldest arm64 version) + os: [macos-13, macos-14] + include: + - 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@v3 + 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@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - name: Restore LLVM source cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-16-macos-v1 + key: llvm-source-19-${{ matrix.os }}-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -44,7 +56,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -55,14 +67,13 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-16-macos-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 @@ -73,16 +84,16 @@ jobs: make llvm-build find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache wasi-libc sysroot - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-wasi-libc with: - key: wasi-libc-sysroot-v4 + key: wasi-libc-sysroot-${{ matrix.os }}-v1 path: lib/wasi-libc/sysroot - name: Build wasi-libc if: steps.cache-wasi-libc.outputs.cache-hit != 'true' @@ -90,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-amd64.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 @@ -106,29 +115,43 @@ jobs: # - have a double-zipped artifact when downloaded from the UI # - have a very slow artifact upload # We're doing the former here, to keep artifact uploads fast. - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: darwin-amd64-double-zipped - path: build/tinygo.darwin-amd64.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, 18, 19] steps: + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@master + - name: Fix Python symlinks + run: | + # Github runners have broken symlinks, so relink + # see: https://github.com/actions/setup-python/issues/577 + brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done - name: Install LLVM - shell: bash run: | - HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@16 + brew install llvm@${{ matrix.version }} - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - - name: Build TinyGo + - 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 == 19 run: go install - name: Check binary + if: matrix.version == 19 run: tinygo version diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5ecfd2239d..b7485387a4 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,15 +19,26 @@ jobs: packages: write contents: read steps: + - name: Free Disk space + shell: bash + run: | + df -h + sudo rm -rf /opt/hostedtoolcache + sudo rm -rf /usr/local/lib/android + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/graalvm + sudo rm -rf /usr/local/share/boost + df -h - name: Check out the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: | tinygo/tinygo-dev @@ -36,24 +47,23 @@ jobs: type=sha,format=long type=raw,value=latest - name: Log in to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Log in to Github Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - build-contexts: tinygo-llvm-build=docker-image://tinygo/llvm-16 cache-from: type=gha cache-to: type=gha,mode=max - name: Trigger Drivers repo build on Github Actions diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 0ef57bf4ad..0a8fabf1f2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -18,32 +18,37 @@ jobs: # statically linked binary. runs-on: ubuntu-latest container: - image: golang:1.21-alpine + image: golang:1.24-alpine + outputs: + version: ${{ steps.version.outputs.version }} steps: - name: Install apk dependencies - # tar: needed for actions/cache@v3 + # 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" - name: Checkout - uses: actions/checkout@v3 + 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@v3 + uses: actions/cache@v4 with: key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }} path: | ~/.cache/go-build ~/go/pkg/mod - name: Restore LLVM source cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-16-linux-alpine-v1 + key: llvm-source-19-linux-alpine-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -54,7 +59,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -65,10 +70,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-16-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' @@ -83,13 +88,13 @@ jobs: # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-binaryen with: key: binaryen-linux-alpine-v1 @@ -100,10 +105,10 @@ jobs: apk add cmake samurai python3 make binaryen STATIC=1 - name: Cache wasi-libc - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-wasi-libc with: - key: wasi-libc-sysroot-linux-alpine-v1 + key: wasi-libc-sysroot-linux-alpine-v2 path: lib/wasi-libc/sysroot - name: Build wasi-libc if: steps.cache-wasi-libc.outputs.cache-hit != 'true' @@ -113,47 +118,54 @@ 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@v3 + 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 needs: build-linux steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + submodules: true - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + 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/v5.0.0/wasmtime-v5.0.0-x86_64-linux.tar.xz -o wasmtime-v5.0.0-x86_64-linux.tar.xz -SfL - tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v5.0.0-x86_64-linux.tar.xz --strip-components=1 wasmtime-v5.0.0-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@v3 + 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 @@ -161,7 +173,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true - name: Install apt dependencies @@ -176,25 +188,25 @@ jobs: simavr \ ninja-build - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '16' + node-version: '18' - name: Install wasmtime - run: | - mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin - curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v5.0.0/wasmtime-v5.0.0-x86_64-linux.tar.xz -o wasmtime-v5.0.0-x86_64-linux.tar.xz -SfL - tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v5.0.0-x86_64-linux.tar.xz --strip-components=1 wasmtime-v5.0.0-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@v3 + uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-16-linux-asserts-v1 + key: llvm-source-19-linux-asserts-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -205,7 +217,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -216,10 +228,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-16-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' @@ -232,13 +244,13 @@ jobs: # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-binaryen with: key: binaryen-linux-asserts-v1 @@ -247,10 +259,10 @@ jobs: if: steps.cache-binaryen.outputs.cache-hit != 'true' run: make binaryen - name: Cache wasi-libc - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-wasi-libc with: - key: wasi-libc-sysroot-linux-asserts-v5 + key: wasi-libc-sysroot-linux-asserts-v6 path: lib/wasi-libc/sysroot - name: Build wasi-libc if: steps.cache-wasi-libc.outputs.cache-hit != 'true' @@ -266,8 +278,8 @@ jobs: run: make tinygo-test - run: make smoketest - run: make wasmtest - - run: make tinygo-baremetal - build-linux-arm: + - 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 # an older glibc version and therefore are compatible with a wide range of @@ -276,28 +288,41 @@ jobs: # in that process to avoid doing lots of duplicate work and to avoid # complications around precompiled libraries such as compiler-rt shipped as # part of the release tarball. - runs-on: ubuntu-20.04 + strategy: + matrix: + goarch: [ arm, arm64 ] + include: + - goarch: arm64 + toolchain: aarch64-linux-gnu + libc: arm64 + - goarch: arm + toolchain: arm-linux-gnueabihf + libc: armhf + runs-on: ubuntu-22.04 # note: use the oldest image available! (see above) needs: build-linux steps: - name: Checkout - uses: actions/checkout@v3 + 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 sudo apt-get install --no-install-recommends \ qemu-user \ - g++-arm-linux-gnueabihf \ - libc6-dev-armhf-cross + g++-${{ matrix.toolchain }} \ + libc6-dev-${{ matrix.libc }}-cross - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - name: Restore LLVM source cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-16-linux-v1 + key: llvm-source-19-linux-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -308,7 +333,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save LLVM source cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -319,10 +344,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore LLVM build cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-16-linux-arm-v1 + key: llvm-build-19-linux-${{ matrix.goarch }}-v3 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -333,141 +358,27 @@ jobs: # Install build dependencies. sudo apt-get install --no-install-recommends ninja-build # build! - make llvm-build CROSS=arm-linux-gnueabihf + make llvm-build CROSS=${{ matrix.toolchain }} # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save LLVM build cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache Binaryen - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-binaryen with: - key: binaryen-linux-arm-v1 + 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 lib/binaryen - make CROSS=arm-linux-gnueabihf binaryen - - name: Install fpm - run: | - sudo gem install --version 4.0.7 public_suffix - sudo gem install --version 2.7.6 dotenv - sudo gem install --no-document fpm - - name: Build TinyGo binary - run: | - make CROSS=arm-linux-gnueabihf - - name: Download amd64 release - uses: actions/download-artifact@v3 - with: - name: linux-amd64-double-zipped - - name: Extract amd64 release - run: | - mkdir -p build/release - tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo - - name: Modify release - run: | - cp -p build/tinygo build/release/tinygo/bin - cp -p build/wasm-opt build/release/tinygo/bin - - name: Create arm release - run: | - make release deb RELEASEONLY=1 DEB_ARCH=armhf - cp -p build/release.tar.gz /tmp/tinygo.linux-arm.tar.gz - cp -p build/release.deb /tmp/tinygo_armhf.deb - - name: Publish release artifact - uses: actions/upload-artifact@v3 - with: - name: linux-arm-double-zipped - path: | - /tmp/tinygo.linux-arm.tar.gz - /tmp/tinygo_armhf.deb - build-linux-arm64: - # Build ARM64 Linux binaries, ready for release. - # It is set to "needs: build-linux" because it modifies the release created - # in that process to avoid doing lots of duplicate work and to avoid - # complications around precompiled libraries such as compiler-rt shipped as - # part of the release tarball. - runs-on: ubuntu-20.04 - needs: build-linux - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Install apt dependencies - run: | - sudo apt-get update - sudo apt-get install --no-install-recommends \ - qemu-user \ - g++-aarch64-linux-gnu \ - libc6-dev-arm64-cross \ - ninja-build - - name: Install Go - uses: actions/setup-go@v3 - with: - go-version: '1.21' - cache: true - - name: Restore LLVM source cache - uses: actions/cache/restore@v3 - id: cache-llvm-source - with: - key: llvm-source-16-linux-v1 - path: | - llvm-project/clang/lib/Headers - llvm-project/clang/include - llvm-project/compiler-rt - llvm-project/lld/include - llvm-project/llvm/include - - name: Download LLVM source - if: steps.cache-llvm-source.outputs.cache-hit != 'true' - run: make llvm-source - - name: Save LLVM source cache - uses: actions/cache/save@v3 - if: steps.cache-llvm-source.outputs.cache-hit != 'true' - with: - key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} - path: | - llvm-project/clang/lib/Headers - llvm-project/clang/include - llvm-project/compiler-rt - llvm-project/lld/include - llvm-project/llvm/include - - name: Restore LLVM build cache - uses: actions/cache/restore@v3 - id: cache-llvm-build - with: - key: llvm-build-16-linux-arm64-v1 - path: llvm-build - - name: Build LLVM - if: steps.cache-llvm-build.outputs.cache-hit != 'true' - run: | - # fetch LLVM source - rm -rf llvm-project - make llvm-source - # build! - make llvm-build CROSS=aarch64-linux-gnu - # Remove unnecessary object files (to reduce cache size). - find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - - name: Save LLVM build cache - uses: actions/cache/save@v3 - if: steps.cache-llvm-build.outputs.cache-hit != 'true' - with: - key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} - path: llvm-build - - name: Cache Binaryen - uses: actions/cache@v3 - id: cache-binaryen - with: - key: binaryen-linux-arm64-v1 - path: build/wasm-opt - - name: Build Binaryen - if: steps.cache-binaryen.outputs.cache-hit != 'true' - run: | - git submodule update --init lib/binaryen - make CROSS=aarch64-linux-gnu binaryen + make CROSS=${{ matrix.toolchain }} binaryen - name: Install fpm run: | sudo gem install --version 4.0.7 public_suffix @@ -475,28 +386,28 @@ jobs: sudo gem install --no-document fpm - name: Build TinyGo binary run: | - make CROSS=aarch64-linux-gnu + make CROSS=${{ matrix.toolchain }} - name: Download amd64 release - uses: actions/download-artifact@v3 + 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 cp -p build/wasm-opt build/release/tinygo/bin - - name: Create arm64 release + - name: Create ${{ matrix.goarch }} release run: | - make release deb RELEASEONLY=1 DEB_ARCH=arm64 - cp -p build/release.tar.gz /tmp/tinygo.linux-arm64.tar.gz - cp -p build/release.deb /tmp/tinygo_arm64.deb + make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }} + cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz + cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb - name: Publish release artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: linux-arm64-double-zipped + name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} path: | - /tmp/tinygo.linux-arm64.tar.gz - /tmp/tinygo_arm64.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 ff9f3952b2..d1ba745b2e 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -25,18 +25,18 @@ jobs: contents: read steps: - name: Check out the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: recursive - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Docker meta id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: | - tinygo/llvm-16 - ghcr.io/${{ github.repository_owner }}/llvm-16 + tinygo/llvm-19 + ghcr.io/${{ github.repository_owner }}/llvm-19 tags: | type=sha,format=long type=raw,value=latest @@ -46,13 +46,13 @@ jobs: username: ${{ secrets.DOCKER_HUB_USERNAME }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Log in to Github Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: target: tinygo-llvm-build context: . diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml new file mode 100644 index 0000000000..b19538c4d9 --- /dev/null +++ b/.github/workflows/nix.yml @@ -0,0 +1,48 @@ +name: Nix + +on: + pull_request: + push: + branches: + - dev + - release + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +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, bdwgc + run: | + 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-19-linux-nix-v1 + path: | + llvm-project/compiler-rt + - name: Download LLVM source + if: steps.cache-llvm-source.outputs.cache-hit != 'true' + run: make llvm-source + - name: Save LLVM source cache + uses: actions/cache/save@v4 + if: steps.cache-llvm-source.outputs.cache-hit != 'true' + with: + key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} + path: | + llvm-project/compiler-rt + - uses: cachix/install-nix-action@v22 + - name: Test + run: | + nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo" diff --git a/.github/workflows/sizediff-install-pkgs.sh b/.github/workflows/sizediff-install-pkgs.sh new file mode 100755 index 0000000000..e81c994ea8 --- /dev/null +++ b/.github/workflows/sizediff-install-pkgs.sh @@ -0,0 +1,12 @@ +# Command that's part of sizediff.yml. This is put in a separate file so that it +# 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/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-19-dev \ + clang-19 \ + libclang-19-dev \ + lld-19 diff --git a/.github/workflows/sizediff.yml b/.github/workflows/sizediff.yml index a74291f959..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: @@ -18,34 +20,26 @@ jobs: run: | echo "$HOME/go/bin" >> $GITHUB_PATH - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # fetch all history (no sparse checkout) submodules: true - name: Install apt dependencies - run: | - echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-16 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-16-dev \ - clang-16 \ - libclang-16-dev \ - lld-16 + run: ./.github/workflows/sizediff-install-pkgs.sh - name: Restore LLVM source cache - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-llvm-source with: - key: llvm-source-16-sizediff-v1 + key: llvm-source-19-sizediff-v1 path: | llvm-project/compiler-rt - name: Download LLVM source if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Cache Go - uses: actions/cache@v3 + 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 @@ -55,22 +49,25 @@ jobs: - name: Save HEAD run: git branch github-actions-saved-HEAD HEAD + # Compute sizes for the PR branch + - name: Build tinygo binary for the PR branch + run: go install + - name: Determine binary sizes on the PR branch + run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt) + # Compute sizes for the dev branch - name: Checkout dev branch - run: git checkout --no-recurse-submodules `git merge-base HEAD origin/dev` + run: | + git reset --hard origin/dev + git checkout --no-recurse-submodules `git merge-base HEAD origin/dev` + - name: Install apt dependencies on the dev branch + # this is only needed on a PR that changes the LLVM version + run: ./.github/workflows/sizediff-install-pkgs.sh - name: Build tinygo binary for the dev branch run: go install - name: Determine binary sizes on the dev branch run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-dev.txt) - # Compute sizes for the PR branch - - name: Checkout PR branch - run: git checkout --no-recurse-submodules github-actions-saved-HEAD - - name: Build tinygo binary for the PR branch - run: go install - - name: Determine binary sizes on the PR branch - run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt) - # Create comment # TODO: add a summary, something like: # - overall size difference (percent) 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 5aaa39000b..42365f59b7 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -14,9 +14,11 @@ 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.3 + uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB @@ -29,19 +31,23 @@ jobs: run: | scoop install ninja binaryen - name: Checkout - uses: actions/checkout@v3 + 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@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - name: Restore cached LLVM source - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-source with: - key: llvm-source-16-windows-v1 + key: llvm-source-19-windows-v1 path: | llvm-project/clang/lib/Headers llvm-project/clang/include @@ -52,7 +58,7 @@ jobs: if: steps.cache-llvm-source.outputs.cache-hit != 'true' run: make llvm-source - name: Save cached LLVM source - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-source.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }} @@ -63,10 +69,10 @@ jobs: llvm-project/lld/include llvm-project/llvm/include - name: Restore cached LLVM build - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 id: cache-llvm-build with: - key: llvm-build-16-windows-v1 + key: llvm-build-19-windows-v1 path: llvm-build - name: Build LLVM if: steps.cache-llvm-build.outputs.cache-hit != 'true' @@ -80,23 +86,30 @@ jobs: # Remove unnecessary object files (to reduce cache size). find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; - name: Save cached LLVM build - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 if: steps.cache-llvm-build.outputs.cache-hit != 'true' with: key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} path: llvm-build - name: Cache wasi-libc sysroot - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-wasi-libc with: - key: wasi-libc-sysroot-v4 + key: wasi-libc-sysroot-v5 path: lib/wasi-libc/sysroot - 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 + 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 @@ -116,17 +129,17 @@ jobs: # - have a dobule-zipped artifact when downloaded from the UI # - have a very slow artifact upload # We're doing the former here, to keep artifact uploads fast. - uses: actions/upload-artifact@v3 + 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 needs: build-windows steps: - name: Configure pagefile - uses: al-cheb/configure-pagefile-action@v1.3 + uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB @@ -139,21 +152,21 @@ jobs: run: | scoop install binaryen - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - name: Download TinyGo build - uses: actions/download-artifact@v2 + 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 @@ -163,27 +176,27 @@ jobs: needs: build-windows steps: - name: Configure pagefile - uses: al-cheb/configure-pagefile-action@v1.3 + uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB disk-root: "C:" - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - name: Download TinyGo build - uses: actions/download-artifact@v2 + 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 @@ -192,7 +205,7 @@ jobs: needs: build-windows steps: - name: Configure pagefile - uses: al-cheb/configure-pagefile-action@v1.3 + uses: al-cheb/configure-pagefile-action@v1.4 with: minimum-size: 8GB maximum-size: 24GB @@ -203,22 +216,22 @@ jobs: - name: Install Dependencies shell: bash run: | - scoop install binaryen wasmtime + scoop install binaryen && scoop install wasmtime@29.0.1 - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.24' cache: true - name: Download TinyGo build - uses: actions/download-artifact@v2 + 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 857acaf46d..97689eff82 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,10 +9,11 @@ url = https://github.com/avr-rust/avr-mcu.git [submodule "lib/cmsis-svd"] path = lib/cmsis-svd - url = https://github.com/tinygo-org/cmsis-svd + url = https://github.com/cmsis-svd/cmsis-svd-data.git + branch = main [submodule "lib/wasi-libc"] path = lib/wasi-libc - url = https://github.com/CraneStation/wasi-libc + url = https://github.com/WebAssembly/wasi-libc [submodule "lib/picolibc"] path = lib/picolibc url = https://github.com/keith-packard/picolibc.git @@ -31,6 +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 6c320c541f..6f90d9badd 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -18,7 +18,8 @@ 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 * CMake @@ -27,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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9c212f47..1c1d488934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,460 @@ +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 +--- + +* **general** + * update the `net` submodule to updated version with `Buffers` implementation + +* **compiler** + * `syscall`: add wasm_unknown tag to some additional files so it can compile more code + +* **standard library** + * `runtime`: add Frame.Entry field + + +0.31.1 +--- + +* **general** + * fix Binaryen build in make task + * update final build stage of Docker `dev` image to go1.22 + * only use GHA cache for building Docker `dev` image + * update the `net` submodule to latest version + +* **compiler** + * `interp`: make getelementptr offsets signed + * `interp`: return a proper error message when indexing out of range + + +0.31.0 +--- + +* **general** + * remove LLVM 14 support + * add LLVM 17 support, and use it by default + * add Nix flake support + * update bundled Binaryen to version 116 + * add `ports` subcommand that lists available serial ports for `-port` and `-monitor` + * support wasmtime version 14 + * add `-serial=rtt` for serial output over SWD + * add Go 1.22 support and use it by default + * change minimum Node.js version from 16 to 18 +* **compiler** + * use the new LLVM pass manager + * allow systems with more stack space to allocate larger values on the stack + * `build`: fix a crash due to sharing GlobalValues between build instances + * `cgo`: add `C._Bool` type + * `cgo`: fix calling CGo callback inside generic function + * `compileopts`: set `purego` build tag by default so that more packages can be built + * `compileopts`: force-enable CGo to avoid build issues + * `compiler`: fix crash on type assert on interfaces with no methods + * `interp`: print LLVM instruction in traceback + * `interp`: support runtime times by running them at runtime + * `loader`: enforce Go language version in the type checker (this may break existing programs with an incorrect Go version in go.mod) + * `transform`: fix bug in StringToBytes optimization pass +* **standard library** + * `crypto/tls`: stub out a lot of functions + * `internal/task`, `machine`: make TinyGo code usable with "big Go" CGo + * `machine`: implement `I2C.SetBaudRate` consistently across chips + * `machine`: implement `SPI.Configure` consistently across chips + * `machine`: add `DeviceID` for nrf, rp2040, sam, stm32 + * `machine`: use smaller UART buffer size on atmega chips + * `machine/usb`: allow setting a serial number using a linker flag + * `math`: support more math functions on baremetal (picolibc) systems + * `net`: replace entire net package with a new one based on the netdev driver + * `os/user`: add bare-bones implementation of this package + * `reflect`: stub `CallSlice` and `FuncOf` + * `reflect`: add `TypeFor[T]` + * `reflect`: update `IsZero` to Go 1.22 semantics + * `reflect`: move indirect values into interface when setting interfaces + * `runtime`: stub `Breakpoint` + * `sync`: implement trylock +* **targets** + * `atmega`: use UART double speed mode for fewer errors and higher throughput + * `atmega328pb`: refactor to enable extra uart + * `avr`: don't compile large parts of picolibc (math, stdio) for LLVM 17 support + * `esp32`: switch over to the official SVD file + * `esp32c3`: implement USB_SERIAL for USBCDC communication + * `esp32c3`: implement I2C + * `esp32c3`: implement RNG + * `esp32c3`: add more ROM functions and update linker script for the in-progress wifi support + * `esp32c3`: update to newer SVD files + * `rp2040`: add support for UART hardware flow control + * `rp2040`: add definition for `machine.PinToggle` + * `rp2040`: set XOSC startup delay multiplier + * `samd21`: add support for UART hardware flow control + * `samd51`: add support for UART hardware flow control + * `wasm`: increase default stack size to 64k for wasi/wasm targets + * `wasm`: bump wasi-libc version to SDK 20 + * `wasm`: remove line of dead code in wasm_exec.js +* **new targets/boards** + * `qtpy-esp32c3`: add Adafruit QT Py ESP32-C3 board + * `mksnanov3`: add support for the MKS Robin Nano V3.x + * `nrf52840-generic`: add generic nrf52840 chip support + * `thumby`: add support for Thumby + * `wasm`: add new `wasm-unknown` target that doesn't depend on WASI or a browser +* **boards** + * `arduino-mkrwifi1010`, `arduino-nano33`, `nano-rp2040`, `matrixportal-m4`, `metro-m4-airlift`, `pybadge`, `pyportal`: add `ninafw` build tag and some constants for BLE support + * `gopher-badge`: fix typo in USB product name + * `nano-rp2040`: add UART1 and correct mappings for NINA via UART + * `pico`: bump default stack size from 2kB to 8kB + * `wioterminal`: expose UART4 + + 0.30.0 --- @@ -57,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** @@ -1736,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 ad3b586d83..520ad7c9b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,15 @@ # tinygo-llvm stage obtains the llvm source for TinyGo -FROM golang:1.21 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 + apt-get install -y apt-utils make cmake clang-15 ninja-build && \ + rm -rf \ + /var/lib/apt/lists/* \ + /var/log/* \ + /var/tmp/* \ + /tmp/* -COPY ./Makefile /tinygo/Makefile +COPY ./GNUmakefile /tinygo/GNUmakefile RUN cd /tinygo/ && \ make llvm-source @@ -15,26 +20,24 @@ FROM tinygo-llvm AS tinygo-llvm-build RUN cd /tinygo/ && \ make llvm-build -# tinygo-compiler stage builds the compiler itself -FROM tinygo-llvm-build AS tinygo-compiler +# tinygo-compiler-build stage builds the compiler itself +FROM tinygo-llvm-build AS tinygo-compiler-build COPY . /tinygo -# update submodules +# build the compiler and tools RUN cd /tinygo/ && \ - rm -rf ./lib/*/ && \ - git submodule sync && \ - git submodule update --init --recursive --force - -RUN cd /tinygo/ && \ - make + git submodule update --init && \ + make gen-device -j4 && \ + make build/release -# tinygo-tools stage installs the needed dependencies to compile TinyGo programs for all platforms. -FROM tinygo-compiler AS tinygo-tools +# tinygo-compiler copies the compiler build over to a base Go container (without +# all the build tools etc). +FROM golang:1.24 AS tinygo-compiler -RUN cd /tinygo/ && \ - make wasi-libc binaryen && \ - make gen-device -j4 && \ - cp build/* $GOPATH/bin/ +# Copy tinygo build. +COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo +# Configure the container. +ENV PATH="${PATH}:/tinygo/bin" CMD ["tinygo"] diff --git a/Makefile b/GNUmakefile similarity index 71% rename from Makefile rename to GNUmakefile index 03c3f17db4..1a97269e9b 100644 --- a/Makefile +++ 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 = 16 15 14 13 12 11 +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,9 +31,7 @@ export GOROOT = $(shell $(GO) env GOROOT) # Flags to pass to go test. GOTESTFLAGS ?= - -# md5sum binary -MD5SUM = md5sum +GOTESTPKGS ?= ./builder ./cgo ./compileopts ./compiler ./interp ./transform . # tinygo binary for tests TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo) @@ -113,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 @@ -130,14 +128,14 @@ ifeq ($(OS),Windows_NT) USE_SYSTEM_BINARYEN ?= 1 else ifeq ($(shell uname -s),Darwin) - MD5SUM = md5 + MD5SUM ?= md5 CGO_LDFLAGS += -lxar USE_SYSTEM_BINARYEN ?= 1 else ifeq ($(shell uname -s),FreeBSD) - MD5SUM = md5 + MD5SUM ?= md5 START_GROUP = -Wl,--start-group END_GROUP = -Wl,--end-group else @@ -145,8 +143,11 @@ else END_GROUP = -Wl,--end-group endif +# md5sum binary default, can be overridden by an environment variable +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,17 +175,17 @@ 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 @@ -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_16.x --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 $(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 @@ -265,25 +264,37 @@ endif wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi - 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) + 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=16 +MIN_NODEJS_VERSION=18 .PHONY: check-nodejs-version check-nodejs-version: ifeq (, $(shell which node)) - @echo "Install NodeJS version 16+ to run tests."; exit 1; + @echo "Install NodeJS version 18+ to run tests."; exit 1; endif - @if [ $(NODEJS_VERSION) -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version 16+ to run tests."; exit 1; fi + @if [ $(NODEJS_VERSION) -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version 18+ to run tests."; exit 1; fi -# Build the Go compiler. -tinygo: - @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`" . +tinygo: ## Build the TinyGo compiler + @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" . test: wasi-libc check-nodejs-version - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=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 @@ -506,21 +597,27 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id + @$(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 - $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1 @$(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=reelboard 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=microbit examples/microbit-blink @$(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=circuitplay_express examples/blinky1 @$(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=circuitplay_bluefruit 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=mch2022 examples/machinetest + @$(MD5SUM) test.wasm + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1 + @$(MD5SUM) test.wasm + GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=pico examples/blinky1 @$(MD5SUM) test.wasm endif # test all targets/boards @@ -596,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 @@ -604,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 @@ -614,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 @@ -652,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 @@ -668,6 +771,16 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=ae-rp2040 examples/echo @$(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 @@ -682,6 +795,8 @@ 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/machinetest + @$(MD5SUM) test.hex ifneq ($(STM32), 0) $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 @$(MD5SUM) test.hex @@ -697,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 @@ -715,8 +832,12 @@ ifneq ($(STM32), 0) @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=swan examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=mksnanov3 examples/blinky1 + @$(MD5SUM) test.hex endif - $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial + $(TINYGO) build -size short -o test.hex -target=atmega328pb examples/blinkm + @$(MD5SUM) test.hex + $(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 @@ -741,32 +862,53 @@ 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=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=esp32c3-12f examples/serial + $(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 + $(TINYGO) build -size short -o wasm.wasm -target=wasm-unknown examples/hello-wasm-unknown endif # test various compiler flags $(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1 @@ -775,11 +917,14 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo @$(MD5SUM) test.hex - $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial + $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo + @$(MD5SUM) test.hex + $(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 @@ -796,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 @@ -807,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 @@ -824,28 +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 @@ -857,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 @@ -878,9 +1040,40 @@ deb: @mkdir -p build/release-deb/usr/local/lib cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo - fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb + fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb 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 7f32edcfd9..518dcdad18 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # TinyGo - Go compiler for small places -[![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) +[![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![Nix](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools. @@ -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 dc360b92ea..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 @@ -83,8 +87,7 @@ type packageAction struct { FileHashes map[string]string // hash of every file that's part of the package EmbeddedFiles map[string]string // hash of all the //go:embed files in the package Imports map[string]string // map from imported package to action ID hash - OptLevel int // LLVM optimization level (0-3) - SizeLevel int // LLVM optimization for size level (0-2) + OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz) UndefinedGlobals []string // globals that are left as external globals (no initializer) } @@ -115,25 +118,51 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe cacheDir = tmpdir } + // Create default global values. + globalValues := map[string]map[string]string{ + "runtime": { + "buildVersion": goenv.Version(), + }, + "testing": {}, + } + if config.TestConfig.CompileTestBinary { + // The testing.testBinary is set to "1" when in a test. + // This is needed for testing.Testing() to work correctly. + globalValues["testing"]["testBinary"] = "1" + } + + // Copy over explicitly set global values, like + // -ldflags="-X main.Version="1.0" + for pkgPath, vals := range config.Options.GlobalValues { + if _, ok := globalValues[pkgPath]; !ok { + globalValues[pkgPath] = map[string]string{} + } + for k, v := range vals { + globalValues[pkgPath][k] = v + } + } + // Check for a libc dependency. // As a side effect, this also creates the headers for the given libc, if // 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 } @@ -145,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 @@ -158,7 +195,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc) } - optLevel, sizeLevel, _ := config.OptLevels() + optLevel, speedLevel, sizeLevel := config.OptLevel() compilerConfig := &compiler.Config{ Triple: config.Triple(), CPU: config.CPU(), @@ -166,16 +203,19 @@ 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, - TinyGoVersion: goenv.Version, + TinyGoVersion: goenv.Version(), Scheduler: config.Scheduler(), AutomaticStackSize: config.AutomaticStackSize(), DefaultStackSize: config.StackSize(), + 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 @@ -188,7 +228,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe defer machine.Dispose() // Load entire program AST into memory. - lprogram, err := loader.Load(config, pkgName, config.ClangHeaders, types.Config{ + lprogram, err := loader.Load(config, pkgName, types.Config{ Sizes: compiler.Sizes(machine), }) if err != nil { @@ -208,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() @@ -217,34 +263,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe var packageJobs []*compileJob packageActionIDJobs := make(map[string]*compileJob) - if config.Options.GlobalValues == nil { - config.Options.GlobalValues = make(map[string]map[string]string) - } - if config.Options.GlobalValues["runtime"]["buildVersion"] == "" { - version := goenv.Version - if strings.HasSuffix(goenv.Version, "-dev") && goenv.GitSha1 != "" { - version += "-" + goenv.GitSha1 - } - if config.Options.GlobalValues["runtime"] == nil { - config.Options.GlobalValues["runtime"] = make(map[string]string) - } - config.Options.GlobalValues["runtime"]["buildVersion"] = version - } - if config.TestConfig.CompileTestBinary { - // The testing.testBinary is set to "1" when in a test. - // This is needed for testing.Testing() to work correctly. - if config.Options.GlobalValues["testing"] == nil { - config.Options.GlobalValues["testing"] = make(map[string]string) - } - config.Options.GlobalValues["testing"]["testBinary"] = "1" - } - var embedFileObjects []*compileJob for _, pkg := range lprogram.Sorted() { pkg := pkg // necessary to avoid a race condition var undefinedGlobals []string - for name := range config.Options.GlobalValues[pkg.Pkg.Path()] { + for name := range globalValues[pkg.Pkg.Path()] { undefinedGlobals = append(undefinedGlobals, name) } sort.Strings(undefinedGlobals) @@ -321,7 +345,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe EmbeddedFiles: make(map[string]string, len(allFiles)), Imports: make(map[string]string, len(pkg.Pkg.Imports())), OptLevel: optLevel, - SizeLevel: sizeLevel, UndefinedGlobals: undefinedGlobals, } for filePath, hash := range pkg.FileHashes { @@ -344,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{ @@ -370,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) @@ -432,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) @@ -521,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") @@ -532,13 +567,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe irbuilder := mod.Context().NewBuilder() defer irbuilder.Dispose() irbuilder.SetInsertPointAtEnd(block) - i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) + ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) for _, pkg := range lprogram.Sorted() { pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") if pkgInit.IsNil() { panic("init not found for " + pkg.Pkg.Path()) } - irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "") + irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(ptrType)}, "") } irbuilder.CreateRetVoid() @@ -587,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) @@ -643,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 } @@ -654,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. @@ -662,7 +732,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe job := &compileJob{ description: "compile extra file " + path, run: func(job *compileJob) error { - result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.Options.PrintCommands) + result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), config.Options.PrintCommands) job.result = result return err }, @@ -676,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 { @@ -740,20 +810,21 @@ 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, - "-Xlink=/opt:lldlto="+strconv.Itoa(optLevel), + "-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) } else if config.GOOS() == "darwin" { // Options for the ld64-compatible lld linker. ldflags = append(ldflags, - "--lto-O"+strconv.Itoa(optLevel), + "--lto-O"+strconv.Itoa(speedLevel), "-cache_path_lto", filepath.Join(cacheDir, "thinlto")) } else { // Options for the ELF linker. ldflags = append(ldflags, - "--lto-O"+strconv.Itoa(optLevel), + "--lto-O"+strconv.Itoa(speedLevel), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), ) } @@ -764,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 15. + // Can hopefully be removed in LLVM 19. ldflags = append(ldflags, "-mllvm", "--rotation-max-header-size=0") } @@ -773,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 @@ -797,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) @@ -807,21 +884,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Run wasm-opt for wasm binaries if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" { - var opt string - switch config.Options.Opt { - case "none", "0": - opt = "-O0" - case "1": - opt = "-O1" - case "2": - opt = "-O2" - case "s": - opt = "-Os" - case "z": - opt = "-Oz" - default: - return fmt.Errorf("unknown opt level: %q", config.Options.Opt) - } + optLevel, _, _ := config.OptLevel() + opt := "-" + optLevel var args []string @@ -829,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 @@ -846,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, + } + + 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) } - sizes, err := loadProgramSize(result.Executable, packagePathMap) + } + + // 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") } @@ -871,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) } } @@ -1060,18 +1194,11 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { } } - // Insert values from -ldflags="-X ..." into the IR. - err = setGlobalValues(mod, config.Options.GlobalValues) - if err != nil { - return err - } - - // Optimization levels here are roughly the same as Clang, but probably not - // exactly. - optLevel, sizeLevel, inlinerThreshold := config.OptLevels() - errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold) + // 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") @@ -1080,10 +1207,19 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { 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) @@ -1099,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) @@ -1127,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 @@ -1198,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) @@ -1361,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 @@ -1372,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: @@ -1411,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 03a33b9d49..ccccef30ba 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/tinygo-org/tinygo/compileopts" - "github.com/tinygo-org/tinygo/goenv" "tinygo.org/x/go-llvm" ) @@ -34,8 +33,11 @@ func TestClangAttributes(t *testing.T) { "k210", "nintendoswitch", "riscv-qemu", - "wasi", + "tkey", + "wasip1", + "wasip2", "wasm", + "wasm-unknown", } if hasBuiltinTools { // hasBuiltinTools is set when TinyGo is statically linked with LLVM, @@ -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) }) @@ -74,7 +85,6 @@ func TestClangAttributes(t *testing.T) { func testClangAttributes(t *testing.T, options *compileopts.Options) { testDir := t.TempDir() - clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) ctx := llvm.NewContext() defer ctx.Dispose() @@ -84,9 +94,8 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) { t.Fatalf("could not load target: %s", err) } config := compileopts.Config{ - Options: options, - Target: target, - ClangHeaders: clangHeaderPath, + Options: options, + Target: target, } // Create a very simple C input file. @@ -98,7 +107,7 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) { // Compile this file using Clang. outpath := filepath.Join(testDir, "test.bc") - flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags()...) + flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags(false)...) if config.GOOS() == "darwin" { // Silence some warnings that happen when testing GOOS=darwin on // something other than MacOS. diff --git a/builder/buildid.go b/builder/buildid.go index 80592dc293..e5edd05e51 100644 --- a/builder/buildid.go +++ b/builder/buildid.go @@ -77,6 +77,15 @@ func ReadBuildID() ([]byte, error) { } return raw[4:], nil } + + // Normally we would have found a build ID by now. But not on Nix, + // unfortunately, because Nix adds -no_uuid for some reason: + // https://github.com/NixOS/nixpkgs/issues/178366 + // Fall back to the same implementation that we use for Windows. + id, err := readRawGoBuildID(f, 32*1024) + if len(id) != 0 || err != nil { + return id, err + } default: // On other platforms (such as Windows) there isn't such a convenient // build ID. Luckily, Go does have an equivalent of the build ID, which @@ -88,16 +97,31 @@ func ReadBuildID() ([]byte, error) { // directly. Luckily the build ID is always at the start of the file. // For details, see: // https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go - fileStart := make([]byte, 4096) - _, err := io.ReadFull(f, fileStart) - index := bytes.Index(fileStart, []byte("\xff Go build ID: \"")) - if index < 0 || index > len(fileStart)-103 { - return nil, fmt.Errorf("could not find build id in %s", err) - } - buf := fileStart[index : index+103] - if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) { - return buf[len("\xff Go build ID: \"") : len(buf)-1], nil + id, err := readRawGoBuildID(f, 4096) + if len(id) != 0 || err != nil { + return id, err } } - return nil, fmt.Errorf("could not find build ID in %s", executable) + return nil, fmt.Errorf("could not find build ID in %v", executable) +} + +// The Go toolchain stores a build ID in the binary that we can use, as a +// fallback if binary file specific build IDs can't be obtained. +// This function reads that build ID from the binary. +func readRawGoBuildID(f *os.File, prefixSize int) ([]byte, error) { + fileStart := make([]byte, prefixSize) + _, err := io.ReadFull(f, fileStart) + if err != nil { + return nil, fmt.Errorf("could not read build id from %s: %v", f.Name(), err) + } + index := bytes.Index(fileStart, []byte("\xff Go build ID: \"")) + if index < 0 || index > len(fileStart)-103 { + return nil, fmt.Errorf("could not find build id in %s", f.Name()) + } + buf := fileStart[index : index+103] + if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) { + return buf[len("\xff Go build ID: \"") : len(buf)-1], nil + } + + return nil, nil } 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 8b28426b05..2cbb7b9f68 100644 --- a/builder/cc1as.cpp +++ b/builder/cc1as.cpp @@ -1,5 +1,12 @@ //go:build byollvm +// Source: https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/cc1as_main.cpp +// This file needs to be updated each LLVM release. +// There are a few small modifications to make, like: +// * ExecuteAssembler is made non-static. +// * The struct AssemblerImplementation is moved to cc1as.h so it can be +// included elsewhere. + //===-- cc1as.cpp - Clang Assembler --------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -21,8 +28,8 @@ #include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/Utils.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" -#include "llvm/ADT/Triple.h" #include "llvm/IR/DataLayout.h" #include "llvm/MC/MCAsmBackend.h" #include "llvm/MC/MCAsmInfo.h" @@ -46,7 +53,6 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormattedStream.h" -#include "llvm/Support/Host.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" @@ -55,6 +61,8 @@ #include "llvm/Support/TargetSelect.h" #include "llvm/Support/Timer.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/TargetParser/Host.h" +#include "llvm/TargetParser/Triple.h" #include #include #include @@ -74,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) { @@ -90,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) @@ -137,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); @@ -151,8 +160,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) { auto Split = StringRef(Arg).split('='); - Opts.DebugPrefixMap.insert( - {std::string(Split.first), std::string(Split.second)}); + Opts.DebugPrefixMap.emplace_back(Split.first, Split.second); } // Frontend Options @@ -225,6 +233,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts, .Case("default", EmitDwarfUnwindType::Default); } + 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); return Success; @@ -260,8 +272,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts, MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true); if (std::error_code EC = Buffer.getError()) { - Error = EC.message(); - return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile; + return Diags.Report(diag::err_fe_error_reading) + << Opts.InputFile << EC.message(); } SourceMgr SrcMgr; @@ -277,7 +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( @@ -286,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()) @@ -332,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()) @@ -370,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. @@ -384,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 { @@ -410,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); } @@ -425,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( @@ -510,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 955ded1193..cc973fd88a 100644 --- a/builder/cc1as.h +++ b/builder/cc1as.h @@ -1,3 +1,6 @@ +// Source: https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/cc1as_main.cpp +// See cc1as.cpp for details. + //===-- cc1as.h - Clang Assembler ----------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. @@ -35,16 +38,23 @@ 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; std::string DwarfDebugProducer; std::string DebugCompilationDir; - std::map DebugPrefixMap; + llvm::SmallVector, 0> DebugPrefixMap; llvm::DebugCompressionType CompressDebugSections = llvm::DebugCompressionType::None; std::string MainFileName; @@ -63,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; /// @} @@ -71,24 +83,41 @@ 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. EmitDwarfUnwindType EmitDwarfUnwind; + // 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; @@ -119,6 +148,7 @@ struct AssemblerInvocation { ShowInst = 0; ShowEncoding = 0; RelaxAll = 0; + SSE2AVX = 0; NoExecStack = 0; FatalWarnings = 0; NoWarn = 0; @@ -128,6 +158,8 @@ struct AssemblerInvocation { DwarfVersion = 0; EmbedBitcode = 0; EmitDwarfUnwind = EmitDwarfUnwindType::Default; + EmitCompactUnwindNonCanonical = false; + Crel = false; } static bool CreateFromArgs(AssemblerInvocation &Res, diff --git a/builder/clang.cpp b/builder/clang.cpp index e51d693389..6ffe75e3e5 100644 --- a/builder/clang.cpp +++ b/builder/clang.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include using namespace llvm; using namespace clang; 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 d5c8166c99..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,23 +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 > 21 { + 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.21, 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) } - clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) + // 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, - ClangHeaders: clangHeaderPath, + GoMinorVersion: gorootMinor, TestConfig: options.TestConfig, }, nil } diff --git a/builder/env.go b/builder/env.go deleted file mode 100644 index d60861317d..0000000000 --- a/builder/env.go +++ /dev/null @@ -1,105 +0,0 @@ -package builder - -import ( - "errors" - "io/fs" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "sort" - "strings" - - "tinygo.org/x/go-llvm" -) - -// getClangHeaderPath returns the path to the built-in Clang headers. It tries -// multiple locations, which should make it find the directory when installed in -// various ways. -func getClangHeaderPath(TINYGOROOT string) string { - // Check whether we're running from the source directory. - path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers") - if _, err := os.Stat(path); !errors.Is(err, fs.ErrNotExist) { - return path - } - - // Check whether we're running from the installation directory. - path = filepath.Join(TINYGOROOT, "lib", "clang", "include") - if _, err := os.Stat(path); !errors.Is(err, fs.ErrNotExist) { - return path - } - - // It looks like we are built with a system-installed LLVM. Do a last - // attempt: try to use Clang headers relative to the clang binary. - llvmMajor := strings.Split(llvm.Version, ".")[0] - for _, cmdName := range commands["clang"] { - binpath, err := exec.LookPath(cmdName) - if err == nil { - // This should be the command that will also be used by - // execCommand. To avoid inconsistencies, make sure we use the - // headers relative to this command. - binpath, err = filepath.EvalSymlinks(binpath) - if err != nil { - // Unexpected. - return "" - } - // Example executable: - // /usr/lib/llvm-9/bin/clang - // Example include path: - // /usr/lib/llvm-9/lib64/clang/9.0.1/include/ - llvmRoot := filepath.Dir(filepath.Dir(binpath)) - clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang") - dirs64, err64 := ioutil.ReadDir(clangVersionRoot) - // Example include path: - // /usr/lib/llvm-9/lib/clang/9.0.1/include/ - clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang") - dirs32, err32 := ioutil.ReadDir(clangVersionRoot) - if err64 != nil && err32 != nil { - // Unexpected. - continue - } - dirnames := make([]string, len(dirs64)+len(dirs32)) - dirCount := 0 - for _, d := range dirs32 { - name := d.Name() - if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") { - dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name) - dirCount++ - } - } - for _, d := range dirs64 { - name := d.Name() - if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") { - dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name) - dirCount++ - } - } - sort.Strings(dirnames) - // Check for the highest version first. - for i := dirCount - 1; i >= 0; i-- { - path := filepath.Join(dirnames[i], "include") - _, err := os.Stat(filepath.Join(path, "stdint.h")) - if err == nil { - return path - } - } - } - } - - // On Arch Linux, the clang executable is stored in /usr/bin rather than being symlinked from there. - // Search directly in /usr/lib for clang. - if matches, err := filepath.Glob("/usr/lib/clang/" + llvmMajor + ".*.*"); err == nil { - // Check for the highest version first. - sort.Strings(matches) - for i := len(matches) - 1; i >= 0; i-- { - path := filepath.Join(matches[i], "include") - _, err := os.Stat(filepath.Join(path, "stdint.h")) - if err == nil { - return path - } - } - } - - // Could not find it. - return "" -} 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 6517355b6b..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 { @@ -143,6 +134,10 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ // reproducible. Otherwise the temporary directory is stored in the archive // itself, which varies each run. args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir) + resourceDir := goenv.ClangResourceDir(false) + if resourceDir != "" { + args = append(args, "-resource-dir="+resourceDir) + } cpu := config.CPU() if cpu != "" { // X86 has deprecated the -mcpu flag, so we need to use -march instead. @@ -158,31 +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 strings.HasPrefix(target, "xtensa") { - // Hack to work around an issue in the Xtensa port: - // https://github.com/espressif/llvm-project/issues/52 - // Hopefully this will be fixed soon (LLVM 14). - args = append(args, "-D__ELF__") + if libc != nil { + args = append(args, config.LibcCFlags()...) } var once sync.Once @@ -236,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 @@ -251,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. @@ -260,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 @@ -280,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/lld.cpp b/builder/lld.cpp index 6cecbebe88..734c5703c6 100644 --- a/builder/lld.cpp +++ b/builder/lld.cpp @@ -5,7 +5,11 @@ #include #include -extern "C" { +LLD_HAS_DRIVER(coff) +LLD_HAS_DRIVER(elf) +LLD_HAS_DRIVER(mingw) +LLD_HAS_DRIVER(macho) +LLD_HAS_DRIVER(wasm) static void configure() { #if _WIN64 @@ -16,28 +20,13 @@ static void configure() { #endif } -bool tinygo_link_elf(int argc, char **argv) { - configure(); - std::vector args(argv, argv + argc); - return lld::elf::link(args, llvm::outs(), llvm::errs(), false, false); -} - -bool tinygo_link_macho(int argc, char **argv) { - configure(); - std::vector args(argv, argv + argc); - return lld::macho::link(args, llvm::outs(), llvm::errs(), false, false); -} - -bool tinygo_link_mingw(int argc, char **argv) { - configure(); - std::vector args(argv, argv + argc); - return lld::mingw::link(args, llvm::outs(), llvm::errs(), false, false); -} +extern "C" { -bool tinygo_link_wasm(int argc, char **argv) { +bool tinygo_link(int argc, char **argv) { configure(); std::vector args(argv, argv + argc); - return lld::wasm::link(args, llvm::outs(), llvm::errs(), false, false); + lld::Result r = lld::lldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS); + return !r.retCode; } } // external "C" 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 a65a920b73..dc03be46f7 100644 --- a/builder/musl.go +++ b/builder/musl.go @@ -6,15 +6,13 @@ import ( "os" "path/filepath" "regexp" - "strconv" "strings" "github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/goenv" - "tinygo.org/x/go-llvm" ) -var Musl = Library{ +var libMusl = Library{ name: "musl", makeHeaders: func(target, includeDir string) error { bits := filepath.Join(includeDir, "bits") @@ -93,6 +91,9 @@ var Musl = Library{ "-Wno-string-plus-int", "-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), @@ -106,42 +107,57 @@ var Musl = Library{ "-I" + muslDir + "/include", "-fno-stack-protector", } - llvmMajor, _ := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0]) - if llvmMajor >= 15 { - // This flag was added in Clang 15. It is not present in LLVM 14. - cflags = append(cflags, "-Wno-deprecated-non-prototype") - } return cflags }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") }, 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 1b7c748bec..9026b99ee4 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -3,13 +3,14 @@ package builder import ( "os" "path/filepath" + "strings" "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")) @@ -28,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", @@ -41,91 +44,23 @@ var Picolibc = Library{ }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") }, librarySources: func(target string) ([]string, error) { - return picolibcSources, nil + sources := append([]string(nil), picolibcSources...) + if !strings.HasPrefix(target, "avr") { + // Small chips without long jumps can't compile many files (printf, + // pow, etc). Therefore exclude those source files for those chips. + // Unfortunately it's difficult to exclude only some chips, so this + // excludes those files on all AVR chips for now. + // More information: + // https://github.com/llvm/llvm-project/issues/67042 + sources = append(sources, picolibcSourcesLarge...) + } + return sources, nil }, } var picolibcSources = []string{ "../../picolibc-stdio.c", - // srcs_tinystdio - "libc/tinystdio/asprintf.c", - "libc/tinystdio/bufio.c", - "libc/tinystdio/clearerr.c", - "libc/tinystdio/ecvt_r.c", - "libc/tinystdio/ecvt.c", - "libc/tinystdio/ecvtf_r.c", - "libc/tinystdio/ecvtf.c", - "libc/tinystdio/fcvt.c", - "libc/tinystdio/fcvt_r.c", - "libc/tinystdio/fcvtf.c", - "libc/tinystdio/fcvtf_r.c", - "libc/tinystdio/gcvt.c", - "libc/tinystdio/gcvtf.c", - "libc/tinystdio/fclose.c", - "libc/tinystdio/fdevopen.c", - "libc/tinystdio/feof.c", - "libc/tinystdio/ferror.c", - "libc/tinystdio/fflush.c", - "libc/tinystdio/fgetc.c", - "libc/tinystdio/fgets.c", - "libc/tinystdio/fileno.c", - "libc/tinystdio/filestrget.c", - "libc/tinystdio/filestrput.c", - "libc/tinystdio/filestrputalloc.c", - "libc/tinystdio/fmemopen.c", - "libc/tinystdio/fprintf.c", - "libc/tinystdio/fputc.c", - "libc/tinystdio/fputs.c", - "libc/tinystdio/fread.c", - //"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369 - "libc/tinystdio/fscanf.c", - "libc/tinystdio/fseek.c", - "libc/tinystdio/fseeko.c", - "libc/tinystdio/ftell.c", - "libc/tinystdio/ftello.c", - "libc/tinystdio/fwrite.c", - "libc/tinystdio/getchar.c", - "libc/tinystdio/gets.c", - "libc/tinystdio/matchcaseprefix.c", - "libc/tinystdio/mktemp.c", - "libc/tinystdio/perror.c", - "libc/tinystdio/printf.c", - "libc/tinystdio/putchar.c", - "libc/tinystdio/puts.c", - "libc/tinystdio/rewind.c", - "libc/tinystdio/scanf.c", - "libc/tinystdio/setbuf.c", - "libc/tinystdio/setbuffer.c", - "libc/tinystdio/setlinebuf.c", - "libc/tinystdio/setvbuf.c", - "libc/tinystdio/snprintf.c", - "libc/tinystdio/sprintf.c", - "libc/tinystdio/snprintfd.c", - "libc/tinystdio/snprintff.c", - "libc/tinystdio/sprintff.c", - "libc/tinystdio/sprintfd.c", - "libc/tinystdio/sscanf.c", - "libc/tinystdio/strfromf.c", - "libc/tinystdio/strfromd.c", - "libc/tinystdio/strtof.c", - "libc/tinystdio/strtof_l.c", - "libc/tinystdio/strtod.c", - "libc/tinystdio/strtod_l.c", - "libc/tinystdio/ungetc.c", - "libc/tinystdio/vasprintf.c", - "libc/tinystdio/vfiprintf.c", - "libc/tinystdio/vfprintf.c", - "libc/tinystdio/vfprintff.c", - "libc/tinystdio/vfscanf.c", - "libc/tinystdio/vfiscanf.c", - "libc/tinystdio/vfscanff.c", - "libc/tinystdio/vprintf.c", - "libc/tinystdio/vscanf.c", - "libc/tinystdio/vsscanf.c", - "libc/tinystdio/vsnprintf.c", - "libc/tinystdio/vsprintf.c", - "libc/string/bcmp.c", "libc/string/bcopy.c", "libc/string/bzero.c", @@ -229,6 +164,87 @@ var picolibcSources = []string{ "libc/string/wmempcpy.c", "libc/string/wmemset.c", "libc/string/xpg_strerror_r.c", +} + +// Parts of picolibc that are too large for small AVRs. +var picolibcSourcesLarge = []string{ + // srcs_tinystdio + "libc/tinystdio/asprintf.c", + "libc/tinystdio/bufio.c", + "libc/tinystdio/clearerr.c", + "libc/tinystdio/ecvt_r.c", + "libc/tinystdio/ecvt.c", + "libc/tinystdio/ecvtf_r.c", + "libc/tinystdio/ecvtf.c", + "libc/tinystdio/fcvt.c", + "libc/tinystdio/fcvt_r.c", + "libc/tinystdio/fcvtf.c", + "libc/tinystdio/fcvtf_r.c", + "libc/tinystdio/gcvt.c", + "libc/tinystdio/gcvtf.c", + "libc/tinystdio/fclose.c", + "libc/tinystdio/fdevopen.c", + "libc/tinystdio/feof.c", + "libc/tinystdio/ferror.c", + "libc/tinystdio/fflush.c", + "libc/tinystdio/fgetc.c", + "libc/tinystdio/fgets.c", + "libc/tinystdio/fileno.c", + "libc/tinystdio/filestrget.c", + "libc/tinystdio/filestrput.c", + "libc/tinystdio/filestrputalloc.c", + "libc/tinystdio/fmemopen.c", + "libc/tinystdio/fprintf.c", + "libc/tinystdio/fputc.c", + "libc/tinystdio/fputs.c", + "libc/tinystdio/fread.c", + //"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369 + "libc/tinystdio/fscanf.c", + "libc/tinystdio/fseek.c", + "libc/tinystdio/fseeko.c", + "libc/tinystdio/ftell.c", + "libc/tinystdio/ftello.c", + "libc/tinystdio/fwrite.c", + "libc/tinystdio/getchar.c", + "libc/tinystdio/gets.c", + "libc/tinystdio/matchcaseprefix.c", + "libc/tinystdio/mktemp.c", + "libc/tinystdio/perror.c", + "libc/tinystdio/printf.c", + "libc/tinystdio/putchar.c", + "libc/tinystdio/puts.c", + "libc/tinystdio/rewind.c", + "libc/tinystdio/scanf.c", + "libc/tinystdio/setbuf.c", + "libc/tinystdio/setbuffer.c", + "libc/tinystdio/setlinebuf.c", + "libc/tinystdio/setvbuf.c", + "libc/tinystdio/snprintf.c", + "libc/tinystdio/sprintf.c", + "libc/tinystdio/snprintfd.c", + "libc/tinystdio/snprintff.c", + "libc/tinystdio/sprintff.c", + "libc/tinystdio/sprintfd.c", + "libc/tinystdio/sscanf.c", + "libc/tinystdio/strfromf.c", + "libc/tinystdio/strfromd.c", + "libc/tinystdio/strtof.c", + "libc/tinystdio/strtof_l.c", + "libc/tinystdio/strtod.c", + "libc/tinystdio/strtod_l.c", + "libc/tinystdio/ungetc.c", + "libc/tinystdio/vasprintf.c", + "libc/tinystdio/vfiprintf.c", + "libc/tinystdio/vfprintf.c", + "libc/tinystdio/vfprintff.c", + "libc/tinystdio/vfscanf.c", + "libc/tinystdio/vfiscanf.c", + "libc/tinystdio/vfscanff.c", + "libc/tinystdio/vprintf.c", + "libc/tinystdio/vscanf.c", + "libc/tinystdio/vsscanf.c", + "libc/tinystdio/vsnprintf.c", + "libc/tinystdio/vsprintf.c", "libm/common/sf_finite.c", "libm/common/sf_copysign.c", @@ -323,6 +339,12 @@ var picolibcSources = []string{ "libm/common/math_err_may_uflow.c", "libm/common/math_err_check_uflow.c", "libm/common/math_err_check_oflow.c", + "libm/common/math_errf_divzerof.c", + "libm/common/math_errf_invalidf.c", + "libm/common/math_errf_may_uflowf.c", + "libm/common/math_errf_oflowf.c", + "libm/common/math_errf_uflowf.c", + "libm/common/math_errf_with_errnof.c", "libm/common/math_inexact.c", "libm/common/math_inexactf.c", "libm/common/log.c", 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 @@ + + + + Size Report for {{.pkgName}} + + + + + + +
+

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 7aaab78a57..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", 4568, 280, 0, 2252}, - {"microbit", "examples/serial", 2728, 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-builtin.go b/builder/tools-builtin.go index 2b3cba6189..4169ebc617 100644 --- a/builder/tools-builtin.go +++ b/builder/tools-builtin.go @@ -12,10 +12,7 @@ import ( #include #include bool tinygo_clang_driver(int argc, char **argv); -bool tinygo_link_elf(int argc, char **argv); -bool tinygo_link_macho(int argc, char **argv); -bool tinygo_link_mingw(int argc, char **argv); -bool tinygo_link_wasm(int argc, char **argv); +bool tinygo_link(int argc, char **argv); */ import "C" @@ -26,16 +23,7 @@ const hasBuiltinTools = true // This version actually runs the tools because TinyGo was compiled while // linking statically with LLVM (with the byollvm build tag). func RunTool(tool string, args ...string) error { - linker := "elf" - if tool == "ld.lld" && len(args) >= 2 { - if args[0] == "-m" && (args[1] == "i386pep" || args[1] == "arm64pe") { - linker = "mingw" - } else if args[0] == "-flavor" { - linker = args[1] - args = args[2:] - } - } - args = append([]string{"tinygo:" + tool}, args...) + args = append([]string{tool}, args...) var cflag *C.char buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag))) @@ -51,19 +39,8 @@ func RunTool(tool string, args ...string) error { switch tool { case "clang": ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf)) - case "ld.lld": - switch linker { - case "darwin": - ok = C.tinygo_link_macho(C.int(len(args)), (**C.char)(buf)) - case "elf": - ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf)) - case "mingw": - ok = C.tinygo_link_mingw(C.int(len(args)), (**C.char)(buf)) - default: - return errors.New("unknown linker: " + linker) - } - case "wasm-ld": - ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf)) + case "ld.lld", "wasm-ld": + ok = C.tinygo_link(C.int(len(args)), (**C.char)(buf)) default: return errors.New("unknown tool: " + tool) } diff --git a/builder/tools.go b/builder/tools.go index 53d89bf0dd..9d15e4ccaa 100644 --- a/builder/tools.go +++ b/builder/tools.go @@ -1,50 +1,191 @@ package builder import ( - "errors" + "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. - headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) - if headerPath == "" { - return errors.New("could not locate Clang headers") + 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 + } } - flags = append(flags, "-I"+headerPath) - cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() } - // 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 67979230cc..6b8ea4373d 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -18,6 +18,7 @@ import ( "go/scanner" "go/token" "path/filepath" + "sort" "strconv" "strings" @@ -25,9 +26,15 @@ import ( "golang.org/x/tools/go/ast/astutil" ) +// Function that's only defined in Go 1.22. +var setASTFileFields = func(f *ast.File, start, end token.Pos) { +} + // cgoPackage holds all CGo-related information of a package. type cgoPackage struct { generated *ast.File + packageName string + cgoFiles []*ast.File generatedPos token.Pos errors []error currentDir string // current working directory @@ -36,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 @@ -74,20 +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", + "_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 @@ -129,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 + +func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { + return _Cgo___GoBytes(ptr, uintptr(length)) +} -//go:linkname C.GoString runtime.cgo_GoString -func GoString(*C.char) string +//go:linkname _Cgo___CBytes runtime.cgo_CBytes +func _Cgo___CBytes([]byte) unsafe.Pointer -//go:linkname C.__GoStringN runtime.cgo_GoStringN -func __GoStringN(*C.char, uintptr) string +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 GoStringN(cstr *C.char, length C.int) string { - return C.__GoStringN(cstr, uintptr(length)) +const generatedGoFilePrefixOther = generatedGoFilePrefixBase + ` +func _Cgo___get_errno() error { + return syscall.Errno(_Cgo___get_errno_num()) } +` -//go:linkname C.__GoBytes runtime.cgo_GoBytes -func __GoBytes(unsafe.Pointer, uintptr) []byte +// 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 GoBytes(ptr unsafe.Pointer, length C.int) []byte { - return C.__GoBytes(ptr, uintptr(length)) +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) } ` @@ -164,13 +254,15 @@ 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, clangHeaders 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, importPath: importPath, fset: fset, tokenFiles: map[string]*token.File{}, definedGlobally: map[string]ast.Node{}, + noescapingFuncs: map[string]*noescapingFunc{}, anonDecls: map[interface{}]string{}, visitedFiles: map[string][]byte{}, } @@ -195,32 +287,21 @@ 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. panic("unexpected error: " + err.Error()) } + p.cgoFiles = append(p.cgoFiles, p.generated) // 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 @@ -291,9 +372,6 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl // have better alternatives anyway. cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...) cflagsForCGo = append(cflagsForCGo, p.cflags...) - if clangHeaders != "" { - cflagsForCGo = append(cflagsForCGo, "-isystem", clangHeaders) - } // Retrieve types such as C.int, C.longlong, etc from C. p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) { @@ -302,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) @@ -311,10 +389,11 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl // Process CGo imports for each file. for i, f := range files { cf := p.newCGoFile(f, i) - // Float and double are aliased, meaning that C.float is the same thing - // as float32 in Go. + // These types are aliased with the corresponding types in C. For + // example, float in C is always float32 in Go. cf.names["float"] = clangCursor{} cf.names["double"] = clangCursor{} + cf.names["_Bool"] = clangCursor{} // Now read all the names (identifies) that C defines in the header // snippet. cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) { @@ -330,10 +409,26 @@ 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) - return p.generated, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors + return p.cgoFiles, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors } func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile { @@ -395,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 { @@ -1131,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 @@ -1241,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. @@ -1251,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) } } } @@ -1265,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 { @@ -1286,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_go122.go b/cgo/cgo_go122.go new file mode 100644 index 0000000000..7304aaa28c --- /dev/null +++ b/cgo/cgo_go122.go @@ -0,0 +1,17 @@ +//go:build go1.22 + +package cgo + +// Code specifically for Go 1.22. + +import ( + "go/ast" + "go/token" +) + +func init() { + setASTFileFields = func(f *ast.File, start, end token.Pos) { + f.FileStart = start + f.FileEnd = end + } +} diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index a44dd8e18b..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. - cgoAST, _, _, _, _, 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,10 +64,10 @@ 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, []*ast.File{f, cgoAST}, nil) + _, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil) if err != nil && len(typecheckErrors) == 0 { // Only report errors when no type errors are found (an // unexpected condition). @@ -91,7 +92,7 @@ func TestCGo(t *testing.T) { } buf.WriteString("\n") } - err = format.Node(buf, fset, cgoAST) + err = format.Node(buf, fset, cgoFiles[0]) if err != nil { t.Errorf("could not write out CGo AST: %v", err) } @@ -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 1f2922c56f..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) @@ -589,6 +634,13 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C. f := p.fset.AddFile(filename, -1, int(size)) f.SetLines(lines) p.tokenFiles[filename] = f + // Add dummy file AST, to satisfy the type checker. + astFile := &ast.File{ + Package: f.Pos(0), + Name: ast.NewIdent(p.packageName), + } + setASTFileFields(astFile, f.Pos(0), f.Pos(int(size))) + p.cgoFiles = append(p.cgoFiles, astFile) } positionFile := p.tokenFiles[filename] @@ -635,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, @@ -700,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: @@ -851,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, @@ -868,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. @@ -881,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" @@ -892,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_llvm14.go b/cgo/libclang_config_llvm14.go deleted file mode 100644 index 861ced9de8..0000000000 --- a/cgo/libclang_config_llvm14.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !byollvm && llvm14 - -package cgo - -/* -#cgo linux CFLAGS: -I/usr/lib/llvm-14/include -#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@14/include -#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@14/include -#cgo freebsd CFLAGS: -I/usr/local/llvm14/include -#cgo linux LDFLAGS: -L/usr/lib/llvm-14/lib -lclang -#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@14/lib -lclang -lffi -#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@14/lib -lclang -lffi -#cgo freebsd LDFLAGS: -L/usr/local/llvm14/lib -lclang -*/ -import "C" diff --git a/cgo/libclang_config_llvm15.go b/cgo/libclang_config_llvm15.go index e105dfe728..edb1128dc2 100644 --- a/cgo/libclang_config_llvm15.go +++ b/cgo/libclang_config_llvm15.go @@ -8,8 +8,8 @@ package cgo #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@15/include #cgo freebsd CFLAGS: -I/usr/local/llvm15/include #cgo linux LDFLAGS: -L/usr/lib/llvm-15/lib -lclang -#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@15/lib -lclang -lffi -#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@15/lib -lclang -lffi +#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@15/lib -lclang +#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@15/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm15/lib -lclang */ import "C" diff --git a/cgo/libclang_config_llvm16.go b/cgo/libclang_config_llvm16.go index 79aacd2f26..ee354e212e 100644 --- a/cgo/libclang_config_llvm16.go +++ b/cgo/libclang_config_llvm16.go @@ -1,8 +1,8 @@ -//go:build !byollvm && !llvm14 && !llvm15 +//go:build !byollvm && llvm16 package cgo -// As of 2023-05-05, there is a packaging issue on Debian: +// As of 2023-05-05, there is a packaging issue with LLVM 16 on Debian: // https://github.com/llvm/llvm-project/issues/62199 // A workaround is to fix this locally, using something like this: // @@ -14,8 +14,8 @@ package cgo #cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@16/include #cgo freebsd CFLAGS: -I/usr/local/llvm16/include #cgo linux LDFLAGS: -L/usr/lib/llvm-16/lib -lclang -#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@16/lib -lclang -lffi -#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@16/lib -lclang -lffi +#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@16/lib -lclang +#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@16/lib -lclang #cgo freebsd LDFLAGS: -L/usr/local/llvm16/lib -lclang */ import "C" diff --git a/cgo/libclang_config_llvm17.go b/cgo/libclang_config_llvm17.go new file mode 100644 index 0000000000..6395d8a3af --- /dev/null +++ b/cgo/libclang_config_llvm17.go @@ -0,0 +1,15 @@ +//go:build !byollvm && llvm17 + +package cgo + +/* +#cgo linux CFLAGS: -I/usr/include/llvm-17 -I/usr/include/llvm-c-17 -I/usr/lib/llvm-17/include +#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@17/include +#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@17/include +#cgo freebsd CFLAGS: -I/usr/local/llvm17/include +#cgo linux LDFLAGS: -L/usr/lib/llvm-17/lib -lclang +#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@17/lib -lclang +#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@17/lib -lclang +#cgo freebsd LDFLAGS: -L/usr/local/llvm17/lib -lclang +*/ +import "C" 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 39fc4f2ac2..a9eb235ad1 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -19,7 +19,6 @@ type Config struct { Options *Options Target *TargetSpec GoMinorVersion int - ClangHeaders string // Clang built-in header include path TestConfig TestConfig } @@ -34,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. @@ -61,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 @@ -73,9 +83,22 @@ 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{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...) + 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 for i := 1; i <= c.GoMinorVersion; i++ { tags = append(tags, fmt.Sprintf("go1.%d", i)) } @@ -83,12 +106,6 @@ func (c *Config) BuildTags() []string { return tags } -// CgoEnabled returns true if (and only if) CGo is enabled. It is true by -// default and false if CGO_ENABLED is set to "0". -func (c *Config) CgoEnabled() bool { - return goenv.Get("CGO_ENABLED") == "1" -} - // GC returns the garbage collection strategy in use on this platform. Valid // values are "none", "leaking", "conservative" and "precise". func (c *Config) GC() string { @@ -145,18 +162,18 @@ func (c *Config) Serial() string { // OptLevels returns the optimization level (0-2), size level (0-2), and inliner // threshold as used in the LLVM optimization pipeline. -func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) { +func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) { switch c.Options.Opt { case "none", "0": - return 0, 0, 0 // -O0 + return "O0", 0, 0 case "1": - return 1, 0, 0 // -O1 + return "O1", 1, 0 case "2": - return 2, 0, 225 // -O2 + return "O2", 2, 0 case "s": - return 2, 1, 225 // -Os + return "Os", 2, 1 case "z": - return 2, 2, 5 // -Oz, default + return "Oz", 2, 2 // default default: // This is not shown to the user: valid choices are already checked as // part of Options.Verify(). It is here as a sanity check. @@ -190,6 +207,15 @@ func (c *Config) StackSize() uint64 { return c.Target.DefaultStackSize } +// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap. +func (c *Config) MaxStackAlloc() uint64 { + if c.StackSize() > 32*1024 { + return 1024 + } + + return 256 +} + // RP2040BootPatch returns whether the RP2040 boot patch should be applied that // calculates and patches in the checksum for the 2nd stage bootloader. func (c *Config) RP2040BootPatch() bool { @@ -199,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). @@ -220,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) @@ -259,80 +304,108 @@ func (c *Config) DefaultBinaryExtension() string { // CFlags returns the flags to pass to the C compiler. This is necessary for CGo // preprocessing. -func (c *Config) CFlags() []string { +func (c *Config) CFlags(libclang bool) []string { var cflags []string for _, flag := range c.Target.CFlags { cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) } + resourceDir := goenv.ClangResourceDir(libclang) + if resourceDir != "" { + // The resource directory contains the built-in clang headers like + // stdbool.h, stdint.h, float.h, etc. + // It is left empty if we're using an external compiler (that already + // knows these headers). + cflags = append(cflags, + "-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, - "--sysroot="+filepath.Join(root, "lib/macos-minimal-sdk/src"), - ) + 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, - "--sysroot="+path, - "-isystem", filepath.Join(path, "include"), // necessary for Xtensa + 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, - "--sysroot="+path, + 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 @@ -349,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 } @@ -416,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 "": @@ -456,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" { @@ -470,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 4440f4cf62..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"} - validPrintSizeOptions = []string{"none", "short", "full"} + validSerialOptions = []string{"none", "uart", "usb", "rtt"} + validPrintSizeOptions = []string{"none", "short", "full", "html"} validPanicStrategyOptions = []string{"print", "trap"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"} ) @@ -23,7 +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 @@ -48,15 +52,25 @@ type Options struct { Programmer string OpenOCDCommands []string LLVMFeatures string - Directory string PrintJSON bool 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 82ee2e3b4b..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. @@ -246,73 +203,216 @@ func LoadTarget(options *Options) (*TargetSpec, error) { return spec, 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. +// GetTargetSpecs retrieves target specifications from the TINYGOROOT targets +// directory. Only valid target JSON files are considered, and the function +// returns a map of target names to their respective TargetSpec. +func GetTargetSpecs() (map[string]*TargetSpec, error) { + dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets") + entries, err := os.ReadDir(dir) + if err != nil { + return nil, fmt.Errorf("could not list targets: %w", err) + } + + maps := map[string]*TargetSpec{} + for _, entry := range entries { + entryInfo, err := entry.Info() + if err != nil { + return nil, fmt.Errorf("could not get entry info: %w", err) + } + if !entryInfo.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") { + // Only inspect JSON files. + continue + } + path := filepath.Join(dir, entry.Name()) + spec, err := LoadTarget(&Options{Target: path}) + if err != nil { + return nil, fmt.Errorf("could not list target: %w", err) + } + if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" { + // This doesn't look like a regular target file, but rather like + // a parent target (such as targets/cortex-m.json). + continue + } + name := entry.Name() + name = name[:len(name)-5] + maps[name] = spec + } + return maps, nil +} + +// 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 = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87" + spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" case "amd64": + llvmarch = "x86_64" spec.CPU = "x86-64" - spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87" + 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 { // windows, linux - 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 = "+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") - } else if goos == "windows" { + 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 + // makes atomics slightly more efficient on systems with many cores. + // But the instructions are only supported on newer aarch64 CPUs, so + // this feature is normally put in a system library which does + // feature detection for you. + // We take the lazy way out and simply disable this feature, instead + // of enabling it in compiler-rt (which is a bit more complicated). + // We don't really need this feature anyway as we don't even support + // proper threading. + spec.CFlags = append(spec.CFlags, "-mno-outline-atomics") + } + 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 @@ -321,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", @@ -338,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 * 32 // 32kB - spec.LDFlags = append(spec.LDFlags, - "--stack-first", - "--no-demangle", - ) - spec.Emulator = "wasmtime --mapdir=/tmp::{tmpDir} {}" - 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) } - if goarch != "wasm" { + + // 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" + } + } + + // 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" { @@ -383,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 48a9fb2d28..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,37 +12,23 @@ 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)) - isPointer := val.Type().TypeKind() == llvm.PointerTypeKind - if isPointer { - // atomicrmw only supports integers, so cast to an integer. - // TODO: this is fixed in LLVM 15. - val = b.CreatePtrToInt(val, b.uintptrType, "") - ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "") - } oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) - if isPointer { - oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "") - } return oldVal case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer": ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) @@ -63,24 +46,6 @@ func (b *builder) createAtomicOp(name string) llvm.Value { case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer": 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") { - // SelectionDAGBuilder is currently missing the "are unaligned atomics allowed" check for stores. - vType := val.Type() - isPointer := vType.TypeKind() == llvm.PointerTypeKind - if isPointer { - // libcalls only supports integers, so cast to an integer. - vType = b.uintptrType - val = b.CreatePtrToInt(val, vType, "") - ptr = b.CreateBitCast(ptr, llvm.PointerType(vType, 0), "") - } - name := fmt.Sprintf("__atomic_store_%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, b.uintptrType}, false)) - } - b.createCall(fn.GlobalValueType(), fn, []llvm.Value{ptr, val, llvm.ConstInt(b.uintptrType, 5, false)}, "") - return llvm.Value{} - } store := b.CreateStore(val, ptr) store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required diff --git a/compiler/calls.go b/compiler/calls.go index a110addcf6..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 @@ -45,7 +46,7 @@ func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name if llvmFn.IsNil() { panic("trying to call non-existent function: " + fn.RelString(nil)) } - args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter + args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter if isInvoke { return b.createInvoke(fnType, llvmFn, args, name) } @@ -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 c8c10fe0b6..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" @@ -33,28 +35,27 @@ func (b *builder) createChanSend(instr *ssa.Send) { // store value-to-send valueType := b.getLLVMType(instr.X.Type()) isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 - var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value + var valueAlloca, valueAllocaSize llvm.Value if isZeroSize { - valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0)) - valueAllocaCast = llvm.ConstNull(b.i8ptrType) + valueAlloca = llvm.ConstNull(b.dataPtrType) } else { - valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") + valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") b.CreateStore(chanValue, valueAlloca) } - // Allocate blockedlist buffer. - channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") - channelBlockedListAlloca, channelBlockedListAllocaCast, 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, valueAllocaCast, 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(channelBlockedListAllocaCast, channelBlockedListAllocaSize) + b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if !isZeroSize { - b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) + b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } } @@ -66,28 +67,27 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { // Allocate memory to receive into. isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 - var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value + var valueAlloca, valueAllocaSize llvm.Value if isZeroSize { - valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0)) - valueAllocaCast = llvm.ConstNull(b.i8ptrType) + valueAlloca = llvm.ConstNull(b.dataPtrType) } else { - valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") + valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") } - // Allocate blockedlist buffer. - channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") - channelBlockedListAlloca, channelBlockedListAllocaCast, 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, valueAllocaCast, channelBlockedListAlloca}, "") + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") var received llvm.Value if isZeroSize { received = llvm.ConstNull(valueType) } else { received = b.CreateLoad(valueType, valueAlloca, "chan.received") - b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) + b.emitLifetimeEnd(valueAlloca, valueAllocaSize) } - b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) + b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if unop.CommaOk { tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) @@ -126,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. @@ -159,8 +173,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { sendValue := b.getValue(state.Send, state.Pos) alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value") b.CreateStore(sendValue, alloca) - ptr := b.CreateBitCast(alloca, b.i8ptrType, "") - selectState = b.CreateInsertValue(selectState, ptr, 1, "") + selectState = b.CreateInsertValue(selectState, alloca, 1, "") default: panic("unreachable") } @@ -168,10 +181,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { } // Create a receive buffer, where the received value will be stored. - recvbuf := llvm.Undef(b.i8ptrType) + recvbuf := llvm.Undef(b.dataPtrType) if recvbufSize != 0 { allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) - recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") + recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca") recvbufAlloca.SetAlignment(recvbufAlign) recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), @@ -181,7 +194,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { // Create the states slice (allocated on the stack). statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates)) - statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca") + statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca") for i, state := range selectStates { // Set each slice element to the appropriate channel. gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{ @@ -201,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, chBlockAllocaPtr, 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") @@ -212,20 +225,23 @@ 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(chBlockAllocaPtr, 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") } // Terminate the lifetime of the states alloca. - b.emitLifetimeEnd(statesI8, statesSize) + b.emitLifetimeEnd(statesAlloca, statesSize) // The result value does not include all the possible received values, // because we can't load them in advance. Instead, the *ssa.Extract @@ -265,7 +281,6 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { // it to the correct type, and dereference it. recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] typ := b.getLLVMType(expr.Type()) - ptr := b.CreateBitCast(recvbuf, llvm.PointerType(typ, 0), "") - return b.CreateLoad(typ, ptr, "") + return b.CreateLoad(typ, recvbuf, "") } } diff --git a/compiler/compiler.go b/compiler/compiler.go index cb3a892a8c..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 @@ -53,8 +55,10 @@ type Config struct { Scheduler string AutomaticStackSize bool DefaultStackSize uint64 + 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 @@ -75,10 +79,9 @@ type compilerContext struct { machine llvm.TargetMachine targetData llvm.TargetData intType llvm.Type - i8ptrType llvm.Type // for convenience - rawVoidFuncType llvm.Type // for convenience + dataPtrType llvm.Type // pointer in address space 0 + funcPtrType llvm.Type // pointer in function address space (1 for AVR, 0 elsewhere) funcPtrAddrSpace int - hasTypedPointers bool // for LLVM 14 backwards compatibility uintptrType llvm.Type program *ssa.Program diagnostics []error @@ -123,13 +126,12 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C } else { panic("unknown pointer size") } - c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0) + c.dataPtrType = llvm.PointerType(c.ctx.Int8Type(), 0) dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false) dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType) c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() - c.hasTypedPointers = c.i8ptrType != llvm.PointerType(c.ctx.Int16Type(), 0) // with opaque pointers, all pointers are the same type (LLVM 15+) - c.rawVoidFuncType = dummyFunc.Type() + c.funcPtrType = dummyFunc.Type() dummyFunc.EraseFromParentAsFunction() return c @@ -242,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() @@ -417,16 +419,14 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type { case types.Uintptr: return c.uintptrType case types.UnsafePointer: - return c.i8ptrType + return c.dataPtrType default: panic("unknown basic type: " + typ.String()) } - case *types.Chan: - return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0) + case *types.Chan, *types.Map, *types.Pointer: + return c.dataPtrType // all pointers are the same case *types.Interface: return c.getLLVMRuntimeType("_interface") - case *types.Map: - return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0) case *types.Named: if st, ok := typ.Underlying().(*types.Struct); ok { // Structs are a special case. While other named types are ignored @@ -441,21 +441,11 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type { return llvmType } return c.getLLVMType(typ.Underlying()) - case *types.Pointer: - if c.hasTypedPointers { - ptrTo := c.getLLVMType(typ.Elem()) - return llvm.PointerType(ptrTo, 0) - } - return c.i8ptrType // all pointers are the same case *types.Signature: // function value return c.getFuncType(typ) case *types.Slice: - ptrType := c.i8ptrType - if c.hasTypedPointers { - ptrType = llvm.PointerType(c.getLLVMType(typ.Elem()), 0) - } members := []llvm.Type{ - ptrType, + c.dataPtrType, c.uintptrType, // len c.uintptrType, // cap } @@ -545,8 +535,8 @@ func (c *compilerContext) createDIType(typ types.Type) llvm.Metadata { Elements: []llvm.Metadata{ c.dibuilder.CreateMemberType(llvm.Metadata{}, llvm.DIMemberType{ Name: "ptr", - SizeInBits: c.targetData.TypeAllocSize(c.i8ptrType) * 8, - AlignInBits: uint32(c.targetData.ABITypeAlignment(c.i8ptrType)) * 8, + SizeInBits: c.targetData.TypeAllocSize(c.dataPtrType) * 8, + AlignInBits: uint32(c.targetData.ABITypeAlignment(c.dataPtrType)) * 8, OffsetInBits: 0, Type: c.getDIType(types.NewPointer(types.Typ[types.Byte])), }), @@ -1396,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 @@ -1548,21 +1543,18 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c src := argValues[0] elems := argValues[1] srcBuf := b.CreateExtractValue(src, 0, "append.srcBuf") - srcPtr := b.CreateBitCast(srcBuf, b.i8ptrType, "append.srcPtr") srcLen := b.CreateExtractValue(src, 1, "append.srcLen") srcCap := b.CreateExtractValue(src, 2, "append.srcCap") elemsBuf := b.CreateExtractValue(elems, 0, "append.elemsBuf") - elemsPtr := b.CreateBitCast(elemsBuf, b.i8ptrType, "append.srcPtr") elemsLen := b.CreateExtractValue(elems, 1, "append.elemsLen") elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) - result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcPtr, elemsPtr, srcLen, srcCap, elemsLen, elemSize}, "append.new") + result := b.createRuntimeCall("sliceAppend", []llvm.Value{srcBuf, elemsBuf, srcLen, srcCap, elemsLen, elemSize}, "append.new") newPtr := b.CreateExtractValue(result, 0, "append.newPtr") - newBuf := b.CreateBitCast(newPtr, srcBuf.Type(), "append.newBuf") newLen := b.CreateExtractValue(result, 1, "append.newLen") newCap := b.CreateExtractValue(result, 2, "append.newCap") newSlice := llvm.Undef(src.Type()) - newSlice = b.CreateInsertValue(newSlice, newBuf, 0, "") + newSlice = b.CreateInsertValue(newSlice, newPtr, 0, "") newSlice = b.CreateInsertValue(newSlice, newLen, 1, "") newSlice = b.CreateInsertValue(newSlice, newCap, 2, "") return newSlice, nil @@ -1610,9 +1602,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c // The pointer to the data to be cleared. llvmBuf := b.CreateExtractValue(value, 0, "buf") - if llvmBuf.Type() != b.i8ptrType { // compatibility with LLVM 14 - llvmBuf = b.CreateBitCast(llvmBuf, b.i8ptrType, "") - } // The length (in bytes) to be cleared. llvmLen := b.CreateExtractValue(value, 1, "len") @@ -1647,8 +1636,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c dstBuf := b.CreateExtractValue(dst, 0, "copy.dstArray") srcBuf := b.CreateExtractValue(src, 0, "copy.srcArray") elemType := b.getLLVMType(argTypes[0].Underlying().(*types.Slice).Elem()) - dstBuf = b.CreateBitCast(dstBuf, b.i8ptrType, "copy.dstPtr") - srcBuf = b.CreateBitCast(srcBuf, b.i8ptrType, "copy.srcPtr") elemSize := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(elemType), false) return b.createRuntimeCall("sliceCopy", []llvm.Value{dstBuf, srcBuf, dstLen, srcLen, elemSize}, "copy.n"), nil case "delete": @@ -1694,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, "") @@ -1755,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] @@ -1866,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) @@ -1876,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) @@ -1888,14 +1894,13 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) switch value := instr.Value.(type) { case *ssa.Function: // Regular function call. No context is necessary. - context = llvm.Undef(b.i8ptrType) + context = llvm.Undef(b.dataPtrType) if info.variadic && len(fn.Params) == 0 { // This matches Clang, see: https://godbolt.org/z/Gqv49xKMq // Eventually we might be able to eliminate this special case // entirely. For details, see: // https://discourse.llvm.org/t/rfc-enabling-wstrict-prototypes-by-default-in-c/60521 calleeType = llvm.FunctionType(callee.GlobalValueType().ReturnType(), nil, false) - callee = llvm.ConstBitCast(callee, llvm.PointerType(calleeType, b.funcPtrAddrSpace)) } case *ssa.MakeClosure: // A call on a func value, but the callee is trivial to find. For @@ -1923,13 +1928,14 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) params = append(params, typecode) callee = b.getInvokeFunction(instr) calleeType = callee.GlobalValueType() - context = llvm.Undef(b.i8ptrType) + context = llvm.Undef(b.dataPtrType) } else { // Function pointer. value := b.getValue(instr.Value, getPos(instr)) // This is a func value, which cannot be called directly. We have to // extract the function pointer and context first from the func value. - calleeType, callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) + callee, context = b.decodeFuncValue(value) + calleeType = b.getLLVMFunctionType(instr.Value.Type().Underlying().(*types.Signature)) b.createNilCheck(instr.Value, callee, "fpcall") } @@ -1962,7 +1968,7 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value { return llvm.Undef(b.getLLVMType(expr.Type())) } _, fn := b.getFunction(expr) - return b.createFuncValue(fn, llvm.Undef(b.i8ptrType), expr.Signature) + return b.createFuncValue(fn, llvm.Undef(b.dataPtrType), expr.Signature) case *ssa.Global: value := b.getGlobal(expr) if value.IsNil() { @@ -1976,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()) } } } @@ -2019,8 +2025,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { case *ssa.Alloc: typ := b.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem()) size := b.targetData.TypeAllocSize(typ) - // Move all "large" allocations to the heap. This value is also transform.maxStackAlloc. - if expr.Heap || size > 256 { + // Move all "large" allocations to the heap. + if expr.Heap || size > b.MaxStackAlloc { // Calculate ^uintptr(0) maxSize := llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)).ZExtValue() if size > maxSize { @@ -2030,7 +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) - buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "") + 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) @@ -2076,10 +2083,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { value = b.CreateInsertValue(value, field, i, "changetype.struct") } return value, nil - case llvm.PointerTypeKind: - // This can happen with pointers to structs. This case is easy: - // simply bitcast the pointer to the destination type. - return b.CreateBitCast(x, llvmType, "changetype.pointer"), nil default: return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String()) } @@ -2156,12 +2159,12 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { // Can't load directly from array (as index is non-constant), so // have to do it using an alloca+gep+load. arrayType := collection.Type() - alloca, allocaPtr, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") + alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca") b.CreateStore(collection, alloca) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep") result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load") - b.emitLifetimeEnd(allocaPtr, allocaSize) + b.emitLifetimeEnd(alloca, allocaSize) return result, nil default: panic("unknown *ssa.Index type") @@ -2199,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) @@ -2241,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) @@ -2264,7 +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 = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array") + 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. @@ -2310,7 +2314,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { default: panic("unknown type in range: " + typ.String()) } - it, _, _ := b.createTemporaryAlloca(iteratorType, "range.it") + it, _ := b.createTemporaryAlloca(iteratorType, "range.it") b.CreateStore(llvm.ConstNull(iteratorType), it) return it, nil case *ssa.Select: @@ -2478,7 +2482,6 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { arrayLen := expr.Type().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len() b.createSliceToArrayPointerCheck(sliceLen, arrayLen) ptr := b.CreateExtractValue(slice, 0, "") - ptr = b.CreateBitCast(ptr, b.getLLVMType(expr.Type()), "") return ptr, nil case *ssa.TypeAssert: return b.createTypeAssert(expr), nil @@ -2577,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") @@ -2944,16 +2947,16 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) strPtr = llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero}) } else { - strPtr = llvm.ConstNull(c.i8ptrType) + strPtr = llvm.ConstNull(c.dataPtrType) } strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) return strObj } else if typ.Kind() == types.UnsafePointer { if !expr.IsNil() { value, _ := constant.Uint64Val(constant.ToInt(expr.Value)) - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.i8ptrType) + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, value, false), c.dataPtrType) } - return llvm.ConstNull(c.i8ptrType) + return llvm.ConstNull(c.dataPtrType) } else if typ.Info()&types.IsUnsigned != 0 { n, _ := constant.Uint64Val(constant.ToInt(expr.Value)) return llvm.ConstInt(llvmType, n, false) @@ -2997,7 +3000,7 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value // Create a generic nil interface with no dynamic type (typecode=0). fields := []llvm.Value{ llvm.ConstInt(c.uintptrType, 0, false), - llvm.ConstPointerNull(c.i8ptrType), + llvm.ConstPointerNull(c.dataPtrType), } return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields) case *types.Pointer: @@ -3014,8 +3017,7 @@ func (c *compilerContext) createConst(expr *ssa.Const, pos token.Pos) llvm.Value if expr.Value != nil { panic("expected nil slice constant") } - elemType := c.getLLVMType(typ.Elem()) - llvmPtr := llvm.ConstPointerNull(llvm.PointerType(elemType, 0)) + llvmPtr := llvm.ConstPointerNull(c.dataPtrType) llvmLen := llvm.ConstInt(c.uintptrType, 0, false) slice := c.ctx.ConstStruct([]llvm.Value{ llvmPtr, // backing array @@ -3056,7 +3058,7 @@ func (b *builder) createConvert(typeFrom, typeTo types.Type, value llvm.Value, p // Conversion between pointers and unsafe.Pointer. if isPtrFrom && isPtrTo { - return b.CreateBitCast(value, llvmTypeTo, ""), nil + return value, nil } switch typeTo := typeTo.Underlying().(type) { @@ -3291,11 +3293,15 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) { // Instead of a load from the global, create a bitcast of the // function pointer itself. name := strings.TrimSuffix(unop.X.(*ssa.Global).Name(), "$funcaddr") - _, fn := b.getFunction(b.fn.Pkg.Members[name].(*ssa.Function)) + pkg := b.fn.Pkg + if pkg == nil { + pkg = b.fn.Origin().Pkg + } + _, fn := b.getFunction(pkg.Members[name].(*ssa.Function)) if fn.IsNil() { return llvm.Value{}, b.makeError(unop.Pos(), "cgo function not found: "+name) } - return b.CreateBitCast(fn, b.i8ptrType, ""), nil + return fn, nil } else { b.createNilCheck(unop.X, x, "deref") load := b.CreateLoad(valueType, x, "") diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 92ce31b012..2e5094875c 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -91,14 +91,12 @@ func TestCompiler(t *testing.T) { } // Optimize IR a little. - funcPasses := llvm.NewFunctionPassManagerForModule(mod) - defer funcPasses.Dispose() - funcPasses.AddInstructionCombiningPass() - funcPasses.InitializeFunc() - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - funcPasses.RunFunc(fn) + passOptions := llvm.NewPassBuilderOptions() + defer passOptions.Dispose() + err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions) + if err != nil { + t.Error(err) } - funcPasses.FinalizeFunc() outFilePrefix := tc.file[:len(tc.file)-3] if tc.target != "" { @@ -169,9 +167,9 @@ func filterIrrelevantIRLines(lines []string) []string { if strings.HasPrefix(line, "source_filename = ") { continue } - if llvmVersion < 14 && strings.HasPrefix(line, "target datalayout = ") { + if llvmVersion < 15 && strings.HasPrefix(line, "target datalayout = ") { // The datalayout string may vary betewen LLVM versions. - // Right now test outputs are for LLVM 14 and higher. + // Right now test outputs are for LLVM 15 and higher. continue } out = append(out, line) @@ -245,7 +243,7 @@ func testCompilePackage(t *testing.T, options *compileopts.Options, file string) defer machine.Dispose() // Load entire program AST into memory. - lprogram, err := loader.Load(config, "./testdata/"+file, config.ClangHeaders, types.Config{ + lprogram, err := loader.Load(config, "./testdata/"+file, types.Config{ Sizes: Sizes(machine), }) if err != nil { diff --git a/compiler/defer.go b/compiler/defer.go index 191b64b91e..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" @@ -60,9 +61,8 @@ func (b *builder) deferInitFunc() { b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) // Create defer list pointer. - deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) - b.deferPtr = b.CreateAlloca(deferType, "deferPtr") - b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr) + b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr") + b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr) if b.hasDeferFrame() { // Set up the defer frame with the current stack pointer. @@ -161,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: @@ -188,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 @@ -249,8 +267,7 @@ func isInLoop(start *ssa.BasicBlock) bool { func (b *builder) createDefer(instr *ssa.Defer) { // The pointer to the previous defer struct, which we will replace to // make a linked list. - deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0) - next := b.CreateLoad(deferType, b.deferPtr, "defer.next") + next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next") var values []llvm.Value valueTypes := []llvm.Type{b.uintptrType, next.Type()} @@ -271,7 +288,7 @@ func (b *builder) createDefer(instr *ssa.Defer) { typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") values = []llvm.Value{callback, next, typecode, receiverValue} - valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType) + valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) for _, arg := range instr.Call.Args { val := b.getValue(arg, getPos(instr)) values = append(values, val) @@ -391,9 +408,8 @@ func (b *builder) createDefer(instr *ssa.Defer) { // This may be hit a variable number of times, so use a heap allocation. size := b.targetData.TypeAllocSize(deferredCallType) sizeValue := llvm.ConstInt(b.uintptrType, size, false) - nilPtr := llvm.ConstNull(b.i8ptrType) - allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call") - alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferredCallType, 0), "defer.alloc") + nilPtr := llvm.ConstNull(b.dataPtrType) + alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call") } if b.NeedsStackObjects { b.trackPointer(alloca) @@ -401,14 +417,12 @@ func (b *builder) createDefer(instr *ssa.Defer) { b.CreateStore(deferredCall, alloca) // Push it on top of the linked list by replacing deferPtr. - allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") - b.CreateStore(allocaCast, b.deferPtr) + b.CreateStore(alloca, b.deferPtr) } // createRunDefers emits code to run all deferred functions. func (b *builder) createRunDefers() { deferType := b.getLLVMRuntimeType("_defer") - deferPtrType := llvm.PointerType(deferType, 0) // Add a loop like the following: // for stack != nil { @@ -435,7 +449,7 @@ func (b *builder) createRunDefers() { // Create loop head: // for stack != nil { b.SetInsertPointAtEnd(loophead) - deferData := b.CreateLoad(deferPtrType, b.deferPtr, "") + deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "") stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil") b.CreateCondBr(stackIsNil, end, loop) @@ -448,7 +462,7 @@ func (b *builder) createRunDefers() { llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field }, "stack.next.gep") - nextStack := b.CreateLoad(deferPtrType, nextStackGEP, "stack.next") + nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next") b.CreateStore(nextStack, b.deferPtr) gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), @@ -469,28 +483,26 @@ func (b *builder) createRunDefers() { // Call on an value or interface value. // Get the real defer struct type and cast to it. - valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} + valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} if !callback.IsInvoke() { //Expect funcValue to be passed through the deferred call. valueTypes = append(valueTypes, b.getFuncType(callback.Signature())) } else { //Expect typecode - valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType) + valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) } for _, arg := range callback.Args { valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) } - deferredCallType := b.ctx.StructType(valueTypes, false) - deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall") - // Extract the params from the struct (including receiver). forwardParams := []llvm.Value{} zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) + deferredCallType := b.ctx.StructType(valueTypes, false) for i := 2; i < len(valueTypes); i++ { - gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep") + gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep") forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParams = append(forwardParams, forwardParam) } @@ -505,7 +517,8 @@ func (b *builder) createRunDefers() { //Get function pointer and context var context llvm.Value - fnType, fnPtr, context = b.decodeFuncValue(funcValue, callback.Signature()) + fnPtr, context = b.decodeFuncValue(funcValue) + fnType = b.getLLVMFunctionType(callback.Signature()) //Pass context forwardParams = append(forwardParams, context) @@ -519,7 +532,7 @@ func (b *builder) createRunDefers() { // Add the context parameter. An interface call cannot also be a // closure but we have to supply the parameter anyway for platforms // with a strict calling convention. - forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) + forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) } b.createCall(fnType, fnPtr, forwardParams, "") @@ -528,18 +541,17 @@ func (b *builder) createRunDefers() { // Direct call. // Get the real defer struct type and cast to it. - valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} + valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} for _, param := range getParams(callback.Signature) { valueTypes = append(valueTypes, b.getLLVMType(param.Type())) } deferredCallType := b.ctx.StructType(valueTypes, false) - deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall") // Extract the params from the struct. forwardParams := []llvm.Value{} zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := range getParams(callback.Signature) { - gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") + gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParams = append(forwardParams, forwardParam) } @@ -549,7 +561,7 @@ func (b *builder) createRunDefers() { if !b.getFunctionInfo(callback).exported { // Add the context parameter. We know it is ignored by the receiving // function, but we have to pass one anyway. - forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType)) + forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) } // Call real function. @@ -559,20 +571,19 @@ func (b *builder) createRunDefers() { case *ssa.MakeClosure: // Get the real defer struct type and cast to it. fn := callback.Fn.(*ssa.Function) - valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} + valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} params := fn.Signature.Params() for i := 0; i < params.Len(); i++ { valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type())) } - valueTypes = append(valueTypes, b.i8ptrType) // closure + valueTypes = append(valueTypes, b.dataPtrType) // closure deferredCallType := b.ctx.StructType(valueTypes, false) - deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall") // Extract the params from the struct. forwardParams := []llvm.Value{} zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 2; i < len(valueTypes); i++ { - gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "") + gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "") forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParams = append(forwardParams, forwardParam) } @@ -584,7 +595,7 @@ func (b *builder) createRunDefers() { db := b.deferBuiltinFuncs[callback] //Get parameter types - valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)} + valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} //Get signature from call results params := callback.Type().Underlying().(*types.Signature).Params() @@ -593,13 +604,12 @@ func (b *builder) createRunDefers() { } deferredCallType := b.ctx.StructType(valueTypes, false) - deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall") // Extract the params from the struct. var argValues []llvm.Value zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) for i := 0; i < params.Len(); i++ { - gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") + gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep") forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") argValues = append(argValues, forwardParam) } diff --git a/compiler/func.go b/compiler/func.go index 3ac42e749c..0af81445c9 100644 --- a/compiler/func.go +++ b/compiler/func.go @@ -13,35 +13,14 @@ import ( // createFuncValue creates a function value from a raw function pointer with no // context. func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { - return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig) -} - -// createFuncValue creates a function value from a raw function pointer with no -// context. -func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { // Closure is: {context, function pointer} - funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType) - funcValueType := c.getFuncType(sig) + funcValueType := b.getFuncType(sig) funcValue := llvm.Undef(funcValueType) - funcValue = builder.CreateInsertValue(funcValue, context, 0, "") - funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "") + funcValue = b.CreateInsertValue(funcValue, context, 0, "") + funcValue = b.CreateInsertValue(funcValue, funcPtr, 1, "") return funcValue } -// getFuncSignatureID returns a new external global for a given signature. This -// global reference is not real, it is only used during func lowering to assign -// signature types to functions and will then be removed. -func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value { - s, _ := getTypeCodeName(sig) - sigGlobalName := "reflect/types.funcid:" + s - sigGlobal := c.mod.NamedGlobal(sigGlobalName) - if sigGlobal.IsNil() { - sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName) - sigGlobal.SetGlobalConstant(true) - } - return sigGlobal -} - // extractFuncScalar returns some scalar that can be used in comparisons. It is // a cheap operation. func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { @@ -55,28 +34,20 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value { } // decodeFuncValue extracts the context and the function pointer from this func -// value. This may be an expensive operation. -func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcType llvm.Type, funcPtr, context llvm.Value) { +// value. +func (b *builder) decodeFuncValue(funcValue llvm.Value) (funcPtr, context llvm.Value) { context = b.CreateExtractValue(funcValue, 0, "") funcPtr = b.CreateExtractValue(funcValue, 1, "") - if !funcPtr.IsAConstantExpr().IsNil() && funcPtr.Opcode() == llvm.BitCast { - funcPtr = funcPtr.Operand(0) // needed for LLVM 14 (no opaque pointers) - } - if sig != nil { - funcType = b.getRawFuncType(sig) - llvmSig := llvm.PointerType(funcType, b.funcPtrAddrSpace) - funcPtr = b.CreateBitCast(funcPtr, llvmSig, "") - } return } // getFuncType returns the type of a func value given a signature. func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { - return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false) + return c.ctx.StructType([]llvm.Type{c.dataPtrType, c.funcPtrType}, false) } -// getRawFuncType returns a LLVM function type for a given signature. -func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { +// getLLVMFunctionType returns a LLVM function type for a given signature. +func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type { // Get the return type. var returnType llvm.Type switch typ.Results().Len() { @@ -104,7 +75,7 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { if recv.StructName() == "runtime._interface" { // This is a call on an interface, not a concrete type. // The receiver is not an interface, but a i8* type. - recv = c.i8ptrType + recv = c.dataPtrType } for _, info := range c.expandFormalParamType(recv, "", nil) { paramTypes = append(paramTypes, info.llvmType) @@ -117,7 +88,7 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type { } } // All functions take these parameters at the end. - paramTypes = append(paramTypes, c.i8ptrType) // context + paramTypes = append(paramTypes, c.dataPtrType) // context // Make a func type out of the signature. return llvm.FunctionType(returnType, paramTypes, false) diff --git a/compiler/gc.go b/compiler/gc.go index 0fd6f5aaec..fc0e6e687f 100644 --- a/compiler/gc.go +++ b/compiler/gc.go @@ -78,12 +78,9 @@ 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) { - if value.Type() != b.i8ptrType { - value = b.CreateBitCast(value, b.i8ptrType, "") - } b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "") } diff --git a/compiler/goroutine.go b/compiler/goroutine.go index 8feb5e799c..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 funcPtrType 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 - } - funcPtrType, 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,13 +45,45 @@ 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)) itfTypeCode := b.CreateExtractValue(itf, 0, "") itfValue := b.CreateExtractValue(itf, 1, "") funcPtr = b.getInvokeFunction(&instr.Call) - funcPtrType = funcPtr.GlobalValueType() + funcType = funcPtr.GlobalValueType() params = append([]llvm.Value{itfValue}, params...) // start with receiver params = append(params, itfTypeCode) // end with typecode } else { @@ -90,7 +93,8 @@ func (b *builder) createGo(instr *ssa.Go) { // * The function context, for closures. // * The function pointer (for tasks). var context llvm.Value - funcPtrType, funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)), instr.Call.Value.Type().Underlying().(*types.Signature)) + funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr))) + funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature)) params = append(params, context, funcPtr) hasContext = true prefix = b.fn.RelString(nil) @@ -98,14 +102,14 @@ func (b *builder) createGo(instr *ssa.Go) { paramBundle := b.emitPointerPack(params) var stackSize llvm.Value - callee := b.createGoroutineStartWrapper(funcPtrType, 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 // section that contains the stack size (and is modified after // linking). stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) - stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType)}, "stacksize") + stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "stacksize") } else { // The stack size is fixed at compile time. By emitting it here as a // constant, it can be optimized. @@ -115,7 +119,148 @@ func (b *builder) createGo(instr *ssa.Go) { stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) } fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) - b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType)}, "") + 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 @@ -141,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{ @@ -159,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.i8ptrType}, false) - wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType) + wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) + wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) @@ -196,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.i8ptrType)) // 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.i8ptrType), - }, "") + 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 { @@ -234,7 +470,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. // merged into one. // Create the wrapper. - wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false) + wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) @@ -279,7 +515,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm. if c.Scheduler == "asyncify" { b.CreateCall(deadlockType, deadlock, []llvm.Value{ - llvm.Undef(c.i8ptrType), + llvm.Undef(c.dataPtrType), }, "") } } @@ -294,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/inlineasm.go b/compiler/inlineasm.go index 72dd68cf3e..5e54b3be60 100644 --- a/compiler/inlineasm.go +++ b/compiler/inlineasm.go @@ -99,8 +99,8 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) case llvm.IntegerTypeKind: constraints = append(constraints, "r") case llvm.PointerTypeKind: - // Memory references require a type in LLVM 14, probably as a - // preparation for opaque pointers. + // Memory references require a type starting with LLVM 14, + // probably as a preparation for opaque pointers. err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23") return s default: diff --git a/compiler/interface.go b/compiler/interface.go index dff5b92412..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++ } } @@ -414,10 +417,10 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { }, typeFields...) if hasMethodSet { typeFields = append([]llvm.Value{ - llvm.ConstBitCast(c.getTypeMethodSet(typ), c.i8ptrType), + c.getTypeMethodSet(typ), }, typeFields...) } - alignment := c.targetData.TypeAllocSize(c.i8ptrType) + alignment := c.targetData.TypeAllocSize(c.dataPtrType) if alignment < 4 { alignment = 4 } @@ -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 @@ -628,7 +630,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { // Construct global value. globalValue := c.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false), - llvm.ConstArray(c.i8ptrType, signatures), + llvm.ConstArray(c.dataPtrType, signatures), c.ctx.ConstStruct(wrappers, false), }, false) global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName) @@ -684,19 +686,25 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type") commaOk := llvm.Value{} - if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { - // Type assert on interface type. - // This is a call to an interface type assert function. - // The interface lowering pass will define this function by filling it - // with a type switch over all concrete types that implement this - // interface, and returning whether it's one of the matched types. - // This is very different from how interface asserts are implemented in - // the main Go compiler, where the runtime checks whether the type - // implements each method of the interface. See: - // https://research.swtch.com/interfaces - fn := b.getInterfaceImplementsFunc(expr.AssertedType) - commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "") + if intf, ok := expr.AssertedType.Underlying().(*types.Interface); ok { + if intf.Empty() { + // intf is the empty interface => no methods + // This type assertion always succeeds, so we can just set commaOk to true. + commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true) + } else { + // Type assert on interface type with methods. + // This is a call to an interface type assert function. + // The interface lowering pass will define this function by filling it + // with a type switch over all concrete types that implement this + // interface, and returning whether it's one of the matched types. + // This is very different from how interface asserts are implemented in + // the main Go compiler, where the runtime checks whether the type + // implements each method of the interface. See: + // https://research.swtch.com/interfaces + fn := b.getInterfaceImplementsFunc(expr.AssertedType) + commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "") + } } else { name, _ := getTypeCodeName(expr.AssertedType) globalName := "reflect/types.typeid:" + name @@ -779,7 +787,7 @@ func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) ll fnName := s + ".$typeassert" llvmFn := c.mod.NamedFunction(fnName) if llvmFn.IsNil() { - llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType}, false) + llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.dataPtrType}, false) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) @@ -802,7 +810,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { paramTuple = append(paramTuple, sig.Params().At(i)) } paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) - llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) + llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) @@ -842,7 +850,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType } // create wrapper function - paramTypes := append([]llvm.Type{c.i8ptrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) + paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) c.addStandardAttributes(wrapper) diff --git a/compiler/interrupt.go b/compiler/interrupt.go index 1fb4c22b4c..68c8a36058 100644 --- a/compiler/interrupt.go +++ b/compiler/interrupt.go @@ -36,11 +36,13 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro // Fall back to a generic error. return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") } - _, funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil) + funcRawPtr, funcContext := b.decodeFuncValue(funcValue) funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType) // 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 af3a57de1b..3c7edd7c95 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -7,7 +7,6 @@ import ( "strconv" "strings" - "github.com/tinygo-org/tinygo/compiler/llvmutil" "tinygo.org/x/go-llvm" ) @@ -24,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"): @@ -48,12 +49,9 @@ func (b *builder) defineIntrinsicFunction() { func (b *builder) createMemoryCopyImpl() { b.createFunctionStart(true) fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - if llvmutil.Major() < 15 { // compatibility with LLVM 14 - fnName = "llvm." + b.fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - } llvmFn := b.mod.NamedFunction(fnName) if llvmFn.IsNil() { - fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false) + fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false) llvmFn = llvm.AddFunction(b.mod, fnName, fnType) } var params []llvm.Value @@ -81,15 +79,20 @@ 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()) - if llvmutil.Major() < 15 { // compatibility with LLVM 14 - fnName = "llvm.memset.p0i8.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) - } llvmFn := c.mod.NamedFunction(fnName) if llvmFn.IsNil() { - fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) + fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) llvmFn = llvm.AddFunction(c.mod, fnName, fnType) } return llvmFn @@ -111,7 +114,7 @@ func (b *builder) createKeepAliveImpl() { // // It should be portable to basically everything as the "r" register type // exists basically everywhere. - asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType}, false) + asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false) asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false) b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "") diff --git a/compiler/llvm.go b/compiler/llvm.go index 0d33ab5648..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" ) @@ -20,7 +21,7 @@ import ( // // This is useful for creating temporary allocas for intrinsics. Don't forget to // end the lifetime using emitLifetimeEnd after you're done with it. -func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { +func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, size llvm.Value) { return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name) } @@ -63,47 +64,45 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { // Allocate memory for the packed data. size := b.targetData.TypeAllocSize(packedType) if size == 0 { - return llvm.ConstPointerNull(b.i8ptrType) + return llvm.ConstPointerNull(b.dataPtrType) } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { - return b.CreateBitCast(values[0], b.i8ptrType, "pack.ptr") - } else if size <= b.targetData.TypeAllocSize(b.i8ptrType) { + return values[0] + } else if size <= b.targetData.TypeAllocSize(b.dataPtrType) { // Packed data fits in a pointer, so store it directly inside the // pointer. if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { // Try to keep this cast in SSA form. - return b.CreateIntToPtr(values[0], b.i8ptrType, "pack.int") + return b.CreateIntToPtr(values[0], b.dataPtrType, "pack.int") } // Because packedType is a struct and we have to cast it to a *i8, store // it in a *i8 alloca first and load the *i8 value from there. This is // effectively a bitcast. - packedAlloc, _, _ := b.createTemporaryAlloca(b.i8ptrType, "") + packedAlloc, _ := b.createTemporaryAlloca(b.dataPtrType, "") - if size < b.targetData.TypeAllocSize(b.i8ptrType) { + if size < b.targetData.TypeAllocSize(b.dataPtrType) { // The alloca is bigger than the value that will be stored in it. // To avoid having some bits undefined, zero the alloca first. // Hopefully this will get optimized away. - b.CreateStore(llvm.ConstNull(b.i8ptrType), packedAlloc) + b.CreateStore(llvm.ConstNull(b.dataPtrType), packedAlloc) } // Store all values in the alloca. - packedAllocCast := b.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "") for i, value := range values { indices := []llvm.Value{ llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), } - gep := b.CreateInBoundsGEP(packedType, packedAllocCast, indices, "") + gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") b.CreateStore(value, gep) } // Load value (the *i8) from the alloca. - result := b.CreateLoad(b.i8ptrType, packedAlloc, "") + result := b.CreateLoad(b.dataPtrType, packedAlloc, "") // End the lifetime of the alloca, to help the optimizer. - packedPtr := b.CreateBitCast(packedAlloc, b.i8ptrType, "") packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false) - b.emitLifetimeEnd(packedPtr, packedSize) + b.emitLifetimeEnd(packedAlloc, packedSize) return result } else { @@ -124,21 +123,22 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { global.SetGlobalConstant(true) global.SetUnnamedAddr(true) global.SetLinkage(llvm.InternalLinkage) - return llvm.ConstBitCast(global, b.i8ptrType) + return global } // 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") - packedHeapAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ + packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{ sizeValue, - llvm.ConstNull(b.i8ptrType), - llvm.Undef(b.i8ptrType), // unused context parameter + 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(packedHeapAlloc) + b.trackPointer(packedAlloc) } - packedAlloc := b.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "") // Store all values in the heap pointer. for i, value := range values { @@ -151,7 +151,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { } // Return the original heap allocation pointer, which already is an *i8. - return packedHeapAlloc + return packedAlloc } } @@ -160,28 +160,27 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll packedType := b.ctx.StructType(valueTypes, false) // Get a correctly-typed pointer to the packed data. - var packedAlloc, packedRawAlloc llvm.Value + var packedAlloc llvm.Value + needsLifetimeEnd := false size := b.targetData.TypeAllocSize(packedType) if size == 0 { // No data to unpack. } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { // A single pointer is always stored directly. - return []llvm.Value{b.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")} - } else if size <= b.targetData.TypeAllocSize(b.i8ptrType) { + return []llvm.Value{ptr} + } else if size <= b.targetData.TypeAllocSize(b.dataPtrType) { // Packed data stored directly in pointer. if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { // Keep this cast in SSA form. return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} } // Fallback: load it using an alloca. - packedRawAlloc, _, _ = b.createTemporaryAlloca(llvm.PointerType(b.i8ptrType, 0), "unpack.raw.alloc") - packedRawValue := b.CreateBitCast(ptr, llvm.PointerType(b.i8ptrType, 0), "unpack.raw.value") - b.CreateStore(packedRawValue, packedRawAlloc) - packedAlloc = b.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc") + packedAlloc, _ = b.createTemporaryAlloca(b.dataPtrType, "unpack.raw.alloc") + b.CreateStore(ptr, packedAlloc) + needsLifetimeEnd = true } else { - // Packed data stored on the heap. Bitcast the passed-in pointer to the - // correct pointer type. - packedAlloc = b.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr") + // Packed data stored on the heap. + packedAlloc = ptr } // Load each value from the packed data. values := make([]llvm.Value, len(valueTypes)) @@ -198,10 +197,9 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "") values[i] = b.CreateLoad(valueType, gep, "") } - if !packedRawAlloc.IsNil() { - allocPtr := b.CreateBitCast(packedRawAlloc, b.i8ptrType, "") + if needsLifetimeEnd { allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false) - b.emitLifetimeEnd(allocPtr, allocSize) + b.emitLifetimeEnd(packedAlloc, allocSize) } return values } @@ -253,12 +251,12 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va // Do a few checks to see whether we need to generate any object layout // information at all. objectSizeBytes := c.targetData.TypeAllocSize(t) - pointerSize := c.targetData.TypeAllocSize(c.i8ptrType) - pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType) + pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) + pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType) if objectSizeBytes < pointerSize { // Too small to contain a pointer. layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType) + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) } bitmap := c.getPointerBitmap(t, pos) if bitmap.BitLen() == 0 { @@ -266,13 +264,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va // TODO: this can be done in many other cases, e.g. when allocating an // array (like [4][]byte, which repeats a slice 4 times). layout := (uint64(1) << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType) + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) } if objectSizeBytes%uint64(pointerAlignment) != 0 { // This shouldn't happen except for packed structs, which aren't // currently used. c.addError(pos, "internal error: unexpected object size for object with pointer field") - return llvm.ConstNull(c.i8ptrType) + return llvm.ConstNull(c.dataPtrType) } objectSizeWords := objectSizeBytes / uint64(pointerAlignment) @@ -297,7 +295,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va // The runtime knows that if the least significant bit of the pointer is // set, the pointer contains the value itself. layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 - return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType) + return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) } // Unfortunately, the object layout is too big to fit in a pointer-sized @@ -308,7 +306,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) global := c.mod.NamedGlobal(globalName) if !global.IsNil() { - return llvm.ConstBitCast(global, c.i8ptrType) + return global } // Create the global initializer. @@ -359,13 +357,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va global.AddMetadata(0, diglobal) } - return llvm.ConstBitCast(global, c.i8ptrType) + return global } // getPointerBitmap scans the given LLVM type for pointers and sets bits in a // bigint at the word offset that contains a pointer. This scan is recursive. func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int { - alignment := c.targetData.PrefTypeAlignment(c.i8ptrType) + alignment := c.targetData.PrefTypeAlignment(c.dataPtrType) switch typ.TypeKind() { case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: return big.NewInt(0) @@ -373,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.i8ptrType, c.i8ptrType}, false) - } for i, subtyp := range typ.StructElementTypes() { subptrs := c.getPointerBitmap(subtyp, pos) if subptrs.BitLen() == 0 { @@ -421,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 @@ -456,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.i8ptrType, nil, false) - stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType) + fnType := llvm.FunctionType(b.dataPtrType, nil, false) + 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 a79ecf9c49..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,22 +8,13 @@ package llvmutil import ( + "encoding/binary" "strconv" "strings" "tinygo.org/x/go-llvm" ) -// Major returns the LLVM major version. -func Major() int { - llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0]) - if err != nil { - // sanity check, should be unreachable - panic("could not parse LLVM version: " + err.Error()) - } - return llvmMajor -} - // CreateEntryBlockAlloca creates a new alloca in the entry block, even though // the IR builder is located elsewhere. It assumes that the insert point is // at the end of the current block. @@ -41,21 +32,19 @@ 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 // end the lifetime using emitLifetimeEnd after you're done with it. -func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { +func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, size llvm.Value) { ctx := t.Context() targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() - i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) alloca = CreateEntryBlockAlloca(builder, t, name) - bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast") size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) fnType, fn := getLifetimeStartFunc(mod) - builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "") + builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") return } @@ -64,21 +53,19 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, ctx := mod.Context() targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() - i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) alloca := CreateEntryBlockAlloca(builder, t, name) builder.SetInsertPointBefore(inst) - bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast") size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) fnType, fn := getLifetimeStartFunc(mod) - builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "") + builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") if next := llvm.NextInstruction(inst); !next.IsNil() { builder.SetInsertPointBefore(next) } else { builder.SetInsertPointAtEnd(inst.InstructionParent()) } fnType, fn = getLifetimeEndFunc(mod) - builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "") + builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "") return alloca } @@ -94,13 +81,10 @@ func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value // first if it doesn't exist yet. func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) { fnName := "llvm.lifetime.start.p0" - if Major() < 15 { // compatibility with LLVM 14 - fnName = "llvm.lifetime.start.p0i8" - } fn := mod.NamedFunction(fnName) ctx := mod.Context() - i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) - fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false) + ptrType := llvm.PointerType(ctx.Int8Type(), 0) + fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) if fn.IsNil() { fn = llvm.AddFunction(mod, fnName, fnType) } @@ -111,13 +95,10 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) { // first if it doesn't exist yet. func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) { fnName := "llvm.lifetime.end.p0" - if Major() < 15 { - fnName = "llvm.lifetime.end.p0i8" - } fn := mod.NamedFunction(fnName) ctx := mod.Context() - i8ptrType := llvm.PointerType(ctx.Int8Type(), 0) - fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false) + ptrType := llvm.PointerType(ctx.Int8Type(), 0) + fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false) if fn.IsNil() { fn = llvm.AddFunction(mod, fnName, fnType) } @@ -196,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). @@ -213,14 +194,36 @@ func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) { } // Add the new values. - i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) + ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) for _, value := range values { - usedValues = append(usedValues, llvm.ConstPointerCast(value, i8ptrType)) + // Note: the bitcast is necessary to cast AVR function pointers to + // address space 0 pointer types. + usedValues = append(usedValues, llvm.ConstPointerCast(value, ptrType)) } // Create a new array (with the old and new values). - usedInitializer := llvm.ConstArray(i8ptrType, usedValues) + usedInitializer := llvm.ConstArray(ptrType, usedValues) used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName) 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 21f0ee4a67..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) @@ -65,7 +59,7 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val // Allocate the memory for the resulting type. Do not zero this memory: it // will be zeroed by the hashmap get implementation if the key is not // present in the map. - mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") + mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value") // We need the map size (with type uintptr) to pass to the hashmap*Get // functions. This is necessary because those *Get functions are valid on @@ -82,19 +76,19 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string - params := []llvm.Value{m, key, mapValuePtr, mapValueSize} + params := []llvm.Value{m, key, mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal // Store the key in an alloca, in the entry block to avoid dynamic stack // growth. - mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") b.CreateStore(key, mapKeyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca) // Fetch the value from the hashmap. - params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize} + params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "") - b.emitLifetimeEnd(mapKeyPtr, mapKeySize) + b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) } else { // Not trivially comparable using memcmp. Make it an interface instead. itfKey := key @@ -102,14 +96,14 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val // Not already an interface, so convert it to an interface now. itfKey = b.createMakeInterface(key, origKeyType, pos) } - params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize} + params := []llvm.Value{m, itfKey, mapValueAlloca, mapValueSize} commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "") } // Load the resulting value from the hashmap. The value is set to the zero // value if the key doesn't exist in the hashmap. mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") - b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize) + b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize) if commaOk { tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) @@ -124,22 +118,22 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val // createMapUpdate updates a map key to a given value, by creating an // appropriate runtime call. func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { - valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") + valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value") b.CreateStore(value, valueAlloca) origKeyType := keyType keyType = keyType.Underlying() if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { // key is a string - params := []llvm.Value{m, key, valuePtr} + params := []llvm.Value{m, key, valueAlloca} b.createRuntimeCall("hashmapStringSet", params, "") } else if hashmapIsBinaryKey(keyType) { // key can be compared with runtime.memequal - keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") b.CreateStore(key, keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) - params := []llvm.Value{m, keyPtr, valuePtr} + params := []llvm.Value{m, keyAlloca, valueAlloca} b.createRuntimeCall("hashmapBinarySet", params, "") - b.emitLifetimeEnd(keyPtr, keySize) + b.emitLifetimeEnd(keyAlloca, keySize) } else { // Key is not trivially comparable, so compare it as an interface instead. itfKey := key @@ -147,10 +141,10 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, // Not already an interface, so convert it to an interface first. itfKey = b.createMakeInterface(key, origKeyType, pos) } - params := []llvm.Value{m, itfKey, valuePtr} + params := []llvm.Value{m, itfKey, valueAlloca} b.createRuntimeCall("hashmapInterfaceSet", params, "") } - b.emitLifetimeEnd(valuePtr, valueSize) + b.emitLifetimeEnd(valueAlloca, valueSize) } // createMapDelete deletes a key from a map by calling the appropriate runtime @@ -164,12 +158,12 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok b.createRuntimeCall("hashmapStringDelete", params, "") return nil } else if hashmapIsBinaryKey(keyType) { - keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") + keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") b.CreateStore(key, keyAlloca) b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) - params := []llvm.Value{m, keyPtr} + params := []llvm.Value{m, keyAlloca} b.createRuntimeCall("hashmapBinaryDelete", params, "") - b.emitLifetimeEnd(keyPtr, keySize) + b.emitLifetimeEnd(keyAlloca, keySize) return nil } else { // Key is not trivially comparable, so compare it as an interface @@ -225,9 +219,9 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv } // Extract the key and value from the map. - mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key") - mapValueAlloca, mapValuePtr, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") - ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next") + mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key") + mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") + ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next") mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "") mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") @@ -238,8 +232,8 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv } // End the lifetimes of the allocas, because we're done with them. - b.emitLifetimeEnd(mapKeyPtr, mapKeySize) - b.emitLifetimeEnd(mapValuePtr, mapValueSize) + b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) + b.emitLifetimeEnd(mapValueAlloca, mapValueSize) // Construct the *ssa.Next return value: {ok, mapKey, mapValue} tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) @@ -326,21 +320,14 @@ 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) } if fieldEndOffset != nextOffset { n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false) llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false) - gepPtr := elemPtr - if gepPtr.Type() != b.i8ptrType { - gepPtr = b.CreateBitCast(gepPtr, b.i8ptrType, "") // LLVM 14 - } - paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), gepPtr, []llvm.Value{llvmStoreSize}, "") - if paddingStart.Type() != b.i8ptrType { - paddingStart = b.CreateBitCast(paddingStart, b.i8ptrType, "") // LLVM 14 - } + paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), elemPtr, []llvm.Value{llvmStoreSize}, "") b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "") } } diff --git a/compiler/symbol.go b/compiler/symbol.go index 60e882d520..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 @@ -96,7 +100,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // Add an extra parameter as the function context. This context is used in // closures and bound methods, but should be optimized away when not used. if !info.exported { - paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", elemSize: 0}) + paramInfos = append(paramInfos, paramInfo{llvmType: c.dataPtrType, name: "context", elemSize: 0}) } var paramTypes []llvm.Type @@ -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,26 +152,26 @@ 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. for _, attrName := range []string{"noalias", "nonnull"} { llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0)) } - if llvmutil.Major() >= 15 { // allockind etc are not available in LLVM 14 - // Add attributes to signal to LLVM that this is an allocator - // function. This enables a number of optimizations. - llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed)) - llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc")) - // Use a special value to indicate the first parameter: - // > allocsize has two integer arguments, but because they're both 32 bits, we can - // > pack them into one 64-bit value, at the cost of making said value - // > nonsensical. - // > - // > In order to do this, we need to reserve one value of the second (optional) - // > allocsize argument to signify "not present." - llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff)) - } + // Add attributes to signal to LLVM that this is an allocator function. + // This enables a number of optimizations. + llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed)) + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc")) + // Use a special value to indicate the first parameter: + // > allocsize has two integer arguments, but because they're both 32 bits, we can + // > pack them into one 64-bit value, at the cost of making said value + // > nonsensical. + // > + // > In order to do this, we need to reserve one value of the second (optional) + // > allocsize argument to signify "not present." + llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff)) case "runtime.sliceAppend": // Appending a slice will only read the to-be-appended slice, it won't // be modified. @@ -170,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 @@ -212,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() @@ -226,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 @@ -241,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 } @@ -250,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 + } } - if !strings.HasPrefix(text, "//go:") { + } + } + + // 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 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. @@ -445,15 +586,10 @@ func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) { llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0)) if strings.Split(c.Triple, "-")[0] == "x86_64" { // Required by the ABI. - if llvmutil.Major() < 15 { - // Needed for LLVM 14 support. - llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0)) - } else { - // The uwtable has two possible values: sync (1) or async (2). We - // use sync because we currently don't use async unwind tables. - // For details, see: https://llvm.org/docs/LangRef.html#function-attributes - llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1)) - } + // The uwtable has two possible values: sync (1) or async (2). We use + // sync because we currently don't use async unwind tables. + // For details, see: https://llvm.org/docs/LangRef.html#function-attributes + llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1)) } } diff --git a/compiler/syscall.go b/compiler/syscall.go index db1ffd7007..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) } @@ -183,7 +278,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { } llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false) fn := b.getValue(call.Args[0], getPos(call)) - fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "") + fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "") // Prepare some functions that will be called later. setLastError := b.mod.NamedFunction("SetLastError") @@ -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 3d81b316f2..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) @@ -21,7 +19,7 @@ entry: ; Function Attrs: nounwind define hidden void @main.regularFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 32768, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.regularFunction$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9 ret void } @@ -43,7 +41,7 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #1 ; Function Attrs: nounwind define hidden void @main.inlineFunctionGoroutine(ptr %context) unnamed_addr #2 { entry: - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 32768, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.inlineFunctionGoroutine$1$gowrapper" to i32), ptr nonnull inttoptr (i32 5 to ptr), i32 65536, ptr undef) #9 ret void } @@ -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 32768, ptr undef) #9 + 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,27 +93,31 @@ 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 32768, ptr undef) #9 + 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,28 +150,28 @@ 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 - call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 32768, ptr undef) #9 + %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/docs/Makefile b/docs/GNUmakefile similarity index 99% rename from docs/Makefile rename to docs/GNUmakefile index e7128bfeb5..fb389bebe4 100644 --- a/docs/Makefile +++ b/docs/GNUmakefile @@ -16,7 +16,7 @@ I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: - @echo "Please use \`make ' where is one of" + @echo "Please use \`$(MAKE) ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" 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 new file mode 100644 index 0000000000..d541b769ca --- /dev/null +++ b/flake.lock @@ -0,0 +1,60 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1728500571, + "narHash": "sha256-dOymOQ3AfNI4Z337yEwHGohrVQb4yPODCW9MDUyAc4w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d51c28603def282a24fa034bcb007e2bcb5b5dd0", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-24.05", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..25ffc70205 --- /dev/null +++ b/flake.nix @@ -0,0 +1,85 @@ +# A Nix flake file, mainly intended for developing TinyGo. +# You can download Nix here, for use on your Linux or macOS system: +# https://nixos.org/download.html +# After you have installed Nix, you can enter the development environment as +# follows: +# +# nix develop +# +# This drops you into a bash shell, where you can install TinyGo simply using +# the following command: +# +# go install +# +# That's all! Assuming you've set up your $PATH correctly, you can now use the +# tinygo command as usual: +# +# tinygo version +# +# 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 # 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 +# a separate LLVM fork. +# +# You can also do many other things from this environment. Building and flashing +# should work as you're used to: it's not a VM or container so there are no +# access restrictions and you're running in the same host environment - just +# with a slightly different set of tools available. +{ + 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-24.05"; + flake-utils.url = "github:numtide/flake-utils"; + }; + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + with pkgs; + { + devShells.default = mkShell { + buildInputs = [ + # These dependencies are required for building tinygo (go install). + go + llvmPackages_18.llvm + llvmPackages_18.libclang + # Additional dependencies needed at runtime, for building and/or + # flashing. + llvmPackages_18.lld + avrdude + binaryen + # Additional dependencies needed for on-chip debugging. + # These tools are rather big (especially GDB) and not frequently + # used, so are commented out. On-chip debugging is still possible if + # these tools are available in the host environment. + #gdb + #openocd + ]; + shellHook= '' + # Configure CLANG, LLVM_AR, and LLVM_NM for `make wasi-libc`. + # 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-18 -resource-dir ${llvmPackages_18.clang.cc.lib}/lib/clang/18" + export LLVM_AR=llvm-ar + export LLVM_NM=llvm-nm + + # Make `make smoketest` work (the default is `md5`, while Nix only + # has `md5sum`). + export MD5SUM=md5sum + + # Ugly hack to make the Clang resources directory available. + 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 9af3402bad..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,14 +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/sys v0.11.0 - golang.org/x/tools v0.12.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-20230918183930-9edb6403d0bc + tinygo.org/x/go-llvm v0.0.0-20250119132755-9dca92dfb4f9 ) require ( @@ -29,5 +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 + 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 587efdf2ac..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,25 +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.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +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.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= +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-20230918183930-9edb6403d0bc h1:IVX1dqCX3c88P7iEMBtz1xCAM4UIqCMgbqHdSefBaWE= -tinygo.org/x/go-llvm v0.0.0-20230918183930-9edb6403d0bc/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 f6a32502a6..fe4c8bf63e 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -14,6 +14,8 @@ import ( "runtime" "strings" "sync" + + "tinygo.org/x/go-llvm" ) // Keys is a slice of all available environment variable keys. @@ -28,16 +30,28 @@ 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") } } +// Set to true if we're linking statically against LLVM. +var hasBuiltinTools = false + // TINYGOROOT is the path to the final location for checking tinygo files. If // unset (by a -X ldflag), then sourceDir() will fallback to the original build // directory. var TINYGOROOT string +// If a particular Clang resource dir must always be used and TinyGo can't +// figure out the directory using heuristics, this global can be set using a +// linker flag. +// This is needed for Nix. +var clangResourceDir string + // Variables read from a `go env` command invocation. var goEnvVars struct { GOPATH string @@ -117,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 @@ -131,11 +152,8 @@ func Get(name string) string { } return filepath.Join(dir, "tinygo") case "CGO_ENABLED": - val := os.Getenv("CGO_ENABLED") - if val == "1" || val == "0" { - return val - } - // Default to enabling CGo. + // Always enable CGo. It is required by a number of targets, including + // macOS and the rp2040. return "1" case "TINYGOROOT": return sourceDir() @@ -151,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 "" } @@ -284,3 +307,95 @@ func isSourceDir(root string) bool { _, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go")) return err == nil } + +// ClangResourceDir returns the clang resource dir if available. This is the +// -resource-dir flag. If it isn't available, an empty string is returned and +// -resource-dir should be left unset. +// The libclang flag must be set if the resource dir is read for use by +// libclang. +// In that case, the resource dir is always returned (even when linking +// dynamically against LLVM) because libclang always needs this directory. +func ClangResourceDir(libclang bool) string { + if clangResourceDir != "" { + // The resource dir is forced to a particular value at build time. + // This is needed on Nix for example, where Clang and libclang don't + // know their own resource dir. + // Also see: + // https://discourse.nixos.org/t/why-is-the-clang-resource-dir-split-in-a-separate-package/34114 + return clangResourceDir + } + + if !hasBuiltinTools && !libclang { + // Using external tools, so the resource dir doesn't need to be + // specified. Clang knows where to find it. + return "" + } + + // Check whether we're running from a TinyGo release directory. + // This is the case for release binaries on GitHub. + root := Get("TINYGOROOT") + releaseHeaderDir := filepath.Join(root, "lib", "clang") + if _, err := os.Stat(releaseHeaderDir); !errors.Is(err, fs.ErrNotExist) { + return releaseHeaderDir + } + + if hasBuiltinTools { + // We are statically linked to LLVM. + // Check whether we're running from the source directory. + // This typically happens when TinyGo was built using `make` as part of + // development. + llvmMajor := strings.Split(llvm.Version, ".")[0] + buildResourceDir := filepath.Join(root, "llvm-build", "lib", "clang", llvmMajor) + if _, err := os.Stat(buildResourceDir); !errors.Is(err, fs.ErrNotExist) { + return buildResourceDir + } + } else { + // We use external tools, either when installed using `go install` or + // when packaged in a Linux distribution (Linux distros typically prefer + // dynamic linking). + // Try to detect the system clang resources directory. + resourceDir := findSystemClangResources(root) + if resourceDir != "" { + return resourceDir + } + } + + // Resource directory not found. + return "" +} + +// Find the Clang resource dir on this particular system. +// Return the empty string when they aren't found. +func findSystemClangResources(TINYGOROOT string) string { + llvmMajor := strings.Split(llvm.Version, ".")[0] + + switch runtime.GOOS { + case "linux", "android": + // Header files are typically stored in /usr/lib/clang//include. + // Tested on Fedora 39, Debian 12, and Arch Linux. + path := filepath.Join("/usr/lib/clang", llvmMajor) + _, err := os.Stat(filepath.Join(path, "include", "stdint.h")) + if err == nil { + return path + } + case "darwin": + // This assumes a Homebrew installation, like in builder/commands.go. + var prefix string + switch runtime.GOARCH { + case "amd64": + prefix = "/usr/local/opt/llvm@" + llvmMajor + case "arm64": + prefix = "/opt/homebrew/opt/llvm@" + llvmMajor + default: + return "" // very unlikely for now + } + path := fmt.Sprintf("%s/lib/clang/%s", prefix, llvmMajor) + _, err := os.Stat(path + "/include/stdint.h") + if err == nil { + return path + } + } + + // Could not find it. + return "" +} diff --git a/goenv/tools-builtin.go b/goenv/tools-builtin.go new file mode 100644 index 0000000000..91f10d99db --- /dev/null +++ b/goenv/tools-builtin.go @@ -0,0 +1,7 @@ +//go:build byollvm + +package goenv + +func init() { + hasBuiltinTools = true +} diff --git a/goenv/version.go b/goenv/version.go index f89b43f1ab..8b0aa07631 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -4,18 +4,36 @@ import ( "errors" "fmt" "io" + "runtime/debug" "strings" ) // Version of TinyGo. // Update this value before release of new version of software. -const Version = "0.30.0" +const version = "0.37.0" -var ( - // This variable is set at build time using -ldflags parameters. - // See: https://stackoverflow.com/a/11355611 - GitSha1 string -) +// Return TinyGo version, either in the form 0.30.0 or as a development version +// (like 0.30.0-dev-abcd012). +func Version() string { + v := version + if strings.HasSuffix(version, "-dev") { + if hash := readGitHash(); hash != "" { + v += "-" + hash + } + } + 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. @@ -24,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/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/errors.go b/interp/errors.go index 7aad39fb8f..551a18d481 100644 --- a/interp/errors.go +++ b/interp/errors.go @@ -60,10 +60,10 @@ func (r *runner) errorAt(inst instruction, err error) *Error { pos := getPosition(inst.llvmInst) return &Error{ ImportPath: r.pkgName, - Inst: inst.String(), + Inst: inst.llvmInst.String(), Pos: pos, Err: err, - Traceback: []ErrorLine{{pos, inst.String()}}, + Traceback: []ErrorLine{{pos, inst.llvmInst.String()}}, } } diff --git a/interp/interp.go b/interp/interp.go index 7833dfe932..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" ) @@ -21,9 +23,10 @@ type runner struct { targetData llvm.TargetData builder llvm.Builder pointerSize uint32 // cached pointer size from the TargetData - i8ptrType llvm.Type // often used type so created in advance + 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{{}}, @@ -46,13 +50,13 @@ func newRunner(mod llvm.Module, timeout time.Duration, debug bool) *runner { timeout: timeout, } r.pointerSize = uint32(r.targetData.PointerSize()) - r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0) + r.dataPtrType = llvm.PointerType(mod.Context().Int8Type(), 0) r.uintptrType = mod.Context().IntType(r.targetData.PointerSize() * 8) - r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case) + r.maxAlign = r.targetData.PrefTypeAlignment(r.dataPtrType) // assume pointers are maximally aligned (this is not always the case) 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{} @@ -126,7 +130,7 @@ func Run(mod llvm.Module, timeout time.Duration, debug bool) error { mem.revert() // Create a call to the package initializer (which was // previously deleted). - i8undef := llvm.Undef(r.i8ptrType) + i8undef := llvm.Undef(r.dataPtrType) r.builder.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{i8undef}, "") // Make sure that any globals touched by the package // initializer, won't be accessed by later package initializers. @@ -174,8 +178,7 @@ func Run(mod llvm.Module, timeout time.Duration, debug bool) error { newGlobal.SetLinkage(obj.llvmGlobal.Linkage()) newGlobal.SetAlignment(obj.llvmGlobal.Alignment()) // TODO: copy debug info, unnamed_addr, ... - bitcast := llvm.ConstBitCast(newGlobal, obj.llvmGlobal.Type()) - obj.llvmGlobal.ReplaceAllUsesWith(bitcast) + obj.llvmGlobal.ReplaceAllUsesWith(newGlobal) name := obj.llvmGlobal.Name() obj.llvmGlobal.EraseFromParentAsGlobal() newGlobal.SetName(name) diff --git a/interp/interp_test.go b/interp/interp_test.go index fc567af20f..cac5650879 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -77,12 +77,9 @@ func runTest(t *testing.T, pathPrefix string) { } // Run some cleanup passes to get easy-to-read outputs. - pm := llvm.NewPassManager() - defer pm.Dispose() - pm.AddGlobalOptimizerPass() - pm.AddDeadStoreEliminationPass() - pm.AddAggressiveDCEPass() - pm.Run(mod) + to := llvm.NewPassBuilderOptions() + defer to.Dispose() + mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to) // Read the expected output IR. out, err := os.ReadFile(pathPrefix + ".out.ll") diff --git a/interp/interpreter.go b/interp/interpreter.go index dcea0044a9..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: @@ -239,7 +239,8 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // already be emitted in initAll. continue case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "runtime.hashmapInterfaceHash" || - callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current": + callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current" || + callFn.name == "time.startTimer" || callFn.name == "time.stopTimer" || callFn.name == "time.resetTimer": // These functions should be run at runtime. Specifically: // * Print and panic functions are best emitted directly without // interpreting them, otherwise we get a ton of putchar (etc.) @@ -252,6 +253,8 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // at runtime. // * internal/task.start, internal/task.Current: start and read shcheduler state, // which is modified elsewhere. + // * Timer functions access runtime internal state which may + // not be initialized. err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err @@ -279,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), @@ -315,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 @@ -371,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 { @@ -414,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:]) @@ -424,7 +439,22 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } - methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r) + // 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) + } + methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } @@ -470,7 +500,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } - methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r) + typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize)) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } @@ -543,7 +577,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // how this function got called. callErr.Traceback = append(callErr.Traceback, ErrorLine{ Pos: getPosition(inst.llvmInst), - Inst: inst.String(), + Inst: inst.llvmInst.String(), }) return nil, mem, callErr } @@ -620,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) @@ -633,11 +668,11 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent case llvm.GetElementPtr: // GetElementPtr does pointer arithmetic, changing the offset of the // pointer into the underlying object. - var offset uint64 + var offset int64 for i := 1; i < len(operands); i += 2 { - index := operands[i].Uint() - elementSize := operands[i+1].Uint() - if int64(elementSize) < 0 { + index := operands[i].Int(r) + elementSize := operands[i+1].Int(r) + if elementSize < 0 { // This is a struct field. offset += index } else { @@ -651,11 +686,14 @@ 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() + offset + ptrValue := operands[0].Uint(r) + uint64(offset) locals[inst.localIndex] = makeLiteralInt(ptrValue, int(operands[0].len(r)*8)) continue } - ptr = ptr.addOffset(int64(offset)) + ptr, err = ptr.addOffset(int64(offset)) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } locals[inst.localIndex] = ptr if r.debug { fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr) @@ -710,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") } @@ -753,20 +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 = 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 @@ -781,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") } @@ -823,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) } @@ -836,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) } @@ -886,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") @@ -929,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) } @@ -1046,15 +1087,3 @@ func intPredicateString(predicate llvm.IntPredicate) string { return "cmp?" } } - -// Strip some pointer casts. This is probably unnecessary once support for -// LLVM 14 (non-opaque pointers) is dropped. -func stripPointerCasts(value llvm.Value) llvm.Value { - if !value.IsAConstantExpr().IsNil() { - switch value.Opcode() { - case llvm.GetElementPtr, llvm.BitCast: - return stripPointerCasts(value.Operand(0)) - } - } - return value -} diff --git a/interp/memory.go b/interp/memory.go index 2065bcdf5f..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) @@ -517,12 +519,12 @@ func (v pointerValue) offset() uint32 { // addOffset essentially does a GEP operation (pointer arithmetic): it adds the // offset to the pointer. It also checks that the offset doesn't overflow the // maximum offset size (which is 4GB). -func (v pointerValue) addOffset(offset int64) pointerValue { +func (v pointerValue) addOffset(offset int64) (pointerValue, error) { result := pointerValue{v.pointer + uint64(offset)} if checks && v.index() != result.index() { - panic("interp: offset out of range") + return result, fmt.Errorf("interp: offset %d out of range for object %v", offset, v) } - return result + return result, nil } func (v pointerValue) len(r *runner) uint32 { @@ -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 @@ -658,20 +662,11 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val if v.offset() != 0 { // If there is an offset, make sure to use a GEP to index into the // pointer. - // Cast to an i8* first (if needed) for easy indexing. - if llvmValue.Type() != mem.r.i8ptrType { - llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType) - } llvmValue = llvm.ConstInBoundsGEP(mem.r.mod.Context().Int8Type(), llvmValue, []llvm.Value{ llvm.ConstInt(mem.r.mod.Context().Int32Type(), uint64(v.offset()), false), }) } - // If a particular LLVM pointer type is requested, cast to it. - if !llvmType.IsNil() && llvmType != llvmValue.Type() { - llvmValue = llvm.ConstBitCast(llvmValue, llvmType) - } - return llvmValue, nil } @@ -711,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)) + "]>" @@ -747,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") } @@ -824,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 @@ -872,7 +859,7 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, if err != nil { panic(err) } - if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.i8ptrType) { + if checks && mem.r.targetData.TypeAllocSize(llvmType) != mem.r.targetData.TypeAllocSize(mem.r.dataPtrType) { // Probably trying to serialize a pointer to a byte array, // perhaps as a result of rawLLVMValue() in a previous interp // run. @@ -887,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: @@ -954,15 +941,13 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value, // Because go-llvm doesn't have addrspacecast at the moment, // do it indirectly with a ptrtoint/inttoptr pair. llvmValue = llvm.ConstIntToPtr(llvm.ConstPtrToInt(llvmValue, mem.r.uintptrType), llvmType) - } else { - llvmValue = llvm.ConstBitCast(llvmValue, llvmType) } } return llvmValue, nil } // 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 @@ -980,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: @@ -1048,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)) @@ -1076,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) } @@ -1120,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) } @@ -1177,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") } @@ -1256,7 +1243,7 @@ func (r *runner) getValue(llvmValue llvm.Value) value { // For details on this format, see src/runtime/gc_precise.go. func (r *runner) readObjectLayout(layoutValue value) (uint64, *big.Int) { pointerSize := layoutValue.len(r) - if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.i8ptrType) { + if checks && uint64(pointerSize) != r.targetData.TypeAllocSize(r.dataPtrType) { panic("inconsistent pointer size") } @@ -1265,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 @@ -1298,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 { @@ -1331,12 +1318,12 @@ func (r *runner) getLLVMTypeFromLayout(layoutValue value) llvm.Type { // Create the LLVM type. pointerSize := layoutValue.len(r) - pointerAlignment := r.targetData.PrefTypeAlignment(r.i8ptrType) + pointerAlignment := r.targetData.PrefTypeAlignment(r.dataPtrType) var fields []llvm.Type for i := 0; i < int(objectSizeWords); { if bitmap.Bit(i) != 0 { // Pointer field. - fields = append(fields, r.i8ptrType) + fields = append(fields, r.dataPtrType) i += int(pointerSize / uint32(pointerAlignment)) } else { // Byte/word field. diff --git a/interp/testdata/basic.ll b/interp/testdata/basic.ll index 6d1d010f49..0275dcbb6c 100644 --- a/interp/testdata/basic.ll +++ b/interp/testdata/basic.ll @@ -10,6 +10,8 @@ target triple = "x86_64--linux" @main.exposedValue1 = global i16 0 @main.exposedValue2 = global i16 0 @main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer +@main.gepArray = global [8 x i8] zeroinitializer +@main.negativeGEP = global ptr null declare void @runtime.printint64(i64) unnamed_addr @@ -88,6 +90,11 @@ entry: %agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0 store {i8, i32, {float, {i64, i16}}} %agg2, ptr @main.insertedValue + ; negative GEP instruction + %ngep1 = getelementptr [8 x i8], ptr @main.negativeGEP, i32 0, i32 5 + %ngep2 = getelementptr [8 x i8], ptr %ngep1, i32 0, i32 -3 + store ptr %ngep2, ptr @main.negativeGEP + ret void } diff --git a/interp/testdata/basic.out.ll b/interp/testdata/basic.out.ll index 342d4965f7..2685850eb2 100644 --- a/interp/testdata/basic.out.ll +++ b/interp/testdata/basic.out.ll @@ -9,6 +9,8 @@ target triple = "x86_64--linux" @main.exposedValue1 = global i16 0 @main.exposedValue2 = local_unnamed_addr global i16 0 @main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer +@main.gepArray = local_unnamed_addr global [8 x i8] zeroinitializer +@main.negativeGEP = global ptr getelementptr inbounds (i8, ptr @main.negativeGEP, i64 2) declare void @runtime.printint64(i64) unnamed_addr 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/binaryen b/lib/binaryen index 96f7acf09a..11dba9b1c2 160000 --- a/lib/binaryen +++ b/lib/binaryen @@ -1 +1 @@ -Subproject commit 96f7acf09aae1ec6e8bc573dfa8f309c4f892a40 +Subproject commit 11dba9b1c2ad988500b329727f39f4d8786918c5 diff --git a/lib/cmsis-svd b/lib/cmsis-svd index df75ff974c..05a9562ec5 160000 --- a/lib/cmsis-svd +++ b/lib/cmsis-svd @@ -1 +1 @@ -Subproject commit df75ff974c76a911fc2815e29807f5ecaae06fc2 +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/lib/wasi-libc b/lib/wasi-libc index 30094b6ed0..1dfe5c302d 160000 --- a/lib/wasi-libc +++ b/lib/wasi-libc @@ -1 +1 @@ -Subproject commit 30094b6ed05f19cee102115215863d185f2db4f0 +Subproject commit 1dfe5c302d1c5ab621f7abf04620fae92700fd22 diff --git a/loader/goroot.go b/loader/goroot.go index c1479b642f..00a7124d80 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -18,7 +18,6 @@ import ( "errors" "io" "io/fs" - "io/ioutil" "os" "os/exec" "path" @@ -157,7 +156,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) // Add files from TinyGo. tinygoDir := filepath.Join(tinygoSrc, dir) - tinygoEntries, err := ioutil.ReadDir(tinygoDir) + tinygoEntries, err := os.ReadDir(tinygoDir) if err != nil { return nil, err } @@ -177,7 +176,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) // Add all directories from $GOROOT that are not part of the TinyGo // overrides. goDir := filepath.Join(goSrc, dir) - goEntries, err := ioutil.ReadDir(goDir) + goEntries, err := os.ReadDir(goDir) if err != nil { return nil, err } @@ -215,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 } } @@ -230,23 +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, - "device/": false, - "examples/": false, - "internal/": true, - "internal/bytealg/": false, - "internal/fuzz/": false, - "internal/reflectlite/": false, - "internal/task/": false, - "machine/": false, - "net/": true, - "os/": true, - "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 { @@ -257,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/list.go b/loader/list.go index 16b674c74a..4ab887ab76 100644 --- a/loader/list.go +++ b/loader/list.go @@ -22,12 +22,8 @@ func List(config *compileopts.Config, extraArgs, pkgs []string) (*exec.Cmd, erro args = append(args, "-tags", strings.Join(config.BuildTags(), " ")) } args = append(args, pkgs...) - cgoEnabled := "0" - if config.CgoEnabled() { - cgoEnabled = "1" - } cmd := exec.Command(filepath.Join(goenv.Get("GOROOT"), "bin", "go"), args...) - cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled) + cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED=1") if config.Options.Directory != "" { cmd.Dir = config.Options.Directory } diff --git a/loader/loader.go b/loader/loader.go index ca0d463554..e935a9de3a 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -27,13 +27,14 @@ import ( "github.com/tinygo-org/tinygo/goenv" ) +var initFileVersions = func(info *types.Info) {} + // Program holds all packages and some metadata about the program as a whole. type Program struct { - config *compileopts.Config - clangHeaders string - typeChecker types.Config - goroot string // synthetic GOROOT - workingDir string + config *compileopts.Config + typeChecker types.Config + goroot string // synthetic GOROOT + workingDir string Packages map[string]*Package sorted []*Package @@ -103,7 +104,7 @@ type EmbedFile struct { // Load loads the given package with all dependencies (including the runtime // package). Call .Parse() afterwards to parse all Go files (including CGo // processing, if necessary). -func Load(config *compileopts.Config, inputPkg string, clangHeaders string, typeChecker types.Config) (*Program, error) { +func Load(config *compileopts.Config, inputPkg string, typeChecker types.Config) (*Program, error) { goroot, err := GetCachedGoroot(config) if err != nil { return nil, err @@ -118,17 +119,16 @@ func Load(config *compileopts.Config, inputPkg string, clangHeaders string, type } } p := &Program{ - config: config, - clangHeaders: clangHeaders, - typeChecker: typeChecker, - goroot: goroot, - workingDir: wd, - Packages: make(map[string]*Package), - fset: token.NewFileSet(), + config: config, + typeChecker: typeChecker, + goroot: goroot, + workingDir: wd, + Packages: make(map[string]*Package), + fset: token.NewFileSet(), } // 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, clangHeaders string, type // 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, clangHeaders string, type 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, clangHeaders string, type 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, clangHeaders string, type 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, clangHeaders string, type 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. @@ -380,13 +395,35 @@ func (p *Package) Check() error { typeErrors = append(typeErrors, err) } checker.Importer = p + if p.Module.GoVersion != "" { + // Setting the Go version for a module makes sure the type checker + // errors out on language features not supported in that particular + // version. + checker.GoVersion = "go" + p.Module.GoVersion + } else { + // Version is not known, so use the currently installed Go version. + // This is needed for `tinygo run` for example. + // Normally we'd use goenv.GorootVersionString(), but for compatibility + // with Go 1.20 and below we need a version in the form of "go1.12" (no + // patch version). + major, minor, err := goenv.GetGorootVersion() + if err != nil { + return err + } + checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor) + } + initFileVersions(&p.info) // Do typechecking of the package. 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" } @@ -395,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 @@ -412,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) @@ -438,9 +483,9 @@ func (p *Package) parseFiles() ([]*ast.File, error) { // to call cgo.Process in that case as it will only cause issues. if len(p.CgoFiles) != 0 && len(files) != 0 { var initialCFlags []string - initialCFlags = append(initialCFlags, p.program.config.CFlags()...) + 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, p.program.clangHeaders) + 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 { @@ -449,7 +494,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) { if errs != nil { fileErrs = append(fileErrs, errs...) } - files = append(files, generated) + files = append(files, generated...) p.program.LDFlags = append(p.program.LDFlags, ldflags...) } diff --git a/loader/loader_go122.go b/loader/loader_go122.go new file mode 100644 index 0000000000..24d0606433 --- /dev/null +++ b/loader/loader_go122.go @@ -0,0 +1,17 @@ +//go:build go1.22 + +// types.Info.FileVersions was added in Go 1.22, so we can only initialize it +// when built with Go 1.22. + +package loader + +import ( + "go/ast" + "go/types" +) + +func init() { + initFileVersions = func(info *types.Info) { + info.FileVersions = make(map[*ast.File]string) + } +} diff --git a/main.go b/main.go index 0a8a652b74..d4562fc4f6 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,7 @@ import ( "errors" "flag" "fmt" - "go/scanner" - "go/types" "io" - "io/ioutil" "os" "os/exec" "os/signal" @@ -31,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" @@ -286,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() @@ -339,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 @@ -349,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()) @@ -357,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 @@ -367,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 } @@ -533,7 +516,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error { return fmt.Errorf("unknown flash method: %s", flashMethod) } if options.Monitor { - return Monitor(result.Executable, "", options) + return Monitor(result.Executable, "", config) } return nil } @@ -783,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 @@ -818,23 +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. - args = append(args, "--dir=.") - for _, v := range environmentVars { - args = append(args, "--env", v) - } - if len(cmdArgs) != 0 { - // mark end of wasmtime arguments and start of program ones: -- - 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([]string(nil), 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,119 +1231,178 @@ func getBMPPorts() (gdbPort, uartPort string, err error) { } } -func usage(command string) { - version := goenv.Version - if strings.HasSuffix(version, "-dev") && goenv.GitSha1 != "" { - version += "-" + goenv.GitSha1 - } +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` +) - switch command { - default: - fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") - fmt.Fprintln(os.Stderr, "version:", 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, " 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") +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) } } @@ -1359,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 @@ -1415,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") @@ -1464,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") @@ -1484,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) @@ -1519,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, @@ -1547,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()) @@ -1583,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" { @@ -1692,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: @@ -1717,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 { @@ -1737,35 +1812,33 @@ func main() { os.Exit(1) } case "monitor": - err := Monitor("", *port, options) + config, err := builder.NewConfig(options) + handleCompilerError(err) + err = Monitor("", *port, config) + handleCompilerError(err) + case "ports": + serialPortInfo, err := ListSerialPorts() handleCompilerError(err) + if len(serialPortInfo) == 0 { + fmt.Println("No serial ports found.") + } + fmt.Printf("%-20s %-9s %s\n", "Port", "ID", "Boards") + for _, s := range serialPortInfo { + fmt.Printf("%-20s %4s:%4s %s\n", s.Name, s.VID, s.PID, s.Target) + } case "targets": - dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets") - entries, err := ioutil.ReadDir(dir) + specs, err := compileopts.GetTargetSpecs() if err != nil { fmt.Fprintln(os.Stderr, "could not list targets:", err) os.Exit(1) return } - for _, entry := range entries { - if !entry.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") { - // Only inspect JSON files. - continue - } - path := filepath.Join(dir, entry.Name()) - spec, err := compileopts.LoadTarget(&compileopts.Options{Target: path}) - if err != nil { - fmt.Fprintln(os.Stderr, "could not list target:", err) - os.Exit(1) - return - } - if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" { - // This doesn't look like a regular target file, but rather like - // a parent target (such as targets/cortex-m.json). - continue - } - name := entry.Name() - name = name[:len(name)-5] + names := []string{} + for key := range specs { + names = append(names, key) + } + sort.Strings(names) + for _, name := range names { fmt.Println(name) } case "info": @@ -1799,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"` @@ -1809,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(), @@ -1874,11 +1949,7 @@ func main() { if s, err := goenv.GorootVersionString(); err == nil { goversion = s } - version := goenv.Version - if strings.HasSuffix(goenv.Version, "-dev") && goenv.GitSha1 != "" { - version += "-" + goenv.GitSha1 - } - fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version) + fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", goenv.Version(), runtime.GOOS, runtime.GOARCH, goversion, llvm.Version) case "env": if flag.NArg() == 0 { // Show all environment variables. @@ -2011,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 ae7aed564d..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", } @@ -69,8 +76,10 @@ func TestBuild(t *testing.T) { "json.go", "map.go", "math.go", + "oldgo/", "print.go", "reflect.go", + "signal.go", "slice.go", "sort.go", "stdlib.go", @@ -90,6 +99,12 @@ func TestBuild(t *testing.T) { if minor >= 21 { tests = append(tests, "go1.21.go") } + 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), @@ -167,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) }) } } @@ -186,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") { @@ -197,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. @@ -223,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() @@ -246,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) @@ -288,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, @@ -316,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 } @@ -332,13 +405,12 @@ 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" - } - expected, err := os.ReadFile(txtpath) - if err != nil { - t.Fatal("could not read expected output file:", err) + expectedOutputPath = path + "out.txt" + options.Directory = path + pkgName = "." } config, err := builder.NewConfig(&options) @@ -348,19 +420,23 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c // Build the test binary. stdout := &bytes.Buffer{} - _, err = buildAndRun("./"+path, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { + _, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { 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) @@ -375,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') @@ -397,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() @@ -425,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 { @@ -597,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 9bef531f9a..46f3de927c 100644 --- a/monitor.go +++ b/monitor.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "debug/dwarf" "debug/elf" "debug/macho" @@ -9,57 +10,167 @@ import ( "fmt" "go/token" "io" + "net" "os" "os/signal" "regexp" "strconv" + "strings" "time" "github.com/mattn/go-tty" - "github.com/tinygo-org/tinygo/builder" "github.com/tinygo-org/tinygo/compileopts" + "go.bug.st/serial" + "go.bug.st/serial/enumerator" ) // Monitor connects to the given port and reads/writes the serial port. -func Monitor(executable, port string, options *compileopts.Options) error { - config, err := builder.NewConfig(options) - if err != nil { - return err - } +func Monitor(executable, port string, config *compileopts.Config) error { + const timeout = time.Second * 3 + var exit func() // function to be called before exiting + var serialConn io.ReadWriter + + if config.Options.Serial == "rtt" { + // Use the RTT interface, which is documented (in part) here: + // https://wiki.segger.com/RTT - wait := 300 - for i := 0; i <= wait; i++ { - port, err = getDefaultPort(port, config.Target.SerialPort) + // Try to find the "machine.rttSerialInstance" symbol, which is the RTT + // control block. + file, err := elf.Open(executable) if err != nil { - if i < wait { - time.Sleep(10 * time.Millisecond) - continue + return fmt.Errorf("could not open ELF file to determine RTT control block: %w", err) + } + defer file.Close() + symbols, err := file.Symbols() + if err != nil { + return fmt.Errorf("could not read ELF symbol table to determine RTT control block: %w", err) + } + var address uint64 + for _, symbol := range symbols { + if symbol.Name == "machine.rttSerialInstance" { + address = symbol.Value + break } + } + if address == 0 { + return fmt.Errorf("could not find RTT control block in ELF file") + } + + // Start an openocd process in the background. + args, err := config.OpenOCDConfiguration() + if err != nil { return err } - break - } + args = append(args, + "-c", fmt.Sprintf("rtt setup 0x%x 16 \"SEGGER RTT\"", address), + "-c", "init", + "-c", "rtt server start 0 0") + cmd := executeCommand(config.Options, "openocd", args...) + stderr, err := cmd.StderrPipe() + if err != nil { + return err + } + cmd.Stdout = os.Stdout + err = cmd.Start() + if err != nil { + return err + } + defer cmd.Process.Kill() + exit = func() { + // Make sure the openocd process is terminated at exit. + // This does not happen through the defer above when exiting through + // os.Exit. + cmd.Process.Kill() + } - br := options.BaudRate - if br <= 0 { - br = 115200 - } + // Read the stderr, which logs various important messages we need. + r := bufio.NewReader(stderr) + var telnet net.Conn + var timeoutAt time.Time + for { + // Read the next line from the openocd process. + lineBytes, err := r.ReadBytes('\n') + if err != nil { + return err + } + line := string(lineBytes) - wait = 300 - var p serial.Port - for i := 0; i <= wait; i++ { - p, err = serial.Open(port, &serial.Mode{BaudRate: br}) - if err != nil { - if i < wait { - time.Sleep(10 * time.Millisecond) - continue + if line == "Info : rtt: No control block found\n" { + // Message that is sent back when OpenOCD can't find the control + // block after a 'rtt start' message. + if time.Now().After(timeoutAt) { + return fmt.Errorf("RTT timeout (could not locate RTT control block at 0x%08x)", address) + } + time.Sleep(time.Millisecond * 100) + telnet.Write([]byte("rtt start\r\n")) + } else if strings.HasPrefix(line, "Info : Listening on port") { + // We need two different ports for controlling OpenOCD + // (typically port 4444) and the RTT channel 0 socket (arbitrary + // port). + var port int + var protocol string + fmt.Sscanf(line, "Info : Listening on port %d for %s connections\n", &port, &protocol) + if protocol == "telnet" && telnet == nil { + // Connect to the "telnet" command line interface. + telnet, err = net.Dial("tcp4", fmt.Sprintf("localhost:%d", port)) + if err != nil { + return err + } + // Tell OpenOCD to start scanning for the RTT control block. + telnet.Write([]byte("rtt start\r\n")) + // Also make sure we will time out if the control block just + // can't be found. + timeoutAt = time.Now().Add(timeout) + } else if protocol == "rtt" { + // Connect to the RTT channel, for both stdin and stdout. + conn, err := net.Dial("tcp4", fmt.Sprintf("localhost:%d", port)) + if err != nil { + return err + } + serialConn = conn + } + } else if strings.HasPrefix(line, "Info : rtt: Control block found at") { + // Connection established! + break } - return err } - break + } else { // -serial=uart or -serial=usb + var err error + wait := 300 + for i := 0; i <= wait; i++ { + port, err = getDefaultPort(port, config.Target.SerialPort) + if err != nil { + if i < wait { + time.Sleep(10 * time.Millisecond) + continue + } + return err + } + break + } + + br := config.Options.BaudRate + if br <= 0 { + br = 115200 + } + + wait = 300 + var p serial.Port + for i := 0; i <= wait; i++ { + p, err = serial.Open(port, &serial.Mode{BaudRate: br}) + if err != nil { + if i < wait { + time.Sleep(10 * time.Millisecond) + continue + } + return err + } + serialConn = p + break + } + defer p.Close() } - defer p.Close() tty, err := tty.Open() if err != nil { @@ -74,6 +185,9 @@ func Monitor(executable, port string, options *compileopts.Options) error { go func() { <-sig tty.Close() + if exit != nil { + exit() + } os.Exit(0) }() @@ -83,31 +197,14 @@ func Monitor(executable, port string, options *compileopts.Options) error { go func() { buf := make([]byte, 100*1024) - var line []byte + writer := newOutputWriter(os.Stdout, executable) for { - n, err := p.Read(buf) + 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]) } }() @@ -121,13 +218,66 @@ func Monitor(executable, port string, options *compileopts.Options) error { if r == 0 { continue } - p.Write([]byte(string(r))) + serialConn.Write([]byte(string(r))) } }() return <-errCh } +// SerialPortInfo is a structure that holds information about the port and its +// associated TargetSpec. +type SerialPortInfo struct { + Name string + IsUSB bool + VID string + PID string + Target string + Spec *compileopts.TargetSpec +} + +// ListSerialPort returns serial port information and any detected TinyGo +// target. +func ListSerialPorts() ([]SerialPortInfo, error) { + maps, err := compileopts.GetTargetSpecs() + if err != nil { + return nil, err + } + + portsList, err := enumerator.GetDetailedPortsList() + if err != nil { + return nil, err + } + + serialPortInfo := []SerialPortInfo{} + for _, p := range portsList { + info := SerialPortInfo{ + Name: p.Name, + IsUSB: p.IsUSB, + VID: p.VID, + PID: p.PID, + } + vid := strings.ToLower(p.VID) + pid := strings.ToLower(p.PID) + for k, v := range maps { + usbInterfaces := v.SerialPort + for _, s := range usbInterfaces { + parts := strings.Split(s, ":") + if len(parts) != 2 { + continue + } + if vid == strings.ToLower(parts[0]) && pid == strings.ToLower(parts[1]) { + info.Target = k + info.Spec = v + } + } + } + serialPortInfo = append(serialPortInfo, info) + } + + return serialPortInfo, nil +} + var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `) // Extract the address from the "panic: runtime error at" message. @@ -233,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 a220c2f985..5711f23eb0 100644 --- a/src/crypto/rand/rand_baremetal.go +++ b/src/crypto/rand/rand_baremetal.go @@ -1,4 +1,7 @@ -//go:build nrf || stm32 || (sam && atsamd51) || (sam && atsame5x) +//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. package rand 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 new file mode 100644 index 0000000000..21a1a20f50 --- /dev/null +++ b/src/crypto/tls/common.go @@ -0,0 +1,491 @@ +// TINYGO: The following is copied and modified from Go 1.19.3 official implementation. + +// 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 tls + +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. +// +// In TLS 1.3, this type is called NamedGroup, but at this time this library +// 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 +// TLS Client Authentication. +type ClientAuthType int + +// ClientSessionCache is a cache of ClientSessionState objects that can be used +// by a client to resume a TLS session with a given server. ClientSessionCache +// implementations should expect to be called concurrently from different +// goroutines. Up to TLS 1.2, only ticket-based resumption is supported, not +// SessionID-based resumption. In TLS 1.3 they were merged into PSK modes, which +// are supported via this interface. +type ClientSessionCache interface { + // Get searches for a ClientSessionState associated with the given key. + // On return, ok is true if one was found. + Get(sessionKey string) (session *ClientSessionState, ok bool) + + // Put adds the ClientSessionState to the cache with the given key. It might + // get called multiple times in a connection if a TLS 1.3 server provides + // more than one session ticket. If called with a nil *ClientSessionState, + // it should remove the cache entry. + Put(sessionKey string, cs *ClientSessionState) +} + +//go:generate stringer -type=SignatureScheme,CurveID,ClientAuthType -output=common_string.go + +// SignatureScheme identifies a signature algorithm supported by TLS. See +// RFC 8446, Section 4.2.3. +type SignatureScheme uint16 + +// ClientHelloInfo contains information from a ClientHello message in order to +// guide application logic in the GetCertificate and GetConfigForClient callbacks. +type ClientHelloInfo struct { + // CipherSuites lists the CipherSuites supported by the client (e.g. + // TLS_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256). + CipherSuites []uint16 + + // ServerName indicates the name of the server requested by the client + // in order to support virtual hosting. ServerName is only set if the + // client is using SNI (see RFC 4366, Section 3.1). + ServerName string + + // SupportedCurves lists the elliptic curves supported by the client. + // SupportedCurves is set only if the Supported Elliptic Curves + // Extension is being used (see RFC 4492, Section 5.1.1). + SupportedCurves []CurveID + + // SupportedPoints lists the point formats supported by the client. + // SupportedPoints is set only if the Supported Point Formats Extension + // is being used (see RFC 4492, Section 5.1.2). + SupportedPoints []uint8 + + // SignatureSchemes lists the signature and hash schemes that the client + // is willing to verify. SignatureSchemes is set only if the Signature + // Algorithms Extension is being used (see RFC 5246, Section 7.4.1.4.1). + SignatureSchemes []SignatureScheme + + // SupportedProtos lists the application protocols supported by the client. + // SupportedProtos is set only if the Application-Layer Protocol + // Negotiation Extension is being used (see RFC 7301, Section 3.1). + // + // Servers can select a protocol by setting Config.NextProtos in a + // GetConfigForClient return value. + SupportedProtos []string + + // SupportedVersions lists the TLS versions supported by the client. + // For TLS versions less than 1.3, this is extrapolated from the max + // version advertised by the client, so values other than the greatest + // might be rejected if used. + SupportedVersions []uint16 + + // Conn is the underlying net.Conn for the connection. Do not read + // from, or write to, this connection; that will cause the TLS + // connection to fail. + Conn net.Conn + + // config is embedded by the GetCertificate or GetConfigForClient caller, + // for use with SupportsCertificate. + config *Config + + // ctx is the context of the handshake that is in progress. + ctx context.Context +} + +// CertificateRequestInfo contains information from a server's +// CertificateRequest message, which is used to demand a certificate and proof +// of control from a client. +type CertificateRequestInfo struct { + // AcceptableCAs contains zero or more, DER-encoded, X.501 + // Distinguished Names. These are the names of root or intermediate CAs + // that the server wishes the returned certificate to be signed by. An + // empty slice indicates that the server has no preference. + AcceptableCAs [][]byte + + // SignatureSchemes lists the signature schemes that the server is + // willing to verify. + SignatureSchemes []SignatureScheme + + // Version is the TLS version that was negotiated for this connection. + Version uint16 + + // ctx is the context of the handshake that is in progress. + ctx context.Context +} + +// RenegotiationSupport enumerates the different levels of support for TLS +// renegotiation. TLS renegotiation is the act of performing subsequent +// handshakes on a connection after the first. This significantly complicates +// the state machine and has been the source of numerous, subtle security +// issues. Initiating a renegotiation is not supported, but support for +// accepting renegotiation requests may be enabled. +// +// Even when enabled, the server may not change its identity between handshakes +// (i.e. the leaf certificate must be the same). Additionally, concurrent +// handshake and application data flow is not permitted so renegotiation can +// only be used with protocols that synchronise with the renegotiation, such as +// HTTPS. +// +// Renegotiation is not defined in TLS 1.3. +type RenegotiationSupport int + +// A Config structure is used to configure a TLS client or server. +// After one has been passed to a TLS function it must not be +// modified. A Config may be reused; the tls package will also not +// modify it. +type Config struct { + // Rand provides the source of entropy for nonces and RSA blinding. + // If Rand is nil, TLS uses the cryptographic random reader in package + // crypto/rand. + // The Reader must be safe for use by multiple goroutines. + Rand io.Reader + + // Time returns the current time as the number of seconds since the epoch. + // If Time is nil, TLS uses time.Now. + Time func() time.Time + + // Certificates contains one or more certificate chains to present to the + // other side of the connection. The first certificate compatible with the + // peer's requirements is selected automatically. + // + // Server configurations must set one of Certificates, GetCertificate or + // GetConfigForClient. Clients doing client-authentication may set either + // Certificates or GetClientCertificate. + // + // Note: if there are multiple Certificates, and they don't have the + // optional field Leaf set, certificate selection will incur a significant + // per-handshake performance cost. + Certificates []Certificate + + // NameToCertificate maps from a certificate name to an element of + // Certificates. Note that a certificate name can be of the form + // '*.example.com' and so doesn't have to be a domain name as such. + // + // Deprecated: NameToCertificate only allows associating a single + // certificate with a given name. Leave this field nil to let the library + // select the first compatible chain from Certificates. + NameToCertificate map[string]*Certificate + + // GetCertificate returns a Certificate based on the given + // ClientHelloInfo. It will only be called if the client supplies SNI + // information or if Certificates is empty. + // + // If GetCertificate is nil or returns nil, then the certificate is + // retrieved from NameToCertificate. If NameToCertificate is nil, the + // best element of Certificates will be used. + // + // Once a Certificate is returned it should not be modified. + GetCertificate func(*ClientHelloInfo) (*Certificate, error) + + // GetClientCertificate, if not nil, is called when a server requests a + // certificate from a client. If set, the contents of Certificates will + // be ignored. + // + // If GetClientCertificate returns an error, the handshake will be + // aborted and that error will be returned. Otherwise + // GetClientCertificate must return a non-nil Certificate. If + // Certificate.Certificate is empty then no certificate will be sent to + // the server. If this is unacceptable to the server then it may abort + // the handshake. + // + // GetClientCertificate may be called multiple times for the same + // connection if renegotiation occurs or if TLS 1.3 is in use. + // + // Once a Certificate is returned it should not be modified. + GetClientCertificate func(*CertificateRequestInfo) (*Certificate, error) + + // GetConfigForClient, if not nil, is called after a ClientHello is + // received from a client. It may return a non-nil Config in order to + // change the Config that will be used to handle this connection. If + // the returned Config is nil, the original Config will be used. The + // Config returned by this callback may not be subsequently modified. + // + // If GetConfigForClient is nil, the Config passed to Server() will be + // used for all connections. + // + // If SessionTicketKey was explicitly set on the returned Config, or if + // SetSessionTicketKeys was called on the returned Config, those keys will + // be used. Otherwise, the original Config keys will be used (and possibly + // rotated if they are automatically managed). + GetConfigForClient func(*ClientHelloInfo) (*Config, error) + + // VerifyPeerCertificate, if not nil, is called after normal + // certificate verification by either a TLS client or server. It + // receives the raw ASN.1 certificates provided by the peer and also + // any verified chains that normal processing found. If it returns a + // non-nil error, the handshake is aborted and that error results. + // + // If normal verification fails then the handshake will abort before + // considering this callback. If normal verification is disabled (on the + // client when InsecureSkipVerify is set, or on a server when ClientAuth is + // RequestClientCert or RequireAnyClientCert), then this callback will be + // considered but the verifiedChains argument will always be nil. When + // ClientAuth is NoClientCert, this callback is not called on the server. + // rawCerts may be empty on the server if ClientAuth is RequestClientCert or + // VerifyClientCertIfGiven. + // + // This callback is not invoked on resumed connections, as certificates are + // not re-verified on resumption. + // + // verifiedChains and its contents should not be modified. + VerifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error + + // VerifyConnection, if not nil, is called after normal certificate + // verification and after VerifyPeerCertificate by either a TLS client + // or server. If it returns a non-nil error, the handshake is aborted + // and that error results. + // + // If normal verification fails then the handshake will abort before + // considering this callback. This callback will run for all connections, + // including resumptions, regardless of InsecureSkipVerify or ClientAuth + // settings. + VerifyConnection func(ConnectionState) error + + // RootCAs defines the set of root certificate authorities + // that clients use when verifying server certificates. + // If RootCAs is nil, TLS uses the host's root CA set. + RootCAs *x509.CertPool + + // NextProtos is a list of supported application level protocols, in + // order of preference. If both peers support ALPN, the selected + // protocol will be one from this list, and the connection will fail + // if there is no mutually supported protocol. If NextProtos is empty + // or the peer doesn't support ALPN, the connection will succeed and + // ConnectionState.NegotiatedProtocol will be empty. + NextProtos []string + + // ServerName is used to verify the hostname on the returned + // certificates unless InsecureSkipVerify is given. It is also included + // in the client's handshake to support virtual hosting unless it is + // an IP address. + ServerName string + + // ClientAuth determines the server's policy for + // TLS Client Authentication. The default is NoClientCert. + ClientAuth ClientAuthType + + // ClientCAs defines the set of root certificate authorities + // that servers use if required to verify a client certificate + // by the policy in ClientAuth. + ClientCAs *x509.CertPool + + // InsecureSkipVerify controls whether a client verifies the server's + // certificate chain and host name. If InsecureSkipVerify is true, crypto/tls + // accepts any certificate presented by the server and any host name in that + // certificate. In this mode, TLS is susceptible to machine-in-the-middle + // attacks unless custom verification is used. This should be used only for + // testing or in combination with VerifyConnection or VerifyPeerCertificate. + InsecureSkipVerify bool + + // CipherSuites is a list of enabled TLS 1.0–1.2 cipher suites. The order of + // the list is ignored. Note that TLS 1.3 ciphersuites are not configurable. + // + // If CipherSuites is nil, a safe default list is used. The default cipher + // suites might change over time. + CipherSuites []uint16 + + // PreferServerCipherSuites is a legacy field and has no effect. + // + // It used to control whether the server would follow the client's or the + // server's preference. Servers now select the best mutually supported + // cipher suite based on logic that takes into account inferred client + // hardware, server hardware, and security. + // + // Deprecated: PreferServerCipherSuites is ignored. + PreferServerCipherSuites bool + + // SessionTicketsDisabled may be set to true to disable session ticket and + // PSK (resumption) support. Note that on clients, session ticket support is + // also disabled if ClientSessionCache is nil. + SessionTicketsDisabled bool + + // SessionTicketKey is used by TLS servers to provide session resumption. + // See RFC 5077 and the PSK mode of RFC 8446. If zero, it will be filled + // with random data before the first server handshake. + // + // Deprecated: if this field is left at zero, session ticket keys will be + // automatically rotated every day and dropped after seven days. For + // customizing the rotation schedule or synchronizing servers that are + // terminating connections for the same host, use SetSessionTicketKeys. + SessionTicketKey [32]byte + + // ClientSessionCache is a cache of ClientSessionState entries for TLS + // session resumption. It is only used by clients. + ClientSessionCache ClientSessionCache + + // UnwrapSession is called on the server to turn a ticket/identity + // previously produced by [WrapSession] into a usable session. + // + // UnwrapSession will usually either decrypt a session state in the ticket + // (for example with [Config.EncryptTicket]), or use the ticket as a handle + // to recover a previously stored state. It must use [ParseSessionState] to + // deserialize the session state. + // + // If UnwrapSession returns an error, the connection is terminated. If it + // returns (nil, nil), the session is ignored. crypto/tls may still choose + // not to resume the returned session. + UnwrapSession func(identity []byte, cs ConnectionState) (*SessionState, error) + + // WrapSession is called on the server to produce a session ticket/identity. + // + // WrapSession must serialize the session state with [SessionState.Bytes]. + // It may then encrypt the serialized state (for example with + // [Config.DecryptTicket]) and use it as the ticket, or store the state and + // return a handle for it. + // + // If WrapSession returns an error, the connection is terminated. + // + // Warning: the return value will be exposed on the wire and to clients in + // plaintext. The application is in charge of encrypting and authenticating + // it (and rotating keys) or returning high-entropy identifiers. Failing to + // do so correctly can compromise current, previous, and future connections + // depending on the protocol version. + WrapSession func(ConnectionState, *SessionState) ([]byte, error) + + // MinVersion contains the minimum TLS version that is acceptable. + // + // By default, TLS 1.2 is currently used as the minimum when acting as a + // client, and TLS 1.0 when acting as a server. TLS 1.0 is the minimum + // supported by this package, both as a client and as a server. + // + // The client-side default can temporarily be reverted to TLS 1.0 by + // including the value "x509sha1=1" in the GODEBUG environment variable. + // Note that this option will be removed in Go 1.19 (but it will still be + // possible to set this field to VersionTLS10 explicitly). + MinVersion uint16 + + // MaxVersion contains the maximum TLS version that is acceptable. + // + // By default, the maximum version supported by this package is used, + // which is currently TLS 1.3. + MaxVersion uint16 + + // CurvePreferences contains the elliptic curves that will be used in + // an ECDHE handshake, in preference order. If empty, the default will + // be used. The client will use the first preference as the type for + // its key share in TLS 1.3. This may change in the future. + CurvePreferences []CurveID + + // DynamicRecordSizingDisabled disables adaptive sizing of TLS records. + // When true, the largest possible TLS record size is always used. When + // false, the size of TLS records may be adjusted in an attempt to + // improve latency. + DynamicRecordSizingDisabled bool + + // Renegotiation controls what types of renegotiation are supported. + // The default, none, is correct for the vast majority of applications. + Renegotiation RenegotiationSupport + + // KeyLogWriter optionally specifies a destination for TLS master secrets + // in NSS key log format that can be used to allow external programs + // such as Wireshark to decrypt TLS connections. + // See https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format. + // Use of KeyLogWriter compromises security and should only be + // used for debugging. + KeyLogWriter io.Writer + + // mutex protects sessionTicketKeys and autoSessionTicketKeys. + mutex sync.RWMutex + // sessionTicketKeys contains zero or more ticket keys. If set, it means + // the keys were set with SessionTicketKey or SetSessionTicketKeys. The + // first key is used for new tickets and any subsequent keys can be used to + // decrypt old tickets. The slice contents are not protected by the mutex + // and are immutable. + sessionTicketKeys []ticketKey + // autoSessionTicketKeys is like sessionTicketKeys but is owned by the + // auto-rotation logic. See Config.ticketKeys. + autoSessionTicketKeys []ticketKey +} + +// ticketKey is the internal representation of a session ticket key. +type ticketKey struct { + aesKey [16]byte + hmacKey [16]byte + // created is the time at which this ticket key was created. See Config.ticketKeys. + created time.Time +} + +// A Certificate is a chain of one or more certificates, leaf first. +type Certificate struct { + Certificate [][]byte + // PrivateKey contains the private key corresponding to the public key in + // Leaf. This must implement crypto.Signer with an RSA, ECDSA or Ed25519 PublicKey. + // For a server up to TLS 1.2, it can also implement crypto.Decrypter with + // an RSA PublicKey. + PrivateKey crypto.PrivateKey + // SupportedSignatureAlgorithms is an optional list restricting what + // signature algorithms the PrivateKey can be used for. + SupportedSignatureAlgorithms []SignatureScheme + // OCSPStaple contains an optional OCSP response which will be served + // to clients that request it. + OCSPStaple []byte + // SignedCertificateTimestamps contains an optional list of Signed + // Certificate Timestamps which will be served to clients that request it. + SignedCertificateTimestamps [][]byte + // Leaf is the parsed form of the leaf certificate, which may be initialized + // using x509.ParseCertificate to reduce per-handshake processing. If nil, + // the leaf certificate will be parsed as needed. + Leaf *x509.Certificate +} diff --git a/src/crypto/tls/ticket.go b/src/crypto/tls/ticket.go new file mode 100644 index 0000000000..152efb7824 --- /dev/null +++ b/src/crypto/tls/ticket.go @@ -0,0 +1,16 @@ +// Copyright 2012 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 tls + +// A SessionState is a resumable session. +type SessionState struct { +} + +// ClientSessionState contains the state needed by a client to +// resume a previous TLS session. +type ClientSessionState struct { + ticket []byte + session *SessionState +} diff --git a/src/crypto/tls/tls.go b/src/crypto/tls/tls.go new file mode 100644 index 0000000000..6fdedc39fc --- /dev/null +++ b/src/crypto/tls/tls.go @@ -0,0 +1,120 @@ +// TINYGO: The following is copied and modified from Go 1.21.4 official implementation. + +// 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 tls partially implements TLS 1.2, as specified in RFC 5246, +// and TLS 1.3, as specified in RFC 8446. +package tls + +// BUG(agl): The crypto/tls package only implements some countermeasures +// against Lucky13 attacks on CBC-mode encryption, and only on SHA1 +// variants. See http://www.isg.rhul.ac.uk/tls/TLStiming.pdf and +// https://www.imperialviolet.org/2013/02/04/luckythirteen.html. + +import ( + "context" + "errors" + "fmt" + "net" +) + +// Client returns a new TLS client side connection +// using conn as the underlying transport. +// The config cannot be nil: users must set either ServerName or +// InsecureSkipVerify in the config. +func Client(conn net.Conn, config *Config) *net.TLSConn { + panic("tls.Client() not implemented") + return nil +} + +// A listener implements a network listener (net.Listener) for TLS connections. +type listener struct { + net.Listener + config *Config +} + +// NewListener creates a Listener which accepts connections from an inner +// Listener and wraps each connection with Server. +// The configuration config must be non-nil and must include +// at least one certificate or else set GetCertificate. +func NewListener(inner net.Listener, config *Config) net.Listener { + l := new(listener) + l.Listener = inner + l.config = config + return l +} + +// DialWithDialer connects to the given network address using dialer.Dial and +// then initiates a TLS handshake, returning the resulting TLS connection. Any +// timeout or deadline given in the dialer apply to connection and TLS +// handshake as a whole. +// +// DialWithDialer interprets a nil configuration as equivalent to the zero +// configuration; see the documentation of Config for the defaults. +// +// DialWithDialer uses context.Background internally; to specify the context, +// use Dialer.DialContext with NetDialer set to the desired dialer. +func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*net.TLSConn, error) { + switch network { + case "tcp", "tcp4": + default: + return nil, fmt.Errorf("Network %s not supported", network) + } + + return net.DialTLS(addr) +} + +// Dial connects to the given network address using net.Dial +// and then initiates a TLS handshake, returning the resulting +// TLS connection. +// Dial interprets a nil configuration as equivalent to +// the zero configuration; see the documentation of Config +// for the defaults. +func Dial(network, addr string, config *Config) (*net.TLSConn, error) { + return DialWithDialer(new(net.Dialer), network, addr, config) +} + +// Dialer dials TLS connections given a configuration and a Dialer for the +// underlying connection. +type Dialer struct { + // NetDialer is the optional dialer to use for the TLS connections' + // underlying TCP connections. + // A nil NetDialer is equivalent to the net.Dialer zero value. + NetDialer *net.Dialer + + // Config is the TLS configuration to use for new connections. + // A nil configuration is equivalent to the zero + // configuration; see the documentation of Config for the + // defaults. + Config *Config +} + +// DialContext connects to the given network address and initiates a TLS +// handshake, returning the resulting TLS connection. +// +// The provided Context must be non-nil. If the context expires before +// the connection is complete, an error is returned. Once successfully +// connected, any expiration of the context will not affect the +// connection. +// +// The returned Conn, if any, will always be of type *Conn. +func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + 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 +// of files. The files must contain PEM encoded data. The certificate file +// may contain intermediate certificates following the leaf certificate to +// form a certificate chain. On successful return, Certificate.Leaf will +// be nil because the parsed form of the certificate is not retained. +func LoadX509KeyPair(certFile, keyFile string) (Certificate, error) { + return Certificate{}, errors.New("tls:LoadX509KeyPair not implemented") +} 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/device-id/main.go b/src/examples/device-id/main.go new file mode 100644 index 0000000000..31e62baef6 --- /dev/null +++ b/src/examples/device-id/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "encoding/hex" + "machine" + "time" +) + +func main() { + time.Sleep(2 * time.Second) + + // For efficiency, it's best to get the device ID once and cache it + // (e.g. on RP2040 XIP flash and interrupts disabled for period of + // retrieving the hardware ID from ROM chip) + id := machine.DeviceID() + + for { + println("Device ID:", hex.EncodeToString(id)) + time.Sleep(1 * time.Second) + } +} 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 new file mode 100644 index 0000000000..ff2ec88f18 --- /dev/null +++ b/src/examples/hello-wasm-unknown/main.go @@ -0,0 +1,22 @@ +// this is intended to be used as wasm32-unknown-unknown module. +// to compile it, run: +// 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 +func echo(x int32) + +//go:export update +func update() { + x++ + echo(x) +} + +func main() { +} diff --git a/src/examples/hid-keyboard/main.go b/src/examples/hid-keyboard/main.go index a5aed9365b..dfa666d378 100644 --- a/src/examples/hid-keyboard/main.go +++ b/src/examples/hid-keyboard/main.go @@ -1,6 +1,6 @@ // to override the USB Manufacturer or Product names: // -// tinygo flash -target circuitplay-express -ldflags="-X main.usbManufacturer='TinyGopher Labs' -X main.usbProduct='GopherKeyboard'" examples/hid-keyboard +// tinygo flash -target circuitplay-express -ldflags="-X main.usbManufacturer='TinyGopher Labs' -X main.usbProduct='GopherKeyboard' -X main.usbSerial='XXXXX'" examples/hid-keyboard // // you can also override the VID/PID. however, only set this if you know what you are doing, // since changing it can make it difficult to reflash some devices. @@ -15,7 +15,7 @@ import ( ) var usbVID, usbPID string -var usbManufacturer, usbProduct string +var usbManufacturer, usbProduct, usbSerial string func main() { button := machine.BUTTON @@ -49,4 +49,8 @@ func init() { if usbProduct != "" { usb.Product = usbProduct } + + if usbSerial != "" { + usb.Serial = usbSerial + } } 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/examples/rtcinterrupt/rtcinterrupt.go b/src/examples/rtcinterrupt/rtcinterrupt.go index 7211e4c30b..83d30bcc68 100644 --- a/src/examples/rtcinterrupt/rtcinterrupt.go +++ b/src/examples/rtcinterrupt/rtcinterrupt.go @@ -24,7 +24,9 @@ func main() { // Schedule and enable recurring interrupt. // The callback function is executed in the context of an interrupt handler, - // so regular restructions for this sort of code apply: no blocking, no memory allocation, etc. + // so regular restrictions for this sort of code apply: no blocking, no memory allocation, etc. + // Please check the online documentation for the details about interrupts: + // https://tinygo.org/docs/concepts/compiler-internals/interrupts/ delay := time.Minute + 12*time.Second machine.RTC.SetInterrupt(uint32(delay.Seconds()), true, func() { println("Peekaboo!") }) diff --git a/src/examples/wasm/Makefile b/src/examples/wasm/GNUmakefile similarity index 100% rename from src/examples/wasm/Makefile rename to src/examples/wasm/GNUmakefile 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 72be9ac587..33ece2bbae 100644 --- a/src/internal/bytealg/bytealg.go +++ b/src/internal/bytealg/bytealg.go @@ -1,5 +1,14 @@ package bytealg +// Some code in this file has been copied from the Go source code, and has +// copyright of their original authors: +// +// Copyright 2020 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 is indicated specifically in the file. + const ( // Index can search any valid length of string. @@ -33,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. @@ -48,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 { @@ -125,10 +159,6 @@ func IndexString(str, sub string) int { return -1 } -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - // The following code has been copied from the Go 1.15 release tree. // PrimeRK is the prime base used in Rabin-Karp algorithm. @@ -136,6 +166,8 @@ const PrimeRK = 16777619 // HashStrBytes returns the hash and the appropriate multiplicative // factor for use in Rabin-Karp algorithm. +// +// This function was removed in Go 1.22. func HashStrBytes(sep []byte) (uint32, uint32) { hash := uint32(0) for i := 0; i < len(sep); i++ { @@ -153,7 +185,9 @@ func HashStrBytes(sep []byte) (uint32, uint32) { // HashStr returns the hash and the appropriate multiplicative // factor for use in Rabin-Karp algorithm. -func HashStr(sep string) (uint32, uint32) { +// +// This function was removed in Go 1.22. +func HashStr[T string | []byte](sep T) (uint32, uint32) { hash := uint32(0) for i := 0; i < len(sep); i++ { hash = hash*PrimeRK + uint32(sep[i]) @@ -170,6 +204,8 @@ func HashStr(sep string) (uint32, uint32) { // HashStrRevBytes returns the hash of the reverse of sep and the // appropriate multiplicative factor for use in Rabin-Karp algorithm. +// +// This function was removed in Go 1.22. func HashStrRevBytes(sep []byte) (uint32, uint32) { hash := uint32(0) for i := len(sep) - 1; i >= 0; i-- { @@ -187,7 +223,9 @@ func HashStrRevBytes(sep []byte) (uint32, uint32) { // HashStrRev returns the hash of the reverse of sep and the // appropriate multiplicative factor for use in Rabin-Karp algorithm. -func HashStrRev(sep string) (uint32, uint32) { +// +// Copied from the Go 1.22rc1 source tree. +func HashStrRev[T string | []byte](sep T) (uint32, uint32) { hash := uint32(0) for i := len(sep) - 1; i >= 0; i-- { hash = hash*PrimeRK + uint32(sep[i]) @@ -203,7 +241,9 @@ func HashStrRev(sep string) (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 { // Rabin-Karp search hashsep, pow := HashStrBytes(sep) @@ -228,16 +268,18 @@ func IndexRabinKarpBytes(s, sep []byte) int { } // IndexRabinKarp uses the Rabin-Karp search algorithm to return the index of the -// first occurence of substr in s, or -1 if not present. -func IndexRabinKarp(s, substr string) int { +// first occurrence of sep in s, or -1 if not present. +// +// Copied from the Go 1.22rc1 source tree. +func IndexRabinKarp[T string | []byte](s, sep T) int { // Rabin-Karp search - hashss, pow := HashStr(substr) - n := len(substr) + hashss, pow := HashStr(sep) + n := len(sep) var h uint32 for i := 0; i < n; i++ { h = h*PrimeRK + uint32(s[i]) } - if h == hashss && s[:n] == substr { + if h == hashss && string(s[:n]) == string(sep) { return 0 } for i := n; i < len(s); { @@ -245,7 +287,7 @@ func IndexRabinKarp(s, substr string) int { h += uint32(s[i]) h -= pow * uint32(s[i-n]) i++ - if h == hashss && s[i-n:i] == substr { + if h == hashss && string(s[i-n:i]) == string(sep) { return i - n } } @@ -261,3 +303,50 @@ func MakeNoZero(n int) []byte { // malloc function implemented in the runtime). return make([]byte, n) } + +// Copied from the Go 1.22rc1 source tree. +func LastIndexByte(s []byte, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i + } + } + return -1 +} + +// Copied from the Go 1.22rc1 source tree. +func LastIndexByteString(s string, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i + } + } + return -1 +} + +// LastIndexRabinKarp uses the Rabin-Karp search algorithm to return the last index of the +// occurrence of sep in s, or -1 if not present. +// +// Copied from the Go 1.22rc1 source tree. +func LastIndexRabinKarp[T string | []byte](s, sep T) int { + // Rabin-Karp search from the end of the string + hashss, pow := HashStrRev(sep) + n := len(sep) + last := len(s) - n + var h uint32 + for i := len(s) - 1; i >= last; i-- { + h = h*PrimeRK + uint32(s[i]) + } + if h == hashss && string(s[last:]) == string(sep) { + return last + } + for i := last - 1; i >= 0; i-- { + h *= PrimeRK + h += uint32(s[i]) + h -= pow * uint32(s[i+n]) + if h == hashss && string(s[i:i+n]) == string(sep) { + return i + } + } + return -1 +} 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_arm.S b/src/internal/task/task_stack_arm.S index 3a1e3f0aea..81a5aa8a09 100644 --- a/src/internal/task/task_stack_arm.S +++ b/src/internal/task/task_stack_arm.S @@ -1,3 +1,5 @@ +//go:build tinygo + // Only generate .debug_frame, don't generate .eh_frame. .cfi_sections .debug_frame diff --git a/src/internal/task/task_stack_avr.S b/src/internal/task/task_stack_avr.S index 2cba528f2d..d8aed8b96b 100644 --- a/src/internal/task/task_stack_avr.S +++ b/src/internal/task/task_stack_avr.S @@ -1,3 +1,5 @@ +//go:build tinygo + .section .bss.tinygo_systemStack .global tinygo_systemStack .type tinygo_systemStack, %object @@ -26,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_cortexm.S b/src/internal/task/task_stack_cortexm.S index fe1f44e05f..dfe713552d 100644 --- a/src/internal/task/task_stack_cortexm.S +++ b/src/internal/task/task_stack_cortexm.S @@ -1,3 +1,5 @@ +//go:build tinygo + // Only generate .debug_frame, don't generate .eh_frame. .cfi_sections .debug_frame diff --git a/src/internal/task/task_stack_cortexm.go b/src/internal/task/task_stack_cortexm.go index 39b90b75c2..226a088c88 100644 --- a/src/internal/task/task_stack_cortexm.go +++ b/src/internal/task/task_stack_cortexm.go @@ -52,17 +52,17 @@ func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { } func (s *state) resume() { - switchToTask(s.sp) + tinygo_switchToTask(s.sp) } //export tinygo_switchToTask -func switchToTask(uintptr) +func tinygo_switchToTask(uintptr) //export tinygo_switchToScheduler -func switchToScheduler(*uintptr) +func tinygo_switchToScheduler(*uintptr) func (s *state) pause() { - switchToScheduler(&s.sp) + tinygo_switchToScheduler(&s.sp) } // SystemStack returns the system stack pointer. On Cortex-M, it is always diff --git a/src/internal/task/task_stack_esp32.S b/src/internal/task/task_stack_esp32.S index 364759b175..fe0afe98d6 100644 --- a/src/internal/task/task_stack_esp32.S +++ b/src/internal/task/task_stack_esp32.S @@ -1,3 +1,5 @@ +//go:build tinygo + .section .text.tinygo_startTask,"ax",@progbits .global tinygo_startTask .type tinygo_startTask, %function @@ -76,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_esp8266.S b/src/internal/task/task_stack_esp8266.S index e7334edcb0..07f4e26592 100644 --- a/src/internal/task/task_stack_esp8266.S +++ b/src/internal/task/task_stack_esp8266.S @@ -1,3 +1,5 @@ +//go:build tinygo + .section .text.tinygo_startTask,"ax",@progbits .global tinygo_startTask .type tinygo_startTask, %function 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/task/task_stack_tinygoriscv.S b/src/internal/task/task_stack_tinygoriscv.S index 0ac6f1231e..5f6127427e 100644 --- a/src/internal/task/task_stack_tinygoriscv.S +++ b/src/internal/task/task_stack_tinygoriscv.S @@ -1,3 +1,5 @@ +//go:build tinygo + .section .text.tinygo_startTask .global tinygo_startTask .type tinygo_startTask, %function 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.go b/src/machine/board_arduino.go index a164406f27..a66889aee5 100644 --- a/src/machine/board_arduino.go +++ b/src/machine/board_arduino.go @@ -2,11 +2,6 @@ package machine -// Return the current CPU frequency in hertz. -func CPUFrequency() uint32 { - return 16000000 -} - // Digital pins, marked as plain numbers on the board. const ( D0 = PD0 // RX 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_nano.go b/src/machine/board_arduino_nano.go index 8bc8833641..c67721951a 100644 --- a/src/machine/board_arduino_nano.go +++ b/src/machine/board_arduino_nano.go @@ -2,11 +2,6 @@ package machine -// Return the current CPU frequency in hertz. -func CPUFrequency() uint32 { - return 16000000 -} - // Digital pins. const ( D0 = PD0 // RX0 diff --git a/src/machine/board_arduino_nano33.go b/src/machine/board_arduino_nano33.go index f7eb2f8e0c..9232d38190 100644 --- a/src/machine/board_arduino_nano33.go +++ b/src/machine/board_arduino_nano33.go @@ -63,6 +63,9 @@ var UART1 = &sercomUSART3 // UART2 on the Arduino Nano 33 connects to the normal TX/RX pins. var UART2 = &sercomUSART5 +// UART_NINA on the Arduino Nano 33 connects to the NINA HCI. +var UART_NINA = &sercomUSART2 + // I2C pins const ( SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1] @@ -99,14 +102,24 @@ const ( NINA_GPIO0 Pin = PA27 NINA_RESETN Pin = PA08 NINA_ACK Pin = PA28 - NINA_TX Pin = PA22 - NINA_RX Pin = PA23 + NINA_TX Pin = PA12 + NINA_RX Pin = PA13 + NINA_RTS Pin = PA14 + NINA_CTS Pin = PA15 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 912600 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = false ) // 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_atmega328p.go b/src/machine/board_atmega328p.go index a00fec5162..234bf31557 100644 --- a/src/machine/board_atmega328p.go +++ b/src/machine/board_atmega328p.go @@ -2,6 +2,11 @@ package machine +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + const ( // Note: start at port B because there is no port A. portB Pin = iota * 8 diff --git a/src/machine/board_atmega328pb.go b/src/machine/board_atmega328pb.go new file mode 100644 index 0000000000..e3e45f01fa --- /dev/null +++ b/src/machine/board_atmega328pb.go @@ -0,0 +1,51 @@ +//go:build avr && atmega328pb + +package machine + +// Return the current CPU frequency in hertz. +func CPUFrequency() uint32 { + return 16000000 +} + +const ( + // Note: start at port B because there is no port A. + portB Pin = iota * 8 + portC + portD + portE +) + +const ( + PB0 = portB + 0 + PB1 = portB + 1 // peripherals: Timer1 channel A + PB2 = portB + 2 // peripherals: Timer1 channel B + PB3 = portB + 3 // peripherals: Timer2 channel A + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 // peripherals: Timer2 channel B + PD4 = portD + 4 + PD5 = portD + 5 // peripherals: Timer0 channel B + PD6 = portD + 6 // peripherals: Timer0 channel A + PD7 = portD + 7 + PE0 = portE + 0 + PE1 = portE + 1 + PE2 = portE + 2 + PE3 = portE + 3 + PE4 = portE + 4 + PE5 = portE + 5 + PE6 = portE + 6 + PE7 = portE + 7 +) 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 59c17beb22..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 @@ -73,7 +68,7 @@ const ( // USB CDC identifiers const ( - usb_STRING_PRODUCT = "Gopher Badger" + usb_STRING_PRODUCT = "Gopher Badge" usb_STRING_MANUFACTURER = "TinyGo" ) @@ -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_metro-m4-airlift.go b/src/machine/board_metro-m4-airlift.go index 30ae80ec42..0fd4291a9a 100644 --- a/src/machine/board_metro-m4-airlift.go +++ b/src/machine/board_metro-m4-airlift.go @@ -62,6 +62,15 @@ var ( UART2 = &sercomUSART0 DefaultUART = UART1 + + UART_NINA = UART2 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = true ) const ( @@ -70,9 +79,12 @@ const ( NINA_GPIO0 = PB01 NINA_RESETN = PB05 + // pins used for the ESP32 connection do not allow hardware + // flow control, which is required. have to emulate with software. NINA_TX = PA04 NINA_RX = PA07 - NINA_RTS = PB23 + NINA_CTS = NINA_ACK + NINA_RTS = NINA_GPIO0 ) // I2C pins diff --git a/src/machine/board_mksnanov3.go b/src/machine/board_mksnanov3.go new file mode 100644 index 0000000000..35fef682d4 --- /dev/null +++ b/src/machine/board_mksnanov3.go @@ -0,0 +1,142 @@ +//go:build mksnanov3 + +// The MKS Robin Nano V3.X board. +// Documented at https://github.com/makerbase-mks/MKS-Robin-Nano-V3.X. + +package machine + +import ( + "device/stm32" + "runtime/interrupt" +) + +// LED is also wired to the SD card card detect (CD) pin. +const LED = PD12 + +// UART pins +const ( + UART_TX_PIN = PB10 + UART_RX_PIN = PB11 +) + +// EXP1 and EXP2 expansion ports for connecting +// the MKS TS35 V2.0 expansion board. +const ( + BEEPER = EXP1_1 + + // LCD pins. + LCD_DC = EXP1_8 + LCD_CS = EXP1_7 + LCD_RS = EXP1_4 + LCD_BACKLIGHT = EXP1_3 + + // Touch pins. Note that some pins are shared with the + // LCD SPI1 interface. + TOUCH_CLK = EXP2_2 + TOUCH_CS = EXP1_5 + TOUCH_DIN = EXP2_6 + TOUCH_DOUT = EXP2_1 + TOUCH_IRQ = EXP1_6 + + BUTTON = BUTTON_JOG + BUTTON_JOG = EXP1_2 + BUTTON_JOG_CCW = EXP2_3 + BUTTON_JOG_CW = EXP2_5 + + EXP1_1 = PC5 + EXP1_2 = PE13 + EXP1_3 = PD13 + EXP1_4 = PC6 + EXP1_5 = PE14 + EXP1_6 = PE15 + EXP1_7 = PD11 + EXP1_8 = PD10 + + EXP2_1 = PA6 + EXP2_2 = PA5 + EXP2_3 = PE8 + EXP2_4 = PE10 + EXP2_5 = PE11 + EXP2_6 = PA7 + EXP2_7 = PE12 +) + +var ( + UART3 = &_UART3 + _UART3 = UART{ + Buffer: NewRingBuffer(), + Bus: stm32.USART3, + TxAltFuncSelector: AF7_USART1_2_3, + RxAltFuncSelector: AF7_USART1_2_3, + } + DefaultUART = UART3 +) + +// set up RX IRQ handler. Follow similar pattern for other UARTx instances +func init() { + UART3.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART3.handleInterrupt) +} + +// SPI pins +const ( + SPI1_SCK_PIN = EXP2_2 + SPI1_SDI_PIN = EXP2_1 + SPI1_SDO_PIN = EXP2_6 + SPI0_SCK_PIN = SPI1_SCK_PIN + SPI0_SDI_PIN = SPI1_SDI_PIN + SPI0_SDO_PIN = SPI1_SDO_PIN +) + +// Since the first interface is named SPI1, both SPI0 and SPI1 refer to SPI1. +var ( + SPI0 = &SPI{ + Bus: stm32.SPI1, + AltFuncSelector: AF5_SPI1_SPI2, + } + SPI1 = SPI0 +) + +const ( + I2C0_SCL_PIN = PB6 + I2C0_SDA_PIN = PB7 +) + +var ( + I2C0 = &I2C{ + Bus: stm32.I2C1, + AltFuncSelector: AF4_I2C1_2_3, + } +) + +// Motor control pins. +const ( + X_ENABLE = PE4 + X_STEP = PE3 + X_DIR = PE2 + X_DIAG = PA15 + X_UART = PD5 + + Y_ENABLE = PE1 + Y_STEP = PE0 + Y_DIR = PB9 + Y_DIAG = PD2 + Y_UART = PD7 + + Z_ENABLE = PB8 + Z_STEP = PB5 + Z_DIR = PB4 + Z_DIAG = PC8 + Z_UART = PD4 + + E0_ENABLE = PB3 + E0_STEP = PD6 + E0_DIR = PD3 + E0_DIAG = PC4 + E0_UART = PD9 + + E1_ENABLE = PA3 + E1_STEP = PD15 + E1_DIR = PA1 + E1_DIAG = PE7 + E1_UART = PD8 +) 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 0a27290dd4..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 @@ -87,8 +82,17 @@ const ( NINA_GPIO0 Pin = GPIO2 NINA_RESETN Pin = GPIO3 - NINA_TX Pin = GPIO9 - NINA_RX Pin = GPIO8 + NINA_TX Pin = GPIO8 + NINA_RX Pin = GPIO9 + NINA_CTS Pin = GPIO10 + NINA_RTS Pin = GPIO11 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = false ) // Onboard crystal oscillator frequency, in MHz. @@ -116,17 +120,7 @@ const ( UART_RX_PIN = UART0_RX_PIN ) -// UART on the RP2040 -var ( - UART0 = &_UART0 - _UART0 = UART{ - Buffer: NewRingBuffer(), - Bus: rp.UART0, - } -) +// 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) -} diff --git a/src/machine/board_nrf52840_generic.go b/src/machine/board_nrf52840_generic.go new file mode 100644 index 0000000000..1cf30e2cfe --- /dev/null +++ b/src/machine/board_nrf52840_generic.go @@ -0,0 +1,25 @@ +//go:build nrf52840 && nrf52840_generic + +package machine + +var ( + LED = NoPin + SDA_PIN = NoPin + SCL_PIN = NoPin + UART_TX_PIN = NoPin + UART_RX_PIN = NoPin + SPI0_SCK_PIN = NoPin + SPI0_SDO_PIN = NoPin + SPI0_SDI_PIN = NoPin + + // https://pid.codes/org/TinyGo/ + usb_VID uint16 = 0x1209 + usb_PID uint16 = 0x9090 + + usb_STRING_MANUFACTURER = "TinyGo" + usb_STRING_PRODUCT = "nRF52840 Generic board" +) + +var ( + DefaultUART = UART0 +) 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_pybadge.go b/src/machine/board_pybadge.go index fa8be2a756..30d44d1645 100644 --- a/src/machine/board_pybadge.go +++ b/src/machine/board_pybadge.go @@ -130,3 +130,33 @@ var ( usb_VID uint16 = 0x239A usb_PID uint16 = 0x8033 ) + +// NINA-W102 settings when using AirLift WiFi FeatherWing +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = true +) + +const ( + NINA_CS = D13 + NINA_ACK = D11 + NINA_GPIO0 = D10 + NINA_RESETN = D12 + + // pins used for the ESP32 connection do not allow hardware + // flow control, which is required. have to emulate with software. + NINA_TX = UART_TX_PIN + NINA_RX = UART_RX_PIN + NINA_CTS = NINA_ACK + NINA_RTS = NINA_GPIO0 + + NINA_SDO = SPI0_SDO_PIN + NINA_SDI = SPI0_SDI_PIN + NINA_SCK = SPI0_SCK_PIN +) + +var ( + NINA_SPI = SPI0 + UART_NINA = UART1 +) diff --git a/src/machine/board_pyportal.go b/src/machine/board_pyportal.go index b244000cf3..98ef01d09e 100644 --- a/src/machine/board_pyportal.go +++ b/src/machine/board_pyportal.go @@ -53,9 +53,12 @@ const ( NINA_GPIO0 = D6 NINA_RESETN = D7 + // pins used for the ESP32 connection do not allow hardware + // flow control, which is required. have to emulate with software. NINA_TX = D1 NINA_RX = D0 - NINA_RTS = D51 + NINA_CTS = NINA_ACK + NINA_RTS = NINA_GPIO0 LCD_DATA0 = D34 @@ -111,6 +114,15 @@ var ( UART1 = &sercomUSART4 DefaultUART = UART1 + + UART_NINA = UART1 +) + +// NINA-W102 settings +const ( + NINA_BAUDRATE = 115200 + NINA_RESET_INVERTED = true + NINA_SOFT_FLOWCONTROL = true ) // I2C pins 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_esp32c3.go b/src/machine/board_qtpy_esp32c3.go new file mode 100644 index 0000000000..a2e3f48e8c --- /dev/null +++ b/src/machine/board_qtpy_esp32c3.go @@ -0,0 +1,60 @@ +//go:build qtpy_esp32c3 + +// This file contains the pin mappings for the Adafruit QtPy ESP32C3 boards. +// +// https://learn.adafruit.com/adafruit-qt-py-esp32-c3-wifi-dev-board/pinouts +package machine + +// Digital Pins +const ( + D0 = GPIO4 + D1 = GPIO3 + D2 = GPIO1 + D3 = GPIO0 +) + +// Analog pins (ADC1) +const ( + A0 = GPIO4 + A1 = GPIO3 + A2 = GPIO1 + A3 = GPIO0 +) + +// UART pins +const ( + RX_PIN = GPIO20 + TX_PIN = GPIO21 + + UART_RX_PIN = RX_PIN + UART_TX_PIN = TX_PIN +) + +// I2C pins +const ( + SDA_PIN = GPIO5 + SCL_PIN = GPIO6 + + I2C0_SDA_PIN = SDA_PIN + I2C0_SCL_PIN = SCL_PIN +) + +// SPI pins +const ( + SCK_PIN = GPIO10 + MI_PIN = GPIO8 + MO_PIN = GPIO7 + + SPI_SCK_PIN = SCK_PIN + SPI_SDI_PIN = MI_PIN + SPI_SDO_PIN = MO_PIN +) + +const ( + NEOPIXEL = GPIO2 + WS2812 = GPIO2 + + // also used for boot button. + // set it to be an input-with-pullup + BUTTON = GPIO9 +) 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 new file mode 100644 index 0000000000..f89a8b7059 --- /dev/null +++ b/src/machine/board_thumby.go @@ -0,0 +1,76 @@ +//go:build thumby + +// This contains the pin mappings for the Thumby. +// +// https://thumby.us/ +package machine + +const ( + THUMBY_SCK_PIN = I2C1_SDA_PIN + THUMBY_SDA_PIN = I2C1_SCL_PIN + + THUMBY_CS_PIN = GPIO16 + THUMBY_DC_PIN = GPIO17 + THUMBY_RESET_PIN = GPIO20 + + THUMBY_LINK_TX_PIN = UART0_TX_PIN + THUMBY_LINK_RX_PIN = UART0_RX_PIN + THUMBY_LINK_PU_PIN = GPIO2 + + THUMBY_BTN_LDPAD_PIN = GPIO3 + THUMBY_BTN_RDPAD_PIN = GPIO5 + THUMBY_BTN_UDPAD_PIN = GPIO4 + THUMBY_BTN_DDPAD_PIN = GPIO6 + THUMBY_BTN_B_PIN = GPIO24 + THUMBY_BTN_A_PIN = GPIO27 + + THUMBY_AUDIO_PIN = GPIO28 + + THUMBY_SCREEN_RESET_PIN = GPIO20 +) + +// I2C pins +const ( + I2C0_SDA_PIN Pin = NoPin + I2C0_SCL_PIN Pin = NoPin + + I2C1_SDA_PIN Pin = GPIO18 + I2C1_SCL_PIN Pin = GPIO19 +) + +// SPI pins +const ( + SPI0_SCK_PIN = GPIO18 + SPI0_SDO_PIN = GPIO19 + SPI0_SDI_PIN = GPIO16 + + SPI1_SCK_PIN = NoPin + SPI1_SDO_PIN = NoPin + SPI1_SDI_PIN = NoPin +) + +// Onboard crystal oscillator frequency, in MHz. +const ( + xoscFreq = 12 // MHz +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Thumby" + usb_STRING_MANUFACTURER = "TinyCircuits" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x0005 +) + +// 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_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 15eefbed1d..6997120b93 100644 --- a/src/machine/board_wioterminal.go +++ b/src/machine/board_wioterminal.go @@ -353,6 +353,9 @@ var ( // RTL8720D (tx: PB24, rx: PC24) UART3 = &sercomUSART0 + + // Right-hand grove port (tx: D0, rx: D1) + UART4 = &sercomUSART4 ) // I2C pins @@ -373,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/buffer.go b/src/machine/buffer.go index 1528c25be0..508700d9e9 100644 --- a/src/machine/buffer.go +++ b/src/machine/buffer.go @@ -4,8 +4,6 @@ import ( "runtime/volatile" ) -const bufferSize = 128 - // RingBuffer is ring buffer implementation inspired by post at // https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php type RingBuffer struct { diff --git a/src/machine/buffer_atmega.go b/src/machine/buffer_atmega.go new file mode 100644 index 0000000000..a321eaecdf --- /dev/null +++ b/src/machine/buffer_atmega.go @@ -0,0 +1,5 @@ +//go:build atmega + +package machine + +const bufferSize = 32 diff --git a/src/machine/buffer_generic.go b/src/machine/buffer_generic.go new file mode 100644 index 0000000000..0d82b44940 --- /dev/null +++ b/src/machine/buffer_generic.go @@ -0,0 +1,5 @@ +//go:build !atmega + +package machine + +const bufferSize = 128 diff --git a/src/machine/deviceid.go b/src/machine/deviceid.go new file mode 100644 index 0000000000..cb2e1d0c1e --- /dev/null +++ b/src/machine/deviceid.go @@ -0,0 +1,17 @@ +//go:build rp2040 || nrf || sam + +package machine + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself, or the +// flash chip at time of manufacture. +// +// It's possible that two different vendors may allocate +// the same DeviceID, so callers should take this into +// account if needing to generate a globally unique id. +// +// The length of the hardware ID is vendor-specific, but +// 8 bytes (64 bits) and 16 bytes (128 bits) are common. +var _ = (func() []byte)(DeviceID) 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 c8bd6d1b4f..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 +//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 package machine @@ -6,6 +6,17 @@ import ( "errors" ) +// If you are getting a compile error on this line please check to see you've +// correctly implemented the methods on the I2C type. They must match +// the i2cController interface method signatures type to type perfectly. +// If not implementing the I2C type please remove your target from the build tags +// at the top of this file. +var _ interface { // 2 + Configure(config I2CConfig) error + Tx(addr uint16, w, r []byte) error + SetBaudRate(br uint32) error +} = (*I2C)(nil) + // TWI_FREQ is the I2C bus speed. Normally either 100 kHz, or 400 kHz for high-speed bus. // // Deprecated: use 100 * machine.KHz or 400 * machine.KHz instead. @@ -25,6 +36,7 @@ var ( errI2CBusError = errors.New("I2C bus error") errI2COverflow = errors.New("I2C receive buffer overflow") errI2COverread = errors.New("I2C transmit buffer overflow") + errI2CNotImplemented = errors.New("I2C operation not yet implemented") ) // I2CTargetEvent reflects events on the I2C bus 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 81a47c0dfa..7a59e5e092 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -11,11 +11,20 @@ import ( // I2C on AVR. type I2C struct { + srReg *volatile.Register8 + brReg *volatile.Register8 + crReg *volatile.Register8 + drReg *volatile.Register8 + + srPS0 byte + srPS1 byte + crEN byte + crINT byte + crSTO byte + crEA byte + crSTA byte } -// I2C0 is the only I2C interface on most AVRs. -var I2C0 *I2C = nil - // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 @@ -31,17 +40,22 @@ func (i2c *I2C) Configure(config I2CConfig) error { // Activate internal pullups for twi. avr.PORTC.SetBits((avr.DIDR0_ADC4D | avr.DIDR0_ADC5D)) + return i2c.SetBaudRate(config.Frequency) +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { // Initialize twi prescaler and bit rate. - avr.TWSR.SetBits((avr.TWSR_TWPS0 | avr.TWSR_TWPS1)) + i2c.srReg.SetBits((i2c.srPS0 | i2c.srPS1)) // twi bit rate formula from atmega128 manual pg. 204: // SCL Frequency = CPU Clock Frequency / (16 + (2 * TWBR)) // NOTE: TWBR should be 10 or higher for controller mode. // It is 72 for a 16mhz board with 100kHz TWI - avr.TWBR.Set(uint8(((CPUFrequency() / config.Frequency) - 16) / 2)) + i2c.brReg.Set(uint8(((CPUFrequency() / br) - 16) / 2)) // Enable twi module. - avr.TWCR.Set(avr.TWCR_TWEN) + i2c.crReg.Set(i2c.crEN) return nil } @@ -72,10 +86,10 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error { // start starts an I2C communication session. func (i2c *I2C) start(address uint8, write bool) { // Clear TWI interrupt flag, put start condition on SDA, and enable TWI. - avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN)) + i2c.crReg.Set((i2c.crINT | i2c.crSTA | i2c.crEN)) // Wait till start condition is transmitted. - for !avr.TWCR.HasBits(avr.TWCR_TWINT) { + for !i2c.crReg.HasBits(i2c.crINT) { } // Write 7-bit shifted peripheral address. @@ -89,23 +103,23 @@ func (i2c *I2C) start(address uint8, write bool) { // stop ends an I2C communication session. func (i2c *I2C) stop() { // Send stop condition. - avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO) + i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crSTO) // Wait for stop condition to be executed on bus. - for !avr.TWCR.HasBits(avr.TWCR_TWSTO) { + for !i2c.crReg.HasBits(i2c.crSTO) { } } // writeByte writes a single byte to the I2C bus. func (i2c *I2C) writeByte(data byte) error { // Write data to register. - avr.TWDR.Set(data) + i2c.drReg.Set(data) // Clear TWI interrupt flag and enable TWI. - avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT) + i2c.crReg.Set(i2c.crEN | i2c.crINT) // Wait till data is transmitted. - for !avr.TWCR.HasBits(avr.TWCR_TWINT) { + for !i2c.crReg.HasBits(i2c.crINT) { } return nil } @@ -113,13 +127,13 @@ func (i2c *I2C) writeByte(data byte) error { // readByte reads a single byte from the I2C bus. func (i2c *I2C) readByte() byte { // Clear TWI interrupt flag and enable TWI. - avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA) + i2c.crReg.Set(i2c.crEN | i2c.crINT | i2c.crEA) // Wait till read request is transmitted. - for !avr.TWCR.HasBits(avr.TWCR_TWINT) { + for !i2c.crReg.HasBits(i2c.crINT) { } - return byte(avr.TWDR.Get()) + return byte(i2c.drReg.Get()) } // Always use UART0 as the serial output. @@ -165,10 +179,18 @@ func (uart *UART) Configure(config UARTConfig) { config.BaudRate = 9600 } - // Set baud rate based on prescale formula from - // https://www.microchip.com/webdoc/AVRLibcReferenceManual/FAQ_1faq_wrong_baud_rate.html - // ((F_CPU + UART_BAUD_RATE * 8L) / (UART_BAUD_RATE * 16L) - 1) - ps := ((CPUFrequency()+config.BaudRate*8)/(config.BaudRate*16) - 1) + // Prescale formula for u2x mode from AVR MiniCore source code. + // Same as formula from specification but taking into account rounding error. + ps := (CPUFrequency()/4/config.BaudRate - 1) / 2 + uart.statusRegA.SetBits(avr.UCSR0A_U2X0) + + // Hardcoded exception for 57600 for compatibility with older bootloaders. + // Also, prescale cannot be > 4095, so switch back to non-u2x mode if the baud rate is too low. + if (CPUFrequency() == 16000000 && config.BaudRate == 57600) || ps > 0xfff { + ps = (CPUFrequency()/8/config.BaudRate - 1) / 2 + uart.statusRegA.ClearBits(avr.UCSR0A_U2X0) + } + uart.baudRegH.Set(uint8(ps >> 8)) uart.baudRegL.Set(uint8(ps & 0xff)) @@ -216,6 +238,17 @@ type SPI struct { spdr *volatile.Register8 spsr *volatile.Register8 + spcrR0 byte + spcrR1 byte + spcrCPHA byte + spcrCPOL byte + spcrDORD byte + spcrSPE byte + spcrMSTR byte + + spsrI2X byte + spsrSPIF byte + // The io pins for the SPIx port set by the chip sck Pin sdi Pin @@ -224,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. @@ -259,48 +292,48 @@ func (s SPI) Configure(config SPIConfig) error { switch { case frequencyDivider >= 128: - s.spcr.SetBits(avr.SPCR_SPR0 | avr.SPCR_SPR1) + s.spcr.SetBits(s.spcrR0 | s.spcrR1) case frequencyDivider >= 64: - s.spcr.SetBits(avr.SPCR_SPR1) + s.spcr.SetBits(s.spcrR1) case frequencyDivider >= 32: - s.spcr.SetBits(avr.SPCR_SPR1) - s.spsr.SetBits(avr.SPSR_SPI2X) + s.spcr.SetBits(s.spcrR1) + s.spsr.SetBits(s.spsrI2X) case frequencyDivider >= 16: - s.spcr.SetBits(avr.SPCR_SPR0) + s.spcr.SetBits(s.spcrR0) case frequencyDivider >= 8: - s.spcr.SetBits(avr.SPCR_SPR0) - s.spsr.SetBits(avr.SPSR_SPI2X) + s.spcr.SetBits(s.spcrR0) + s.spsr.SetBits(s.spsrI2X) case frequencyDivider >= 4: // The clock is already set to all 0's. default: // defaults to fastest which is /2 - s.spsr.SetBits(avr.SPSR_SPI2X) + s.spsr.SetBits(s.spsrI2X) } switch config.Mode { case Mode1: - s.spcr.SetBits(avr.SPCR_CPHA) + s.spcr.SetBits(s.spcrCPHA) case Mode2: - s.spcr.SetBits(avr.SPCR_CPOL) + s.spcr.SetBits(s.spcrCPHA) case Mode3: - s.spcr.SetBits(avr.SPCR_CPHA | avr.SPCR_CPOL) + s.spcr.SetBits(s.spcrCPHA | s.spcrCPOL) default: // default is mode 0 } if config.LSBFirst { - s.spcr.SetBits(avr.SPCR_DORD) + s.spcr.SetBits(s.spcrDORD) } // enable SPI, set controller, set clock rate - s.spcr.SetBits(avr.SPCR_SPE | avr.SPCR_MSTR) + s.spcr.SetBits(s.spcrSPE | s.spcrMSTR) return nil } // 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(avr.SPSR_SPIF) { + for !s.spsr.HasBits(s.spsrSPIF) { } return byte(s.spdr.Get()), nil 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 new file mode 100644 index 0000000000..c354ccb888 --- /dev/null +++ b/src/machine/machine_atmega328.go @@ -0,0 +1,548 @@ +//go:build avr && (atmega328p || atmega328pb) + +package machine + +import ( + "device/avr" + "runtime/interrupt" + "runtime/volatile" +) + +// PWM is one PWM peripheral, which consists of a counter and two output +// channels (that can be connected to two fixed pins). You can set the frequency +// using SetPeriod, but only for all the channels in this PWM peripheral at +// once. +type PWM struct { + num uint8 +} + +var ( + Timer0 = PWM{0} // 8 bit timer for PD5 and PD6 + Timer1 = PWM{1} // 16 bit timer for PB1 and PB2 + Timer2 = PWM{2} // 8 bit timer for PB3 and PD3 +) + +// Configure enables and configures this PWM. +// +// For the two 8 bit timers, there is only a limited number of periods +// available, namely the CPU frequency divided by 256 and again divided by 1, 8, +// 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs, +// 128µs, 1024µs, 4096µs, or 16384µs. +func (pwm PWM) Configure(config PWMConfig) error { + switch pwm.num { + case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2) + // Calculate the timer prescaler. + // While we could configure a flexible top, that would sacrifice one of + // the PWM output compare registers and thus a PWM channel. I've chosen + // to instead limit this timer to a fixed number of frequencies. + var prescaler uint8 + switch config.Period { + case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): + prescaler = 1 + case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): + prescaler = 2 + case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): + prescaler = 3 + case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): + prescaler = 4 + case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): + prescaler = 5 + default: + return ErrPWMPeriodTooLong + } + + if pwm.num == 0 { + avr.TCCR0B.Set(prescaler) + // 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 adjust internal settings of monotonic timer when PWM:0 settings changed + adjustMonotonicTimer() + } else { + avr.TCCR2B.Set(prescaler) + // Set the PWM mode to fast PWM (mode = 3). + avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21) + } + case 1: // Timer/counter 1 + // The top value is the number of PWM ticks a PWM period takes. It is + // initially picked assuming an unlimited counter top and no PWM + // prescaler. + var top uint64 + if config.Period == 0 { + // Use a top appropriate for LEDs. Picking a relatively low period + // here (0xff) for consistency with the other timers. + top = 0xff + } else { + // The formula below calculates the following formula, optimized: + // top = period * (CPUFrequency() / 1e9) + // By dividing the CPU frequency first (an operation that is easily + // optimized away) the period has less chance of overflowing. + top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + avr.TCCR1A.Set(avr.TCCR1A_WGM11) + + // The ideal PWM period may be larger than would fit in the PWM counter, + // which is 16 bits (see maxTop). Therefore, try to make the PWM clock + // speed lower with a prescaler to make the top value fit the maximum + // top value. + const maxTop = 0x10000 + switch { + case top <= maxTop: + avr.TCCR1B.Set(3<<3 | 1) // no prescaling + case top/8 <= maxTop: + avr.TCCR1B.Set(3<<3 | 2) // divide by 8 + top /= 8 + case top/64 <= maxTop: + avr.TCCR1B.Set(3<<3 | 3) // divide by 64 + top /= 64 + case top/256 <= maxTop: + avr.TCCR1B.Set(3<<3 | 4) // divide by 256 + top /= 256 + case top/1024 <= maxTop: + avr.TCCR1B.Set(3<<3 | 5) // divide by 1024 + top /= 1024 + default: + return ErrPWMPeriodTooLong + } + + // A top of 0x10000 is at 100% duty cycle. Subtract one because the + // counter counts from 0, not 1 (avoiding an off-by-one). + top -= 1 + + avr.ICR1H.Set(uint8(top >> 8)) + avr.ICR1L.Set(uint8(top)) + } + return nil +} + +// SetPeriod updates the period of this PWM peripheral. +// To set a particular frequency, use the following formula: +// +// period = 1e9 / frequency +// +// If you use a period of 0, a period that works well for LEDs will be picked. +// +// SetPeriod will not change the prescaler, but also won't change the current +// value in any of the channels. This means that you may need to update the +// value for the particular channel. +// +// Note that you cannot pick any arbitrary period after the PWM peripheral has +// been configured. If you want to switch between frequencies, pick the lowest +// frequency (longest period) once when calling Configure and adjust the +// frequency here as needed. +func (pwm PWM) SetPeriod(period uint64) error { + if pwm.num != 1 { + return ErrPWMPeriodTooLong // TODO better error message + } + + // The top value is the number of PWM ticks a PWM period takes. It is + // initially picked assuming an unlimited counter top and no PWM + // prescaler. + var top uint64 + if period == 0 { + // Use a top appropriate for LEDs. Picking a relatively low period + // here (0xff) for consistency with the other timers. + top = 0xff + } else { + // The formula below calculates the following formula, optimized: + // top = period * (CPUFrequency() / 1e9) + // By dividing the CPU frequency first (an operation that is easily + // optimized away) the period has less chance of overflowing. + top = period * (uint64(CPUFrequency()) / 1000000) / 1000 + } + + prescaler := avr.TCCR1B.Get() & 0x7 + switch prescaler { + case 1: + top /= 1 + case 2: + top /= 8 + case 3: + top /= 64 + case 4: + top /= 256 + case 5: + top /= 1024 + } + + // A top of 0x10000 is at 100% duty cycle. Subtract one because the counter + // counts from 0, not 1 (avoiding an off-by-one). + top -= 1 + + if top > 0xffff { + return ErrPWMPeriodTooLong + } + + // Warning: this change is not atomic! + avr.ICR1H.Set(uint8(top >> 8)) + avr.ICR1L.Set(uint8(top)) + + // ... and because of that, set the counter back to zero to avoid most of + // the effects of this non-atomicity. + avr.TCNT1H.Set(0) + avr.TCNT1L.Set(0) + + return nil +} + +// Top returns the current counter top, for use in duty cycle calculation. It +// will only change with a call to Configure or SetPeriod, otherwise it is +// constant. +// +// The value returned here is hardware dependent. In general, it's best to treat +// it as an opaque value that can be divided by some number and passed to Set +// (see Set documentation for more information). +func (pwm PWM) Top() uint32 { + if pwm.num == 1 { + // Timer 1 has a configurable top value. + low := avr.ICR1L.Get() + high := avr.ICR1H.Get() + return uint32(high)<<8 | uint32(low) + 1 + } + // Other timers go from 0 to 0xff (0x100 or 256 in total). + return 256 +} + +// Counter returns the current counter value of the timer in this PWM +// peripheral. It may be useful for debugging. +func (pwm PWM) Counter() uint32 { + switch pwm.num { + case 0: + return uint32(avr.TCNT0.Get()) + case 1: + mask := interrupt.Disable() + low := avr.TCNT1L.Get() + high := avr.TCNT1H.Get() + interrupt.Restore(mask) + return uint32(high)<<8 | uint32(low) + case 2: + return uint32(avr.TCNT2.Get()) + } + // Unknown PWM. + return 0 +} + +// Period returns the used PWM period in nanoseconds. It might deviate slightly +// from the configured period due to rounding. +func (pwm PWM) Period() uint64 { + var prescaler uint8 + switch pwm.num { + case 0: + prescaler = avr.TCCR0B.Get() & 0x7 + case 1: + prescaler = avr.TCCR1B.Get() & 0x7 + case 2: + prescaler = avr.TCCR2B.Get() & 0x7 + } + top := uint64(pwm.Top()) + switch prescaler { + case 1: // prescaler 1 + return 1 * top * 1000 / uint64(CPUFrequency()/1e6) + case 2: // prescaler 8 + return 8 * top * 1000 / uint64(CPUFrequency()/1e6) + case 3: // prescaler 64 + return 64 * top * 1000 / uint64(CPUFrequency()/1e6) + case 4: // prescaler 256 + return 256 * top * 1000 / uint64(CPUFrequency()/1e6) + case 5: // prescaler 1024 + return 1024 * top * 1000 / uint64(CPUFrequency()/1e6) + default: // unknown clock source + return 0 + } +} + +// Channel returns a PWM channel for the given pin. +func (pwm PWM) Channel(pin Pin) (uint8, error) { + pin.Configure(PinConfig{Mode: PinOutput}) + pin.Low() + switch pwm.num { + case 0: + switch pin { + case PD6: // channel A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + return 0, nil + case PD5: // channel B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + return 1, nil + } + case 1: + switch pin { + case PB1: // channel A + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + return 0, nil + case PB2: // channel B + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + return 1, nil + } + case 2: + switch pin { + case PB3: // channel A + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + return 0, nil + case PD3: // channel B + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + return 1, nil + } + } + return 0, ErrInvalidOutputPin +} + +// SetInverting sets whether to invert the output of this channel. +// Without inverting, a 25% duty cycle would mean the output is high for 25% of +// the time and low for the rest. Inverting flips the output as if a NOT gate +// was placed at the output, meaning that the output would be 25% low and 75% +// high with a duty cycle of 25%. +// +// Note: the invert state may not be applied on the AVR until the next call to +// ch.Set(). +func (pwm PWM) SetInverting(channel uint8, inverting bool) { + switch pwm.num { + case 0: + switch channel { + case 0: // channel A + if inverting { + avr.PORTB.SetBits(1 << 6) // PB6 high + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) + } else { + avr.PORTB.ClearBits(1 << 6) // PB6 low + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) + } + case 1: // channel B + if inverting { + avr.PORTB.SetBits(1 << 5) // PB5 high + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) + } else { + avr.PORTB.ClearBits(1 << 5) // PB5 low + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) + } + } + case 1: + // Note: the COM1A0/COM1B0 bit is not set with the configuration below. + // It will be set the following call to Set(), however. + switch channel { + case 0: // channel A, PB1 + if inverting { + avr.PORTB.SetBits(1 << 1) // PB1 high + } else { + avr.PORTB.ClearBits(1 << 1) // PB1 low + } + case 1: // channel B, PB2 + if inverting { + avr.PORTB.SetBits(1 << 2) // PB2 high + } else { + avr.PORTB.ClearBits(1 << 2) // PB2 low + } + } + case 2: + switch channel { + case 0: // channel A + if inverting { + avr.PORTB.SetBits(1 << 3) // PB3 high + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0) + } else { + avr.PORTB.ClearBits(1 << 3) // PB3 low + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0) + } + case 1: // channel B + if inverting { + avr.PORTD.SetBits(1 << 3) // PD3 high + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0) + } else { + avr.PORTD.ClearBits(1 << 3) // PD3 low + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0) + } + } + } +} + +// Set updates the channel value. This is used to control the channel duty +// cycle, in other words the fraction of time the channel output is high (or low +// when inverted). For example, to set it to a 25% duty cycle, use: +// +// pwm.Set(channel, pwm.Top() / 4) +// +// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, +// pwm.Top()) will set the output to high, assuming the output isn't inverted. +func (pwm PWM) Set(channel uint8, value uint32) { + switch pwm.num { + case 0: + value := uint16(value) + switch channel { + case 0: // channel A + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) + } else { + avr.OCR0A.Set(uint8(value - 1)) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + } + case 1: // channel B + if value == 0 { + avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) + } else { + avr.OCR0B.Set(uint8(value) - 1) + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + } + } + // monotonic timer is using the same time as PWM:0 + // we must adjust internal settings of monotonic timer when PWM:0 settings changed + adjustMonotonicTimer() + case 1: + mask := interrupt.Disable() + switch channel { + case 0: // channel A, PB1 + if value == 0 { + avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR1AH.Set(uint8(value >> 8)) + avr.OCR1AL.Set(uint8(value)) + if avr.PORTB.HasBits(1 << 1) { // is PB1 high? + // Yes, set the inverting bit. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) + } else { + // No, output is non-inverting. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + } + } + case 1: // channel B, PB2 + if value == 0 { + avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) + } else { + value := uint16(value) - 1 // yes, this is safe (it relies on underflow) + avr.OCR1BH.Set(uint8(value >> 8)) + avr.OCR1BL.Set(uint8(value)) + if avr.PORTB.HasBits(1 << 2) { // is PB2 high? + // Yes, set the inverting bit. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) + } else { + // No, output is non-inverting. + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + } + } + } + interrupt.Restore(mask) + case 2: + value := uint16(value) + switch channel { + case 0: // channel A + if value == 0 { + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1) + } else { + avr.OCR2A.Set(uint8(value - 1)) + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + } + case 1: // channel B + if value == 0 { + avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1) + } else { + avr.OCR2B.Set(uint8(value - 1)) + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + } + } + } +} + +// Pin Change Interrupts +type PinChange uint8 + +const ( + PinRising PinChange = 1 << iota + PinFalling + PinToggle = PinRising | PinFalling +) + +func (pin Pin) SetInterrupt(pinChange PinChange, callback func(Pin)) (err error) { + + switch { + case pin >= PB0 && pin <= PB7: + // PCMSK0 - PCINT0-7 + pinStates[0] = avr.PINB.Get() + pinIndex := pin - PB0 + if pinChange&PinRising > 0 { + pinCallbacks[0][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[0][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK0.SetBits(1 << pinIndex) + } else { + avr.PCMSK0.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE0) + interrupt.New(avr.IRQ_PCINT0, handlePCINT0Interrupts) + case pin >= PC0 && pin <= PC7: + // PCMSK1 - PCINT8-14 + pinStates[1] = avr.PINC.Get() + pinIndex := pin - PC0 + if pinChange&PinRising > 0 { + pinCallbacks[1][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[1][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK1.SetBits(1 << pinIndex) + } else { + avr.PCMSK1.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE1) + interrupt.New(avr.IRQ_PCINT1, handlePCINT1Interrupts) + case pin >= PD0 && pin <= PD7: + // PCMSK2 - PCINT16-23 + pinStates[2] = avr.PIND.Get() + pinIndex := pin - PD0 + if pinChange&PinRising > 0 { + pinCallbacks[2][pinIndex][0] = callback + } + if pinChange&PinFalling > 0 { + pinCallbacks[2][pinIndex][1] = callback + } + if callback != nil { + avr.PCMSK2.SetBits(1 << pinIndex) + } else { + avr.PCMSK2.ClearBits(1 << pinIndex) + } + avr.PCICR.SetBits(avr.PCICR_PCIE2) + interrupt.New(avr.IRQ_PCINT2, handlePCINT2Interrupts) + default: + return ErrInvalidInputPin + } + + return nil +} + +var pinCallbacks [3][8][2]func(Pin) +var pinStates [3]uint8 + +func handlePCINTInterrupts(intr uint8, port *volatile.Register8) { + current := port.Get() + change := pinStates[intr] ^ current + pinStates[intr] = current + for i := uint8(0); i < 8; i++ { + if (change>>i)&0x01 != 0x01 { + continue + } + pin := Pin(intr*8 + i) + value := pin.Get() + if value && pinCallbacks[intr][i][0] != nil { + pinCallbacks[intr][i][0](pin) + } + if !value && pinCallbacks[intr][i][1] != nil { + pinCallbacks[intr][i][1](pin) + } + } +} + +func handlePCINT0Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(0, avr.PINB) +} + +func handlePCINT1Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(1, avr.PINC) +} + +func handlePCINT2Interrupts(intr interrupt.Interrupt) { + handlePCINTInterrupts(2, avr.PIND) +} diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 0987a145c1..5bacfb8f20 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -4,569 +4,57 @@ package machine import ( "device/avr" - "runtime/interrupt" "runtime/volatile" ) const irq_USART0_RX = avr.IRQ_USART_RX -// getPortMask returns the PORTx register and mask for the pin. -func (p Pin) getPortMask() (*volatile.Register8, uint8) { - switch { - case p >= PB0 && p <= PB7: // port B - return avr.PORTB, 1 << uint8(p-portB) - case p >= PC0 && p <= PC7: // port C - return avr.PORTC, 1 << uint8(p-portC) - default: // port D - return avr.PORTD, 1 << uint8(p-portD) - } -} - -// PWM is one PWM peripheral, which consists of a counter and two output -// channels (that can be connected to two fixed pins). You can set the frequency -// using SetPeriod, but only for all the channels in this PWM peripheral at -// once. -type PWM struct { - num uint8 -} - -var ( - Timer0 = PWM{0} // 8 bit timer for PD5 and PD6 - Timer1 = PWM{1} // 16 bit timer for PB1 and PB2 - Timer2 = PWM{2} // 8 bit timer for PB3 and PD3 -) - -// Configure enables and configures this PWM. -// -// For the two 8 bit timers, there is only a limited number of periods -// available, namely the CPU frequency divided by 256 and again divided by 1, 8, -// 64, 256, or 1024. For a MCU running at 16MHz, this would be a period of 16µs, -// 128µs, 1024µs, 4096µs, or 16384µs. -func (pwm PWM) Configure(config PWMConfig) error { - switch pwm.num { - case 0, 2: // 8-bit timers (Timer/counter 0 and Timer/counter 2) - // Calculate the timer prescaler. - // While we could configure a flexible top, that would sacrifice one of - // the PWM output compare registers and thus a PWM channel. I've chosen - // to instead limit this timer to a fixed number of frequencies. - var prescaler uint8 - switch config.Period { - case 0, (uint64(1e9) * 256 * 1) / uint64(CPUFrequency()): - prescaler = 1 - case (uint64(1e9) * 256 * 8) / uint64(CPUFrequency()): - prescaler = 2 - case (uint64(1e9) * 256 * 64) / uint64(CPUFrequency()): - prescaler = 3 - case (uint64(1e9) * 256 * 256) / uint64(CPUFrequency()): - prescaler = 4 - case (uint64(1e9) * 256 * 1024) / uint64(CPUFrequency()): - prescaler = 5 - default: - return ErrPWMPeriodTooLong - } - - if pwm.num == 0 { - avr.TCCR0B.Set(prescaler) - // 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 - adjustMonotonicTimer() - } else { - avr.TCCR2B.Set(prescaler) - // Set the PWM mode to fast PWM (mode = 3). - avr.TCCR2A.Set(avr.TCCR2A_WGM20 | avr.TCCR2A_WGM21) - } - case 1: // Timer/counter 1 - // The top value is the number of PWM ticks a PWM period takes. It is - // initially picked assuming an unlimited counter top and no PWM - // prescaler. - var top uint64 - if config.Period == 0 { - // Use a top appropriate for LEDs. Picking a relatively low period - // here (0xff) for consistency with the other timers. - top = 0xff - } else { - // The formula below calculates the following formula, optimized: - // top = period * (CPUFrequency() / 1e9) - // By dividing the CPU frequency first (an operation that is easily - // optimized away) the period has less chance of overflowing. - top = config.Period * (uint64(CPUFrequency()) / 1000000) / 1000 - } - - avr.TCCR1A.Set(avr.TCCR1A_WGM11) - - // The ideal PWM period may be larger than would fit in the PWM counter, - // which is 16 bits (see maxTop). Therefore, try to make the PWM clock - // speed lower with a prescaler to make the top value fit the maximum - // top value. - const maxTop = 0x10000 - switch { - case top <= maxTop: - avr.TCCR1B.Set(3<<3 | 1) // no prescaling - case top/8 <= maxTop: - avr.TCCR1B.Set(3<<3 | 2) // divide by 8 - top /= 8 - case top/64 <= maxTop: - avr.TCCR1B.Set(3<<3 | 3) // divide by 64 - top /= 64 - case top/256 <= maxTop: - avr.TCCR1B.Set(3<<3 | 4) // divide by 256 - top /= 256 - case top/1024 <= maxTop: - avr.TCCR1B.Set(3<<3 | 5) // divide by 1024 - top /= 1024 - default: - return ErrPWMPeriodTooLong - } - - // A top of 0x10000 is at 100% duty cycle. Subtract one because the - // counter counts from 0, not 1 (avoiding an off-by-one). - top -= 1 - - avr.ICR1H.Set(uint8(top >> 8)) - avr.ICR1L.Set(uint8(top)) - } - return nil -} - -// SetPeriod updates the period of this PWM peripheral. -// To set a particular frequency, use the following formula: -// -// period = 1e9 / frequency -// -// If you use a period of 0, a period that works well for LEDs will be picked. -// -// SetPeriod will not change the prescaler, but also won't change the current -// value in any of the channels. This means that you may need to update the -// value for the particular channel. -// -// Note that you cannot pick any arbitrary period after the PWM peripheral has -// been configured. If you want to switch between frequencies, pick the lowest -// frequency (longest period) once when calling Configure and adjust the -// frequency here as needed. -func (pwm PWM) SetPeriod(period uint64) error { - if pwm.num != 1 { - return ErrPWMPeriodTooLong // TODO better error message - } - - // The top value is the number of PWM ticks a PWM period takes. It is - // initially picked assuming an unlimited counter top and no PWM - // prescaler. - var top uint64 - if period == 0 { - // Use a top appropriate for LEDs. Picking a relatively low period - // here (0xff) for consistency with the other timers. - top = 0xff - } else { - // The formula below calculates the following formula, optimized: - // top = period * (CPUFrequency() / 1e9) - // By dividing the CPU frequency first (an operation that is easily - // optimized away) the period has less chance of overflowing. - top = period * (uint64(CPUFrequency()) / 1000000) / 1000 - } - - prescaler := avr.TCCR1B.Get() & 0x7 - switch prescaler { - case 1: - top /= 1 - case 2: - top /= 8 - case 3: - top /= 64 - case 4: - top /= 256 - case 5: - top /= 1024 - } - - // A top of 0x10000 is at 100% duty cycle. Subtract one because the counter - // counts from 0, not 1 (avoiding an off-by-one). - top -= 1 - - if top > 0xffff { - return ErrPWMPeriodTooLong - } - - // Warning: this change is not atomic! - avr.ICR1H.Set(uint8(top >> 8)) - avr.ICR1L.Set(uint8(top)) - - // ... and because of that, set the counter back to zero to avoid most of - // the effects of this non-atomicity. - avr.TCNT1H.Set(0) - avr.TCNT1L.Set(0) - - return nil -} - -// Top returns the current counter top, for use in duty cycle calculation. It -// will only change with a call to Configure or SetPeriod, otherwise it is -// constant. -// -// The value returned here is hardware dependent. In general, it's best to treat -// it as an opaque value that can be divided by some number and passed to Set -// (see Set documentation for more information). -func (pwm PWM) Top() uint32 { - if pwm.num == 1 { - // Timer 1 has a configurable top value. - low := avr.ICR1L.Get() - high := avr.ICR1H.Get() - return uint32(high)<<8 | uint32(low) + 1 - } - // Other timers go from 0 to 0xff (0x100 or 256 in total). - return 256 -} - -// Counter returns the current counter value of the timer in this PWM -// peripheral. It may be useful for debugging. -func (pwm PWM) Counter() uint32 { - switch pwm.num { - case 0: - return uint32(avr.TCNT0.Get()) - case 1: - mask := interrupt.Disable() - low := avr.TCNT1L.Get() - high := avr.TCNT1H.Get() - interrupt.Restore(mask) - return uint32(high)<<8 | uint32(low) - case 2: - return uint32(avr.TCNT2.Get()) - } - // Unknown PWM. - return 0 -} - -// Period returns the used PWM period in nanoseconds. It might deviate slightly -// from the configured period due to rounding. -func (pwm PWM) Period() uint64 { - var prescaler uint8 - switch pwm.num { - case 0: - prescaler = avr.TCCR0B.Get() & 0x7 - case 1: - prescaler = avr.TCCR1B.Get() & 0x7 - case 2: - prescaler = avr.TCCR2B.Get() & 0x7 - } - top := uint64(pwm.Top()) - switch prescaler { - case 1: // prescaler 1 - return 1 * top * 1000 / uint64(CPUFrequency()/1e6) - case 2: // prescaler 8 - return 8 * top * 1000 / uint64(CPUFrequency()/1e6) - case 3: // prescaler 64 - return 64 * top * 1000 / uint64(CPUFrequency()/1e6) - case 4: // prescaler 256 - return 256 * top * 1000 / uint64(CPUFrequency()/1e6) - case 5: // prescaler 1024 - return 1024 * top * 1000 / uint64(CPUFrequency()/1e6) - default: // unknown clock source - return 0 - } -} - -// Channel returns a PWM channel for the given pin. -func (pwm PWM) Channel(pin Pin) (uint8, error) { - pin.Configure(PinConfig{Mode: PinOutput}) - pin.Low() - switch pwm.num { - case 0: - switch pin { - case PD6: // channel A - avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) - return 0, nil - case PD5: // channel B - avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) - return 1, nil - } - case 1: - switch pin { - case PB1: // channel A - avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) - return 0, nil - case PB2: // channel B - avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) - return 1, nil - } - case 2: - switch pin { - case PB3: // channel A - avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) - return 0, nil - case PD3: // channel B - avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) - return 1, nil - } - } - return 0, ErrInvalidOutputPin -} - -// SetInverting sets whether to invert the output of this channel. -// Without inverting, a 25% duty cycle would mean the output is high for 25% of -// the time and low for the rest. Inverting flips the output as if a NOT gate -// was placed at the output, meaning that the output would be 25% low and 75% -// high with a duty cycle of 25%. -// -// Note: the invert state may not be applied on the AVR until the next call to -// ch.Set(). -func (pwm PWM) SetInverting(channel uint8, inverting bool) { - switch pwm.num { - case 0: - switch channel { - case 0: // channel A - if inverting { - avr.PORTB.SetBits(1 << 6) // PB6 high - avr.TCCR0A.SetBits(avr.TCCR0A_COM0A0) - } else { - avr.PORTB.ClearBits(1 << 6) // PB6 low - avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A0) - } - case 1: // channel B - if inverting { - avr.PORTB.SetBits(1 << 5) // PB5 high - avr.TCCR0A.SetBits(avr.TCCR0A_COM0B0) - } else { - avr.PORTB.ClearBits(1 << 5) // PB5 low - avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B0) - } - } - case 1: - // Note: the COM1A0/COM1B0 bit is not set with the configuration below. - // It will be set the following call to Set(), however. - switch channel { - case 0: // channel A, PB1 - if inverting { - avr.PORTB.SetBits(1 << 1) // PB1 high - } else { - avr.PORTB.ClearBits(1 << 1) // PB1 low - } - case 1: // channel B, PB2 - if inverting { - avr.PORTB.SetBits(1 << 2) // PB2 high - } else { - avr.PORTB.ClearBits(1 << 2) // PB2 low - } - } - case 2: - switch channel { - case 0: // channel A - if inverting { - avr.PORTB.SetBits(1 << 3) // PB3 high - avr.TCCR2A.SetBits(avr.TCCR2A_COM2A0) - } else { - avr.PORTB.ClearBits(1 << 3) // PB3 low - avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A0) - } - case 1: // channel B - if inverting { - avr.PORTD.SetBits(1 << 3) // PD3 high - avr.TCCR2A.SetBits(avr.TCCR2A_COM2B0) - } else { - avr.PORTD.ClearBits(1 << 3) // PD3 low - avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B0) - } - } - } -} - -// Set updates the channel value. This is used to control the channel duty -// cycle, in other words the fraction of time the channel output is high (or low -// when inverted). For example, to set it to a 25% duty cycle, use: -// -// pwm.Set(channel, pwm.Top() / 4) -// -// pwm.Set(channel, 0) will set the output to low and pwm.Set(channel, -// pwm.Top()) will set the output to high, assuming the output isn't inverted. -func (pwm PWM) Set(channel uint8, value uint32) { - switch pwm.num { - case 0: - value := uint16(value) - switch channel { - case 0: // channel A - if value == 0 { - avr.TCCR0A.ClearBits(avr.TCCR0A_COM0A1) - } else { - avr.OCR0A.Set(uint8(value - 1)) - avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) - } - case 1: // channel B - if value == 0 { - avr.TCCR0A.ClearBits(avr.TCCR0A_COM0B1) - } else { - avr.OCR0B.Set(uint8(value) - 1) - avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) - } - } - // monotonic timer is using the same time as PWM:0 - // we must adust internal settings of monotonic timer when PWM:0 settings changed - adjustMonotonicTimer() - case 1: - mask := interrupt.Disable() - switch channel { - case 0: // channel A, PB1 - if value == 0 { - avr.TCCR1A.ClearBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) - } else { - value := uint16(value) - 1 // yes, this is safe (it relies on underflow) - avr.OCR1AH.Set(uint8(value >> 8)) - avr.OCR1AL.Set(uint8(value)) - if avr.PORTB.HasBits(1 << 1) { // is PB1 high? - // Yes, set the inverting bit. - avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1 | avr.TCCR1A_COM1A0) - } else { - // No, output is non-inverting. - avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) - } - } - case 1: // channel B, PB2 - if value == 0 { - avr.TCCR1A.ClearBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) - } else { - value := uint16(value) - 1 // yes, this is safe (it relies on underflow) - avr.OCR1BH.Set(uint8(value >> 8)) - avr.OCR1BL.Set(uint8(value)) - if avr.PORTB.HasBits(1 << 2) { // is PB2 high? - // Yes, set the inverting bit. - avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1 | avr.TCCR1A_COM1B0) - } else { - // No, output is non-inverting. - avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) - } - } - } - interrupt.Restore(mask) - case 2: - value := uint16(value) - switch channel { - case 0: // channel A - if value == 0 { - avr.TCCR2A.ClearBits(avr.TCCR2A_COM2A1) - } else { - avr.OCR2A.Set(uint8(value - 1)) - avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) - } - case 1: // channel B - if value == 0 { - avr.TCCR2A.ClearBits(avr.TCCR2A_COM2B1) - } else { - avr.OCR2B.Set(uint8(value - 1)) - avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) - } - } - } +// I2C0 is the only I2C interface on most AVRs. +var I2C0 = &I2C{ + srReg: avr.TWSR, + brReg: avr.TWBR, + crReg: avr.TWCR, + drReg: avr.TWDR, + srPS0: avr.TWSR_TWPS0, + srPS1: avr.TWSR_TWPS1, + crEN: avr.TWCR_TWEN, + crINT: avr.TWCR_TWINT, + crSTO: avr.TWCR_TWSTO, + crEA: avr.TWCR_TWEA, + crSTA: avr.TWCR_TWSTA, } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR, spdr: avr.SPDR, spsr: avr.SPSR, - sck: PB5, - sdo: PB3, - sdi: PB4, - cs: PB2} - -// Pin Change Interrupts -type PinChange uint8 -const ( - PinRising PinChange = 1 << iota - PinFalling - PinToggle = PinRising | PinFalling -) + spcrR0: avr.SPCR_SPR0, + spcrR1: avr.SPCR_SPR1, + spcrCPHA: avr.SPCR_CPHA, + spcrCPOL: avr.SPCR_CPOL, + spcrDORD: avr.SPCR_DORD, + spcrSPE: avr.SPCR_SPE, + spcrMSTR: avr.SPCR_MSTR, -func (pin Pin) SetInterrupt(pinChange PinChange, callback func(Pin)) (err error) { + spsrI2X: avr.SPSR_SPI2X, + spsrSPIF: avr.SPSR_SPIF, - switch { - case pin >= PB0 && pin <= PB7: - // PCMSK0 - PCINT0-7 - pinStates[0] = avr.PINB.Get() - pinIndex := pin - PB0 - if pinChange&PinRising > 0 { - pinCallbacks[0][pinIndex][0] = callback - } - if pinChange&PinFalling > 0 { - pinCallbacks[0][pinIndex][1] = callback - } - if callback != nil { - avr.PCMSK0.SetBits(1 << pinIndex) - } else { - avr.PCMSK0.ClearBits(1 << pinIndex) - } - avr.PCICR.SetBits(avr.PCICR_PCIE0) - interrupt.New(avr.IRQ_PCINT0, handlePCINT0Interrupts) - case pin >= PC0 && pin <= PC7: - // PCMSK1 - PCINT8-14 - pinStates[1] = avr.PINC.Get() - pinIndex := pin - PC0 - if pinChange&PinRising > 0 { - pinCallbacks[1][pinIndex][0] = callback - } - if pinChange&PinFalling > 0 { - pinCallbacks[1][pinIndex][1] = callback - } - if callback != nil { - avr.PCMSK1.SetBits(1 << pinIndex) - } else { - avr.PCMSK1.ClearBits(1 << pinIndex) - } - avr.PCICR.SetBits(avr.PCICR_PCIE1) - interrupt.New(avr.IRQ_PCINT1, handlePCINT1Interrupts) - case pin >= PD0 && pin <= PD7: - // PCMSK2 - PCINT16-23 - pinStates[2] = avr.PIND.Get() - pinIndex := pin - PD0 - if pinChange&PinRising > 0 { - pinCallbacks[2][pinIndex][0] = callback - } - if pinChange&PinFalling > 0 { - pinCallbacks[2][pinIndex][1] = callback - } - if callback != nil { - avr.PCMSK2.SetBits(1 << pinIndex) - } else { - avr.PCMSK2.ClearBits(1 << pinIndex) - } - avr.PCICR.SetBits(avr.PCICR_PCIE2) - interrupt.New(avr.IRQ_PCINT2, handlePCINT2Interrupts) - default: - return ErrInvalidInputPin - } - - return nil + sck: PB5, + sdo: PB3, + sdi: PB4, + cs: PB2, } -var pinCallbacks [3][8][2]func(Pin) -var pinStates [3]uint8 - -func handlePCINTInterrupts(intr uint8, port *volatile.Register8) { - current := port.Get() - change := pinStates[intr] ^ current - pinStates[intr] = current - for i := uint8(0); i < 8; i++ { - if (change>>i)&0x01 != 0x01 { - continue - } - pin := Pin(intr*8 + i) - value := pin.Get() - if value && pinCallbacks[intr][i][0] != nil { - pinCallbacks[intr][i][0](pin) - } - if !value && pinCallbacks[intr][i][1] != nil { - pinCallbacks[intr][i][1](pin) - } +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: // port C + return avr.PORTC, 1 << uint8(p-portC) + default: // port D + return avr.PORTD, 1 << uint8(p-portD) } } - -func handlePCINT0Interrupts(intr interrupt.Interrupt) { - handlePCINTInterrupts(0, avr.PINB) -} - -func handlePCINT1Interrupts(intr interrupt.Interrupt) { - handlePCINTInterrupts(1, avr.PINC) -} - -func handlePCINT2Interrupts(intr interrupt.Interrupt) { - handlePCINTInterrupts(2, avr.PIND) -} diff --git a/src/machine/machine_atmega328pb.go b/src/machine/machine_atmega328pb.go index af36b63e1d..935c581d54 100644 --- a/src/machine/machine_atmega328pb.go +++ b/src/machine/machine_atmega328pb.go @@ -4,106 +4,116 @@ package machine import ( "device/avr" + "runtime/interrupt" "runtime/volatile" ) const irq_USART0_RX = avr.IRQ_USART0_RX +const irq_USART1_RX = avr.IRQ_USART1_RX -// getPortMask returns the PORTx register and mask for the pin. -func (p Pin) getPortMask() (*volatile.Register8, uint8) { - switch { - case p >= PB0 && p <= PB7: // port B - return avr.PORTB, 1 << uint8(p-portB) - case p >= PC0 && p <= PC7: // port C - return avr.PORTC, 1 << uint8(p-portC) - default: // port D - return avr.PORTD, 1 << uint8(p-portD) - } -} +var ( + UART1 = &_UART1 + _UART1 = UART{ + Buffer: NewRingBuffer(), -// InitPWM initializes the registers needed for PWM. -func InitPWM() { - // use waveform generation - avr.TCCR0A.SetBits(avr.TCCR0A_WGM00) - - // set timer 0 prescale factor to 64 - avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00) - - // set timer 1 prescale factor to 64 - avr.TCCR1B.SetBits(avr.TCCR1B_CS11) - - // put timer 1 in 8-bit phase correct pwm mode - avr.TCCR1A.SetBits(avr.TCCR1A_WGM10) - - // set timer 2 prescale factor to 64 - avr.TCCR2B.SetBits(avr.TCCR2B_CS22) + dataReg: avr.UDR1, + baudRegH: avr.UBRR1H, + baudRegL: avr.UBRR1L, + statusRegA: avr.UCSR1A, + statusRegB: avr.UCSR1B, + statusRegC: avr.UCSR1C, + } +) - // configure timer 2 for phase correct pwm (8-bit) - avr.TCCR2A.SetBits(avr.TCCR2A_WGM20) +func init() { + // Register the UART interrupt. + interrupt.New(irq_USART1_RX, _UART1.handleInterrupt) } -// Configure configures a PWM pin for output. -func (pwm PWM) Configure() error { - switch pwm.Pin / 8 { - case 0: // port B - avr.DDRB.SetBits(1 << uint8(pwm.Pin)) - case 2: // port D - avr.DDRD.SetBits(1 << uint8(pwm.Pin-16)) - } - return nil +// I2C0 is the only I2C interface on most AVRs. +var I2C0 = &I2C{ + srReg: avr.TWSR0, + brReg: avr.TWBR0, + crReg: avr.TWCR0, + drReg: avr.TWDR0, + srPS0: avr.TWSR0_TWPS0, + srPS1: avr.TWSR0_TWPS1, + crEN: avr.TWCR0_TWEN, + crINT: avr.TWCR0_TWINT, + crSTO: avr.TWCR0_TWSTO, + crEA: avr.TWCR0_TWEA, + crSTA: avr.TWCR0_TWSTA, } -// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a -// 8-bit value ranging from 0 to 255. -func (pwm PWM) Set(value uint16) { - value8 := uint8(value >> 8) - switch pwm.Pin { - case PD3: - // connect pwm to pin on timer 2, channel B - avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) - avr.OCR2B.Set(value8) // set pwm duty - case PD5: - // connect pwm to pin on timer 0, channel B - avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) - avr.OCR0B.Set(value8) // set pwm duty - case PD6: - // connect pwm to pin on timer 0, channel A - avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) - avr.OCR0A.Set(value8) // set pwm duty - case PB1: - // connect pwm to pin on timer 1, channel A - avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) - // this is a 16-bit value, but we only currently allow the low order bits to be set - avr.OCR1AL.Set(value8) // set pwm duty - case PB2: - // connect pwm to pin on timer 1, channel B - avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) - // this is a 16-bit value, but we only currently allow the low order bits to be set - avr.OCR1BL.Set(value8) // set pwm duty - case PB3: - // connect pwm to pin on timer 2, channel A - avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) - avr.OCR2A.Set(value8) // set pwm duty - default: - panic("Invalid PWM pin") - } +var I2C1 = &I2C{ + srReg: avr.TWSR1, + brReg: avr.TWBR1, + crReg: avr.TWCR1, + drReg: avr.TWDR1, + srPS0: avr.TWSR1_TWPS10, + srPS1: avr.TWSR1_TWPS11, + crEN: avr.TWCR1_TWEN1, + crINT: avr.TWCR1_TWINT1, + crSTO: avr.TWCR1_TWSTO1, + crEA: avr.TWCR1_TWEA1, + crSTA: avr.TWCR1_TWSTA1, } // SPI configuration -var SPI0 = SPI{ +var SPI0 = &SPI{ spcr: avr.SPCR0, spdr: avr.SPDR0, spsr: avr.SPSR0, - sck: PB5, - sdo: PB3, - sdi: PB4, - cs: PB2} -var SPI1 = SPI{ + spcrR0: avr.SPCR0_SPR0, + spcrR1: avr.SPCR0_SPR1, + spcrCPHA: avr.SPCR0_CPHA, + spcrCPOL: avr.SPCR0_CPOL, + spcrDORD: avr.SPCR0_DORD, + spcrSPE: avr.SPCR0_SPE, + spcrMSTR: avr.SPCR0_MSTR, + + spsrI2X: avr.SPSR0_SPI2X, + spsrSPIF: avr.SPSR0_SPIF, + + sck: PB5, + sdo: PB3, + sdi: PB4, + cs: PB2, +} + +var SPI1 = &SPI{ spcr: avr.SPCR1, spdr: avr.SPDR1, spsr: avr.SPSR1, - sck: PC1, - sdo: PE3, - sdi: PC0, - cs: PE2} + + spcrR0: avr.SPCR1_SPR10, + spcrR1: avr.SPCR1_SPR11, + spcrCPHA: avr.SPCR1_CPHA1, + spcrCPOL: avr.SPCR1_CPOL1, + spcrDORD: avr.SPCR1_DORD1, + spcrSPE: avr.SPCR1_SPE1, + spcrMSTR: avr.SPCR1_MSTR1, + + spsrI2X: avr.SPSR1_SPI2X1, + spsrSPIF: avr.SPSR1_SPIF1, + + sck: PC1, + sdo: PE3, + sdi: PC0, + cs: PE2, +} + +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: // port C + return avr.PORTC, 1 << uint8(p-portC) + case p >= PD0 && p <= PD7: // port D + return avr.PORTD, 1 << uint8(p-portD) + default: // port E + return avr.PORTE, 1 << uint8(p-portE) + } +} diff --git a/src/machine/machine_atsam.go b/src/machine/machine_atsam.go new file mode 100644 index 0000000000..ad2f073786 --- /dev/null +++ b/src/machine/machine_atsam.go @@ -0,0 +1,31 @@ +//go:build sam + +package machine + +import ( + "runtime/volatile" + "unsafe" +) + +var deviceID [16]byte + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself, or the +// flash chip at time of manufacture. +// +// It's possible that two different vendors may allocate +// the same DeviceID, so callers should take this into +// account if needing to generate a globally unique id. +// +// The length of the hardware ID is vendor-specific, but +// 8 bytes (64 bits) and 16 bytes (128 bits) are common. +func DeviceID() []byte { + for i := 0; i < len(deviceID); i++ { + word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get() + deviceID[i] = byte(word >> ((i % 4) * 8)) + } + + return deviceID[:] +} diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index 443c7af56a..e0c5f2cc29 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -7,17 +7,19 @@ package machine import ( - "bytes" "device/arm" "device/sam" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "unsafe" ) const deviceName = sam.Device +// DS40001882F, Section 10.3.3: Serial Number +var deviceIDAddr = []uintptr{0x0080A00C, 0x0080A040, 0x0080A044, 0x0080A048} + const ( PinAnalog PinMode = 1 PinSERCOM PinMode = 2 @@ -531,16 +533,16 @@ func (uart *UART) Configure(config UARTConfig) error { if !ok { return ErrInvalidOutputPin } - var txPinOut uint32 + var txPadOut uint32 // See table 25-9 of the datasheet (page 459) for how pads are mapped to // pinout values. switch txPad { case 0: - txPinOut = 0 + txPadOut = 0 case 2: - txPinOut = 1 + txPadOut = 1 default: - // TODO: flow control (RTS/CTS) + // this should be a flow control (RTS/CTS) pin return ErrInvalidOutputPin } @@ -551,12 +553,35 @@ func (uart *UART) Configure(config UARTConfig) error { } // As you can see in table 25-8 on page 459 of the datasheet, input pins // are mapped directly. - rxPinOut := rxPad + rxPadOut := rxPad // configure pins config.TX.Configure(PinConfig{Mode: txPinMode}) config.RX.Configure(PinConfig{Mode: rxPinMode}) + // configure RTS/CTS pins if provided + if config.RTS != 0 && config.CTS != 0 { + rtsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.RTS) + if !ok { + return ErrInvalidOutputPin + } + + ctsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.CTS) + if !ok { + return ErrInvalidInputPin + } + + // See table 25-9 of the datasheet (page 459) for how pads are mapped to + // pinout values. + if txPadOut == 1 { + return ErrInvalidOutputPin + } + txPadOut = 2 + + config.RTS.Configure(PinConfig{Mode: rtsPinMode}) + config.CTS.Configure(PinConfig{Mode: ctsPinMode}) + } + // reset SERCOM0 uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_SWRST) for uart.Bus.CTRLA.HasBits(sam.SERCOM_USART_CTRLA_SWRST) || @@ -589,8 +614,8 @@ func (uart *UART) Configure(config UARTConfig) error { // set UART pads. This is not same as pins... // SERCOM_USART_CTRLA_TXPO(txPad) | // SERCOM_USART_CTRLA_RXPO(rxPad); - uart.Bus.CTRLA.SetBits((txPinOut << sam.SERCOM_USART_CTRLA_TXPO_Pos) | - (rxPinOut << sam.SERCOM_USART_CTRLA_RXPO_Pos)) + uart.Bus.CTRLA.SetBits((txPadOut << sam.SERCOM_USART_CTRLA_TXPO_Pos) | + (rxPadOut << sam.SERCOM_USART_CTRLA_RXPO_Pos)) // Enable Transceiver and Receiver //sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ; @@ -732,12 +757,13 @@ func (i2c *I2C) Configure(config I2CConfig) error { return nil } -// SetBaudRate sets the communication speed for the I2C. -func (i2c *I2C) SetBaudRate(br uint32) { +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { // Synchronous arithmetic baudrate, via Arduino SAMD implementation: // SystemCoreClock / ( 2 * baudrate) - 5 - (((SystemCoreClock / 1000000) * WIRE_RISE_TIME_NANOSECONDS) / (2 * 1000)); baud := CPUFrequency()/(2*br) - 5 - (((CPUFrequency() / 1000000) * riseTimeNanoseconds) / (2 * 1000)) i2c.Bus.BAUD.Set(baud) + return nil } // Tx does a single I2C transaction at the specified address. @@ -899,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 { @@ -925,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 { @@ -1040,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) @@ -1063,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 mono data from the I2S bus into the provided slice. +// The I2S bus must already have been configured correctly. +func (i2s *I2S) ReadMono(p []uint16) (n int, err error) { + return i2sRead(i2s, p) } -// Read data from the I2S bus into the provided slice. +// Read stereo 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) 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 @@ -1078,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) @@ -1087,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 @@ -1100,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) @@ -1109,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) { } @@ -1143,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 @@ -1265,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)) @@ -1277,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. // @@ -1294,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. @@ -1315,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) { } @@ -1330,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) { } @@ -1346,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) { } @@ -1432,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 } @@ -1579,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 @@ -1630,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) { @@ -1732,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() @@ -1835,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() @@ -1910,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 8d37f2fa5f..d169eab995 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -7,17 +7,19 @@ package machine import ( - "bytes" "device/arm" "device/sam" - "encoding/binary" "errors" + "internal/binary" "runtime/interrupt" "unsafe" ) const deviceName = sam.Device +// DS60001507, Section 9.6: Serial Number +var deviceIDAddr = []uintptr{0x008061FC, 0x00806010, 0x00806014, 0x00806018} + func CPUFrequency() uint32 { return 120000000 } @@ -1012,14 +1014,14 @@ func (uart *UART) Configure(config UARTConfig) error { if !ok { return ErrInvalidOutputPin } - var txPinOut uint32 + var txPadOut uint32 // See CTRLA.RXPO bits of the SERCOM USART peripheral (page 945-946) for how // pads are mapped to pinout values. switch txPad { case 0: - txPinOut = 0 + txPadOut = 0 default: - // TODO: flow control (RTS/CTS) + // should be flow control (RTS/CTS) pin return ErrInvalidOutputPin } @@ -1030,12 +1032,32 @@ func (uart *UART) Configure(config UARTConfig) error { } // As you can see in the CTRLA.RXPO bits of the SERCOM USART peripheral // (page 945), input pins are mapped directly. - rxPinOut := rxPad + rxPadOut := rxPad // configure pins config.TX.Configure(PinConfig{Mode: txPinMode}) config.RX.Configure(PinConfig{Mode: rxPinMode}) + // configure RTS/CTS pins if provided + if config.RTS != 0 && config.CTS != 0 { + rtsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.RTS) + if !ok { + return ErrInvalidOutputPin + } + + ctsPinMode, _, ok := findPinPadMapping(uart.SERCOM, config.CTS) + if !ok { + return ErrInvalidInputPin + } + + // See CTRLA.RXPO bits of the SERCOM USART peripheral (page 945-946) for how + // pads are mapped to pinout values. + txPadOut = 2 + + config.RTS.Configure(PinConfig{Mode: rtsPinMode}) + config.CTS.Configure(PinConfig{Mode: ctsPinMode}) + } + // reset SERCOM uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_INT_CTRLA_SWRST) for uart.Bus.CTRLA.HasBits(sam.SERCOM_USART_INT_CTRLA_SWRST) || @@ -1072,8 +1094,8 @@ func (uart *UART) Configure(config UARTConfig) error { // set UART pads. This is not same as pins... // SERCOM_USART_CTRLA_TXPO(txPad) | // SERCOM_USART_CTRLA_RXPO(rxPad); - uart.Bus.CTRLA.SetBits((txPinOut << sam.SERCOM_USART_INT_CTRLA_TXPO_Pos) | - (rxPinOut << sam.SERCOM_USART_INT_CTRLA_RXPO_Pos)) + uart.Bus.CTRLA.SetBits((txPadOut << sam.SERCOM_USART_INT_CTRLA_TXPO_Pos) | + (rxPadOut << sam.SERCOM_USART_INT_CTRLA_RXPO_Pos)) // Enable Transceiver and Receiver //sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ; @@ -1228,12 +1250,13 @@ func (i2c *I2C) Configure(config I2CConfig) error { return nil } -// SetBaudRate sets the communication speed for the I2C. -func (i2c *I2C) SetBaudRate(br uint32) { +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { // Synchronous arithmetic baudrate, via Adafruit SAMD51 implementation: // sercom->I2CM.BAUD.bit.BAUD = SERCOM_FREQ_REF / ( 2 * baudrate) - 1 ; baud := SERCOM_FREQ_REF/(2*br) - 1 i2c.Bus.BAUD.Set(baud) + return nil } // Tx does a single I2C transaction at the specified address. @@ -1408,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 @@ -1551,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)) @@ -1563,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. // @@ -1580,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. @@ -1601,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) { } @@ -1616,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) { } @@ -1632,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) { } @@ -1696,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 } @@ -1904,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) { @@ -2150,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) @@ -2239,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 4491b3dd95..237a1292c2 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -237,13 +237,13 @@ func (p Pin) mux() *volatile.Register32 { case 27: return &esp.IO_MUX.GPIO27 case 14: - return &esp.IO_MUX.MTMS + return &esp.IO_MUX.GPIO14 case 12: - return &esp.IO_MUX.MTDI + return &esp.IO_MUX.GPIO12 case 13: - return &esp.IO_MUX.MTCK + return &esp.IO_MUX.GPIO13 case 15: - return &esp.IO_MUX.MTDO + return &esp.IO_MUX.GPIO15 case 2: return &esp.IO_MUX.GPIO2 case 0: @@ -255,17 +255,17 @@ func (p Pin) mux() *volatile.Register32 { case 17: return &esp.IO_MUX.GPIO17 case 9: - return &esp.IO_MUX.SD_DATA2 + return &esp.IO_MUX.GPIO9 case 10: - return &esp.IO_MUX.SD_DATA3 + return &esp.IO_MUX.GPIO10 case 11: - return &esp.IO_MUX.SD_CMD + return &esp.IO_MUX.GPIO11 case 6: - return &esp.IO_MUX.SD_CLK + return &esp.IO_MUX.GPIO6 case 7: - return &esp.IO_MUX.SD_DATA0 + return &esp.IO_MUX.GPIO7 case 8: - return &esp.IO_MUX.SD_DATA1 + return &esp.IO_MUX.GPIO8 case 5: return &esp.IO_MUX.GPIO5 case 18: @@ -279,9 +279,9 @@ func (p Pin) mux() *volatile.Register32 { case 22: return &esp.IO_MUX.GPIO22 case 3: - return &esp.IO_MUX.U0RXD + return &esp.IO_MUX.GPIO3 case 1: - return &esp.IO_MUX.U0TXD + return &esp.IO_MUX.GPIO1 case 23: return &esp.IO_MUX.GPIO23 case 24: @@ -320,7 +320,8 @@ func (uart *UART) writeByte(b byte) error { // many bytes there are in the transmit buffer. Wait until there are // less than 128 bytes in this buffer (the default buffer size). } - uart.Bus.TX_FIFO.Set(b) + // Write to the TX_FIFO register. + (*volatile.Register8)(unsafe.Add(unsafe.Pointer(uart.Bus), 0x200C0000)).Set(b) return nil } @@ -333,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 @@ -353,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 } @@ -444,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) @@ -459,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 e447e4fe0d..727fcc1e64 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -111,6 +111,10 @@ func (p Pin) outFunc() *volatile.Register32 { return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4)) } +func (p Pin) pinReg() *volatile.Register32 { + return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4))) +} + // inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input // function selection. func inFunc(signal uint32) *volatile.Register32 { @@ -188,7 +192,7 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { if callback == nil { // Disable this pin interrupt - p.pin().ClearBits(esp.GPIO_PIN_PIN_INT_TYPE_Msk | esp.GPIO_PIN_PIN_INT_ENA_Msk) + p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk) if pinCallbacks[p] != nil { pinCallbacks[p] = nil @@ -212,8 +216,8 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) { } p.pin().Set( - (p.pin().Get() & ^uint32(esp.GPIO_PIN_PIN_INT_TYPE_Msk|esp.GPIO_PIN_PIN_INT_ENA_Msk)) | - uint32(change)< 0 { riscv.Asm("nop") @@ -415,7 +419,7 @@ func (uart *UART) setupPins(config UARTConfig, regs registerSet) { // link TX with GPIO signal X (technical reference manual 5.10) (this is not interrupt signal!) config.TX.outFunc().Set(regs.gpioMatrixSignal) // link RX with GPIO signal X and route signals via GPIO matrix (GPIO_SIGn_IN_SEL 0x40) - inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SIG_IN_SEL | uint32(config.RX)) + inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)) } func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) { // Disable all UART interrupts @@ -476,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) } @@ -504,3 +508,130 @@ func (uart *UART) writeByte(b byte) error { } func (uart *UART) flush() {} + +type Serialer interface { + WriteByte(c byte) error + Write(data []byte) (n int, err error) + Configure(config UARTConfig) error + Buffered() int + ReadByte() (byte, error) + DTR() bool + RTS() bool +} + +// USB Serial/JTAG Controller +// See esp32-c3_technical_reference_manual_en.pdf +// pg. 736 +type USB_DEVICE struct { + Bus *esp.USB_DEVICE_Type +} + +var ( + _USBCDC = &USB_DEVICE{ + Bus: esp.USB_DEVICE, + } + + USBCDC Serialer = _USBCDC +) + +var ( + errUSBWrongSize = errors.New("USB: invalid write size") + errUSBCouldNotWriteAllData = errors.New("USB: could not write all data") + errUSBBufferEmpty = errors.New("USB: read buffer empty") +) + +func (usbdev *USB_DEVICE) Configure(config UARTConfig) error { + return nil +} + +func (usbdev *USB_DEVICE) WriteByte(c byte) error { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + return errUSBCouldNotWriteAllData + } + + usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) + usbdev.flush() + + return nil +} + +func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) { + if len(data) == 0 || len(data) > 64 { + return 0, errUSBWrongSize + } + + for i, c := range data { + if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + if i > 0 { + usbdev.flush() + } + + return i, errUSBCouldNotWriteAllData + } + usbdev.Bus.SetEP1_RDWR_BYTE(uint32(c)) + } + + usbdev.flush() + return len(data), nil +} + +func (usbdev *USB_DEVICE) Buffered() int { + return int(usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL()) +} + +func (usbdev *USB_DEVICE) ReadByte() (byte, error) { + if usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 { + return byte(usbdev.Bus.GetEP1_RDWR_BYTE()), nil + } + + return 0, nil +} + +func (usbdev *USB_DEVICE) DTR() bool { + return false +} + +func (usbdev *USB_DEVICE) RTS() bool { + return false +} + +func (usbdev *USB_DEVICE) flush() { + usbdev.Bus.SetEP1_CONF_WR_DONE(1) + for usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 { + } +} + +// GetRNG returns 32-bit random numbers using the ESP32-C3 true random number generator, +// Random numbers are generated based on the thermal noise in the system and the +// asynchronous clock mismatch. +// For maximum entropy also make sure that the SAR_ADC is enabled. +// See esp32-c3_technical_reference_manual_en.pdf p.524 +func GetRNG() (ret uint32, err error) { + // ensure ADC clock is initialized + initADCClock() + + // ensure fast RTC clock is enabled + if esp.RTC_CNTL.GetCLK_CONF_DIG_CLK8M_EN() == 0 { + esp.RTC_CNTL.SetCLK_CONF_DIG_CLK8M_EN(1) + } + + return esp.APB_CTRL.GetRND_DATA(), nil +} + +func initADCClock() { + if esp.APB_SARADC.GetCLKM_CONF_CLK_EN() == 1 { + return + } + + // only support ADC_CTRL_CLK set to 1 + esp.APB_SARADC.SetCLKM_CONF_CLK_SEL(1) + + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_GATED(1) + + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_NUM(15) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_B(1) + esp.APB_SARADC.SetCLKM_CONF_CLKM_DIV_A(0) + + esp.APB_SARADC.SetCTRL_SARADC_SAR_CLK_DIV(1) + esp.APB_SARADC.SetCLKM_CONF_CLK_EN(1) +} diff --git a/src/machine/machine_esp32c3_i2c.go b/src/machine/machine_esp32c3_i2c.go new file mode 100644 index 0000000000..dd334b0db7 --- /dev/null +++ b/src/machine/machine_esp32c3_i2c.go @@ -0,0 +1,353 @@ +//go:build esp32c3 && !m5stamp_c3 + +package machine + +import ( + "device/esp" + "runtime/volatile" + "unsafe" +) + +var ( + I2C0 = &I2C{} +) + +type I2C struct{} + +// I2CConfig is used to store config info for I2C. +type I2CConfig struct { + Frequency uint32 // in Hz + SCL Pin + SDA Pin +} + +const ( + clkXTAL = 0 + clkFOSC = 1 + clkXTALFrequency = uint32(40e6) + clkFOSCFrequency = uint32(17.5e6) + i2cClkSourceFrequency = clkXTALFrequency + i2cClkSource = clkXTAL +) + +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.initClock(config) + i2c.initNoiseFilter() + i2c.initPins(config) + i2c.initFrequency(config) + i2c.startMaster() + return nil +} + +//go:inline +func (i2c *I2C) initClock(config I2CConfig) { + // reset I2C clock + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0) + // disable interrupts + esp.I2C0.INT_ENA.ClearBits(0x3fff) + esp.I2C0.INT_CLR.ClearBits(0x3fff) + + esp.I2C0.SetCLK_CONF_SCLK_SEL(i2cClkSource) + esp.I2C0.SetCLK_CONF_SCLK_ACTIVE(1) + esp.I2C0.SetCLK_CONF_SCLK_DIV_NUM(i2cClkSourceFrequency / (config.Frequency * 1024)) + esp.I2C0.SetCTR_CLK_EN(1) +} + +//go:inline +func (i2c *I2C) initNoiseFilter() { + esp.I2C0.FILTER_CFG.Set(0x377) +} + +//go:inline +func (i2c *I2C) initPins(config I2CConfig) { + var muxConfig uint32 + const function = 1 // function 1 is just GPIO + + // SDA + muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos + config.SDA.mux().Set(muxConfig) + config.SDA.outFunc().Set(54) + inFunc(54).Set(uint32(esp.GPIO_FUNC_IN_SEL_CFG_SEL | config.SDA)) + config.SDA.Set(true) + // Configure the pad with the given IO mux configuration. + config.SDA.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE.SetBits(1 << int(config.SDA)) + esp.I2C0.SetCTR_SDA_FORCE_OUT(1) + + // SCL + muxConfig = function << esp.IO_MUX_GPIO_MCU_SEL_Pos + // Make this pin an input pin (always). + muxConfig |= esp.IO_MUX_GPIO_FUN_IE + // Set drive strength: 0 is lowest, 3 is highest. + muxConfig |= 1 << esp.IO_MUX_GPIO_FUN_DRV_Pos + config.SCL.mux().Set(muxConfig) + config.SCL.outFunc().Set(53) + inFunc(53).Set(uint32(config.SCL)) + config.SCL.Set(true) + // Configure the pad with the given IO mux configuration. + config.SCL.pinReg().SetBits(esp.GPIO_PIN_PAD_DRIVER) + + esp.GPIO.ENABLE.SetBits(1 << int(config.SCL)) + esp.I2C0.SetCTR_SCL_FORCE_OUT(1) +} + +//go:inline +func (i2c *I2C) initFrequency(config I2CConfig) { + + clkmDiv := i2cClkSourceFrequency/(config.Frequency*1024) + 1 + sclkFreq := i2cClkSourceFrequency / clkmDiv + halfCycle := sclkFreq / config.Frequency / 2 + //SCL + sclLow := halfCycle + sclWaitHigh := uint32(0) + if 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 + + esp.I2C0.SetSCL_LOW_PERIOD(sclLow - 1) + esp.I2C0.SetSCL_HIGH_PERIOD(sclHigh) + esp.I2C0.SetSCL_HIGH_PERIOD_SCL_WAIT_HIGH_PERIOD(25) + esp.I2C0.SetSCL_RSTART_SETUP_TIME(setup) + esp.I2C0.SetSCL_STOP_SETUP_TIME(setup) + esp.I2C0.SetSCL_START_HOLD_TIME(hold - 1) + esp.I2C0.SetSCL_STOP_HOLD_TIME(hold - 1) + esp.I2C0.SetSDA_SAMPLE_TIME(sda_sample) + esp.I2C0.SetSDA_HOLD_TIME(sdaHold) +} + +//go:inline +func (i2c *I2C) startMaster() { + // FIFO mode for data + esp.I2C0.SetFIFO_CONF_NONFIFO_EN(0) + // Reset TX & RX buffers + esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(1) + esp.I2C0.SetFIFO_CONF_RX_FIFO_RST(0) + esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(1) + esp.I2C0.SetFIFO_CONF_TX_FIFO_RST(0) + // set timeout value + esp.I2C0.TO.Set(0x10) + // enable master mode + esp.I2C0.CTR.Set(0x113) + esp.I2C0.SetCTR_CONF_UPGATE(1) + resetMaster() +} + +//go:inline +func resetMaster() { + // reset FSM + esp.I2C0.SetCTR_FSM_RST(1) + // clear the bus + esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(9) + esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_EN(1) + esp.I2C0.SetSCL_STRETCH_CONF_SLAVE_SCL_STRETCH_EN(1) + esp.I2C0.SetCTR_CONF_UPGATE(1) + esp.I2C0.FILTER_CFG.Set(0x377) + // wait for SCL_RST_SLV_EN + for esp.I2C0.GetSCL_SP_CONF_SCL_RST_SLV_EN() != 0 { + } + esp.I2C0.SetSCL_SP_CONF_SCL_RST_SLV_NUM(0) +} + +type i2cCommandType = uint32 +type i2cAck = uint32 + +const ( + i2cCMD_RSTART i2cCommandType = 6 << 11 + i2cCMD_WRITE i2cCommandType = 1<<11 | 1<<8 // WRITE + ack_check_en + i2cCMD_READ i2cCommandType = 3<<11 | 1<<8 // READ + ack_check_en + i2cCMD_READLAST i2cCommandType = 3<<11 | 5<<8 // READ + ack_check_en + NACK + i2cCMD_STOP i2cCommandType = 2 << 11 + i2cCMD_END i2cCommandType = 4 << 11 +) + +type i2cCommand struct { + cmd i2cCommandType + data []byte + head int +} + +//go:linkname nanotime runtime.nanotime +func nanotime() int64 + +func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error { + const intMask = esp.I2C_INT_STATUS_END_DETECT_INT_ST_Msk | esp.I2C_INT_STATUS_TRANS_COMPLETE_INT_ST_Msk | esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk | esp.I2C_INT_STATUS_NACK_INT_ST_Msk + esp.I2C0.INT_CLR.SetBits(intMask) + esp.I2C0.INT_ENA.SetBits(intMask) + esp.I2C0.SetCTR_CONF_UPGATE(1) + + defer func() { + esp.I2C0.INT_CLR.SetBits(intMask) + esp.I2C0.INT_ENA.ClearBits(intMask) + }() + + timeoutNS := int64(timeoutMS) * 1000000 + needAddress := true + needRestart := false + readLast := false + var readTo []byte + for cmdIdx, reg := 0, &esp.I2C0.COMD0; cmdIdx < len(cmd); { + c := &cmd[cmdIdx] + + switch c.cmd { + case i2cCMD_RSTART: + reg.Set(i2cCMD_RSTART) + reg = nextAddress(reg) + cmdIdx++ + + case i2cCMD_WRITE: + count := 32 + if needAddress { + needAddress = false + esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr) & 0x7f) << 1) + count-- + esp.I2C0.SLAVE_ADDR.Set(uint32(addr)) + esp.I2C0.SetCTR_CONF_UPGATE(1) + } + for ; count > 0 && c.head < len(c.data); count, c.head = count-1, c.head+1 { + esp.I2C0.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 + esp.I2C0.SetDATA_FIFO_RDATA((uint32(addr)&0x7f)<<1 | 1) + esp.I2C0.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) + esp.I2C0.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 + } + 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 + esp.I2C0.SetCTR_CONF_UPGATE(1) + esp.I2C0.SetCTR_TRANS_START(1) + end := nanotime() + timeoutNS + var mask uint32 + for mask = esp.I2C0.INT_STATUS.Get(); mask&intMask == 0; mask = esp.I2C0.INT_STATUS.Get() { + if nanotime() > end { + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + } + switch { + case mask&esp.I2C_INT_STATUS_NACK_INT_ST_Msk != 0 && !readLast: + return errI2CAckExpected + case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: + if readTo != nil { + return errI2CReadTimeout + } + return errI2CWriteTimeout + } + esp.I2C0.INT_CLR.SetBits(intMask) + for i := 0; i < len(readTo); i++ { + readTo[i] = byte(esp.I2C0.GetDATA_FIFO_RDATA() & 0xff) + c.head++ + } + readTo = nil + reg = &esp.I2C0.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 nil +} + +func nextAddress(reg *volatile.Register32) *volatile.Register32 { + return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(reg), 4)) +} diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index 8f831202db..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 @@ -201,7 +201,7 @@ func (spi SPI) Configure(config SPIConfig) error { // configure esp32c3 gpio pin matrix config.SDI.Configure(PinConfig{Mode: PinInput}) - inFunc(FSPIQ_IN_IDX).Set(esp.GPIO_FUNC_IN_SEL_CFG_SIG_IN_SEL | uint32(config.SDI)) + inFunc(FSPIQ_IN_IDX).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.SDI)) config.SDO.Configure(PinConfig{Mode: PinOutput}) config.SDO.outFunc().Set(FSPID_OUT_IDX) config.SCK.Configure(PinConfig{Mode: PinOutput}) @@ -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 85a2c5bd37..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) { } @@ -229,9 +231,10 @@ type I2CConfig struct { SDA Pin } +var i2cClockFrequency uint32 = 32000000 + // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { - var i2cClockFrequency uint32 = 32000000 if config.Frequency == 0 { config.Frequency = 100 * KHz } @@ -241,7 +244,17 @@ func (i2c *I2C) Configure(config I2CConfig) error { config.SCL = I2C0_SCL_PIN } - var prescaler = i2cClockFrequency/(5*config.Frequency) - 1 + i2c.SetBaudRate(config.Frequency) + + config.SDA.Configure(PinConfig{Mode: PinI2C}) + config.SCL.Configure(PinConfig{Mode: PinI2C}) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + var prescaler = i2cClockFrequency/(5*br) - 1 // disable controller before setting the prescale registers i2c.Bus.CTR.ClearBits(sifive.I2C_CTR_EN) @@ -253,9 +266,6 @@ func (i2c *I2C) Configure(config I2CConfig) error { // enable controller i2c.Bus.CTR.SetBits(sifive.I2C_CTR_EN) - config.SDA.Configure(PinConfig{Mode: PinI2C}) - config.SCL.Configure(PinConfig{Mode: PinI2C}) - return nil } diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index b0aea80621..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,21 +60,57 @@ type SPIConfig struct { Mode uint8 } -func (spi SPI) Configure(config SPIConfig) { +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. @@ -107,9 +146,25 @@ func (i2c *I2C) Configure(config I2CConfig) error { return nil } +// SetBaudRate sets the I2C frequency. +func (i2c *I2C) SetBaudRate(br uint32) error { + i2cSetBaudRate(i2c.Bus, br) + return nil +} + // 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 } @@ -117,6 +172,9 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) error { //export __tinygo_i2c_configure func i2cConfigure(bus uint8, scl Pin, sda Pin) +//export __tinygo_i2c_set_baud_rate +func i2cSetBaudRate(bus uint8, br uint32) + //export __tinygo_i2c_transfer func i2cTransfer(bus uint8, w *byte, wlen int, r *byte, rlen int) int @@ -166,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 ( @@ -185,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 e8a304c850..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. @@ -563,7 +563,19 @@ func (i2c *I2C) Configure(config I2CConfig) error { config.SCL.SetFPIOAFunction(FUNC_I2C2_SCLK) } - div := CPUFrequency() / config.Frequency / 16 + i2c.SetBaudRate(config.Frequency) + + i2c.Bus.INTR_MASK.Set(0) + i2c.Bus.DMA_CR.Set(0x03) + i2c.Bus.DMA_RDLR.Set(0) + i2c.Bus.DMA_TDLR.Set(0x4) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + div := CPUFrequency() / br / 16 // Disable controller before setting the prescale register. i2c.Bus.ENABLE.Set(0) @@ -574,11 +586,6 @@ func (i2c *I2C) Configure(config I2CConfig) error { i2c.Bus.SS_SCL_HCNT.Set(uint32(div)) i2c.Bus.SS_SCL_LCNT.Set(uint32(div)) - i2c.Bus.INTR_MASK.Set(0) - i2c.Bus.DMA_CR.Set(0x03) - i2c.Bus.DMA_RDLR.Set(0) - i2c.Bus.DMA_TDLR.Set(0x4) - return nil } @@ -612,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_mimxrt1062_i2c.go b/src/machine/machine_mimxrt1062_i2c.go index d7ba82f558..f3c4636178 100644 --- a/src/machine/machine_mimxrt1062_i2c.go +++ b/src/machine/machine_mimxrt1062_i2c.go @@ -6,19 +6,6 @@ package machine import ( "device/nxp" - "errors" -) - -var ( - errI2CWriteTimeout = errors.New("I2C timeout during write") - errI2CReadTimeout = errors.New("I2C timeout during read") - errI2CBusReadyTimeout = errors.New("I2C timeout on bus ready") - errI2CSignalStartTimeout = errors.New("I2C timeout on signal start") - errI2CSignalReadTimeout = errors.New("I2C timeout on signal read") - errI2CSignalStopTimeout = errors.New("I2C timeout on signal stop") - errI2CAckExpected = errors.New("I2C error: expected ACK not NACK") - errI2CBusError = errors.New("I2C bus error") - errI2CNotConfigured = errors.New("I2C interface is not yet configured") ) // I2CConfig is used to store config info for I2C. @@ -150,7 +137,7 @@ func (i2c *I2C) setPins(c I2CConfig) (sda, scl Pin) { } // Configure is intended to setup an I2C interface for transmit/receive. -func (i2c *I2C) Configure(config I2CConfig) { +func (i2c *I2C) Configure(config I2CConfig) error { // init pins sda, scl := i2c.setPins(config) @@ -169,6 +156,14 @@ func (i2c *I2C) Configure(config I2CConfig) { // reset clock and registers, and enable LPI2C module interface i2c.reset(freq) + + return nil +} + +// SetBaudRate sets the communication speed for I2C. +func (i2c I2C) SetBaudRate(br uint32) error { + // TODO: implement + return errI2CNotImplemented } func (i2c I2C) Tx(addr uint16, w, r []byte) error { @@ -212,13 +207,13 @@ func (i2c I2C) Tx(addr uint16, w, r []byte) error { return nil } -// WriteRegister transmits first the register and then the data to the +// WriteRegisterEx transmits first the register and then the data to the // peripheral device. // // Many I2C-compatible devices are organized in terms of registers. This method // is a shortcut to easily write to such registers. Also, it only works for // devices with 7-bit addresses, which is the vast majority. -func (i2c I2C) WriteRegister(address uint8, register uint8, data []byte) error { +func (i2c I2C) WriteRegisterEx(address uint8, register uint8, data []byte) error { option := transferOption{ flags: transferDefault, // transfer options bit mask (0 = normal transfer) peripheral: uint16(address), // 7-bit peripheral address @@ -232,13 +227,13 @@ func (i2c I2C) WriteRegister(address uint8, register uint8, data []byte) error { return nil } -// ReadRegister transmits the register, restarts the connection as a read +// ReadRegisterEx transmits the register, restarts the connection as a read // operation, and reads the response. // // Many I2C-compatible devices are organized in terms of registers. This method // is a shortcut to easily read such registers. Also, it only works for devices // with 7-bit addresses, which is the vast majority. -func (i2c I2C) ReadRegister(address uint8, register uint8, data []byte) error { +func (i2c I2C) ReadRegisterEx(address uint8, register uint8, data []byte) error { option := transferOption{ flags: transferDefault, // transfer options bit mask (0 = normal transfer) peripheral: uint16(address), // 7-bit peripheral address @@ -560,7 +555,7 @@ func (i2c *I2C) controllerReceive(rxBuffer []byte) resultFlag { return result } -// controllerReceive performs a polling transmit transfer on the I2C bus. +// controllerTransmit performs a polling transmit transfer on the I2C bus. func (i2c *I2C) controllerTransmit(txBuffer []byte) resultFlag { txSize := len(txBuffer) for txSize > 0 { diff --git a/src/machine/machine_mimxrt1062_spi.go b/src/machine/machine_mimxrt1062_spi.go index 9b92fc6d19..d982eeaa20 100644 --- a/src/machine/machine_mimxrt1062_spi.go +++ b/src/machine/machine_mimxrt1062_spi.go @@ -68,7 +68,7 @@ var ( ) // Configure is intended to setup an SPI interface for transmit/receive. -func (spi *SPI) Configure(config SPIConfig) { +func (spi *SPI) Configure(config SPIConfig) error { const defaultSpiFreq = 4000000 // 4 MHz @@ -132,6 +132,8 @@ func (spi *SPI) Configure(config SPIConfig) { spi.Bus.CR.Set(nxp.LPSPI_CR_MEN) spi.configured = true + + return nil } // Transfer writes/reads a single byte using the SPI interface. diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index d7b87d9aec..d6d6349f29 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -3,15 +3,42 @@ package machine import ( - "bytes" "device/nrf" - "encoding/binary" + "internal/binary" "runtime/interrupt" "unsafe" ) const deviceName = nrf.Device +var deviceID [8]byte + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself, or the +// flash chip at time of manufacture. +// +// It's possible that two different vendors may allocate +// the same DeviceID, so callers should take this into +// account if needing to generate a globally unique id. +// +// The length of the hardware ID is vendor-specific, but +// 8 bytes (64 bits) is common. +func DeviceID() []byte { + words := make([]uint32, 2) + words[0] = nrf.FICR.DEVICEID[0].Get() + words[1] = nrf.FICR.DEVICEID[1].Get() + + for i := 0; i < 8; i++ { + shift := (i % 4) * 8 + w := i / 4 + deviceID[i] = byte(words[w] >> shift) + } + + return deviceID[:] +} + const ( PinInput PinMode = (nrf.GPIO_PIN_CNF_DIR_Input << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Connect << nrf.GPIO_PIN_CNF_INPUT_Pos) PinInputPullup PinMode = PinInput | (nrf.GPIO_PIN_CNF_PULL_Pullup << nrf.GPIO_PIN_CNF_PULL_Pos) @@ -80,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) @@ -246,13 +273,8 @@ func (i2c *I2C) Configure(config I2CConfig) error { i2c.setPins(config.SCL, config.SDA) i2c.mode = config.Mode - if i2c.mode == I2CModeController { - if config.Frequency >= 400*KHz { - i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K400) - } else { - i2c.Bus.FREQUENCY.Set(nrf.TWI_FREQUENCY_FREQUENCY_K100) - } + i2c.SetBaudRate(config.Frequency) i2c.enableAsController() } else { @@ -262,6 +284,23 @@ func (i2c *I2C) Configure(config I2CConfig) error { return nil } +// SetBaudRate sets the I2C frequency. It has the side effect of also +// enabling the I2C hardware if disabled beforehand. +// +//go:inline +func (i2c *I2C) SetBaudRate(br uint32) error { + switch { + case br >= 400*KHz: + i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K400) + case br >= 250*KHz: + i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K250) + default: + i2c.Bus.SetFREQUENCY(nrf.TWI_FREQUENCY_FREQUENCY_K100) + } + + return nil +} + // signalStop sends a stop signal to the I2C peripheral and waits for confirmation. func (i2c *I2C) signalStop() error { tries := 0 @@ -278,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 { @@ -346,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() @@ -404,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 95723cfd06..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) { +func (spi *SPI) Configure(config SPIConfig) error { // Disable bus to configure it spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Disabled) @@ -117,10 +117,12 @@ func (spi SPI) Configure(config SPIConfig) { // Re-enable bus now that it is configured. spi.Bus.ENABLE.Set(nrf.SPI_ENABLE_ENABLE_Enabled) + + 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) { spi.Bus.TXD.Set(uint32(w)) for spi.Bus.EVENTS_READY.Get() == 0 { } @@ -131,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. // @@ -148,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_nrf52840_lfxtal_false.go b/src/machine/machine_nrf52840_lfxtal_false.go new file mode 100644 index 0000000000..ba38a32645 --- /dev/null +++ b/src/machine/machine_nrf52840_lfxtal_false.go @@ -0,0 +1,5 @@ +//go:build nrf52840 && nrf52840_lfxtal_false + +package machine + +const HasLowFrequencyCrystal = false diff --git a/src/machine/machine_nrf52840_lfxtal_true.go b/src/machine/machine_nrf52840_lfxtal_true.go new file mode 100644 index 0000000000..4bef553b04 --- /dev/null +++ b/src/machine/machine_nrf52840_lfxtal_true.go @@ -0,0 +1,5 @@ +//go:build nrf52840 && ((nrf52840_generic && !nrf52840_lfxtal_false) || nrf52840_lfxtal_true) + +package machine + +const HasLowFrequencyCrystal = true diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index e35cb02010..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<io[1].ctrl + uint32_t *addr = (uint32_t*)(IO_QSPI_BASE + (1 * 8) + 4); + + *addr = ((*addr) & !IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS) + | (field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB); + +} + // See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_flash/flash.c#L86 void ram_func flash_range_write(uint32_t offset, const uint8_t *data, size_t count) { @@ -132,6 +159,42 @@ void ram_func flash_erase_blocks(uint32_t offset, size_t count) flash_enable_xip_via_boot2(); } +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(ROM_FUNC_CONNECT_INTERNAL_FLASH); + flash_exit_xip_fn flash_exit_xip_func = (flash_exit_xip_fn) rom_func_lookup(ROM_FUNC_FLASH_EXIT_XIP); + flash_flush_cache_fn flash_flush_cache_func = (flash_flush_cache_fn) rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE); + + flash_init_boot2_copyout(); + + __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; + // We may be interrupted -- don't want FIFO to overflow if we're distracted. + const size_t max_in_flight = 16 - 2; + while (tx_remaining || rx_remaining) { + uint32_t flags = *(uint32_t*)(XIP_SSI_BASE + SSI_SR_OFFSET); + bool can_put = !!(flags & SSI_SR_TFNF_BITS); + bool can_get = !!(flags & SSI_SR_RFNE_BITS); + if (can_put && tx_remaining && rx_remaining - tx_remaining < max_in_flight) { + *(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET) = *txbuf++; + --tx_remaining; + } + if (can_get && rx_remaining) { + *rxbuf++ = (uint8_t)*(uint32_t*)(XIP_SSI_BASE + SSI_DR0_OFFSET); + --rx_remaining; + } + } + flash_cs_force(1); + + flash_flush_cache_func(); + flash_enable_xip_via_boot2(); +} + */ import "C" @@ -139,6 +202,19 @@ 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 @@ -154,7 +230,7 @@ func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { // e.g. real address 0x10003000 is written to at // 0x00003000 address := writeAddress(off) - padded := f.pad(p) + padded := flashPad(p, int(f.WriteBlockSize())) C.flash_range_write(C.uint32_t(address), (*C.uint8_t)(unsafe.Pointer(&padded[0])), diff --git a/src/machine/machine_rp2040_usb.go b/src/machine/machine_rp2040_usb.go index 6e6fd49623..d69cd6ad5f 100644 --- a/src/machine/machine_rp2040_usb.go +++ b/src/machine/machine_rp2040_usb.go @@ -25,7 +25,7 @@ func (dev *USBDevice) Configure(config UARTConfig) { unresetBlockWait(rp.RESETS_RESET_USBCTRL) // Clear any previous state in dpram just in case - usbDPSRAM.clear() + _usbDPSRAM.clear() // Enable USB interrupt at processor rp.USBCTRL_REGS.INTE.Set(0) @@ -62,7 +62,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) { // Setup packet received if (status & rp.USBCTRL_REGS_INTS_SETUP_REQ) > 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 77% rename from src/machine/machine_rp2040_flash.go rename to src/machine/machine_rp2_flash.go index f3d24e8e7d..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" ) @@ -13,6 +12,32 @@ func EnterBootloader() { enterBootloader() } +// 13 = 1 + FLASH_RUID_DUMMY_BYTES(4) + FLASH_RUID_DATA_BYTES(8) +var deviceIDBuf [13]byte + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself, or the +// flash chip at time of manufacture. +// +// It's possible that two different vendors may allocate +// the same DeviceID, so callers should take this into +// account if needing to generate a globally unique id. +// +// The length of the hardware ID is vendor-specific, but +// 8 bytes (64 bits) is common. +func DeviceID() []byte { + deviceIDBuf[0] = 0x4b // FLASH_RUID_CMD + + err := doFlashCommand(deviceIDBuf[:], deviceIDBuf[:]) + if err != nil { + panic(err) + } + + return deviceIDBuf[5:13] +} + // compile-time check for ensuring we fulfill BlockDevice interface var _ BlockDevice = flashBlockDevice{} @@ -75,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 94715d963f..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. @@ -241,6 +190,8 @@ const ( PinFalling PinChange = 4 << iota // Edge rising PinRising + + PinToggle = PinFalling | PinRising ) // Callbacks to be called for pins configured with SetInterrupt. 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 @@ -138,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 67% rename from src/machine/machine_rp2040_xosc.go rename to src/machine/machine_rp2_xosc.go index c3d6d713ad..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 @@ -8,12 +8,17 @@ import ( "unsafe" ) +// On some boards, the XOSC can take longer than usual to stabilize. On such +// boards, this is needed to avoid a hard fault on boot/reset. Refer to +// PICO_XOSC_STARTUP_DELAY_MULTIPLIER in the Pico SDK for additional details. +const XOSC_STARTUP_DELAY_MULTIPLIER = 64 + type xoscType struct { ctrl volatile.Register32 status volatile.Register32 dormant volatile.Register32 startup volatile.Register32 - reserved [3]volatile.Register32 + reserved [3 - 3*rp2350ExtraReg]volatile.Register32 count volatile.Register32 } @@ -30,7 +35,7 @@ func (osc *xoscType) init() { osc.ctrl.Set(rp.XOSC_CTRL_FREQ_RANGE_1_15MHZ) // Set xosc startup delay - delay := (((xoscFreq * MHz) / 1000) + 128) / 256 + delay := (((xoscFreq * MHz) / 1000) + 128) / 256 * XOSC_STARTUP_DELAY_MULTIPLIER osc.startup.Set(uint32(delay)) // Set the enable bit now that we have set freq range and startup delay diff --git a/src/machine/machine_stm32.go b/src/machine/machine_stm32.go index 4f04cae4ce..1edaa2cd06 100644 --- a/src/machine/machine_stm32.go +++ b/src/machine/machine_stm32.go @@ -2,7 +2,12 @@ package machine -import "device/stm32" +import ( + "device/stm32" + + "runtime/volatile" + "unsafe" +) const deviceName = stm32.Device @@ -80,3 +85,20 @@ func (p Pin) PortMaskClear() (*uint32, uint32) { pin := uint8(p) % 16 return &port.BSRR.Reg, 1 << (pin + 16) } + +var deviceID [12]byte + +// DeviceID returns an identifier that is unique within +// a particular chipset. +// +// The identity is one burnt into the MCU itself. +// +// The length of the device ID for STM32 is 12 bytes (96 bits). +func DeviceID() []byte { + for i := 0; i < len(deviceID); i++ { + word := (*volatile.Register32)(unsafe.Pointer(deviceIDAddr[i/4])).Get() + deviceID[i] = byte(word >> ((i % 4) * 8)) + } + + return deviceID[:] +} 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_reva.go b/src/machine/machine_stm32_i2c_reva.go index 38ebdcdc42..1e4fd282e9 100644 --- a/src/machine/machine_stm32_i2c_reva.go +++ b/src/machine/machine_stm32_i2c_reva.go @@ -157,6 +157,12 @@ func (i2c *I2C) Configure(config I2CConfig) error { return nil } +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + // TODO: implement + return errI2CNotImplemented +} + func (i2c *I2C) Tx(addr uint16, w, r []byte) error { if err := i2c.controllerTransmit(addr, w); nil != err { diff --git a/src/machine/machine_stm32_i2c_revb.go b/src/machine/machine_stm32_i2c_revb.go index 6adb704dbd..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) @@ -84,6 +93,25 @@ func (i2c *I2C) Configure(config I2CConfig) error { return nil } +// SetBaudRate sets the communication speed for I2C. +func (i2c *I2C) SetBaudRate(br uint32) error { + 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 { if len(w) > 0 { if err := i2c.controllerTransmit(addr, w); nil != err { diff --git a/src/machine/machine_stm32_spi.go b/src/machine/machine_stm32_spi.go index 192411c47b..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) { +func (spi *SPI) Configure(config SPIConfig) error { // -- CONFIGURING THE SPI IN MASTER MODE -- // @@ -93,10 +93,12 @@ func (spi SPI) Configure(config SPIConfig) { // enable SPI spi.Bus.CR1.SetBits(stm32.SPI_CR1_SPE) + + 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) { // 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 e7593829bf..9e7bb347c9 100644 --- a/src/machine/machine_stm32f103.go +++ b/src/machine/machine_stm32f103.go @@ -15,6 +15,8 @@ func CPUFrequency() uint32 { return 72000000 } +var deviceIDAddr = []uintptr{0x1FFFF7E8, 0x1FFFF7EC, 0x1FFFF7F0} + // 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 @@ -219,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") } } @@ -326,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) @@ -361,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}) @@ -397,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 3b8923cb76..31f1d2c635 100644 --- a/src/machine/machine_stm32f4.go +++ b/src/machine/machine_stm32f4.go @@ -6,14 +6,16 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "math/bits" "runtime/interrupt" "runtime/volatile" "unsafe" ) +var deviceIDAddr = []uintptr{0x1FFF7A10, 0x1FFF7A14, 0x1FFF7A18} + const ( PA0 = portA + 0 PA1 = portA + 1 @@ -670,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_stm32f7.go b/src/machine/machine_stm32f7.go index a08d083a23..11eff11081 100644 --- a/src/machine/machine_stm32f7.go +++ b/src/machine/machine_stm32f7.go @@ -11,6 +11,8 @@ import ( "unsafe" ) +var deviceIDAddr = []uintptr{0x1FF0F420, 0x1FF0F424, 0x1FF0F428} + // Alternative peripheral pin functions const ( AF0_SYSTEM = 0 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 f3d213c483..1ecd958b81 100644 --- a/src/machine/machine_stm32l0.go +++ b/src/machine/machine_stm32l0.go @@ -13,6 +13,8 @@ func CPUFrequency() uint32 { return 32000000 } +var deviceIDAddr = []uintptr{0x1FF80050, 0x1FF80054, 0x1FF80058} + // 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 @@ -232,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 @@ -254,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: @@ -287,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) @@ -296,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 f60a77e700..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" @@ -13,6 +13,8 @@ import ( // Peripheral abstraction layer for the stm32l4 +var deviceIDAddr = []uintptr{0x1FFF7590, 0x1FFF7594, 0x1FFF7598} + const ( AF0_SYSTEM = 0 AF1_TIM1_2_LPTIM1 = 1 @@ -307,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 @@ -325,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 @@ -357,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_stm32l5.go b/src/machine/machine_stm32l5.go index d85157dbe9..faa583c9c3 100644 --- a/src/machine/machine_stm32l5.go +++ b/src/machine/machine_stm32l5.go @@ -11,6 +11,8 @@ import ( "unsafe" ) +var deviceIDAddr = []uintptr{0x0BFA0590, 0x0BFA0594, 0x0BFA0598} + const ( AF0_SYSTEM = 0 AF1_TIM1_2_5_8_LPTIM1 = 1 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 010d038e03..80ca791e6a 100644 --- a/src/machine/machine_stm32wlx.go +++ b/src/machine/machine_stm32wlx.go @@ -6,14 +6,16 @@ package machine import ( "device/stm32" - "encoding/binary" "errors" + "internal/binary" "math/bits" "runtime/interrupt" "runtime/volatile" "unsafe" ) +var deviceIDAddr = []uintptr{0x1FFF7590, 0x1FFF7594, 0x1FFF7598} + const ( AF0_SYSTEM = 0 AF1_TIM1_2_LPTIM1 = 1 @@ -233,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 @@ -287,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/serial-rtt.go b/src/machine/serial-rtt.go new file mode 100644 index 0000000000..62c3eb9e8b --- /dev/null +++ b/src/machine/serial-rtt.go @@ -0,0 +1,144 @@ +//go:build baremetal && serial.rtt + +// Implement Segger RTT support. +// This is mostly useful for targets that only have a debug connection +// available, and no serial output (or input). It is somewhat like semihosting, +// but not unusably slow. +// It was originally specified by Segger, but support is available in OpenOCD +// for at least the DAPLink debuggers so I assume it works on any SWD debugger. + +package machine + +import ( + "runtime/interrupt" + "runtime/volatile" + "unsafe" +) + +// This symbol name is known by the compiler, see monitor.go. +var rttSerialInstance rttSerial + +var Serial = &rttSerialInstance + +func InitSerial() { + Serial.Configure(UARTConfig{}) +} + +const ( + // Some constants, see: + // https://github.com/SEGGERMicro/RTT/blob/master/RTT/SEGGER_RTT.h + + rttMaxNumUpBuffers = 1 + rttMaxNumDownBuffers = 1 + rttBufferSizeUp = 1024 + rttBufferSizeDown = 16 + + rttModeNoBlockSkip = 0 + rttModeNoBlockTrim = 1 + rttModeBlockIfFifoFull = 2 +) + +// The debugger knows about the layout of this struct, so it must not change. +// This is SEGGER_RTT_CB. +type rttControlBlock struct { + id [16]volatile.Register8 + maxNumUpBuffers int32 + maxNumDownBuffers int32 + buffersUp [rttMaxNumUpBuffers]rttBuffer + buffersDown [rttMaxNumDownBuffers]rttBuffer +} + +// Up or down buffer. +// This is SEGGER_RTT_BUFFER_UP and SEGGER_RTT_BUFFER_DOWN. +type rttBuffer struct { + name *byte + buffer *volatile.Register8 + bufferSize uint32 + writeOffset volatile.Register32 + readOffset volatile.Register32 + flags uint32 +} + +// Static buffers, for the default up and down buffer. +var ( + rttBufferUpData [rttBufferSizeUp]volatile.Register8 + rttBufferDownData [rttBufferSizeDown]volatile.Register8 +) + +type rttSerial struct { + rttControlBlock +} + +func (s *rttSerial) Configure(config UARTConfig) error { + s.maxNumUpBuffers = rttMaxNumUpBuffers + s.maxNumDownBuffers = rttMaxNumDownBuffers + + s.buffersUp[0].name = &[]byte("Terminal\x00")[0] + s.buffersUp[0].buffer = &rttBufferUpData[0] + s.buffersUp[0].bufferSize = rttBufferSizeUp + s.buffersUp[0].flags = rttModeNoBlockSkip + + s.buffersDown[0].name = &[]byte("Terminal\x00")[0] + s.buffersDown[0].buffer = &rttBufferDownData[0] + s.buffersDown[0].bufferSize = rttBufferSizeDown + s.buffersDown[0].flags = rttModeNoBlockSkip + + id := "SEGGER RTT" + for i := 0; i < len(id); i++ { + s.id[i].Set(id[i]) + } + + return nil +} + +func (b *rttBuffer) writeByte(c byte) { + state := interrupt.Disable() + readOffset := b.readOffset.Get() + writeOffset := b.writeOffset.Get() + newWriteOffset := writeOffset + 1 + if newWriteOffset == b.bufferSize { + newWriteOffset = 0 + } + if newWriteOffset != readOffset { + unsafe.Slice(b.buffer, b.bufferSize)[writeOffset].Set(c) + b.writeOffset.Set(newWriteOffset) + } + interrupt.Restore(state) +} + +func (b *rttBuffer) readByte() byte { + readOffset := b.readOffset.Get() + writeOffset := b.writeOffset.Get() + for readOffset == writeOffset { + readOffset = b.readOffset.Get() + } + c := unsafe.Slice(b.buffer, b.bufferSize)[readOffset].Get() + b.readOffset.Set(readOffset + 1) + return c +} + +func (b *rttBuffer) buffered() int { + readOffset := b.readOffset.Get() + writeOffset := b.writeOffset.Get() + return int((writeOffset - readOffset) % rttBufferSizeDown) +} + +func (s *rttSerial) WriteByte(b byte) error { + s.buffersUp[0].writeByte(b) + return nil +} + +func (s *rttSerial) ReadByte() (byte, error) { + return s.buffersDown[0].readByte(), errNoByte +} + +func (s *rttSerial) Buffered() int { + return s.buffersDown[0].buffered() +} + +func (s *rttSerial) Write(data []byte) (n int, err error) { + for _, v := range data { + s.WriteByte(v) + } + return len(data), nil +} diff --git a/src/machine/serial.go b/src/machine/serial.go index fd02d6ca00..4aacc502e8 100644 --- a/src/machine/serial.go +++ b/src/machine/serial.go @@ -11,6 +11,8 @@ type UARTConfig struct { BaudRate uint32 TX Pin RX Pin + RTS Pin + CTS Pin } // NullSerial is a serial version of /dev/null (or null router): it drops diff --git a/src/machine/spi.go b/src/machine/spi.go index 803f4ffcb3..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 @@ -16,3 +16,14 @@ var ( ErrTxInvalidSliceSize = errors.New("SPI write and read slices must be same size") errSPIInvalidMachineConfig = errors.New("SPI port was not configured properly by the machine") ) + +// If you are getting a compile error on this line please check to see you've +// correctly implemented the methods on the SPI type. They must match +// the interface method signatures type to type perfectly. +// If not implementing the SPI type please remove your target from the build tags +// at the top of this file. +var _ interface { // 2 + Configure(config SPIConfig) error + Tx(w, r []byte) error + Transfer(w byte) (byte, error) +} = (*SPI)(nil) 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 c28462e536..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 @@ -63,6 +63,13 @@ func usbProduct() string { return usb_STRING_PRODUCT } +func usbSerial() string { + if usb.Serial != "" { + return usb.Serial + } + return "" +} + // strToUTF16LEDescriptor converts a utf8 string into a string descriptor // note: the following code only converts ascii characters to UTF16LE. In order // to do a "proper" conversion, we would need to pull in the 'unicode/utf16' @@ -81,8 +88,9 @@ func strToUTF16LEDescriptor(in string, out []byte) { const cdcLineInfoSize = 7 var ( - ErrUSBReadTimeout = errors.New("USB read timeout") - ErrUSBBytesRead = errors.New("USB invalid number of bytes read") + ErrUSBReadTimeout = errors.New("USB read timeout") + ErrUSBBytesRead = errors.New("USB invalid number of bytes read") + ErrUSBBytesWritten = errors.New("USB invalid number of bytes written") ) var ( @@ -160,8 +168,14 @@ func sendDescriptor(setup usb.Setup) { sendUSBPacket(0, b, setup.WLength) case usb.ISERIAL: - // TODO: allow returning a product serial number - SendZlp() + sz := len(usbSerial()) + if sz == 0 { + SendZlp() + } else { + b := usb_trans_buffer[:(sz<<1)+2] + strToUTF16LEDescriptor(usbSerial(), b) + sendUSBPacket(0, b, setup.WLength) + } } return case descriptor.TypeHIDReport: 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/usb/usb.go b/src/machine/usb/usb.go index 47a8a3456b..360ac39026 100644 --- a/src/machine/usb/usb.go +++ b/src/machine/usb/usb.go @@ -134,4 +134,8 @@ var ( // Product is the product name displayed for this USB device. Product string + + // Serial is the serial value displayed for this USB device. Assign a value to + // transmit the serial to the host when requested. + Serial string ) 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 new file mode 160000 index 0000000000..ca7cd08f85 --- /dev/null +++ b/src/net @@ -0,0 +1 @@ +Subproject commit ca7cd08f851a1f3dde5fca2e217b7e06d17842ae diff --git a/src/net/conn_test.go b/src/net/conn_test.go deleted file mode 100644 index 4e1ac28c43..0000000000 --- a/src/net/conn_test.go +++ /dev/null @@ -1,478 +0,0 @@ -// The following is copied from x/net official implementation. -// Source: https://cs.opensource.google/go/x/net/+/f15817d1:nettest/conntest.go -// Changes from original the file: -// - Some variables are pulled in from nettest/nettest.go file. -// - The implementation of checkForTimeoutError() function is changed in -// accordance with error returned by the Pipe implementation. - -// 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 net - -import ( - "bytes" - "encoding/binary" - "io" - "io/ioutil" - "math/rand" - "os" - "runtime" - "sync" - "testing" - "time" -) - -// The following variables are copied from nettest/nettest.go file -var ( - aLongTimeAgo = time.Unix(233431200, 0) - neverTimeout = time.Time{} -) - -// MakePipe creates a connection between two endpoints and returns the pair -// as c1 and c2, such that anything written to c1 is read by c2 and vice-versa. -// The stop function closes all resources, including c1, c2, and the underlying -// Listener (if there is one), and should not be nil. -type MakePipe func() (c1, c2 Conn, stop func(), err error) - -// testConn tests that a Conn implementation properly satisfies the interface. -// The tests should not produce any false positives, but may experience -// false negatives. Thus, some issues may only be detected when the test is -// run multiple times. For maximal effectiveness, run the tests under the -// race detector. -func testConn(t *testing.T, mp MakePipe) { - t.Run("BasicIO", func(t *testing.T) { timeoutWrapper(t, mp, testBasicIO) }) - t.Run("PingPong", func(t *testing.T) { timeoutWrapper(t, mp, testPingPong) }) - t.Run("RacyRead", func(t *testing.T) { timeoutWrapper(t, mp, testRacyRead) }) - t.Run("RacyWrite", func(t *testing.T) { timeoutWrapper(t, mp, testRacyWrite) }) - t.Run("ReadTimeout", func(t *testing.T) { timeoutWrapper(t, mp, testReadTimeout) }) - t.Run("WriteTimeout", func(t *testing.T) { timeoutWrapper(t, mp, testWriteTimeout) }) - t.Run("PastTimeout", func(t *testing.T) { timeoutWrapper(t, mp, testPastTimeout) }) - t.Run("PresentTimeout", func(t *testing.T) { timeoutWrapper(t, mp, testPresentTimeout) }) - t.Run("FutureTimeout", func(t *testing.T) { timeoutWrapper(t, mp, testFutureTimeout) }) - t.Run("CloseTimeout", func(t *testing.T) { timeoutWrapper(t, mp, testCloseTimeout) }) - t.Run("ConcurrentMethods", func(t *testing.T) { timeoutWrapper(t, mp, testConcurrentMethods) }) -} - -type connTester func(t *testing.T, c1, c2 Conn) - -func timeoutWrapper(t *testing.T, mp MakePipe, f connTester) { - t.Helper() - c1, c2, stop, err := mp() - if err != nil { - t.Fatalf("unable to make pipe: %v", err) - } - var once sync.Once - defer once.Do(func() { stop() }) - timer := time.AfterFunc(time.Minute, func() { - once.Do(func() { - t.Error("test timed out; terminating pipe") - stop() - }) - }) - defer timer.Stop() - f(t, c1, c2) -} - -// testBasicIO tests that the data sent on c1 is properly received on c2. -func testBasicIO(t *testing.T, c1, c2 Conn) { - want := make([]byte, 1<<20) - rand.New(rand.NewSource(0)).Read(want) - - dataCh := make(chan []byte) - go func() { - rd := bytes.NewReader(want) - if err := chunkedCopy(c1, rd); err != nil { - t.Errorf("unexpected c1.Write error: %v", err) - } - if err := c1.Close(); err != nil { - t.Errorf("unexpected c1.Close error: %v", err) - } - }() - - go func() { - wr := new(bytes.Buffer) - if err := chunkedCopy(wr, c2); err != nil { - t.Errorf("unexpected c2.Read error: %v", err) - } - if err := c2.Close(); err != nil { - t.Errorf("unexpected c2.Close error: %v", err) - } - dataCh <- wr.Bytes() - }() - - if got := <-dataCh; !bytes.Equal(got, want) { - t.Error("transmitted data differs") - } -} - -// testPingPong tests that the two endpoints can synchronously send data to -// each other in a typical request-response pattern. -func testPingPong(t *testing.T, c1, c2 Conn) { - var wg sync.WaitGroup - defer wg.Wait() - - pingPonger := func(c Conn) { - defer wg.Done() - buf := make([]byte, 8) - var prev uint64 - for { - if _, err := io.ReadFull(c, buf); err != nil { - if err == io.EOF { - break - } - t.Errorf("unexpected Read error: %v", err) - } - - v := binary.LittleEndian.Uint64(buf) - binary.LittleEndian.PutUint64(buf, v+1) - if prev != 0 && prev+2 != v { - t.Errorf("mismatching value: got %d, want %d", v, prev+2) - } - prev = v - if v == 1000 { - break - } - - if _, err := c.Write(buf); err != nil { - t.Errorf("unexpected Write error: %v", err) - break - } - } - if err := c.Close(); err != nil { - t.Errorf("unexpected Close error: %v", err) - } - } - - wg.Add(2) - go pingPonger(c1) - go pingPonger(c2) - - // Start off the chain reaction. - if _, err := c1.Write(make([]byte, 8)); err != nil { - t.Errorf("unexpected c1.Write error: %v", err) - } -} - -// testRacyRead tests that it is safe to mutate the input Read buffer -// immediately after cancelation has occurred. -func testRacyRead(t *testing.T, c1, c2 Conn) { - go chunkedCopy(c2, rand.New(rand.NewSource(0))) - - var wg sync.WaitGroup - defer wg.Wait() - - c1.SetReadDeadline(time.Now().Add(time.Millisecond)) - for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() - - b1 := make([]byte, 1024) - b2 := make([]byte, 1024) - for j := 0; j < 100; j++ { - _, err := c1.Read(b1) - copy(b1, b2) // Mutate b1 to trigger potential race - if err != nil { - checkForTimeoutError(t, err) - c1.SetReadDeadline(time.Now().Add(time.Millisecond)) - } - } - }() - } -} - -// testRacyWrite tests that it is safe to mutate the input Write buffer -// immediately after cancelation has occurred. -func testRacyWrite(t *testing.T, c1, c2 Conn) { - go chunkedCopy(ioutil.Discard, c2) - - var wg sync.WaitGroup - defer wg.Wait() - - c1.SetWriteDeadline(time.Now().Add(time.Millisecond)) - for i := 0; i < 10; i++ { - wg.Add(1) - go func() { - defer wg.Done() - - b1 := make([]byte, 1024) - b2 := make([]byte, 1024) - for j := 0; j < 100; j++ { - _, err := c1.Write(b1) - copy(b1, b2) // Mutate b1 to trigger potential race - if err != nil { - checkForTimeoutError(t, err) - c1.SetWriteDeadline(time.Now().Add(time.Millisecond)) - } - } - }() - } -} - -// testReadTimeout tests that Read timeouts do not affect Write. -func testReadTimeout(t *testing.T, c1, c2 Conn) { - go chunkedCopy(ioutil.Discard, c2) - - c1.SetReadDeadline(aLongTimeAgo) - _, err := c1.Read(make([]byte, 1024)) - checkForTimeoutError(t, err) - if _, err := c1.Write(make([]byte, 1024)); err != nil { - t.Errorf("unexpected Write error: %v", err) - } -} - -// testWriteTimeout tests that Write timeouts do not affect Read. -func testWriteTimeout(t *testing.T, c1, c2 Conn) { - go chunkedCopy(c2, rand.New(rand.NewSource(0))) - - c1.SetWriteDeadline(aLongTimeAgo) - _, err := c1.Write(make([]byte, 1024)) - checkForTimeoutError(t, err) - if _, err := c1.Read(make([]byte, 1024)); err != nil { - t.Errorf("unexpected Read error: %v", err) - } -} - -// testPastTimeout tests that a deadline set in the past immediately times out -// Read and Write requests. -func testPastTimeout(t *testing.T, c1, c2 Conn) { - go chunkedCopy(c2, c2) - - testRoundtrip(t, c1) - - c1.SetDeadline(aLongTimeAgo) - n, err := c1.Write(make([]byte, 1024)) - if n != 0 { - t.Errorf("unexpected Write count: got %d, want 0", n) - } - checkForTimeoutError(t, err) - n, err = c1.Read(make([]byte, 1024)) - if n != 0 { - t.Errorf("unexpected Read count: got %d, want 0", n) - } - checkForTimeoutError(t, err) - - testRoundtrip(t, c1) -} - -// testPresentTimeout tests that a past deadline set while there are pending -// Read and Write operations immediately times out those operations. -func testPresentTimeout(t *testing.T, c1, c2 Conn) { - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(3) - - deadlineSet := make(chan bool, 1) - go func() { - defer wg.Done() - time.Sleep(100 * time.Millisecond) - deadlineSet <- true - c1.SetReadDeadline(aLongTimeAgo) - c1.SetWriteDeadline(aLongTimeAgo) - }() - go func() { - defer wg.Done() - n, err := c1.Read(make([]byte, 1024)) - if n != 0 { - t.Errorf("unexpected Read count: got %d, want 0", n) - } - checkForTimeoutError(t, err) - if len(deadlineSet) == 0 { - t.Error("Read timed out before deadline is set") - } - }() - go func() { - defer wg.Done() - var err error - for err == nil { - _, err = c1.Write(make([]byte, 1024)) - } - checkForTimeoutError(t, err) - if len(deadlineSet) == 0 { - t.Error("Write timed out before deadline is set") - } - }() -} - -// testFutureTimeout tests that a future deadline will eventually time out -// Read and Write operations. -func testFutureTimeout(t *testing.T, c1, c2 Conn) { - var wg sync.WaitGroup - wg.Add(2) - - c1.SetDeadline(time.Now().Add(100 * time.Millisecond)) - go func() { - defer wg.Done() - _, err := c1.Read(make([]byte, 1024)) - checkForTimeoutError(t, err) - }() - go func() { - defer wg.Done() - var err error - for err == nil { - _, err = c1.Write(make([]byte, 1024)) - } - checkForTimeoutError(t, err) - }() - wg.Wait() - - go chunkedCopy(c2, c2) - resyncConn(t, c1) - testRoundtrip(t, c1) -} - -// testCloseTimeout tests that calling Close immediately times out pending -// Read and Write operations. -func testCloseTimeout(t *testing.T, c1, c2 Conn) { - go chunkedCopy(c2, c2) - - var wg sync.WaitGroup - defer wg.Wait() - wg.Add(3) - - // Test for cancelation upon connection closure. - c1.SetDeadline(neverTimeout) - go func() { - defer wg.Done() - time.Sleep(100 * time.Millisecond) - c1.Close() - }() - go func() { - defer wg.Done() - var err error - buf := make([]byte, 1024) - for err == nil { - _, err = c1.Read(buf) - } - }() - go func() { - defer wg.Done() - var err error - buf := make([]byte, 1024) - for err == nil { - _, err = c1.Write(buf) - } - }() -} - -// testConcurrentMethods tests that the methods of Conn can safely -// be called concurrently. -func testConcurrentMethods(t *testing.T, c1, c2 Conn) { - if runtime.GOOS == "plan9" { - t.Skip("skipping on plan9; see https://golang.org/issue/20489") - } - go chunkedCopy(c2, c2) - - // The results of the calls may be nonsensical, but this should - // not trigger a race detector warning. - var wg sync.WaitGroup - for i := 0; i < 100; i++ { - wg.Add(7) - go func() { - defer wg.Done() - c1.Read(make([]byte, 1024)) - }() - go func() { - defer wg.Done() - c1.Write(make([]byte, 1024)) - }() - go func() { - defer wg.Done() - c1.SetDeadline(time.Now().Add(10 * time.Millisecond)) - }() - go func() { - defer wg.Done() - c1.SetReadDeadline(aLongTimeAgo) - }() - go func() { - defer wg.Done() - c1.SetWriteDeadline(aLongTimeAgo) - }() - go func() { - defer wg.Done() - c1.LocalAddr() - }() - go func() { - defer wg.Done() - c1.RemoteAddr() - }() - } - wg.Wait() // At worst, the deadline is set 10ms into the future - - resyncConn(t, c1) - testRoundtrip(t, c1) -} - -// checkForTimeoutError checks that the error satisfies the OpError interface -// and that underlying Err is os.ErrDeadlineExceeded -func checkForTimeoutError(t *testing.T, err error) { - t.Helper() - operr, ok := err.(*OpError) - if !ok { - t.Errorf("got %T: %v, want OpError", err, err) - return - } - if operr.Err != os.ErrDeadlineExceeded { - t.Errorf("got %T: %v, want os.ErrDeadlineExceeded", err, err) - } -} - -// testRoundtrip writes something into c and reads it back. -// It assumes that everything written into c is echoed back to itself. -func testRoundtrip(t *testing.T, c Conn) { - t.Helper() - if err := c.SetDeadline(neverTimeout); err != nil { - t.Errorf("roundtrip SetDeadline error: %v", err) - } - - const s = "Hello, world!" - buf := []byte(s) - if _, err := c.Write(buf); err != nil { - t.Errorf("roundtrip Write error: %v", err) - } - if _, err := io.ReadFull(c, buf); err != nil { - t.Errorf("roundtrip Read error: %v", err) - } - if string(buf) != s { - t.Errorf("roundtrip data mismatch: got %q, want %q", buf, s) - } -} - -// resyncConn resynchronizes the connection into a sane state. -// It assumes that everything written into c is echoed back to itself. -// It assumes that 0xff is not currently on the wire or in the read buffer. -func resyncConn(t *testing.T, c Conn) { - t.Helper() - c.SetDeadline(neverTimeout) - errCh := make(chan error) - go func() { - _, err := c.Write([]byte{0xff}) - errCh <- err - }() - buf := make([]byte, 1024) - for { - n, err := c.Read(buf) - if n > 0 && bytes.IndexByte(buf[:n], 0xff) == n-1 { - break - } - if err != nil { - t.Errorf("unexpected Read error: %v", err) - break - } - } - if err := <-errCh; err != nil { - t.Errorf("unexpected Write error: %v", err) - } -} - -// chunkedCopy copies from r to w in fixed-width chunks to avoid -// causing a Write that exceeds the maximum packet size for packet-based -// connections like "unixpacket". -// We assume that the maximum packet size is at least 1024. -func chunkedCopy(w io.Writer, r io.Reader) error { - b := make([]byte, 1024) - _, err := io.CopyBuffer(struct{ io.Writer }{w}, struct{ io.Reader }{r}, b) - return err -} diff --git a/src/net/dial.go b/src/net/dial.go deleted file mode 100644 index a1cb75d87f..0000000000 --- a/src/net/dial.go +++ /dev/null @@ -1,25 +0,0 @@ -package net - -import ( - "context" - "time" -) - -type Dialer struct { - Timeout time.Duration - Deadline time.Time - DualStack bool - KeepAlive time.Duration -} - -func Dial(network, address string) (Conn, error) { - return nil, ErrNotImplemented -} - -func Listen(network, address string) (Listener, error) { - return nil, ErrNotImplemented -} - -func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) { - return nil, ErrNotImplemented -} diff --git a/src/net/errors.go b/src/net/errors.go deleted file mode 100644 index c1dc7b31c8..0000000000 --- a/src/net/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package net - -import "errors" - -var ( - // copied from poll.ErrNetClosing - errClosed = errors.New("use of closed network connection") - - ErrNotImplemented = errors.New("operation not implemented") -) diff --git a/src/net/interface.go b/src/net/interface.go deleted file mode 100644 index 32206f78fc..0000000000 --- a/src/net/interface.go +++ /dev/null @@ -1,253 +0,0 @@ -// The following is copied from Go 1.16 official implementation. - -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package net - -import ( - "errors" - "internal/itoa" - "sync" - "time" -) - -var ( - errInvalidInterface = errors.New("invalid network interface") - errInvalidInterfaceIndex = errors.New("invalid network interface index") - errInvalidInterfaceName = errors.New("invalid network interface name") - errNoSuchInterface = errors.New("no such network interface") - errNoSuchMulticastInterface = errors.New("no such multicast network interface") -) - -// Interface represents a mapping between network interface name -// and index. It also represents network interface facility -// information. -type Interface struct { - Index int // positive integer that starts at one, zero is never used - MTU int // maximum transmission unit - Name string // e.g., "en0", "lo0", "eth0.100" - HardwareAddr HardwareAddr // IEEE MAC-48, EUI-48 and EUI-64 form - Flags Flags // e.g., FlagUp, FlagLoopback, FlagMulticast -} - -type Flags uint - -const ( - FlagUp Flags = 1 << iota // interface is up - FlagBroadcast // interface supports broadcast access capability - FlagLoopback // interface is a loopback interface - FlagPointToPoint // interface belongs to a point-to-point link - FlagMulticast // interface supports multicast access capability -) - -var flagNames = []string{ - "up", - "broadcast", - "loopback", - "pointtopoint", - "multicast", -} - -func (f Flags) String() string { - s := "" - for i, name := range flagNames { - if f&(1< bits { - return nil - } - l := bits / 8 - m := make(IPMask, l) - n := uint(ones) - for i := 0; i < l; i++ { - if n >= 8 { - m[i] = 0xff - n -= 8 - continue - } - m[i] = ^byte(0xff >> n) - n = 0 - } - return m -} - -// Well-known IPv4 addresses -var ( - IPv4bcast = IPv4(255, 255, 255, 255) // limited broadcast - IPv4allsys = IPv4(224, 0, 0, 1) // all systems - IPv4allrouter = IPv4(224, 0, 0, 2) // all routers - IPv4zero = IPv4(0, 0, 0, 0) // all zeros -) - -// Well-known IPv6 addresses -var ( - IPv6zero = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - IPv6unspecified = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - IPv6loopback = IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} - IPv6interfacelocalallnodes = IP{0xff, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01} - IPv6linklocalallnodes = IP{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01} - IPv6linklocalallrouters = IP{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02} -) - -// IsUnspecified reports whether ip is an unspecified address, either -// the IPv4 address "0.0.0.0" or the IPv6 address "::". -func (ip IP) IsUnspecified() bool { - return ip.Equal(IPv4zero) || ip.Equal(IPv6unspecified) -} - -// IsLoopback reports whether ip is a loopback address. -func (ip IP) IsLoopback() bool { - if ip4 := ip.To4(); ip4 != nil { - return ip4[0] == 127 - } - return ip.Equal(IPv6loopback) -} - -// IsMulticast reports whether ip is a multicast address. -func (ip IP) IsMulticast() bool { - if ip4 := ip.To4(); ip4 != nil { - return ip4[0]&0xf0 == 0xe0 - } - return len(ip) == IPv6len && ip[0] == 0xff -} - -// IsInterfaceLocalMulticast reports whether ip is -// an interface-local multicast address. -func (ip IP) IsInterfaceLocalMulticast() bool { - return len(ip) == IPv6len && ip[0] == 0xff && ip[1]&0x0f == 0x01 -} - -// IsLinkLocalMulticast reports whether ip is a link-local -// multicast address. -func (ip IP) IsLinkLocalMulticast() bool { - if ip4 := ip.To4(); ip4 != nil { - return ip4[0] == 224 && ip4[1] == 0 && ip4[2] == 0 - } - return len(ip) == IPv6len && ip[0] == 0xff && ip[1]&0x0f == 0x02 -} - -// IsLinkLocalUnicast reports whether ip is a link-local -// unicast address. -func (ip IP) IsLinkLocalUnicast() bool { - if ip4 := ip.To4(); ip4 != nil { - return ip4[0] == 169 && ip4[1] == 254 - } - return len(ip) == IPv6len && ip[0] == 0xfe && ip[1]&0xc0 == 0x80 -} - -// IsGlobalUnicast reports whether ip is a global unicast -// address. -// -// The identification of global unicast addresses uses address type -// identification as defined in RFC 1122, RFC 4632 and RFC 4291 with -// the exception of IPv4 directed broadcast addresses. -// It returns true even if ip is in IPv4 private address space or -// local IPv6 unicast address space. -func (ip IP) IsGlobalUnicast() bool { - return (len(ip) == IPv4len || len(ip) == IPv6len) && - !ip.Equal(IPv4bcast) && - !ip.IsUnspecified() && - !ip.IsLoopback() && - !ip.IsMulticast() && - !ip.IsLinkLocalUnicast() -} - -// Is p all zeros? -func isZeros(p IP) bool { - for i := 0; i < len(p); i++ { - if p[i] != 0 { - return false - } - } - return true -} - -// To4 converts the IPv4 address ip to a 4-byte representation. -// If ip is not an IPv4 address, To4 returns nil. -func (ip IP) To4() IP { - if len(ip) == IPv4len { - return ip - } - if len(ip) == IPv6len && - isZeros(ip[0:10]) && - ip[10] == 0xff && - ip[11] == 0xff { - return ip[12:16] - } - return nil -} - -// To16 converts the IP address ip to a 16-byte representation. -// If ip is not an IP address (it is the wrong length), To16 returns nil. -func (ip IP) To16() IP { - if len(ip) == IPv4len { - return IPv4(ip[0], ip[1], ip[2], ip[3]) - } - if len(ip) == IPv6len { - return ip - } - return nil -} - -// Default route masks for IPv4. -var ( - classAMask = IPv4Mask(0xff, 0, 0, 0) - classBMask = IPv4Mask(0xff, 0xff, 0, 0) - classCMask = IPv4Mask(0xff, 0xff, 0xff, 0) -) - -// DefaultMask returns the default IP mask for the IP address ip. -// Only IPv4 addresses have default masks; DefaultMask returns -// nil if ip is not a valid IPv4 address. -func (ip IP) DefaultMask() IPMask { - if ip = ip.To4(); ip == nil { - return nil - } - switch { - case ip[0] < 0x80: - return classAMask - case ip[0] < 0xC0: - return classBMask - default: - return classCMask - } -} - -func allFF(b []byte) bool { - for _, c := range b { - if c != 0xff { - return false - } - } - return true -} - -// Mask returns the result of masking the IP address ip with mask. -func (ip IP) Mask(mask IPMask) IP { - if len(mask) == IPv6len && len(ip) == IPv4len && allFF(mask[:12]) { - mask = mask[12:] - } - if len(mask) == IPv4len && len(ip) == IPv6len && bytealg.Equal(ip[:12], v4InV6Prefix) { - ip = ip[12:] - } - n := len(ip) - if n != len(mask) { - return nil - } - out := make(IP, n) - for i := 0; i < n; i++ { - out[i] = ip[i] & mask[i] - } - return out -} - -// ubtoa encodes the string form of the integer v to dst[start:] and -// returns the number of bytes written to dst. The caller must ensure -// that dst has sufficient length. -func ubtoa(dst []byte, start int, v byte) int { - if v < 10 { - dst[start] = v + '0' - return 1 - } else if v < 100 { - dst[start+1] = v%10 + '0' - dst[start] = v/10 + '0' - return 2 - } - - dst[start+2] = v%10 + '0' - dst[start+1] = (v/10)%10 + '0' - dst[start] = v/100 + '0' - return 3 -} - -// String returns the string form of the IP address ip. -// It returns one of 4 forms: -// - "", if ip has length 0 -// - dotted decimal ("192.0.2.1"), if ip is an IPv4 or IP4-mapped IPv6 address -// - IPv6 ("2001:db8::1"), if ip is a valid IPv6 address -// - the hexadecimal form of ip, without punctuation, if no other cases apply -func (ip IP) String() string { - p := ip - - if len(ip) == 0 { - return "" - } - - // If IPv4, use dotted notation. - if p4 := p.To4(); len(p4) == IPv4len { - const maxIPv4StringLen = len("255.255.255.255") - b := make([]byte, maxIPv4StringLen) - - n := ubtoa(b, 0, p4[0]) - b[n] = '.' - n++ - - n += ubtoa(b, n, p4[1]) - b[n] = '.' - n++ - - n += ubtoa(b, n, p4[2]) - b[n] = '.' - n++ - - n += ubtoa(b, n, p4[3]) - return string(b[:n]) - } - if len(p) != IPv6len { - return "?" + hexString(ip) - } - - // Find longest run of zeros. - e0 := -1 - e1 := -1 - for i := 0; i < IPv6len; i += 2 { - j := i - for j < IPv6len && p[j] == 0 && p[j+1] == 0 { - j += 2 - } - if j > i && j-i > e1-e0 { - e0 = i - e1 = j - i = j - } - } - // The symbol "::" MUST NOT be used to shorten just one 16 bit 0 field. - if e1-e0 <= 2 { - e0 = -1 - e1 = -1 - } - - const maxLen = len("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff") - b := make([]byte, 0, maxLen) - - // Print with possible :: in place of run of zeros - for i := 0; i < IPv6len; i += 2 { - if i == e0 { - b = append(b, ':', ':') - i = e1 - if i >= IPv6len { - break - } - } else if i > 0 { - b = append(b, ':') - } - b = appendHex(b, (uint32(p[i])<<8)|uint32(p[i+1])) - } - return string(b) -} - -func hexString(b []byte) string { - s := make([]byte, len(b)*2) - for i, tn := range b { - s[i*2], s[i*2+1] = hexDigit[tn>>4], hexDigit[tn&0xf] - } - return string(s) -} - -// ipEmptyString is like ip.String except that it returns -// an empty string when ip is unset. -func ipEmptyString(ip IP) string { - if len(ip) == 0 { - return "" - } - return ip.String() -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The encoding is the same as returned by String, with one exception: -// When len(ip) is zero, it returns an empty slice. -func (ip IP) MarshalText() ([]byte, error) { - if len(ip) == 0 { - return []byte(""), nil - } - if len(ip) != IPv4len && len(ip) != IPv6len { - return nil, &AddrError{Err: "invalid IP address", Addr: hexString(ip)} - } - return []byte(ip.String()), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// The IP address is expected in a form accepted by ParseIP. -func (ip *IP) UnmarshalText(text []byte) error { - if len(text) == 0 { - *ip = nil - return nil - } - s := string(text) - x := ParseIP(s) - if x == nil { - return &ParseError{Type: "IP address", Text: s} - } - *ip = x - return nil -} - -// Equal reports whether ip and x are the same IP address. -// An IPv4 address and that same address in IPv6 form are -// considered to be equal. -func (ip IP) Equal(x IP) bool { - if len(ip) == len(x) { - return bytealg.Equal(ip, x) - } - if len(ip) == IPv4len && len(x) == IPv6len { - return bytealg.Equal(x[0:12], v4InV6Prefix) && bytealg.Equal(ip, x[12:]) - } - if len(ip) == IPv6len && len(x) == IPv4len { - return bytealg.Equal(ip[0:12], v4InV6Prefix) && bytealg.Equal(ip[12:], x) - } - return false -} - -func (ip IP) matchAddrFamily(x IP) bool { - return ip.To4() != nil && x.To4() != nil || ip.To16() != nil && ip.To4() == nil && x.To16() != nil && x.To4() == nil -} - -// If mask is a sequence of 1 bits followed by 0 bits, -// return the number of 1 bits. -func simpleMaskLength(mask IPMask) int { - var n int - for i, v := range mask { - if v == 0xff { - n += 8 - continue - } - // found non-ff byte - // count 1 bits - for v&0x80 != 0 { - n++ - v <<= 1 - } - // rest must be 0 bits - if v != 0 { - return -1 - } - for i++; i < len(mask); i++ { - if mask[i] != 0 { - return -1 - } - } - break - } - return n -} - -// Size returns the number of leading ones and total bits in the mask. -// If the mask is not in the canonical form--ones followed by zeros--then -// Size returns 0, 0. -func (m IPMask) Size() (ones, bits int) { - ones, bits = simpleMaskLength(m), len(m)*8 - if ones == -1 { - return 0, 0 - } - return -} - -// String returns the hexadecimal form of m, with no punctuation. -func (m IPMask) String() string { - if len(m) == 0 { - return "" - } - return hexString(m) -} - -func networkNumberAndMask(n *IPNet) (ip IP, m IPMask) { - if ip = n.IP.To4(); ip == nil { - ip = n.IP - if len(ip) != IPv6len { - return nil, nil - } - } - m = n.Mask - switch len(m) { - case IPv4len: - if len(ip) != IPv4len { - return nil, nil - } - case IPv6len: - if len(ip) == IPv4len { - m = m[12:] - } - default: - return nil, nil - } - return -} - -// Contains reports whether the network includes ip. -func (n *IPNet) Contains(ip IP) bool { - nn, m := networkNumberAndMask(n) - if x := ip.To4(); x != nil { - ip = x - } - l := len(ip) - if l != len(nn) { - return false - } - for i := 0; i < l; i++ { - if nn[i]&m[i] != ip[i]&m[i] { - return false - } - } - return true -} - -// Network returns the address's network name, "ip+net". -func (n *IPNet) Network() string { return "ip+net" } - -// String returns the CIDR notation of n like "192.0.2.0/24" -// or "2001:db8::/48" as defined in RFC 4632 and RFC 4291. -// If the mask is not in the canonical form, it returns the -// string which consists of an IP address, followed by a slash -// character and a mask expressed as hexadecimal form with no -// punctuation like "198.51.100.0/c000ff00". -func (n *IPNet) String() string { - nn, m := networkNumberAndMask(n) - if nn == nil || m == nil { - return "" - } - l := simpleMaskLength(m) - if l == -1 { - return nn.String() + "/" + m.String() - } - return nn.String() + "/" + itoa.Uitoa(uint(l)) -} - -// Parse IPv4 address (d.d.d.d). -func parseIPv4(s string) IP { - var p [IPv4len]byte - for i := 0; i < IPv4len; i++ { - if len(s) == 0 { - // Missing octets. - return nil - } - if i > 0 { - if s[0] != '.' { - return nil - } - s = s[1:] - } - n, c, ok := dtoi(s) - if !ok || n > 0xFF { - return nil - } - s = s[c:] - p[i] = byte(n) - } - if len(s) != 0 { - return nil - } - return IPv4(p[0], p[1], p[2], p[3]) -} - -// parseIPv6Zone parses s as a literal IPv6 address and its associated zone -// identifier which is described in RFC 4007. -func parseIPv6Zone(s string) (IP, string) { - s, zone := splitHostZone(s) - return parseIPv6(s), zone -} - -// parseIPv6 parses s as a literal IPv6 address described in RFC 4291 -// and RFC 5952. -func parseIPv6(s string) (ip IP) { - ip = make(IP, IPv6len) - ellipsis := -1 // position of ellipsis in ip - - // Might have leading ellipsis - if len(s) >= 2 && s[0] == ':' && s[1] == ':' { - ellipsis = 0 - s = s[2:] - // Might be only ellipsis - if len(s) == 0 { - return ip - } - } - - // Loop, parsing hex numbers followed by colon. - i := 0 - for i < IPv6len { - // Hex number. - n, c, ok := xtoi(s) - if !ok || n > 0xFFFF { - return nil - } - - // If followed by dot, might be in trailing IPv4. - if c < len(s) && s[c] == '.' { - if ellipsis < 0 && i != IPv6len-IPv4len { - // Not the right place. - return nil - } - if i+IPv4len > IPv6len { - // Not enough room. - return nil - } - ip4 := parseIPv4(s) - if ip4 == nil { - return nil - } - ip[i] = ip4[12] - ip[i+1] = ip4[13] - ip[i+2] = ip4[14] - ip[i+3] = ip4[15] - s = "" - i += IPv4len - break - } - - // Save this 16-bit chunk. - ip[i] = byte(n >> 8) - ip[i+1] = byte(n) - i += 2 - - // Stop at end of string. - s = s[c:] - if len(s) == 0 { - break - } - - // Otherwise must be followed by colon and more. - if s[0] != ':' || len(s) == 1 { - return nil - } - s = s[1:] - - // Look for ellipsis. - if s[0] == ':' { - if ellipsis >= 0 { // already have one - return nil - } - ellipsis = i - s = s[1:] - if len(s) == 0 { // can be at end - break - } - } - } - - // Must have used entire string. - if len(s) != 0 { - return nil - } - - // If didn't parse enough, expand ellipsis. - if i < IPv6len { - if ellipsis < 0 { - return nil - } - n := IPv6len - i - for j := i - 1; j >= ellipsis; j-- { - ip[j+n] = ip[j] - } - for j := ellipsis + n - 1; j >= ellipsis; j-- { - ip[j] = 0 - } - } else if ellipsis >= 0 { - // Ellipsis must represent at least one 0 group. - return nil - } - return ip -} - -// ParseIP parses s as an IP address, returning the result. -// The string s can be in IPv4 dotted decimal ("192.0.2.1"), IPv6 -// ("2001:db8::68"), or IPv4-mapped IPv6 ("::ffff:192.0.2.1") form. -// If s is not a valid textual representation of an IP address, -// ParseIP returns nil. -func ParseIP(s string) IP { - for i := 0; i < len(s); i++ { - switch s[i] { - case '.': - return parseIPv4(s) - case ':': - return parseIPv6(s) - } - } - return nil -} - -// parseIPZone parses s as an IP address, return it and its associated zone -// identifier (IPv6 only). -func parseIPZone(s string) (IP, string) { - for i := 0; i < len(s); i++ { - switch s[i] { - case '.': - return parseIPv4(s), "" - case ':': - return parseIPv6Zone(s) - } - } - return nil, "" -} - -// ParseCIDR parses s as a CIDR notation IP address and prefix length, -// like "192.0.2.0/24" or "2001:db8::/32", as defined in -// RFC 4632 and RFC 4291. -// -// It returns the IP address and the network implied by the IP and -// prefix length. -// For example, ParseCIDR("192.0.2.1/24") returns the IP address -// 192.0.2.1 and the network 192.0.2.0/24. -func ParseCIDR(s string) (IP, *IPNet, error) { - i := bytealg.IndexByteString(s, '/') - if i < 0 { - return nil, nil, &ParseError{Type: "CIDR address", Text: s} - } - addr, mask := s[:i], s[i+1:] - iplen := IPv4len - ip := parseIPv4(addr) - if ip == nil { - iplen = IPv6len - ip = parseIPv6(addr) - } - n, i, ok := dtoi(mask) - if ip == nil || !ok || i != len(mask) || n < 0 || n > 8*iplen { - return nil, nil, &ParseError{Type: "CIDR address", Text: s} - } - m := CIDRMask(n, 8*iplen) - return ip, &IPNet{IP: ip.Mask(m), Mask: m}, nil -} diff --git a/src/net/iprawsock.go b/src/net/iprawsock.go deleted file mode 100644 index d16d5eac1d..0000000000 --- a/src/net/iprawsock.go +++ /dev/null @@ -1,41 +0,0 @@ -// The following is copied from Go 1.16 official implementation. - -// Copyright 2010 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 net - -// IPAddr represents the address of an IP end point. -type IPAddr struct { - IP IP - Zone string // IPv6 scoped addressing zone -} - -// Network returns the address's network name, "ip". -func (a *IPAddr) Network() string { return "ip" } - -func (a *IPAddr) String() string { - if a == nil { - return "" - } - ip := ipEmptyString(a.IP) - if a.Zone != "" { - return ip + "%" + a.Zone - } - return ip -} - -func (a *IPAddr) isWildcard() bool { - if a == nil || a.IP == nil { - return true - } - return a.IP.IsUnspecified() -} - -func (a *IPAddr) opAddr() Addr { - if a == nil { - return nil - } - return a -} diff --git a/src/net/ipsock.go b/src/net/ipsock.go deleted file mode 100644 index 57ceaebf09..0000000000 --- a/src/net/ipsock.go +++ /dev/null @@ -1,98 +0,0 @@ -// The following is copied from Go 1.16 official implementation. - -// 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 net - -import "internal/bytealg" - -// SplitHostPort splits a network address of the form "host:port", -// "host%zone:port", "[host]:port" or "[host%zone]:port" into host or -// host%zone and port. -// -// A literal IPv6 address in hostport must be enclosed in square -// brackets, as in "[::1]:80", "[::1%lo0]:80". -// -// See func Dial for a description of the hostport parameter, and host -// and port results. -func SplitHostPort(hostport string) (host, port string, err error) { - const ( - missingPort = "missing port in address" - tooManyColons = "too many colons in address" - ) - addrErr := func(addr, why string) (host, port string, err error) { - return "", "", &AddrError{Err: why, Addr: addr} - } - j, k := 0, 0 - - // The port starts after the last colon. - i := last(hostport, ':') - if i < 0 { - return addrErr(hostport, missingPort) - } - - if hostport[0] == '[' { - // Expect the first ']' just before the last ':'. - end := bytealg.IndexByteString(hostport, ']') - if end < 0 { - return addrErr(hostport, "missing ']' in address") - } - switch end + 1 { - case len(hostport): - // There can't be a ':' behind the ']' now. - return addrErr(hostport, missingPort) - case i: - // The expected result. - default: - // Either ']' isn't followed by a colon, or it is - // followed by a colon that is not the last one. - if hostport[end+1] == ':' { - return addrErr(hostport, tooManyColons) - } - return addrErr(hostport, missingPort) - } - host = hostport[1:end] - j, k = 1, end+1 // there can't be a '[' resp. ']' before these positions - } else { - host = hostport[:i] - if bytealg.IndexByteString(host, ':') >= 0 { - return addrErr(hostport, tooManyColons) - } - } - if bytealg.IndexByteString(hostport[j:], '[') >= 0 { - return addrErr(hostport, "unexpected '[' in address") - } - if bytealg.IndexByteString(hostport[k:], ']') >= 0 { - return addrErr(hostport, "unexpected ']' in address") - } - - port = hostport[i+1:] - return host, port, nil -} - -func splitHostZone(s string) (host, zone string) { - // The IPv6 scoped addressing zone identifier starts after the - // last percent sign. - if i := last(s, '%'); i > 0 { - host, zone = s[:i], s[i+1:] - } else { - host = s - } - return -} - -// JoinHostPort combines host and port into a network address of the -// form "host:port". If host contains a colon, as found in literal -// IPv6 addresses, then JoinHostPort returns "[host]:port". -// -// See func Dial for a description of the host and port parameters. -func JoinHostPort(host, port string) string { - // We assume that host is a literal IPv6 address if host has - // colons. - if bytealg.IndexByteString(host, ':') >= 0 { - return "[" + host + "]:" + port - } - return host + ":" + port -} diff --git a/src/net/mac.go b/src/net/mac.go deleted file mode 100644 index 2bad98c462..0000000000 --- a/src/net/mac.go +++ /dev/null @@ -1,88 +0,0 @@ -// The following is copied from Go 1.16 official implementation. - -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package net - -const hexDigit = "0123456789abcdef" - -// A HardwareAddr represents a physical hardware address. -type HardwareAddr []byte - -func (a HardwareAddr) String() string { - if len(a) == 0 { - return "" - } - buf := make([]byte, 0, len(a)*3-1) - for i, b := range a { - if i > 0 { - buf = append(buf, ':') - } - buf = append(buf, hexDigit[b>>4]) - buf = append(buf, hexDigit[b&0xF]) - } - return string(buf) -} - -// ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet -// IP over InfiniBand link-layer address using one of the following formats: -// -// 00:00:5e:00:53:01 -// 02:00:5e:10:00:00:00:01 -// 00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01 -// 00-00-5e-00-53-01 -// 02-00-5e-10-00-00-00-01 -// 00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01 -// 0000.5e00.5301 -// 0200.5e10.0000.0001 -// 0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001 -func ParseMAC(s string) (hw HardwareAddr, err error) { - if len(s) < 14 { - goto err - } - - if s[2] == ':' || s[2] == '-' { - if (len(s)+1)%3 != 0 { - goto err - } - n := (len(s) + 1) / 3 - if n != 6 && n != 8 && n != 20 { - goto err - } - hw = make(HardwareAddr, n) - for x, i := 0, 0; i < n; i++ { - var ok bool - if hw[i], ok = xtoi2(s[x:], s[2]); !ok { - goto err - } - x += 3 - } - } else if s[4] == '.' { - if (len(s)+1)%5 != 0 { - goto err - } - n := 2 * (len(s) + 1) / 5 - if n != 6 && n != 8 && n != 20 { - goto err - } - hw = make(HardwareAddr, n) - for x, i := 0, 0; i < n; i += 2 { - var ok bool - if hw[i], ok = xtoi2(s[x:x+2], 0); !ok { - goto err - } - if hw[i+1], ok = xtoi2(s[x+2:], s[4]); !ok { - goto err - } - x += 5 - } - } else { - goto err - } - return hw, nil - -err: - return nil, &AddrError{Err: "invalid MAC address", Addr: s} -} diff --git a/src/net/net.go b/src/net/net.go deleted file mode 100644 index db4d8f117f..0000000000 --- a/src/net/net.go +++ /dev/null @@ -1,279 +0,0 @@ -// The following is copied from Go 1.18 official implementation. - -// 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 net - -import ( - "io" - "time" -) - -// Addr represents a network end point address. -// -// The two methods Network and String conventionally return strings -// that can be passed as the arguments to Dial, but the exact form -// and meaning of the strings is up to the implementation. -type Addr interface { - Network() string // name of the network (for example, "tcp", "udp") - String() string // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80") -} - -// Conn is a generic stream-oriented network connection. -// -// Multiple goroutines may invoke methods on a Conn simultaneously. -type Conn interface { - // Read reads data from the connection. - // Read can be made to time out and return an error after a fixed - // time limit; see SetDeadline and SetReadDeadline. - Read(b []byte) (n int, err error) - - // Write writes data to the connection. - // Write can be made to time out and return an error after a fixed - // time limit; see SetDeadline and SetWriteDeadline. - Write(b []byte) (n int, err error) - - // Close closes the connection. - // Any blocked Read or Write operations will be unblocked and return errors. - Close() error - - // LocalAddr returns the local network address, if known. - LocalAddr() Addr - - // RemoteAddr returns the remote network address, if known. - RemoteAddr() Addr - - // SetDeadline sets the read and write deadlines associated - // with the connection. It is equivalent to calling both - // SetReadDeadline and SetWriteDeadline. - // - // A deadline is an absolute time after which I/O operations - // fail instead of blocking. The deadline applies to all future - // and pending I/O, not just the immediately following call to - // Read or Write. After a deadline has been exceeded, the - // connection can be refreshed by setting a deadline in the future. - // - // If the deadline is exceeded a call to Read or Write or to other - // I/O methods will return an error that wraps os.ErrDeadlineExceeded. - // This can be tested using errors.Is(err, os.ErrDeadlineExceeded). - // The error's Timeout method will return true, but note that there - // are other possible errors for which the Timeout method will - // return true even if the deadline has not been exceeded. - // - // An idle timeout can be implemented by repeatedly extending - // the deadline after successful Read or Write calls. - // - // A zero value for t means I/O operations will not time out. - SetDeadline(t time.Time) error - - // SetReadDeadline sets the deadline for future Read calls - // and any currently-blocked Read call. - // A zero value for t means Read will not time out. - SetReadDeadline(t time.Time) error - - // SetWriteDeadline sets the deadline for future Write calls - // and any currently-blocked Write call. - // Even if write times out, it may return n > 0, indicating that - // some of the data was successfully written. - // A zero value for t means Write will not time out. - SetWriteDeadline(t time.Time) error -} - -type conn struct { - // -} - -// A Listener is a generic network listener for stream-oriented protocols. -// -// Multiple goroutines may invoke methods on a Listener simultaneously. -type Listener interface { - // Accept waits for and returns the next connection to the listener. - Accept() (Conn, error) - - // Close closes the listener. - // Any blocked Accept operations will be unblocked and return errors. - Close() error - - // Addr returns the listener's network address. - Addr() Addr -} - -// An Error represents a network error. -type Error interface { - error - Timeout() bool // Is the error a timeout? - - // Deprecated: Temporary errors are not well-defined. - // Most "temporary" errors are timeouts, and the few exceptions are surprising. - // Do not use this method. - Temporary() bool -} - -// OpError is the error type usually returned by functions in the net -// package. It describes the operation, network type, and address of -// an error. -type OpError struct { - // Op is the operation which caused the error, such as - // "read" or "write". - Op string - - // Net is the network type on which this error occurred, - // such as "tcp" or "udp6". - Net string - - // For operations involving a remote network connection, like - // Dial, Read, or Write, Source is the corresponding local - // network address. - Source Addr - - // Addr is the network address for which this error occurred. - // For local operations, like Listen or SetDeadline, Addr is - // the address of the local endpoint being manipulated. - // For operations involving a remote network connection, like - // Dial, Read, or Write, Addr is the remote address of that - // connection. - Addr Addr - - // Err is the error that occurred during the operation. - // The Error method panics if the error is nil. - Err error -} - -func (e *OpError) Unwrap() error { return e.Err } - -func (e *OpError) Error() string { - if e == nil { - return "" - } - s := e.Op - if e.Net != "" { - s += " " + e.Net - } - if e.Source != nil { - s += " " + e.Source.String() - } - if e.Addr != nil { - if e.Source != nil { - s += "->" - } else { - s += " " - } - s += e.Addr.String() - } - s += ": " + e.Err.Error() - return s -} - -// A ParseError is the error type of literal network address parsers. -type ParseError struct { - // Type is the type of string that was expected, such as - // "IP address", "CIDR address". - Type string - - // Text is the malformed text string. - Text string -} - -func (e *ParseError) Error() string { return "invalid " + e.Type + ": " + e.Text } - -type AddrError struct { - Err string - Addr string -} - -func (e *AddrError) Error() string { - if e == nil { - return "" - } - s := e.Err - if e.Addr != "" { - s = "address " + e.Addr + ": " + s - } - return s -} - -func (e *AddrError) Timeout() bool { return false } -func (e *AddrError) Temporary() bool { return false } - -// ErrClosed is the error returned by an I/O call on a network -// connection that has already been closed, or that is closed by -// another goroutine before the I/O is completed. This may be wrapped -// in another error, and should normally be tested using -// errors.Is(err, net.ErrClosed). -var ErrClosed = errClosed - -// buffersWriter is the interface implemented by Conns that support a -// "writev"-like batch write optimization. -// writeBuffers should fully consume and write all chunks from the -// provided Buffers, else it should report a non-nil error. -type buffersWriter interface { - writeBuffers(*Buffers) (int64, error) -} - -// Buffers contains zero or more runs of bytes to write. -// -// On certain machines, for certain types of connections, this is -// optimized into an OS-specific batch write operation (such as -// "writev"). -type Buffers [][]byte - -var ( - _ io.WriterTo = (*Buffers)(nil) - _ io.Reader = (*Buffers)(nil) -) - -// WriteTo writes contents of the buffers to w. -// -// WriteTo implements io.WriterTo for Buffers. -// -// WriteTo modifies the slice v as well as v[i] for 0 <= i < len(v), -// but does not modify v[i][j] for any i, j. -func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) { - if wv, ok := w.(buffersWriter); ok { - return wv.writeBuffers(v) - } - for _, b := range *v { - nb, err := w.Write(b) - n += int64(nb) - if err != nil { - v.consume(n) - return n, err - } - } - v.consume(n) - return n, nil -} - -// Read from the buffers. -// -// Read implements io.Reader for Buffers. -// -// Read modifies the slice v as well as v[i] for 0 <= i < len(v), -// but does not modify v[i][j] for any i, j. -func (v *Buffers) Read(p []byte) (n int, err error) { - for len(p) > 0 && len(*v) > 0 { - n0 := copy(p, (*v)[0]) - v.consume(int64(n0)) - p = p[n0:] - n += n0 - } - if len(*v) == 0 { - err = io.EOF - } - return -} - -func (v *Buffers) consume(n int64) { - for len(*v) > 0 { - ln0 := int64(len((*v)[0])) - if ln0 > n { - (*v)[0] = (*v)[0][n:] - return - } - n -= ln0 - (*v)[0] = nil - *v = (*v)[1:] - } -} diff --git a/src/net/parse.go b/src/net/parse.go deleted file mode 100644 index f1f2ccb5c9..0000000000 --- a/src/net/parse.go +++ /dev/null @@ -1,90 +0,0 @@ -// The following is copied from Go 1.16 official implementation. - -// 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 net - -// Bigger than we need, not too big to worry about overflow -const big = 0xFFFFFF - -// Decimal to integer. -// Returns number, characters consumed, success. -func dtoi(s string) (n int, i int, ok bool) { - n = 0 - for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ { - n = n*10 + int(s[i]-'0') - if n >= big { - return big, i, false - } - } - if i == 0 { - return 0, 0, false - } - return n, i, true -} - -// Hexadecimal to integer. -// Returns number, characters consumed, success. -func xtoi(s string) (n int, i int, ok bool) { - n = 0 - for i = 0; i < len(s); i++ { - if '0' <= s[i] && s[i] <= '9' { - n *= 16 - n += int(s[i] - '0') - } else if 'a' <= s[i] && s[i] <= 'f' { - n *= 16 - n += int(s[i]-'a') + 10 - } else if 'A' <= s[i] && s[i] <= 'F' { - n *= 16 - n += int(s[i]-'A') + 10 - } else { - break - } - if n >= big { - return 0, i, false - } - } - if i == 0 { - return 0, i, false - } - return n, i, true -} - -// xtoi2 converts the next two hex digits of s into a byte. -// If s is longer than 2 bytes then the third byte must be e. -// If the first two bytes of s are not hex digits or the third byte -// does not match e, false is returned. -func xtoi2(s string, e byte) (byte, bool) { - if len(s) > 2 && s[2] != e { - return 0, false - } - n, ei, ok := xtoi(s[:2]) - return byte(n), ok && ei == 2 -} - -// Convert i to a hexadecimal string. Leading zeros are not printed. -func appendHex(dst []byte, i uint32) []byte { - if i == 0 { - return append(dst, '0') - } - for j := 7; j >= 0; j-- { - v := i >> uint(j*4) - if v > 0 { - dst = append(dst, hexDigit[v&0xf]) - } - } - return dst -} - -// Index of rightmost occurrence of b in s. -func last(s string, b byte) int { - i := len(s) - for i--; i >= 0; i-- { - if s[i] == b { - break - } - } - return i -} diff --git a/src/net/pipe.go b/src/net/pipe.go deleted file mode 100644 index 02dd07cf9a..0000000000 --- a/src/net/pipe.go +++ /dev/null @@ -1,240 +0,0 @@ -// The following is copied from Go 1.19.2 official implementation. - -// Copyright 2010 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 net - -import ( - "io" - "os" - "sync" - "time" -) - -// pipeDeadline is an abstraction for handling timeouts. -type pipeDeadline struct { - mu sync.Mutex // Guards timer and cancel - timer *time.Timer - cancel chan struct{} // Must be non-nil -} - -func makePipeDeadline() pipeDeadline { - return pipeDeadline{cancel: make(chan struct{})} -} - -// set sets the point in time when the deadline will time out. -// A timeout event is signaled by closing the channel returned by waiter. -// Once a timeout has occurred, the deadline can be refreshed by specifying a -// t value in the future. -// -// A zero value for t prevents timeout. -func (d *pipeDeadline) set(t time.Time) { - d.mu.Lock() - defer d.mu.Unlock() - - if d.timer != nil && !d.timer.Stop() { - <-d.cancel // Wait for the timer callback to finish and close cancel - } - d.timer = nil - - // Time is zero, then there is no deadline. - closed := isClosedChan(d.cancel) - if t.IsZero() { - if closed { - d.cancel = make(chan struct{}) - } - return - } - - // Time in the future, setup a timer to cancel in the future. - if dur := time.Until(t); dur > 0 { - if closed { - d.cancel = make(chan struct{}) - } - d.timer = time.AfterFunc(dur, func() { - close(d.cancel) - }) - return - } - - // Time in the past, so close immediately. - if !closed { - close(d.cancel) - } -} - -// wait returns a channel that is closed when the deadline is exceeded. -func (d *pipeDeadline) wait() chan struct{} { - d.mu.Lock() - defer d.mu.Unlock() - return d.cancel -} - -func isClosedChan(c <-chan struct{}) bool { - select { - case <-c: - return true - default: - return false - } -} - -type pipeAddr struct{} - -func (pipeAddr) Network() string { return "pipe" } -func (pipeAddr) String() string { return "pipe" } - -type pipe struct { - wrMu sync.Mutex // Serialize Write operations - - // Used by local Read to interact with remote Write. - // Successful receive on rdRx is always followed by send on rdTx. - rdRx <-chan []byte - rdTx chan<- int - - // Used by local Write to interact with remote Read. - // Successful send on wrTx is always followed by receive on wrRx. - wrTx chan<- []byte - wrRx <-chan int - - once sync.Once // Protects closing localDone - localDone chan struct{} - remoteDone <-chan struct{} - - readDeadline pipeDeadline - writeDeadline pipeDeadline -} - -// Pipe creates a synchronous, in-memory, full duplex -// network connection; both ends implement the Conn interface. -// Reads on one end are matched with writes on the other, -// copying data directly between the two; there is no internal -// buffering. -func Pipe() (Conn, Conn) { - cb1 := make(chan []byte) - cb2 := make(chan []byte) - cn1 := make(chan int) - cn2 := make(chan int) - done1 := make(chan struct{}) - done2 := make(chan struct{}) - - p1 := &pipe{ - rdRx: cb1, rdTx: cn1, - wrTx: cb2, wrRx: cn2, - localDone: done1, remoteDone: done2, - readDeadline: makePipeDeadline(), - writeDeadline: makePipeDeadline(), - } - p2 := &pipe{ - rdRx: cb2, rdTx: cn2, - wrTx: cb1, wrRx: cn1, - localDone: done2, remoteDone: done1, - readDeadline: makePipeDeadline(), - writeDeadline: makePipeDeadline(), - } - return p1, p2 -} - -func (*pipe) LocalAddr() Addr { return pipeAddr{} } -func (*pipe) RemoteAddr() Addr { return pipeAddr{} } - -func (p *pipe) Read(b []byte) (int, error) { - n, err := p.read(b) - if err != nil && err != io.EOF && err != io.ErrClosedPipe { - err = &OpError{Op: "read", Net: "pipe", Err: err} - } - return n, err -} - -func (p *pipe) read(b []byte) (n int, err error) { - switch { - case isClosedChan(p.localDone): - return 0, io.ErrClosedPipe - case isClosedChan(p.remoteDone): - return 0, io.EOF - case isClosedChan(p.readDeadline.wait()): - return 0, os.ErrDeadlineExceeded - } - - select { - case bw := <-p.rdRx: - nr := copy(b, bw) - p.rdTx <- nr - return nr, nil - case <-p.localDone: - return 0, io.ErrClosedPipe - case <-p.remoteDone: - return 0, io.EOF - case <-p.readDeadline.wait(): - return 0, os.ErrDeadlineExceeded - } -} - -func (p *pipe) Write(b []byte) (int, error) { - n, err := p.write(b) - if err != nil && err != io.ErrClosedPipe { - err = &OpError{Op: "write", Net: "pipe", Err: err} - } - return n, err -} - -func (p *pipe) write(b []byte) (n int, err error) { - switch { - case isClosedChan(p.localDone): - return 0, io.ErrClosedPipe - case isClosedChan(p.remoteDone): - return 0, io.ErrClosedPipe - case isClosedChan(p.writeDeadline.wait()): - return 0, os.ErrDeadlineExceeded - } - - p.wrMu.Lock() // Ensure entirety of b is written together - defer p.wrMu.Unlock() - for once := true; once || len(b) > 0; once = false { - select { - case p.wrTx <- b: - nw := <-p.wrRx - b = b[nw:] - n += nw - case <-p.localDone: - return n, io.ErrClosedPipe - case <-p.remoteDone: - return n, io.ErrClosedPipe - case <-p.writeDeadline.wait(): - return n, os.ErrDeadlineExceeded - } - } - return n, nil -} - -func (p *pipe) SetDeadline(t time.Time) error { - if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { - return io.ErrClosedPipe - } - p.readDeadline.set(t) - p.writeDeadline.set(t) - return nil -} - -func (p *pipe) SetReadDeadline(t time.Time) error { - if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { - return io.ErrClosedPipe - } - p.readDeadline.set(t) - return nil -} - -func (p *pipe) SetWriteDeadline(t time.Time) error { - if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) { - return io.ErrClosedPipe - } - p.writeDeadline.set(t) - return nil -} - -func (p *pipe) Close() error { - p.once.Do(func() { close(p.localDone) }) - return nil -} diff --git a/src/net/pipe_test.go b/src/net/pipe_test.go deleted file mode 100644 index 7978fc6aa0..0000000000 --- a/src/net/pipe_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// The following is copied from Go 1.19.2 official implementation. - -// Copyright 2010 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 net - -import ( - "io" - "testing" - "time" -) - -func TestPipe(t *testing.T) { - testConn(t, func() (c1, c2 Conn, stop func(), err error) { - c1, c2 = Pipe() - stop = func() { - c1.Close() - c2.Close() - } - return - }) -} - -func TestPipeCloseError(t *testing.T) { - c1, c2 := Pipe() - c1.Close() - - if _, err := c1.Read(nil); err != io.ErrClosedPipe { - t.Errorf("c1.Read() = %v, want io.ErrClosedPipe", err) - } - if _, err := c1.Write(nil); err != io.ErrClosedPipe { - t.Errorf("c1.Write() = %v, want io.ErrClosedPipe", err) - } - if err := c1.SetDeadline(time.Time{}); err != io.ErrClosedPipe { - t.Errorf("c1.SetDeadline() = %v, want io.ErrClosedPipe", err) - } - if _, err := c2.Read(nil); err != io.EOF { - t.Errorf("c2.Read() = %v, want io.EOF", err) - } - if _, err := c2.Write(nil); err != io.ErrClosedPipe { - t.Errorf("c2.Write() = %v, want io.ErrClosedPipe", err) - } - if err := c2.SetDeadline(time.Time{}); err != io.ErrClosedPipe { - t.Errorf("c2.SetDeadline() = %v, want io.ErrClosedPipe", err) - } -} diff --git a/src/net/tcpsock.go b/src/net/tcpsock.go deleted file mode 100644 index d6aa602a9a..0000000000 --- a/src/net/tcpsock.go +++ /dev/null @@ -1,65 +0,0 @@ -package net - -import ( - "internal/itoa" - "net/netip" -) - -// TCPAddr represents the address of a TCP end point. -type TCPAddr struct { - IP IP - Port int - Zone string // IPv6 scoped addressing zone -} - -// AddrPort returns the TCPAddr a as a netip.AddrPort. -// -// If a.Port does not fit in a uint16, it's silently truncated. -// -// If a is nil, a zero value is returned. -func (a *TCPAddr) AddrPort() netip.AddrPort { - if a == nil { - return netip.AddrPort{} - } - na, _ := netip.AddrFromSlice(a.IP) - na = na.WithZone(a.Zone) - return netip.AddrPortFrom(na, uint16(a.Port)) -} - -// Network returns the address's network name, "tcp". -func (a *TCPAddr) Network() string { return "tcp" } - -func (a *TCPAddr) String() string { - if a == nil { - return "" - } - ip := ipEmptyString(a.IP) - if a.Zone != "" { - return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) - } - return JoinHostPort(ip, itoa.Itoa(a.Port)) -} - -func (a *TCPAddr) isWildcard() bool { - if a == nil || a.IP == nil { - return true - } - return a.IP.IsUnspecified() -} - -func (a *TCPAddr) opAddr() Addr { - if a == nil { - return nil - } - return a -} - -// TCPConn is an implementation of the Conn interface for TCP network -// connections. -type TCPConn struct { - conn -} - -func (c *TCPConn) CloseWrite() error { - return &OpError{"close", "", nil, nil, ErrNotImplemented} -} diff --git a/src/net/udpsock.go b/src/net/udpsock.go deleted file mode 100644 index f10800220c..0000000000 --- a/src/net/udpsock.go +++ /dev/null @@ -1,55 +0,0 @@ -package net - -import ( - "internal/itoa" - "net/netip" -) - -// UDPAddr represents the address of a UDP end point. -type UDPAddr struct { - IP IP - Port int - Zone string // IPv6 scoped addressing zone -} - -// AddrPort returns the UDPAddr a as a netip.AddrPort. -// -// If a.Port does not fit in a uint16, it's silently truncated. -// -// If a is nil, a zero value is returned. -func (a *UDPAddr) AddrPort() netip.AddrPort { - if a == nil { - return netip.AddrPort{} - } - na, _ := netip.AddrFromSlice(a.IP) - na = na.WithZone(a.Zone) - return netip.AddrPortFrom(na, uint16(a.Port)) -} - -// Network returns the address's network name, "udp". -func (a *UDPAddr) Network() string { return "udp" } - -func (a *UDPAddr) String() string { - if a == nil { - return "" - } - ip := ipEmptyString(a.IP) - if a.Zone != "" { - return JoinHostPort(ip+"%"+a.Zone, itoa.Itoa(a.Port)) - } - return JoinHostPort(ip, itoa.Itoa(a.Port)) -} - -func (a *UDPAddr) isWildcard() bool { - if a == nil || a.IP == nil { - return true - } - return a.IP.IsUnspecified() -} - -func (a *UDPAddr) opAddr() Addr { - if a == nil { - return nil - } - return a -} diff --git a/src/net/writev_test.go b/src/net/writev_test.go deleted file mode 100644 index 3a2c3efa3c..0000000000 --- a/src/net/writev_test.go +++ /dev/null @@ -1,132 +0,0 @@ -// The following is copied from Go 1.17 official implementation and -// modified to accommodate TinyGo. - -// 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 net - -import ( - "bytes" - "fmt" - "io" - "reflect" - "testing" -) - -func TestBuffers_read(t *testing.T) { - const story = "once upon a time in Gopherland ... " - buffers := Buffers{ - []byte("once "), - []byte("upon "), - []byte("a "), - []byte("time "), - []byte("in "), - []byte("Gopherland ... "), - } - got, err := io.ReadAll(&buffers) - if err != nil { - t.Fatal(err) - } - if string(got) != story { - t.Errorf("read %q; want %q", got, story) - } - if len(buffers) != 0 { - t.Errorf("len(buffers) = %d; want 0", len(buffers)) - } -} - -func TestBuffers_consume(t *testing.T) { - tests := []struct { - in Buffers - consume int64 - want Buffers - }{ - { - in: Buffers{[]byte("foo"), []byte("bar")}, - consume: 0, - want: Buffers{[]byte("foo"), []byte("bar")}, - }, - { - in: Buffers{[]byte("foo"), []byte("bar")}, - consume: 2, - want: Buffers{[]byte("o"), []byte("bar")}, - }, - { - in: Buffers{[]byte("foo"), []byte("bar")}, - consume: 3, - want: Buffers{[]byte("bar")}, - }, - { - in: Buffers{[]byte("foo"), []byte("bar")}, - consume: 4, - want: Buffers{[]byte("ar")}, - }, - { - in: Buffers{nil, nil, nil, []byte("bar")}, - consume: 1, - want: Buffers{[]byte("ar")}, - }, - { - in: Buffers{nil, nil, nil, []byte("foo")}, - consume: 0, - want: Buffers{[]byte("foo")}, - }, - { - in: Buffers{nil, nil, nil}, - consume: 0, - want: Buffers{}, - }, - } - for i, tt := range tests { - in := tt.in - in.consume(tt.consume) - if !reflect.DeepEqual(in, tt.want) { - t.Errorf("%d. after consume(%d) = %+v, want %+v", i, tt.consume, in, tt.want) - } - } -} - -func TestBuffers_WriteTo(t *testing.T) { - for _, name := range []string{"WriteTo", "Copy"} { - for _, size := range []int{0, 10, 1023, 1024, 1025} { - t.Run(fmt.Sprintf("%s/%d", name, size), func(t *testing.T) { - testBuffer_writeTo(t, size, name == "Copy") - }) - } - } -} - -func testBuffer_writeTo(t *testing.T, chunks int, useCopy bool) { - var want bytes.Buffer - for i := 0; i < chunks; i++ { - want.WriteByte(byte(i)) - } - - var b bytes.Buffer - buffers := make(Buffers, chunks) - for i := range buffers { - buffers[i] = want.Bytes()[i : i+1] - } - var n int64 - var err error - if useCopy { - n, err = io.Copy(&b, &buffers) - } else { - n, err = buffers.WriteTo(&b) - } - if err != nil { - t.Fatal(err) - } - if len(buffers) != 0 { - t.Fatal(fmt.Errorf("len(buffers) = %d; want 0", len(buffers))) - } - if n != int64(want.Len()) { - t.Fatal(fmt.Errorf("Buffers.WriteTo returned %d; want %d", n, want.Len())) - } - all, err := io.ReadAll(&b) - if !bytes.Equal(all, want.Bytes()) || err != nil { - t.Fatal(fmt.Errorf("read %q, %v; want %q, nil", all, err, want.Bytes())) - } -} 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 d093e3d184..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,20 @@ 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. +// If there is an error, it will be of type *LinkError. +func Rename(oldpath, newpath string) error { + return ErrNotImplemented +} + // Read reads up to len(b) bytes from machine.Serial. // It returns the number of bytes read and any error encountered. func (f stdioFileHandle) Read(b []byte) (n int, err error) { @@ -120,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 } @@ -127,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/reflect/all_test.go b/src/reflect/all_test.go index 39fc7a3747..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) @@ -1450,6 +1438,11 @@ func TestIsNil(t *testing.T) { NotNil(fi, t) } +func setField[S, V any](in S, offset uintptr, value V) (out S) { + *(*V)(unsafe.Add(unsafe.Pointer(&in), offset)) = value + return in +} + func TestIsZero(t *testing.T) { for i, tt := range []struct { x any @@ -1483,14 +1476,14 @@ func TestIsZero(t *testing.T) { {float32(1.2), false}, {float64(0), true}, {float64(1.2), false}, - {math.Copysign(0, -1), false}, + {math.Copysign(0, -1), true}, {complex64(0), true}, {complex64(1.2), false}, {complex128(0), true}, {complex128(1.2), false}, - {complex(math.Copysign(0, -1), 0), false}, - {complex(0, math.Copysign(0, -1)), false}, - {complex(math.Copysign(0, -1), math.Copysign(0, -1)), false}, + {complex(math.Copysign(0, -1), 0), true}, + {complex(0, math.Copysign(0, -1)), true}, + {complex(math.Copysign(0, -1), math.Copysign(0, -1)), true}, {uintptr(0), true}, {uintptr(128), false}, // Array @@ -1503,6 +1496,8 @@ func TestIsZero(t *testing.T) { {[3][]int{{1}}, false}, // incomparable array {[1 << 12]byte{}, true}, {[1 << 12]byte{1}, false}, + {[1]struct{ p *int }{}, true}, + {[1]struct{ p *int }{{new(int)}}, false}, {[3]Value{}, true}, {[3]Value{{}, ValueOf(0), {}}, false}, // Chan @@ -1539,6 +1534,20 @@ func TestIsZero(t *testing.T) { {struct{ s []int }{[]int{1}}, false}, // incomparable struct {struct{ Value }{}, true}, {struct{ Value }{ValueOf(0)}, false}, + {struct{ _, a, _ uintptr }{}, true}, // comparable struct with blank fields + {setField(struct{ _, a, _ uintptr }{}, 0*unsafe.Sizeof(uintptr(0)), 1), true}, + {setField(struct{ _, a, _ uintptr }{}, 1*unsafe.Sizeof(uintptr(0)), 1), false}, + {setField(struct{ _, a, _ uintptr }{}, 2*unsafe.Sizeof(uintptr(0)), 1), true}, + {struct{ _, a, _ func() }{}, true}, // incomparable struct with blank fields + {setField(struct{ _, a, _ func() }{}, 0*unsafe.Sizeof((func())(nil)), func() {}), true}, + {setField(struct{ _, a, _ func() }{}, 1*unsafe.Sizeof((func())(nil)), func() {}), false}, + {setField(struct{ _, a, _ func() }{}, 2*unsafe.Sizeof((func())(nil)), func() {}), true}, + {struct{ a [256]S }{}, true}, + {struct{ a [256]S }{a: [256]S{2: {i1: 1}}}, false}, + {struct{ a [256]float32 }{}, true}, + {struct{ a [256]float32 }{a: [256]float32{2: 1.0}}, false}, + {struct{ _, a [256]S }{}, true}, + {setField(struct{ _, a [256]S }{}, 0*unsafe.Sizeof(int64(0)), int64(1)), true}, // UnsafePointer {(unsafe.Pointer)(nil), true}, {(unsafe.Pointer)(new(int)), false}, @@ -1564,9 +1573,7 @@ func TestIsZero(t *testing.T) { p.SetZero() if !p.IsZero() { t.Errorf("%d: IsZero((%s)(%+v)) is true after SetZero", i, p.Kind(), tt.x) - } - } /* // TODO(tinygo): panic/recover support @@ -1581,7 +1588,8 @@ func TestIsZero(t *testing.T) { */ } -/* +// extra comment for gofmt + func TestInterfaceExtraction(t *testing.T) { var s struct { W io.Writer @@ -1593,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() { @@ -3349,6 +3354,8 @@ func TestNestedMethods(t *testing.T) { } } +*/ + type unexp struct{} func (*unexp) f() (int32, int8) { return 7, 7 } @@ -3360,8 +3367,6 @@ type unexpI interface { var unexpi unexpI = new(unexp) -/* - func TestUnexportedMethods(t *testing.T) { typ := TypeOf(unexpi) @@ -3370,8 +3375,6 @@ func TestUnexportedMethods(t *testing.T) { } } -*/ - type InnerInt struct { X int } @@ -3413,6 +3416,8 @@ func TestEmbeddedMethods(t *testing.T) { } } +*/ + type FuncDDD func(...any) error func (f FuncDDD) M() {} @@ -3424,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 @@ -3470,6 +3476,8 @@ func TestPtrToGC(t *testing.T) { } } +*/ + func TestAddr(t *testing.T) { var p struct { X, Y int @@ -3572,8 +3580,6 @@ func TestAllocations(t *testing.T) { }) } -*/ - func TestSmallNegativeInt(t *testing.T) { i := int16(-1) v := ValueOf(i) @@ -4887,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) } @@ -4926,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) { @@ -7906,6 +7951,8 @@ func TestConvertibleTo(t *testing.T) { } } +*/ + func TestSetIter(t *testing.T) { data := map[string]int{ "foo": 1, @@ -7995,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/benchmark_test.go b/src/reflect/benchmark_test.go new file mode 100644 index 0000000000..6c080ec16e --- /dev/null +++ b/src/reflect/benchmark_test.go @@ -0,0 +1,10 @@ +// 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 reflect_test + +type S struct { + i1 int64 + i2 int64 +} 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 39659cc628..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 -) - -// The base type struct. All type structs start with this. -type rawType struct { - meta uint8 // metadata byte, contains kind and flags (see contants 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 -} + // 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 -type ptrType struct { - rawType - numMethod uint16 - elem *rawType -} + // 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 -type arrayType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - arrayLen uintptr - slicePtr *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 mapType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - key *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 namedType struct { - rawType - numMethod uint16 - ptrTo *rawType - elem *rawType - pkg *byte - name [1]byte -} + // CanSeq reports whether a [Value] with this type can be iterated over using [Value.Seq]. + CanSeq() 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 + // CanSeq2 reports whether a [Value] with this type can be iterated over using [Value.Seq2]. + CanSeq2() bool } -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) - } + return toType(t.RawType.Elem()) } -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, 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) + f := t.RawType.Field(i) + return toStructField(f) } -// 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 -} - -// 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) + return t.RawType.Implements(&(u.(*rawType).RawType)) } -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()") + return toType(t.RawType.Key()) } -func (t rawType) Out(i int) Type { - panic("unimplemented: (reflect.Type).Out()") -} - -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,129 +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()") +// IsExported reports whether the field is exported. +func (f StructField) IsExported() bool { + return f.PkgPath == "" } -func MapOf(key, value Type) Type { - panic("unimplemented: reflect.MapOf()") -} +type StructTag = reflectlite.StructTag -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() -} - -func (it *MapIter) Value() Value { - if !it.valid { - panic("reflect.MapIter.Value called on invalid iterator") - } - - return it.val.Elem() -} - -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 { - 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) + return Value{((*reflectlite.MapIter)(it)).Key()} } -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) SetIterKey(iter *MapIter) { + v.Value.SetIterKey((*reflectlite.MapIter)(iter)) } -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()) -} - -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) + return Value{v.Value.Convert(toRawType(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, - } -} - -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" + return Value{reflectlite.New(toRawType(typ))} } -//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 @@ -1998,13 +184,27 @@ 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 { panic("unimplemented: (reflect.Value).Call()") } +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()") } @@ -2020,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 9be9789e15..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,214 @@ 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) + + // get the pointer to the interface value + inPtr := ValueOf(&value) + + // dereference to get the actual value (an interface) + inElem := inPtr.Elem() + + // create a new value of the same concrete type + uint16Type := TypeOf(uint16(0)) + newUint16Value := New(uint16Type).Elem() + newUint16Value.Set(ValueOf(uint16(13))) + + // set the new value to the interface + inElem.Set(newUint16Value) + + if value.(uint16) != 13 { + t.Errorf("Failed to set interface value from uint16") + } +} + func equal[T comparable](a, b []T) bool { if len(a) != len(b) { return false 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 11b39200f3..d8cd1ca43a 100644 --- a/src/runtime/algorithm.go +++ b/src/runtime/algorithm.go @@ -16,11 +16,19 @@ func rand_fastrand64() uint64 { } // This function is used by hash/maphash. +// This function isn't required anymore since Go 1.22, so should be removed once +// that becomes the minimum requirement. func fastrand() uint32 { xorshift32State = xorshift32(xorshift32State) 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 { @@ -34,6 +42,8 @@ func xorshift32(x uint32) uint32 { } // This function is used by hash/maphash. +// This function isn't required anymore since Go 1.22, so should be removed once +// that becomes the minimum requirement. func fastrand64() uint64 { xorshift64State = xorshiftMult64(xorshift64State) return xorshift64State @@ -56,3 +66,16 @@ func memhash(p unsafe.Pointer, seed, s uintptr) uintptr { } return uintptr(hash32(p, s, seed)) } + +// Function that's called from various packages starting with Go 1.22. +func rand() uint64 { + // Return a random number from hardware, falling back to software if + // unavailable. + n, ok := hardwareRand() + if !ok { + // Fallback to static random number. + // Not great, but we can't do much better than this. + n = fastrand64() + } + return n +} 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/arch_tinygowasm_malloc.go b/src/runtime/arch_tinygowasm_malloc.go index acbea9ab7a..239f7c73eb 100644 --- a/src/runtime/arch_tinygowasm_malloc.go +++ b/src/runtime/arch_tinygowasm_malloc.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && !custommalloc +//go:build tinygo.wasm && !(custommalloc || wasm_unknown) package runtime 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.go b/src/runtime/debug.go index 3dab52353b..139e18bcd2 100644 --- a/src/runtime/debug.go +++ b/src/runtime/debug.go @@ -18,3 +18,8 @@ func NumCgoCall() int { func NumGoroutine() int { return 1 } + +// Stub for Breakpoint, does not do anything. +func Breakpoint() { + panic("Breakpoint not supported") +} 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/extern.go b/src/runtime/extern.go index 17f42ebeef..f4e7c002c4 100644 --- a/src/runtime/extern.go +++ b/src/runtime/extern.go @@ -10,8 +10,7 @@ func Callers(skip int, pc []uintptr) int { var buildVersion string // Version returns the Tinygo tree's version string. -// It is the same as goenv.Version, or in case of a development build, -// it will be the concatenation of goenv.Version and the git commit hash. +// It is the same as goenv.Version(). func Version() string { return buildVersion } 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 79a3785564..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 @@ -17,11 +17,11 @@ // // | object type | size | bitstring | note // |-------------|------|-----------|------ -// | int | 1 | 0 | no pointers in this object -// | string | 2 | 10 | {pointer, len} pair so there is one pointer -// | []int | 3 | 100 | {pointer, len, cap} -// | [4]*int | 1 | 1 | even though it contains 4 pointers, an array repeats so it can be stored with size=1 -// | [30]byte | 1 | 0 | there are no pointers so the layout is very simple +// | int | 1 | 0 | no pointers in this object +// | string | 2 | 01 | {pointer, len} pair so there is one pointer +// | []int | 3 | 001 | {pointer, len, cap} +// | [4]*int | 1 | 1 | even though it contains 4 pointers, an array repeats so it can be stored with size=1 +// | [30]byte | 1 | 0 | there are no pointers so the layout is very simple // // The garbage collector scans objects by starting at the first word value in // the object. If the least significant bit of the bitstring is clear, it is 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 6f75110264..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,8 +113,119 @@ 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)) } } + +func hardwareRand() (n uint64, ok bool) { + n |= uint64(libc_arc4random()) + n |= uint64(libc_arc4random()) << 32 + 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 0b513dca50..0ae105c5fc 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -1,11 +1,13 @@ -//go:build linux && !baremetal && !nintendoswitch && !wasi +//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)) { @@ -120,3 +133,27 @@ func libc_getpagesize() int func syscall_Getpagesize() int { return libc_getpagesize() } + +func hardwareRand() (n uint64, ok bool) { + read := libc_getrandom(unsafe.Pointer(&n), 8, 0) + if read != 8 { + return 0, false + } + return n, true +} + +// ssize_t getrandom(void buf[.buflen], size_t buflen, unsigned int flags); +// +//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 63d8153fe6..33814c4368 100644 --- a/src/runtime/os_other.go +++ b/src/runtime/os_other.go @@ -1,4 +1,4 @@ -//go:build linux && (baremetal || nintendoswitch || wasi) +//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 new file mode 100644 index 0000000000..9f1a152d7f --- /dev/null +++ b/src/runtime/rand_hwrng.go @@ -0,0 +1,16 @@ +//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. + +package runtime + +import "machine" + +func hardwareRand() (n uint64, ok bool) { + n1, err1 := machine.GetRNG() + n2, err2 := machine.GetRNG() + n = uint64(n1)<<32 | uint64(n2) + ok = err1 == nil && err2 == nil + return +} diff --git a/src/runtime/rand_norng.go b/src/runtime/rand_norng.go new file mode 100644 index 0000000000..aa79c500dc --- /dev/null +++ b/src/runtime/rand_norng.go @@ -0,0 +1,7 @@ +//go:build baremetal && !(nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey || (tinygo.riscv32 && virt)) + +package runtime + +func hardwareRand() (n uint64, ok bool) { + return 0, false // no RNG available +} 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_esp32.go b/src/runtime/runtime_esp32.go index 909d3cc72e..39219bb031 100644 --- a/src/runtime/runtime_esp32.go +++ b/src/runtime/runtime_esp32.go @@ -15,31 +15,31 @@ import ( func main() { // Disable the protection on the watchdog timer (needed when started from // the bootloader). - esp.RTCCNTL.WDTWPROTECT.Set(0x050D83AA1) + esp.RTC_CNTL.WDTWPROTECT.Set(0x050D83AA1) // Disable both watchdog timers that are enabled by default on startup. // Note that these watchdogs can be protected, but the ROM bootloader // doesn't seem to protect them. - esp.RTCCNTL.WDTCONFIG0.Set(0) + esp.RTC_CNTL.WDTCONFIG0.Set(0) esp.TIMG0.WDTCONFIG0.Set(0) // Switch SoC clock source to PLL (instead of the default which is XTAL). // This switches the CPU (and APB) clock from 40MHz to 80MHz. // Options: - // RTCCNTL_CLK_CONF_SOC_CLK_SEL: PLL (default XTAL) - // RTCCNTL_CLK_CONF_CK8M_DIV_SEL: 2 (default) - // RTCCNTL_CLK_CONF_DIG_CLK8M_D256_EN: Enable (default) - // RTCCNTL_CLK_CONF_CK8M_DIV: DIV256 (default) - // The only real change made here is modifying RTCCNTL_CLK_CONF_SOC_CLK_SEL, + // RTC_CNTL_CLK_CONF_SOC_CLK_SEL: PLL (1) (default XTAL) + // RTC_CNTL_CLK_CONF_CK8M_DIV_SEL: 2 (default) + // RTC_CNTL_CLK_CONF_DIG_CLK8M_D256_EN: Enable (default) + // RTC_CNTL_CLK_CONF_CK8M_DIV: divide by 256 (default) + // The only real change made here is modifying RTC_CNTL_CLK_CONF_SOC_CLK_SEL, // but setting a fixed value produces smaller code. - esp.RTCCNTL.CLK_CONF.Set((esp.RTCCNTL_CLK_CONF_SOC_CLK_SEL_PLL << esp.RTCCNTL_CLK_CONF_SOC_CLK_SEL_Pos) | - (2 << esp.RTCCNTL_CLK_CONF_CK8M_DIV_SEL_Pos) | - (esp.RTCCNTL_CLK_CONF_DIG_CLK8M_D256_EN_Enable << esp.RTCCNTL_CLK_CONF_DIG_CLK8M_D256_EN_Pos) | - (esp.RTCCNTL_CLK_CONF_CK8M_DIV_DIV256 << esp.RTCCNTL_CLK_CONF_CK8M_DIV_Pos)) + esp.RTC_CNTL.CLK_CONF.Set((1 << esp.RTC_CNTL_CLK_CONF_SOC_CLK_SEL_Pos) | + (2 << esp.RTC_CNTL_CLK_CONF_CK8M_DIV_SEL_Pos) | + (1 << esp.RTC_CNTL_CLK_CONF_DIG_CLK8M_D256_EN_Pos) | + (1 << esp.RTC_CNTL_CLK_CONF_CK8M_DIV_Pos)) // Switch CPU from 80MHz to 160MHz. This doesn't affect the APB clock, // which is still running at 80MHz. - esp.DPORT.CPU_PER_CONF.Set(esp.DPORT_CPU_PER_CONF_CPUPERIOD_SEL_SEL_160) + esp.DPORT.CPU_PER_CONF.Set(1) // PLL_CLK / 2, see table 3-3 in the reference manual // Clear .bss section. .data has already been loaded by the ROM bootloader. // Do this after increasing the CPU clock to possibly make startup slightly diff --git a/src/runtime/runtime_esp32c3.go b/src/runtime/runtime_esp32c3.go index 8a4c40df4c..013c939246 100644 --- a/src/runtime/runtime_esp32c3.go +++ b/src/runtime/runtime_esp32c3.go @@ -5,6 +5,7 @@ package runtime import ( "device/esp" "device/riscv" + "machine" "runtime/volatile" "unsafe" ) @@ -30,12 +31,12 @@ func main() { esp.TIMG0.WDTCONFIG0.Set(0) // Disable RTC watchdog. - esp.RTC_CNTL.RTC_WDTWPROTECT.Set(0x50D83AA1) - esp.RTC_CNTL.RTC_WDTCONFIG0.Set(0) + esp.RTC_CNTL.WDTWPROTECT.Set(0x50D83AA1) + esp.RTC_CNTL.WDTCONFIG0.Set(0) // Disable super watchdog. - esp.RTC_CNTL.RTC_SWD_WPROTECT.Set(0x8F1D312A) - esp.RTC_CNTL.RTC_SWD_CONF.Set(esp.RTC_CNTL_RTC_SWD_CONF_SWD_DISABLE) + esp.RTC_CNTL.SWD_WPROTECT.Set(0x8F1D312A) + esp.RTC_CNTL.SWD_CONF.Set(esp.RTC_CNTL_SWD_CONF_SWD_DISABLE) // Change CPU frequency from 20MHz to 80MHz, by switching from the XTAL to // the PLL clock source (see table "CPU Clock Frequency" in the reference @@ -53,6 +54,10 @@ func main() { // Configure interrupt handler interruptInit() + // Initialize UART. + machine.USBCDC.Configure(machine.UARTConfig{}) + machine.InitSerial() + // Initialize main system timer used for time.Now. initTimer() diff --git a/src/runtime/runtime_esp32xx.go b/src/runtime/runtime_esp32xx.go index b2a16f872a..e4fe8835ae 100644 --- a/src/runtime/runtime_esp32xx.go +++ b/src/runtime/runtime_esp32xx.go @@ -10,22 +10,6 @@ import ( type timeUnit int64 -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() -} - // Initialize .bss: zero-initialized global variables. // The .data section has already been loaded by the ROM bootloader. func clearbss() { @@ -43,7 +27,7 @@ func initTimer() { // DIVIDER: 16-bit prescaler, set to 2 for dividing the APB clock by two // (40MHz). // esp.TIMG0.T0CONFIG.Set(0 << esp.TIMG_T0CONFIG_T0_EN_Pos) - esp.TIMG0.T0CONFIG.Set(esp.TIMG_T0CONFIG_T0_EN | esp.TIMG_T0CONFIG_T0_INCREASE | 2<= 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 059e449d55..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 } @@ -308,3 +329,14 @@ func svcOutputDebugString(str *uint8, size uint64) uint64 // //export svcGetInfo func svcGetInfo(output *uint64, id0 uint32, handle uint32, id1 uint64) uint64 + +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 bd8adea6d8..67367b479e 100644 --- a/src/runtime/runtime_tinygowasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -1,4 +1,7 @@ -//go:build tinygo.wasm +//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. @@ -85,3 +101,19 @@ func procPin() { //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { } + +func hardwareRand() (n uint64, ok bool) { + n |= uint64(libc_arc4random()) + n |= uint64(libc_arc4random()) << 32 + return n, true +} + +// uint32_t arc4random(void); +// +//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 new file mode 100644 index 0000000000..b67d70aeab --- /dev/null +++ b/src/runtime/runtime_tinygowasm_unknown.go @@ -0,0 +1,59 @@ +//go:build wasm_unknown + +package runtime + +const ( + stdout = 1 +) + +func putchar(c byte) { +} + +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) { + return 0, 0, 0 +} + +// Abort executes the wasm 'unreachable' instruction. +func abort() { + trap() +} + +//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 +// 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 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 8af3d673c4..6add9157d3 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -1,8 +1,13 @@ -//go:build (darwin || (linux && !baremetal && !wasi)) && !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 fc599a2a82..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 +//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 new file mode 100644 index 0000000000..27e2485791 --- /dev/null +++ b/src/runtime/runtime_wasm_unknown.go @@ -0,0 +1,41 @@ +//go:build wasm_unknown + +package runtime + +// TODO: this is essentially reactor mode wasm. So we might want to support +// -buildmode=c-shared (and default to it). + +type timeUnit int64 + +// libc constructors +// +//export __wasm_call_ctors +func __wasm_call_ctors() + +func init() { + __wasm_call_ctors() +} + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + +// with the wasm32-unknown-unknown target there is no way to determine any `precision` +const timePrecisionNanoseconds = 1000 + +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 30cb00c1ba..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 { @@ -228,3 +237,19 @@ func procPin() { //go:linkname procUnpin sync/atomic.runtime_procUnpin func procUnpin() { } + +func hardwareRand() (n uint64, ok bool) { + var n1, n2 uint32 + errCode1 := libc_rand_s(&n1) + errCode2 := libc_rand_s(&n2) + n = uint64(n1)<<32 | uint64(n2) + ok = errCode1 == 0 && errCode2 == 0 + return +} + +// Cryptographically secure random number generator. +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/rand-s?view=msvc-170 +// errno_t rand_s(unsigned int* randomValue); +// +//export rand_s +func libc_rand_s(randomValue *uint32) int32 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/symtab.go b/src/runtime/symtab.go index 37c21ee895..03b0d8cb7e 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -13,6 +13,8 @@ type Frame struct { File string Line int + + Entry uintptr } func CallersFrames(callers []uintptr) *Frames { 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 e12bf40c17..08c674d7ea 100644 --- a/src/sync/mutex.go +++ b/src/sync/mutex.go @@ -2,40 +2,9 @@ package sync import ( "internal/task" - _ "unsafe" ) -type Mutex struct { - locked bool - blocked task.Stack -} - -//go:linkname scheduleTask runtime.runqueuePushBack -func scheduleTask(*task.Task) - -func (m *Mutex) Lock() { - if m.locked { - // Push self onto stack of blocked tasks, and wait to be resumed. - m.blocked.Push(task.Current()) - task.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 - } -} +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 88ae317d34..be24d93031 100644 --- a/src/sync/mutex_test.go +++ b/src/sync/mutex_test.go @@ -7,6 +7,42 @@ import ( "testing" ) +func HammerMutex(m *sync.Mutex, loops int, cdone chan bool) { + for i := 0; i < loops; i++ { + if i%3 == 0 { + if m.TryLock() { + m.Unlock() + } + continue + } + m.Lock() + m.Unlock() + } + cdone <- true +} + +func TestMutex(t *testing.T) { + m := new(sync.Mutex) + + m.Lock() + if m.TryLock() { + t.Fatalf("TryLock succeeded with mutex locked") + } + m.Unlock() + if !m.TryLock() { + t.Fatalf("TryLock failed with mutex unlocked") + } + m.Unlock() + + c := make(chan bool) + for i := 0; i < 10; i++ { + go HammerMutex(m, 1000, c) + } + for i := 0; i < 10; i++ { + <-c + } +} + // TestMutexUncontended tests locking and unlocking a Mutex that is not shared with any other goroutines. func TestMutexUncontended(t *testing.T) { var mu sync.Mutex @@ -84,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() @@ -160,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 5ed57f13b4..7b50d4f7e8 100644 --- a/src/syscall/file_emulated.go +++ b/src/syscall/file_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasip1) +//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 d9198a779d..a079f400fb 100644 --- a/src/syscall/file_hosted.go +++ b/src/syscall/file_hosted.go @@ -1,4 +1,4 @@ -//go:build !(baremetal || (wasm && !wasip1)) +//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 2d789b63ea..f22289c5ad 100644 --- a/src/syscall/syscall.go +++ b/src/syscall/syscall.go @@ -1,3 +1,22 @@ package syscall +import ( + "errors" +) + +const ( + MSG_DONTWAIT = 0x40 + AF_INET = 0x2 + AF_INET6 = 0xa +) + func Exit(code int) + +type Rlimit struct { + Cur uint64 + Max uint64 +} + +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 5e6a231dff..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,9 +411,9 @@ func Chmod(path string, mode uint32) (err error) { return Lstat(path, &stat) } +// TODO: should this return runtime.wasmPageSize? func Getpagesize() int { - // per upstream - return 65536 + return libc_getpagesize() } type Utsname struct { @@ -411,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) @@ -422,12 +441,24 @@ 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) // This is a stub, it is not functional. func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) +// int getpagesize(void); +// +//export getpagesize +func libc_getpagesize() int + // int stat(const char *path, struct stat * buf); // //export stat @@ -462,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 87d4d9d7b5..a0965692f4 100644 --- a/src/syscall/syscall_nonhosted.go +++ b/src/syscall/syscall_nonhosted.go @@ -1,4 +1,4 @@ -//go:build baremetal || js +//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 d9f2f8654d..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 +//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/arduino-mkrwifi1010.json b/targets/arduino-mkrwifi1010.json index c1c695831e..225d29be3c 100644 --- a/targets/arduino-mkrwifi1010.json +++ b/targets/arduino-mkrwifi1010.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd21g18a"], - "build-tags": ["arduino_mkrwifi1010"], + "build-tags": ["arduino_mkrwifi1010", "ninafw"], "serial": "usb", "serial-port": ["2341:8054", "2341:0054"], "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", diff --git a/targets/arduino-nano33.json b/targets/arduino-nano33.json index 688d992730..f37fbabb5b 100644 --- a/targets/arduino-nano33.json +++ b/targets/arduino-nano33.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd21g18a"], - "build-tags": ["arduino_nano33"], + "build-tags": ["arduino_nano33", "ninafw", "ninafw_machine_init"], "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "serial-port": ["2341:8057", "2341:0057"], "flash-1200-bps-reset": "true" 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/atmega328pb.json b/targets/atmega328pb.json new file mode 100644 index 0000000000..c4e1b447f4 --- /dev/null +++ b/targets/atmega328pb.json @@ -0,0 +1,15 @@ +{ + "inherits": ["avr"], + "cpu": "atmega328pb", + "build-tags": ["atmega328pb", "atmega", "avr5"], + "ldflags": [ + "--defsym=_bootloader_size=512", + "--defsym=_stack_size=512" + ], + "serial": "uart", + "linkerscript": "src/device/avr/atmega328pb.ld", + "extra-files": [ + "targets/avr.S", + "src/device/avr/atmega328pb.s" + ] +} diff --git a/targets/avrtiny.S b/targets/avrtiny.S index 75f0c604fb..6b1a195cf3 100644 --- a/targets/avrtiny.S +++ b/targets/avrtiny.S @@ -1,4 +1,3 @@ -; TODO: remove these in LLVM 15 #define __tmp_reg__ r16 #define __zero_reg__ r17 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.json b/targets/esp32.json index c0b0f76a60..f49282fdd5 100644 --- a/targets/esp32.json +++ b/targets/esp32.json @@ -1,7 +1,7 @@ { "inherits": ["xtensa"], "cpu": "esp32", - "features": "+atomctl,+bool,+coprocessor,+debug,+density,+dfpaccel,+div32,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", + "features": "+atomctl,+bool,+clamps,+coprocessor,+debug,+density,+dfpaccel,+div32,+exception,+fp,+highpriinterrupts,+interrupt,+loop,+mac16,+memctl,+minmax,+miscsr,+mul32,+mul32high,+nsa,+prid,+regprotect,+rvector,+s32c1i,+sext,+threadptr,+timerint,+windowed", "build-tags": ["esp32", "esp"], "scheduler": "tasks", "serial": "uart", 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 2795c37f57..900c4845eb 100644 --- a/targets/esp32c3.json +++ b/targets/esp32c3.json @@ -1,8 +1,8 @@ { "inherits": ["riscv32"], - "features": "+32bit,+c,+m,-64bit,-a,-d,-e,-experimental-zawrs,-experimental-zca,-experimental-zcd,-experimental-zcf,-experimental-zihintntl,-experimental-ztso,-experimental-zvfh,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xtheadvdot,-xventanacondops,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zihintpause,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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": "uart", + "serial": "usb", "rtlib": "compiler-rt", "libc": "picolibc", "cflags": [ diff --git a/targets/esp32c3.ld b/targets/esp32c3.ld index b6be6e7acb..81a8c4f2ae 100644 --- a/targets/esp32c3.ld +++ b/targets/esp32c3.ld @@ -58,7 +58,7 @@ SECTIONS */ .rodata : ALIGN(4) { - *(.rodata .rodata.*) + *(.rodata*) . = ALIGN (4); } >DROM @@ -97,6 +97,7 @@ SECTIONS _sdata = ABSOLUTE(.); *(.sdata) *(.data .data.*) + *(.dram*) . = ALIGN (4); _edata = ABSOLUTE(.); } >DRAM @@ -113,12 +114,22 @@ SECTIONS . += SIZEOF(.data); } > IRAM - /* Initialization code is loaded into IRAM. This memory area is also used by - * the heap, so no RAM is wasted. + /* IRAM segment. This contains some functions that always need to be loaded + * in IRAM, and contains initialization code. + * The initialization code is later reclaimed for the heap, so no RAM is + * wasted. */ - .init : ALIGN(4) + .iram : ALIGN(4) { + *(.iram*) + *(.wifislprxiram*) + *(.wifiextrairam*) + *(.wifi0iram*) + *(.wifislpiram*) + *(.wifirxiram*) + __init_start = .; *(.init) + __init_end = .; } >IRAM /* Dummy section to put the IROM segment exactly behind the IRAM segment. @@ -132,7 +143,7 @@ SECTIONS . += 0x18; /* esp_image_header_t */ . += SIZEOF(.rodata) + ((SIZEOF(.rodata) != 0) ? 0x8 : 0); /* DROM segment (optional) */ . += SIZEOF(.data) + ((SIZEOF(.data) != 0) ? 0x8 : 0); /* DRAM segment (optional) */ - . += SIZEOF(.init) + 0x8; /* IRAM segment */ + . += SIZEOF(.iram) + 0x8; /* IRAM segment */ . += 0x8; /* IROM segment header */ } > IROM @@ -158,17 +169,17 @@ SECTIONS * The magic value comes from here: * https://github.com/espressif/esp-idf/blob/61299f879e/components/bootloader/subproject/main/ld/esp32c3/bootloader.ld#L191 */ - ASSERT((_edata + SIZEOF(.init)) < 0x3FCDE710, "the .init section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") + ASSERT((_edata + SIZEOF(.iram)) < 0x3FCDE710, "the .iram section overlaps with the stack used by the boot ROM, possibly causing corruption at startup") } /* For the garbage collector. - * Note that _heap_start starts after _edata (without caring for the .init - * section), because the .init section isn't necessary anymore after startup and - * can thus be overwritten by the heap. + * Note that _heap_start starts after _edata + most of the IRAM section. + * It starts just before the initialisation code, which isn't necessary anymore + * after startup and can thus be overwritten by the heap. */ _globals_start = _sbss; _globals_end = _edata; -_heap_start = _edata; +_heap_start = _edata + SIZEOF(.iram) - (__init_end - __init_start); _heap_end = ORIGIN(DRAM) + LENGTH(DRAM); _stack_size = 4K; @@ -286,3 +297,1942 @@ __unordsf2 = 0x400008c8; memset = 0x40000354; memcpy = 0x40000358; memmove = 0x4000035c; + + +/* From ESP-IDF: + * components/esp_rom/esp32c3/ld/esp32c3.rom.ld + * These are needed for wifi/BLE support and are available on the Apache 2.0 + * license. + */ +/* ROM function interface esp32c3.rom.ld for esp32c3 + * + * + * Generated from ./interface-esp32c3.yml md5sum 93b28a9e1fe42d212018eb4336849208 + * + * Compatible with ROM where ECO version equal or greater to 0. + * + * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. + */ + +/*************************************** + Group common + ***************************************/ + +/* Functions */ +rtc_get_reset_reason = 0x40000018; +analog_super_wdt_reset_happened = 0x4000001c; +jtag_cpu_reset_happened = 0x40000020; +rtc_get_wakeup_cause = 0x40000024; +rtc_boot_control = 0x40000028; +rtc_select_apb_bridge = 0x4000002c; +rtc_unhold_all_pads = 0x40000030; +set_rtc_memory_crc = 0x40000034; +cacl_rtc_memory_crc = 0x40000038; +ets_is_print_boot = 0x4000003c; +ets_printf = 0x40000040; +ets_install_putc1 = 0x40000044; +ets_install_uart_printf = 0x40000048; +ets_install_putc2 = 0x4000004c; +PROVIDE( ets_delay_us = 0x40000050 ); +ets_get_stack_info = 0x40000054; +ets_install_lock = 0x40000058; +ets_backup_dma_copy = 0x4000005c; +ets_apb_backup_init_lock_func = 0x40000060; +UartRxString = 0x40000064; +uart_tx_one_char = 0x40000068; +uart_tx_one_char2 = 0x4000006c; +uart_rx_one_char = 0x40000070; +uart_rx_one_char_block = 0x40000074; +uart_rx_readbuff = 0x40000078; +uartAttach = 0x4000007c; +uart_tx_flush = 0x40000080; +uart_tx_wait_idle = 0x40000084; +uart_div_modify = 0x40000088; +multofup = 0x4000008c; +software_reset = 0x40000090; +software_reset_cpu = 0x40000094; +assist_debug_clock_enable = 0x40000098; +assist_debug_record_enable = 0x4000009c; +clear_super_wdt_reset_flag = 0x400000a0; +disable_default_watchdog = 0x400000a4; +send_packet = 0x400000a8; +recv_packet = 0x400000ac; +GetUartDevice = 0x400000b0; +UartDwnLdProc = 0x400000b4; +Uart_Init = 0x400000b8; +ets_set_user_start = 0x400000bc; +/* Data (.data, .bss, .rodata) */ +ets_rom_layout_p = 0x3ff1fffc; +ets_ops_table_ptr = 0x3fcdfffc; + + +/*************************************** + Group miniz + ***************************************/ + +/* Functions */ +mz_adler32 = 0x400000c0; +mz_crc32 = 0x400000c4; +mz_free = 0x400000c8; +tdefl_compress = 0x400000cc; +tdefl_compress_buffer = 0x400000d0; +tdefl_compress_mem_to_heap = 0x400000d4; +tdefl_compress_mem_to_mem = 0x400000d8; +tdefl_compress_mem_to_output = 0x400000dc; +tdefl_get_adler32 = 0x400000e0; +tdefl_get_prev_return_status = 0x400000e4; +tdefl_init = 0x400000e8; +tdefl_write_image_to_png_file_in_memory = 0x400000ec; +tdefl_write_image_to_png_file_in_memory_ex = 0x400000f0; +tinfl_decompress = 0x400000f4; +tinfl_decompress_mem_to_callback = 0x400000f8; +tinfl_decompress_mem_to_heap = 0x400000fc; +tinfl_decompress_mem_to_mem = 0x40000100; + + +/*************************************** + Group tjpgd + ***************************************/ + +/* Functions */ +PROVIDE( jd_prepare = 0x40000104 ); +PROVIDE( jd_decomp = 0x40000108 ); + + +/*************************************** + Group spiflash_legacy + ***************************************/ + +/* Functions */ +PROVIDE( esp_rom_spiflash_wait_idle = 0x4000010c ); +PROVIDE( esp_rom_spiflash_write_encrypted = 0x40000110 ); +PROVIDE( esp_rom_spiflash_write_encrypted_dest = 0x40000114 ); +PROVIDE( esp_rom_spiflash_write_encrypted_enable = 0x40000118 ); +PROVIDE( esp_rom_spiflash_write_encrypted_disable = 0x4000011c ); +PROVIDE( esp_rom_spiflash_erase_chip = 0x40000120 ); +PROVIDE( esp_rom_spiflash_erase_block = 0x40000124 ); +PROVIDE( esp_rom_spiflash_erase_sector = 0x40000128 ); +PROVIDE( esp_rom_spiflash_write = 0x4000012c ); +PROVIDE( esp_rom_spiflash_read = 0x40000130 ); +PROVIDE( esp_rom_spiflash_config_param = 0x40000134 ); +PROVIDE( esp_rom_spiflash_read_user_cmd = 0x40000138 ); +PROVIDE( esp_rom_spiflash_select_qio_pins = 0x4000013c ); +PROVIDE( esp_rom_spiflash_unlock = 0x40000140 ); +PROVIDE( esp_rom_spi_flash_auto_sus_res = 0x40000144 ); +PROVIDE( esp_rom_spi_flash_send_resume = 0x40000148 ); +PROVIDE( esp_rom_spi_flash_update_id = 0x4000014c ); +PROVIDE( esp_rom_spiflash_config_clk = 0x40000150 ); +PROVIDE( esp_rom_spiflash_config_readmode = 0x40000154 ); +PROVIDE( esp_rom_spiflash_read_status = 0x40000158 ); +PROVIDE( esp_rom_spiflash_read_statushigh = 0x4000015c ); +PROVIDE( esp_rom_spiflash_write_status = 0x40000160 ); +PROVIDE( esp_rom_spiflash_attach = 0x40000164 ); +PROVIDE( spi_flash_get_chip_size = 0x40000168 ); +PROVIDE( spi_flash_guard_set = 0x4000016c ); +PROVIDE( spi_flash_guard_get = 0x40000170 ); +PROVIDE( spi_flash_write_config_set = 0x40000174 ); +PROVIDE( spi_flash_write_config_get = 0x40000178 ); +PROVIDE( spi_flash_safe_write_address_func_set = 0x4000017c ); +PROVIDE( spi_flash_unlock = 0x40000180 ); +PROVIDE( spi_flash_erase_range = 0x40000184 ); +PROVIDE( spi_flash_erase_sector = 0x40000188 ); +PROVIDE( spi_flash_write = 0x4000018c ); +PROVIDE( spi_flash_read = 0x40000190 ); +PROVIDE( spi_flash_write_encrypted = 0x40000194 ); +PROVIDE( spi_flash_read_encrypted = 0x40000198 ); +PROVIDE( spi_flash_mmap_os_func_set = 0x4000019c ); +PROVIDE( spi_flash_mmap_page_num_init = 0x400001a0 ); +PROVIDE( spi_flash_mmap = 0x400001a4 ); +PROVIDE( spi_flash_mmap_pages = 0x400001a8 ); +PROVIDE( spi_flash_munmap = 0x400001ac ); +PROVIDE( spi_flash_mmap_dump = 0x400001b0 ); +PROVIDE( spi_flash_check_and_flush_cache = 0x400001b4 ); +PROVIDE( spi_flash_mmap_get_free_pages = 0x400001b8 ); +PROVIDE( spi_flash_cache2phys = 0x400001bc ); +PROVIDE( spi_flash_phys2cache = 0x400001c0 ); +PROVIDE( spi_flash_disable_cache = 0x400001c4 ); +PROVIDE( spi_flash_restore_cache = 0x400001c8 ); +PROVIDE( spi_flash_cache_enabled = 0x400001cc ); +PROVIDE( spi_flash_enable_cache = 0x400001d0 ); +PROVIDE( spi_cache_mode_switch = 0x400001d4 ); +PROVIDE( spi_common_set_dummy_output = 0x400001d8 ); +PROVIDE( spi_common_set_flash_cs_timing = 0x400001dc ); +PROVIDE( esp_enable_cache_flash_wrap = 0x400001e0 ); +PROVIDE( SPIEraseArea = 0x400001e4 ); +PROVIDE( SPILock = 0x400001e8 ); +PROVIDE( SPIMasterReadModeCnfig = 0x400001ec ); +PROVIDE( SPI_Common_Command = 0x400001f0 ); +PROVIDE( SPI_WakeUp = 0x400001f4 ); +PROVIDE( SPI_block_erase = 0x400001f8 ); +PROVIDE( SPI_chip_erase = 0x400001fc ); +PROVIDE( SPI_init = 0x40000200 ); +PROVIDE( SPI_page_program = 0x40000204 ); +PROVIDE( SPI_read_data = 0x40000208 ); +PROVIDE( SPI_sector_erase = 0x4000020c ); +PROVIDE( SPI_write_enable = 0x40000210 ); +PROVIDE( SelectSpiFunction = 0x40000214 ); +PROVIDE( SetSpiDrvs = 0x40000218 ); +PROVIDE( Wait_SPI_Idle = 0x4000021c ); +PROVIDE( spi_dummy_len_fix = 0x40000220 ); +PROVIDE( Disable_QMode = 0x40000224 ); +PROVIDE( Enable_QMode = 0x40000228 ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( rom_spiflash_legacy_funcs = 0x3fcdfff4 ); +PROVIDE( rom_spiflash_legacy_data = 0x3fcdfff0 ); +PROVIDE( g_flash_guard_ops = 0x3fcdfff8 ); + + +/*************************************** + Group spi_flash_hal + ***************************************/ + +/* Functions */ +PROVIDE( spi_flash_hal_poll_cmd_done = 0x4000022c ); +PROVIDE( spi_flash_hal_device_config = 0x40000230 ); +PROVIDE( spi_flash_hal_configure_host_io_mode = 0x40000234 ); +PROVIDE( spi_flash_hal_common_command = 0x40000238 ); +PROVIDE( spi_flash_hal_read = 0x4000023c ); +PROVIDE( spi_flash_hal_erase_chip = 0x40000240 ); +PROVIDE( spi_flash_hal_erase_sector = 0x40000244 ); +PROVIDE( spi_flash_hal_erase_block = 0x40000248 ); +PROVIDE( spi_flash_hal_program_page = 0x4000024c ); +PROVIDE( spi_flash_hal_set_write_protect = 0x40000250 ); +PROVIDE( spi_flash_hal_host_idle = 0x40000254 ); + + +/*************************************** + Group spi_flash_chips + ***************************************/ + +/* Functions */ +PROVIDE( spi_flash_chip_generic_probe = 0x40000258 ); +PROVIDE( spi_flash_chip_generic_detect_size = 0x4000025c ); +PROVIDE( spi_flash_chip_generic_write = 0x40000260 ); +PROVIDE( spi_flash_chip_generic_write_encrypted = 0x40000264 ); +PROVIDE( spi_flash_chip_generic_set_write_protect = 0x40000268 ); +PROVIDE( spi_flash_common_write_status_16b_wrsr = 0x4000026c ); +PROVIDE( spi_flash_chip_generic_reset = 0x40000270 ); +PROVIDE( spi_flash_chip_generic_erase_chip = 0x40000274 ); +PROVIDE( spi_flash_chip_generic_erase_sector = 0x40000278 ); +PROVIDE( spi_flash_chip_generic_erase_block = 0x4000027c ); +PROVIDE( spi_flash_chip_generic_page_program = 0x40000280 ); +PROVIDE( spi_flash_chip_generic_get_write_protect = 0x40000284 ); +PROVIDE( spi_flash_common_read_status_16b_rdsr_rdsr2 = 0x40000288 ); +PROVIDE( spi_flash_chip_generic_read_reg = 0x4000028c ); +PROVIDE( spi_flash_chip_generic_yield = 0x40000290 ); +PROVIDE( spi_flash_generic_wait_host_idle = 0x40000294 ); +PROVIDE( spi_flash_chip_generic_wait_idle = 0x40000298 ); +PROVIDE( spi_flash_chip_generic_config_host_io_mode = 0x4000029c ); +PROVIDE( spi_flash_chip_generic_read = 0x400002a0 ); +PROVIDE( spi_flash_common_read_status_8b_rdsr2 = 0x400002a4 ); +PROVIDE( spi_flash_chip_generic_get_io_mode = 0x400002a8 ); +PROVIDE( spi_flash_common_read_status_8b_rdsr = 0x400002ac ); +PROVIDE( spi_flash_common_write_status_8b_wrsr = 0x400002b0 ); +PROVIDE( spi_flash_common_write_status_8b_wrsr2 = 0x400002b4 ); +PROVIDE( spi_flash_common_set_io_mode = 0x400002b8 ); +PROVIDE( spi_flash_chip_generic_set_io_mode = 0x400002bc ); +PROVIDE( spi_flash_chip_gd_get_io_mode = 0x400002c0 ); +PROVIDE( spi_flash_chip_gd_probe = 0x400002c4 ); +PROVIDE( spi_flash_chip_gd_set_io_mode = 0x400002c8 ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( spi_flash_chip_generic_config_data = 0x3fcdffec ); + + +/*************************************** + Group memspi_host + ***************************************/ + +/* Functions */ +PROVIDE( memspi_host_read_id_hs = 0x400002cc ); +PROVIDE( memspi_host_read_status_hs = 0x400002d0 ); +PROVIDE( memspi_host_flush_cache = 0x400002d4 ); +PROVIDE( memspi_host_erase_chip = 0x400002d8 ); +PROVIDE( memspi_host_erase_sector = 0x400002dc ); +PROVIDE( memspi_host_erase_block = 0x400002e0 ); +PROVIDE( memspi_host_program_page = 0x400002e4 ); +PROVIDE( memspi_host_read = 0x400002e8 ); +PROVIDE( memspi_host_set_write_protect = 0x400002ec ); +PROVIDE( memspi_host_set_max_read_len = 0x400002f0 ); +PROVIDE( memspi_host_read_data_slicer = 0x400002f4 ); +PROVIDE( memspi_host_write_data_slicer = 0x400002f8 ); + + +/*************************************** + Group esp_flash + ***************************************/ + +/* Functions */ +PROVIDE( esp_flash_chip_driver_initialized = 0x400002fc ); +PROVIDE( esp_flash_read_id = 0x40000300 ); +PROVIDE( esp_flash_get_size = 0x40000304 ); +PROVIDE( esp_flash_erase_chip = 0x40000308 ); +PROVIDE( rom_esp_flash_erase_region = 0x4000030c ); +PROVIDE( esp_flash_get_chip_write_protect = 0x40000310 ); +PROVIDE( esp_flash_set_chip_write_protect = 0x40000314 ); +PROVIDE( esp_flash_get_protectable_regions = 0x40000318 ); +PROVIDE( esp_flash_get_protected_region = 0x4000031c ); +PROVIDE( esp_flash_set_protected_region = 0x40000320 ); +PROVIDE( esp_flash_read = 0x40000324 ); +PROVIDE( esp_flash_write = 0x40000328 ); +PROVIDE( esp_flash_write_encrypted = 0x4000032c ); +PROVIDE( esp_flash_read_encrypted = 0x40000330 ); +PROVIDE( esp_flash_get_io_mode = 0x40000334 ); +PROVIDE( esp_flash_set_io_mode = 0x40000338 ); +PROVIDE( spi_flash_boot_attach = 0x4000033c ); +PROVIDE( spi_flash_dump_counters = 0x40000340 ); +PROVIDE( spi_flash_get_counters = 0x40000344 ); +PROVIDE( spi_flash_op_counters_config = 0x40000348 ); +PROVIDE( spi_flash_reset_counters = 0x4000034c ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( esp_flash_default_chip = 0x3fcdffe8 ); +PROVIDE( esp_flash_api_funcs = 0x3fcdffe4 ); + + +/*************************************** + Group cache + ***************************************/ + +/* Functions */ +PROVIDE( Cache_Get_ICache_Line_Size = 0x400004b0 ); +PROVIDE( Cache_Get_Mode = 0x400004b4 ); +PROVIDE( Cache_Address_Through_IBus = 0x400004b8 ); +PROVIDE( Cache_Address_Through_DBus = 0x400004bc ); +PROVIDE( Cache_Set_Default_Mode = 0x400004c0 ); +PROVIDE( Cache_Enable_Defalut_ICache_Mode = 0x400004c4 ); +PROVIDE( ROM_Boot_Cache_Init = 0x400004c8 ); +PROVIDE( Cache_Invalidate_ICache_Items = 0x400004cc ); +PROVIDE( Cache_Op_Addr = 0x400004d0 ); +PROVIDE( Cache_Invalidate_Addr = 0x400004d4 ); +PROVIDE( Cache_Invalidate_ICache_All = 0x400004d8 ); +PROVIDE( Cache_Mask_All = 0x400004dc ); +PROVIDE( Cache_UnMask_Dram0 = 0x400004e0 ); +PROVIDE( Cache_Suspend_ICache_Autoload = 0x400004e4 ); +PROVIDE( Cache_Resume_ICache_Autoload = 0x400004e8 ); +PROVIDE( Cache_Start_ICache_Preload = 0x400004ec ); +PROVIDE( Cache_ICache_Preload_Done = 0x400004f0 ); +PROVIDE( Cache_End_ICache_Preload = 0x400004f4 ); +PROVIDE( Cache_Config_ICache_Autoload = 0x400004f8 ); +PROVIDE( Cache_Enable_ICache_Autoload = 0x400004fc ); +PROVIDE( Cache_Disable_ICache_Autoload = 0x40000500 ); +PROVIDE( Cache_Enable_ICache_PreLock = 0x40000504 ); +PROVIDE( Cache_Disable_ICache_PreLock = 0x40000508 ); +PROVIDE( Cache_Lock_ICache_Items = 0x4000050c ); +PROVIDE( Cache_Unlock_ICache_Items = 0x40000510 ); +PROVIDE( Cache_Lock_Addr = 0x40000514 ); +PROVIDE( Cache_Unlock_Addr = 0x40000518 ); +PROVIDE( Cache_Disable_ICache = 0x4000051c ); +PROVIDE( Cache_Enable_ICache = 0x40000520 ); +PROVIDE( Cache_Suspend_ICache = 0x40000524 ); +PROVIDE( Cache_Resume_ICache = 0x40000528 ); +PROVIDE( Cache_Freeze_ICache_Enable = 0x4000052c ); +PROVIDE( Cache_Freeze_ICache_Disable = 0x40000530 ); +PROVIDE( Cache_Pms_Lock = 0x40000534 ); +PROVIDE( Cache_Ibus_Pms_Set_Addr = 0x40000538 ); +PROVIDE( Cache_Ibus_Pms_Set_Attr = 0x4000053c ); +PROVIDE( Cache_Dbus_Pms_Set_Addr = 0x40000540 ); +PROVIDE( Cache_Dbus_Pms_Set_Attr = 0x40000544 ); +PROVIDE( Cache_Set_IDROM_MMU_Size = 0x40000548 ); +PROVIDE( Cache_Get_IROM_MMU_End = 0x4000054c ); +PROVIDE( Cache_Get_DROM_MMU_End = 0x40000550 ); +PROVIDE( Cache_Owner_Init = 0x40000554 ); +PROVIDE( Cache_Occupy_ICache_MEMORY = 0x40000558 ); +PROVIDE( Cache_MMU_Init = 0x4000055c ); +PROVIDE( Cache_Ibus_MMU_Set = 0x40000560 ); +PROVIDE( Cache_Dbus_MMU_Set = 0x40000564 ); +PROVIDE( Cache_Count_Flash_Pages = 0x40000568 ); +PROVIDE( Cache_Travel_Tag_Memory = 0x4000056c ); +PROVIDE( Cache_Get_Virtual_Addr = 0x40000570 ); +PROVIDE( Cache_Get_Memory_BaseAddr = 0x40000574 ); +PROVIDE( Cache_Get_Memory_Addr = 0x40000578 ); +PROVIDE( Cache_Get_Memory_value = 0x4000057c ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( rom_cache_op_cb = 0x3fcdffd8 ); +PROVIDE( rom_cache_internal_table_ptr = 0x3fcdffd4 ); + + +/*************************************** + Group clock + ***************************************/ + +/* Functions */ +ets_get_apb_freq = 0x40000580; +ets_get_cpu_frequency = 0x40000584; +ets_update_cpu_frequency = 0x40000588; +ets_get_printf_channel = 0x4000058c; +ets_get_xtal_div = 0x40000590; +ets_set_xtal_div = 0x40000594; +ets_get_xtal_freq = 0x40000598; + + +/*************************************** + Group gpio + ***************************************/ + +/* Functions */ +gpio_input_get = 0x4000059c; +gpio_matrix_in = 0x400005a0; +gpio_matrix_out = 0x400005a4; +gpio_output_disable = 0x400005a8; +gpio_output_enable = 0x400005ac; +gpio_output_set = 0x400005b0; +gpio_pad_hold = 0x400005b4; +gpio_pad_input_disable = 0x400005b8; +gpio_pad_input_enable = 0x400005bc; +gpio_pad_pulldown = 0x400005c0; +gpio_pad_pullup = 0x400005c4; +gpio_pad_select_gpio = 0x400005c8; +gpio_pad_set_drv = 0x400005cc; +gpio_pad_unhold = 0x400005d0; +gpio_pin_wakeup_disable = 0x400005d4; +gpio_pin_wakeup_enable = 0x400005d8; +gpio_bypass_matrix_in = 0x400005dc; + + +/*************************************** + Group interrupts + ***************************************/ + +/* Functions */ +esprv_intc_int_set_priority = 0x400005e0; +esprv_intc_int_set_threshold = 0x400005e4; +esprv_intc_int_enable = 0x400005e8; +esprv_intc_int_disable = 0x400005ec; +esprv_intc_int_set_type = 0x400005f0; +intr_matrix_set = 0x400005f4; +ets_intr_lock = 0x400005f8; +ets_intr_unlock = 0x400005fc; +PROVIDE( intr_handler_set = 0x40000600 ); +ets_isr_attach = 0x40000604; +ets_isr_mask = 0x40000608; +ets_isr_unmask = 0x4000060c; + + +/*************************************** + Group crypto + ***************************************/ + +/* Functions */ +md5_vector = 0x40000610; +MD5Init = 0x40000614; +MD5Update = 0x40000618; +MD5Final = 0x4000061c; +hmac_md5_vector = 0x40000620; +hmac_md5 = 0x40000624; +crc32_le = 0x40000628; +crc32_be = 0x4000062c; +crc16_le = 0x40000630; +crc16_be = 0x40000634; +crc8_le = 0x40000638; +crc8_be = 0x4000063c; +esp_crc8 = 0x40000640; +ets_sha_enable = 0x40000644; +ets_sha_disable = 0x40000648; +ets_sha_get_state = 0x4000064c; +ets_sha_init = 0x40000650; +ets_sha_process = 0x40000654; +ets_sha_starts = 0x40000658; +ets_sha_update = 0x4000065c; +ets_sha_finish = 0x40000660; +ets_sha_clone = 0x40000664; +ets_hmac_enable = 0x40000668; +ets_hmac_disable = 0x4000066c; +ets_hmac_calculate_message = 0x40000670; +ets_hmac_calculate_downstream = 0x40000674; +ets_hmac_invalidate_downstream = 0x40000678; +ets_jtag_enable_temporarily = 0x4000067c; +ets_aes_enable = 0x40000680; +ets_aes_disable = 0x40000684; +ets_aes_setkey = 0x40000688; +ets_aes_block = 0x4000068c; +ets_bigint_enable = 0x40000690; +ets_bigint_disable = 0x40000694; +ets_bigint_multiply = 0x40000698; +ets_bigint_modmult = 0x4000069c; +ets_bigint_modexp = 0x400006a0; +ets_bigint_wait_finish = 0x400006a4; +ets_bigint_getz = 0x400006a8; +ets_ds_enable = 0x400006ac; +ets_ds_disable = 0x400006b0; +ets_ds_start_sign = 0x400006b4; +ets_ds_is_busy = 0x400006b8; +ets_ds_finish_sign = 0x400006bc; +ets_ds_encrypt_params = 0x400006c0; +ets_aes_setkey_dec = 0x400006c4; +ets_aes_setkey_enc = 0x400006c8; +ets_mgf1_sha256 = 0x400006cc; + + +/*************************************** + Group efuse + ***************************************/ + +/* Functions */ +ets_efuse_read = 0x400006d0; +ets_efuse_program = 0x400006d4; +ets_efuse_clear_program_registers = 0x400006d8; +ets_efuse_write_key = 0x400006dc; +ets_efuse_get_read_register_address = 0x400006e0; +ets_efuse_get_key_purpose = 0x400006e4; +ets_efuse_key_block_unused = 0x400006e8; +ets_efuse_find_unused_key_block = 0x400006ec; +ets_efuse_rs_calculate = 0x400006f0; +ets_efuse_count_unused_key_blocks = 0x400006f4; +ets_efuse_secure_boot_enabled = 0x400006f8; +ets_efuse_secure_boot_aggressive_revoke_enabled = 0x400006fc; +ets_efuse_cache_encryption_enabled = 0x40000700; +ets_efuse_download_modes_disabled = 0x40000704; +ets_efuse_find_purpose = 0x40000708; +ets_efuse_flash_opi_5pads_power_sel_vddspi = 0x4000070c; +ets_efuse_force_send_resume = 0x40000710; +ets_efuse_get_flash_delay_us = 0x40000714; +ets_efuse_get_mac = 0x40000718; +ets_efuse_get_spiconfig = 0x4000071c; +ets_efuse_usb_print_is_disabled = 0x40000720; +/*ets_efuse_get_uart_print_channel = 0x40000724;*/ +ets_efuse_usb_serial_jtag_print_is_disabled = 0x40000724; +ets_efuse_get_uart_print_control = 0x40000728; +ets_efuse_get_wp_pad = 0x4000072c; +ets_efuse_legacy_spi_boot_mode_disabled = 0x40000730; +ets_efuse_security_download_modes_enabled = 0x40000734; +ets_efuse_set_timing = 0x40000738; +ets_efuse_jtag_disabled = 0x4000073c; +ets_efuse_usb_download_mode_disabled = 0x40000740; +ets_efuse_usb_module_disabled = 0x40000744; +ets_efuse_usb_device_disabled = 0x40000748; + + +/*************************************** + Group secureboot + ***************************************/ + +/* Functions */ +ets_emsa_pss_verify = 0x4000074c; +ets_rsa_pss_verify = 0x40000750; +ets_secure_boot_verify_bootloader_with_keys = 0x40000754; +ets_secure_boot_verify_signature = 0x40000758; +ets_secure_boot_read_key_digests = 0x4000075c; +ets_secure_boot_revoke_public_key_digest = 0x40000760; + + +/*************************************** + Group usb_uart + ***************************************/ + +/* Functions */ +PROVIDE( usb_uart_rx_one_char = 0x400008cc ); +PROVIDE( usb_uart_rx_one_char_block = 0x400008d0 ); +PROVIDE( usb_uart_tx_flush = 0x400008d4 ); +PROVIDE( usb_uart_tx_one_char = 0x400008d8 ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( g_uart_print = 0x3fcdffd1 ); +PROVIDE( g_usb_print = 0x3fcdffd0 ); + + +/*************************************** + Group bluetooth + ***************************************/ + +/* Functions */ +bt_rf_coex_get_dft_cfg = 0x400008dc; +bt_rf_coex_hooks_p_set = 0x400008e0; +btdm_con_maxevtime_cal_impl = 0x400008e4; +btdm_controller_get_compile_version_impl = 0x400008e8; +btdm_controller_rom_data_init = 0x400008ec; +btdm_dis_privacy_err_report_impl = 0x400008f0; +btdm_disable_adv_delay_impl = 0x400008f4; +btdm_enable_scan_continue_impl = 0x400008f8; +btdm_enable_scan_forever_impl = 0x400008fc; +btdm_get_power_state_impl = 0x40000900; +btdm_get_prevent_sleep_flag_impl = 0x40000904; +btdm_power_state_active_impl = 0x40000908; +btdm_switch_phy_coded_impl = 0x4000090c; +hci_acl_data_handler = 0x40000910; +hci_disconnect_cmd_handler = 0x40000914; +hci_le_con_upd_cmd_handler = 0x40000918; +hci_le_ltk_req_neg_reply_cmd_handler = 0x4000091c; +hci_le_ltk_req_reply_cmd_handler = 0x40000920; +hci_le_rd_chnl_map_cmd_handler = 0x40000924; +hci_le_rd_phy_cmd_handler = 0x40000928; +hci_le_rd_rem_feats_cmd_handler = 0x4000092c; +hci_le_rem_con_param_req_neg_reply_cmd_handler = 0x40000930; +hci_le_rem_con_param_req_reply_cmd_handler = 0x40000934; +hci_le_set_data_len_cmd_handler = 0x40000938; +hci_le_set_phy_cmd_handler = 0x4000093c; +hci_le_start_enc_cmd_handler = 0x40000940; +hci_rd_auth_payl_to_cmd_handler = 0x40000944; +hci_rd_rem_ver_info_cmd_handler = 0x40000948; +hci_rd_rssi_cmd_handler = 0x4000094c; +hci_rd_tx_pwr_lvl_cmd_handler = 0x40000950; +hci_vs_set_pref_slave_evt_dur_cmd_handler = 0x40000954; +hci_vs_set_pref_slave_latency_cmd_handler = 0x40000958; +hci_wr_auth_payl_to_cmd_handler = 0x4000095c; +ll_channel_map_ind_handler = 0x40000960; +ll_connection_param_req_handler = 0x40000964; +ll_connection_param_rsp_handler = 0x40000968; +ll_connection_update_ind_handler = 0x4000096c; +ll_enc_req_handler = 0x40000970; +ll_enc_rsp_handler = 0x40000974; +ll_feature_req_handler = 0x40000978; +ll_feature_rsp_handler = 0x4000097c; +ll_length_req_handler = 0x40000980; +ll_length_rsp_handler = 0x40000984; +ll_min_used_channels_ind_handler = 0x40000988; +ll_pause_enc_req_handler = 0x4000098c; +ll_pause_enc_rsp_handler = 0x40000990; +ll_phy_req_handler = 0x40000994; +ll_phy_rsp_handler = 0x40000998; +ll_phy_update_ind_handler = 0x4000099c; +ll_ping_req_handler = 0x400009a0; +ll_ping_rsp_handler = 0x400009a4; +ll_slave_feature_req_handler = 0x400009a8; +ll_start_enc_req_handler = 0x400009ac; +ll_start_enc_rsp_handler = 0x400009b0; +ll_terminate_ind_handler = 0x400009b4; +ll_version_ind_handler = 0x400009b8; +llc_auth_payl_nearly_to_handler = 0x400009bc; +llc_auth_payl_real_to_handler = 0x400009c0; +llc_encrypt_ind_handler = 0x400009c4; +llc_hci_command_handler_wrapper = 0x400009c8; +llc_ll_connection_param_req_pdu_send = 0x400009cc; +llc_ll_connection_param_rsp_pdu_send = 0x400009d0; +llc_ll_connection_update_ind_pdu_send = 0x400009d4; +llc_ll_enc_req_pdu_send = 0x400009d8; +llc_ll_enc_rsp_pdu_send = 0x400009dc; +llc_ll_feature_req_pdu_send = 0x400009e0; +llc_ll_feature_rsp_pdu_send = 0x400009e4; +llc_ll_length_req_pdu_send = 0x400009e8; +llc_ll_length_rsp_pdu_send = 0x400009ec; +llc_ll_pause_enc_req_pdu_send = 0x400009f0; +llc_ll_pause_enc_rsp_pdu_send = 0x400009f4; +llc_ll_phy_req_pdu_send = 0x400009f8; +llc_ll_phy_rsp_pdu_send = 0x400009fc; +llc_ll_ping_req_pdu_send = 0x40000a00; +llc_ll_ping_rsp_pdu_send = 0x40000a04; +llc_ll_start_enc_req_pdu_send = 0x40000a08; +llc_ll_start_enc_rsp_pdu_send = 0x40000a0c; +llc_ll_terminate_ind_pdu_send = 0x40000a10; +llc_ll_unknown_rsp_pdu_send = 0x40000a14; +llc_llcp_ch_map_update_ind_pdu_send = 0x40000a18; +llc_llcp_phy_upd_ind_pdu_send = 0x40000a1c; +llc_llcp_version_ind_pdu_send = 0x40000a20; +llc_op_ch_map_upd_ind_handler = 0x40000a24; +llc_op_con_upd_ind_handler = 0x40000a28; +llc_op_disconnect_ind_handler = 0x40000a2c; +llc_op_dl_upd_ind_handler = 0x40000a30; +llc_op_encrypt_ind_handler = 0x40000a34; +llc_op_feats_exch_ind_handler = 0x40000a38; +llc_op_le_ping_ind_handler = 0x40000a3c; +llc_op_phy_upd_ind_handler = 0x40000a40; +llc_op_ver_exch_ind_handler = 0x40000a44; +llc_stopped_ind_handler = 0x40000a48; +lld_acl_rx_ind_handler = 0x40000a4c; +lld_acl_tx_cfm_handler = 0x40000a50; +lld_adv_end_ind_handler = 0x40000a54; +lld_adv_rep_ind_handler = 0x40000a58; +lld_ch_map_upd_cfm_handler = 0x40000a5c; +lld_con_estab_ind_handler = 0x40000a60; +lld_con_evt_sd_evt_time_set = 0x40000a64; +lld_con_offset_upd_ind_handler = 0x40000a68; +lld_con_param_upd_cfm_handler = 0x40000a6c; +lld_disc_ind_handler = 0x40000a70; +lld_init_end_ind_handler = 0x40000a74; +lld_llcp_rx_ind_handler_wrapper = 0x40000a78; +lld_llcp_tx_cfm_handler = 0x40000a7c; +lld_per_adv_end_ind_handler = 0x40000a80; +lld_per_adv_rep_ind_handler = 0x40000a84; +lld_per_adv_rx_end_ind_handler = 0x40000a88; +lld_phy_coded_500k_get = 0x40000a8c; +lld_phy_upd_cfm_handler = 0x40000a90; +lld_scan_end_ind_handler = 0x40000a94; +lld_scan_req_ind_handler = 0x40000a98; +lld_sync_start_req_handler = 0x40000a9c; +lld_test_end_ind_handler = 0x40000aa0; +lld_update_rxbuf_handler = 0x40000aa4; +llm_ch_map_update_ind_handler = 0x40000aa8; +llm_hci_command_handler_wrapper = 0x40000aac; +llm_scan_period_to_handler = 0x40000ab0; +r_Add2SelfBigHex256 = 0x40000ab4; +r_AddBigHex256 = 0x40000ab8; +r_AddBigHexModP256 = 0x40000abc; +r_AddP256 = 0x40000ac0; +r_AddPdiv2_256 = 0x40000ac4; +r_GF_Jacobian_Point_Addition256 = 0x40000ac8; +r_GF_Jacobian_Point_Double256 = 0x40000acc; +r_GF_Point_Jacobian_To_Affine256 = 0x40000ad0; +r_MultiplyBigHexByUint32_256 = 0x40000ad4; +r_MultiplyBigHexModP256 = 0x40000ad8; +r_MultiplyByU16ModP256 = 0x40000adc; +r_SubtractBigHex256 = 0x40000ae0; +r_SubtractBigHexMod256 = 0x40000ae4; +r_SubtractBigHexUint32_256 = 0x40000ae8; +r_SubtractFromSelfBigHex256 = 0x40000aec; +r_SubtractFromSelfBigHexSign256 = 0x40000af0; +r_aes_alloc = 0x40000af4; +r_aes_ccm_continue = 0x40000af8; +r_aes_ccm_process_e = 0x40000afc; +r_aes_ccm_xor_128_lsb = 0x40000b00; +r_aes_ccm_xor_128_msb = 0x40000b04; +r_aes_cmac_continue = 0x40000b08; +r_aes_cmac_start = 0x40000b0c; +r_aes_k1_continue = 0x40000b10; +r_aes_k2_continue = 0x40000b14; +r_aes_k3_continue = 0x40000b18; +r_aes_k4_continue = 0x40000b1c; +r_aes_shift_left_128 = 0x40000b20; +r_aes_start = 0x40000b24; +r_aes_xor_128 = 0x40000b28; +r_assert_err = 0x40000b2c; +r_assert_param = 0x40000b30; +r_assert_warn = 0x40000b34; +r_bigHexInversion256 = 0x40000b38; +r_ble_sw_cca_check_isr = 0x40000b3c; +r_ble_util_buf_acl_tx_alloc = 0x40000b40; +r_ble_util_buf_acl_tx_elt_get = 0x40000b44; +r_ble_util_buf_acl_tx_free = 0x40000b48; +r_ble_util_buf_acl_tx_free_in_isr = 0x40000b4c; +r_ble_util_buf_adv_tx_alloc = 0x40000b50; +r_ble_util_buf_adv_tx_free = 0x40000b54; +r_ble_util_buf_adv_tx_free_in_isr = 0x40000b58; +r_ble_util_buf_env_deinit = 0x40000b5c; +r_ble_util_buf_env_init = 0x40000b60; +r_ble_util_buf_get_rx_buf_nb = 0x40000b64; +r_ble_util_buf_get_rx_buf_size = 0x40000b68; +r_ble_util_buf_llcp_tx_alloc = 0x40000b6c; +r_ble_util_buf_llcp_tx_free = 0x40000b70; +r_ble_util_buf_rx_alloc = 0x40000b74; +r_ble_util_buf_rx_alloc_in_isr = 0x40000b78; +r_ble_util_buf_rx_free = 0x40000b7c; +r_ble_util_buf_rx_free_in_isr = 0x40000b80; +r_ble_util_buf_set_rx_buf_nb = 0x40000b84; +r_ble_util_buf_set_rx_buf_size = 0x40000b88; +r_ble_util_data_rx_buf_reset = 0x40000b8c; +r_bt_bb_get_intr_mask = 0x40000b90; +r_bt_bb_intr_clear = 0x40000b94; +r_bt_bb_intr_mask_set = 0x40000b98; +r_bt_rf_coex_cfg_set = 0x40000ba0; +r_bt_rf_coex_conn_dynamic_pti_en_get = 0x40000ba4; +r_bt_rf_coex_ext_adv_dynamic_pti_en_get = 0x40000bac; +r_bt_rf_coex_ext_scan_dynamic_pti_en_get = 0x40000bb0; +r_bt_rf_coex_legacy_adv_dynamic_pti_en_get = 0x40000bb4; +r_bt_rf_coex_per_adv_dynamic_pti_en_get = 0x40000bb8; +r_bt_rf_coex_pti_table_get = 0x40000bbc; +r_bt_rf_coex_st_param_get = 0x40000bc0; +r_bt_rf_coex_st_param_set = 0x40000bc4; +r_bt_rf_coex_sync_scan_dynamic_pti_en_get = 0x40000bc8; +r_bt_rma_apply_rule_cs_fmt = 0x40000bcc; +r_bt_rma_apply_rule_cs_idx = 0x40000bd0; +r_bt_rma_configure = 0x40000bd4; +r_bt_rma_deregister_rule_cs_fmt = 0x40000bd8; +r_bt_rma_deregister_rule_cs_idx = 0x40000bdc; +r_bt_rma_get_ant_by_act = 0x40000be0; +r_bt_rma_init = 0x40000be4; +r_bt_rma_register_rule_cs_fmt = 0x40000be8; +r_bt_rma_register_rule_cs_idx = 0x40000bec; +r_bt_rtp_apply_rule_cs_fmt = 0x40000bf0; +r_bt_rtp_apply_rule_cs_idx = 0x40000bf4; +r_bt_rtp_deregister_rule_cs_fmt = 0x40000bf8; +r_bt_rtp_deregister_rule_cs_idx = 0x40000bfc; +r_bt_rtp_init = 0x40000c04; +r_bt_rtp_register_rule_cs_fmt = 0x40000c08; +r_bt_rtp_register_rule_cs_idx = 0x40000c0c; +r_btdm_isr = 0x40000c10; +r_cali_phase_match_p = 0x40000c20; +r_cmp_abs_time = 0x40000c24; +r_cmp_dest_id = 0x40000c28; +r_cmp_timer_id = 0x40000c2c; +r_co_bdaddr_compare = 0x40000c30; +r_co_ble_pkt_dur_in_us = 0x40000c34; +r_co_list_extract = 0x40000c38; +r_co_list_extract_after = 0x40000c3c; +r_co_list_extract_sublist = 0x40000c40; +r_co_list_find = 0x40000c44; +r_co_list_init = 0x40000c48; +r_co_list_insert_after = 0x40000c4c; +r_co_list_insert_before = 0x40000c50; +r_co_list_merge = 0x40000c54; +r_co_list_pool_init = 0x40000c58; +r_co_list_pop_front = 0x40000c5c; +r_co_list_push_back = 0x40000c60; +r_co_list_push_back_sublist = 0x40000c64; +r_co_list_push_front = 0x40000c68; +r_co_list_size = 0x40000c6c; +r_co_nb_good_le_channels = 0x40000c70; +r_co_util_pack = 0x40000c74; +r_co_util_read_array_size = 0x40000c78; +r_co_util_unpack = 0x40000c7c; +r_dbg_env_deinit = 0x40000c80; +r_dbg_env_init = 0x40000c84; +r_dbg_platform_reset_complete = 0x40000c88; +r_dl_upd_proc_start = 0x40000c8c; +r_dump_data = 0x40000c90; +r_ecc_abort_key256_generation = 0x40000c94; +r_ecc_gen_new_public_key = 0x40000c98; +r_ecc_gen_new_secret_key = 0x40000c9c; +r_ecc_generate_key256 = 0x40000ca0; +r_ecc_get_debug_Keys = 0x40000ca4; +r_ecc_init = 0x40000ca8; +r_ecc_is_valid_point = 0x40000cac; +r_ecc_multiplication_event_handler = 0x40000cb0; +r_ecc_point_multiplication_win_256 = 0x40000cb4; +r_emi_alloc_em_mapping_by_offset = 0x40000cb8; +r_emi_base_reg_lut_show = 0x40000cbc; +r_emi_em_base_reg_show = 0x40000cc0; +r_emi_free_em_mapping_by_offset = 0x40000cc4; +r_emi_get_em_mapping_idx_by_offset = 0x40000cc8; +r_emi_get_mem_addr_by_offset = 0x40000ccc; +r_emi_overwrite_em_mapping_by_offset = 0x40000cd0; +r_esp_vendor_hci_command_handler = 0x40000cd4; +r_get_stack_usage = 0x40000cd8; +r_h4tl_acl_hdr_rx_evt_handler = 0x40000cdc; +r_h4tl_cmd_hdr_rx_evt_handler = 0x40000ce0; +r_h4tl_cmd_pld_rx_evt_handler = 0x40000ce4; +r_h4tl_eif_io_event_post = 0x40000ce8; +r_h4tl_eif_register = 0x40000cec; +r_h4tl_init = 0x40000cf0; +r_h4tl_out_of_sync = 0x40000cf4; +r_h4tl_out_of_sync_check = 0x40000cf8; +r_h4tl_read_hdr = 0x40000cfc; +r_h4tl_read_next_out_of_sync = 0x40000d00; +r_h4tl_read_payl = 0x40000d04; +r_h4tl_read_start = 0x40000d08; +r_h4tl_rx_acl_hdr_extract = 0x40000d0c; +r_h4tl_rx_cmd_hdr_extract = 0x40000d10; +r_h4tl_rx_done = 0x40000d14; +r_h4tl_start = 0x40000d18; +r_h4tl_stop = 0x40000d1c; +r_h4tl_tx_done = 0x40000d20; +r_h4tl_tx_evt_handler = 0x40000d24; +r_h4tl_write = 0x40000d28; +r_hci_acl_tx_data_alloc = 0x40000d2c; +r_hci_acl_tx_data_received = 0x40000d30; +r_hci_basic_cmd_send_2_controller = 0x40000d34; +r_hci_ble_adv_report_filter_check = 0x40000d38; +r_hci_ble_adv_report_tx_check = 0x40000d3c; +r_hci_ble_conhdl_register = 0x40000d40; +r_hci_ble_conhdl_unregister = 0x40000d44; +r_hci_build_acl_data = 0x40000d48; +r_hci_build_cc_evt = 0x40000d4c; +r_hci_build_cs_evt = 0x40000d50; +r_hci_build_evt = 0x40000d54; +r_hci_build_le_evt = 0x40000d58; +r_hci_cmd_get_max_param_size = 0x40000d5c; +r_hci_cmd_received = 0x40000d60; +r_hci_cmd_reject = 0x40000d64; +r_hci_evt_mask_check = 0x40000d68; +r_hci_evt_mask_set = 0x40000d6c; +r_hci_fc_acl_buf_size_set = 0x40000d70; +r_hci_fc_acl_en = 0x40000d74; +r_hci_fc_acl_packet_sent = 0x40000d78; +r_hci_fc_check_host_available_nb_acl_packets = 0x40000d7c; +r_hci_fc_host_nb_acl_pkts_complete = 0x40000d80; +r_hci_fc_init = 0x40000d84; +r_hci_look_for_cmd_desc = 0x40000d88; +r_hci_look_for_evt_desc = 0x40000d8c; +r_hci_look_for_le_evt_desc = 0x40000d90; +r_hci_look_for_le_evt_desc_esp = 0x40000d94; +r_hci_pack_bytes = 0x40000d98; +r_hci_send_2_controller = 0x40000da0; +r_hci_send_2_host = 0x40000da4; +r_hci_tl_c2h_data_flow_on = 0x40000da8; +r_hci_tl_cmd_hdr_rx_evt_handler = 0x40000dac; +r_hci_tl_cmd_pld_rx_evt_handler = 0x40000db0; +r_hci_tl_get_pkt = 0x40000db4; +r_hci_tl_hci_pkt_handler = 0x40000db8; +r_hci_tl_hci_tx_done_evt_handler = 0x40000dbc; +r_hci_tl_inc_nb_h2c_cmd_pkts = 0x40000dc0; +r_hci_tl_save_pkt = 0x40000dc4; +r_hci_tl_send = 0x40000dc8; +r_hci_tx_done = 0x40000dcc; +r_hci_tx_start = 0x40000dd0; +r_hci_tx_trigger = 0x40000dd4; +r_isValidSecretKey_256 = 0x40000dd8; +r_ke_check_malloc = 0x40000ddc; +r_ke_event_callback_set = 0x40000de0; +r_ke_event_clear = 0x40000de4; +r_ke_event_flush = 0x40000de8; +r_ke_event_get = 0x40000dec; +r_ke_event_get_all = 0x40000df0; +r_ke_event_init = 0x40000df4; +r_ke_event_schedule = 0x40000df8; +r_ke_event_set = 0x40000dfc; +r_ke_flush = 0x40000e00; +r_ke_free = 0x40000e04; +r_ke_handler_search = 0x40000e08; +r_ke_init = 0x40000e0c; +r_ke_is_free = 0x40000e10; +r_ke_malloc = 0x40000e14; +r_ke_mem_init = 0x40000e18; +r_ke_mem_is_empty = 0x40000e1c; +r_ke_mem_is_in_heap = 0x40000e20; +r_ke_msg_alloc = 0x40000e24; +r_ke_msg_dest_id_get = 0x40000e28; +r_ke_msg_discard = 0x40000e2c; +r_ke_msg_forward = 0x40000e30; +r_ke_msg_forward_new_id = 0x40000e34; +r_ke_msg_free = 0x40000e38; +r_ke_msg_in_queue = 0x40000e3c; +r_ke_msg_save = 0x40000e40; +r_ke_msg_send = 0x40000e44; +r_ke_msg_send_basic = 0x40000e48; +r_ke_msg_src_id_get = 0x40000e4c; +r_ke_queue_extract = 0x40000e50; +r_ke_queue_insert = 0x40000e54; +r_ke_sleep_check = 0x40000e58; +r_ke_state_get = 0x40000e5c; +r_ke_state_set = 0x40000e60; +r_ke_task_check = 0x40000e64; +r_ke_task_create = 0x40000e68; +r_ke_task_delete = 0x40000e6c; +r_ke_task_handler_get = 0x40000e70; +r_ke_task_init = 0x40000e74; +r_ke_task_msg_flush = 0x40000e78; +r_ke_task_saved_update = 0x40000e7c; +r_ke_time = 0x40000e84; +r_ke_time_cmp = 0x40000e88; +r_ke_time_past = 0x40000e8c; +r_ke_timer_active = 0x40000e90; +r_ke_timer_adjust_all = 0x40000e94; +r_ke_timer_clear = 0x40000e98; +r_ke_timer_init = 0x40000e9c; +r_ke_timer_schedule = 0x40000ea0; +r_ke_timer_set = 0x40000ea4; +r_led_init = 0x40000ea8; +r_led_set_all = 0x40000eac; +r_llc_aes_res_cb = 0x40000eb0; +r_llc_ch_map_up_proc_err_cb = 0x40000eb4; +r_llc_cleanup = 0x40000eb8; +r_llc_cmd_cmp_send = 0x40000ebc; +r_llc_cmd_stat_send = 0x40000ec0; +r_llc_con_move_cbk = 0x40000ec4; +r_llc_con_plan_set_update = 0x40000ec8; +r_llc_con_upd_param_in_range = 0x40000ecc; +r_llc_disconnect = 0x40000ed0; +r_llc_disconnect_end = 0x40000ed4; +r_llc_disconnect_proc_continue = 0x40000ed8; +r_llc_disconnect_proc_err_cb = 0x40000edc; +r_llc_dl_chg_check = 0x40000ee0; +r_llc_dle_proc_err_cb = 0x40000ee4; +r_llc_feats_exch_proc_err_cb = 0x40000ee8; +r_llc_hci_cmd_handler_tab_p_get = 0x40000eec; +r_llc_hci_con_param_req_evt_send = 0x40000ef4; +r_llc_hci_con_upd_info_send = 0x40000ef8; +r_llc_hci_disconnected_dis = 0x40000efc; +r_llc_hci_dl_upd_info_send = 0x40000f00; +r_llc_hci_enc_evt_send = 0x40000f04; +r_llc_hci_feats_info_send = 0x40000f08; +r_llc_hci_le_phy_upd_cmp_evt_send = 0x40000f0c; +r_llc_hci_ltk_request_evt_send = 0x40000f10; +r_llc_hci_nb_cmp_pkts_evt_send = 0x40000f14; +r_llc_hci_version_info_send = 0x40000f18; +r_llc_init_term_proc = 0x40000f1c; +r_llc_iv_skd_rand_gen = 0x40000f20; +r_llc_le_ping_proc_continue = 0x40000f24; +r_llc_le_ping_proc_err_cb = 0x40000f28; +r_llc_le_ping_restart = 0x40000f2c; +r_llc_le_ping_set = 0x40000f30; +r_llc_ll_pause_enc_rsp_ack_handler = 0x40000f34; +r_llc_ll_reject_ind_ack_handler = 0x40000f38; +r_llc_ll_reject_ind_pdu_send = 0x40000f3c; +r_llc_ll_start_enc_rsp_ack_handler = 0x40000f40; +r_llc_ll_terminate_ind_ack = 0x40000f44; +r_llc_ll_unknown_ind_handler = 0x40000f48; +r_llc_llcp_send = 0x40000f4c; +r_llc_llcp_state_set = 0x40000f50; +r_llc_llcp_trans_timer_set = 0x40000f54; +r_llc_llcp_tx_check = 0x40000f58; +r_llc_loc_ch_map_proc_continue = 0x40000f5c; +r_llc_loc_con_upd_proc_err_cb = 0x40000f64; +r_llc_loc_dl_upd_proc_continue = 0x40000f68; +r_llc_loc_encrypt_proc_continue = 0x40000f6c; +r_llc_loc_encrypt_proc_err_cb = 0x40000f70; +r_llc_loc_feats_exch_proc_continue = 0x40000f74; +r_llc_loc_phy_upd_proc_err_cb = 0x40000f7c; +r_llc_msg_handler_tab_p_get = 0x40000f80; +r_llc_pref_param_compute = 0x40000f84; +r_llc_proc_collision_check = 0x40000f88; +r_llc_proc_err_ind = 0x40000f8c; +r_llc_proc_get = 0x40000f90; +r_llc_proc_id_get = 0x40000f94; +r_llc_proc_reg = 0x40000f98; +r_llc_proc_state_get = 0x40000f9c; +r_llc_proc_state_set = 0x40000fa0; +r_llc_proc_timer_pause_set = 0x40000fa4; +r_llc_proc_timer_set = 0x40000fa8; +r_llc_proc_unreg = 0x40000fac; +r_llc_rem_ch_map_proc_continue = 0x40000fb0; +r_llc_rem_con_upd_proc_err_cb = 0x40000fb8; +r_llc_rem_dl_upd_proc = 0x40000fbc; +r_llc_rem_encrypt_proc_continue = 0x40000fc0; +r_llc_rem_encrypt_proc_err_cb = 0x40000fc4; +r_llc_rem_phy_upd_proc_continue = 0x40000fc8; +r_llc_rem_phy_upd_proc_err_cb = 0x40000fcc; +r_llc_role_get = 0x40000fd0; +r_llc_sk_gen = 0x40000fd4; +r_llc_start = 0x40000fd8; +r_llc_stop = 0x40000fdc; +r_llc_ver_exch_loc_proc_continue = 0x40000fe0; +r_llc_ver_proc_err_cb = 0x40000fe4; +r_llcp_pdu_handler_tab_p_get = 0x40000fe8; +r_lld_aa_gen = 0x40000fec; +r_lld_adv_adv_data_set = 0x40000ff0; +r_lld_adv_adv_data_update = 0x40000ff4; +r_lld_adv_aux_ch_idx_set = 0x40000ff8; +r_lld_adv_aux_evt_canceled_cbk = 0x40000ffc; +r_lld_adv_aux_evt_start_cbk = 0x40001000; +r_lld_adv_coex_check_ext_adv_synced = 0x40001004; +r_lld_adv_coex_env_reset = 0x40001008; +r_lld_adv_duration_update = 0x4000100c; +r_lld_adv_dynamic_pti_process = 0x40001010; +r_lld_adv_end = 0x40001014; +r_lld_adv_evt_canceled_cbk = 0x40001018; +r_lld_adv_evt_start_cbk = 0x4000101c; +r_lld_adv_ext_chain_construct = 0x40001020; +r_lld_adv_ext_pkt_prepare = 0x40001024; +r_lld_adv_frm_cbk = 0x40001028; +r_lld_adv_frm_isr = 0x4000102c; +r_lld_adv_frm_skip_isr = 0x40001030; +r_lld_adv_init = 0x40001034; +r_lld_adv_pkt_rx = 0x40001038; +r_lld_adv_pkt_rx_connect_ind = 0x4000103c; +r_lld_adv_pkt_rx_send_scan_req_evt = 0x40001040; +r_lld_adv_rand_addr_update = 0x40001044; +r_lld_adv_restart = 0x40001048; +r_lld_adv_scan_rsp_data_set = 0x4000104c; +r_lld_adv_scan_rsp_data_update = 0x40001050; +r_lld_adv_set_tx_power = 0x40001054; +r_lld_adv_start = 0x40001058; +r_lld_adv_stop = 0x4000105c; +r_lld_adv_sync_info_set = 0x40001060; +r_lld_adv_sync_info_update = 0x40001064; +r_lld_calc_aux_rx = 0x40001068; +r_lld_cca_alloc = 0x4000106c; +r_lld_cca_data_reset = 0x40001070; +r_lld_cca_free = 0x40001074; +r_lld_ch_assess_data_get = 0x40001078; +r_lld_ch_idx_get = 0x4000107c; +r_lld_ch_map_set = 0x40001080; +r_lld_channel_assess = 0x40001084; +r_lld_con_activity_act_offset_compute = 0x40001088; +r_lld_con_activity_offset_compute = 0x4000108c; +r_lld_con_ch_map_update = 0x40001090; +r_lld_con_cleanup = 0x40001094; +r_lld_con_current_tx_power_get = 0x40001098; +r_lld_con_data_flow_set = 0x4000109c; +r_lld_con_data_len_update = 0x400010a0; +r_lld_con_data_tx = 0x400010a4; +r_lld_con_enc_key_load = 0x400010a8; +r_lld_con_event_counter_get = 0x400010ac; +r_lld_con_evt_canceled_cbk = 0x400010b0; +r_lld_con_evt_duration_min_get = 0x400010b4; +r_lld_con_evt_max_eff_time_cal = 0x400010b8; +r_lld_con_evt_sd_evt_time_get = 0x400010bc; +r_lld_con_evt_start_cbk = 0x400010c0; +r_lld_con_evt_time_update = 0x400010c4; +r_lld_con_free_all_tx_buf = 0x400010c8; +r_lld_con_frm_cbk = 0x400010cc; +r_lld_con_frm_isr = 0x400010d0; +r_lld_con_frm_skip_isr = 0x400010d4; +r_lld_con_init = 0x400010d8; +r_lld_con_llcp_tx = 0x400010dc; +r_lld_con_max_lat_calc = 0x400010e0; +r_lld_con_offset_get = 0x400010e4; +r_lld_con_param_update = 0x400010e8; +r_lld_con_phys_update = 0x400010ec; +r_lld_con_pref_slave_evt_dur_set = 0x400010f0; +r_lld_con_pref_slave_latency_set = 0x400010f4; +r_lld_con_rssi_get = 0x400010f8; +r_lld_con_rx = 0x400010fc; +r_lld_con_rx_channel_assess = 0x40001100; +r_lld_con_rx_enc = 0x40001104; +r_lld_con_rx_isr = 0x40001108; +r_lld_con_rx_link_info_check = 0x4000110c; +r_lld_con_rx_llcp_check = 0x40001110; +r_lld_con_rx_sync_time_update = 0x40001114; +r_lld_con_set_tx_power = 0x4000111c; +r_lld_con_start = 0x40001120; +r_lld_con_tx = 0x40001128; +r_lld_con_tx_enc = 0x4000112c; +r_lld_con_tx_isr = 0x40001130; +r_lld_con_tx_len_update = 0x40001134; +r_lld_con_tx_len_update_for_intv = 0x40001138; +r_lld_con_tx_len_update_for_rate = 0x4000113c; +r_lld_con_tx_prog = 0x40001140; +r_lld_conn_dynamic_pti_process = 0x40001144; +r_lld_continue_scan_rx_isr_end_process = 0x40001148; +r_lld_ext_scan_dynamic_pti_process = 0x4000114c; +r_lld_hw_cca_end_isr = 0x40001150; +r_lld_hw_cca_evt_handler = 0x40001154; +r_lld_hw_cca_isr = 0x40001158; +r_lld_init_cal_anchor_point = 0x4000115c; +r_lld_init_compute_winoffset = 0x40001160; +r_lld_init_connect_req_pack = 0x40001164; +r_lld_init_end = 0x40001168; +r_lld_init_evt_canceled_cbk = 0x4000116c; +r_lld_init_evt_start_cbk = 0x40001170; +r_lld_init_frm_cbk = 0x40001174; +r_lld_init_frm_eof_isr = 0x40001178; +r_lld_init_frm_skip_isr = 0x4000117c; +r_lld_init_init = 0x40001180; +r_lld_init_process_pkt_rx = 0x40001184; +r_lld_init_process_pkt_rx_adv_ext_ind = 0x40001188; +r_lld_init_process_pkt_rx_adv_ind_or_direct_ind = 0x4000118c; +r_lld_init_process_pkt_rx_aux_connect_rsp = 0x40001190; +r_lld_init_process_pkt_tx = 0x40001194; +r_lld_init_process_pkt_tx_cal_con_timestamp = 0x40001198; +r_lld_init_sched = 0x4000119c; +r_lld_init_set_tx_power = 0x400011a0; +r_lld_init_start = 0x400011a4; +r_lld_init_stop = 0x400011a8; +r_lld_instant_proc_end = 0x400011ac; +r_lld_per_adv_ch_map_update = 0x400011b4; +r_lld_per_adv_chain_construct = 0x400011b8; +r_lld_per_adv_cleanup = 0x400011bc; +r_lld_per_adv_coex_env_reset = 0x400011c0; +r_lld_per_adv_data_set = 0x400011c4; +r_lld_per_adv_data_update = 0x400011c8; +r_lld_per_adv_dynamic_pti_process = 0x400011cc; +r_lld_per_adv_evt_canceled_cbk = 0x400011d0; +r_lld_per_adv_evt_start_cbk = 0x400011d4; +r_lld_per_adv_ext_pkt_prepare = 0x400011d8; +r_lld_per_adv_frm_cbk = 0x400011dc; +r_lld_per_adv_frm_isr = 0x400011e0; +r_lld_per_adv_frm_skip_isr = 0x400011e4; +r_lld_per_adv_init = 0x400011e8; +r_lld_per_adv_init_info_get = 0x400011ec; +r_lld_per_adv_list_add = 0x400011f0; +r_lld_per_adv_list_rem = 0x400011f4; +r_lld_per_adv_set_tx_power = 0x400011fc; +r_lld_per_adv_start = 0x40001200; +r_lld_per_adv_stop = 0x40001204; +r_lld_per_adv_sync_info_get = 0x40001208; +r_lld_process_cca_data = 0x4000120c; +r_lld_ral_search = 0x40001210; +r_lld_read_clock = 0x40001214; +r_lld_res_list_add = 0x40001218; +r_lld_res_list_is_empty = 0x40001220; +r_lld_res_list_local_rpa_get = 0x40001224; +r_lld_res_list_peer_rpa_get = 0x40001228; +r_lld_res_list_peer_update = 0x4000122c; +r_lld_res_list_priv_mode_update = 0x40001230; +r_lld_reset_reg = 0x40001238; +r_lld_rpa_renew = 0x4000123c; +r_lld_rpa_renew_evt_canceled_cbk = 0x40001240; +r_lld_rpa_renew_evt_start_cbk = 0x40001244; +r_lld_rpa_renew_instant_cbk = 0x40001248; +r_lld_rxdesc_check = 0x4000124c; +r_lld_rxdesc_free = 0x40001250; +r_lld_scan_create_sync = 0x40001254; +r_lld_scan_create_sync_cancel = 0x40001258; +r_lld_scan_end = 0x4000125c; +r_lld_scan_evt_canceled_cbk = 0x40001260; +r_lld_scan_evt_start_cbk = 0x40001264; +r_lld_scan_frm_cbk = 0x40001268; +r_lld_scan_frm_eof_isr = 0x4000126c; +r_lld_scan_frm_rx_isr = 0x40001270; +r_lld_scan_frm_skip_isr = 0x40001274; +r_lld_scan_init = 0x40001278; +r_lld_scan_params_update = 0x4000127c; +r_lld_scan_process_pkt_rx_aux_adv_ind = 0x40001288; +r_lld_scan_process_pkt_rx_aux_chain_ind = 0x4000128c; +r_lld_scan_process_pkt_rx_aux_scan_rsp = 0x40001290; +r_lld_scan_process_pkt_rx_ext_adv = 0x40001294; +r_lld_scan_process_pkt_rx_ext_adv_ind = 0x40001298; +r_lld_scan_process_pkt_rx_legacy_adv = 0x4000129c; +r_lld_scan_restart = 0x400012a0; +r_lld_scan_sched = 0x400012a4; +r_lld_scan_set_tx_power = 0x400012a8; +r_lld_scan_start = 0x400012ac; +r_lld_scan_stop = 0x400012b0; +r_lld_scan_sync_accept = 0x400012b4; +r_lld_scan_sync_info_unpack = 0x400012b8; +r_lld_scan_trunc_ind = 0x400012bc; +r_lld_sw_cca_evt_handler = 0x400012c0; +r_lld_sw_cca_isr = 0x400012c4; +r_lld_sync_ch_map_update = 0x400012c8; +r_lld_sync_cleanup = 0x400012cc; +r_lld_sync_evt_canceled_cbk = 0x400012d0; +r_lld_sync_evt_start_cbk = 0x400012d4; +r_lld_sync_frm_cbk = 0x400012d8; +r_lld_sync_frm_eof_isr = 0x400012dc; +r_lld_sync_frm_rx_isr = 0x400012e0; +r_lld_sync_frm_skip_isr = 0x400012e4; +r_lld_sync_init = 0x400012e8; +r_lld_sync_process_pkt_rx = 0x400012ec; +r_lld_sync_process_pkt_rx_aux_sync_ind = 0x400012f0; +r_lld_sync_process_pkt_rx_pkt_check = 0x400012f4; +r_lld_sync_scan_dynamic_pti_process = 0x400012f8; +r_lld_sync_sched = 0x400012fc; +r_lld_sync_start = 0x40001300; +r_lld_sync_stop = 0x40001304; +r_lld_sync_trunc_ind = 0x40001308; +r_lld_test_cleanup = 0x4000130c; +r_lld_test_evt_canceled_cbk = 0x40001310; +r_lld_test_evt_start_cbk = 0x40001314; +r_lld_test_freq2chnl = 0x40001318; +r_lld_test_frm_cbk = 0x4000131c; +r_lld_test_frm_isr = 0x40001320; +r_lld_test_init = 0x40001324; +r_lld_test_rx_isr = 0x40001328; +r_lld_test_set_tx_power = 0x4000132c; +r_lld_test_start = 0x40001330; +r_lld_test_stop = 0x40001334; +r_lld_update_rxbuf = 0x40001338; +r_lld_update_rxbuf_isr = 0x4000133c; +r_lld_white_list_add = 0x40001340; +r_lld_white_list_rem = 0x40001344; +r_llm_activity_free_get = 0x40001348; +r_llm_activity_free_set = 0x4000134c; +r_llm_activity_syncing_get = 0x40001350; +r_llm_adv_con_len_check = 0x40001354; +r_llm_adv_hdl_to_id = 0x40001358; +r_llm_adv_rep_flow_control_check = 0x4000135c; +r_llm_adv_rep_flow_control_update = 0x40001360; +r_llm_adv_reports_list_check = 0x40001364; +r_llm_adv_set_all_release = 0x40001368; +r_llm_adv_set_dft_params = 0x4000136c; +r_llm_adv_set_release = 0x40001370; +r_llm_aes_res_cb = 0x40001374; +r_llm_ble_update_adv_flow_control = 0x40001378; +r_llm_ch_map_update = 0x4000137c; +r_llm_cmd_cmp_send = 0x40001380; +r_llm_cmd_stat_send = 0x40001384; +r_llm_dev_list_empty_entry = 0x40001388; +r_llm_dev_list_search = 0x4000138c; +r_llm_env_adv_dup_filt_deinit = 0x40001390; +r_llm_env_adv_dup_filt_init = 0x40001394; +r_llm_init_ble_adv_report_flow_contol = 0x40001398; +r_llm_is_dev_connected = 0x4000139c; +r_llm_is_dev_synced = 0x400013a0; +r_llm_is_non_con_act_ongoing_check = 0x400013a4; +r_llm_is_wl_accessible = 0x400013a8; +r_llm_le_evt_mask_check = 0x400013ac; +r_llm_link_disc = 0x400013b4; +r_llm_master_ch_map_get = 0x400013b8; +r_llm_msg_handler_tab_p_get = 0x400013bc; +r_llm_no_activity = 0x400013c0; +r_llm_per_adv_slot_dur = 0x400013c4; +r_llm_plan_elt_get = 0x400013c8; +r_llm_rx_path_comp_get = 0x400013cc; +r_llm_scan_start = 0x400013d0; +r_llm_scan_sync_acad_attach = 0x400013d4; +r_llm_scan_sync_acad_detach = 0x400013d8; +r_llm_send_adv_lost_event_to_host = 0x400013dc; +r_llm_tx_path_comp_get = 0x400013e0; +r_misc_deinit = 0x400013e4; +r_misc_free_em_buf_in_isr = 0x400013e8; +r_misc_init = 0x400013ec; +r_misc_msg_handler_tab_p_get = 0x400013f0; +r_notEqual256 = 0x400013f4; +r_phy_upd_proc_start = 0x400013f8; +r_platform_reset = 0x400013fc; +r_rf_em_init = 0x40001404; +r_rf_force_agc_enable = 0x40001408; +r_rf_reg_rd = 0x4000140c; +r_rf_reg_wr = 0x40001410; +r_rf_reset = 0x40001414; +r_rf_rssi_convert = 0x40001418; +r_rf_rw_v9_le_disable = 0x4000141c; +r_rf_rw_v9_le_enable = 0x40001420; +r_rf_sleep = 0x40001424; +r_rf_util_cs_fmt_convert = 0x40001430; +r_rw_crypto_aes_ccm = 0x40001434; +r_rw_crypto_aes_encrypt = 0x40001438; +r_rw_crypto_aes_init = 0x4000143c; +r_rw_crypto_aes_k1 = 0x40001440; +r_rw_crypto_aes_k2 = 0x40001444; +r_rw_crypto_aes_k3 = 0x40001448; +r_rw_crypto_aes_k4 = 0x4000144c; +r_rw_crypto_aes_rand = 0x40001450; +r_rw_crypto_aes_result_handler = 0x40001454; +r_rw_crypto_aes_s1 = 0x40001458; +r_rw_cryto_aes_cmac = 0x4000145c; +r_rw_v9_init_em_radio_table = 0x40001460; +r_rwble_sleep_enter = 0x40001468; +r_rwble_sleep_wakeup_end = 0x4000146c; +r_rwbtdm_isr_wrapper = 0x40001470; +r_rwip_active_check = 0x40001474; +r_rwip_aes_encrypt = 0x40001478; +r_rwip_assert = 0x4000147c; +r_rwip_crypt_evt_handler = 0x40001480; +r_rwip_crypt_isr_handler = 0x40001484; +r_rwip_eif_get = 0x40001488; +r_rwip_half_slot_2_lpcycles = 0x4000148c; +r_rwip_hus_2_lpcycles = 0x40001490; +r_rwip_isr = 0x40001494; +r_rwip_lpcycles_2_hus = 0x40001498; +r_rwip_prevent_sleep_clear = 0x4000149c; +r_rwip_prevent_sleep_set = 0x400014a0; +r_rwip_schedule = 0x400014a4; +r_rwip_sleep = 0x400014a8; +r_rwip_sw_int_handler = 0x400014ac; +r_rwip_sw_int_req = 0x400014b0; +r_rwip_time_get = 0x400014b4; +r_rwip_timer_10ms_handler = 0x400014b8; +r_rwip_timer_10ms_set = 0x400014bc; +r_rwip_timer_hs_handler = 0x400014c0; +r_rwip_timer_hs_set = 0x400014c4; +r_rwip_timer_hus_handler = 0x400014c8; +r_rwip_timer_hus_set = 0x400014cc; +r_rwip_wakeup = 0x400014d0; +r_rwip_wakeup_end = 0x400014d4; +r_rwip_wlcoex_set = 0x400014d8; +r_sch_alarm_clear = 0x400014dc; +r_sch_alarm_init = 0x400014e0; +r_sch_alarm_prog = 0x400014e4; +r_sch_alarm_set = 0x400014e8; +r_sch_alarm_timer_isr = 0x400014ec; +r_sch_arb_conflict_check = 0x400014f0; +r_sch_arb_elt_cancel = 0x400014f4; +r_sch_arb_init = 0x400014fc; +r_sch_arb_insert = 0x40001500; +r_sch_arb_prog_timer = 0x40001504; +r_sch_arb_remove = 0x40001508; +r_sch_arb_sw_isr = 0x4000150c; +r_sch_plan_chk = 0x40001510; +r_sch_plan_clock_wrap_offset_update = 0x40001514; +r_sch_plan_init = 0x40001518; +r_sch_plan_interval_req = 0x4000151c; +r_sch_plan_offset_max_calc = 0x40001520; +r_sch_plan_offset_req = 0x40001524; +r_sch_plan_position_range_compute = 0x40001528; +r_sch_plan_rem = 0x4000152c; +r_sch_plan_req = 0x40001530; +r_sch_prog_init = 0x4000153c; +r_sch_prog_push = 0x40001540; +r_sch_prog_rx_isr = 0x40001544; +r_sch_prog_skip_isr = 0x40001548; +r_sch_prog_tx_isr = 0x4000154c; +r_sch_slice_bg_add = 0x40001550; +r_sch_slice_bg_remove = 0x40001554; +r_sch_slice_compute = 0x40001558; +r_sch_slice_fg_add = 0x4000155c; +r_sch_slice_fg_remove = 0x40001560; +r_sch_slice_init = 0x40001564; +r_sch_slice_per_add = 0x40001568; +r_sch_slice_per_remove = 0x4000156c; +r_sdk_config_get_bt_sleep_enable = 0x40001570; +r_sdk_config_get_hl_derived_opts = 0x40001574; +r_sdk_config_get_opts = 0x40001578; +r_sdk_config_get_priv_opts = 0x4000157c; +r_sdk_config_set_bt_sleep_enable = 0x40001580; +r_sdk_config_set_hl_derived_opts = 0x40001584; +r_sdk_config_set_opts = 0x40001588; +r_specialModP256 = 0x4000158c; +r_unloaded_area_init = 0x40001590; +r_vhci_flow_off = 0x40001594; +r_vhci_flow_on = 0x40001598; +r_vhci_notify_host_send_available = 0x4000159c; +r_vhci_send_to_host = 0x400015a0; +r_vnd_hci_command_handler = 0x400015a4; +r_vshci_init = 0x400015a8; +vnd_hci_command_handler_wrapper = 0x400015ac; +/* Data (.data, .bss, .rodata) */ +bt_rf_coex_cfg_p = 0x3fcdffcc; +bt_rf_coex_hooks_p = 0x3fcdffc8; +btdm_env_p = 0x3fcdffc4; +g_rw_controller_task_handle = 0x3fcdffc0; +g_rw_init_sem = 0x3fcdffbc; +g_rw_schd_queue = 0x3fcdffb8; +lld_init_env = 0x3fcdffb4; +lld_rpa_renew_env = 0x3fcdffb0; +lld_scan_env = 0x3fcdffac; +lld_scan_sync_env = 0x3fcdffa8; +lld_test_env = 0x3fcdffa4; +p_ble_util_buf_env = 0x3fcdffa0; +p_lld_env = 0x3fcdff9c; +p_llm_env = 0x3fcdff98; +r_h4tl_eif_p = 0x3fcdff94; +r_hli_funcs_p = 0x3fcdff90; +r_ip_funcs_p = 0x3fcdff8c; +r_modules_funcs_p = 0x3fcdff88; +r_osi_funcs_p = 0x3fcdff84; +r_plf_funcs_p = 0x3fcdff80; +vhci_env_p = 0x3fcdff7c; +aa_gen = 0x3fcdff78; +aes_env = 0x3fcdff6c; +bt_rf_coex_cfg_cb = 0x3fcdff1c; +btdm_pwr_state = 0x3fcdff18; +btdm_slp_err = 0x3fcdff14; +ecc_env = 0x3fcdff0c; +esp_handler = 0x3fcdff04; +esp_vendor_cmd = 0x3fcdfefc; +g_adv_delay_dis = 0x3fcdfef8; +g_conflict_elt = 0x3fcdfef4; +g_eif_api = 0x3fcdfee4; +g_event_empty = 0x3fcdfed8; +g_llc_state = 0x3fcdfecc; +g_llm_state = 0x3fcdfec8; +g_max_evt_env = 0x3fcdfec4; +g_misc_state = 0x3fcdfec0; +g_rma_rule_db = 0x3fcdfea4; +g_rtp_rule_db = 0x3fcdfe88; +g_scan_forever = 0x3fcdfe85; +g_time_msb = 0x3fcdfe84; +h4tl_env = 0x3fcdfe5c; +hci_env = 0x3fcdfe38; +hci_ext_host = 0x3fcdfe34; +hci_fc_env = 0x3fcdfe2c; +hci_tl_env = 0x3fcdfe00; +ke_env = 0x3fcdfdd0; +ke_event_env = 0x3fcdfd90; +ke_task_env = 0x3fcdfd14; +llc_env = 0x3fcdfcec; +lld_adv_env = 0x3fcdfcc4; +lld_con_env = 0x3fcdfc9c; +lld_exp_sync_pos_tab = 0x3fcdfc94; +lld_per_adv_env = 0x3fcdfc6c; +lld_sync_env = 0x3fcdfc44; +llm_le_adv_flow_env = 0x3fcdfc38; +rw_sleep_enable = 0x3fcdfc34; +rwble_env = 0x3fcdfc2c; +rwip_env = 0x3fcdfc10; +rwip_param = 0x3fcdfc04; +rwip_prog_delay = 0x3fcdfc00; +rwip_rf = 0x3fcdfbc8; +sch_alarm_env = 0x3fcdfbc0; +sch_arb_env = 0x3fcdfbac; +sch_plan_env = 0x3fcdfba4; +sch_prog_env = 0x3fcdfaa0; +sch_slice_env = 0x3fcdfa40; +sch_slice_params = 0x3fcdfa38; +timer_env = 0x3fcdfa30; +unloaded_area = 0x3fcdfa2c; +vshci_state = 0x3fcdfa28; +TASK_DESC_LLC = 0x3fcdfa1c; +TASK_DESC_LLM = 0x3fcdfa10; +TASK_DESC_VSHCI = 0x3fcdfa04; +co_default_bdaddr = 0x3fcdf9fc; +dbg_assert_block = 0x3fcdf9f8; +g_bt_plf_log_level = 0x3fcdf9f4; +hci_cmd_desc_tab_vs_esp = 0x3fcdf9d0; +hci_command_handler_tab_esp = 0x3fcdf9b8; +privacy_en = 0x3fcdf9b4; +sdk_cfg_priv_opts = 0x3fcdf96c; +BasePoint_x_256 = 0x3ff1ffdc; +BasePoint_y_256 = 0x3ff1ffbc; +DebugE256PublicKey_x = 0x3ff1ff9c; +DebugE256PublicKey_y = 0x3ff1ff7c; +DebugE256SecretKey = 0x3ff1ff5c; +ECC_4Win_Look_up_table = 0x3ff1f7a0; +LLM_AA_CT1 = 0x3ff1f79c; +LLM_AA_CT2 = 0x3ff1f798; +RF_TX_PW_CONV_TBL = 0x3ff1f790; +TASK_DESC_MISC = 0x3ff1f784; +adv_evt_prop2type = 0x3ff1f768; +adv_evt_type2prop = 0x3ff1f760; +aes_cmac_zero = 0x3ff1f750; +aes_k2_salt = 0x3ff1f740; +aes_k3_id64 = 0x3ff1f738; +aes_k3_salt = 0x3ff1f728; +aes_k4_id6 = 0x3ff1f724; +aes_k4_salt = 0x3ff1f714; +bigHexP256 = 0x3ff1f6e8; +byte_tx_time = 0x3ff1f6e0; +co_null_bdaddr = 0x3ff1f6d8; +co_phy_mask_to_rate = 0x3ff1f6d0; +co_phy_mask_to_value = 0x3ff1f6c8; +co_phy_to_rate = 0x3ff1f6c4; +co_phy_value_to_mask = 0x3ff1f6c0; +co_rate_to_byte_dur_us = 0x3ff1f6b8; +co_rate_to_phy = 0x3ff1f6b0; +co_rate_to_phy_mask = 0x3ff1f6ac; +co_sca2ppm = 0x3ff1f69c; +coef_B = 0x3ff1f670; +connect_req_dur_tab = 0x3ff1f668; +ecc_Jacobian_InfinityPoint256 = 0x3ff1f5e4; +em_base_reg_lut = 0x3ff1f518; +fixed_tx_time = 0x3ff1f510; +h4tl_msgtype2hdrlen = 0x3ff1f508; +hci_cmd_desc_root_tab = 0x3ff1f4d8; +hci_cmd_desc_tab_ctrl_bb = 0x3ff1f46c; +hci_cmd_desc_tab_info_par = 0x3ff1f43c; +hci_cmd_desc_tab_le = 0x3ff1f0a0; +hci_cmd_desc_tab_lk_ctrl = 0x3ff1f088; +hci_cmd_desc_tab_stat_par = 0x3ff1f07c; +hci_cmd_desc_tab_vs = 0x3ff1f040; +hci_evt_desc_tab = 0x3ff1eff8; +hci_evt_le_desc_tab = 0x3ff1ef58; +hci_evt_le_desc_tab_esp = 0x3ff1ef50; +hci_rsvd_evt_msk = 0x3ff1ef48; +lld_aux_phy_to_rate = 0x3ff1ef44; +lld_init_max_aux_dur_tab = 0x3ff1ef3c; +lld_scan_map_legacy_pdu_to_evt_type = 0x3ff1ef34; +lld_scan_max_aux_dur_tab = 0x3ff1ef2c; +lld_sync_max_aux_dur_tab = 0x3ff1ef24; +llm_local_le_feats = 0x3ff1ef1c; +llm_local_le_states = 0x3ff1ef14; +llm_local_supp_cmds = 0x3ff1eeec; +maxSecretKey_256 = 0x3ff1eecc; +max_data_tx_time = 0x3ff1eec4; +one_bits = 0x3ff1eeb4; +rwip_coex_cfg = 0x3ff1eeac; +rwip_priority = 0x3ff1ee94; +veryBigHexP256 = 0x3ff1ee48; + +/* bluetooth hook funcs */ +r_llc_loc_encrypt_proc_continue_hook = 0x40001c60; +r_llc_loc_phy_upd_proc_continue_hook = 0x40001c64; +r_llc_rem_phy_upd_proc_continue_hook = 0x40001c68; +r_lld_scan_frm_eof_isr_hook = 0x40001c6c; +r_lld_scan_evt_start_cbk_hook = 0x40001c70; +r_lld_scan_process_pkt_rx_ext_adv_hook = 0x40001c78; +r_lld_scan_sched_hook = 0x40001c7c; +r_lld_adv_evt_start_cbk_hook = 0x40001c84; +r_lld_adv_aux_evt_start_cbk_hook = 0x40001c88; +r_lld_adv_frm_isr_hook = 0x40001c8c; +r_lld_adv_start_init_evt_param_hook = 0x40001c90; +r_lld_con_evt_canceled_cbk_hook = 0x40001c94; +r_lld_con_frm_isr_hook = 0x40001c98; +r_lld_con_tx_hook = 0x40001c9c; +r_lld_con_rx_hook = 0x40001ca0; +r_lld_con_evt_start_cbk_hook = 0x40001ca4; +r_lld_con_tx_prog_new_packet_hook = 0x40001cac; +r_lld_init_frm_eof_isr_hook = 0x40001cb0; +r_lld_init_evt_start_cbk_hook = 0x40001cb4; +r_lld_init_sched_hook = 0x40001cbc; +r_lld_init_process_pkt_tx_hook = 0x40001cc0; +r_lld_per_adv_evt_start_cbk_hook = 0x40001cc4; +r_lld_per_adv_frm_isr_hook = 0x40001cc8; +r_lld_per_adv_start_hook = 0x40001ccc; +r_lld_sync_frm_eof_isr_hook = 0x40001cd0; +r_lld_sync_evt_start_cbk_hook = 0x40001cd4; +r_lld_sync_start_hook = 0x40001cd8; +r_lld_sync_process_pkt_rx_pkt_check_hook = 0x40001cdc; +r_sch_arb_insert_hook = 0x40001ce0; +r_sch_plan_offset_req_hook = 0x40001ce4; + +/*************************************** + Group rom_pp + ***************************************/ + +/* Functions */ +esp_pp_rom_version_get = 0x400015b0; +RC_GetBlockAckTime = 0x400015b4; +ebuf_list_remove = 0x400015b8; +/*esf_buf_alloc = 0x400015bc;*/ +GetAccess = 0x400015c8; +hal_mac_is_low_rate_enabled = 0x400015cc; +hal_mac_tx_get_blockack = 0x400015d0; +/* hal_mac_tx_set_ppdu = 0x400015d4;*/ +ic_get_trc = 0x400015d8; +/* ic_mac_deinit = 0x400015dc; */ +ic_mac_init = 0x400015e0; +ic_interface_enabled = 0x400015e4; +is_lmac_idle = 0x400015e8; +/*lmacAdjustTimestamp = 0x400015ec;*/ +lmacDiscardAgedMSDU = 0x400015f0; +/*lmacDiscardMSDU = 0x400015f4;*/ +lmacEndFrameExchangeSequence = 0x400015f8; +lmacIsIdle = 0x400015fc; +lmacIsLongFrame = 0x40001600; +/*lmacMSDUAged = 0x40001604;*/ +lmacPostTxComplete = 0x40001608; +lmacProcessAllTxTimeout = 0x4000160c; +lmacProcessCollisions = 0x40001610; +lmacProcessRxSucData = 0x40001614; +lmacReachLongLimit = 0x40001618; +lmacReachShortLimit = 0x4000161c; +lmacRecycleMPDU = 0x40001620; +lmacRxDone = 0x40001624; +/*lmacSetTxFrame = 0x40001628;*/ +/*lmacTxFrame = 0x40001630;*/ +mac_tx_set_duration = 0x40001634; +/* mac_tx_set_htsig = 0x40001638;*/ +mac_tx_set_plcp0 = 0x4000163c; +/* mac_tx_set_plcp1 = 0x40001640;*/ +mac_tx_set_plcp2 = 0x40001644; +/* pm_check_state = 0x40001648; */ +pm_disable_dream_timer = 0x4000164c; +pm_disable_sleep_delay_timer = 0x40001650; +pm_dream = 0x40001654; +pm_mac_wakeup = 0x40001658; +pm_mac_sleep = 0x4000165c; +pm_enable_active_timer = 0x40001660; +pm_enable_sleep_delay_timer = 0x40001664; +pm_local_tsf_process = 0x40001668; +pm_set_beacon_filter = 0x4000166c; +pm_is_in_wifi_slice_threshold = 0x40001670; +pm_is_waked = 0x40001674; +pm_keep_alive = 0x40001678; +/* pm_on_beacon_rx = 0x4000167c; */ +pm_on_data_rx = 0x40001680; +pm_on_tbtt = 0x40001684; +/* pm_parse_beacon = 0x40001688;*/ +/* pm_process_tim = 0x4000168c; */ +/*pm_rx_beacon_process = 0x40001690;*/ +/* pm_rx_data_process = 0x40001694; */ +/*pm_sleep = 0x40001698;*/ +pm_sleep_for = 0x4000169c; +/* pm_tbtt_process = 0x400016a0; */ +ppAMPDU2Normal = 0x400016a4; +/*ppAssembleAMPDU = 0x400016a8;*/ +ppCalFrameTimes = 0x400016ac; +ppCalSubFrameLength = 0x400016b0; +/*ppCalTxAMPDULength = 0x400016b4;*/ +ppCheckTxAMPDUlength = 0x400016b8; +ppDequeueRxq_Locked = 0x400016bc; +ppDequeueTxQ = 0x400016c0; +ppEmptyDelimiterLength = 0x400016c4; +ppEnqueueRxq = 0x400016c8; +ppEnqueueTxDone = 0x400016cc; +ppGetTxQFirstAvail_Locked = 0x400016d0; +ppGetTxframe = 0x400016d4; +ppProcessRxPktHdr = 0x400016e0; +ppProcessTxQ = 0x400016e4; +ppRecordBarRRC = 0x400016e8; +lmacRequestTxopQueue = 0x400016ec; +lmacReleaseTxopQueue = 0x400016f0; +ppRecycleAmpdu = 0x400016f4; +ppRecycleRxPkt = 0x400016f8; +ppResortTxAMPDU = 0x400016fc; +ppResumeTxAMPDU = 0x40001700; +/* ppRxFragmentProc = 0x40001704; */ +/* ppRxPkt = 0x40001708; */ +ppRxProtoProc = 0x4000170c; +ppSearchTxQueue = 0x40001710; +ppSearchTxframe = 0x40001714; +ppSelectNextQueue = 0x40001718; +ppSubFromAMPDU = 0x4000171c; +ppTask = 0x40001720; +ppTxPkt = 0x40001724; +ppTxProtoProc = 0x40001728; +ppTxqUpdateBitmap = 0x4000172c; +pp_coex_tx_request = 0x40001730; +pp_hdrsize = 0x40001734; +pp_post = 0x40001738; +pp_process_hmac_waiting_txq = 0x4000173c; +rcGetAmpduSched = 0x40001740; +rcUpdateRxDone = 0x40001744; +rc_get_trc = 0x40001748; +rc_get_trc_by_index = 0x4000174c; +rcAmpduLowerRate = 0x40001750; +rcampduuprate = 0x40001754; +rcClearCurAMPDUSched = 0x40001758; +rcClearCurSched = 0x4000175c; +rcClearCurStat = 0x40001760; +rcLowerSched = 0x40001768; +rcSetTxAmpduLimit = 0x4000176c; +/* rcTxUpdatePer = 0x40001770;*/ +rcUpdateAckSnr = 0x40001774; +/*rcUpdateRate = 0x40001778;*/ +/* rcUpdateTxDone = 0x4000177c; */ +rcUpdateTxDoneAmpdu2 = 0x40001780; +rcUpSched = 0x40001784; +rssi_margin = 0x40001788; +rx11NRate2AMPDULimit = 0x4000178c; +TRC_AMPDU_PER_DOWN_THRESHOLD = 0x40001790; +TRC_AMPDU_PER_UP_THRESHOLD = 0x40001794; +trc_calc_duration = 0x40001798; +trc_isTxAmpduOperational = 0x4000179c; +trc_onAmpduOp = 0x400017a0; +TRC_PER_IS_GOOD = 0x400017a4; +trc_SetTxAmpduState = 0x400017a8; +trc_tid_isTxAmpduOperational = 0x400017ac; +trcAmpduSetState = 0x400017b0; +wDev_AppendRxBlocks = 0x400017b8; +wDev_DiscardFrame = 0x400017bc; +wDev_GetNoiseFloor = 0x400017c0; +wDev_IndicateAmpdu = 0x400017c4; +/*wDev_IndicateFrame = 0x400017c8;*/ +wdev_bank_store = 0x400017cc; +wdev_bank_load = 0x400017d0; +wdev_mac_reg_load = 0x400017d4; +wdev_mac_reg_store = 0x400017d8; +wdev_mac_special_reg_load = 0x400017dc; +wdev_mac_special_reg_store = 0x400017e0; +wdev_mac_wakeup = 0x400017e4; +wdev_mac_sleep = 0x400017e8; +hal_mac_is_dma_enable = 0x400017ec; +/*wDev_ProcessFiq = 0x400017f0;*/ +/*wDev_ProcessRxSucData = 0x400017f4;*/ +wdevProcessRxSucDataAll = 0x400017f8; +wdev_csi_len_align = 0x400017fc; +ppDequeueTxDone_Locked = 0x40001800; +/*pm_tx_data_done_process = 0x40001808;*/ +config_is_cache_tx_buf_enabled = 0x4000180c; +//ppMapWaitTxq = 0x40001810; +ppProcessWaitingQueue = 0x40001814; +ppDisableQueue = 0x40001818; +pm_allow_tx = 0x4000181c; +/* Data (.data, .bss, .rodata) */ +our_instances_ptr = 0x3ff1ee44; +pTxRx = 0x3fcdf968; +lmacConfMib_ptr = 0x3fcdf964; +our_wait_eb = 0x3fcdf960; +our_tx_eb = 0x3fcdf95c; +pp_wdev_funcs = 0x3fcdf958; +g_osi_funcs_p = 0x3fcdf954; +wDevCtrl_ptr = 0x3fcdf950; +g_wdev_last_desc_reset_ptr = 0x3ff1ee40; +wDevMacSleep_ptr = 0x3fcdf94c; +g_lmac_cnt_ptr = 0x3fcdf948; +our_controls_ptr = 0x3ff1ee3c; +pp_sig_cnt_ptr = 0x3fcdf944; +g_eb_list_desc_ptr = 0x3fcdf940; +s_fragment_ptr = 0x3fcdf93c; +if_ctrl_ptr = 0x3fcdf938; +g_intr_lock_mux = 0x3fcdf934; +g_wifi_global_lock = 0x3fcdf930; +s_wifi_queue = 0x3fcdf92c; +pp_task_hdl = 0x3fcdf928; +s_pp_task_create_sem = 0x3fcdf924; +s_pp_task_del_sem = 0x3fcdf920; +g_wifi_menuconfig_ptr = 0x3fcdf91c; +xphyQueue = 0x3fcdf918; +ap_no_lr_ptr = 0x3fcdf914; +rc11BSchedTbl_ptr = 0x3fcdf910; +rc11NSchedTbl_ptr = 0x3fcdf90c; +rcLoRaSchedTbl_ptr = 0x3fcdf908; +BasicOFDMSched_ptr = 0x3fcdf904; +trc_ctl_ptr = 0x3fcdf900; +g_pm_cnt_ptr = 0x3fcdf8fc; +g_pm_ptr = 0x3fcdf8f8; +g_pm_cfg_ptr = 0x3fcdf8f4; +g_esp_mesh_quick_funcs_ptr = 0x3fcdf8f0; +g_txop_queue_status_ptr = 0x3fcdf8ec; +g_mac_sleep_en_ptr = 0x3fcdf8e8; +g_mesh_is_root_ptr = 0x3fcdf8e4; +g_mesh_topology_ptr = 0x3fcdf8e0; +g_mesh_init_ps_type_ptr = 0x3fcdf8dc; +g_mesh_is_started_ptr = 0x3fcdf8d8; +g_config_func = 0x3fcdf8d4; +g_net80211_tx_func = 0x3fcdf8d0; +g_timer_func = 0x3fcdf8cc; +s_michael_mic_failure_cb = 0x3fcdf8c8; +wifi_sta_rx_probe_req = 0x3fcdf8c4; +g_tx_done_cb_func = 0x3fcdf8c0; +g_per_conn_trc = 0x3fcdf874; +s_encap_amsdu_func = 0x3fcdf870; + + +/*************************************** + Group rom_net80211 + ***************************************/ + +/* Functions */ +esp_net80211_rom_version_get = 0x40001820; +ampdu_dispatch = 0x40001824; +ampdu_dispatch_all = 0x40001828; +ampdu_dispatch_as_many_as_possible = 0x4000182c; +ampdu_dispatch_movement = 0x40001830; +ampdu_dispatch_upto = 0x40001834; +chm_is_at_home_channel = 0x40001838; +cnx_node_is_existing = 0x4000183c; +cnx_node_search = 0x40001840; +ic_ebuf_recycle_rx = 0x40001844; +ic_ebuf_recycle_tx = 0x40001848; +ic_reset_rx_ba = 0x4000184c; +ieee80211_align_eb = 0x40001850; +ieee80211_ampdu_reorder = 0x40001854; +ieee80211_ampdu_start_age_timer = 0x40001858; +/*ieee80211_encap_esfbuf = 0x4000185c;*/ +ieee80211_is_tx_allowed = 0x40001860; +ieee80211_output_pending_eb = 0x40001864; +/*ieee80211_output_process = 0x40001868;*/ +ieee80211_set_tx_desc = 0x4000186c; +rom_sta_input = 0x40001870; +wifi_get_macaddr = 0x40001874; +wifi_rf_phy_disable = 0x40001878; +wifi_rf_phy_enable = 0x4000187c; +ic_ebuf_alloc = 0x40001880; +ieee80211_classify = 0x40001884; +ieee80211_copy_eb_header = 0x40001888; +ieee80211_recycle_cache_eb = 0x4000188c; +ieee80211_search_node = 0x40001890; +roundup2 = 0x40001894; +ieee80211_crypto_encap = 0x40001898; +/* ieee80211_crypto_decap = 0x4000189c; */ +/* ieee80211_decap = 0x400018a0; */ +ieee80211_set_tx_pti = 0x400018a4; +wifi_is_started = 0x400018a8; +/* Data (.data, .bss, .rodata) */ +net80211_funcs = 0x3fcdf86c; +g_scan = 0x3fcdf868; +g_chm = 0x3fcdf864; +g_ic_ptr = 0x3fcdf860; +g_hmac_cnt_ptr = 0x3fcdf85c; +g_tx_cacheq_ptr = 0x3fcdf858; +s_netstack_free = 0x3fcdf854; +mesh_rxcb = 0x3fcdf850; +sta_rxcb = 0x3fcdf84c; + + +/*************************************** + Group rom_coexist + ***************************************/ + +/* Functions */ +esp_coex_rom_version_get = 0x400018ac; +coex_bt_release = 0x400018b0; +coex_bt_request = 0x400018b4; +coex_core_ble_conn_dyn_prio_get = 0x400018b8; +coex_core_event_duration_get = 0x400018bc; +coex_core_pti_get = 0x400018c0; +coex_core_release = 0x400018c4; +coex_core_request = 0x400018c8; +coex_core_status_get = 0x400018cc; +/*coex_core_timer_idx_get = 0x400018d0;*/ +coex_event_duration_get = 0x400018d4; +coex_hw_timer_disable = 0x400018d8; +coex_hw_timer_enable = 0x400018dc; +coex_hw_timer_set = 0x400018e0; +coex_schm_interval_set = 0x400018e4; +coex_schm_lock = 0x400018e8; +coex_schm_unlock = 0x400018ec; +coex_status_get = 0x400018f0; +coex_wifi_release = 0x400018f4; +esp_coex_ble_conn_dynamic_prio_get = 0x400018f8; +/* Data (.data, .bss, .rodata) */ +coex_env_ptr = 0x3fcdf848; +coex_pti_tab_ptr = 0x3fcdf844; +coex_schm_env_ptr = 0x3fcdf840; +coexist_funcs = 0x3fcdf83c; +g_coa_funcs_p = 0x3fcdf838; +g_coex_param_ptr = 0x3fcdf834; + + +/*************************************** + Group rom_phy + ***************************************/ + +/* Functions */ +phy_get_romfuncs = 0x400018fc; +rom_abs_temp = 0x40001900; +rom_bb_bss_cbw40_dig = 0x40001904; +rom_bb_wdg_test_en = 0x40001908; +rom_bb_wdt_get_status = 0x4000190c; +rom_bb_wdt_int_enable = 0x40001910; +rom_bb_wdt_rst_enable = 0x40001914; +rom_bb_wdt_timeout_clear = 0x40001918; +rom_cbw2040_cfg = 0x4000191c; +rom_check_noise_floor = 0x40001920; +rom_chip_i2c_readReg = 0x40001924; +rom_chip_i2c_writeReg = 0x40001928; +rom_correct_rf_ana_gain = 0x4000192c; +rom_dc_iq_est = 0x40001930; +rom_disable_agc = 0x40001934; +rom_en_pwdet = 0x40001938; +rom_enable_agc = 0x4000193c; +rom_get_bbgain_db = 0x40001940; +rom_get_data_sat = 0x40001944; +rom_get_i2c_read_mask = 0x40001948; +rom_get_pwctrl_correct = 0x4000194c; +rom_get_rf_gain_qdb = 0x40001950; +rom_i2c_readReg = 0x40001954; +rom_i2c_readReg_Mask = 0x40001958; +rom_i2c_writeReg = 0x4000195c; +rom_i2c_writeReg_Mask = 0x40001960; +/* rom_index_to_txbbgain = 0x40001964; */ +rom_iq_est_disable = 0x40001968; +rom_iq_est_enable = 0x4000196c; +rom_linear_to_db = 0x40001970; +rom_loopback_mode_en = 0x40001974; +rom_mhz2ieee = 0x40001978; +rom_noise_floor_auto_set = 0x4000197c; +rom_pbus_debugmode = 0x40001980; +rom_pbus_force_mode = 0x40001984; +rom_pbus_force_test = 0x40001988; +rom_pbus_rd = 0x4000198c; +rom_pbus_rd_addr = 0x40001990; +rom_pbus_rd_shift = 0x40001994; +rom_pbus_set_dco = 0x40001998; +rom_pbus_set_rxgain = 0x4000199c; +rom_pbus_workmode = 0x400019a0; +rom_pbus_xpd_rx_off = 0x400019a4; +rom_pbus_xpd_rx_on = 0x400019a8; +rom_pbus_xpd_tx_off = 0x400019ac; +/* rom_pbus_xpd_tx_on = 0x400019b0; */ +rom_phy_byte_to_word = 0x400019b4; +rom_phy_disable_cca = 0x400019b8; +rom_phy_enable_cca = 0x400019bc; +rom_phy_get_noisefloor = 0x400019c0; +rom_phy_get_rx_freq = 0x400019c4; +rom_phy_set_bbfreq_init = 0x400019c8; +rom_pow_usr = 0x400019cc; +rom_pwdet_sar2_init = 0x400019d0; +rom_read_hw_noisefloor = 0x400019d4; +rom_read_sar_dout = 0x400019d8; +rom_set_cal_rxdc = 0x400019dc; +rom_set_chan_cal_interp = 0x400019e0; +rom_set_loopback_gain = 0x400019e4; +rom_set_noise_floor = 0x400019e8; +rom_set_rxclk_en = 0x400019ec; +/* rom_set_tx_dig_gain = 0x400019f0; */ +/* rom_set_txcap_reg = 0x400019f4; */ +rom_set_txclk_en = 0x400019f8; +rom_spur_cal = 0x400019fc; +rom_spur_reg_write_one_tone = 0x40001a00; +rom_target_power_add_backoff = 0x40001a04; +rom_tx_pwctrl_bg_init = 0x40001a08; +/* rom_txbbgain_to_index = 0x40001a0c; */ +rom_wifi_11g_rate_chg = 0x40001a10; +rom_write_gain_mem = 0x40001a14; +chip726_phyrom_version = 0x40001a18; +rom_disable_wifi_agc = 0x40001a1c; +rom_enable_wifi_agc = 0x40001a20; +rom_set_tx_gain_table = 0x40001a24; +rom_bt_index_to_bb = 0x40001a28; +rom_bt_bb_to_index = 0x40001a2c; +rom_wr_bt_tx_atten = 0x40001a30; +rom_wr_bt_tx_gain_mem = 0x40001a34; +rom_spur_coef_cfg = 0x40001a38; +rom_bb_bss_cbw40 = 0x40001a3c; +rom_set_cca = 0x40001a40; +rom_tx_paon_set = 0x40001a44; +rom_i2cmst_reg_init = 0x40001a48; +rom_iq_corr_enable = 0x40001a4c; +rom_fe_reg_init = 0x40001a50; +/* rom_agc_reg_init = 0x40001a54; */ +/* rom_bb_reg_init = 0x40001a58; */ +rom_mac_enable_bb = 0x40001a5c; +rom_bb_wdg_cfg = 0x40001a60; +rom_force_txon = 0x40001a64; +rom_fe_txrx_reset = 0x40001a68; +rom_set_rx_comp = 0x40001a6c; +/* rom_set_pbus_reg = 0x40001a70; */ +rom_write_chan_freq = 0x40001a74; +/* rom_phy_xpd_rf = 0x40001a78; */ +rom_set_xpd_sar = 0x40001a7c; +rom_write_dac_gain2 = 0x40001a80; +rom_rtc_sar2_init = 0x40001a84; +rom_get_target_power_offset = 0x40001a88; +/* rom_write_txrate_power_offset = 0x40001a8c; */ +rom_get_rate_fcc_index = 0x40001a90; +rom_get_rate_target_power = 0x40001a94; +rom_write_wifi_dig_gain = 0x40001a98; +rom_bt_correct_rf_ana_gain = 0x40001a9c; +rom_pkdet_vol_start = 0x40001aa0; +rom_read_sar2_code = 0x40001aa4; +rom_get_sar2_vol = 0x40001aa8; +rom_get_pll_vol = 0x40001aac; +rom_get_phy_target_power = 0x40001ab0; +/* rom_temp_to_power = 0x40001ab4; */ +rom_phy_track_pll_cap = 0x40001ab8; +rom_phy_pwdet_always_en = 0x40001abc; +rom_phy_pwdet_onetime_en = 0x40001ac0; +rom_get_i2c_mst0_mask = 0x40001ac4; +rom_get_i2c_hostid = 0x40001ac8; +rom_enter_critical_phy = 0x40001acc; +rom_exit_critical_phy = 0x40001ad0; +rom_chip_i2c_readReg_org = 0x40001ad4; +rom_i2c_paral_set_mst0 = 0x40001ad8; +rom_i2c_paral_set_read = 0x40001adc; +rom_i2c_paral_read = 0x40001ae0; +rom_i2c_paral_write = 0x40001ae4; +rom_i2c_paral_write_num = 0x40001ae8; +rom_i2c_paral_write_mask = 0x40001aec; +rom_bb_bss_cbw40_ana = 0x40001af0; +rom_chan_to_freq = 0x40001af4; +/* rom_open_i2c_xpd = 0x40001af8; */ +rom_dac_rate_set = 0x40001afc; +/* rom_tsens_read_init = 0x40001b00; */ +/* rom_tsens_code_read = 0x40001b04; */ +rom_tsens_index_to_dac = 0x40001b08; +rom_tsens_index_to_offset = 0x40001b0c; +/* rom_tsens_dac_cal = 0x40001b10; */ +rom_code_to_temp = 0x40001b14; +rom_write_pll_cap_mem = 0x40001b18; +rom_pll_correct_dcap = 0x40001b1c; +rom_phy_en_hw_set_freq = 0x40001b20; +rom_phy_dis_hw_set_freq = 0x40001b24; +/* rom_pll_vol_cal = 0x40001b28; */ diff --git a/targets/fe310.json b/targets/fe310.json index 37bd5a4c25..cd92c4fb1b 100644 --- a/targets/fe310.json +++ b/targets/fe310.json @@ -1,6 +1,6 @@ { "inherits": ["riscv32"], "cpu": "sifive-e31", - "features": "+32bit,+a,+c,+m,-64bit,-d,-e,-experimental-zawrs,-experimental-zca,-experimental-zcd,-experimental-zcf,-experimental-zihintntl,-experimental-ztso,-experimental-zvfh,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xtheadvdot,-xventanacondops,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zihintpause,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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 347ffe35b4..2140f459e4 100644 --- a/targets/k210.json +++ b/targets/k210.json @@ -1,6 +1,6 @@ { "inherits": ["riscv64"], - "features": "+64bit,+a,+c,+d,+f,+m,-e,-experimental-zawrs,-experimental-zca,-experimental-zcd,-experimental-zcf,-experimental-zihintntl,-experimental-ztso,-experimental-zvfh,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xtheadvdot,-xventanacondops,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zihintpause,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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/m5stamp-c3.json b/targets/m5stamp-c3.json index 53e556eec1..6c50abfb46 100644 --- a/targets/m5stamp-c3.json +++ b/targets/m5stamp-c3.json @@ -1,6 +1,7 @@ { "inherits": ["esp32c3"], "build-tags": ["m5stamp_c3"], + "serial": "uart", "serial-port": ["1a86:55d4"] } diff --git a/targets/matrixportal-m4.json b/targets/matrixportal-m4.json index 7a7156f0dc..c1f27a4aa0 100644 --- a/targets/matrixportal-m4.json +++ b/targets/matrixportal-m4.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd51j19a"], - "build-tags": ["matrixportal_m4"], + "build-tags": ["matrixportal_m4", "ninafw"], "serial": "usb", "serial-port": ["239a:80c9", "239a:80ca"], "flash-1200-bps-reset": "true", diff --git a/targets/metro-m4-airlift.json b/targets/metro-m4-airlift.json index 4bc29f9d3f..386584275d 100644 --- a/targets/metro-m4-airlift.json +++ b/targets/metro-m4-airlift.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd51j19a"], - "build-tags": ["metro_m4_airlift"], + "build-tags": ["metro_m4_airlift", "ninafw"], "serial": "usb", "serial-port": ["239A:8037"], "flash-1200-bps-reset": "true", diff --git a/targets/mksnanov3.json b/targets/mksnanov3.json new file mode 100644 index 0000000000..fc57a71d9b --- /dev/null +++ b/targets/mksnanov3.json @@ -0,0 +1,13 @@ +{ + "inherits": ["cortex-m4"], + "build-tags": ["mksnanov3", "stm32f407", "stm32f4", "stm32"], + "serial": "uart", + "linkerscript": "targets/stm32f407.ld", + "extra-files": [ + "src/device/stm32/stm32f407.s" + ], + "flash-method": "openocd", + "openocd-interface": "stlink", + "openocd-target": "stm32f4x", + "openocd-commands": ["stm32f4x.cpu configure -event reset-init { adapter speed 1800 }"] +} diff --git a/targets/nano-rp2040.json b/targets/nano-rp2040.json index 313fead9c0..85eed28d44 100644 --- a/targets/nano-rp2040.json +++ b/targets/nano-rp2040.json @@ -3,7 +3,7 @@ "rp2040" ], "serial-port": ["2341:005e"], - "build-tags": ["nano_rp2040"], + "build-tags": ["nano_rp2040", "ninafw", "ninafw_machine_init"], "ldflags": [ "--defsym=__flash_size=16M" ], diff --git a/targets/nintendoswitch.json b/targets/nintendoswitch.json index 5efede411b..f83f8fcc10 100644 --- a/targets/nintendoswitch.json +++ b/targets/nintendoswitch.json @@ -1,7 +1,7 @@ { "llvm-target": "aarch64", "cpu": "cortex-a57", - "features": "+aes,+crc,+crypto,+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/nrf52840-s140v6-uf2-generic.json b/targets/nrf52840-s140v6-uf2-generic.json new file mode 100644 index 0000000000..034f593393 --- /dev/null +++ b/targets/nrf52840-s140v6-uf2-generic.json @@ -0,0 +1,5 @@ +{ + "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], + "build-tags": ["nrf52840_generic"], + "serial-port": ["1209:9090"] +} 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/pico.json b/targets/pico.json index 88aec23d0c..cfc2056563 100644 --- a/targets/pico.json +++ b/targets/pico.json @@ -4,6 +4,7 @@ ], "build-tags": ["pico"], "serial-port": ["2e8a:000A"], + "default-stack-size": 8192, "ldflags": [ "--defsym=__flash_size=2048K" ], 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/pybadge.json b/targets/pybadge.json index 232a1bab2f..b41f87ada5 100644 --- a/targets/pybadge.json +++ b/targets/pybadge.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd51j19a"], - "build-tags": ["pybadge"], + "build-tags": ["pybadge", "ninafw"], "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", diff --git a/targets/pyportal.json b/targets/pyportal.json index bc503926eb..dbd4ed374c 100644 --- a/targets/pyportal.json +++ b/targets/pyportal.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd51j20a"], - "build-tags": ["pyportal"], + "build-tags": ["pyportal", "ninafw", "ninafw_machine_init"], "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", diff --git a/targets/qtpy-esp32c3.json b/targets/qtpy-esp32c3.json new file mode 100644 index 0000000000..b893a11bc3 --- /dev/null +++ b/targets/qtpy-esp32c3.json @@ -0,0 +1,4 @@ +{ + "inherits": ["esp32c3"], + "build-tags": ["qtpy_esp32c3"] +} 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 1a920282c2..90f1c312fc 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -1,8 +1,8 @@ { "inherits": ["riscv32"], - "features": "+32bit,+a,+c,+m,-64bit,-d,-e,-experimental-zawrs,-experimental-zca,-experimental-zcd,-experimental-zcf,-experimental-zihintntl,-experimental-ztso,-experimental-zvfh,-f,-h,-relax,-save-restore,-svinval,-svnapot,-svpbmt,-v,-xtheadvdot,-xventanacondops,-zba,-zbb,-zbc,-zbkb,-zbkc,-zbkx,-zbs,-zdinx,-zfh,-zfhmin,-zfinx,-zhinx,-zhinxmin,-zicbom,-zicbop,-zicboz,-zihintpause,-zk,-zkn,-zknd,-zkne,-zknh,-zkr,-zks,-zksed,-zksh,-zkt,-zmmul,-zve32f,-zve32x,-zve64d,-zve64f,-zve64x,-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/thumby.json b/targets/thumby.json new file mode 100644 index 0000000000..41cbc5e127 --- /dev/null +++ b/targets/thumby.json @@ -0,0 +1,14 @@ +{ + "inherits": [ + "rp2040" + ], + "serial-port": ["2e8a:0005"], + "build-tags": ["thumby"], + "default-stack-size": 8192, + "ldflags": [ + "--defsym=__flash_size=2048K" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} 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 33b37a89f6..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": 32768, - "cflags": [ - "-mbulk-memory", - "-mnontrapping-fptoint", - "-msign-ext" - ], - "ldflags": [ - "--stack-first", - "--no-demangle" - ], - "extra-files": [ - "src/runtime/asm_tinygowasm.S" - ], - "emulator": "wasmtime --mapdir=/tmp::{tmpDir} {}" + "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 new file mode 100644 index 0000000000..d64fa575cb --- /dev/null +++ b/targets/wasm-unknown.json @@ -0,0 +1,30 @@ +{ + "llvm-target": "wasm32-unknown-unknown", + "cpu": "generic", + "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": [ + "-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 --dir={tmpDir}::/tmp {}" +} diff --git a/targets/wasm.json b/targets/wasm.json index 2b93567872..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": 32768, + "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 5dfc67c357..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,23 +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; - const mem = new DataView(this._inst.exports.memory.buffer) - - 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(); } } @@ -489,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(); } @@ -519,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 4555c922f9..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" @@ -9,6 +10,7 @@ int mul(int, int); #include #cgo CFLAGS: -DSOME_CONSTANT=17 #define someDefine -5 + 2 * 7 +bool someBool; */ import "C" @@ -16,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()) @@ -42,6 +47,7 @@ func main() { println("callback 1:", C.doCallback(20, 30, cb)) cb = C.binop_t(C.mul) println("callback 2:", C.doCallback(20, 30, cb)) + genericCallbackCall[int]() // variadic functions println("variadic0:", C.variadic0()) @@ -56,6 +62,9 @@ func main() { var goInt8 int8 = 5 var _ C.int8_t = goInt8 + var _ bool = C.someBool + var _ C._Bool = C.someBool + // more globals println("bool:", C.globalBool, C.globalBool2 == true) println("float:", C.globalFloat) @@ -159,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") @@ -166,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 { @@ -193,3 +219,11 @@ func printBitfield(bitfield *C.bitfield_t) { println("bitfield d:", bitfield.d) println("bitfield e:", bitfield.e) } + +type Int interface { + int | uint +} + +func genericCallbackCall[T Int]() { + println("callback inside generic function:", C.doCallback(20, 30, C.binop_t(C.add))) +} 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 d5f3b13795..1d63f5e82f 100644 --- a/testdata/cgo/out.txt +++ b/testdata/cgo/out.txt @@ -13,6 +13,7 @@ defined expr: 9 25: 25 callback 1: 50 callback 2: 600 +callback inside generic function: 50 variadic0: 1 variadic2: 15 headerfunc: 6 @@ -72,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/corpus.yaml b/testdata/corpus.yaml index 02fde0c585..f239c4f3f9 100644 --- a/testdata/corpus.yaml +++ b/testdata/corpus.yaml @@ -24,12 +24,12 @@ - repo: github.com/buger/jsonparser - repo: github.com/dgryski/go-bloomindex - tags: purego noasm + tags: noasm - repo: github.com/dgryski/go-arc - repo: github.com/dgryski/go-camellia - repo: github.com/dgryski/go-change - repo: github.com/dgryski/go-chaskey - tags: appengine purego noasm + tags: appengine noasm skipwasi: true # siphash has build tag issues - repo: github.com/dgryski/go-clefia - repo: github.com/dgryski/go-clockpro @@ -40,7 +40,7 @@ - repo: github.com/dgryski/go-expirecache - repo: github.com/dgryski/go-factor - repo: github.com/dgryski/go-farm - tags: purego noasm + tags: noasm - repo: github.com/dgryski/go-fuzzstr - repo: github.com/dgryski/go-hollow - repo: github.com/dgryski/go-idea @@ -58,13 +58,12 @@ tags: appengine # for dchest/siphash skipwasi: true - repo: github.com/dgryski/go-marvin32 - tags: purego - repo: github.com/dgryski/go-md5crypt - repo: github.com/dgryski/go-metro - tags: purego noasm + tags: noasm - repo: github.com/dgryski/go-misty1 - repo: github.com/dgryski/go-mph - tags: purego noasm + tags: noasm - repo: github.com/dgryski/go-mpchash tags: appengine # for dchest/siphash skipwasi: true @@ -82,7 +81,7 @@ - repo: github.com/dgryski/go-s4lru - repo: github.com/dgryski/go-sequitur - repo: github.com/dgryski/go-sip13 - tags: purego noasm + tags: noasm - repo: github.com/dgryski/go-skinny - repo: github.com/dgryski/go-skip32 - repo: github.com/dgryski/go-skipjack @@ -97,9 +96,9 @@ - repo: github.com/dgryski/go-xoshiro - repo: github.com/dgryski/go-zlatlong - repo: github.com/dgryski/go-postings - tags: purego noasm + tags: noasm - repo: golang.org/x/crypto - tags: purego noasm + tags: noasm subdirs: - pkg: argon2 - pkg: bcrypt 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.22/go.mod b/testdata/go1.22/go.mod new file mode 100644 index 0000000000..fd97e8b22a --- /dev/null +++ b/testdata/go1.22/go.mod @@ -0,0 +1,3 @@ +module github.com/tinygo-org/tinygo/testdata/go1.22 + +go 1.22 diff --git a/testdata/go1.22/main.go b/testdata/go1.22/main.go new file mode 100644 index 0000000000..80f6c887b3 --- /dev/null +++ b/testdata/go1.22/main.go @@ -0,0 +1,31 @@ +package main + +func main() { + testIntegerRange() + testLoopVar() +} + +func testIntegerRange() { + for i := range 10 { + println(10 - i) + } + println("go1.22 has lift-off!") +} + +func testLoopVar() { + var f func() int + for i := 0; i < 1; i++ { + if i == 0 { + f = func() int { return i } + } + } + // Variable n is 1 in Go 1.21, or 0 in Go 1.22. + n := f() + if n == 0 { + println("loops behave like Go 1.22") + } else if n == 1 { + println("loops behave like Go 1.21") + } else { + println("unknown loop behavior") + } +} diff --git a/testdata/go1.22/out.txt b/testdata/go1.22/out.txt new file mode 100644 index 0000000000..ac9836aff8 --- /dev/null +++ b/testdata/go1.22/out.txt @@ -0,0 +1,12 @@ +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +go1.22 has lift-off! +loops behave like Go 1.22 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/interface.go b/testdata/interface.go index 72cc76bce5..1a3a838288 100644 --- a/testdata/interface.go +++ b/testdata/interface.go @@ -116,6 +116,9 @@ func main() { // check that pointer-to-pointer type switches work ptrptrswitch() + + // check that type asserts to interfaces with no methods work + emptyintfcrash() } func printItf(val interface{}) { @@ -334,3 +337,9 @@ func identify(itf any) { println("other type??") } } + +func emptyintfcrash() { + if x, ok := any(5).(any); ok { + println("x is", x.(int)) + } +} diff --git a/testdata/interface.txt b/testdata/interface.txt index fec46637b8..55f7ae1def 100644 --- a/testdata/interface.txt +++ b/testdata/interface.txt @@ -27,3 +27,4 @@ slept 1ms type is int type is *int type is **int +x is 5 diff --git a/testdata/math.go b/testdata/math.go index eceb39f3cf..58b19096ad 100644 --- a/testdata/math.go +++ b/testdata/math.go @@ -39,6 +39,7 @@ func main() { println(" remainder:", math.Remainder(n, n+0.2)) println(" sin: ", math.Sin(n)) println(" sinh: ", math.Sinh(n)) + println(" sqrt: ", float32(math.Sqrt(float64(n)))) println(" tan: ", math.Tan(n)) println(" tanh: ", math.Tanh(n)) println(" trunc: ", math.Trunc(n)) diff --git a/testdata/math.txt b/testdata/math.txt index 473ca4fe15..8c8da627a4 100644 --- a/testdata/math.txt +++ b/testdata/math.txt @@ -31,6 +31,7 @@ n: +3.000000e-001 remainder: -2.000000e-001 sin: +2.955202e-001 sinh: +3.045203e-001 + sqrt: +5.477226e-001 tan: +3.093362e-001 tanh: +2.913126e-001 trunc: +0.000000e+000 @@ -67,6 +68,7 @@ n: +1.500000e+000 remainder: -2.000000e-001 sin: +9.974950e-001 sinh: +2.129279e+000 + sqrt: +1.224745e+000 tan: +1.410142e+001 tanh: +9.051483e-001 trunc: +1.000000e+000 @@ -103,6 +105,7 @@ n: +2.600000e+000 remainder: -2.000000e-001 sin: +5.155014e-001 sinh: +6.694732e+000 + sqrt: +1.612452e+000 tan: -6.015966e-001 tanh: +9.890274e-001 trunc: +2.000000e+000 @@ -139,6 +142,7 @@ n: -1.100000e+000 remainder: -2.000000e-001 sin: -8.912074e-001 sinh: -1.335647e+000 + sqrt: NaN tan: -1.964760e+000 tanh: -8.004990e-001 trunc: -1.000000e+000 @@ -175,6 +179,7 @@ n: -3.100000e+000 remainder: -2.000000e-001 sin: -4.158066e-002 sinh: -1.107645e+001 + sqrt: NaN tan: +4.161665e-002 tanh: -9.959494e-001 trunc: -3.000000e+000 @@ -211,6 +216,7 @@ n: -3.800000e+000 remainder: -2.000000e-001 sin: +6.118579e-001 sinh: -2.233941e+001 + sqrt: NaN tan: -7.735561e-001 tanh: -9.989996e-001 trunc: -3.000000e+000 diff --git a/testdata/oldgo/go.mod b/testdata/oldgo/go.mod new file mode 100644 index 0000000000..f3c7277a9e --- /dev/null +++ b/testdata/oldgo/go.mod @@ -0,0 +1,5 @@ +module github.com/tinygo-org/tinygo/testdata/oldgo + +// Go version doesn't matter much, as long as it's old. + +go 1.15 diff --git a/testdata/oldgo/main.go b/testdata/oldgo/main.go new file mode 100644 index 0000000000..4bc0304440 --- /dev/null +++ b/testdata/oldgo/main.go @@ -0,0 +1,26 @@ +package main + +// This package verifies that the Go language version is correctly picked up +// from the go.mod file. + +func main() { + testLoopVar() +} + +func testLoopVar() { + var f func() int + for i := 0; i < 1; i++ { + if i == 0 { + f = func() int { return i } + } + } + // Variable n is 1 in Go 1.21, or 0 in Go 1.22. + n := f() + if n == 0 { + println("loops behave like Go 1.22") + } else if n == 1 { + println("loops behave like Go 1.21") + } else { + println("unknown loop behavior") + } +} diff --git a/testdata/oldgo/out.txt b/testdata/oldgo/out.txt new file mode 100644 index 0000000000..57ef14f10b --- /dev/null +++ b/testdata/oldgo/out.txt @@ -0,0 +1 @@ +loops behave like Go 1.21 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/timers.go b/testdata/timers.go index d0d9d4c77a..c179e47445 100644 --- a/testdata/timers.go +++ b/testdata/timers.go @@ -2,6 +2,8 @@ package main import "time" +var timer = time.NewTimer(time.Millisecond) + func main() { // Test ticker. ticker := time.NewTicker(time.Millisecond * 500) 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/sizediff b/tools/sizediff index 9c5dc705c1..64a61a67f6 100755 --- a/tools/sizediff +++ b/tools/sizediff @@ -5,26 +5,16 @@ import sys class Comparison: - def __init__(self, command, code0, data0, bss0, code1, data1, bss1): + def __init__(self, command, flash0, ram0, flash1, ram1): self.command = command - self.code0 = code0 - self.data0 = data0 - self.bss0 = bss0 - self.code1 = code1 - self.data1 = data1 - self.bss1 = bss1 + self.flash0 = flash0 + self.ram0 = ram0 + self.flash1 = flash1 + self.ram1 = ram1 @property - def flash0(self): - return self.code0 + self.data0 - - @property - def flash1(self): - return self.code1 + self.data1 - - @property - def codediff(self): - return self.code1 - self.code0 + def ramdiff(self): + return self.ram1 - self.ram0 @property def flashdiff(self): @@ -37,13 +27,12 @@ def readSizes(path): if not lines[i].strip().startswith('code '): continue # found a size header - code, data, bss = map(int, lines[i+1].split()[:3]) + code, data, bss, flash, ram = map(int, lines[i+1].replace("|", "").split()) command = lines[i-1].strip() sizes.append({ 'command': command, - 'code': code, - 'data': data, - 'bss': bss, + 'flash': flash, + 'ram': ram }) return sizes @@ -62,25 +51,36 @@ def main(): print('not the same command!') print(' ', sizes0[i]['command']) print(' ', sizes1[i]['command']) - comparisons.append(Comparison(sizes0[i]['command'], sizes0[i]['code'], sizes0[i]['data'], sizes0[i]['bss'], sizes1[i]['code'], sizes1[i]['data'], sizes1[i]['bss'])) + comparisons.append(Comparison(sizes0[i]['command'], sizes0[i]['flash'], sizes0[i]['ram'], sizes1[i]['flash'], sizes1[i]['ram'])) if len(sizes0) < len(sizes1): print('%s has more commands than %s' % (path1, path0)) print(' ', sizes1[len(sizes0)]['command']) comparisons.sort(key=lambda x: x.flashdiff) - totalCode0 = 0 - totalCode1 = 0 + totalFlash0 = 0 + totalFlash1 = 0 + totalRam0 = 0 + totalRam1 = 0 totalDiff = 0 + totalRamDiff = 0 totalProduct = 1 - print(' before after diff') + totalRamProduct = 1 + print(' flash ram') + print(' before after diff before after diff') for comparison in comparisons: diffPct = comparison.flashdiff / comparison.flash0 - print('%7d %7d %6d %6.2f%% %s' % (comparison.flash0, comparison.flash1, comparison.flashdiff, diffPct * 100, comparison.command)) - totalCode0 += comparison.flash0 - totalCode1 += comparison.flash1 + diffRamPct = comparison.ramdiff / comparison.ram0 + print('%7d %7d %6d %6.2f%% %7d %7d %6d %6.2f%% %s' % (comparison.flash0, comparison.flash1, comparison.flashdiff, diffPct * 100, comparison.ram0, comparison.ram1, comparison.ramdiff, diffRamPct * 100, comparison.command)) + totalFlash0 += comparison.flash0 + totalFlash1 += comparison.flash1 totalDiff += comparison.flashdiff totalProduct *= (1 + diffPct) + totalRam0 += comparison.ram0 + totalRam1 += comparison.ram1 + totalRamDiff += comparison.ramdiff + totalRamProduct *= (1 + diffRamPct) geomean = totalProduct ** (1.0 / float(len(comparisons))) - print('%7d %7d %6d %6.2f%% sum' % (totalCode0, totalCode1, totalDiff, geomean - 1)) + geomeanRam = totalRamProduct ** (1.0 / float(len(comparisons))) + print('%7d %7d %6d %6.2f%% %7d %7d %6d %6.2f%%' % (totalFlash0, totalFlash1, totalDiff, geomean - 1, totalRam0, totalRam1, totalRamDiff, geomeanRam - 1)) if __name__ == '__main__': 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 5be7df2d36..870faa5b75 100644 --- a/transform/allocs.go +++ b/transform/allocs.go @@ -13,14 +13,6 @@ import ( "tinygo.org/x/go-llvm" ) -// maxStackAlloc is the maximum size of an object that will be allocated on the -// stack. Bigger objects have increased risk of stack overflows and thus will -// always be heap allocated. -// -// TODO: tune this, this is just a random value. -// This value is also used in the compiler when translating ssa.Alloc nodes. -const maxStackAlloc = 256 - // OptimizeAllocs tries to replace heap allocations with stack allocations // whenever possible. It relies on the LLVM 'nocapture' flag for interprocedural // escape analysis, and within a function looks whether an allocation can escape @@ -28,7 +20,7 @@ const maxStackAlloc = 256 // If printAllocs is non-nil, it indicates the regexp of functions for which a // heap allocation explanation should be printed (why the object can't be stack // allocated). -func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(token.Position, string)) { +func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc uint64, logger func(token.Position, string)) { allocator := mod.NamedFunction("runtime.alloc") if allocator.IsNil() { // nothing to optimize @@ -37,10 +29,14 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() - i8ptrType := 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() { @@ -98,21 +94,14 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok } // 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(i8ptrType); 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 @@ -120,7 +109,7 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok fn := bitcast.InstructionParent().Parent() builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction()) allocaType := llvm.ArrayType(mod.Context().Int8Type(), int(size)) - alloca := builder.CreateAlloca(allocaType, "stackalloc.alloca") + alloca := builder.CreateAlloca(allocaType, "stackalloc") alloca.SetAlignment(alignment) // Zero the allocation inside the block where the value was originally allocated. @@ -130,8 +119,7 @@ func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(tok store.SetAlignment(alignment) // Replace heap alloc bitcast with stack alloc bitcast. - stackalloc := builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc") - bitcast.ReplaceAllUsesWith(stackalloc) + bitcast.ReplaceAllUsesWith(alloca) if heapalloc != bitcast { bitcast.EraseFromParentAsInstruction() } diff --git a/transform/allocs_test.go b/transform/allocs_test.go index 27bb9706a1..7f7ff5b75e 100644 --- a/transform/allocs_test.go +++ b/transform/allocs_test.go @@ -17,7 +17,7 @@ import ( func TestAllocs(t *testing.T) { t.Parallel() testTransform(t, "testdata/allocs", func(mod llvm.Module) { - transform.OptimizeAllocs(mod, nil, nil) + transform.OptimizeAllocs(mod, nil, 256, nil) }) } @@ -38,15 +38,16 @@ func TestAllocs2(t *testing.T) { mod := compileGoFileForTesting(t, "./testdata/allocs2.go") // Run functionattrs pass, which is necessary for escape analysis. - pm := llvm.NewPassManager() - defer pm.Dispose() - pm.AddInstructionCombiningPass() - pm.AddFunctionAttrsPass() - pm.Run(mod) + po := llvm.NewPassBuilderOptions() + defer po.Dispose() + err := mod.RunPasses("function(instcombine),function-attrs", llvm.TargetMachine{}, po) + if err != nil { + t.Error("failed to run passes:", err) + } // Run heap to stack transform. var testOutputs []allocsTestOutput - transform.OptimizeAllocs(mod, regexp.MustCompile("."), func(pos token.Position, msg string) { + transform.OptimizeAllocs(mod, regexp.MustCompile("."), 256, func(pos token.Position, msg string) { testOutputs = append(testOutputs, allocsTestOutput{ filename: filepath.Base(pos.Filename), line: pos.Line, diff --git a/transform/gc.go b/transform/gc.go index a9b5ba7491..9d1d8f3fbb 100644 --- a/transform/gc.go +++ b/transform/gc.go @@ -225,8 +225,7 @@ func MakeGCStackSlots(mod llvm.Module) bool { llvm.ConstInt(ctx.Int32Type(), 0, false), }, "") builder.CreateStore(parent, gep) - stackObjectCast := builder.CreateBitCast(stackObject, stackChainStartType, "") - builder.CreateStore(stackObjectCast, stackChainStart) + builder.CreateStore(stackObject, stackChainStart) // Do a store to the stack object after each new pointer that is created. pointerStores := make(map[llvm.Value]struct{}) diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index 11d81e4f63..7f0b6fdc57 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -92,7 +92,7 @@ type lowerInterfacesPass struct { ctx llvm.Context uintptrType llvm.Type targetData llvm.TargetData - i8ptrType llvm.Type + ptrType llvm.Type types map[string]*typeInfo signatures map[string]*signatureInfo interfaces map[string]*interfaceInfo @@ -113,7 +113,7 @@ func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error { ctx: ctx, targetData: targetData, uintptrType: mod.Context().IntType(targetData.PointerSize() * 8), - i8ptrType: llvm.PointerType(ctx.Int8Type(), 0), + ptrType: llvm.PointerType(ctx.Int8Type(), 0), types: make(map[string]*typeInfo), signatures: make(map[string]*signatureInfo), interfaces: make(map[string]*interfaceInfo), @@ -356,11 +356,9 @@ func (p *lowerInterfacesPass) run() error { } // Fallback. if hasUses(t.typecode) { - bitcast := llvm.ConstBitCast(newGlobal, p.i8ptrType) - negativeOffset := -int64(p.targetData.TypeAllocSize(p.i8ptrType)) - gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), bitcast, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "") - bitcast2 := llvm.ConstBitCast(gep, t.typecode.Type()) - t.typecode.ReplaceAllUsesWith(bitcast2) + negativeOffset := -int64(p.targetData.TypeAllocSize(p.ptrType)) + gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), newGlobal, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "") + t.typecode.ReplaceAllUsesWith(gep) } t.typecode.EraseFromParentAsGlobal() newGlobal.SetName(typecodeName) @@ -514,7 +512,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte params[i] = fn.Param(i + 1) } params = append(params, - llvm.Undef(p.i8ptrType), + llvm.Undef(p.ptrType), ) // Start chain in the entry block. @@ -554,27 +552,12 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte p.builder.SetInsertPointAtEnd(bb) receiver := fn.FirstParam() - if receiver.Type() != function.FirstParam().Type() { - // When the receiver is a pointer, it is not wrapped. This means the - // i8* has to be cast to the correct pointer type of the target - // function. - receiver = p.builder.CreateBitCast(receiver, function.FirstParam().Type(), "") - } - // Check whether the called function has the same signature as would be - // expected from the parameters. This can happen in rare cases when - // named struct types are renamed after merging multiple LLVM modules. paramTypes := []llvm.Type{receiver.Type()} for _, param := range params { paramTypes = append(paramTypes, param.Type()) } - calledFunctionType := function.Type() functionType := llvm.FunctionType(returnType, paramTypes, false) - sig := llvm.PointerType(functionType, calledFunctionType.PointerAddressSpace()) - if sig != function.Type() { - function = p.builder.CreateBitCast(function, sig, "") - } - retval := p.builder.CreateCall(functionType, function, append([]llvm.Value{receiver}, params...), "") if retval.Type().TypeKind() == llvm.VoidTypeKind { p.builder.CreateRetVoid() @@ -596,7 +579,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte // method on a nil interface. nilPanic := p.mod.NamedFunction("runtime.nilPanic") p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{ - llvm.Undef(p.i8ptrType), + llvm.Undef(p.ptrType), }, "") p.builder.CreateUnreachable() } diff --git a/transform/interface-lowering_test.go b/transform/interface-lowering_test.go index 7bcce6055c..65f14dd95e 100644 --- a/transform/interface-lowering_test.go +++ b/transform/interface-lowering_test.go @@ -15,9 +15,11 @@ func TestInterfaceLowering(t *testing.T) { t.Error(err) } - pm := llvm.NewPassManager() - defer pm.Dispose() - pm.AddGlobalDCEPass() - pm.Run(mod) + po := llvm.NewPassBuilderOptions() + defer po.Dispose() + err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po) + if err != nil { + t.Error("failed to run passes:", err) + } }) } 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/maps_test.go b/transform/maps_test.go index e8b1113374..329de698e2 100644 --- a/transform/maps_test.go +++ b/transform/maps_test.go @@ -15,10 +15,11 @@ func TestOptimizeMaps(t *testing.T) { // Run an optimization pass, to clean up the result. // This shows that all code related to the map is really eliminated. - pm := llvm.NewPassManager() - defer pm.Dispose() - pm.AddDeadStoreEliminationPass() - pm.AddAggressiveDCEPass() - pm.Run(mod) + po := llvm.NewPassBuilderOptions() + defer po.Dispose() + err := mod.RunPasses("dse,adce", llvm.TargetMachine{}, po) + if err != nil { + t.Error("failed to run passes:", err) + } }) } diff --git a/transform/optimizer.go b/transform/optimizer.go index 20258ef4fe..54f9762bc4 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -8,60 +8,29 @@ 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" ) // OptimizePackage runs optimization passes over the LLVM module for the given // Go package. func OptimizePackage(mod llvm.Module, config *compileopts.Config) { - optLevel, sizeLevel, _ := config.OptLevels() - - // Run function passes for each function in the module. - // These passes are intended to be run on each function right - // after they're created to reduce IR size (and maybe also for - // cache locality to improve performance), but for now they're - // run here for each function in turn. Maybe this can be - // improved in the future. - builder := llvm.NewPassManagerBuilder() - defer builder.Dispose() - builder.SetOptLevel(optLevel) - builder.SetSizeLevel(sizeLevel) - funcPasses := llvm.NewFunctionPassManagerForModule(mod) - defer funcPasses.Dispose() - builder.PopulateFunc(funcPasses) - funcPasses.InitializeFunc() - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - if fn.IsDeclaration() { - continue - } - funcPasses.RunFunc(fn) - } - funcPasses.FinalizeFunc() + _, speedLevel, _ := config.OptLevel() // Run TinyGo-specific optimization passes. - if optLevel > 0 { + if speedLevel > 0 { OptimizeMaps(mod) } } // Optimize runs a number of optimization and transformation passes over the // given module. Some passes are specific to TinyGo, others are generic LLVM -// passes. You can set a preferred performance (0-3) and size (0-2) level and -// control the limits of the inliner (higher numbers mean more inlining, set it -// to 0 to disable entirely). +// passes. // // Please note that some optimizations are not optional, thus Optimize must -// alwasy be run before emitting machine code. Set all controls (optLevel, -// sizeLevel, inlinerThreshold) to 0 to reduce the number of optimizations to a -// minimum. -func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel int, inlinerThreshold uint) []error { - builder := llvm.NewPassManagerBuilder() - defer builder.Dispose() - builder.SetOptLevel(optLevel) - builder.SetSizeLevel(sizeLevel) - if inlinerThreshold != 0 { - builder.UseInlinerWithThreshold(inlinerThreshold) - } +// always be run before emitting machine code. +func Optimize(mod llvm.Module, config *compileopts.Config) []error { + optLevel, speedLevel, _ := config.OptLevel() // Make sure these functions are kept in tact during TinyGo transformation passes. for _, name := range functionsUsedInTransforms { @@ -72,10 +41,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i 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) @@ -84,23 +49,26 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i } } - if optLevel > 0 { + if speedLevel > 0 { // Run some preparatory passes for the Go optimizer. - goPasses := llvm.NewPassManager() - defer goPasses.Dispose() - goPasses.AddGlobalDCEPass() - goPasses.AddGlobalOptimizerPass() - goPasses.AddIPSCCPPass() - goPasses.AddInstructionCombiningPass() // necessary for OptimizeReflectImplements - goPasses.AddAggressiveDCEPass() - goPasses.AddFunctionAttrsPass() - goPasses.Run(mod) + po := llvm.NewPassBuilderOptions() + defer po.Dispose() + 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)} + } // Run TinyGo-specific optimization passes. OptimizeStringToBytes(mod) OptimizeReflectImplements(mod) - OptimizeAllocs(mod, nil, nil) - err := LowerInterfaces(mod, config) + maxStackSize := config.MaxStackAlloc() + OptimizeAllocs(mod, nil, maxStackSize, nil) + err = LowerInterfaces(mod, config) if err != nil { return []error{err} } @@ -113,10 +81,13 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i // After interfaces are lowered, there are many more opportunities for // interprocedural optimizations. To get them to work, function // attributes have to be updated first. - goPasses.Run(mod) + err = mod.RunPasses(optPasses, llvm.TargetMachine{}, po) + if err != nil { + return []error{fmt.Errorf("could not build pass pipeline: %w", err)} + } // Run TinyGo-specific interprocedural optimizations. - OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) { + OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, msg string) { fmt.Fprintln(os.Stderr, pos.String()+": "+msg) }) OptimizeStringToBytes(mod) @@ -134,10 +105,12 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i } // Clean up some leftover symbols of the previous transformations. - goPasses := llvm.NewPassManager() - defer goPasses.Dispose() - goPasses.AddGlobalDCEPass() - goPasses.Run(mod) + po := llvm.NewPassBuilderOptions() + defer po.Dispose() + err = mod.RunPasses("globaldce", llvm.TargetMachine{}, po) + if err != nil { + return []error{fmt.Errorf("could not build pass pipeline: %w", err)} + } } if config.Scheduler() == "none" { @@ -169,23 +142,17 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i fn.SetLinkage(llvm.InternalLinkage) } - // Run function passes again, because without it, llvm.coro.size.i32() - // doesn't get lowered. - funcPasses := llvm.NewFunctionPassManagerForModule(mod) - defer funcPasses.Dispose() - builder.PopulateFunc(funcPasses) - funcPasses.InitializeFunc() - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - funcPasses.RunFunc(fn) + // 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("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)} } - funcPasses.FinalizeFunc() - - // Run module passes. - // TODO: somehow set the PrepareForThinLTO flag in the pass manager builder. - modPasses := llvm.NewPassManager() - defer modPasses.Dispose() - builder.Populate(modPasses) - modPasses.Run(mod) hasGCPass := MakeGCStackSlots(mod) if hasGCPass { 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 36d2853b6c..3abc1d3952 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -28,32 +28,44 @@ func OptimizeStringToBytes(mod llvm.Module) { // strptr is always constant because strings are always constant. - convertedAllUses := true + var pointerUses []llvm.Value + canConvertPointer := true for _, use := range getUses(call) { if use.IsAExtractValueInst().IsNil() { // Expected an extractvalue, but this is something else. - convertedAllUses = false - continue + canConvertPointer = false + break } switch use.Type().TypeKind() { case llvm.IntegerTypeKind: // A length (len or cap). Propagate the length value. + // This can always be done because the byte slice is always the + // same length as the original string. use.ReplaceAllUsesWith(strlen) use.EraseFromParentAsInstruction() case llvm.PointerTypeKind: // The string pointer itself. if !isReadOnly(use) { - convertedAllUses = false - continue + // There is a store to the byte slice. This means that none + // of the pointer uses can't be propagated. + canConvertPointer = false + break } - use.ReplaceAllUsesWith(strptr) - use.EraseFromParentAsInstruction() + // It may be that the pointer value can be propagated, if all of + // the pointer uses are readonly. + pointerUses = append(pointerUses, use) default: // should not happen panic("unknown return type of runtime.stringToBytes: " + use.Type().String()) } } - if convertedAllUses { + if canConvertPointer { + // All pointer uses are readonly, so they can be converted. + for _, use := range pointerUses { + use.ReplaceAllUsesWith(strptr) + use.EraseFromParentAsInstruction() + } + // Call to runtime.stringToBytes can be eliminated: both the input // and the output is constant. call.EraseFromParentAsInstruction() @@ -105,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 } @@ -120,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 d1b07e6c42..e4a5e4f7e9 100644 --- a/transform/testdata/allocs.out.ll +++ b/transform/testdata/allocs.out.ll @@ -6,54 +6,73 @@ target triple = "armv7m-none-eabi" declare nonnull ptr @runtime.alloc(i32, ptr) define void @testInt() { - %stackalloc.alloca = alloca [4 x i8], align 4 - store [4 x i8] zeroinitializer, ptr %stackalloc.alloca, align 4 - store i32 5, ptr %stackalloc.alloca, align 4 + %stackalloc = alloca [4 x i8], align 4 + store [4 x i8] zeroinitializer, ptr %stackalloc, align 4 + store i32 5, ptr %stackalloc, align 4 ret void } define i16 @testArray() { - %stackalloc.alloca = alloca [6 x i8], align 2 - store [6 x i8] zeroinitializer, ptr %stackalloc.alloca, align 2 - %alloc.1 = getelementptr i16, ptr %stackalloc.alloca, i32 1 + %stackalloc = alloca [6 x i8], align 2 + store [6 x i8] zeroinitializer, ptr %stackalloc, align 2 + %alloc.1 = getelementptr i16, ptr %stackalloc, i32 1 store i16 5, ptr %alloc.1, align 2 - %alloc.2 = getelementptr i16, ptr %stackalloc.alloca, i32 2 + %alloc.2 = getelementptr i16, ptr %stackalloc, i32 2 %val = load i16, ptr %alloc.2, align 2 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 } define void @testNonEscapingCall() { - %stackalloc.alloca = alloca [4 x i8], align 4 - store [4 x i8] zeroinitializer, ptr %stackalloc.alloca, align 4 - %val = call ptr @noescapeIntPtr(ptr %stackalloc.alloca) + %stackalloc = alloca [4 x i8], align 4 + store [4 x i8] zeroinitializer, ptr %stackalloc, align 4 + %val = call ptr @noescapeIntPtr(ptr %stackalloc) ret void } 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 } define void @testNonEscapingLoop() { entry: - %stackalloc.alloca = alloca [4 x i8], align 4 + %stackalloc = alloca [4 x i8], align 4 br label %loop loop: ; preds = %loop, %entry - store [4 x i8] zeroinitializer, ptr %stackalloc.alloca, align 4 - %ptr = call ptr @noescapeIntPtr(ptr %stackalloc.alloca) + store [4 x i8] zeroinitializer, ptr %stackalloc, align 4 + %ptr = call ptr @noescapeIntPtr(ptr %stackalloc) %result = icmp eq ptr null, %ptr br i1 %result, label %loop, label %end 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 fa43f3d02f..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 } @@ -30,3 +38,19 @@ entry: call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3) ret void } + +; Test that pointer values are never propagated if there is even a single write +; to the pointer value (but len/cap values still can be). +define void @testReadSome() { +entry: + %s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6) + %s.ptr = extractvalue { ptr, i64, i64 } %s, 0 + %s.len = extractvalue { ptr, i64, i64 } %s, 1 + %s.cap = extractvalue { ptr, i64, i64 } %s, 2 + call fastcc void @writeToSlice(ptr %s.ptr, i64 %s.len, i64 %s.cap) + %s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0 + %s.len2 = extractvalue { ptr, i64, i64 } %s, 1 + %s.cap2 = extractvalue { ptr, i64, i64 } %s, 2 + call fastcc void @printSlice(ptr %s.ptr2, i64 %s.len2, i64 %s.cap2) + ret void +} diff --git a/transform/testdata/stringtobytes.out.ll b/transform/testdata/stringtobytes.out.ll index 30aa520ad1..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 } @@ -22,3 +27,13 @@ entry: call fastcc void @writeToSlice(ptr %1, i64 6, i64 6) ret void } + +define void @testReadSome() { +entry: + %s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6) + %s.ptr = extractvalue { ptr, i64, i64 } %s, 0 + call fastcc void @writeToSlice(ptr %s.ptr, i64 6, i64 6) + %s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0 + call fastcc void @printSlice(ptr %s.ptr2, i64 6, i64 6) + ret void +} diff --git a/transform/transform.go b/transform/transform.go index ab08317e17..429cbd5f30 100644 --- a/transform/transform.go +++ b/transform/transform.go @@ -22,7 +22,7 @@ import ( // the -opt= compiler flag. func AddStandardAttributes(fn llvm.Value, config *compileopts.Config) { ctx := fn.Type().Context() - _, sizeLevel, _ := config.OptLevels() + _, _, sizeLevel := config.OptLevel() if sizeLevel >= 1 { fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0)) } diff --git a/transform/transform_test.go b/transform/transform_test.go index 4cab255e61..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 { @@ -145,7 +146,7 @@ func compileGoFileForTesting(t *testing.T, filename string) llvm.Module { defer machine.Dispose() // Load entire program AST into memory. - lprogram, err := loader.Load(config, filename, config.ClangHeaders, types.Config{ + lprogram, err := loader.Load(config, filename, types.Config{ Sizes: compiler.Sizes(machine), }) 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 }