diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 3151ac5637..487c22ff3e 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -27,7 +27,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -108,7 +108,7 @@ jobs: # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v2 with: - name: release-double-zipped + name: darwin-amd64-double-zipped path: build/tinygo.darwin-amd64.tar.gz - name: Smoke tests shell: bash @@ -126,7 +126,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Build TinyGo run: go install diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index bd020b77e0..c5745137ea 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -18,7 +18,7 @@ jobs: # statically linked binary. runs-on: ubuntu-latest container: - image: golang:1.20-alpine + image: golang:1.21-alpine steps: - name: Install apk dependencies # tar: needed for actions/cache@v3 @@ -135,7 +135,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Install wasmtime run: | @@ -153,6 +153,7 @@ jobs: tar -C ~/lib -xf tinygo.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 smoketest assert-test-linux: # Run all tests that can run on Linux, with LLVM assertions enabled to catch @@ -177,12 +178,12 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Install Node.js uses: actions/setup-node@v3 with: - node-version: '14' + node-version: '16' - name: Install wasmtime run: | mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin @@ -290,7 +291,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 @@ -407,7 +408,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Restore LLVM source cache uses: actions/cache/restore@v3 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index fd46e8d874..4c4b59fcfe 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -35,7 +35,7 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Restore cached LLVM source uses: actions/cache/restore@v3 @@ -118,7 +118,7 @@ jobs: # We're doing the former here, to keep artifact uploads fast. uses: actions/upload-artifact@v3 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/release/release.zip smoke-test-windows: @@ -143,12 +143,12 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/ - name: Unzip TinyGo build shell: bash @@ -173,12 +173,12 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/ - name: Unzip TinyGo build shell: bash @@ -209,12 +209,12 @@ jobs: - name: Install Go uses: actions/setup-go@v3 with: - go-version: '1.20' + go-version: '1.21' cache: true - name: Download TinyGo build uses: actions/download-artifact@v2 with: - name: release-double-zipped + name: windows-amd64-double-zipped path: build/ - name: Unzip TinyGo build shell: bash diff --git a/.gitmodules b/.gitmodules index 35e6870c08..857acaf46d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 48016fd04b..2474a44a6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,74 @@ +0.29.0 +--- + +* **general** + - Go 1.21 support + - use https for renesas submodule #3856 + - ci: rename release-double-zipped to something more useful + - ci: update Node.js from version 14 to version 16 + - ci: switch GH actions builds to use Go 1.21 final release + - docker: update clang to version 15 + - docker: use Go 1.21 for Docker dev container build + - `main`: add target JSON file in `tinygo info` output + - `main`: improve detection of filesystems + - `main`: use `go env` instead of doing all detection manually + - make: add make task to generate Renesas device wrappers + - make: add task to check NodeJS version before running tests + - add submodule for Renesas SVD file mirror repo + - update to go-serial package v1.6.0 + - `testing`: add Testing function + - `tools/gen-device-svd`: small changes needed for Renesas MCUs +* **compiler** + - `builder`: update message for max supported Go version + - `compiler,reflect`: NumMethods reports exported methods only + - `compiler`: add compiler-rt and wasm symbols to table + - `compiler`: add compiler-rt to wasm.json + - `compiler`: add min and max builtin support + - `compiler`: implement clear builtin for maps + - `compiler`: implement clear builtin for slices + - `compiler`: improve panic message when a runtime call is unavailable + - `compiler`: update .ll test output + - `loader`: merge go.env file which is now required starting in Go 1.21 to correctly get required packages +* **standard library** + - `os`: define ErrNoDeadline + - `reflect`: Add FieldByNameFunc + - `reflect`: add SetZero + - `reflect`: fix iterating over maps with interface{} keys + - `reflect`: implement Value.Grow + - `reflect`: remove unecessary heap allocations + - `reflect`: use .key() instead of a type assert + - `sync`: add implementation from upstream Go for OnceFunc, OnceValue, and OnceValues +* **targets** + - `machine`: UART refactor (#3832) + - `machine/avr`: pin change interrupt + - `machine/macropad_rp2040`: add machine.BUTTON + - `machine/nrf`: add I2C timeout + - `machine/nrf`: wait for stop condition after reading from the I2C bus + - `machine/nRF52`: set SPI TX/RX lengths even data is empty. Fixes #3868 (#3877) + - `machine/rp2040`: add missing suffix to CMD_READ_STATUS + - `machine/rp2040`: add NoPin support + - `machine/rp2040`: move flash related functions into separate file from C imports for correct - LSP. Fixes #3852 + - `machine/rp2040`: wait for 1000 us after flash reset to avoid issues with busy USB bus + - `machine/samd51,rp2040,nrf528xx,stm32`: implement watchdog + - `machine/samd51`: fix i2cTimeout was decreasing due to cache activation + - `machine/usb`: Add support for HID Keyboard LEDs + - `machine/usb`: allow USB Endpoint settings to be changed externally + - `machine/usb`: refactor endpoint configuration + - `machine/usb`: remove usbDescriptorConfig + - `machine/usb/hid,joystick`: fix hidreport (3) (#3802) + - `machine/usb/hid`: add RxHandler interface + - `machine/usb/hid`: rename Handler() to TxHandler() + - `wasi`: allow zero inodes when reading directories + - `wasm`: add support for GOOS=wasip1 + - `wasm`: fix functions exported through //export + - `wasm`: remove i64 workaround, use BigInt instead + - `example`: adjust time offset + - `example`: simplify pininterrupt +* **boards** + - `targets`: add AKIZUKI DENSHI AE-RP2040 + - `targets`: adding new uf2 target for PCA10056 (#3765) + + 0.28.0 --- diff --git a/Dockerfile b/Dockerfile index e4c2e67f92..ad3b586d83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ # tinygo-llvm stage obtains the llvm source for TinyGo -FROM golang:1.20 AS tinygo-llvm +FROM golang:1.21 AS tinygo-llvm RUN apt-get update && \ - apt-get install -y apt-utils make cmake clang-11 ninja-build + apt-get install -y apt-utils make cmake clang-15 ninja-build COPY ./Makefile /tinygo/Makefile diff --git a/Makefile b/Makefile index 8ee56699f7..f213713d42 100644 --- a/Makefile +++ b/Makefile @@ -232,6 +232,10 @@ gen-device-rp: build/gen-device-svd ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/ 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/ + GO111MODULE=off $(GO) fmt ./src/device/renesas + # Get LLVM sources. $(LLVM_PROJECTDIR)/llvm: git clone -b xtensa_release_15.x --depth=1 https://github.com/espressif/llvm-project $(LLVM_PROJECTDIR) @@ -263,12 +267,22 @@ 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) +# Check for Node.js used during WASM tests. +NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-))) +MIN_NODEJS_VERSION=16 + +.PHONY: check-nodejs-version +check-nodejs-version: +ifeq (, $(shell which node)) + @echo "Install NodeJS version 16+ 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 # 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`" . -test: wasi-libc +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 . # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi @@ -413,8 +427,12 @@ tinygo-bench-fast: # Same thing, except for wasi rather than the current platform. tinygo-test-wasi: $(TINYGO) test -target wasi $(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: @@ -478,12 +496,16 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/test @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pca10040 examples/time-offset + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-mouse @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/i2c-target @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog + @$(MD5SUM) test.hex # test simulated boards on play.tinygo.org ifneq ($(WASM), 0) $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 @@ -642,6 +664,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=gopher-badge examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=ae-rp2040 examples/echo + @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @$(MD5SUM) test.hex diff --git a/builder/ar.go b/builder/ar.go index c3aad171eb..3f1c8c213f 100644 --- a/builder/ar.go +++ b/builder/ar.go @@ -12,6 +12,7 @@ import ( "path/filepath" "time" + wasm "github.com/aykevl/go-wasm" "github.com/blakesmith/ar" ) @@ -74,8 +75,25 @@ func makeArchive(arfile *os.File, objs []string) error { fileIndex int }{symbol.Name, i}) } + } 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 + continue + } + symbolTable = append(symbolTable, struct { + name string + fileIndex int + }{ln.Field, i}) + } + } + } } else { - return fmt.Errorf("failed to open file %s as ELF or PE/COFF: %w", objpath, err) + return fmt.Errorf("failed to open file %s as WASM, ELF or PE/COFF: %w", objpath, err) } // Close file, to avoid issues with too many open files (especially on diff --git a/builder/build.go b/builder/build.go index 516b9e64f7..85114d9d72 100644 --- a/builder/build.go +++ b/builder/build.go @@ -217,19 +217,27 @@ 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 == nil { - config.Options.GlobalValues = make(map[string]map[string]string) - } 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() { @@ -1054,17 +1062,6 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error { return err } - // Browsers cannot handle external functions that have type i64 because it - // cannot be represented exactly in JavaScript (JS only has doubles). To - // keep functions interoperable, pass int64 types as pointers to - // stack-allocated values. - if config.WasmAbi() == "js" { - err := transform.ExternalInt64AsPtr(mod, config) - if err != nil { - return err - } - } - // Optimization levels here are roughly the same as Clang, but probably not // exactly. optLevel, sizeLevel, inlinerThreshold := config.OptLevels() diff --git a/builder/builder_test.go b/builder/builder_test.go index f8bdd51341..03a33b9d49 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -60,6 +60,7 @@ func TestClangAttributes(t *testing.T) { {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" { diff --git a/builder/config.go b/builder/config.go index f9fe715b76..d5c8166c99 100644 --- a/builder/config.go +++ b/builder/config.go @@ -1,7 +1,6 @@ package builder import ( - "errors" "fmt" "github.com/tinygo-org/tinygo/compileopts" @@ -24,19 +23,14 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { spec.OpenOCDCommands = options.OpenOCDCommands } - goroot := goenv.Get("GOROOT") - if goroot == "" { - return nil, errors.New("cannot locate $GOROOT, please set it manually") - } - - major, minor, err := goenv.GetGorootVersion(goroot) + major, minor, err := goenv.GetGorootVersion() if err != nil { - return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) + return nil, err } - if major != 1 || minor < 18 || minor > 20 { + if major != 1 || minor < 18 || minor > 21 { // 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.20, got go%d.%d", major, minor) + return nil, fmt.Errorf("requires go version 1.18 through 1.21, got go%d.%d", major, minor) } clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT")) diff --git a/builder/sizes_test.go b/builder/sizes_test.go index d640fa21f5..a97fc01c33 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -43,7 +43,7 @@ func TestBinarySize(t *testing.T) { // microcontrollers {"hifive1b", "examples/echo", 4612, 280, 0, 2252}, {"microbit", "examples/serial", 2724, 388, 8, 2256}, - {"wioterminal", "examples/pininterrupt", 6159, 1485, 116, 6816}, + {"wioterminal", "examples/pininterrupt", 6039, 1485, 116, 6816}, // TODO: also check wasm. Right now this is difficult, because // wasm binaries are run through wasm-opt and therefore the diff --git a/compileopts/config.go b/compileopts/config.go index 9a4bc31069..39fc4f2ac2 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -492,11 +492,6 @@ func (c *Config) RelocationModel() string { return "static" } -// WasmAbi returns the WASM ABI which is specified in the target JSON file. -func (c *Config) WasmAbi() string { - return c.Target.WasmAbi -} - // EmulatorName is a shorthand to get the command for this emulator, something // like qemu-system-arm or simavr. func (c *Config) EmulatorName() string { diff --git a/compileopts/target.go b/compileopts/target.go index 40a1d445c7..f83464b274 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -23,46 +23,45 @@ import ( // https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html // https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json type TargetSpec struct { - Inherits []string `json:"inherits"` - Triple string `json:"llvm-target"` - CPU string `json:"cpu"` - ABI string `json:"target-abi"` // rougly equivalent to -mabi= flag - Features string `json:"features"` - GOOS string `json:"goos"` - GOARCH string `json:"goarch"` - BuildTags []string `json:"build-tags"` - GC string `json:"gc"` - Scheduler string `json:"scheduler"` - Serial string `json:"serial"` // which serial output to use (uart, usb, none) - Linker string `json:"linker"` - RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt) - Libc string `json:"libc"` - AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time. - DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time. - CFlags []string `json:"cflags"` - LDFlags []string `json:"ldflags"` - LinkerScript string `json:"linkerscript"` - ExtraFiles []string `json:"extra-files"` - RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum - Emulator string `json:"emulator"` - FlashCommand string `json:"flash-command"` - GDB []string `json:"gdb"` - PortReset string `json:"flash-1200-bps-reset"` - SerialPort []string `json:"serial-port"` // serial port IDs in the form "vid:pid" - FlashMethod string `json:"flash-method"` - FlashVolume []string `json:"msd-volume-name"` - FlashFilename string `json:"msd-firmware-name"` - UF2FamilyID string `json:"uf2-family-id"` - BinaryFormat string `json:"binary-format"` - OpenOCDInterface string `json:"openocd-interface"` - OpenOCDTarget string `json:"openocd-target"` - OpenOCDTransport string `json:"openocd-transport"` - OpenOCDCommands []string `json:"openocd-commands"` - OpenOCDVerify *bool `json:"openocd-verify"` // enable verify when flashing with openocd - JLinkDevice string `json:"jlink-device"` - CodeModel string `json:"code-model"` - RelocationModel string `json:"relocation-model"` - WasmAbi string `json:"wasm-abi"` + 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 + Features string `json:"features,omitempty"` + GOOS string `json:"goos,omitempty"` + GOARCH string `json:"goarch,omitempty"` + BuildTags []string `json:"build-tags,omitempty"` + GC string `json:"gc,omitempty"` + Scheduler string `json:"scheduler,omitempty"` + Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) + Linker string `json:"linker,omitempty"` + RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) + Libc string `json:"libc,omitempty"` + AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. + DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time. + CFlags []string `json:"cflags,omitempty"` + LDFlags []string `json:"ldflags,omitempty"` + LinkerScript string `json:"linkerscript,omitempty"` + ExtraFiles []string `json:"extra-files,omitempty"` + RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum + Emulator string `json:"emulator,omitempty"` + FlashCommand string `json:"flash-command,omitempty"` + GDB []string `json:"gdb,omitempty"` + PortReset string `json:"flash-1200-bps-reset,omitempty"` + SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid" + FlashMethod string `json:"flash-method,omitempty"` + FlashVolume []string `json:"msd-volume-name,omitempty"` + FlashFilename string `json:"msd-firmware-name,omitempty"` + UF2FamilyID string `json:"uf2-family-id,omitempty"` + BinaryFormat string `json:"binary-format,omitempty"` + OpenOCDInterface string `json:"openocd-interface,omitempty"` + OpenOCDTarget string `json:"openocd-target,omitempty"` + OpenOCDTransport string `json:"openocd-transport,omitempty"` + OpenOCDCommands []string `json:"openocd-commands,omitempty"` + OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd + JLinkDevice string `json:"jlink-device,omitempty"` + CodeModel string `json:"code-model,omitempty"` + RelocationModel string `json:"relocation-model,omitempty"` } // overrideProperties overrides all properties that are set in child into itself using reflection. @@ -192,12 +191,15 @@ func LoadTarget(options *Options) (*TargetSpec, error) { 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 - if llvmos == "darwin" { + switch llvmos { + case "darwin": // Use macosx* instead of darwin, otherwise darwin/arm64 will refer // to iOS! llvmos = "macosx10.12.0" @@ -208,6 +210,8 @@ func LoadTarget(options *Options) (*TargetSpec, error) { 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: @@ -278,6 +282,15 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) { case "arm64": spec.CPU = "generic" spec.Features = "+neon" + case "wasm": + spec.CPU = "generic" + spec.Features = "+bulk-memory,+nontrapping-fptoint,+sign-ext" + spec.BuildTags = append(spec.BuildTags, "tinygo.wasm") + spec.CFlags = append(spec.CFlags, + "-mbulk-memory", + "-mnontrapping-fptoint", + "-msign-ext", + ) } if goos == "darwin" { spec.Linker = "ld.lld" @@ -321,6 +334,22 @@ 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 * 16 // 16kB + 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 } diff --git a/compiler/calls.go b/compiler/calls.go index 65a69fea3c..a110addcf6 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -36,7 +36,11 @@ const ( // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeInvoke instead. func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value { - fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function) + member := b.program.ImportedPackage("runtime").Members[fnName] + if member == nil { + panic("unknown runtime call: " + fnName) + } + fn := member.(*ssa.Function) fnType, llvmFn := b.getFunction(fn) if llvmFn.IsNil() { panic("trying to call non-existent function: " + fn.RelString(nil)) diff --git a/compiler/compiler.go b/compiler/compiler.go index 09f11e614f..6dd43935a6 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1600,6 +1600,45 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil + case "clear": + value := argValues[0] + switch typ := argTypes[0].Underlying().(type) { + case *types.Slice: + elementType := b.getLLVMType(typ.Elem()) + elementSize := b.targetData.TypeAllocSize(elementType) + elementAlign := b.targetData.ABITypeAlignment(elementType) + + // 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") + llvmLen = b.CreateMul(llvmLen, llvm.ConstInt(llvmLen.Type(), elementSize, false), "") + + // Do the clear operation using the LLVM memset builtin. + // This is also correct for nil slices: in those cases, len will be + // 0 which means the memset call is a no-op (according to the LLVM + // LangRef). + memset := b.getMemsetFunc() + call := b.createCall(memset.GlobalValueType(), memset, []llvm.Value{ + llvmBuf, // dest + llvm.ConstInt(b.ctx.Int8Type(), 0, false), // val + llvmLen, // len + llvm.ConstInt(b.ctx.Int1Type(), 0, false), // isVolatile + }, "") + call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign))) + + return llvm.Value{}, nil + case *types.Map: + m := argValues[0] + b.createMapClear(m) + return llvm.Value{}, nil + default: + return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String()) + } case "copy": dst := argValues[0] src := argValues[1] @@ -1637,6 +1676,24 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c llvmLen = b.CreateZExt(llvmLen, b.intType, "len.int") } return llvmLen, nil + case "min", "max": + // min and max builtins, added in Go 1.21. + // We can simply reuse the existing binop comparison code, which has all + // the edge cases figured out already. + tok := token.LSS + if callName == "max" { + tok = token.GTR + } + result := argValues[0] + typ := argTypes[0] + for _, arg := range argValues[1:] { + cmp, err := b.createBinOp(tok, typ, typ, result, arg, pos) + if err != nil { + return result, err + } + result = b.CreateSelect(cmp, result, arg, "") + } + return result, nil case "print", "println": for i, value := range argValues { if i >= 1 && callName == "println" { diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 6b5fbe13cb..92ce31b012 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -29,7 +29,7 @@ func TestCompiler(t *testing.T) { t.Parallel() // Determine Go minor version (e.g. 16 in go1.16.3). - _, goMinor, err := goenv.GetGorootVersion(goenv.Get("GOROOT")) + _, goMinor, err := goenv.GetGorootVersion() if err != nil { t.Fatal("could not read Go version:", err) } @@ -54,6 +54,9 @@ func TestCompiler(t *testing.T) { if goMinor >= 20 { tests = append(tests, testCase{"go1.20.go", "", ""}) } + if goMinor >= 21 { + tests = append(tests, testCase{"go1.21.go", "", ""}) + } for _, tc := range tests { name := tc.file diff --git a/compiler/interface.go b/compiler/interface.go index da99821489..81234b0c9a 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -128,6 +128,15 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { hasMethodSet = false } + var numMethods int + if hasMethodSet { + for i := 0; i < ms.Len(); i++ { + if ms.At(i).Obj().Exported() { + numMethods++ + } + } + } + // Short-circuit all the global pointer logic here for pointers to pointers. if typ, ok := typ.(*types.Pointer); ok { if _, ok := typ.Elem().(*types.Pointer); ok { @@ -277,11 +286,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { } pkgPathPtr := c.pkgPathPtr(pkgpath) typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods - c.getTypeCode(types.NewPointer(typ)), // ptrTo - c.getTypeCode(typ.Underlying()), // underlying - pkgPathPtr, // pkgpath pointer - c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name + llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods + c.getTypeCode(types.NewPointer(typ)), // ptrTo + c.getTypeCode(typ.Underlying()), // underlying + pkgPathPtr, // pkgpath pointer + c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name } metabyte |= 1 << 5 // "named" flag case *types.Chan: @@ -308,7 +317,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { } case *types.Pointer: typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods + llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods c.getTypeCode(typ.Elem()), } case *types.Array: @@ -337,8 +346,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { llvmStructType := c.getLLVMType(typ) size := c.targetData.TypeStoreSize(llvmStructType) typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(ms.Len()), false), // numMethods - c.getTypeCode(types.NewPointer(typ)), // ptrTo + llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods + c.getTypeCode(types.NewPointer(typ)), // ptrTo pkgPathPtr, llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index 5761a438f1..af3a57de1b 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -70,15 +70,7 @@ func (b *builder) createMemoryCopyImpl() { // regular libc memset calls if they aren't optimized out in a different way. func (b *builder) createMemoryZeroImpl() { b.createFunctionStart(true) - fnName := "llvm.memset.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - if llvmutil.Major() < 15 { // compatibility with LLVM 14 - fnName = "llvm.memset.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.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false) - llvmFn = llvm.AddFunction(b.mod, fnName, fnType) - } + llvmFn := b.getMemsetFunc() params := []llvm.Value{ b.getValue(b.fn.Params[0], getPos(b.fn)), llvm.ConstInt(b.ctx.Int8Type(), 0, false), @@ -89,6 +81,20 @@ func (b *builder) createMemoryZeroImpl() { b.CreateRetVoid() } +// 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) + llvmFn = llvm.AddFunction(c.mod, fnName, fnType) + } + return llvmFn +} + // createKeepAlive creates the runtime.KeepAlive function. It is implemented // using inline assembly. func (b *builder) createKeepAliveImpl() { diff --git a/compiler/map.go b/compiler/map.go index c3d4289023..21f0ee4a67 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -185,6 +185,11 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok } } +// Clear the given map. +func (b *builder) createMapClear(m llvm.Value) { + b.createRuntimeCall("hashmapClear", []llvm.Value{m}, "") +} + // createMapIteratorNext lowers the *ssa.Next instruction for iterating over a // map. It returns a tuple of {bool, key, value} with the result of the // iteration. diff --git a/compiler/symbol.go b/compiler/symbol.go index 6426604ec1..7e712fbae8 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -23,9 +23,9 @@ import ( // The linkName value contains a valid link name, even if //go:linkname is not // present. type functionInfo struct { - module string // go:wasm-module - importName string // go:linkname, go:export - The name the developer assigns - linkName string // go:linkname, go:export - The name that we map for the particular module -> importName + 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 @@ -194,20 +194,14 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) // External/exported functions may not retain pointer values. // https://golang.org/cmd/cgo/#hdr-Passing_pointers if info.exported { - if c.archFamily() == "wasm32" { + if c.archFamily() == "wasm32" && len(fn.Blocks) == 0 { // We need to add the wasm-import-module and the wasm-import-name // attributes. - module := info.module - if module == "" { - module = "env" + if info.wasmModule != "" { + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", info.wasmModule)) } - llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", module)) - name := info.importName - if name == "" { - name = info.linkName - } - llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", name)) + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName)) } nocaptureKind := llvm.AttributeKindID("nocapture") nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) @@ -260,10 +254,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { return } if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil { - - // Our importName for a wasm module (if we are compiling to wasm), or llvm link name - var importName string - for _, comment := range decl.Doc.List { text := comment.Text if strings.HasPrefix(text, "//export ") { @@ -281,7 +271,8 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { continue } - importName = parts[1] + info.linkName = parts[1] + info.wasmName = info.linkName info.exported = true case "//go:interrupt": if hasUnsafeImport(f.Pkg.Pkg) { @@ -289,10 +280,11 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } case "//go:wasm-module": // Alternative comment for setting the import module. + // This is deprecated, use //go:wasmimport instead. if len(parts) != 2 { continue } - info.module = parts[1] + 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 @@ -302,8 +294,8 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } c.checkWasmImport(f, comment.Text) info.exported = true - info.module = parts[1] - info.importName = parts[2] + info.wasmModule = parts[1] + info.wasmName = parts[2] case "//go:inline": info.inline = inlineHint case "//go:noinline": @@ -347,17 +339,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { } } } - - // Set the importName for our exported function if we have one - if importName != "" { - if info.module == "" { - info.linkName = importName - } else { - // WebAssembly import - info.importName = importName - } - } - } } @@ -367,7 +348,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { // 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" { + if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" { // The runtime is a special case. Allow all kinds of parameters // (importantly, including pointers). return diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go new file mode 100644 index 0000000000..589486d024 --- /dev/null +++ b/compiler/testdata/go1.21.go @@ -0,0 +1,65 @@ +package main + +func min1(a int) int { + return min(a) +} + +func min2(a, b int) int { + return min(a, b) +} + +func min3(a, b, c int) int { + return min(a, b, c) +} + +func min4(a, b, c, d int) int { + return min(a, b, c, d) +} + +func minUint8(a, b uint8) uint8 { + return min(a, b) +} + +func minUnsigned(a, b uint) uint { + return min(a, b) +} + +func minFloat32(a, b float32) float32 { + return min(a, b) +} + +func minFloat64(a, b float64) float64 { + return min(a, b) +} + +func minString(a, b string) string { + return min(a, b) +} + +func maxInt(a, b int) int { + return max(a, b) +} + +func maxUint(a, b uint) uint { + return max(a, b) +} + +func maxFloat32(a, b float32) float32 { + return max(a, b) +} + +func maxString(a, b string) string { + return max(a, b) +} + +func clearSlice(s []int) { + clear(s) +} + +func clearZeroSizedSlice(s []struct{}) { + clear(s) +} + +func clearMap(m map[string]int) { + clear(m) +} diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll new file mode 100644 index 0000000000..d65c75f4f0 --- /dev/null +++ b/compiler/testdata/go1.21.ll @@ -0,0 +1,179 @@ +; ModuleID = 'go1.21.go' +source_filename = "go1.21.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 } + +; Function Attrs: allockind("alloc,zeroed") allocsize(0) +declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 + +declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 + +; Function Attrs: nounwind +define hidden void @main.init(ptr %context) unnamed_addr #2 { +entry: + ret void +} + +; Function Attrs: nounwind +define hidden i32 @main.min1(i32 %a, ptr %context) unnamed_addr #2 { +entry: + ret i32 %a +} + +; Function Attrs: nounwind +define hidden i32 @main.min2(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.min3(i32 %a, i32 %b, i32 %c, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) + ret i32 %1 +} + +; Function Attrs: nounwind +define hidden i32 @main.min4(i32 %a, i32 %b, i32 %c, i32 %d, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smin.i32(i32 %a, i32 %b) + %1 = call i32 @llvm.smin.i32(i32 %0, i32 %c) + %2 = call i32 @llvm.smin.i32(i32 %1, i32 %d) + ret i32 %2 +} + +; Function Attrs: nounwind +define hidden i8 @main.minUint8(i8 %a, i8 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i8 @llvm.umin.i8(i8 %a, i8 %b) + ret i8 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.minUnsigned(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.umin.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden float @main.minFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp olt float %a, %b + %1 = select i1 %0, float %a, float %b + ret float %1 +} + +; Function Attrs: nounwind +define hidden double @main.minFloat64(double %a, double %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp olt double %a, %b + %1 = select i1 %0, double %a, double %b + ret double %1 +} + +; Function Attrs: nounwind +define hidden %runtime._string @main.minString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +entry: + %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 + %1 = insertvalue %runtime._string %0, i32 %a.len, 1 + %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 + %3 = insertvalue %runtime._string %2, i32 %b.len, 1 + %stackalloc = alloca i8, align 1 + %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #5 + %5 = select i1 %4, %runtime._string %1, %runtime._string %3 + %6 = extractvalue %runtime._string %5, 0 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 + ret %runtime._string %5 +} + +declare i1 @runtime.stringLess(ptr, i32, ptr, i32, ptr) #1 + +; Function Attrs: nounwind +define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.smax.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden i32 @main.maxUint(i32 %a, i32 %b, ptr %context) unnamed_addr #2 { +entry: + %0 = call i32 @llvm.umax.i32(i32 %a, i32 %b) + ret i32 %0 +} + +; Function Attrs: nounwind +define hidden float @main.maxFloat32(float %a, float %b, ptr %context) unnamed_addr #2 { +entry: + %0 = fcmp ogt float %a, %b + %1 = select i1 %0, float %a, float %b + ret float %1 +} + +; Function Attrs: nounwind +define hidden %runtime._string @main.maxString(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr %context) unnamed_addr #2 { +entry: + %0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0 + %1 = insertvalue %runtime._string %0, i32 %a.len, 1 + %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 + %3 = insertvalue %runtime._string %2, i32 %b.len, 1 + %stackalloc = alloca i8, align 1 + %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #5 + %5 = select i1 %4, %runtime._string %1, %runtime._string %3 + %6 = extractvalue %runtime._string %5, 0 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 + ret %runtime._string %5 +} + +; Function Attrs: nounwind +define hidden void @main.clearSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + %0 = shl i32 %s.len, 2 + call void @llvm.memset.p0.i32(ptr align 4 %s.data, i8 0, i32 %0, i1 false) + ret void +} + +; Function Attrs: argmemonly nocallback nofree nounwind willreturn writeonly +declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #3 + +; Function Attrs: nounwind +define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + ret void +} + +; Function Attrs: nounwind +define hidden void @main.clearMap(ptr dereferenceable_or_null(40) %m, ptr %context) unnamed_addr #2 { +entry: + call void @runtime.hashmapClear(ptr %m, ptr undef) #5 + ret void +} + +declare void @runtime.hashmapClear(ptr dereferenceable_or_null(40), ptr) #1 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.smin.i32(i32, i32) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i8 @llvm.umin.i8(i8, i8) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.umin.i32(i32, i32) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.smax.i32(i32, i32) #4 + +; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn +declare i32 @llvm.umax.i32(i32, i32) #4 + +attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } +attributes #3 = { argmemonly nocallback nofree nounwind willreturn writeonly } +attributes #4 = { nocallback nofree nosync nounwind readnone speculatable willreturn } +attributes #5 = { nounwind } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index edf6118c78..8150dc2eec 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 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: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.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 diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go index 0a4be11aea..fa1d4b0e96 100644 --- a/compiler/testdata/pragma.go +++ b/compiler/testdata/pragma.go @@ -62,6 +62,19 @@ func exportedFunctionInSection() { //go:wasmimport modulename import1 func declaredImport() +// Legacy way of importing a function. +// +//go:wasm-module foobar +//export imported +func foobarImport() + +// The wasm-module pragma is not functional here, but it should be safe. +// +//go:wasm-module foobar +//export exported +func foobarExportModule() { +} + // This function should not: it's only a declaration and not a definition. // //go:section .special_function_section diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index 78cc29457d..397772e5d0 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -6,7 +6,7 @@ target triple = "wasm32-unknown-wasi" @extern_global = external global [0 x i8], align 1 @main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32 @main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16 -@llvm.used = appending global [2 x ptr] [ptr @extern_func, ptr @exportedFunctionInSection] +@llvm.used = appending global [3 x ptr] [ptr @extern_func, ptr @exportedFunctionInSection, ptr @exported] @main.globalInSection = hidden global i32 0, section ".special_global_section", align 4 @undefinedGlobalNotInSection = external global i32, align 4 @main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024 @@ -62,13 +62,23 @@ entry: declare void @main.declaredImport() #7 +declare void @imported() #8 + +; Function Attrs: nounwind +define void @exported() #9 { +entry: + ret void +} + declare void @main.undefinedFunctionNotInSection(ptr) #1 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" "wasm-import-module"="env" "wasm-import-name"="extern_func" } +attributes #3 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="extern_func" } attributes #4 = { inlinehint nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } -attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" "wasm-import-module"="env" "wasm-import-name"="exportedFunctionInSection" } +attributes #6 = { noinline nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exportedFunctionInSection" } attributes #7 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="modulename" "wasm-import-name"="import1" } +attributes #8 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-import-module"="foobar" "wasm-import-name"="imported" } +attributes #9 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" "wasm-export-name"="exported" } diff --git a/go.mod b/go.mod index 3aabc46aed..a6d051a852 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/mattn/go-colorable v0.1.8 github.com/mattn/go-tty v0.0.4 github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 - go.bug.st/serial v1.3.5 + go.bug.st/serial v1.6.0 golang.org/x/sys v0.4.0 golang.org/x/tools v0.5.1-0.20230114154351-e035d0c426c8 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 78a7ea867d..0df04dcff5 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA 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/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919 h1:Hi7G1bCG70NwlyqGswJKEHoIi4hJVN1SsmfwZ+DQHtw= +github.com/deadprogram/go-serial v0.0.0-20230717164825-4529b3232919/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE= 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= @@ -46,8 +48,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb 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= -go.bug.st/serial v1.3.5 h1:k50SqGZCnHZ2MiBQgzccXWG+kd/XpOs1jUljpDDKzaE= -go.bug.st/serial v1.3.5/go.mod h1:z8CesKorE90Qr/oRSJiEuvzYRKol9r/anJZEb5kt304= +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.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= 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= diff --git a/goenv/goenv.go b/goenv/goenv.go index 445b723774..7bf52392ee 100644 --- a/goenv/goenv.go +++ b/goenv/goenv.go @@ -4,15 +4,16 @@ package goenv import ( "bytes" + "encoding/json" "errors" "fmt" "io/fs" "os" "os/exec" - "os/user" "path/filepath" "runtime" "strings" + "sync" ) // Keys is a slice of all available environment variable keys. @@ -37,6 +38,53 @@ func init() { // directory. var TINYGOROOT string +// Variables read from a `go env` command invocation. +var goEnvVars struct { + GOPATH string + GOROOT string + GOVERSION string +} + +var goEnvVarsOnce sync.Once +var goEnvVarsErr error // error returned from cmd.Run + +// Make sure goEnvVars is fresh. This can be called multiple times, the first +// time will update all environment variables in goEnvVars. +func readGoEnvVars() error { + goEnvVarsOnce.Do(func() { + cmd := exec.Command("go", "env", "-json", "GOPATH", "GOROOT", "GOVERSION") + output, err := cmd.Output() + if err != nil { + // Check for "command not found" error. + if execErr, ok := err.(*exec.Error); ok { + goEnvVarsErr = fmt.Errorf("could not find '%s' command: %w", execErr.Name, execErr.Err) + return + } + // It's perhaps a bit ugly to handle this error here, but I couldn't + // think of a better place further up in the call chain. + if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() != 0 { + if len(exitErr.Stderr) != 0 { + // The 'go' command exited with an error message. Print that + // message and exit, so we behave in a similar way. + os.Stderr.Write(exitErr.Stderr) + os.Exit(exitErr.ExitCode()) + } + } + // Other errors. Not sure whether there are any, but just in case. + goEnvVarsErr = err + return + } + err = json.Unmarshal(output, &goEnvVars) + if err != nil { + // This should never happen if we have a sane Go toolchain + // installed. + goEnvVarsErr = fmt.Errorf("unexpected error while unmarshalling `go env` output: %w", err) + } + }) + + return goEnvVarsErr +} + // Get returns a single environment variable, possibly calculating it on-demand. // The empty string is returned for unknown environment variables. func Get(name string) string { @@ -70,15 +118,11 @@ func Get(name string) string { // especially when floating point instructions are involved. return "6" case "GOROOT": - return getGoroot() + readGoEnvVars() + return goEnvVars.GOROOT case "GOPATH": - if dir := os.Getenv("GOPATH"); dir != "" { - return dir - } - - // fallback - home := getHomeDir() - return filepath.Join(home, "go") + readGoEnvVars() + return goEnvVars.GOPATH case "GOCACHE": // Get the cache directory, usually ~/.cache/tinygo dir, err := os.UserCacheDir() @@ -244,93 +288,3 @@ func isSourceDir(root string) bool { _, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go")) return err == nil } - -func getHomeDir() string { - u, err := user.Current() - if err != nil { - panic("cannot get current user: " + err.Error()) - } - if u.HomeDir == "" { - // This is very unlikely, so panic here. - // Not the nicest solution, however. - panic("could not find home directory") - } - return u.HomeDir -} - -// getGoroot returns an appropriate GOROOT from various sources. If it can't be -// found, it returns an empty string. -func getGoroot() string { - // An explicitly set GOROOT always has preference. - goroot := os.Getenv("GOROOT") - if goroot != "" { - // Convert to the standard GOROOT being referenced, if it's a TinyGo cache. - return getStandardGoroot(goroot) - } - - // Check for the location of the 'go' binary and base GOROOT on that. - binpath, err := exec.LookPath("go") - if err == nil { - binpath, err = filepath.EvalSymlinks(binpath) - if err == nil { - goroot := filepath.Dir(filepath.Dir(binpath)) - if isGoroot(goroot) { - return goroot - } - } - } - - // Check what GOROOT was at compile time. - if isGoroot(runtime.GOROOT()) { - return runtime.GOROOT() - } - - // Check for some standard locations, as a last resort. - var candidates []string - switch runtime.GOOS { - case "linux": - candidates = []string{ - "/usr/local/go", // manually installed - "/usr/lib/go", // from the distribution - "/snap/go/current/", // installed using snap - } - case "darwin": - candidates = []string{ - "/usr/local/go", // manually installed - "/usr/local/opt/go/libexec", // from Homebrew - } - } - - for _, candidate := range candidates { - if isGoroot(candidate) { - return candidate - } - } - - // Can't find GOROOT... - return "" -} - -// isGoroot checks whether the given path looks like a GOROOT. -func isGoroot(goroot string) bool { - _, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go")) - return err == nil -} - -// getStandardGoroot returns the physical path to a real, standard Go GOROOT -// implied by the given path. -// If the given path appears to be a TinyGo cached GOROOT, it returns the path -// referenced by symlinks contained in the cache. Otherwise, it returns the -// given path as-is. -func getStandardGoroot(path string) string { - // Check if the "bin" subdirectory of our given GOROOT is a symlink, and then - // return the _parent_ directory of its destination. - if dest, err := os.Readlink(filepath.Join(path, "bin")); nil == err { - // Clean the destination to remove any trailing slashes, so that - // filepath.Dir will always return the parent. - // (because both "/foo" and "/foo/" are valid symlink destinations, - // but filepath.Dir would return "/" and "/foo", respectively) - return filepath.Dir(filepath.Clean(dest)) - } - return path -} diff --git a/goenv/version.go b/goenv/version.go index e74f14c35e..da58358a58 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -4,15 +4,12 @@ import ( "errors" "fmt" "io" - "os" - "path/filepath" - "regexp" "strings" ) // Version of TinyGo. // Update this value before release of new version of software. -const Version = "0.28.1" +const Version = "0.29.0" var ( // This variable is set at build time using -ldflags parameters. @@ -22,8 +19,8 @@ var ( // GetGorootVersion returns the major and minor version for a given GOROOT path. // If the goroot cannot be determined, (0, 0) is returned. -func GetGorootVersion(goroot string) (major, minor int, err error) { - s, err := GorootVersionString(goroot) +func GetGorootVersion() (major, minor int, err error) { + s, err := GorootVersionString() if err != nil { return 0, 0, err } @@ -51,24 +48,9 @@ func GetGorootVersion(goroot string) (major, minor int, err error) { } // GorootVersionString returns the version string as reported by the Go -// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but -// can have some variations (for beta releases, for example). -func GorootVersionString(goroot string) (string, error) { - if data, err := os.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { - return string(data), nil - - } else if data, err := os.ReadFile(filepath.Join( - goroot, "src", "internal", "buildcfg", "zbootstrap.go")); err == nil { - - r := regexp.MustCompile("const version = `(.*)`") - matches := r.FindSubmatch(data) - if len(matches) != 2 { - return "", errors.New("Invalid go version output:\n" + string(data)) - } - - return string(matches[1]), nil - - } else { - return "", err - } +// toolchain. It is usually of the form `go1.x.y` but can have some variations +// (for beta releases, for example). +func GorootVersionString() (string, error) { + err := readGoEnvVars() + return goEnvVars.GOVERSION, err } diff --git a/lib/renesas-svd b/lib/renesas-svd new file mode 160000 index 0000000000..03d7688085 --- /dev/null +++ b/lib/renesas-svd @@ -0,0 +1 @@ +Subproject commit 03d76880854b9042f5d043f4355cdf8eef522fa5 diff --git a/loader/goroot.go b/loader/goroot.go index b6812badc4..c1479b642f 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -207,6 +207,11 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool) merges[dir] = filepath.Join(goroot, dir) } + // Required starting in Go 1.21 due to https://github.com/golang/go/issues/61928 + if _, err := os.Stat(filepath.Join(goroot, "go.env")); err == nil { + merges["go.env"] = filepath.Join(goroot, "go.env") + } + return merges, nil } diff --git a/main.go b/main.go index 4b0e6c726b..a84c7aed3c 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "regexp" "runtime" "runtime/pprof" + "sort" "strconv" "strings" "sync" @@ -946,112 +947,130 @@ func touchSerialPortAt1200bps(port string) (err error) { } func flashUF2UsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { - // find standard UF2 info path - infoPaths := make([]string, 0, len(volumes)) - for _, volume := range volumes { - switch runtime.GOOS { - case "linux", "freebsd": - fi, err := os.Stat("/run/media") - if err != nil || !fi.IsDir() { - infoPaths = append(infoPaths, "/media/*/"+volume+"/INFO_UF2.TXT") - } else { - infoPaths = append(infoPaths, "/run/media/*/"+volume+"/INFO_UF2.TXT") - } - case "darwin": - infoPaths = append(infoPaths, "/Volumes/"+volume+"/INFO_UF2.TXT") - case "windows": - path, err := windowsFindUSBDrive(volume, options) - if err == nil { - infoPaths = append(infoPaths, path+"/INFO_UF2.TXT") + for start := time.Now(); time.Since(start) < options.Timeout; { + // Find a UF2 mount point. + mounts, err := findFATMounts(options) + if err != nil { + return err + } + for _, mount := range mounts { + for _, volume := range volumes { + if mount.name != volume { + continue + } + if _, err := os.Stat(filepath.Join(mount.path, "INFO_UF2.TXT")); err != nil { + // No INFO_UF2.TXT found, which is expected on a UF2 + // filesystem. + continue + } + // Found the filesystem, so flash the device! + return moveFile(tmppath, filepath.Join(mount.path, "flash.uf2")) } } + time.Sleep(500 * time.Millisecond) } - - d, err := locateDevice(volumes, infoPaths, options.Timeout) - if err != nil { - return err - } - - return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2") + return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") } func flashHexUsingMSD(volumes []string, tmppath string, options *compileopts.Options) error { - // find expected volume path - destPaths := make([]string, 0, len(volumes)) - for _, volume := range volumes { - switch runtime.GOOS { - case "linux", "freebsd": - fi, err := os.Stat("/run/media") - if err != nil || !fi.IsDir() { - destPaths = append(destPaths, "/media/*/"+volume) - } else { - destPaths = append(destPaths, "/run/media/*/"+volume) - } - case "darwin": - destPaths = append(destPaths, "/Volumes/"+volume) - case "windows": - path, err := windowsFindUSBDrive(volume, options) - if err == nil { - destPaths = append(destPaths, path+"/") + for start := time.Now(); time.Since(start) < options.Timeout; { + // Find all mount points. + mounts, err := findFATMounts(options) + if err != nil { + return err + } + for _, mount := range mounts { + for _, volume := range volumes { + if mount.name != volume { + continue + } + // Found the filesystem, so flash the device! + return moveFile(tmppath, filepath.Join(mount.path, "flash.hex")) } } + time.Sleep(500 * time.Millisecond) } + return errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") +} - d, err := locateDevice(volumes, destPaths, options.Timeout) - if err != nil { - return err - } - - return moveFile(tmppath, d+"/flash.hex") +type mountPoint struct { + name string + path string } -func locateDevice(volumes, paths []string, timeout time.Duration) (string, error) { - var d []string - var err error - for start := time.Now(); time.Since(start) < timeout; { - for _, path := range paths { - d, err = filepath.Glob(path) - if err != nil { - return "", err +// Find all the mount points on the system that use the FAT filesystem. +func findFATMounts(options *compileopts.Options) ([]mountPoint, error) { + var points []mountPoint + switch runtime.GOOS { + case "darwin": + list, err := os.ReadDir("/Volumes") + if err != nil { + return nil, fmt.Errorf("could not list mount points: %w", err) + } + for _, elem := range list { + // TODO: find a way to check for the filesystem type. + // (Only return FAT filesystems). + points = append(points, mountPoint{ + name: elem.Name(), + path: filepath.Join("/Volumes", elem.Name()), + }) + } + sort.Slice(points, func(i, j int) bool { + return points[i].path < points[j].name + }) + return points, nil + case "linux": + tab, err := os.ReadFile("/proc/mounts") // symlink to /proc/self/mounts on my system + if err != nil { + return nil, fmt.Errorf("could not list mount points: %w", err) + } + for _, line := range strings.Split(string(tab), "\n") { + fields := strings.Fields(line) + if len(fields) <= 2 { + continue } - if d != nil { - break + fstype := fields[2] + if fstype != "vfat" { + continue } + points = append(points, mountPoint{ + name: filepath.Base(fields[1]), + path: fields[1], + }) + } + 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") + var out bytes.Buffer + cmd.Stdout = &out + err := cmd.Run() + if err != nil { + return nil, fmt.Errorf("could not list mount points: %w", err) } - if d != nil { - break - } - - time.Sleep(500 * time.Millisecond) - } - if d == nil { - return "", errors.New("unable to locate any volume: [" + strings.Join(volumes, ",") + "]") - } - return d[0], nil -} - -func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, error) { - cmd := executeCommand(options, "wmic", - "PATH", "Win32_LogicalDisk", "WHERE", "VolumeName = '"+volume+"'", - "get", "DeviceID,VolumeName,FileSystem,DriveType") - - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - if err != nil { - return "", err - } - - for _, line := range strings.Split(out.String(), "\n") { - words := strings.Fields(line) - if len(words) >= 3 { - if words[1] == "2" && words[2] == "FAT" { - return words[0], nil + // Extract data to convert to a []mountPoint slice. + for _, line := range strings.Split(out.String(), "\n") { + words := strings.Fields(line) + if len(words) < 3 { + continue } + if words[1] != "2" || words[2] != "FAT" { + // - DriveType 2 is removable (which we're looking for). + // - We only want to return FAT filesystems. + continue + } + points = append(points, mountPoint{ + name: words[3], + path: words[0], + }) } + return points, nil + default: + return nil, fmt.Errorf("unknown GOOS for listing mount points: %s", runtime.GOOS) } - return "", errors.New("unable to locate a USB device to be flashed") } // getDefaultPort returns the default serial port depending on the operating system. @@ -1780,15 +1799,17 @@ func main() { } if flagJSON { json, _ := json.MarshalIndent(struct { - GOROOT string `json:"goroot"` - GOOS string `json:"goos"` - GOARCH string `json:"goarch"` - GOARM string `json:"goarm"` - BuildTags []string `json:"build_tags"` - GC string `json:"garbage_collector"` - Scheduler string `json:"scheduler"` - LLVMTriple string `json:"llvm_triple"` + Target *compileopts.TargetSpec `json:"target"` + GOROOT string `json:"goroot"` + GOOS string `json:"goos"` + GOARCH string `json:"goarch"` + GOARM string `json:"goarm"` + BuildTags []string `json:"build_tags"` + GC string `json:"garbage_collector"` + Scheduler string `json:"scheduler"` + LLVMTriple string `json:"llvm_triple"` }{ + Target: config.Target, GOROOT: cachedGOROOT, GOOS: config.GOOS(), GOARCH: config.GOARCH(), @@ -1855,7 +1876,7 @@ func main() { usage(command) case "version": goversion := "" - if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil { + if s, err := goenv.GorootVersionString(); err == nil { goversion = s } version := goenv.Version diff --git a/main_test.go b/main_test.go index 59254656ee..ae7aed564d 100644 --- a/main_test.go +++ b/main_test.go @@ -34,6 +34,16 @@ var supportedLinuxArches = map[string]string{ "X86Linux": "linux/386", "ARMLinux": "linux/arm/6", "ARM64Linux": "linux/arm64", + "WASIp1": "wasip1/wasm", +} + +func init() { + major, _, _ := goenv.GetGorootVersion() + if major < 21 { + // Go 1.20 backwards compatibility. + // Should be removed once we drop support for Go 1.20. + delete(supportedLinuxArches, "WASIp1") + } } var sema = make(chan struct{}, runtime.NumCPU()) @@ -71,6 +81,16 @@ func TestBuild(t *testing.T) { "zeroalloc.go", } + // Go 1.21 made some changes to the language, which we can only test when + // we're actually on Go 1.21. + _, minor, err := goenv.GetGorootVersion() + if err != nil { + t.Fatal("could not get version:", minor) + } + if minor >= 21 { + tests = append(tests, "go1.21.go") + } + if *testTarget != "" { // This makes it possible to run one specific test (instead of all), // which is especially useful to quickly check whether some changes @@ -166,6 +186,8 @@ 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") + for _, name := range tests { if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") { switch name { @@ -216,7 +238,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("env.go", options, t, []string{"first", "second"}, []string{"ENV1=VALUE1", "ENV2=VALUE2"}) }) } - if options.Target == "wasi" || options.Target == "wasm" { + if isWebAssembly { t.Run("alias.go-scheduler-none", func(t *testing.T) { t.Parallel() options := compileopts.Options(options) @@ -236,7 +258,7 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("rand.go", options, t, nil, nil) }) } - if options.Target != "wasi" && options.Target != "wasm" { + if !isWebAssembly { // The recover() builtin isn't supported yet on WebAssembly and Windows. t.Run("recover.go", func(t *testing.T) { t.Parallel() diff --git a/src/examples/pininterrupt/arduino.go b/src/examples/pininterrupt/arduino.go new file mode 100644 index 0000000000..aefdacf968 --- /dev/null +++ b/src/examples/pininterrupt/arduino.go @@ -0,0 +1,11 @@ +//go:build arduino + +package main + +import "machine" + +const ( + button = machine.D2 + buttonMode = machine.PinInputPullup + buttonPinChange = machine.PinRising +) diff --git a/src/examples/pininterrupt/circuitplay-express.go b/src/examples/pininterrupt/circuitplay-express.go index e036550644..54339c9168 100644 --- a/src/examples/pininterrupt/circuitplay-express.go +++ b/src/examples/pininterrupt/circuitplay-express.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInputPulldown buttonPinChange = machine.PinFalling ) diff --git a/src/examples/pininterrupt/pca10040.go b/src/examples/pininterrupt/pca10040.go index e4a9d6feb2..35bb7a866c 100644 --- a/src/examples/pininterrupt/pca10040.go +++ b/src/examples/pininterrupt/pca10040.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInputPullup buttonPinChange = machine.PinRising ) diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go index 0cb29bc854..e13ba5eb3a 100644 --- a/src/examples/pininterrupt/pininterrupt.go +++ b/src/examples/pininterrupt/pininterrupt.go @@ -8,18 +8,14 @@ package main import ( "machine" - "runtime/volatile" "time" ) const ( - button = machine.BUTTON - led = machine.LED + led = machine.LED ) func main() { - var lightLed volatile.Register8 - lightLed.Set(0) // Configure the LED, defaulting to on (usually setting the pin to low will // turn the LED on). @@ -33,13 +29,7 @@ func main() { // Set an interrupt on this pin. err := button.SetInterrupt(buttonPinChange, func(machine.Pin) { - if lightLed.Get() != 0 { - lightLed.Set(0) - led.Low() - } else { - lightLed.Set(1) - led.High() - } + led.Set(!led.Get()) }) if err != nil { println("could not configure pin interrupt:", err.Error()) diff --git a/src/examples/pininterrupt/stm32.go b/src/examples/pininterrupt/stm32.go index 24d7e5f7bc..02f2fb94e8 100644 --- a/src/examples/pininterrupt/stm32.go +++ b/src/examples/pininterrupt/stm32.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInputPulldown buttonPinChange = machine.PinRising | machine.PinFalling ) diff --git a/src/examples/pininterrupt/wioterminal.go b/src/examples/pininterrupt/wioterminal.go index 90443537aa..da5291db18 100644 --- a/src/examples/pininterrupt/wioterminal.go +++ b/src/examples/pininterrupt/wioterminal.go @@ -5,6 +5,7 @@ package main import "machine" const ( + button = machine.BUTTON buttonMode = machine.PinInput buttonPinChange = machine.PinFalling ) diff --git a/src/examples/time-offset/time-offset.go b/src/examples/time-offset/time-offset.go new file mode 100644 index 0000000000..ef84997f7c --- /dev/null +++ b/src/examples/time-offset/time-offset.go @@ -0,0 +1,32 @@ +package main + +// This example demonstrates how to set the system time. +// +// Usually, boards don't keep time on power cycles and restarts, it resets to Unix epoch. +// The system time can be set by calling runtime.AdjustTimeOffset(). +// +// Possible time sources: an external RTC clock or network (WiFi access point or NTP server) + +import ( + "fmt" + "runtime" + "time" +) + +const myTime = "2006-01-02T15:04:05Z" // this is an example time you source somewhere + +func main() { + + // measure how many nanoseconds the internal clock is behind + timeOfMeasurement := time.Now() + actualTime, _ := time.Parse(time.RFC3339, myTime) + offset := actualTime.Sub(timeOfMeasurement) + + // adjust internal clock by adding the offset to the internal clock + runtime.AdjustTimeOffset(int64(offset)) + + for { + fmt.Printf("%v\r\n", time.Now().Format(time.RFC3339)) + time.Sleep(5 * time.Second) + } +} diff --git a/src/examples/watchdog/main.go b/src/examples/watchdog/main.go new file mode 100644 index 0000000000..7c17c82bb7 --- /dev/null +++ b/src/examples/watchdog/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "machine" + "time" +) + +func main() { + //sleep for 2 secs for console + time.Sleep(2 * time.Second) + + config := machine.WatchdogConfig{ + TimeoutMillis: 1000, + } + + println("configuring watchdog for max 1 second updates") + machine.Watchdog.Configure(config) + + // From this point the watchdog is running and Update must be + // called periodically, per the config + machine.Watchdog.Start() + + // This loop should complete because watchdog update is called + // every 100ms. + start := time.Now() + println("updating watchdog for 3 seconds") + for i := 0; i < 30; i++ { + time.Sleep(100 * time.Millisecond) + machine.Watchdog.Update() + fmt.Printf("alive @ %v\n", time.Now().Sub(start)) + } + + // This loop should cause a watchdog reset after 1s since + // there is no update call. + start = time.Now() + println("entering tight loop") + for { + time.Sleep(100 * time.Millisecond) + fmt.Printf("alive @ %v\n", time.Now().Sub(start)) + } +} diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go index a6744c4144..72be9ac587 100644 --- a/src/internal/bytealg/bytealg.go +++ b/src/internal/bytealg/bytealg.go @@ -251,3 +251,13 @@ func IndexRabinKarp(s, substr string) int { } return -1 } + +// MakeNoZero makes a slice of length and capacity n without zeroing the bytes. +// It is the caller's responsibility to ensure uninitialized bytes +// do not leak to the end user. +func MakeNoZero(n int) []byte { + // Note: this does zero the buffer even though that's not necessary. + // For performance reasons we might want to change this (similar to the + // malloc function implemented in the runtime). + return make([]byte, n) +} diff --git a/src/machine/board_ae_rp2040.go b/src/machine/board_ae_rp2040.go new file mode 100644 index 0000000000..91432e4b41 --- /dev/null +++ b/src/machine/board_ae_rp2040.go @@ -0,0 +1,111 @@ +//go:build ae_rp2040 + +package machine + +import ( + "device/rp" + "runtime/interrupt" +) + +// 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 + GP29 Pin = GPIO29 + + // 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 +) + +// 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" + usb_STRING_MANUFACTURER = "AKIZUKI DENSHI" +) + +var ( + usb_VID uint16 = 0x2E8A + usb_PID uint16 = 0x000A +) diff --git a/src/machine/board_macropad-rp2040.go b/src/machine/board_macropad-rp2040.go index 8b8f4a7c60..eca20f7970 100644 --- a/src/machine/board_macropad-rp2040.go +++ b/src/machine/board_macropad-rp2040.go @@ -16,6 +16,7 @@ const ( const ( SWITCH = GPIO0 + BUTTON = GPIO0 KEY1 = GPIO1 KEY2 = GPIO2 diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index 90977fb877..81a47c0dfa 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -97,7 +97,7 @@ func (i2c *I2C) stop() { } // writeByte writes a single byte to the I2C bus. -func (i2c *I2C) writeByte(data byte) { +func (i2c *I2C) writeByte(data byte) error { // Write data to register. avr.TWDR.Set(data) @@ -107,6 +107,7 @@ func (i2c *I2C) writeByte(data byte) { // Wait till data is transmitted. for !avr.TWCR.HasBits(avr.TWCR_TWINT) { } + return nil } // readByte reads a single byte from the I2C bus. @@ -190,7 +191,7 @@ func (uart *UART) handleInterrupt(intr interrupt.Interrupt) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // Wait until UART buffer is not busy. for !uart.statusRegA.HasBits(avr.UCSR0A_UDRE0) { } @@ -198,6 +199,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + // SPIConfig is used to store config info for SPI. type SPIConfig struct { Frequency uint32 diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index ed832e6d33..0987a145c1 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -466,3 +466,107 @@ var SPI0 = SPI{ sdo: PB3, sdi: PB4, cs: PB2} + +// 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_atsamd21.go b/src/machine/machine_atsamd21.go index 59df853e5c..443c7af56a 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -626,7 +626,7 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // wait until ready to receive for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INTFLAG_DRE) { } @@ -634,6 +634,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + // handleInterrupt should be called from the appropriate interrupt handler for // this UART instance. func (uart *UART) handleInterrupt(interrupt.Interrupt) { diff --git a/src/machine/machine_atsamd51.go b/src/machine/machine_atsamd51.go index 24ae89aab8..8d37f2fa5f 100644 --- a/src/machine/machine_atsamd51.go +++ b/src/machine/machine_atsamd51.go @@ -1114,7 +1114,7 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // wait until ready to receive for !uart.Bus.INTFLAG.HasBits(sam.SERCOM_USART_INT_INTFLAG_DRE) { } @@ -1122,6 +1122,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + func (uart *UART) handleInterrupt(interrupt.Interrupt) { // should reset IRQ uart.Receive(byte((uart.Bus.DATA.Get() & 0xFF))) @@ -1162,7 +1164,7 @@ const ( wireCmdStop = 3 ) -const i2cTimeout = 1000 +const i2cTimeout = 28000 // about 210us // Configure is intended to setup the I2C interface. func (i2c *I2C) Configure(config I2CConfig) error { @@ -2297,3 +2299,52 @@ func checkFlashError() error { return nil } + +// Watchdog provides access to the hardware watchdog available +// in the SAMD51. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxTimeout in milliseconds (16s) + WatchdogMaxTimeout = (16384 * 1000) / 1024 // CYC16384/1024kHz +) + +type watchdogImpl struct{} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // 1.024kHz clock + cycles := int((int64(config.TimeoutMillis) * 1024) / 1000) + + // period is expressed as a power-of-two, starting at 8 / 1024ths of a second + period := uint8(0) + cfgCycles := 8 + for cfgCycles < cycles { + period++ + cfgCycles <<= 1 + + if period >= 0xB { + break + } + } + + sam.WDT.CONFIG.Set(period << sam.WDT_CONFIG_PER_Pos) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + sam.WDT.CTRLA.SetBits(sam.WDT_CTRLA_ENABLE) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + // 0xA5 = magic value (see datasheet) + sam.WDT.CLEAR.Set(0xA5) +} diff --git a/src/machine/machine_esp32.go b/src/machine/machine_esp32.go index b58cef66ae..4491b3dd95 100644 --- a/src/machine/machine_esp32.go +++ b/src/machine/machine_esp32.go @@ -314,7 +314,7 @@ func (uart *UART) Configure(config UARTConfig) { uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate) } -func (uart *UART) WriteByte(b byte) error { +func (uart *UART) writeByte(b byte) error { for (uart.Bus.STATUS.Get()>>16)&0xff >= 128 { // Read UART_TXFIFO_CNT from the status register, which indicates how // many bytes there are in the transmit buffer. Wait until there are @@ -324,6 +324,8 @@ func (uart *UART) WriteByte(b byte) error { return nil } +func (uart *UART) flush() {} + // Serial Peripheral Interface on the ESP32. type SPI struct { Bus *esp.SPI_Type diff --git a/src/machine/machine_esp32c3.go b/src/machine/machine_esp32c3.go index f1f646fd5e..e447e4fe0d 100644 --- a/src/machine/machine_esp32c3.go +++ b/src/machine/machine_esp32c3.go @@ -493,7 +493,7 @@ func (uart *UART) enableReceiver() { uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1) } -func (uart *UART) WriteByte(b byte) error { +func (uart *UART) writeByte(b byte) error { for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 { // Read UART_TXFIFO_CNT from the status register, which indicates how // many bytes there are in the transmit buffer. Wait until there are @@ -502,3 +502,5 @@ func (uart *UART) WriteByte(b byte) error { uart.Bus.FIFO.Set(uint32(b)) return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/machine_esp8266.go b/src/machine/machine_esp8266.go index 37ab29a17d..4edd4a535f 100644 --- a/src/machine/machine_esp8266.go +++ b/src/machine/machine_esp8266.go @@ -181,12 +181,14 @@ func (uart *UART) Configure(config UARTConfig) { esp.UART0.UART_CLKDIV.Set(CPUFrequency() / config.BaudRate) } -// WriteByte writes a single byte to the output buffer. Note that the hardware +// writeByte writes a single byte to the output buffer. Note that the hardware // includes a buffer of 128 bytes which will be used first. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { for (esp.UART0.UART_STATUS.Get()>>16)&0xff >= 128 { // Wait until the TX buffer has room. } esp.UART0.UART_FIFO.Set(uint32(c)) return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go index 3a0c451349..85a2c5bd37 100644 --- a/src/machine/machine_fe310.go +++ b/src/machine/machine_fe310.go @@ -111,13 +111,16 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) { uart.Receive(c) } -func (uart *UART) WriteByte(c byte) { +func (uart *UART) writeByte(c byte) error { for sifive.UART0.TXDATA.Get()&sifive.UART_TXDATA_FULL != 0 { } sifive.UART0.TXDATA.Set(uint32(c)) + return nil } +func (uart *UART) flush() {} + // SPI on the FE310. The normal SPI0 is actually a quad-SPI meant for flash, so it is best // to use SPI1 or SPI2 port for most applications. type SPI struct { diff --git a/src/machine/machine_k210.go b/src/machine/machine_k210.go index 9c63a8f766..e8a304c850 100644 --- a/src/machine/machine_k210.go +++ b/src/machine/machine_k210.go @@ -392,13 +392,16 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) { uart.Receive(c) } -func (uart *UART) WriteByte(c byte) { +func (uart *UART) writeByte(c byte) error { for uart.Bus.TXDATA.Get()&kendryte.UARTHS_TXDATA_FULL != 0 { } uart.Bus.TXDATA.Set(uint32(c)) + return nil } +func (uart *UART) flush() {} + type SPI struct { Bus *kendryte.SPI_Type } diff --git a/src/machine/machine_mimxrt1062_uart.go b/src/machine/machine_mimxrt1062_uart.go index d24206dfb9..6265b85656 100644 --- a/src/machine/machine_mimxrt1062_uart.go +++ b/src/machine/machine_mimxrt1062_uart.go @@ -173,7 +173,7 @@ func (uart *UART) Sync() error { } // WriteByte writes a single byte of data to the UART interface. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { uart.startTransmitting() for !uart.txBuffer.Put(c) { } @@ -181,6 +181,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + // getBaudRateDivisor finds the greatest over-sampling factor (4..32) and // corresponding baud rate divisor (1..8191) that best partition a given baud // rate into equal intervals. diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 83fa57b3e9..d7b87d9aec 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -186,7 +186,7 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { nrf.UART0.EVENTS_TXDRDY.Set(0) nrf.UART0.TXD.Set(uint32(c)) for nrf.UART0.EVENTS_TXDRDY.Get() == 0 { @@ -194,6 +194,8 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() {} + func (uart *UART) handleInterrupt(interrupt.Interrupt) { if nrf.UART0.EVENTS_RXDRDY.Get() != 0 { uart.Receive(byte(nrf.UART0.RXD.Get())) @@ -201,6 +203,8 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) { } } +const i2cTimeout = 0xffff // this is around 29ms on a nrf52 + // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 @@ -259,11 +263,17 @@ func (i2c *I2C) Configure(config I2CConfig) error { } // signalStop sends a stop signal to the I2C peripheral and waits for confirmation. -func (i2c *I2C) signalStop() { +func (i2c *I2C) signalStop() error { + tries := 0 i2c.Bus.TASKS_STOP.Set(1) for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + tries++ + if tries >= i2cTimeout { + return errI2CSignalStopTimeout + } } i2c.Bus.EVENTS_STOPPED.Set(0) + return nil } var rngStarted = false diff --git a/src/machine/machine_nrf528xx.go b/src/machine/machine_nrf528xx.go index 019a66cf1b..f8937aec06 100644 --- a/src/machine/machine_nrf528xx.go +++ b/src/machine/machine_nrf528xx.go @@ -214,3 +214,46 @@ func twisError(val uint32) error { return errI2CBusError } + +var ( + Watchdog = &watchdogImpl{} +) + +const ( + // WatchdogMaxTimeout in milliseconds (approx 36h) + WatchdogMaxTimeout = (0xffffffff * 1000) / 32768 +) + +type watchdogImpl struct { +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // 32.768kHz counter + crv := int32((int64(config.TimeoutMillis) * 32768) / 1000) + nrf.WDT.CRV.Set(uint32(crv)) + + // One source + nrf.WDT.RREN.Set(0x1) + + // Run during sleep + nrf.WDT.CONFIG.Set(nrf.WDT_CONFIG_SLEEP_Run) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + nrf.WDT.TASKS_START.Set(nrf.WDT_TASKS_START_TASKS_START) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + // 0x6E524635 = magic value from datasheet + nrf.WDT.RR[0].Set(0x6E524635) +} diff --git a/src/machine/machine_nrf52xxx.go b/src/machine/machine_nrf52xxx.go index c4605d5820..e35cb02010 100644 --- a/src/machine/machine_nrf52xxx.go +++ b/src/machine/machine_nrf52xxx.go @@ -288,24 +288,27 @@ func (spi SPI) Tx(w, r []byte) error { // supported. for len(r) != 0 || len(w) != 0 { // Prepare the SPI transfer: set the DMA pointers and lengths. - if len(r) != 0 { - spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) - n := uint32(len(r)) - if n > 255 { - n = 255 + // read buffer + nr := uint32(len(r)) + if nr > 0 { + if nr > 255 { + nr = 255 } - spi.Bus.RXD.MAXCNT.Set(n) - r = r[n:] + spi.Bus.RXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&r[0])))) + r = r[nr:] } - if len(w) != 0 { - spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) - n := uint32(len(w)) - if n > 255 { - n = 255 + spi.Bus.RXD.MAXCNT.Set(nr) + + // write buffer + nw := uint32(len(w)) + if nw > 0 { + if nw > 255 { + nw = 255 } - spi.Bus.TXD.MAXCNT.Set(n) - w = w[n:] + spi.Bus.TXD.PTR.Set(uint32(uintptr(unsafe.Pointer(&w[0])))) + w = w[nw:] } + spi.Bus.TXD.MAXCNT.Set(nw) // Do the transfer. // Note: this can be improved by not waiting until the transfer is diff --git a/src/machine/machine_nrf5x.go b/src/machine/machine_nrf5x.go index f36c7c93c9..4c731036be 100644 --- a/src/machine/machine_nrf5x.go +++ b/src/machine/machine_nrf5x.go @@ -70,11 +70,21 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { i2c.Bus.SHORTS.Set(nrf.TWI_SHORTS_BB_SUSPEND_Disabled) } - // Stop explicitly when no reads were executed, stoping unconditionally would be a mistake. - // It may execute after I2C peripheral has already been stopped by the shortcut in the read block, - // so stop task will trigger first thing in a subsequent transaction, hanging it. if len(r) == 0 { - i2c.signalStop() + // Stop the I2C transaction after the write. + err = i2c.signalStop() + } else { + // The last byte read has already stopped the transaction, via + // TWI_SHORTS_BB_STOP. But we still need to wait until we receive the + // STOPPED event. + tries := 0 + for i2c.Bus.EVENTS_STOPPED.Get() == 0 { + tries++ + if tries >= i2cTimeout { + return errI2CSignalStopTimeout + } + } + i2c.Bus.EVENTS_STOPPED.Set(0) } return @@ -82,12 +92,17 @@ func (i2c *I2C) Tx(addr uint16, w, r []byte) (err error) { // writeByte writes a single byte to the I2C bus and waits for confirmation. func (i2c *I2C) writeByte(data byte) error { + tries := 0 i2c.Bus.TXD.Set(uint32(data)) for i2c.Bus.EVENTS_TXDSENT.Get() == 0 { if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { i2c.Bus.EVENTS_ERROR.Set(0) return errI2CBusError } + tries++ + if tries >= i2cTimeout { + return errI2CWriteTimeout + } } i2c.Bus.EVENTS_TXDSENT.Set(0) return nil @@ -95,11 +110,16 @@ func (i2c *I2C) writeByte(data byte) error { // readByte reads a single byte from the I2C bus when it is ready. func (i2c *I2C) readByte() (byte, error) { + tries := 0 for i2c.Bus.EVENTS_RXDREADY.Get() == 0 { if e := i2c.Bus.EVENTS_ERROR.Get(); e != 0 { i2c.Bus.EVENTS_ERROR.Set(0) return 0, errI2CBusError } + tries++ + if tries >= i2cTimeout { + return 0, errI2CReadTimeout + } } i2c.Bus.EVENTS_RXDREADY.Set(0) return byte(i2c.Bus.RXD.Get()), nil diff --git a/src/machine/machine_nxpmk66f18_uart.go b/src/machine/machine_nxpmk66f18_uart.go index d62fc64e01..a14d18f5ef 100644 --- a/src/machine/machine_nxpmk66f18_uart.go +++ b/src/machine/machine_nxpmk66f18_uart.go @@ -292,7 +292,7 @@ func (u *UART) handleStatusInterrupt(interrupt.Interrupt) { } // WriteByte writes a byte of data to the UART. -func (u *UART) WriteByte(c byte) error { +func (u *UART) writeByte(c byte) error { if !u.Configured { return ErrNotConfigured } @@ -305,3 +305,5 @@ func (u *UART) WriteByte(c byte) error { u.C2.Set(uartC2TXActive) return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/machine_rp2040_clocks.go b/src/machine/machine_rp2040_clocks.go index 093731049d..57dfa68b0b 100644 --- a/src/machine/machine_rp2040_clocks.go +++ b/src/machine/machine_rp2040_clocks.go @@ -182,7 +182,7 @@ func (clk *clock) configure(src, auxsrc, srcFreq, freq uint32) { // Must be called before any other clock function. func (clks *clocksType) init() { // Start the watchdog tick - watchdog.startTick(xoscFreq) + Watchdog.startTick(xoscFreq) // Disable resus that may be enabled from previous software clks.resus.ctrl.Set(0) diff --git a/src/machine/machine_rp2040_flash.go b/src/machine/machine_rp2040_flash.go new file mode 100644 index 0000000000..f3d24e8e7d --- /dev/null +++ b/src/machine/machine_rp2040_flash.go @@ -0,0 +1,97 @@ +//go:build rp2040 + +package machine + +import ( + "bytes" + "unsafe" +) + +// EnterBootloader should perform a system reset in preparation +// to switch to the bootloader to flash new firmware. +func EnterBootloader() { + enterBootloader() +} + +// compile-time check for ensuring we fulfill BlockDevice interface +var _ BlockDevice = flashBlockDevice{} + +var Flash flashBlockDevice + +type flashBlockDevice struct { +} + +// ReadAt reads the given number of bytes from the block device. +func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { + if readAddress(off) > FlashDataEnd() { + return 0, errFlashCannotReadPastEOF + } + + data := unsafe.Slice((*byte)(unsafe.Pointer(readAddress(off))), len(p)) + copy(p, data) + + return len(p), nil +} + +// WriteAt writes the given number of bytes to the block device. +// Only word (32 bits) length data can be programmed. +// If the length of p is not long enough it will be padded with 0xFF bytes. +// This method assumes that the destination is already erased. +func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { + return f.writeAt(p, off) +} + +// Size returns the number of bytes in this block device. +func (f flashBlockDevice) Size() int64 { + return int64(FlashDataEnd() - FlashDataStart()) +} + +const writeBlockSize = 1 << 8 + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +func (f flashBlockDevice) WriteBlockSize() int64 { + return writeBlockSize +} + +const eraseBlockSizeValue = 1 << 12 + +func eraseBlockSize() int64 { + return eraseBlockSizeValue +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +func (f flashBlockDevice) EraseBlockSize() int64 { + return eraseBlockSize() +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +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) +} + +// return the correct address to be used for reads +func readAddress(off int64) uintptr { + return FlashDataStart() + uintptr(off) +} diff --git a/src/machine/machine_rp2040_gpio.go b/src/machine/machine_rp2040_gpio.go index fa2051af86..94715d963f 100644 --- a/src/machine/machine_rp2040_gpio.go +++ b/src/machine/machine_rp2040_gpio.go @@ -174,6 +174,9 @@ func (p Pin) init() { // 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 { @@ -213,6 +216,9 @@ func (p Pin) Configure(config PinConfig) { // Set drives the pin high if value is true else drives it low. func (p Pin) Set(value bool) { + if p == NoPin { + return + } if value { p.set() } else { @@ -251,6 +257,9 @@ var ( // nil func to unset the pin change interrupt. If you do so, the change // parameter is ignored and can be set to any value (such as 0). func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + if p == NoPin { + return nil + } if p > 31 || p < 0 { return ErrInvalidInputPin } diff --git a/src/machine/machine_rp2040_rom.go b/src/machine/machine_rp2040_rom.go index b4370c77c7..fdb05bb627 100644 --- a/src/machine/machine_rp2040_rom.go +++ b/src/machine/machine_rp2040_rom.go @@ -3,7 +3,6 @@ package machine import ( - "bytes" "runtime/interrupt" "unsafe" ) @@ -136,40 +135,14 @@ void ram_func flash_erase_blocks(uint32_t offset, size_t count) */ import "C" -// EnterBootloader should perform a system reset in preparation -// to switch to the bootloader to flash new firmware. -func EnterBootloader() { +func enterBootloader() { C.reset_usb_boot(0, 0) } // Flash related code const memoryStart = C.XIP_BASE // memory start for purpose of erase -// compile-time check for ensuring we fulfill BlockDevice interface -var _ BlockDevice = flashBlockDevice{} - -var Flash flashBlockDevice - -type flashBlockDevice struct { -} - -// ReadAt reads the given number of bytes from the block device. -func (f flashBlockDevice) ReadAt(p []byte, off int64) (n int, err error) { - if readAddress(off) > FlashDataEnd() { - return 0, errFlashCannotReadPastEOF - } - - data := unsafe.Slice((*byte)(unsafe.Pointer(readAddress(off))), len(p)) - copy(p, data) - - return len(p), nil -} - -// WriteAt writes the given number of bytes to the block device. -// Only word (32 bits) length data can be programmed. -// If the length of p is not long enough it will be padded with 0xFF bytes. -// This method assumes that the destination is already erased. -func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { +func (f flashBlockDevice) writeAt(p []byte, off int64) (n int, err error) { if writeAddress(off)+uintptr(C.XIP_BASE) > FlashDataEnd() { return 0, errFlashCannotWritePastEOF } @@ -190,37 +163,7 @@ func (f flashBlockDevice) WriteAt(p []byte, off int64) (n int, err error) { return len(padded), nil } -// Size returns the number of bytes in this block device. -func (f flashBlockDevice) Size() int64 { - return int64(FlashDataEnd() - FlashDataStart()) -} - -const writeBlockSize = 1 << 8 - -// WriteBlockSize returns the block size in which data can be written to -// memory. It can be used by a client to optimize writes, non-aligned writes -// should always work correctly. -func (f flashBlockDevice) WriteBlockSize() int64 { - return writeBlockSize -} - -const eraseBlockSizeValue = 1 << 12 - -func eraseBlockSize() int64 { - return eraseBlockSizeValue -} - -// EraseBlockSize returns the smallest erasable area on this particular chip -// in bytes. This is used for the block size in EraseBlocks. -func (f flashBlockDevice) EraseBlockSize() int64 { - return eraseBlockSize() -} - -// EraseBlocks erases the given number of blocks. An implementation may -// transparently coalesce ranges of blocks into larger bundles if the chip -// supports this. The start and len parameters are in block numbers, use -// EraseBlockSize to map addresses to blocks. -func (f flashBlockDevice) EraseBlocks(start, length int64) error { +func (f flashBlockDevice) eraseBlocks(start, length int64) error { address := writeAddress(start * f.EraseBlockSize()) if address+uintptr(C.XIP_BASE) > FlashDataEnd() { return errFlashCannotErasePastEOF @@ -233,24 +176,3 @@ func (f flashBlockDevice) EraseBlocks(start, length 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...) -} - -// return the correct address to be used for write -func writeAddress(off int64) uintptr { - return readAddress(off) - uintptr(C.XIP_BASE) -} - -// return the correct address to be used for reads -func readAddress(off int64) uintptr { - return FlashDataStart() + uintptr(off) -} diff --git a/src/machine/machine_rp2040_uart.go b/src/machine/machine_rp2040_uart.go index e5e4f77de3..1d927df128 100644 --- a/src/machine/machine_rp2040_uart.go +++ b/src/machine/machine_rp2040_uart.go @@ -86,9 +86,10 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { // wait until buffer is not full for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_TXFF) { + gosched() } // write data @@ -96,6 +97,12 @@ func (uart *UART) WriteByte(c byte) error { return nil } +func (uart *UART) flush() { + for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_BUSY) { + gosched() + } +} + // SetFormat for number of data bits, stop bits, and parity for the UART. func (uart *UART) SetFormat(databits, stopbits uint8, parity UARTParity) error { var pen, pev uint8 diff --git a/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go b/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go index c67cfe1d30..eedcc4d21e 100644 --- a/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go +++ b/src/machine/machine_rp2040_usb_fix_usb_device_enumeration.go @@ -3,6 +3,7 @@ package machine import ( + "device/arm" "device/rp" ) @@ -87,6 +88,7 @@ func hw_enumeration_fix_force_ls_j() { rp.USBCTRL_REGS.USB_MUXING.Set(rp.USBCTRL_REGS_USB_MUXING_TO_DIGITAL_PAD | rp.USBCTRL_REGS_USB_MUXING_SOFTCON) // LS_J is now forced but while loop here just to check + waitCycles(125000) // if timer pool disabled, or no timer available, have to busy wait. hw_enumeration_fix_finish() @@ -109,3 +111,10 @@ func hw_enumeration_fix_finish() { // Restore the pad ctrl value padsBank0.io[dp].Set(padCtrlPrev) } + +func waitCycles(n int) { + for n > 0 { + arm.Asm("nop") + n-- + } +} diff --git a/src/machine/machine_rp2040_watchdog.go b/src/machine/machine_rp2040_watchdog.go index 6e9fb874f8..a67df80ca8 100644 --- a/src/machine/machine_rp2040_watchdog.go +++ b/src/machine/machine_rp2040_watchdog.go @@ -4,24 +4,67 @@ package machine import ( "device/rp" - "runtime/volatile" - "unsafe" ) -type watchdogType struct { - ctrl volatile.Register32 - load volatile.Register32 - reason volatile.Register32 - scratch [8]volatile.Register32 - tick volatile.Register32 +// Watchdog provides access to the hardware watchdog available +// in the RP2040. +var Watchdog = &watchdogImpl{} + +const ( + // WatchdogMaxTimeout in milliseconds (approx 8.3s). + // + // Nominal 1us per watchdog tick, 24-bit counter, + // but due to errata two ticks consumed per 1us. + // See: Errata RP2040-E1 + WatchdogMaxTimeout = (rp.WATCHDOG_LOAD_LOAD_Msk / 1000) / 2 +) + +type watchdogImpl struct { + // The value to reset the counter to on each Update + loadValue uint32 } -var watchdog = (*watchdogType)(unsafe.Pointer(rp.WATCHDOG)) +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + // x2 due to errata RP2040-E1 + wd.loadValue = config.TimeoutMillis * 1000 * 2 + if wd.loadValue > rp.WATCHDOG_LOAD_LOAD_Msk { + wd.loadValue = rp.WATCHDOG_LOAD_LOAD_Msk + } + + rp.WATCHDOG.CTRL.ClearBits(rp.WATCHDOG_CTRL_ENABLE) + + // Reset everything apart from ROSC and XOSC + rp.PSM.WDSEL.Set(0x0001ffff &^ (rp.PSM_WDSEL_ROSC | rp.PSM_WDSEL_XOSC)) + + // Pause watchdog during debug + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_PAUSE_DBG0 | rp.WATCHDOG_CTRL_PAUSE_DBG1 | rp.WATCHDOG_CTRL_PAUSE_JTAG) + + // Load initial counter + rp.WATCHDOG.LOAD.Set(wd.loadValue) + + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + rp.WATCHDOG.CTRL.SetBits(rp.WATCHDOG_CTRL_ENABLE) + return nil +} + +// Update the watchdog, indicating that the app is healthy. +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 *watchdogType) startTick(cycles uint32) { - wd.tick.Set(cycles | rp.WATCHDOG_TICK_ENABLE) +func (wd *watchdogImpl) startTick(cycles uint32) { + rp.WATCHDOG.TICK.Set(cycles | rp.WATCHDOG_TICK_ENABLE) } diff --git a/src/machine/machine_stm32_iwdg.go b/src/machine/machine_stm32_iwdg.go new file mode 100644 index 0000000000..1139d54619 --- /dev/null +++ b/src/machine/machine_stm32_iwdg.go @@ -0,0 +1,66 @@ +//go:build stm32 + +package machine + +import "device/stm32" + +var ( + Watchdog = &watchdogImpl{} +) + +const ( + // WatchdogMaxTimeout in milliseconds (32.768s) + // + // Timeout is based on 12-bit counter with /256 divider on + // 32.768kHz clock. See 21.3.3 of RM0090 for table. + WatchdogMaxTimeout = ((0xfff + 1) * 256 * 1024) / 32768 +) + +const ( + // Enable access to PR, RLR and WINR registers (0x5555) + iwdgKeyEnable = 0x5555 + // Reset the watchdog value (0xAAAA) + iwdgKeyReset = 0xaaaa + // Start the watchdog (0xCCCC) + iwdgKeyStart = 0xcccc + // Divide by 256 + iwdgDiv256 = 6 +) + +type watchdogImpl struct { +} + +// Configure the watchdog. +// +// This method should not be called after the watchdog is started and on +// some platforms attempting to reconfigure after starting the watchdog +// is explicitly forbidden / will not work. +func (wd *watchdogImpl) Configure(config WatchdogConfig) error { + + // Enable configuration of IWDG + stm32.IWDG.KR.Set(iwdgKeyEnable) + + // Unconditionally divide by /256 since we don't really need accuracy + // less than 8ms + stm32.IWDG.PR.Set(iwdgDiv256) + + timeout := config.TimeoutMillis + if timeout > WatchdogMaxTimeout { + timeout = WatchdogMaxTimeout + } + + // Set reload value based on /256 divider + stm32.IWDG.RLR.Set(((config.TimeoutMillis*32768 + (256 * 1024) - 1) / (256 * 1024)) - 1) + return nil +} + +// Starts the watchdog. +func (wd *watchdogImpl) Start() error { + stm32.IWDG.KR.Set(iwdgKeyStart) + return nil +} + +// Update the watchdog, indicating that `source` is healthy. +func (wd *watchdogImpl) Update() { + stm32.IWDG.KR.Set(iwdgKeyReset) +} diff --git a/src/machine/machine_stm32_uart.go b/src/machine/machine_stm32_uart.go index 6ae6f0f994..6e8806c876 100644 --- a/src/machine/machine_stm32_uart.go +++ b/src/machine/machine_stm32_uart.go @@ -74,10 +74,12 @@ func (uart *UART) SetBaudRate(br uint32) { } // WriteByte writes a byte of data to the UART. -func (uart *UART) WriteByte(c byte) error { +func (uart *UART) writeByte(c byte) error { uart.txReg.Set(uint32(c)) for !uart.statusReg.HasBits(uart.txEmptyFlag) { } return nil } + +func (uart *UART) flush() {} diff --git a/src/machine/uart.go b/src/machine/uart.go index 37d3223324..eeeb7d6a0b 100644 --- a/src/machine/uart.go +++ b/src/machine/uart.go @@ -59,11 +59,27 @@ func (uart *UART) Read(data []byte) (n int, err error) { return size, nil } -// Write data to the UART. +// WriteByte writes a byte of data over the UART's Tx. +// This function blocks until the data is finished being sent. +func (uart *UART) WriteByte(c byte) error { + err := uart.writeByte(c) + if err != nil { + return err + } + uart.flush() // flush() blocks until all data has been transmitted. + return nil +} + +// Write data over the UART's Tx. +// This function blocks until the data is finished being sent. func (uart *UART) Write(data []byte) (n int, err error) { - for _, v := range data { - uart.WriteByte(v) + for i, v := range data { + err = uart.writeByte(v) + if err != nil { + return i, err + } } + uart.flush() // flush() blocks until all data has been transmitted. return len(data), nil } diff --git a/src/machine/usb.go b/src/machine/usb.go index a90fab177c..c28462e536 100644 --- a/src/machine/usb.go +++ b/src/machine/usb.go @@ -29,9 +29,7 @@ type Serialer interface { RTS() bool } -var usbDescriptor = descriptor.CDC - -var usbDescriptorConfig uint8 = usb.DescriptorConfigCDC +var usbDescriptor descriptor.Descriptor func usbVendorID() uint16 { if usb.VendorID != 0 { @@ -138,18 +136,6 @@ func sendDescriptor(setup usb.Setup) { sendUSBPacket(0, usbDescriptor.Configuration, setup.WLength) return case descriptor.TypeDevice: - // composite descriptor - switch { - case (usbDescriptorConfig & usb.DescriptorConfigHID) > 0: - usbDescriptor = descriptor.CDCHID - case (usbDescriptorConfig & usb.DescriptorConfigMIDI) > 0: - usbDescriptor = descriptor.CDCMIDI - case (usbDescriptorConfig & usb.DescriptorConfigJoystick) > 0: - usbDescriptor = descriptor.CDCJoystick - default: - usbDescriptor = descriptor.CDC - } - usbDescriptor.Configure(usbVendorID(), usbProductID()) sendUSBPacket(0, usbDescriptor.Device, setup.WLength) return @@ -273,48 +259,56 @@ func handleStandardSetup(setup usb.Setup) bool { } func EnableCDC(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptorConfig |= usb.DescriptorConfigCDC - endPoints[usb.CDC_ENDPOINT_ACM] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) - endPoints[usb.CDC_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut) - endPoints[usb.CDC_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn) - usbRxHandler[usb.CDC_ENDPOINT_OUT] = rxHandler - usbTxHandler[usb.CDC_ENDPOINT_IN] = txHandler - usbSetupHandler[usb.CDC_ACM_INTERFACE] = setupHandler // 0x02 (Communications and CDC Control) - usbSetupHandler[usb.CDC_DATA_INTERFACE] = nil // 0x0A (CDC-Data) -} - -// EnableHID enables HID. This function must be executed from the init(). -func EnableHID(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptorConfig |= usb.DescriptorConfigHID - endPoints[usb.HID_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) - usbTxHandler[usb.HID_ENDPOINT_IN] = txHandler - usbSetupHandler[usb.HID_INTERFACE] = setupHandler // 0x03 (HID - Human Interface Device) + if len(usbDescriptor.Device) == 0 { + usbDescriptor = descriptor.CDC + } + // Initialization of endpoints is required even for non-CDC + ConfigureUSBEndpoint(usbDescriptor, + []usb.EndpointConfig{ + { + Index: usb.CDC_ENDPOINT_ACM, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + }, + { + Index: usb.CDC_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_BULK, + RxHandler: rxHandler, + }, + { + Index: usb.CDC_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_BULK, + TxHandler: txHandler, + }, + }, + []usb.SetupConfig{ + { + Index: usb.CDC_ACM_INTERFACE, + Handler: setupHandler, + }, + }) } -// EnableMIDI enables MIDI. This function must be executed from the init(). -func EnableMIDI(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool) { - usbDescriptorConfig |= usb.DescriptorConfigMIDI - endPoints[usb.MIDI_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut) - endPoints[usb.MIDI_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn) - usbRxHandler[usb.MIDI_ENDPOINT_OUT] = rxHandler - usbTxHandler[usb.MIDI_ENDPOINT_IN] = txHandler -} +func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) { + usbDescriptor = desc -// EnableJoystick enables HID. This function must be executed from the init(). -func EnableJoystick(txHandler func(), rxHandler func([]byte), setupHandler func(usb.Setup) bool, hidDesc []byte) { - class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes()) - if err != nil { - // TODO: some way to notify about error - return + for _, ep := range epSettings { + if ep.IsIn { + endPoints[ep.Index] = uint32(ep.Type | usb.EndpointIn) + if ep.TxHandler != nil { + usbTxHandler[ep.Index] = ep.TxHandler + } + } else { + endPoints[ep.Index] = uint32(ep.Type | usb.EndpointOut) + if ep.RxHandler != nil { + usbRxHandler[ep.Index] = ep.RxHandler + } + } } - class.ClassLength(uint16(len(hidDesc))) - descriptor.CDCJoystick.HID[2] = hidDesc - - usbDescriptorConfig |= usb.DescriptorConfigJoystick - endPoints[usb.HID_ENDPOINT_OUT] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut) - usbRxHandler[usb.HID_ENDPOINT_OUT] = rxHandler - endPoints[usb.HID_ENDPOINT_IN] = (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn) - usbTxHandler[usb.HID_ENDPOINT_IN] = txHandler - usbSetupHandler[usb.HID_INTERFACE] = setupHandler // 0x03 (HID - Human Interface Device) + for _, s := range setup { + usbSetupHandler[s.Index] = s.Handler + } } diff --git a/src/machine/usb/adc/midi/midi.go b/src/machine/usb/adc/midi/midi.go index 2697866f3c..30b645ee23 100644 --- a/src/machine/usb/adc/midi/midi.go +++ b/src/machine/usb/adc/midi/midi.go @@ -3,6 +3,7 @@ package midi import ( "machine" "machine/usb" + "machine/usb/descriptor" ) const ( @@ -40,7 +41,23 @@ func newMidi() *midi { m := &midi{ buf: NewRingBuffer(), } - machine.EnableMIDI(m.Handler, m.RxHandler, nil) + machine.ConfigureUSBEndpoint(descriptor.CDCMIDI, + []usb.EndpointConfig{ + { + Index: usb.MIDI_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_BULK, + RxHandler: m.RxHandler, + }, + { + Index: usb.MIDI_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_BULK, + TxHandler: m.Handler, + }, + }, + []usb.SetupConfig{}, + ) return m } diff --git a/src/machine/usb/config.go b/src/machine/usb/config.go new file mode 100644 index 0000000000..ef7cd31530 --- /dev/null +++ b/src/machine/usb/config.go @@ -0,0 +1,14 @@ +package usb + +type EndpointConfig struct { + Index uint8 + IsIn bool + TxHandler func() + RxHandler func([]byte) + Type uint8 +} + +type SetupConfig struct { + Index uint8 + Handler func(Setup) bool +} diff --git a/src/machine/usb/descriptor/hid.go b/src/machine/usb/descriptor/hid.go index 8135c2a608..cdd4fc7e57 100644 --- a/src/machine/usb/descriptor/hid.go +++ b/src/machine/usb/descriptor/hid.go @@ -26,7 +26,7 @@ var interfaceHID = [interfaceTypeLen]byte{ TypeInterface, 0x02, // InterfaceNumber 0x00, // AlternateSetting - 0x01, // NumEndpoints + 0x02, // NumEndpoints 0x03, // InterfaceClass 0x00, // InterfaceSubClass 0x00, // InterfaceProtocol @@ -103,7 +103,7 @@ var classHID = [ClassHIDTypeLen]byte{ 0x00, // CountryCode 0x01, // NumDescriptors 0x22, // ClassType - 0x7E, // ClassLength L + 0x90, // ClassLength L 0x00, // ClassLength H } @@ -128,6 +128,7 @@ var CDCHID = Descriptor{ InterfaceHID.Bytes(), ClassHID.Bytes(), EndpointEP4IN.Bytes(), + EndpointEP5OUT.Bytes(), }), HID: map[uint16][]byte{ 2: Append([][]byte{ @@ -147,6 +148,15 @@ var CDCHID = Descriptor{ HIDReportCount(1), HIDReportSize(8), HIDInputConstVarAbs, + HIDReportCount(3), + HIDReportSize(1), + HIDUsagePageLED, + HIDUsageMinimum(1), + HIDUsageMaximum(3), + HIDOutputDataVarAbs, + HIDReportCount(5), + HIDReportSize(1), + HIDOutputConstVarAbs, HIDReportCount(6), HIDReportSize(8), HIDLogicalMinimum(0), diff --git a/src/machine/usb/descriptor/hidreport.go b/src/machine/usb/descriptor/hidreport.go index 914279a51f..5819f6ad69 100644 --- a/src/machine/usb/descriptor/hidreport.go +++ b/src/machine/usb/descriptor/hidreport.go @@ -3,21 +3,29 @@ package descriptor const ( hidUsagePage = 0x05 hidUsage = 0x09 - hidLogicalMinimum = 0x15 - hidLogicalMaximum = 0x25 - hidUsageMinimum = 0x19 - hidUsageMaximum = 0x29 - hidPhysicalMinimum = 0x35 - hidPhysicalMaximum = 0x46 + 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 ) +const ( + hidSizeValue0 = 0x00 + hidSizeValue1 = 0x01 + hidSizeValue2 = 0x02 + hidSizeValue4 = 0x03 +) + var ( HIDUsagePageGenericDesktop = []byte{hidUsagePage, 0x01} HIDUsagePageSimulationControls = []byte{hidUsagePage, 0x02} @@ -114,6 +122,12 @@ var ( // Input (Data, Variable, Relative), 2 position bytes (X & Y) HIDInputDataVarRel = []byte{hidInput, 0x06} + + // Output (Data, Variable, Absolute), Modifier byte + HIDOutputDataVarAbs = []byte{hidOutput, 0x02} + + // Output (Const, Variable, Absolute), Modifier byte + HIDOutputConstVarAbs = []byte{hidOutput, 0x03} ) func HIDReportSize(size int) []byte { @@ -129,51 +143,69 @@ func HIDReportID(id int) []byte { } func HIDLogicalMinimum(min int) []byte { - if min > 255 { - return []byte{hidLogicalMinimum + 1, uint8(min), uint8(min >> 8)} + 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 []byte{hidLogicalMinimum, byte(min)} } func HIDLogicalMaximum(max int) []byte { - if max > 255 { - return []byte{hidLogicalMaximum + 1, uint8(max), uint8(max >> 8)} + 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 []byte{hidLogicalMaximum, byte(max)} } func HIDUsageMinimum(min int) []byte { - if min > 255 { - return []byte{hidUsageMinimum + 1, uint8(min), uint8(min >> 8)} + 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 []byte{hidUsageMinimum, byte(min)} } func HIDUsageMaximum(max int) []byte { - if max > 255 { - return []byte{hidUsageMaximum + 1, uint8(max), uint8(max >> 8)} + 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 []byte{hidUsageMaximum, byte(max)} } func HIDPhysicalMinimum(min int) []byte { - if min > 255 { - return []byte{hidPhysicalMinimum + 1, uint8(min), uint8(min >> 8)} + 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 []byte{hidPhysicalMinimum, byte(min)} } func HIDPhysicalMaximum(max int) []byte { - if max > 255 { - return []byte{hidPhysicalMaximum + 1, uint8(max), uint8(max >> 8)} + 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 []byte{hidPhysicalMaximum, byte(max)} } func HIDUnitExponent(exp int) []byte { diff --git a/src/machine/usb/descriptor/joystick.go b/src/machine/usb/descriptor/joystick.go index ebf2f7c314..03bde25904 100644 --- a/src/machine/usb/descriptor/joystick.go +++ b/src/machine/usb/descriptor/joystick.go @@ -80,10 +80,7 @@ var JoystickDefaultHIDReport = Append([][]byte{ HIDLogicalMaximum(1), HIDReportSize(1), HIDReportCount(16), - HIDInputDataVarAbs, - HIDReportCount(1), - HIDReportSize(3), - HIDUnitExponent(-16), + HIDUnitExponent(0), HIDUnit(0), HIDInputDataVarAbs, diff --git a/src/machine/usb/hid/hid.go b/src/machine/usb/hid/hid.go index 6765696212..791fd06a66 100644 --- a/src/machine/usb/hid/hid.go +++ b/src/machine/usb/hid/hid.go @@ -4,6 +4,7 @@ import ( "errors" "machine" "machine/usb" + "machine/usb/descriptor" ) // from usb-hid.go @@ -22,7 +23,8 @@ const ( ) type hidDevicer interface { - Handler() bool + TxHandler() bool + RxHandler([]byte) bool } var devices [5]hidDevicer @@ -32,19 +34,50 @@ var size int // calls machine.EnableHID for USB configuration func SetHandler(d hidDevicer) { if size == 0 { - machine.EnableHID(handler, nil, setupHandler) + machine.ConfigureUSBEndpoint(descriptor.CDCHID, + []usb.EndpointConfig{ + { + Index: usb.HID_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + RxHandler: rxHandler, + }, + { + Index: usb.HID_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + TxHandler: txHandler, + }, + }, + []usb.SetupConfig{ + { + Index: usb.HID_INTERFACE, + Handler: setupHandler, + }, + }) } devices[size] = d size++ } -func handler() { +func txHandler() { for _, d := range devices { if d == nil { continue } - if done := d.Handler(); done { + if done := d.TxHandler(); done { + return + } + } +} + +func rxHandler(b []byte) { + for _, d := range devices { + if d == nil { + continue + } + if done := d.RxHandler(b); done { return } } diff --git a/src/machine/usb/hid/joystick/joystick.go b/src/machine/usb/hid/joystick/joystick.go index 763b39ee2f..2c2f719c0d 100644 --- a/src/machine/usb/hid/joystick/joystick.go +++ b/src/machine/usb/hid/joystick/joystick.go @@ -3,6 +3,7 @@ package joystick import ( "machine" "machine/usb" + "machine/usb/descriptor" "machine/usb/hid" ) @@ -32,18 +33,50 @@ func UseSettings(def Definitions, rxHandlerFunc func(b []byte), setupFunc func(s if setupFunc == nil { setupFunc = hid.DefaultSetupHandler } - machine.EnableJoystick(js.handler, rxHandlerFunc, setupFunc, hidDesc) + if rxHandlerFunc == nil { + rxHandlerFunc = js.rxHandlerFunc + } + if len(hidDesc) == 0 { + hidDesc = descriptor.JoystickDefaultHIDReport + } + class, err := descriptor.FindClassHIDType(descriptor.CDCJoystick.Configuration, descriptor.ClassHIDJoystick.Bytes()) + if err != nil { + // TODO: some way to notify about error + return nil + } + + class.ClassLength(uint16(len(hidDesc))) + descriptor.CDCJoystick.HID[2] = hidDesc + + machine.ConfigureUSBEndpoint(descriptor.CDCJoystick, + []usb.EndpointConfig{ + { + Index: usb.HID_ENDPOINT_OUT, + IsIn: false, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + RxHandler: rxHandlerFunc, + }, + { + Index: usb.HID_ENDPOINT_IN, + IsIn: true, + Type: usb.ENDPOINT_TYPE_INTERRUPT, + TxHandler: js.handler, + }, + }, + []usb.SetupConfig{ + { + Index: usb.HID_INTERFACE, + Handler: setupFunc, + }, + }, + ) Joystick = js return js } func newDefaultJoystick() *joystick { def := DefaultDefinitions() - js := &joystick{ - State: def.NewState(), - buf: hid.NewRingBuffer(), - } - machine.EnableJoystick(js.handler, js.rxHandler, hid.DefaultSetupHandler, def.Descriptor()) + js := UseSettings(def, nil, nil, nil) return js } diff --git a/src/machine/usb/hid/keyboard/keyboard.go b/src/machine/usb/hid/keyboard/keyboard.go index 6a5bad647b..9f5f420ed0 100644 --- a/src/machine/usb/hid/keyboard/keyboard.go +++ b/src/machine/usb/hid/keyboard/keyboard.go @@ -81,7 +81,7 @@ func newKeyboard() *keyboard { } } -func (kb *keyboard) Handler() bool { +func (kb *keyboard) TxHandler() bool { kb.waitTxc = false if b, ok := kb.buf.Get(); ok { kb.waitTxc = true @@ -91,6 +91,13 @@ func (kb *keyboard) Handler() bool { return false } +func (kb *keyboard) RxHandler(b []byte) bool { + if len(b) >= 2 && b[0] == 2 /* ReportID */ { + kb.led = b[1] + } + return false +} + func (kb *keyboard) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if kb.waitTxc { @@ -102,6 +109,18 @@ func (kb *keyboard) tx(b []byte) { } } +func (kb *keyboard) NumLockLed() bool { + return kb.led&1 != 0 +} + +func (kb *keyboard) CapsLockLed() bool { + return kb.led&2 != 0 +} + +func (kb *keyboard) ScrollLockLed() bool { + return kb.led&4 != 0 +} + func (kb *keyboard) ready() bool { return true } diff --git a/src/machine/usb/hid/mouse/mouse.go b/src/machine/usb/hid/mouse/mouse.go index d778fd2018..d790bdbb83 100644 --- a/src/machine/usb/hid/mouse/mouse.go +++ b/src/machine/usb/hid/mouse/mouse.go @@ -47,7 +47,7 @@ func newMouse() *mouse { } } -func (m *mouse) Handler() bool { +func (m *mouse) TxHandler() bool { m.waitTxc = false if b, ok := m.buf.Get(); ok { m.waitTxc = true @@ -57,6 +57,10 @@ func (m *mouse) Handler() bool { return false } +func (m *mouse) RxHandler(b []byte) bool { + return false +} + func (m *mouse) tx(b []byte) { if machine.USBDev.InitEndpointComplete { if m.waitTxc { diff --git a/src/machine/watchdog.go b/src/machine/watchdog.go new file mode 100644 index 0000000000..d1516350d7 --- /dev/null +++ b/src/machine/watchdog.go @@ -0,0 +1,34 @@ +//go:build nrf52840 || nrf52833 || rp2040 || atsamd51 || atsame5x || stm32 + +package machine + +// WatchdogConfig holds configuration for the watchdog timer. +type WatchdogConfig struct { + // The timeout (in milliseconds) before the watchdog fires. + // + // If the requested timeout exceeds `MaxTimeout` it will be rounded + // down. + TimeoutMillis uint32 +} + +// watchdog must be implemented by any platform supporting watchdog functionality +type watchdog interface { + // Configure the watchdog. + // + // This method should not be called after the watchdog is started and on + // some platforms attempting to reconfigure after starting the watchdog + // is explicitly forbidden / will not work. + Configure(config WatchdogConfig) error + + // Starts the watchdog. + Start() error + + // Update the watchdog, indicating that the app is healthy. + Update() +} + +// Ensure required public symbols var exists and meets interface spec +var _ = watchdog(Watchdog) + +// Ensure required public constants exist +const _ = WatchdogMaxTimeout diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index f290ea38e7..baacdd68dc 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 +//go:build linux && !baremetal && !wasi && !wasip1 package os diff --git a/src/os/dir_wasi.go b/src/os/dir_wasi.go index 088dcc7ff0..23be3950ee 100644 --- a/src/os/dir_wasi.go +++ b/src/os/dir_wasi.go @@ -6,7 +6,7 @@ // 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 +//go:build wasi || wasip1 package os @@ -53,9 +53,6 @@ func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEn if dirent == nil { // EOF break } - if dirent.Ino == 0 { - continue - } name := dirent.Name() // Check for useless names before allocating a string. if string(name) == "." || string(name) == ".." { diff --git a/src/os/env_unix_test.go b/src/os/env_unix_test.go index b5f6473e14..034f481544 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 +//go:build darwin || linux || wasip1 package os_test diff --git a/src/os/errors.go b/src/os/errors.go index 23b738c97c..4b087a8435 100644 --- a/src/os/errors.go +++ b/src/os/errors.go @@ -32,6 +32,10 @@ var ( // The following code is copied from the official implementation. // src/internal/poll/fd.go +// ErrNoDeadline is returned when a request is made to set a deadline +// on a file type that does not use the poller. +var ErrNoDeadline = errors.New("file type does not support deadline") + // ErrDeadlineExceeded is returned for an expired deadline. // This is exported by the os package as os.ErrDeadlineExceeded. var ErrDeadlineExceeded error = &DeadlineExceededError{} diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go index 151520cca7..3ccb6963bb 100644 --- a/src/os/exec_posix.go +++ b/src/os/exec_posix.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 aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || windows +//go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris || wasip1 || windows package os diff --git a/src/os/file_other.go b/src/os/file_other.go index 7e5833d353..d093e3d184 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi) +//go:build baremetal || (wasm && !wasi && !wasip1) package os diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 6314db8fae..665fb0937e 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) +//go:build darwin || (linux && !baremetal) || wasip1 // target wasi sets GOOS=linux and thus the +linux build tag, // even though it doesn't show up in "tinygo info target -wasi" diff --git a/src/os/getpagesize_test.go b/src/os/getpagesize_test.go index 705cfd47d8..80475552be 100644 --- a/src/os/getpagesize_test.go +++ b/src/os/getpagesize_test.go @@ -1,4 +1,4 @@ -//go:build windows || darwin || (linux && !baremetal) +//go:build windows || darwin || (linux && !baremetal) || wasip1 package os_test diff --git a/src/os/is_wasi_no_test.go b/src/os/is_wasi_no_test.go index a8d04b674a..aa7745085b 100644 --- a/src/os/is_wasi_no_test.go +++ b/src/os/is_wasi_no_test.go @@ -1,4 +1,4 @@ -//go:build !wasi +//go:build !wasi && !wasip1 package os_test diff --git a/src/os/is_wasi_test.go b/src/os/is_wasi_test.go index 551e000752..619b1cb64f 100644 --- a/src/os/is_wasi_test.go +++ b/src/os/is_wasi_test.go @@ -1,4 +1,4 @@ -//go:build wasi +//go:build wasi || wasip1 package os_test diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 76bf4f368a..44606e163b 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) +//go:build windows || darwin || (linux && !baremetal) || wasip1 package os_test @@ -23,7 +23,6 @@ var dot = []string{ "os_test.go", "types.go", "stat_darwin.go", - "stat_linux.go", } func randomName() string { diff --git a/src/os/os_chmod_test.go b/src/os/os_chmod_test.go index af54815d84..911438d954 100644 --- a/src/os/os_chmod_test.go +++ b/src/os/os_chmod_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasi && !wasip1 // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/src/os/os_symlink_test.go b/src/os/os_symlink_test.go index f231f8d9d5..f252116f5a 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 +//go:build !windows && !baremetal && !js && !wasi && !wasip1 // Copyright 2009 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 6915dfaf91..e037b23498 100644 --- a/src/os/read_test.go +++ b/src/os/read_test.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !js && !wasi +//go:build !baremetal && !js && !wasi && !wasip1 // 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 03339ae3bb..ae945c2497 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 +//go:build !baremetal && !js && !wasi && !wasip1 package os diff --git a/src/os/removeall_other.go b/src/os/removeall_other.go index a388a6c222..ec055a9875 100644 --- a/src/os/removeall_other.go +++ b/src/os/removeall_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || js || wasi +//go:build baremetal || js || wasi || wasip1 // 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_linux.go b/src/os/stat_linuxlike.go similarity index 82% rename from src/os/stat_linux.go rename to src/os/stat_linuxlike.go index d407c5c7ec..f2ff8a5f61 100644 --- a/src/os/stat_linux.go +++ b/src/os/stat_linuxlike.go @@ -1,9 +1,13 @@ -//go:build linux && !baremetal +//go:build (linux && !baremetal) || wasip1 // 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. +// Note: this file is used for both Linux and WASI. +// Eventually it might be better to spit it up, and make the syscall constants +// match the typical WASI constants instead of the Linux-equivalents used here. + package os import ( diff --git a/src/os/stat_other.go b/src/os/stat_other.go index 162313b115..ff1bc37745 100644 --- a/src/os/stat_other.go +++ b/src/os/stat_other.go @@ -1,4 +1,4 @@ -//go:build baremetal || (wasm && !wasi) +//go:build baremetal || (wasm && !wasi && !wasip1) // 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 4667d96a70..54b2bb4857 100644 --- a/src/os/stat_unix.go +++ b/src/os/stat_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) +//go:build darwin || (linux && !baremetal) || wasip1 // 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_test.go b/src/os/tempfile_test.go index f25390d6c2..4b7416f4e0 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 +//go:build !baremetal && !js && !wasi && !wasip1 package os_test diff --git a/src/os/types_unix.go b/src/os/types_unix.go index 68a4e628ad..943fc00f52 100644 --- a/src/os/types_unix.go +++ b/src/os/types_unix.go @@ -1,4 +1,4 @@ -//go:build darwin || (linux && !baremetal) +//go:build darwin || (linux && !baremetal) || wasip1 // 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 4f9f223f17..39fc7a3747 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -790,6 +790,8 @@ func TestFunctionValue(t *testing.T) { assert(t, v.Type().String(), "func()") } +*/ + func TestGrow(t *testing.T) { v := ValueOf([]int(nil)) shouldPanic("reflect.Value.Grow using unaddressable value", func() { v.Grow(0) }) @@ -857,8 +859,6 @@ func TestGrow(t *testing.T) { }) } -*/ - var appendTests = []struct { orig, extra []int }{ @@ -1559,8 +1559,6 @@ func TestIsZero(t *testing.T) { t.Errorf("%d: IsZero(Zero(TypeOf((%s)(%+v)))) is false", i, x.Kind(), tt.x) } - /* // TODO(tinygo): missing SetZero support - p := New(x.Type()).Elem() p.Set(x) p.SetZero() @@ -1568,7 +1566,6 @@ func TestIsZero(t *testing.T) { t.Errorf("%d: IsZero((%s)(%+v)) is true after SetZero", i, p.Kind(), tt.x) } - */ } diff --git a/src/reflect/type.go b/src/reflect/type.go index 5a2ae3b748..bd717af0d6 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -601,6 +601,18 @@ func (t *rawType) Kind() Kind { 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 { @@ -619,14 +631,14 @@ func (t *rawType) elem() *rawType { case Chan, Slice, Array, Map: return (*elemType)(unsafe.Pointer(underlying)).elem default: - panic(&TypeError{"Elem"}) + panic(errTypeElem) } } func (t *rawType) key() *rawType { underlying := t.underlying() if underlying.Kind() != Map { - panic(&TypeError{"Key"}) + panic(errTypeKey) } return (*mapType)(unsafe.Pointer(underlying)).key } @@ -683,7 +695,7 @@ func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data // For internal use only. func (t *rawType) rawField(n int) rawStructField { if t.Kind() != Struct { - panic(&TypeError{"Field"}) + panic(errTypeField) } descriptor := (*structType)(unsafe.Pointer(t.underlying())) if uint(n) >= uint(descriptor.numField) { @@ -716,7 +728,7 @@ func (t *rawType) rawField(n int) rawStructField { // For internal use only. func (t *rawType) rawFieldByNameFunc(match func(string) bool) (rawStructField, []int, bool) { if t.Kind() != Struct { - panic(&TypeError{"Field"}) + panic(errTypeField) } type fieldWalker struct { @@ -812,14 +824,14 @@ func (t *rawType) Bits() int { if kind >= Int && kind <= Complex128 { return int(t.Size()) * 8 } - panic(TypeError{"Bits"}) + 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(TypeError{"Len"}) + panic(errTypeLen) } return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) @@ -829,7 +841,7 @@ func (t *rawType) Len() int { // type kinds. func (t *rawType) NumField() int { if t.Kind() != Struct { - panic(&TypeError{"NumField"}) + panic(errTypeNumField) } return int((*structType)(unsafe.Pointer(t.underlying())).numField) } @@ -973,7 +985,7 @@ func (t *rawType) isBinary() bool { func (t *rawType) ChanDir() ChanDir { if t.Kind() != Chan { - panic(TypeError{"ChanDir"}) + panic(errTypeChanDir) } dir := int((*elemType)(unsafe.Pointer(t)).numMethod) @@ -1084,7 +1096,7 @@ func (t *rawType) PkgPath() string { func (t *rawType) FieldByName(name string) (StructField, bool) { if t.Kind() != Struct { - panic(TypeError{"FieldByName"}) + panic(errTypeFieldByName) } field, index, ok := t.rawFieldByNameFunc(func(n string) bool { return n == name }) @@ -1131,7 +1143,7 @@ func (t *rawType) FieldByIndex(index []int) StructField { for _, n := range index { structOrPtrToStruct := ftype.Kind() == Struct || (ftype.Kind() == Pointer && ftype.elem().Kind() == Struct) if !structOrPtrToStruct { - panic(&TypeError{"FieldByIndex:" + ftype.Kind().String()}) + panic(errTypeFieldByIndex) } if ftype.Kind() == Pointer { diff --git a/src/reflect/value.go b/src/reflect/value.go index dc98d78a50..2499fca785 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -913,11 +913,12 @@ func (v Value) MapKeys() []Value { k := New(v.typecode.Key()) e := New(v.typecode.Elem()) - keyType := v.typecode.Key().(*rawType) - isKeyStoredAsInterface := keyType.Kind() != String && !keyType.isBinary() + 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 isKeyStoredAsInterface { + if shouldUnpackInterface { intf := *(*interface{})(k.value) v := ValueOf(intf) keys = append(keys, v) @@ -992,12 +993,14 @@ func (v Value) MapRange() *MapIter { } keyType := v.typecode.key() - isKeyStoredAsInterface := keyType.Kind() != String && !keyType.isBinary() + + keyTypeIsEmptyInterface := keyType.Kind() == Interface && keyType.NumMethod() == 0 + shouldUnpackInterface := !keyTypeIsEmptyInterface && keyType.Kind() != String && !keyType.isBinary() return &MapIter{ - m: v, - it: hashmapNewIterator(), - keyInterface: isKeyStoredAsInterface, + m: v, + it: hashmapNewIterator(), + unpackKeyInterface: shouldUnpackInterface, } } @@ -1007,8 +1010,8 @@ type MapIter struct { key Value val Value - valid bool - keyInterface bool + valid bool + unpackKeyInterface bool } func (it *MapIter) Key() Value { @@ -1016,7 +1019,7 @@ func (it *MapIter) Key() Value { panic("reflect.MapIter.Key called on invalid iterator") } - if it.keyInterface { + if it.unpackKeyInterface { intf := *(*interface{})(it.key.value) v := ValueOf(intf) return v @@ -1065,6 +1068,13 @@ func (v Value) Set(x Value) { memcpy(v.value, xptr, size) } +func (v Value) SetZero() { + v.checkAddressable() + v.checkRO() + size := v.typecode.Size() + memzero(v.value, size) +} + func (v Value) SetBool(x bool) { v.checkAddressable() v.checkRO() @@ -1566,6 +1576,9 @@ func (e *ValueError) Error() string { //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 @@ -1638,7 +1651,7 @@ func buflen(v Value) (unsafe.Pointer, uintptr) { func sliceGrow(buf unsafe.Pointer, oldLen, oldCap, newCap, elemSize uintptr) (unsafe.Pointer, uintptr, uintptr) // extend slice to hold n new elements -func (v *Value) extendSlice(n int) { +func extendSlice(v Value, n int) sliceHeader { if v.Kind() != Slice { panic(&ValueError{Method: "extendSlice", Kind: v.Kind()}) } @@ -1661,14 +1674,11 @@ func (v *Value) extendSlice(n int) { ncap = old.cap } - newslice := sliceHeader{ + return sliceHeader{ data: nbuf, len: nlen + uintptr(n), cap: ncap, } - - v.flags = valueFlagExported - v.value = (unsafe.Pointer)(&newslice) } // Append appends the values x to a slice s and returns the resulting slice. @@ -1678,7 +1688,9 @@ func Append(v Value, x ...Value) Value { panic(&ValueError{Method: "Append", Kind: v.Kind()}) } oldLen := v.Len() - v.extendSlice(len(x)) + newslice := extendSlice(v, len(x)) + v.flags = valueFlagExported + v.value = (unsafe.Pointer)(&newslice) for i, xx := range x { v.Index(oldLen + i).Set(xx) } @@ -1713,6 +1725,27 @@ func AppendSlice(s, t Value) 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) diff --git a/src/reflect/value_test.go b/src/reflect/value_test.go index ef6eabcf2f..9be9789e15 100644 --- a/src/reflect/value_test.go +++ b/src/reflect/value_test.go @@ -191,6 +191,24 @@ func TestTinyMap(t *testing.T) { if _, ok := utIterKey.Interface().(unmarshalerText); !ok { t.Errorf("Map keys via MapIter() have wrong type: %v", utIterKey.Type().String()) } + + { + m := map[any]any{1: 2} + rv := ValueOf(m) + iter := rv.MapRange() + + iter.Next() + k := iter.Key() + if k.Kind().String() != "interface" { + t.Errorf("map[any]any MapRange has wrong key type: %v", k.Kind().String()) + } + + keys := rv.MapKeys() + if k := keys[0]; k.Kind().String() != "interface" { + t.Errorf("map[any]any MapRange has wrong key type: %v", k.Kind().String()) + } + + } } // For an interface type, it returns the number of exported and unexported methods. @@ -542,7 +560,7 @@ type methodStruct struct { i int } -func (m methodStruct) valueMethod1() int { +func (m methodStruct) ValueMethod1() int { return m.i } @@ -550,11 +568,11 @@ func (m methodStruct) valueMethod2() int { return m.i } -func (m *methodStruct) pointerMethod1() int { +func (m *methodStruct) PointerMethod1() int { return m.i } -func (m *methodStruct) pointerMethod2() int { +func (m *methodStruct) PointerMethod2() int { return m.i } @@ -564,12 +582,12 @@ func (m *methodStruct) pointerMethod3() int { func TestTinyNumMethods(t *testing.T) { refptrt := TypeOf(&methodStruct{}) - if got, want := refptrt.NumMethod(), 2+3; got != want { + if got, want := refptrt.NumMethod(), 1+2; got != want { t.Errorf("Pointer Methods=%v, want %v", got, want) } reft := refptrt.Elem() - if got, want := reft.NumMethod(), 2; got != want { + if got, want := reft.NumMethod(), 1; got != want { t.Errorf("Value Methods=%v, want %v", got, want) } } diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index a51191bb95..173d0db25e 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -38,6 +38,8 @@ func growHeap() bool { //export malloc func libc_malloc(size uintptr) unsafe.Pointer { + // Note: this zeroes the returned buffer which is not necessary. + // The same goes for bytealg.MakeNoZero. return alloc(size, nil) } diff --git a/src/runtime/env.go b/src/runtime/env.go index dceb6bd189..72232e2f58 100644 --- a/src/runtime/env.go +++ b/src/runtime/env.go @@ -1,4 +1,4 @@ -//go:build linux || darwin || windows +//go:build linux || darwin || windows || wasip1 package runtime diff --git a/src/runtime/env_unix.go b/src/runtime/env_unix.go index e205c14d6f..42dfd5158f 100644 --- a/src/runtime/env_unix.go +++ b/src/runtime/env_unix.go @@ -1,4 +1,4 @@ -//go:build linux || darwin +//go:build linux || darwin || wasip1 package runtime diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 8a902a55d2..dfbec300ed 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -91,6 +91,35 @@ func hashmapMakeUnsafePointer(keySize, valueSize uintptr, sizeHint uintptr, alg return (unsafe.Pointer)(hashmapMake(keySize, valueSize, sizeHint, alg)) } +// Remove all entries from the map, without actually deallocating the space for +// it. This is used for the clear builtin, and can be used to reuse a map (to +// avoid extra heap allocations). +func hashmapClear(m *hashmap) { + if m == nil { + // Nothing to do. According to the spec: + // > If the map or slice is nil, clear is a no-op. + return + } + + m.count = 0 + numBuckets := uintptr(1) << m.bucketBits + bucketSize := hashmapBucketSize(m) + for i := uintptr(0); i < numBuckets; i++ { + bucket := hashmapBucketAddr(m, m.buckets, i) + for bucket != nil { + // Clear the tophash, to mark these keys/values as removed. + bucket.tophash = [8]uint8{} + + // Clear the keys and values in the bucket so that the GC won't pin + // these allocations. + memzero(unsafe.Add(unsafe.Pointer(bucket), unsafe.Sizeof(hashmapBucket{})), bucketSize-unsafe.Sizeof(hashmapBucket{})) + + // Move on to the next bucket in the chain. + bucket = bucket.next + } + } +} + func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool { switch alg { case hashmapAlgorithmBinary: diff --git a/src/runtime/metrics.go b/src/runtime/metrics.go new file mode 100644 index 0000000000..bd07777db6 --- /dev/null +++ b/src/runtime/metrics.go @@ -0,0 +1,10 @@ +package runtime + +// Implementation of functions needed by runtime/metrics. +// Mostly just dummy implementations: we don't currently use any of these +// metrics. + +//go:linkname godebug_registerMetric internal/godebug.registerMetric +func godebug_registerMetric(name string, read func() uint64) { + // Dummy function for compatibility with Go 1.21. +} diff --git a/src/runtime/os_wasip1.go b/src/runtime/os_wasip1.go new file mode 100644 index 0000000000..bc233ffe85 --- /dev/null +++ b/src/runtime/os_wasip1.go @@ -0,0 +1,10 @@ +//go:build wasip1 + +package runtime + +// The actual GOOS=wasip1, as newly added in the Go 1.21 toolchain. +// Previously we supported -target=wasi, but that was essentially faked by using +// linux/arm instead because that was the closest thing that was already +// supported in the Go standard library. + +const GOOS = "wasip1" diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index ceb6a2240c..ac7cd25c93 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -100,3 +100,27 @@ func godebug_setUpdate(update func(string, string)) { // variable changes (for example, via os.Setenv). godebugUpdate = update } + +//go:linkname godebug_setNewIncNonDefault internal/godebug.setNewIncNonDefault +func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) { + // Dummy function necessary in Go 1.21. +} + +// Write to the given file descriptor. +// This is called from internal/godebug starting with Go 1.21, and only seems to +// be called with the stderr file descriptor. +func write(fd uintptr, p unsafe.Pointer, n int32) int32 { + if fd == 2 { // stderr + // Convert to a string, because we know that p won't change during the + // call to printstring. + // TODO: use unsafe.String instead once we require Go 1.20. + s := _string{ + ptr: (*byte)(p), + length: uintptr(n), + } + str := *(*string)(unsafe.Pointer(&s)) + printstring(str) + return n + } + return 0 +} diff --git a/src/runtime/runtime_nintendoswitch.go b/src/runtime/runtime_nintendoswitch.go index f2606023ff..bcfa5151d0 100644 --- a/src/runtime/runtime_nintendoswitch.go +++ b/src/runtime/runtime_nintendoswitch.go @@ -105,7 +105,7 @@ func abort() { } //export write -func write(fd int32, buf *byte, count int) int { +func libc_write(fd int32, buf *byte, count int) int { // TODO: Proper handling write for i := 0; i < count; i++ { putchar(*buf) diff --git a/src/runtime/runtime_wasm_js.go b/src/runtime/runtime_wasm_js.go index c735c76fc1..18ca44abec 100644 --- a/src/runtime/runtime_wasm_js.go +++ b/src/runtime/runtime_wasm_js.go @@ -1,4 +1,4 @@ -//go:build wasm && !wasi +//go:build wasm && !wasi && !wasip1 package runtime @@ -44,8 +44,8 @@ func nanosecondsToTicks(ns int64) timeUnit { // This function is called by the scheduler. // Schedule a call to runtime.scheduler, do not actually sleep. // -//export runtime.sleepTicks +//go:wasmimport gojs runtime.sleepTicks func sleepTicks(d timeUnit) -//export runtime.ticks +//go:wasmimport gojs runtime.ticks func ticks() timeUnit diff --git a/src/runtime/runtime_wasm_js_scheduler.go b/src/runtime/runtime_wasm_js_scheduler.go index 8f8823fef2..fc599a2a82 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 +//go:build wasm && !wasi && !scheduler.none && !wasip1 package runtime diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasm_wasi.go index aec27e4c81..f258039ae6 100644 --- a/src/runtime/runtime_wasm_wasi.go +++ b/src/runtime/runtime_wasm_wasi.go @@ -1,4 +1,4 @@ -//go:build tinygo.wasm && wasi +//go:build tinygo.wasm && (wasi || wasip1) package runtime diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go index cfc9885d7f..37c21ee895 100644 --- a/src/runtime/symtab.go +++ b/src/runtime/symtab.go @@ -5,11 +5,14 @@ type Frames struct { } type Frame struct { + PC uintptr + + Func *Func + Function string File string Line int - PC uintptr } func CallersFrames(callers []uintptr) *Frames { diff --git a/src/sync/oncefunc.go b/src/sync/oncefunc.go new file mode 100644 index 0000000000..9ef8344132 --- /dev/null +++ b/src/sync/oncefunc.go @@ -0,0 +1,97 @@ +// 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 sync + +// OnceFunc returns a function that invokes f only once. The returned function +// may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceFunc(f func()) func() { + var ( + once Once + valid bool + p any + ) + // Construct the inner closure just once to reduce costs on the fast path. + g := func() { + defer func() { + p = recover() + if !valid { + // Re-panic immediately so on the first call the user gets a + // complete stack trace into f. + panic(p) + } + }() + f() + valid = true // Set only if f does not panic + } + return func() { + once.Do(g) + if !valid { + panic(p) + } + } +} + +// OnceValue returns a function that invokes f only once and returns the value +// returned by f. The returned function may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceValue[T any](f func() T) func() T { + var ( + once Once + valid bool + p any + result T + ) + g := func() { + defer func() { + p = recover() + if !valid { + panic(p) + } + }() + result = f() + valid = true + } + return func() T { + once.Do(g) + if !valid { + panic(p) + } + return result + } +} + +// OnceValues returns a function that invokes f only once and returns the values +// returned by f. The returned function may be called concurrently. +// +// If f panics, the returned function will panic with the same value on every call. +func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) { + var ( + once Once + valid bool + p any + r1 T1 + r2 T2 + ) + g := func() { + defer func() { + p = recover() + if !valid { + panic(p) + } + }() + r1, r2 = f() + valid = true + } + return func() (T1, T2) { + once.Do(g) + if !valid { + panic(p) + } + return r1, r2 + } +} diff --git a/src/sync/oncefunc_test.go b/src/sync/oncefunc_test.go new file mode 100644 index 0000000000..0273b894a9 --- /dev/null +++ b/src/sync/oncefunc_test.go @@ -0,0 +1,159 @@ +// 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 sync_test + +import ( + "sync" + "testing" +) + +// We assume that the Once.Do tests have already covered parallelism. + +func TestOnceFunc(t *testing.T) { + calls := 0 + f := sync.OnceFunc(func() { calls++ }) + allocs := testing.AllocsPerRun(10, f) + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +func TestOnceValue(t *testing.T) { + calls := 0 + f := sync.OnceValue(func() int { + calls++ + return calls + }) + allocs := testing.AllocsPerRun(10, func() { f() }) + value := f() + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if value != 1 { + t.Errorf("want value==1, got %d", value) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +func TestOnceValues(t *testing.T) { + calls := 0 + f := sync.OnceValues(func() (int, int) { + calls++ + return calls, calls + 1 + }) + allocs := testing.AllocsPerRun(10, func() { f() }) + v1, v2 := f() + if calls != 1 { + t.Errorf("want calls==1, got %d", calls) + } + if v1 != 1 || v2 != 2 { + t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2) + } + if allocs != 0 { + t.Errorf("want 0 allocations per call, got %v", allocs) + } +} + +// TODO: need to implement more complete panic handling for these tests. +// func testOncePanicX(t *testing.T, calls *int, f func()) { +// testOncePanicWith(t, calls, f, func(label string, p any) { +// if p != "x" { +// t.Fatalf("%s: want panic %v, got %v", label, "x", p) +// } +// }) +// } + +// func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) { +// // Check that the each call to f panics with the same value, but the +// // underlying function is only called once. +// for _, label := range []string{"first time", "second time"} { +// var p any +// panicked := true +// func() { +// defer func() { +// p = recover() +// }() +// f() +// panicked = false +// }() +// if !panicked { +// t.Fatalf("%s: f did not panic", label) +// } +// check(label, p) +// } +// if *calls != 1 { +// t.Errorf("want calls==1, got %d", *calls) +// } +// } + +// func TestOnceFuncPanic(t *testing.T) { +// calls := 0 +// f := sync.OnceFunc(func() { +// calls++ +// panic("x") +// }) +// testOncePanicX(t, &calls, f) +// } + +// func TestOnceValuePanic(t *testing.T) { +// calls := 0 +// f := sync.OnceValue(func() int { +// calls++ +// panic("x") +// }) +// testOncePanicX(t, &calls, func() { f() }) +// } + +// func TestOnceValuesPanic(t *testing.T) { +// calls := 0 +// f := sync.OnceValues(func() (int, int) { +// calls++ +// panic("x") +// }) +// testOncePanicX(t, &calls, func() { f() }) +// } +// +// func TestOnceFuncPanicNil(t *testing.T) { +// calls := 0 +// f := sync.OnceFunc(func() { +// calls++ +// panic(nil) +// }) +// testOncePanicWith(t, &calls, f, func(label string, p any) { +// switch p.(type) { +// case nil, *runtime.PanicNilError: +// return +// } +// t.Fatalf("%s: want nil panic, got %v", label, p) +// }) +// } +// +// func TestOnceFuncGoexit(t *testing.T) { +// // If f calls Goexit, the results are unspecified. But check that f doesn't +// // get called twice. +// calls := 0 +// f := sync.OnceFunc(func() { +// calls++ +// runtime.Goexit() +// }) +// var wg sync.WaitGroup +// for i := 0; i < 2; i++ { +// wg.Add(1) +// go func() { +// defer wg.Done() +// defer func() { recover() }() +// f() +// }() +// wg.Wait() +// } +// if calls != 1 { +// t.Errorf("want calls==1, got %d", calls) +// } +// } diff --git a/src/syscall/errno_other.go b/src/syscall/errno_other.go index 19822a1c6c..a001096525 100644 --- a/src/syscall/errno_other.go +++ b/src/syscall/errno_other.go @@ -1,4 +1,4 @@ -//go:build !wasi && !darwin +//go:build !wasi && !wasip1 && !darwin package syscall diff --git a/src/syscall/file_emulated.go b/src/syscall/file_emulated.go index f12d74e58e..5ed57f13b4 100644 --- a/src/syscall/file_emulated.go +++ b/src/syscall/file_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || wasm +//go:build baremetal || (wasm && !wasip1) // 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 553322f02c..d9198a779d 100644 --- a/src/syscall/file_hosted.go +++ b/src/syscall/file_hosted.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !wasm +//go:build !(baremetal || (wasm && !wasip1)) // This file assumes there is a libc available that runs on a real operating // system. diff --git a/src/syscall/proc_emulated.go b/src/syscall/proc_emulated.go index 46570f5304..d8e7ff7a92 100644 --- a/src/syscall/proc_emulated.go +++ b/src/syscall/proc_emulated.go @@ -1,4 +1,4 @@ -//go:build baremetal || wasi || wasm +//go:build baremetal || tinygo.wasm // 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 ab35762d06..05c509e6ff 100644 --- a/src/syscall/proc_hosted.go +++ b/src/syscall/proc_hosted.go @@ -1,4 +1,4 @@ -//go:build !baremetal && !wasi && !wasm +//go:build !baremetal && !tinygo.wasm // This file assumes there is a libc available that runs on a real operating // system. diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index ea0a000d18..7a13245b6d 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -1,4 +1,4 @@ -//go:build darwin || nintendoswitch || wasi +//go:build darwin || nintendoswitch || wasi || wasip1 package syscall diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index 704ba29cae..d64f1061f3 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -68,6 +68,7 @@ const ( EISDIR Errno = 21 EINVAL Errno = 22 EMFILE Errno = 24 + EROFS Errno = 30 EPIPE Errno = 32 EAGAIN Errno = 35 ENOTCONN Errno = 57 diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index a3bd3a4872..5e6a231dff 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -1,4 +1,4 @@ -//go:build wasi +//go:build wasi || wasip1 package syscall diff --git a/src/testing/is_wasi_no_test.go b/src/testing/is_wasi_no_test.go index 86fab391eb..630467ec0b 100644 --- a/src/testing/is_wasi_no_test.go +++ b/src/testing/is_wasi_no_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 !wasi +//go:build !wasi && !wasip1 package testing_test diff --git a/src/testing/is_wasi_test.go b/src/testing/is_wasi_test.go index e50af901b2..e20e15fc04 100644 --- a/src/testing/is_wasi_test.go +++ b/src/testing/is_wasi_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 wasi +//go:build wasi || wasip1 package testing_test diff --git a/src/testing/testing.go b/src/testing/testing.go index fee9d40b19..8429e92212 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -120,6 +120,15 @@ func Verbose() bool { return flagVerbose } +// String constant that is being set when running a test. +var testBinary string + +// Testing returns whether the program was compiled as a test, using "tinygo +// test". It returns false when built using "tinygo build", "tinygo flash", etc. +func Testing() bool { + return testBinary == "1" +} + // flushToParent writes c.output to the parent after first writing the header // with the given format and arguments. func (c *common) flushToParent(testName, format string, args ...interface{}) { diff --git a/src/testing/testing_test.go b/src/testing/testing_test.go index 8a2586535e..631a313414 100644 --- a/src/testing/testing_test.go +++ b/src/testing/testing_test.go @@ -195,3 +195,9 @@ func TestSetenv(t *testing.T) { } } } + +func TestTesting(t *testing.T) { + if !testing.Testing() { + t.Error("Expected testing.Testing() to return true while in a test") + } +} diff --git a/targets/ae-rp2040.json b/targets/ae-rp2040.json new file mode 100644 index 0000000000..026ea4437c --- /dev/null +++ b/targets/ae-rp2040.json @@ -0,0 +1,14 @@ +{ + "inherits": [ + "rp2040" + ], + "build-tags": ["ae_rp2040"], + "serial-port": ["2e8a:000A"], + "ldflags": [ + "--defsym=__flash_size=2048K" + ], + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} + diff --git a/targets/pca10056-s140v6-uf2.json b/targets/pca10056-s140v6-uf2.json new file mode 100644 index 0000000000..341a620489 --- /dev/null +++ b/targets/pca10056-s140v6-uf2.json @@ -0,0 +1,6 @@ +{ + "inherits": ["nrf52840", "nrf52840-s140v6-uf2"], + "build-tags": ["pca10056"], + "serial-port": ["239a:0x0029"], + "msd-volume-name": ["NRF52BOOT"] +} diff --git a/targets/rp2040-boot-stage2.S b/targets/rp2040-boot-stage2.S index 9cc2ce09b9..e5135c9d4e 100644 --- a/targets/rp2040-boot-stage2.S +++ b/targets/rp2040-boot-stage2.S @@ -63,7 +63,7 @@ // Expanded include files // #define CMD_WRITE_ENABLE 0x06 -#define CMD_READ_STATUS 0x05 +#define CMD_READ_STATUS1 0x05 #define CMD_READ_STATUS2 0x35 #define CMD_WRITE_STATUS1 0x01 #define CMD_WRITE_STATUS2 0x31 @@ -301,7 +301,7 @@ program_sregs: # endif // Poll status register for write completion 1: - movs r0, #CMD_READ_STATUS + movs r0, #CMD_READ_STATUS1 bl read_flash_sreg movs r1, #1 tst r0, r1 diff --git a/targets/wasi.json b/targets/wasi.json index 4c43193f04..83f808e8ee 100644 --- a/targets/wasi.json +++ b/targets/wasi.json @@ -7,6 +7,7 @@ "goarch": "arm", "linker": "wasm-ld", "libc": "wasi-libc", + "rtlib": "compiler-rt", "scheduler": "asyncify", "default-stack-size": 16384, "cflags": [ @@ -21,6 +22,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "wasmtime --mapdir=/tmp::{tmpDir} {}", - "wasm-abi": "generic" + "emulator": "wasmtime --mapdir=/tmp::{tmpDir} {}" } diff --git a/targets/wasm.json b/targets/wasm.json index 26494cc450..4e0de37d1e 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -7,6 +7,7 @@ "goarch": "wasm", "linker": "wasm-ld", "libc": "wasi-libc", + "rtlib": "compiler-rt", "scheduler": "asyncify", "default-stack-size": 16384, "cflags": [ @@ -22,6 +23,5 @@ "extra-files": [ "src/runtime/asm_tinygowasm.S" ], - "emulator": "node {root}/targets/wasm_exec.js {}", - "wasm-abi": "js" + "emulator": "node {root}/targets/wasm_exec.js {}" } diff --git a/targets/wasm_exec.js b/targets/wasm_exec.js index 8021b44eb9..5dfc67c357 100644 --- a/targets/wasm_exec.js +++ b/targets/wasm_exec.js @@ -130,6 +130,7 @@ const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); + let reinterpretBuf = new DataView(new ArrayBuffer(8)); var logLine = []; global.Go = class { @@ -142,19 +143,9 @@ return new DataView(this._inst.exports.memory.buffer); } - const setInt64 = (addr, v) => { - mem().setUint32(addr + 0, v, true); - mem().setUint32(addr + 4, Math.floor(v / 4294967296), true); - } - - const getInt64 = (addr) => { - const low = mem().getUint32(addr + 0, true); - const high = mem().getInt32(addr + 4, true); - return low + high * 4294967296; - } - - const loadValue = (addr) => { - const f = mem().getFloat64(addr, true); + const unboxValue = (v_ref) => { + reinterpretBuf.setBigInt64(0, v_ref, true); + const f = reinterpretBuf.getFloat64(0, true); if (f === 0) { return undefined; } @@ -162,71 +153,70 @@ return f; } - const id = mem().getUint32(addr, true); + const id = v_ref & 0xffffffffn; return this._values[id]; } - const storeValue = (addr, v) => { - const nanHead = 0x7FF80000; + + const loadValue = (addr) => { + let v_ref = mem().getBigUint64(addr, true); + return unboxValue(v_ref); + } + + const boxValue = (v) => { + const nanHead = 0x7FF80000n; if (typeof v === "number") { if (isNaN(v)) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 0, true); - return; + return nanHead << 32n; } if (v === 0) { - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 1, true); - return; + return (nanHead << 32n) | 1n; } - mem().setFloat64(addr, v, true); - return; + reinterpretBuf.setFloat64(0, v, true); + return reinterpretBuf.getBigInt64(0, true); } switch (v) { case undefined: - mem().setFloat64(addr, 0, true); - return; + return 0n; case null: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 2, true); - return; + return (nanHead << 32n) | 2n; case true: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 3, true); - return; + return (nanHead << 32n) | 3n; case false: - mem().setUint32(addr + 4, nanHead, true); - mem().setUint32(addr, 4, true); - return; + return (nanHead << 32n) | 4n; } let id = this._ids.get(v); if (id === undefined) { id = this._idPool.pop(); if (id === undefined) { - id = this._values.length; + id = BigInt(this._values.length); } this._values[id] = v; this._goRefCounts[id] = 0; this._ids.set(v, id); } this._goRefCounts[id]++; - let typeFlag = 1; + let typeFlag = 1n; switch (typeof v) { case "string": - typeFlag = 2; + typeFlag = 2n; break; case "symbol": - typeFlag = 3; + typeFlag = 3n; break; case "function": - typeFlag = 4; + typeFlag = 4n; break; } - mem().setUint32(addr + 4, nanHead | typeFlag, true); - mem().setUint32(addr, id, true); + return id | ((nanHead | typeFlag) << 32n); + } + + const storeValue = (addr, v) => { + let v_ref = boxValue(v); + mem().setBigUint64(addr, v_ref, true); } const loadSlice = (array, len, cap) => { @@ -294,7 +284,7 @@ return 0; }, }, - env: { + gojs: { // func ticks() float64 "runtime.ticks": () => { return timeOrigin + performance.now(); @@ -307,54 +297,54 @@ }, // func finalizeRef(v ref) - "syscall/js.finalizeRef": (sp) => { + "syscall/js.finalizeRef": (v_ref) => { // Note: TinyGo does not support finalizers so this should never be // called. console.error('syscall/js.finalizeRef not implemented'); }, // func stringVal(value string) ref - "syscall/js.stringVal": (ret_ptr, value_ptr, value_len) => { + "syscall/js.stringVal": (value_ptr, value_len) => { const s = loadString(value_ptr, value_len); - storeValue(ret_ptr, s); + return boxValue(s); }, // func valueGet(v ref, p string) ref - "syscall/js.valueGet": (retval, v_addr, p_ptr, p_len) => { + "syscall/js.valueGet": (v_ref, p_ptr, p_len) => { let prop = loadString(p_ptr, p_len); - let value = loadValue(v_addr); - let result = Reflect.get(value, prop); - storeValue(retval, result); + let v = unboxValue(v_ref); + let result = Reflect.get(v, prop); + return boxValue(result); }, // func valueSet(v ref, p string, x ref) - "syscall/js.valueSet": (v_addr, p_ptr, p_len, x_addr) => { - const v = loadValue(v_addr); + "syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { + const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); - const x = loadValue(x_addr); + const x = unboxValue(x_ref); Reflect.set(v, p, x); }, // func valueDelete(v ref, p string) - "syscall/js.valueDelete": (v_addr, p_ptr, p_len) => { - const v = loadValue(v_addr); + "syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { + const v = unboxValue(v_ref); const p = loadString(p_ptr, p_len); Reflect.deleteProperty(v, p); }, // func valueIndex(v ref, i int) ref - "syscall/js.valueIndex": (ret_addr, v_addr, i) => { - storeValue(ret_addr, Reflect.get(loadValue(v_addr), i)); + "syscall/js.valueIndex": (v_ref, i) => { + return boxValue(Reflect.get(unboxValue(v_ref), i)); }, // valueSetIndex(v ref, i int, x ref) - "syscall/js.valueSetIndex": (v_addr, i, x_addr) => { - Reflect.set(loadValue(v_addr), i, loadValue(x_addr)); + "syscall/js.valueSetIndex": (v_ref, i, x_ref) => { + Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); }, // func valueCall(v ref, m string, args []ref) (ref, bool) - "syscall/js.valueCall": (ret_addr, v_addr, m_ptr, m_len, args_ptr, args_len, args_cap) => { - const v = loadValue(v_addr); + "syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); const name = loadString(m_ptr, m_len); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { @@ -368,9 +358,9 @@ }, // func valueInvoke(v ref, args []ref) (ref, bool) - "syscall/js.valueInvoke": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { + "syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { try { - const v = loadValue(v_addr); + const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); storeValue(ret_addr, Reflect.apply(v, undefined, args)); mem().setUint8(ret_addr + 8, 1); @@ -381,8 +371,8 @@ }, // func valueNew(v ref, args []ref) (ref, bool) - "syscall/js.valueNew": (ret_addr, v_addr, args_ptr, args_len, args_cap) => { - const v = loadValue(v_addr); + "syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { + const v = unboxValue(v_ref); const args = loadSliceOfValues(args_ptr, args_len, args_cap); try { storeValue(ret_addr, Reflect.construct(v, args)); @@ -394,66 +384,70 @@ }, // func valueLength(v ref) int - "syscall/js.valueLength": (v_addr) => { - return loadValue(v_addr).length; + "syscall/js.valueLength": (v_ref) => { + return unboxValue(v_ref).length; }, // valuePrepareString(v ref) (ref, int) - "syscall/js.valuePrepareString": (ret_addr, v_addr) => { - const s = String(loadValue(v_addr)); + "syscall/js.valuePrepareString": (ret_addr, v_ref) => { + const s = String(unboxValue(v_ref)); const str = encoder.encode(s); storeValue(ret_addr, str); - setInt64(ret_addr + 8, str.length); + mem().setInt32(ret_addr + 8, str.length, true); }, // valueLoadString(v ref, b []byte) - "syscall/js.valueLoadString": (v_addr, slice_ptr, slice_len, slice_cap) => { - const str = loadValue(v_addr); + "syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { + const str = unboxValue(v_ref); loadSlice(slice_ptr, slice_len, slice_cap).set(str); }, // func valueInstanceOf(v ref, t ref) bool - "syscall/js.valueInstanceOf": (v_addr, t_addr) => { - return loadValue(v_addr) instanceof loadValue(t_addr); + "syscall/js.valueInstanceOf": (v_ref, t_ref) => { + return unboxValue(v_ref) instanceof unboxValue(t_ref); }, // func copyBytesToGo(dst []byte, src ref) (int, bool) - "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, source_addr) => { + "syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable const dst = loadSlice(dest_addr, dest_len); - const src = loadValue(source_addr); + const src = unboxValue(src_ref); if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, // copyBytesToJS(dst ref, src []byte) (int, bool) // Originally copied from upstream Go project, then modified: // https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 - "syscall/js.copyBytesToJS": (ret_addr, dest_addr, source_addr, source_len, source_cap) => { + "syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { let num_bytes_copied_addr = ret_addr; let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable - const dst = loadValue(dest_addr); - const src = loadSlice(source_addr, source_len); + const dst = unboxValue(dst_ref); + const src = loadSlice(src_addr, src_len); if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { mem().setUint8(returned_status_addr, 0); // Return "not ok" status return; } const toCopy = src.subarray(0, dst.length); dst.set(toCopy); - setInt64(num_bytes_copied_addr, toCopy.length); + mem().setUint32(num_bytes_copied_addr, toCopy.length, true); mem().setUint8(returned_status_addr, 1); // Return "ok" status }, } }; + + // Go 1.20 uses 'env'. Go 1.21 uses 'gojs'. + // For compatibility, we use both as long as Go 1.20 is supported. + this.importObject.env = this.importObject.gojs; } async run(instance) { diff --git a/testdata/go1.21.go b/testdata/go1.21.go new file mode 100644 index 0000000000..603bd06e27 --- /dev/null +++ b/testdata/go1.21.go @@ -0,0 +1,29 @@ +package main + +func main() { + // The new min/max builtins. + ia := 1 + ib := 5 + ic := -3 + fa := 1.0 + fb := 5.0 + fc := -3.0 + println("min/max:", min(ia, ib, ic), max(ia, ib, ic)) + println("min/max:", min(fa, fb, fc), max(fa, fb, fc)) + + // The clear builtin, for slices. + s := []int{1, 2, 3, 4, 5} + clear(s[:3]) + println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4]) + + // The clear builtin, for maps. + m := map[int]string{ + 1: "one", + 2: "two", + 3: "three", + } + clear(m) + println("cleared map:", m[1], m[2], m[3], len(m)) + m[4] = "four" + println("added to cleared map:", m[1], m[2], m[3], m[4], len(m)) +} diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt new file mode 100644 index 0000000000..3edfdb4568 --- /dev/null +++ b/testdata/go1.21.txt @@ -0,0 +1,5 @@ +min/max: -3 5 +min/max: -3.000000e+000 +5.000000e+000 +cleared s[:3]: 0 0 0 4 5 +cleared map: 0 +added to cleared map: four 1 diff --git a/testdata/testing.go b/testdata/testing.go index 4c1cf44efe..ff378fea95 100644 --- a/testdata/testing.go +++ b/testdata/testing.go @@ -73,6 +73,9 @@ func fakeMatchString(pat, str string) (bool, error) { } func main() { + if testing.Testing() { + println("not running a test at the moment, testing.Testing() should return false") + } testing.Init() flag.Set("test.run", ".*/B") m := testing.MainStart(matchStringOnly(fakeMatchString /*regexp.MatchString*/), tests, benchmarks, fuzzes, examples) diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go index 8eb308c7f5..0c49986ab9 100755 --- a/tools/gen-device-svd/gen-device-svd.go +++ b/tools/gen-device-svd/gen-device-svd.go @@ -18,7 +18,7 @@ import ( ) var validName = regexp.MustCompile("^[a-zA-Z0-9_]+$") -var enumBitSpecifier = regexp.MustCompile("^#[x01]+$") +var enumBitSpecifier = regexp.MustCompile("^#x*[01]+[01x]*$") type SVDFile struct { XMLName xml.Name `xml:"device"` @@ -628,6 +628,11 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre } for _, enumEl := range enumeratedValues.EnumeratedValue { enumName := enumEl.Name + // Renesas has enum without actual values that we have to skip + if enumEl.Value == "" { + continue + } + if strings.EqualFold(enumName, "reserved") || !validName.MatchString(enumName) { continue } @@ -645,7 +650,7 @@ func parseBitfields(groupName, regName string, fieldEls []*SVDField, bitfieldPre } if err != nil { if enumBitSpecifier.MatchString(enumEl.Value) { - // NXP SVDs use the form #xx1x, #x0xx, etc for values + // NXP and Renesas SVDs use the form #xx1x, #x0xx, etc for values enumValue, err = strconv.ParseUint(strings.ReplaceAll(enumEl.Value[1:], "x", "0"), 2, 64) if err != nil { panic(err) @@ -760,6 +765,14 @@ func (r *Register) dimIndex() []string { t := strings.Split(*r.element.DimIndex, "-") if len(t) == 2 { + // renesas uses hex letters e.g. A-B + if strings.Contains("ABCDEFabcdef", t[0]) { + t[0] = "0x" + t[0] + } + if strings.Contains("ABCDEFabcdef", t[1]) { + t[1] = "0x" + t[1] + } + x, err := strconv.ParseInt(t[0], 0, 32) if err != nil { panic(err) diff --git a/transform/testdata/wasm-abi.ll b/transform/testdata/wasm-abi.ll deleted file mode 100644 index ade4b5af56..0000000000 --- a/transform/testdata/wasm-abi.ll +++ /dev/null @@ -1,28 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" -target triple = "wasm32-unknown-unknown-wasm" - -declare i64 @externalCall(ptr, i32, i64) - -define internal i64 @testCall(ptr %ptr, i32 %len, i64 %foo) { - %val = call i64 @externalCall(ptr %ptr, i32 %len, i64 %foo) - ret i64 %val -} - -define internal i64 @testCallNonEntry(ptr %ptr, i32 %len) { -entry: - br label %bb1 - -bb1: - %val = call i64 @externalCall(ptr %ptr, i32 %len, i64 3) - ret i64 %val -} - -define void @exportedFunction(i64 %foo) { - %unused = shl i64 %foo, 1 - ret void -} - -define internal void @callExportedFunction(i64 %foo) { - call void @exportedFunction(i64 %foo) - ret void -} diff --git a/transform/testdata/wasm-abi.out.ll b/transform/testdata/wasm-abi.out.ll deleted file mode 100644 index a1fc7d6a9b..0000000000 --- a/transform/testdata/wasm-abi.out.ll +++ /dev/null @@ -1,45 +0,0 @@ -target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" -target triple = "wasm32-unknown-unknown-wasm" - -declare i64 @"externalCall$i64wrap"(ptr, i32, i64) - -define internal i64 @testCall(ptr %ptr, i32 %len, i64 %foo) { - %i64asptr = alloca i64, align 8 - %i64asptr1 = alloca i64, align 8 - store i64 %foo, ptr %i64asptr1, align 8 - call void @externalCall(ptr %i64asptr, ptr %ptr, i32 %len, ptr %i64asptr1) - %retval = load i64, ptr %i64asptr, align 8 - ret i64 %retval -} - -define internal i64 @testCallNonEntry(ptr %ptr, i32 %len) { -entry: - %i64asptr = alloca i64, align 8 - %i64asptr1 = alloca i64, align 8 - br label %bb1 - -bb1: ; preds = %entry - store i64 3, ptr %i64asptr1, align 8 - call void @externalCall(ptr %i64asptr, ptr %ptr, i32 %len, ptr %i64asptr1) - %retval = load i64, ptr %i64asptr, align 8 - ret i64 %retval -} - -define internal void @"exportedFunction$i64wrap"(i64 %foo) unnamed_addr { - %unused = shl i64 %foo, 1 - ret void -} - -define internal void @callExportedFunction(i64 %foo) { - call void @"exportedFunction$i64wrap"(i64 %foo) - ret void -} - -declare void @externalCall(ptr, ptr, i32, ptr) - -define void @exportedFunction(ptr %0) { -entry: - %i64 = load i64, ptr %0, align 8 - call void @"exportedFunction$i64wrap"(i64 %i64) - ret void -} diff --git a/transform/wasm-abi.go b/transform/wasm-abi.go deleted file mode 100644 index 081558c9e4..0000000000 --- a/transform/wasm-abi.go +++ /dev/null @@ -1,167 +0,0 @@ -package transform - -import ( - "errors" - "strings" - - "github.com/tinygo-org/tinygo/compileopts" - "tinygo.org/x/go-llvm" -) - -// ExternalInt64AsPtr converts i64 parameters in externally-visible functions to -// values passed by reference (*i64), to work around the lack of 64-bit integers -// in JavaScript (commonly used together with WebAssembly). Once that's -// resolved, this pass may be avoided. For more details: -// https://github.com/WebAssembly/design/issues/1172 -// -// This pass is enabled via the wasm-abi JSON target key. -func ExternalInt64AsPtr(mod llvm.Module, config *compileopts.Config) error { - ctx := mod.Context() - builder := ctx.NewBuilder() - defer builder.Dispose() - int64Type := ctx.Int64Type() - int64PtrType := llvm.PointerType(int64Type, 0) - - // This builder is only used for creating new allocas in the entry block of - // a function, avoiding many SetInsertPoint* calls. - entryBlockBuilder := ctx.NewBuilder() - defer entryBlockBuilder.Dispose() - - for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - if fn.Linkage() != llvm.ExternalLinkage { - // Only change externally visible functions (exports and imports). - continue - } - if strings.HasPrefix(fn.Name(), "llvm.") || strings.HasPrefix(fn.Name(), "runtime.") { - // Do not try to modify the signature of internal LLVM functions and - // assume that runtime functions are only temporarily exported for - // transforms. - continue - } - if !fn.GetStringAttributeAtIndex(-1, "tinygo-methods").IsNil() { - // These are internal functions (interface method call, interface - // type assert) that will be lowered by the interface lowering pass. - // Don't transform them. - continue - } - - hasInt64 := false - paramTypes := []llvm.Type{} - - // Check return type for 64-bit integer. - fnType := fn.GlobalValueType() - returnType := fnType.ReturnType() - if returnType == int64Type { - hasInt64 = true - paramTypes = append(paramTypes, int64PtrType) - returnType = ctx.VoidType() - } - - // Check param types for 64-bit integers. - for param := fn.FirstParam(); !param.IsNil(); param = llvm.NextParam(param) { - if param.Type() == int64Type { - hasInt64 = true - paramTypes = append(paramTypes, int64PtrType) - } else { - paramTypes = append(paramTypes, param.Type()) - } - } - - if !hasInt64 { - // No i64 in the paramter list. - continue - } - - // Add $i64wrapper to the real function name as it is only used - // internally. - // Add a new function with the correct signature that is exported. - name := fn.Name() - fn.SetName(name + "$i64wrap") - externalFnType := llvm.FunctionType(returnType, paramTypes, fnType.IsFunctionVarArg()) - externalFn := llvm.AddFunction(mod, name, externalFnType) - AddStandardAttributes(fn, config) - - if fn.IsDeclaration() { - // Just a declaration: the definition doesn't exist on the Go side - // so it cannot be called from external code. - // Update all users to call the external function. - // The old $i64wrapper function could be removed, but it may as well - // be left in place. - for _, call := range getUses(fn) { - entryBlockBuilder.SetInsertPointBefore(call.InstructionParent().Parent().EntryBasicBlock().FirstInstruction()) - builder.SetInsertPointBefore(call) - callParams := []llvm.Value{} - var retvalAlloca llvm.Value - if fnType.ReturnType() == int64Type { - retvalAlloca = entryBlockBuilder.CreateAlloca(int64Type, "i64asptr") - callParams = append(callParams, retvalAlloca) - } - for i := 0; i < call.OperandsCount()-1; i++ { - operand := call.Operand(i) - if operand.Type() == int64Type { - // Pass a stack-allocated pointer instead of the value - // itself. - alloca := entryBlockBuilder.CreateAlloca(int64Type, "i64asptr") - builder.CreateStore(operand, alloca) - callParams = append(callParams, alloca) - } else { - // Unchanged parameter. - callParams = append(callParams, operand) - } - } - var callName string - if returnType.TypeKind() != llvm.VoidTypeKind { - // Only use the name of the old call instruction if the new - // call is not a void call. - // A call instruction with an i64 return type may have had a - // name, but it cannot have a name after this transform - // because the return type will now be void. - callName = call.Name() - } - if fnType.ReturnType() == int64Type { - // Pass a stack-allocated pointer as the first parameter - // where the return value should be stored, instead of using - // the regular return value. - builder.CreateCall(externalFnType, externalFn, callParams, callName) - returnValue := builder.CreateLoad(int64Type, retvalAlloca, "retval") - call.ReplaceAllUsesWith(returnValue) - call.EraseFromParentAsInstruction() - } else { - newCall := builder.CreateCall(externalFnType, externalFn, callParams, callName) - call.ReplaceAllUsesWith(newCall) - call.EraseFromParentAsInstruction() - } - } - } else { - // The function has a definition in Go. This means that it may still - // be called both Go and from external code. - // Keep existing calls with the existing convention in place (for - // better performance), but export a new wrapper function with the - // correct calling convention. - fn.SetLinkage(llvm.InternalLinkage) - fn.SetUnnamedAddr(true) - entryBlock := ctx.AddBasicBlock(externalFn, "entry") - builder.SetInsertPointAtEnd(entryBlock) - var callParams []llvm.Value - if fnType.ReturnType() == int64Type { - return errors.New("not yet implemented: exported function returns i64 with the JS wasm-abi; " + - "see https://tinygo.org/compiler-internals/calling-convention/") - } - for i, origParam := range fn.Params() { - paramValue := externalFn.Param(i) - if origParam.Type() == int64Type { - paramValue = builder.CreateLoad(int64Type, paramValue, "i64") - } - callParams = append(callParams, paramValue) - } - retval := builder.CreateCall(fn.GlobalValueType(), fn, callParams, "") - if retval.Type().TypeKind() == llvm.VoidTypeKind { - builder.CreateRetVoid() - } else { - builder.CreateRet(retval) - } - } - } - - return nil -} diff --git a/transform/wasm-abi_test.go b/transform/wasm-abi_test.go deleted file mode 100644 index 374bba1388..0000000000 --- a/transform/wasm-abi_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package transform_test - -import ( - "testing" - - "github.com/tinygo-org/tinygo/transform" - "tinygo.org/x/go-llvm" -) - -func TestWasmABI(t *testing.T) { - t.Parallel() - testTransform(t, "testdata/wasm-abi", func(mod llvm.Module) { - // Run ABI change pass. - err := transform.ExternalInt64AsPtr(mod, defaultTestConfig) - if err != nil { - t.Errorf("failed to change wasm ABI: %v", err) - } - }) -}