diff --git a/.github/workflows/build-tinygo-dev-docker.yml b/.github/workflows/build-tinygo-dev-docker.yml new file mode 100644 index 000000000..ba8ba220e --- /dev/null +++ b/.github/workflows/build-tinygo-dev-docker.yml @@ -0,0 +1,45 @@ +name: CI for tinygo-dev docker container +on: + push: + branches: [ dev ] + +jobs: + push_to_registry: + name: Push Docker image to GHCR/Docker Hub + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Check out the repo + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: | + tinygo/tinygo-dev + ghcr.io/${{ github.repository }}/tinygo-dev + tags: | + type=sha,format=long + type=raw,value=latest + - name: Log in to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + - name: Log in to Github Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.gitignore b/.gitignore index 20a0a2fb0..483c789c7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,8 @@ src/device/stm32/*.go src/device/stm32/*.s src/device/kendryte/*.go src/device/kendryte/*.s +src/device/rp/*.go +src/device/rp/*.s vendor llvm-build llvm-project diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe0f0e30..9c427d9e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,69 @@ +0.19.0 +--- + +* **command line** + - don't consider compile-only tests as failing + - add -test flag for `tinygo list` + - escape commands while printing them with the -x flag + - make flash-command portable and safer to use + - use `extended-remote` instead of `remote` in GDB + - detect specific serial port IDs based on USB vid/pid + - add a flag to the command line to select the serial implementation +* **compiler** + - `cgo`: improve constant parser + - `compiler`: support chained interrupt handlers + - `compiler`: add support for running a builtin in a goroutine + - `compiler`: do not emit nil checks for loading closure variables + - `compiler`: skip context parameter when starting regular goroutine + - `compiler`: refactor method names + - `compiler`: add function and global section pragmas + - `compiler`: implement `syscall.rawSyscallNoError` in inline assembly + - `interp`: ignore inline assembly in markExternal + - `interp`: fix a bug in pointer cast workaround + - `loader`: fix testing a main package +* **standard library** + - `crypto/rand`: replace this package with a TinyGo version + - `machine`: make USBCDC global a pointer + - `machine`: make UART objects pointer receivers + - `machine`: define Serial as the default output + - `net`: add initial support for net.IP + - `net`: add more net compatibility + - `os`: add stub for os.ReadDir + - `os`: add FileMode constants from Go 1.16 + - `os`: add stubs required for net/http + - `os`: implement process related functions + - `reflect`: implement AppendSlice + - `reflect`: add stubs required for net/http + - `runtime`: make task.Data a 64-bit integer to avoid overflow + - `runtime`: expose memory stats + - `sync`: implement NewCond + - `syscall`: fix int type in libc version +* **targets** + - `cortexm`: do not disable interrupts on abort + - `cortexm`: bump default stack size to 2048 bytes + - `nrf`: avoid heap allocation in waitForEvent + - `nrf`: don't trigger a heap allocation in SPI.Transfer + - `nrf52840`: add support for flashing with the BOSSA tool + - `rp2040`: add support for GPIO input + - `rp2040`: add basic support for ADC + - `rp2040`: gpio and adc pin definitions + - `rp2040`: implement UART + - `rp2040`: patch elf to checksum 2nd stage boot + - `stm32`: add PWM for most chips + - `stm32`: add support for pin interrupts + - `stm32f103`: add support for PinInputPullup / PinInputPulldown + - `wasi`: remove wasm build tag +* **boards** + - `feather-rp2040`: add support for this board + - `feather-nrf52840-sense`: add board definition for this board + - `pca10059`: support flashing from Windows + - `nano-rp2040`: add this board + - `nano-33-ble`: add support for this board + - `pico`: add the Raspberry Pi Pico board with the new RP2040 chip + - `qtpy`: add pin for neopixels + - all: add definition for ws2812 for supported boards + + 0.18.0 --- diff --git a/Makefile b/Makefile index da699cc3c..b62956a56 100644 --- a/Makefile +++ b/Makefile @@ -230,6 +230,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt @@ -334,9 +336,11 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1 + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1 @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1 + $(TINYGO) build -size short -o test.hex -target=qtpy examples/serial @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1 @$(MD5SUM) test.hex @@ -354,7 +358,13 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1 @$(MD5SUM) test.hex - $(TINYGO) build -size short -o test.hex -target=pico examples/blinky1 + $(TINYGO) build -size short -o test.hex -target=pico examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1 @$(MD5SUM) test.hex # test pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm @@ -428,6 +438,8 @@ endif @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo + @$(MD5SUM) test.hex $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial @$(MD5SUM) test.nro $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go diff --git a/README.md b/README.md index 21941e96b..8f233d6ba 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for You can compile TinyGo programs for microcontrollers, WebAssembly and Linux. -The following 62 microcontroller boards are currently supported: +The following 68 microcontroller boards are currently supported: * [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) @@ -52,6 +52,8 @@ The following 62 microcontroller boards are currently supported: * [Adafruit Feather M4](https://www.adafruit.com/product/3857) * [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759) * [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062) +* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516) +* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884) * [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382) * [Adafruit Grand Central M4](https://www.adafruit.com/product/4064) * [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727) @@ -68,7 +70,10 @@ The following 62 microcontroller boards are currently supported: * [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3) * [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi) * [Arduino Nano](https://store.arduino.cc/arduino-nano) -* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot) +* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble) +* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense) +* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot) +* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect) * [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3) * [Arduino Zero](https://store.arduino.cc/usa/arduino-zero) * [BBC micro:bit](https://microbit.org/) @@ -95,6 +100,7 @@ The following 62 microcontroller boards are currently supported: * [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html) * [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html) * [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) +* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) * [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) * [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html) * [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html) diff --git a/builder/build.go b/builder/build.go index 9d26df816..c1fd78e3f 100644 --- a/builder/build.go +++ b/builder/build.go @@ -12,7 +12,9 @@ import ( "errors" "fmt" "go/types" + "hash/crc32" "io/ioutil" + "math/bits" "os" "path/filepath" "runtime" @@ -98,7 +100,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil AutomaticStackSize: config.AutomaticStackSize(), DefaultStackSize: config.Target.DefaultStackSize, NeedsStackObjects: config.NeedsStackObjects(), - Debug: config.Debug(), + Debug: true, LLVMFeatures: config.LLVMFeatures(), } @@ -468,33 +470,10 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil linkerDependencies = append(linkerDependencies, job) } - // Add libc dependency if needed. - root := goenv.Get("TINYGOROOT") - switch config.Target.Libc { - case "picolibc": - job, err := Picolibc.load(config.Triple(), config.CPU(), dir) - if err != nil { - return err - } - // The library needs to be compiled (cache miss). - jobs = append(jobs, job.dependencies...) - jobs = append(jobs, job) - linkerDependencies = append(linkerDependencies, job) - case "wasi-libc": - path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a") - if _, err := os.Stat(path); os.IsNotExist(err) { - return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?") - } - ldflags = append(ldflags, path) - case "": - // no library specified, so nothing to do - default: - return fmt.Errorf("unknown libc: %s", config.Target.Libc) - } - // Add jobs to compile extra files. These files are in C or assembly and // contain things like the interrupt vector table and low level operations // such as stack switching. + root := goenv.Get("TINYGOROOT") for _, path := range config.ExtraFiles() { abspath := filepath.Join(root, path) job := &compileJob{ @@ -535,6 +514,64 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil ldflags = append(ldflags, lprogram.LDFlags...) } + // Add libc dependency if needed. + switch config.Target.Libc { + case "picolibc": + job, err := Picolibc.load(config.Triple(), config.CPU(), dir) + if err != nil { + return err + } + // The library needs to be compiled (cache miss). + jobs = append(jobs, job.dependencies...) + jobs = append(jobs, job) + linkerDependencies = append(linkerDependencies, job) + case "wasi-libc": + path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a") + if _, err := os.Stat(path); os.IsNotExist(err) { + return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?") + } + job := dummyCompileJob(path) + jobs = append(jobs, job) + linkerDependencies = append(linkerDependencies, job) + case "": + // no library specified, so nothing to do + default: + return fmt.Errorf("unknown libc: %s", config.Target.Libc) + } + + // Strip debug information with -no-debug. + if !config.Debug() { + for _, tag := range config.BuildTags() { + if tag == "baremetal" { + // Don't use -no-debug on baremetal targets. It makes no sense: + // the debug information isn't flashed to the device anyway. + return fmt.Errorf("stripping debug information is unnecessary for baremetal targets") + } + } + if config.Target.Linker == "wasm-ld" { + // Don't just strip debug information, also compress relocations + // while we're at it. Relocations can only be compressed when debug + // information is stripped. + ldflags = append(ldflags, "--strip-debug", "--compress-relocations") + } else { + switch config.GOOS() { + case "linux": + // Either real linux or an embedded system (like AVR) that + // pretends to be Linux. It's a ELF linker wrapped by GCC in any + // case. + ldflags = append(ldflags, "-Wl,--strip-debug") + case "darwin": + // MacOS (darwin) doesn't have a linker flag to strip debug + // information. Apple expects you to use the strip command + // instead. + return errors.New("cannot remove debug information: MacOS doesn't suppor this linker flag") + default: + // Other OSes may have different flags. + return errors.New("cannot remove debug information: unknown OS: " + config.GOOS()) + } + } + } + // Create a linker job, which links all object files together and does some // extra stuff that can only be done after linking. jobs = append(jobs, &compileJob{ @@ -547,8 +584,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil } ldflags = append(ldflags, dependency.result) } - if config.Options.PrintCommands { - fmt.Printf("%s %s\n", config.Target.Linker, strings.Join(ldflags, " ")) + if config.Options.PrintCommands != nil { + config.Options.PrintCommands(config.Target.Linker, ldflags...) } err = link(config.Target.Linker, ldflags...) if err != nil { @@ -566,6 +603,8 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil return err } } + + // Apply ELF patches if config.AutomaticStackSize() { // Modify the .tinygo_stacksizes section that contains a stack size // for each goroutine. @@ -574,6 +613,13 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil return fmt.Errorf("could not modify stack sizes: %w", err) } } + if config.RP2040BootPatch() { + // Patch the second stage bootloader CRC into the .boot2 section + err = patchRP2040BootCRC(executable) + if err != nil { + return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err) + } + } if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { sizes, err := loadProgramSize(executable) @@ -639,6 +685,18 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil if err != nil { return err } + case "nrf-dfu": + // special format for nrfutil for Nordic chips + tmphexpath := filepath.Join(dir, "main.hex") + err := objcopy(executable, tmphexpath, "hex") + if err != nil { + return err + } + tmppath = filepath.Join(dir, "main"+outext) + err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath) + if err != nil { + return err + } default: return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) } @@ -908,30 +966,7 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri // stack size information. Before this modification, all stack sizes in the // section assume the default stack size (which is relatively big). func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error { - fp, err := os.OpenFile(executable, os.O_RDWR, 0) - if err != nil { - return err - } - defer fp.Close() - - elfFile, err := elf.NewFile(fp) - if err != nil { - return err - } - - section := elfFile.Section(".tinygo_stacksizes") - if section == nil { - return errors.New("could not find .tinygo_stacksizes section") - } - - if section.Size != section.FileSize { - // Sanity check. - return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize) - } - - // Read all goroutine stack sizes. - data := make([]byte, section.Size) - _, err = fp.ReadAt(data, int64(section.Offset)) + data, fileHeader, err := getElfSectionData(executable, ".tinygo_stacksizes") if err != nil { return err } @@ -953,15 +988,19 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map if fn.stackSizeType == stacksize.Bounded { stackSize := uint32(fn.stackSize) - // Adding 4 for the stack canary. Even though the size may be - // automatically determined, stack overflow checking is still - // important as the stack size cannot be determined for all - // goroutines. - stackSize += 4 - // Add stack size used by interrupts. - switch elfFile.Machine { + switch fileHeader.Machine { case elf.EM_ARM: + if stackSize%8 != 0 { + // If the stack isn't a multiple of 8, it means the leaf + // function with the biggest stack depth doesn't have an aligned + // stack. If the STKALIGN flag is set (which it is by default) + // the interrupt controller will forcibly align the stack before + // storing in-use registers. This will thus overwrite one word + // past the end of the stack (off-by-one). + stackSize += 4 + } + // On Cortex-M (assumed here), this stack size is 8 words or 32 // bytes. This is only to store the registers that the interrupt // may modify, the interrupt will switch to the interrupt stack @@ -969,6 +1008,14 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map // Some background: // https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching stackSize += 32 + + // Adding 4 for the stack canary, and another 4 to keep the + // stack aligned. Even though the size may be automatically + // determined, stack overflow checking is still important as the + // stack size cannot be determined for all goroutines. + stackSize += 8 + default: + return fmt.Errorf("unknown architecture: %s", fileHeader.Machine.String()) } // Finally write the stack size to the binary. @@ -976,13 +1023,7 @@ func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map } } - // Write back the modified stack sizes. - _, err = fp.WriteAt(data, int64(section.Offset)) - if err != nil { - return err - } - - return nil + return replaceElfSection(executable, ".tinygo_stacksizes", data) } // printStacks prints the maximum stack depth for functions that are started as @@ -1014,3 +1055,41 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS } } } + +// RP2040 second stage bootloader CRC32 calculation +// +// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf +// Section: 2.8.1.3.1. Checksum +func patchRP2040BootCRC(executable string) error { + bytes, _, err := getElfSectionData(executable, ".boot2") + if err != nil { + return err + } + + if len(bytes) != 256 { + return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes") + } + + // From the 'official' RP2040 checksum script: + // + // Our bootrom CRC32 is slightly bass-ackward but it's + // best to work around for now (FIXME) + // 100% worth it to save two Thumb instructions + revBytes := make([]byte, len(bytes)) + for i := range bytes { + revBytes[i] = bits.Reverse8(bytes[i]) + } + + // crc32.Update does an initial negate and negates the + // result, so to meet RP2040 spec, pass 0x0 as initial + // hash and negate returned value. + // + // Note: checksum is over 252 bytes (256 - 4) + hash := bits.Reverse32(crc32.Update(0x0, crc32.IEEETable, revBytes[:252]) ^ 0xFFFFFFFF) + + // Write the CRC to the end of the bootloader. + binary.LittleEndian.PutUint32(bytes[252:], hash) + + // Update the .boot2 section to included the CRC + return replaceElfSection(executable, ".boot2", bytes) +} diff --git a/builder/cc.go b/builder/cc.go index a450cc6e8..7f0dad28a 100644 --- a/builder/cc.go +++ b/builder/cc.go @@ -56,7 +56,7 @@ import ( // depfile but without invalidating its name. For this reason, the depfile is // written on each new compilation (even when it seems unnecessary). However, it // could in rare cases lead to a stale file fetched from the cache. -func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands bool) (string, error) { +func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) { // Hash input file. fileHash, err := hashFile(abspath) if err != nil { @@ -128,8 +128,8 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands // flags (for the assembler) is a compiler error. flags = append(flags, "-Qunused-arguments") } - if printCommands { - fmt.Printf("clang %s\n", strings.Join(flags, " ")) + if printCommands != nil { + printCommands("clang", flags...) } err = runCCompiler(flags...) if err != nil { diff --git a/builder/elfpatch.go b/builder/elfpatch.go new file mode 100644 index 000000000..6a407db6f --- /dev/null +++ b/builder/elfpatch.go @@ -0,0 +1,57 @@ +package builder + +import ( + "debug/elf" + "fmt" + "os" +) + +func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) { + elfFile, err := elf.Open(executable) + if err != nil { + return nil, elf.FileHeader{}, err + } + defer elfFile.Close() + + section := elfFile.Section(sectionName) + if section == nil { + return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName) + } + + data, err := section.Data() + + return data, elfFile.FileHeader, err +} + +func replaceElfSection(executable string, sectionName string, data []byte) error { + fp, err := os.OpenFile(executable, os.O_RDWR, 0) + if err != nil { + return err + } + defer fp.Close() + + elfFile, err := elf.Open(executable) + if err != nil { + return err + } + defer elfFile.Close() + + section := elfFile.Section(sectionName) + if section == nil { + return fmt.Errorf("could not find %s section", sectionName) + } + + // Implicitly check for compressed sections + if section.Size != section.FileSize { + return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize) + } + + // Only permit complete replacement of section + if section.Size != uint64(len(data)) { + return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size) + } + + // Write the replacement section data + _, err = fp.WriteAt(data, int64(section.Offset)) + return err +} diff --git a/builder/nrfutil.go b/builder/nrfutil.go new file mode 100644 index 000000000..55aef45e3 --- /dev/null +++ b/builder/nrfutil.go @@ -0,0 +1,27 @@ +package builder + +import ( + "fmt" + "io/ioutil" + "os/exec" + + "github.com/tinygo-org/tinygo/compileopts" +) + +// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html + +func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error { + cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile} + + if options.PrintCommands != nil { + options.PrintCommands(cmdLine[0], cmdLine[1:]...) + } + + cmd := exec.Command(cmdLine[0], cmdLine[1:]...) + cmd.Stdout = ioutil.Discard + err := cmd.Run() + if err != nil { + return fmt.Errorf("could not run nrfutil pkg generate: %w", err) + } + return nil +} diff --git a/compileopts/config.go b/compileopts/config.go index d5f392664..f2ba28578 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -55,7 +55,7 @@ func (c *Config) GOARCH() string { // BuildTags returns the complete list of build tags used during this build. func (c *Config) BuildTags() []string { - tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...) + tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...) for i := 1; i <= c.GoMinorVersion; i++ { tags = append(tags, fmt.Sprintf("go1.%d", i)) } @@ -89,7 +89,7 @@ func (c *Config) NeedsStackObjects() bool { switch c.GC() { case "conservative", "extalloc": for _, tag := range c.BuildTags() { - if tag == "wasm" { + if tag == "tinygo.wasm" { return true } } @@ -113,6 +113,18 @@ func (c *Config) Scheduler() string { return "coroutines" } +// Serial returns the serial implementation for this build configuration: uart, +// usb (meaning USB-CDC), or none. +func (c *Config) Serial() string { + if c.Options.Serial != "" { + return c.Options.Serial + } + if c.Target.Serial != "" { + return c.Target.Serial + } + return "none" +} + // OptLevels returns the optimization level (0-2), size level (0-2), and inliner // threshold as used in the LLVM optimization pipeline. func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) { @@ -176,6 +188,15 @@ func (c *Config) AutomaticStackSize() bool { return false } +// RP2040BootPatch returns whether the RP2040 boot patch should be applied that +// calculates and patches in the checksum for the 2nd stage bootloader. +func (c *Config) RP2040BootPatch() bool { + if c.Target.RP2040BootPatch != nil { + return *c.Target.RP2040BootPatch + } + return false +} + // CFlags returns the flags to pass to the C compiler. This is necessary for CGo // preprocessing. func (c *Config) CFlags() []string { @@ -188,9 +209,8 @@ func (c *Config) CFlags() []string { cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include")) cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include")) } - if c.Debug() { - cflags = append(cflags, "-g") - } + // Always emit debug information. It is optionally stripped at link time. + cflags = append(cflags, "-g") return cflags } @@ -229,8 +249,9 @@ func (c *Config) VerifyIR() bool { return c.Options.VerifyIR } -// Debug returns whether to add debug symbols to the IR, for debugging with GDB -// and similar. +// Debug returns whether debug (DWARF) information should be retained by the +// linker. By default, debug information is retained but it can be removed with +// the -no-debug flag. func (c *Config) Debug() bool { return c.Options.Debug } @@ -254,6 +275,11 @@ func (c *Config) BinaryFormat(ext string) string { // More information: // https://github.com/Microsoft/uf2 return "uf2" + case ".zip": + if c.Target.BinaryFormat != "" { + return c.Target.BinaryFormat + } + return "zip" default: // Use the ELF format for unrecognized file formats. return "elf" diff --git a/compileopts/options.go b/compileopts/options.go index ee5621003..7e7bfcafc 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -9,6 +9,7 @@ import ( var ( validGCOptions = []string{"none", "leaking", "extalloc", "conservative"} validSchedulerOptions = []string{"none", "tasks", "coroutines"} + validSerialOptions = []string{"none", "uart", "usb"} validPrintSizeOptions = []string{"none", "short", "full"} validPanicStrategyOptions = []string{"print", "trap"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"} @@ -22,10 +23,11 @@ type Options struct { GC string PanicStrategy string Scheduler string + Serial string PrintIR bool DumpSSA bool VerifyIR bool - PrintCommands bool + PrintCommands func(cmd string, args ...string) Debug bool PrintSizes string PrintAllocs *regexp.Regexp // regexp string @@ -59,6 +61,15 @@ func (o *Options) Verify() error { } } + if o.Serial != "" { + valid := isInArray(validSerialOptions, o.Serial) + if !valid { + return fmt.Errorf(`invalid serial option '%s': valid values are %s`, + o.Serial, + strings.Join(validSerialOptions, ", ")) + } + } + if o.PrintSizes != "" { valid := isInArray(validPrintSizeOptions, o.PrintSizes) if !valid { diff --git a/compileopts/target.go b/compileopts/target.go index 81de4ed07..ba5f73826 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -31,6 +31,7 @@ type TargetSpec struct { 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"` @@ -40,10 +41,12 @@ type TargetSpec struct { 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" override:"copy"` // inherited Emulator must not be append 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 "acm:vid:pid" or "usb:vid:pid" FlashMethod string `json:"flash-method"` FlashVolume string `json:"msd-volume-name"` FlashFilename string `json:"msd-firmware-name"` diff --git a/compiler/compiler.go b/compiler/compiler.go index f25832acf..7841cd656 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 10 // last change: context parameter in go wrapper +const Version = 12 // last change: implement syscall.rawSyscallNoError func init() { llvm.InitializeAllTargets() @@ -715,11 +715,11 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package member := pkg.Members[name] switch member := member.(type) { case *ssa.Function: + // Create the function definition. + b := newBuilder(c, irbuilder, member) if member.Blocks == nil { continue // external function } - // Create the function definition. - b := newBuilder(c, irbuilder, member) b.createFunction() case *ssa.Type: if types.IsInterface(member.Type()) { @@ -758,10 +758,13 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package case *ssa.Global: // Global variable. info := c.getGlobalInfo(member) + global := c.getGlobal(member) if !info.extern { - global := c.getGlobal(member) global.SetInitializer(llvm.ConstNull(global.Type().ElementType())) global.SetVisibility(llvm.HiddenVisibility) + if info.section != "" { + global.SetSection(info.section) + } } } } @@ -787,6 +790,9 @@ func (b *builder) createFunction() { b.llvmFn.SetVisibility(llvm.HiddenVisibility) b.llvmFn.SetUnnamedAddr(true) } + if b.info.section != "" { + b.llvmFn.SetSection(b.info.section) + } if b.info.exported && strings.HasPrefix(b.Triple, "wasm") { // Set the exported name. This is necessary for WebAssembly because // otherwise the function is not exported. @@ -1304,6 +1310,8 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.emitCSROperation(instr) case strings.HasPrefix(name, "syscall.Syscall"): return b.createSyscall(instr) + case strings.HasPrefix(name, "syscall.rawSyscallNoError"): + return b.createRawSyscallNoError(instr) case strings.HasPrefix(name, "runtime/volatile.Load"): return b.createVolatileLoad(instr) case strings.HasPrefix(name, "runtime/volatile.Store"): diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 4149e1491..45d5b8c3a 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -45,6 +45,7 @@ func TestCompiler(t *testing.T) { {"float.go", ""}, {"interface.go", ""}, {"func.go", ""}, + {"pragma.go", ""}, {"goroutine.go", "wasm"}, {"goroutine.go", "cortex-m-qemu"}, } diff --git a/compiler/interface.go b/compiler/interface.go index eda7e3653..5d8895d71 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -311,10 +311,21 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value { // used during the interface lowering pass. func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { signature := methodSignature(method) - signatureGlobal := c.mod.NamedGlobal("func " + signature) + var globalName string + if token.IsExported(method.Name()) { + globalName = "reflect/methods." + signature + } else { + globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature + } + signatureGlobal := c.mod.NamedGlobal(globalName) if signatureGlobal.IsNil() { - signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature) + // TODO: put something useful in these globals, such as the method + // signature. Useful to one day implement reflect.Value.Method(n). + signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName) + signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false)) + signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage) signatureGlobal.SetGlobalConstant(true) + signatureGlobal.SetAlignment(1) } return signatureGlobal } diff --git a/compiler/symbol.go b/compiler/symbol.go index 6eec892dd..49ccfa20d 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -24,6 +24,7 @@ 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 + section string // go:section - object file section name exported bool // go:export, CGo nobounds bool // go:nobounds variadic bool // go:variadic (CGo only) @@ -270,6 +271,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) { if hasUnsafeImport(f.Pkg.Pkg) { info.linkName = parts[2] } + case "//go:section": + if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) { + info.section = parts[1] + } case "//go:nobounds": // Skip bounds checking in this function. Useful for some // runtime functions. @@ -325,6 +330,7 @@ type globalInfo struct { linkName string // go:extern extern bool // go:extern align int // go:align + section string // go:section } // loadASTComments loads comments on globals from the AST, for use later in the @@ -438,6 +444,10 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) { if err == nil { info.align = align } + case "//go:section": + if len(parts) == 2 { + info.section = parts[1] + } } } } diff --git a/compiler/syscall.go b/compiler/syscall.go index 5b93e9ec1..6a0bd3287 100644 --- a/compiler/syscall.go +++ b/compiler/syscall.go @@ -10,11 +10,11 @@ import ( "tinygo.org/x/go-llvm" ) -// createSyscall emits an inline system call instruction, depending on the -// target OS/arch. -func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { +// createRawSyscall creates a system call with the provided system call number +// and returns the result as a single integer (the system call result). The +// result is not further interpreted. +func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { num := b.getValue(call.Args[0]) - var syscallResult llvm.Value switch { case b.GOARCH == "amd64": if b.GOOS == "darwin" { @@ -57,7 +57,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { constraints += ",~{rcx},~{r11}" fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel) - syscallResult = b.CreateCall(target, args, "") + return b.CreateCall(target, args, ""), nil case b.GOARCH == "386" && b.GOOS == "linux": // Sources: // syscall(2) man page @@ -83,7 +83,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { } fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel) - syscallResult = b.CreateCall(target, args, "") + return b.CreateCall(target, args, ""), nil case b.GOARCH == "arm" && b.GOOS == "linux": // Implement the EABI system call convention for Linux. // Source: syscall(2) man page. @@ -115,7 +115,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { } fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) - syscallResult = b.CreateCall(target, args, "") + return b.CreateCall(target, args, ""), nil case b.GOARCH == "arm64" && b.GOOS == "linux": // Source: syscall(2) man page. args := []llvm.Value{} @@ -147,10 +147,19 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { constraints += ",~{x16},~{x17}" // scratch registers fnType := llvm.FunctionType(b.uintptrType, argTypes, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0) - syscallResult = b.CreateCall(target, args, "") + return b.CreateCall(target, args, ""), nil default: return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } +} + +// createSyscall emits instructions for the syscall.Syscall* family of +// functions, depending on the target OS/arch. +func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { + syscallResult, err := b.createRawSyscall(call) + if err != nil { + return syscallResult, err + } switch b.GOOS { case "linux", "freebsd": // Return values: r0, r1 uintptr, err Errno @@ -190,3 +199,16 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) { return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) } } + +// createRawSyscallNoError emits instructions for the Linux-specific +// syscall.rawSyscallNoError function. +func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) { + syscallResult, err := b.createRawSyscall(call) + if err != nil { + return syscallResult, err + } + retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)) + retval = b.CreateInsertValue(retval, syscallResult, 0, "") + retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "") + return retval, nil +} diff --git a/compiler/testdata/goroutine-cortex-m-qemu.ll b/compiler/testdata/goroutine-cortex-m-qemu.ll index a00d49be6..a98592c15 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu.ll @@ -5,7 +5,7 @@ target triple = "armv7m-none-eabi" %runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* } %runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } } -%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i32, %"internal/task.state" } +%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" } %"internal/task.state" = type { i32, i32* } %runtime.chanSelectState = type { %runtime.channel*, i8* } diff --git a/compiler/testdata/goroutine-wasm.ll b/compiler/testdata/goroutine-wasm.ll index 212bd714e..1d840e6da 100644 --- a/compiler/testdata/goroutine-wasm.ll +++ b/compiler/testdata/goroutine-wasm.ll @@ -6,7 +6,7 @@ target triple = "wasm32--wasi" %runtime.funcValueWithSignature = type { i32, i8* } %runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* } %runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } } -%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i32, %"internal/task.state" } +%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" } %"internal/task.state" = type { i8* } %runtime.chanSelectState = type { %runtime.channel*, i8* } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index eb217bdc2..be9e229a1 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -13,15 +13,15 @@ target triple = "wasm32--wasi" @"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" } -@"func Error() string" = external constant i8 -@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"] +@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1 +@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } @"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" } -@"func String() string" = external constant i8 -@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func String() string"] +@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1 +@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"] @"reflect/types.typeid:basic:int" = external constant i8 -@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"] +@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) @@ -92,7 +92,7 @@ typeassert.next: ; preds = %typeassert.ok, %ent define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"func Error() string", i8* undef, i8* null) + %invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Error() string", i8* undef, i8* null) %invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)* %0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef) ret %runtime._string %0 diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go new file mode 100644 index 000000000..b6ebc83b2 --- /dev/null +++ b/compiler/testdata/pragma.go @@ -0,0 +1,66 @@ +package main + +import _ "unsafe" + +// Creates an external global with name extern_global. +//go:extern extern_global +var externGlobal [0]byte + +// Creates a +//go:align 32 +var alignedGlobal [4]uint32 + +// Test conflicting pragmas (the last one counts). +//go:align 64 +//go:align 16 +var alignedGlobal16 [4]uint32 + +// Test exported functions. +//export extern_func +func externFunc() { +} + +// Define a function in a different package using go:linkname. +//go:linkname withLinkageName1 somepkg.someFunction1 +func withLinkageName1() { +} + +// Import a function from a different package using go:linkname. +//go:linkname withLinkageName2 somepkg.someFunction2 +func withLinkageName2() + +// Function has an 'inline hint', similar to the inline keyword in C. +//go:inline +func inlineFunc() { +} + +// Function should never be inlined, equivalent to GCC +// __attribute__((noinline)). +//go:noinline +func noinlineFunc() { +} + +// This function should have the specified section. +//go:section .special_function_section +func functionInSection() { +} + +//export exportedFunctionInSection +//go:section .special_function_section +func exportedFunctionInSection() { +} + +// This function should not: it's only a declaration and not a definition. +//go:section .special_function_section +func undefinedFunctionNotInSection() + +//go:section .special_global_section +var globalInSection uint32 + +//go:section .special_global_section +//go:extern undefinedGlobalNotInSection +var undefinedGlobalNotInSection uint32 + +//go:align 1024 +//go:section .global_section +var multipleGlobalPragmas uint32 diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll new file mode 100644 index 000000000..0515098c5 --- /dev/null +++ b/compiler/testdata/pragma.ll @@ -0,0 +1,59 @@ +; ModuleID = 'pragma.go' +source_filename = "pragma.go" +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32--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 +@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 + +declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) + +define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +define void @extern_func() #0 { +entry: + ret void +} + +define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +declare void @somepkg.someFunction2(i8*, i8*) + +; Function Attrs: inlinehint +define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #1 { +entry: + ret void +} + +; Function Attrs: noinline +define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #2 { +entry: + ret void +} + +define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr section ".special_function_section" { +entry: + ret void +} + +define void @exportedFunctionInSection() #3 section ".special_function_section" { +entry: + ret void +} + +declare void @main.undefinedFunctionNotInSection(i8*, i8*) + +attributes #0 = { "wasm-export-name"="extern_func" } +attributes #1 = { inlinehint } +attributes #2 = { noinline } +attributes #3 = { "wasm-export-name"="exportedFunctionInSection" } diff --git a/goenv/version.go b/goenv/version.go index 909f330f4..e0fe0949c 100644 --- a/goenv/version.go +++ b/goenv/version.go @@ -12,7 +12,7 @@ import ( // Version of TinyGo. // Update this value before release of new version of software. -const Version = "0.19.0-dev" +const Version = "0.20.0-dev" // GetGorootVersion returns the major and minor version for a given GOROOT path. // If the goroot cannot be determined, (0, 0) is returned. diff --git a/interp/errors.go b/interp/errors.go index 48cf85df0..c90de3329 100644 --- a/interp/errors.go +++ b/interp/errors.go @@ -57,6 +57,7 @@ func (r *runner) errorAt(inst instruction, err error) *Error { pos := getPosition(inst.llvmInst) return &Error{ ImportPath: r.pkgName, + Inst: inst.llvmInst, Pos: pos, Err: err, Traceback: []ErrorLine{{pos, inst.llvmInst}}, diff --git a/interp/interp.go b/interp/interp.go index 58be2c86b..cab9d9e35 100644 --- a/interp/interp.go +++ b/interp/interp.go @@ -15,7 +15,7 @@ import ( // package is changed in a way that affects the output so that cached package // builds will be invalidated. // This version is independent of the TinyGo version number. -const Version = 1 +const Version = 2 // last change: fix GEP on untyped pointers // Enable extra checks, which should be disabled by default. // This may help track down bugs by adding a few more sanity checks. @@ -110,17 +110,26 @@ func Run(mod llvm.Module, debug bool) error { fmt.Fprintln(os.Stderr, "call:", fn.Name()) } _, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ") + call.EraseFromParentAsInstruction() if callErr != nil { if isRecoverableError(callErr.Err) { if r.debug { fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error()) } + // Remove instructions that were created as part of interpreting + // the package. mem.revert() + // Create a call to the package initializer (which was + // previously deleted). + i8undef := llvm.Undef(r.i8ptrType) + r.builder.CreateCall(fn, []llvm.Value{i8undef, i8undef}, "") + // Make sure that any globals touched by the package + // initializer, won't be accessed by later package initializers. + r.markExternalLoad(fn) continue } return callErr } - call.EraseFromParentAsInstruction() for index, obj := range mem.objects { r.objects[index] = obj } @@ -129,7 +138,7 @@ func Run(mod llvm.Module, debug bool) error { // Update all global variables in the LLVM module. mem := memoryView{r: r} - for _, obj := range r.objects { + for i, obj := range r.objects { if obj.llvmGlobal.IsNil() { continue } @@ -159,6 +168,12 @@ func Run(mod llvm.Module, debug bool) error { name := obj.llvmGlobal.Name() obj.llvmGlobal.EraseFromParentAsGlobal() newGlobal.SetName(name) + + // Update interp-internal references. + delete(r.globals, obj.llvmGlobal) + obj.llvmGlobal = newGlobal + r.globals[newGlobal] = i + r.objects[i] = obj continue } if err != nil { @@ -264,3 +279,19 @@ func (r *runner) getFunction(llvmFn llvm.Value) *function { r.functionCache[llvmFn] = fn return fn } + +// markExternalLoad marks the given llvmValue as being loaded externally. This +// is primarily used to mark package initializers that could not be run at +// compile time. As an example, a package initialize might store to a global +// variable. Another package initializer might read from the same global +// variable. By marking this function as being run at runtime, that load +// instruction will need to be run at runtime instead of at compile time. +func (r *runner) markExternalLoad(llvmValue llvm.Value) { + mem := memoryView{r: r} + mem.markExternalLoad(llvmValue) + for index, obj := range mem.objects { + if obj.marked > r.objects[index].marked { + r.objects[index].marked = obj.marked + } + } +} diff --git a/interp/interp_test.go b/interp/interp_test.go index 50af8af60..9702cb837 100644 --- a/interp/interp_test.go +++ b/interp/interp_test.go @@ -17,6 +17,7 @@ func TestInterp(t *testing.T) { "slice-copy", "consteval", "interface", + "revert", } { name := name // make tc local to this closure t.Run(name, func(t *testing.T) { diff --git a/interp/memory.go b/interp/memory.go index ccd98c8b0..53b798a3e 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -572,6 +572,17 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val } if llvmType.IsNil() { + if v.offset() != 0 { + // If there is an offset, make sure to use a GEP to index into the + // pointer. Because there is no expected type, we use whatever is + // most convenient: an *i8 type. It is trivial to index byte-wise. + if llvmValue.Type() != mem.r.i8ptrType { + llvmValue = llvm.ConstBitCast(llvmValue, mem.r.i8ptrType) + } + llvmValue = llvm.ConstInBoundsGEP(llvmValue, []llvm.Value{ + llvm.ConstInt(llvmValue.Type().Context().Int32Type(), uint64(v.offset()), false), + }) + } return llvmValue, nil } diff --git a/interp/testdata/revert.ll b/interp/testdata/revert.ll new file mode 100644 index 000000000..41fb6a81e --- /dev/null +++ b/interp/testdata/revert.ll @@ -0,0 +1,32 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +declare void @externalCall(i64) + +@foo.knownAtRuntime = global i64 0 +@bar.knownAtRuntime = global i64 0 + +define void @runtime.initAll() unnamed_addr { +entry: + call void @foo.init(i8* undef, i8* undef) + call void @bar.init(i8* undef, i8* undef) + call void @main.init(i8* undef, i8* undef) + ret void +} + +define internal void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr { + store i64 5, i64* @foo.knownAtRuntime + unreachable ; this triggers a revert of @foo.init. +} + +define internal void @bar.init(i8* %context, i8* %parentHandle) unnamed_addr { + %val = load i64, i64* @foo.knownAtRuntime + store i64 %val, i64* @bar.knownAtRuntime + ret void +} + +define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + call void @externalCall(i64 3) + ret void +} diff --git a/interp/testdata/revert.out.ll b/interp/testdata/revert.out.ll new file mode 100644 index 000000000..4f38e4c41 --- /dev/null +++ b/interp/testdata/revert.out.ll @@ -0,0 +1,21 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +@foo.knownAtRuntime = local_unnamed_addr global i64 0 +@bar.knownAtRuntime = local_unnamed_addr global i64 0 + +declare void @externalCall(i64) local_unnamed_addr + +define void @runtime.initAll() unnamed_addr { +entry: + call fastcc void @foo.init(i8* undef, i8* undef) + %val = load i64, i64* @foo.knownAtRuntime, align 8 + store i64 %val, i64* @bar.knownAtRuntime, align 8 + call void @externalCall(i64 3) + ret void +} + +define internal fastcc void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr { + store i64 5, i64* @foo.knownAtRuntime, align 8 + unreachable +} diff --git a/loader/goroot.go b/loader/goroot.go index 03c8919e7..6770d0458 100644 --- a/loader/goroot.go +++ b/loader/goroot.go @@ -2,6 +2,14 @@ package loader // This file constructs a new temporary GOROOT directory by merging both the // standard Go GOROOT and the GOROOT from TinyGo using symlinks. +// +// The goal is to replace specific packages from Go with a TinyGo version. It's +// never a partial replacement, either a package is fully replaced or it is not. +// This is important because if we did allow to merge packages (e.g. by adding +// files to a package), it would lead to a dependency on implementation details +// with all the maintenance burden that results in. Only allowing to replace +// packages as a whole avoids this as packages are already designed to have a +// public (backwards-compatible) API. import ( "crypto/sha512" @@ -139,6 +147,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides if err != nil { return err } + hasTinyGoFiles := false for _, e := range tinygoEntries { if e.IsDir() { // A directory, so merge this thing. @@ -154,6 +163,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides if err != nil { return err } + hasTinyGoFiles = true } } @@ -164,21 +174,30 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides return err } for _, e := range gorootEntries { - if !e.IsDir() { - // Don't merge in files from Go. Otherwise we'd end up with a - // weird syscall package with files from both roots. - continue - } - if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok { - // Already included above, so don't bother trying to create this - // symlink. - continue - } - newname := filepath.Join(tmpgoroot, "src", importPath, e.Name()) - oldname := filepath.Join(goroot, "src", importPath, e.Name()) - err := symlink(oldname, newname) - if err != nil { - return err + if e.IsDir() { + if _, ok := overrides[path.Join(importPath, e.Name())+"/"]; ok { + // Already included above, so don't bother trying to create this + // symlink. + continue + } + newname := filepath.Join(tmpgoroot, "src", importPath, e.Name()) + oldname := filepath.Join(goroot, "src", importPath, e.Name()) + err := symlink(oldname, newname) + if err != nil { + return err + } + } else { + // Only merge files from Go if TinyGo does not have any files. + // Otherwise we'd end up with a weird mix from both Go + // implementations. + if !hasTinyGoFiles { + newname := filepath.Join(tmpgoroot, "src", importPath, e.Name()) + oldname := filepath.Join(goroot, "src", importPath, e.Name()) + err := symlink(oldname, newname) + if err != nil { + return err + } + } } } } @@ -201,6 +220,8 @@ func needsSyscallPackage(buildTags []string) bool { func pathsToOverride(needsSyscallPackage bool) map[string]bool { paths := map[string]bool{ "/": true, + "crypto/": true, + "crypto/rand/": false, "device/": false, "examples/": false, "internal/": true, diff --git a/loader/loader.go b/loader/loader.go index 27d5ea76e..eed57e6d6 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -343,12 +343,11 @@ func (p *Package) Check() error { checker.Importer = p packageName := p.ImportPath - if p.Name == "main" { - // The main package normally has a different import path, such as - // "command-line-arguments" or "./testdata/cgo". Therefore, use the name - // "main" in such a case: this package isn't imported from anywhere. - // This is safe as it isn't possible to import a package with the name - // "main". + if p == p.program.MainPkg() { + if p.Name != "main" { + // Sanity check. Should not ever trigger. + panic("expected main package to have name 'main'") + } packageName = "main" } typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info) diff --git a/main.go b/main.go index c56c0752f..c9fd0c042 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "path/filepath" "regexp" "runtime" + "strconv" "strings" "sync/atomic" "time" @@ -95,12 +96,31 @@ func copyFile(src, dst string) error { // executeCommand is a simple wrapper to exec.Cmd func executeCommand(options *compileopts.Options, name string, arg ...string) *exec.Cmd { - if options.PrintCommands { - fmt.Printf("%s %s\n", name, strings.Join(arg, " ")) + if options.PrintCommands != nil { + options.PrintCommands(name, arg...) } return exec.Command(name, arg...) } +// printCommand prints a command to stdout while formatting it like a real +// command (escaping characters etc). The resulting command should be easy to +// run directly in a shell, although it is not guaranteed to be a safe shell +// escape. That's not a problem as the primary use case is printing the command, +// not running it. +func printCommand(cmd string, args ...string) { + command := append([]string{cmd}, args...) + for i, arg := range command { + // Source: https://www.oreilly.com/library/view/learning-the-bash/1565923472/ch01s09.html + const specialChars = "~`#$&*()\\|[]{};'\"<>?! " + if strings.ContainsAny(arg, specialChars) { + // See: https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash + arg = "'" + strings.ReplaceAll(arg, `'`, `'\''`) + "'" + command[i] = arg + } + } + fmt.Fprintln(os.Stderr, strings.Join(command, " ")) +} + // Build compiles and links the given package and writes it to outpath. func Build(pkgName, outpath string, options *compileopts.Options) error { config, err := builder.NewConfig(options) @@ -145,7 +165,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly bool, ou return false, err } - var passed bool + passed := true err = builder.Build(pkgName, outpath, config, func(result builder.BuildResult) error { if testCompileOnly || outpath != "" { // Write test binary to the specified file name. @@ -256,6 +276,8 @@ func Flash(pkgName, port string, options *compileopts.Options) error { fileExt = ".bin" case strings.Contains(config.Target.FlashCommand, "{uf2}"): fileExt = ".uf2" + case strings.Contains(config.Target.FlashCommand, "{zip}"): + fileExt = ".zip" default: return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?") } @@ -275,7 +297,7 @@ func Flash(pkgName, port string, options *compileopts.Options) error { return builder.Build(pkgName, fileExt, config, func(result builder.BuildResult) error { // do we need port reset to put MCU into bootloader mode? if config.Target.PortReset == "true" && flashMethod != "openocd" { - port, err := getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' })) + port, err := getDefaultPort(port, config.Target.SerialPort) if err != nil { return err } @@ -293,36 +315,36 @@ func Flash(pkgName, port string, options *compileopts.Options) error { case "", "command": // Create the command. flashCmd := config.Target.FlashCommand - fileToken := "{" + fileExt[1:] + "}" - flashCmd = strings.ReplaceAll(flashCmd, fileToken, result.Binary) + flashCmdList, err := shlex.Split(flashCmd) + if err != nil { + return fmt.Errorf("could not parse flash command %#v: %w", flashCmd, err) + } if strings.Contains(flashCmd, "{port}") { var err error - port, err = getDefaultPort(strings.FieldsFunc(port, func(c rune) bool { return c == ',' })) + port, err = getDefaultPort(port, config.Target.SerialPort) if err != nil { return err } } - flashCmd = strings.ReplaceAll(flashCmd, "{port}", port) - - // Execute the command. - var cmd *exec.Cmd - switch runtime.GOOS { - case "windows": - command := strings.Split(flashCmd, " ") - if len(command) < 2 { - return errors.New("invalid flash command") - } - cmd = executeCommand(config.Options, command[0], command[1:]...) - default: - cmd = executeCommand(config.Options, "/bin/sh", "-c", flashCmd) + // Fill in fields in the command template. + fileToken := "{" + fileExt[1:] + "}" + for i, arg := range flashCmdList { + arg = strings.ReplaceAll(arg, fileToken, result.Binary) + arg = strings.ReplaceAll(arg, "{port}", port) + flashCmdList[i] = arg } + // Execute the command. + if len(flashCmdList) < 2 { + return fmt.Errorf("invalid flash command: %#v", flashCmd) + } + cmd := executeCommand(config.Options, flashCmdList[0], flashCmdList[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Dir = goenv.Get("TINYGOROOT") - err := cmd.Run() + err = cmd.Run() if err != nil { return &commandError{"failed to flash", result.Binary, err} } @@ -413,7 +435,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro case "native": // Run GDB directly. case "openocd": - gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset halt") + gdbCommands = append(gdbCommands, "target extended-remote :3333", "monitor halt", "load", "monitor reset halt") // We need a separate debugging daemon for on-chip debugging. args, err := config.OpenOCDConfiguration() @@ -432,7 +454,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stderr = w } case "jlink": - gdbCommands = append(gdbCommands, "target remote :2331", "load", "monitor reset halt") + gdbCommands = append(gdbCommands, "target extended-remote :2331", "load", "monitor reset halt") // We need a separate debugging daemon for on-chip debugging. daemon = executeCommand(config.Options, "JLinkGDBServer", "-device", config.Target.JLinkDevice) @@ -447,7 +469,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stderr = w } case "qemu": - gdbCommands = append(gdbCommands, "target remote :1234") + gdbCommands = append(gdbCommands, "target extended-remote :1234") // Run in an emulator. args := append(config.Target.Emulator[1:], result.Binary, "-s", "-S") @@ -455,7 +477,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "qemu-user": - gdbCommands = append(gdbCommands, "target remote :1234") + gdbCommands = append(gdbCommands, "target extended-remote :1234") // Run in an emulator. args := append(config.Target.Emulator[1:], "-g", "1234", result.Binary) @@ -463,7 +485,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "mgba": - gdbCommands = append(gdbCommands, "target remote :2345") + gdbCommands = append(gdbCommands, "target extended-remote :2345") // Run in an emulator. args := append(config.Target.Emulator[1:], result.Binary, "-g") @@ -471,7 +493,7 @@ func FlashGDB(pkgName string, ocdOutput bool, options *compileopts.Options) erro daemon.Stdout = os.Stdout daemon.Stderr = os.Stderr case "simavr": - gdbCommands = append(gdbCommands, "target remote :1234") + gdbCommands = append(gdbCommands, "target extended-remote :1234") // Run in an emulator. args := append(config.Target.Emulator[1:], "-g", result.Binary) @@ -697,7 +719,8 @@ func windowsFindUSBDrive(volume string, options *compileopts.Options) (string, e } // getDefaultPort returns the default serial port depending on the operating system. -func getDefaultPort(portCandidates []string) (port string, err error) { +func getDefaultPort(portFlag string, usbInterfaces []string) (port string, err error) { + portCandidates := strings.FieldsFunc(portFlag, func(c rune) bool { return c == ',' }) if len(portCandidates) == 1 { return portCandidates[0], nil } @@ -713,13 +736,70 @@ func getDefaultPort(portCandidates []string) (port string, err error) { return "", err } - for _, p := range portsList { - if p.IsUSB { - ports = append(ports, p.Name) + var preferredPortIDs [][2]uint16 + for _, s := range usbInterfaces { + parts := strings.Split(s, ":") + if len(parts) != 3 || (parts[0] != "acm" && parts[0] == "usb") { + // acm and usb are the two types of serial ports recognized + // under Linux (ttyACM*, ttyUSB*). Other operating systems don't + // generally make this distinction. If this is not one of the + // given USB devices, don't try to parse the USB IDs. + continue } + vid, err := strconv.ParseUint(parts[1], 16, 16) + if err != nil { + return "", fmt.Errorf("could not parse USB vendor ID %q: %w", parts[1], err) + } + pid, err := strconv.ParseUint(parts[2], 16, 16) + if err != nil { + return "", fmt.Errorf("could not parse USB product ID %q: %w", parts[1], err) + } + preferredPortIDs = append(preferredPortIDs, [2]uint16{uint16(vid), uint16(pid)}) } - if ports == nil || len(ports) == 0 { + var primaryPorts []string // ports picked from preferred USB VID/PID + var secondaryPorts []string // other ports (as a fallback) + for _, p := range portsList { + if !p.IsUSB { + continue + } + if p.VID != "" && p.PID != "" { + foundPort := false + vid, vidErr := strconv.ParseUint(p.VID, 16, 16) + pid, pidErr := strconv.ParseUint(p.PID, 16, 16) + if vidErr == nil && pidErr == nil { + for _, id := range preferredPortIDs { + if uint16(vid) == id[0] && uint16(pid) == id[1] { + primaryPorts = append(primaryPorts, p.Name) + foundPort = true + continue + } + } + } + if foundPort { + continue + } + } + + secondaryPorts = append(secondaryPorts, p.Name) + } + if len(primaryPorts) == 1 { + // There is exactly one match in the set of preferred ports. Use + // this port, even if there may be others available. This allows + // flashing a specific board even if there are multiple available. + return primaryPorts[0], nil + } else if len(primaryPorts) > 1 { + // There are multiple preferred ports, probably because more than + // one device of the same type are connected (e.g. two Arduino + // Unos). + ports = primaryPorts + } else { + // No preferred ports found. Fall back to other serial ports + // available in the system. + ports = secondaryPorts + } + + if len(ports) == 0 { // fallback switch runtime.GOOS { case "darwin": @@ -929,6 +1009,7 @@ func main() { gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)") + serial := flag.String("serial", "", "which serial output to use (none, uart, usb)") printIR := flag.Bool("printir", false, "print LLVM IR") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR") @@ -938,7 +1019,7 @@ func main() { printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines") printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed") printCommands := flag.Bool("x", false, "Print commands") - nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation") + nodebug := flag.Bool("no-debug", false, "strip debug information") ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)") ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)") @@ -947,10 +1028,11 @@ func main() { wasmAbi := flag.String("wasm-abi", "", "WebAssembly ABI conventions: js (no i64 params) or generic") llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") - var flagJSON, flagDeps *bool + var flagJSON, flagDeps, flagTest *bool if command == "help" || command == "list" { flagJSON = flag.Bool("json", false, "print data in JSON format") - flagDeps = flag.Bool("deps", false, "") + flagDeps = flag.Bool("deps", false, "supply -deps flag to go list") + flagTest = flag.Bool("test", false, "supply -test flag to go list") } var outpath string if command == "help" || command == "build" || command == "build-library" || command == "test" { @@ -1000,6 +1082,7 @@ func main() { GC: *gc, PanicStrategy: *panicStrategy, Scheduler: *scheduler, + Serial: *serial, PrintIR: *printIR, DumpSSA: *dumpSSA, VerifyIR: *verifyIR, @@ -1007,7 +1090,6 @@ func main() { PrintSizes: *printSize, PrintStacks: *printStacks, PrintAllocs: printAllocs, - PrintCommands: *printCommands, Tags: *tags, GlobalValues: globalVarValues, WasmAbi: *wasmAbi, @@ -1015,6 +1097,9 @@ func main() { OpenOCDCommands: ocdCommands, LLVMFeatures: *llvmFeatures, } + if *printCommands { + options.PrintCommands = printCommand + } os.Setenv("CC", "clang -target="+*target) @@ -1200,6 +1285,9 @@ func main() { if *flagDeps { extraArgs = append(extraArgs, "-deps") } + if *flagTest { + extraArgs = append(extraArgs, "-test") + } cmd, err := loader.List(config, extraArgs, flag.Args()) if err != nil { fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) diff --git a/main_test.go b/main_test.go index 51fd21fd2..9ed0c6c54 100644 --- a/main_test.go +++ b/main_test.go @@ -125,7 +125,7 @@ func TestCompiler(t *testing.T) { // Test with few optimizations enabled (no inlining, etc). t.Run("opt=1", func(t *testing.T) { t.Parallel() - runTestWithConfig("stdlib.go", "", t, &compileopts.Options{ + runTestWithConfig("stdlib.go", "", t, compileopts.Options{ Opt: "1", }, nil, nil) }) @@ -134,15 +134,14 @@ func TestCompiler(t *testing.T) { // TODO: fix this for stdlib.go, which currently fails. t.Run("opt=0", func(t *testing.T) { t.Parallel() - runTestWithConfig("print.go", "", t, &compileopts.Options{ + runTestWithConfig("print.go", "", t, compileopts.Options{ Opt: "0", }, nil, nil) }) t.Run("ldflags", func(t *testing.T) { t.Parallel() - runTestWithConfig("ldflags.go", "", t, &compileopts.Options{ - Opt: "z", + runTestWithConfig("ldflags.go", "", t, compileopts.Options{ GlobalValues: map[string]map[string]string{ "main": { "someGlobal": "foobar", @@ -188,20 +187,20 @@ func runBuild(src, out string, opts *compileopts.Options) error { } func runTest(name, target string, t *testing.T, cmdArgs, environmentVars []string) { - options := &compileopts.Options{ - Target: target, - Opt: "z", - PrintIR: false, - DumpSSA: false, - VerifyIR: true, - Debug: true, - PrintSizes: "", - WasmAbi: "", + options := compileopts.Options{ + Target: target, } runTestWithConfig(name, target, t, options, cmdArgs, environmentVars) } -func runTestWithConfig(name, target string, t *testing.T, options *compileopts.Options, cmdArgs, environmentVars []string) { +func runTestWithConfig(name, target string, t *testing.T, options compileopts.Options, cmdArgs, environmentVars []string) { + // Set default config. + options.Debug = true + options.VerifyIR = true + if options.Opt == "" { + options.Opt = "z" + } + // Get the expected output for this test. // Note: not using filepath.Join as it strips the path separator at the end // of the path. @@ -230,7 +229,7 @@ func runTestWithConfig(name, target string, t *testing.T, options *compileopts.O // Build the test binary. binary := filepath.Join(tmpdir, "test") - err = runBuild("./"+path, binary, options) + err = runBuild("./"+path, binary, &options) if err != nil { printCompilerError(t.Log, err) t.Fail() diff --git a/src/crypto/rand/rand.go b/src/crypto/rand/rand.go new file mode 100644 index 000000000..e09acefe4 --- /dev/null +++ b/src/crypto/rand/rand.go @@ -0,0 +1,19 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rand implements a cryptographically secure +// random number generator. +package rand + +import "io" + +// Reader is a global, shared instance of a cryptographically +// secure random number generator. +var Reader io.Reader + +// Read is a helper function that calls Reader.Read using io.ReadFull. +// On return, n == len(b) if and only if err == nil. +func Read(b []byte) (n int, err error) { + return io.ReadFull(Reader, b) +} diff --git a/src/crypto/rand/rand_getentropy.go b/src/crypto/rand/rand_getentropy.go new file mode 100644 index 000000000..661132fb7 --- /dev/null +++ b/src/crypto/rand/rand_getentropy.go @@ -0,0 +1,38 @@ +// +build darwin freebsd wasi + +// This implementation of crypto/rand uses the getentropy system call (available +// on both MacOS and WASI) to generate random numbers. + +package rand + +import ( + "errors" + "unsafe" +) + +var errReadFailed = errors.New("rand: could not read random bytes") + +func init() { + Reader = &reader{} +} + +type reader struct { +} + +func (r *reader) Read(b []byte) (n int, err error) { + if len(b) != 0 { + if len(b) > 256 { + b = b[:256] + } + result := libc_getentropy(unsafe.Pointer(&b[0]), len(b)) + if result < 0 { + // Maybe we should return a syscall.Errno here? + return 0, errReadFailed + } + } + return len(b), nil +} + +// int getentropy(void *buf, size_t buflen); +//export getentropy +func libc_getentropy(buf unsafe.Pointer, buflen int) int diff --git a/src/crypto/rand/rand_urandom.go b/src/crypto/rand/rand_urandom.go new file mode 100644 index 000000000..64388de7c --- /dev/null +++ b/src/crypto/rand/rand_urandom.go @@ -0,0 +1,38 @@ +// +build linux,!baremetal,!wasi + +// This implementation of crypto/rand uses the /dev/urandom pseudo-file to +// generate random numbers. +// TODO: convert to the getentropy or getrandom libc function on Linux once it +// is more widely supported. + +package rand + +import ( + "syscall" +) + +func init() { + Reader = &reader{} +} + +type reader struct { + fd int +} + +func (r *reader) Read(b []byte) (n int, err error) { + if len(b) == 0 { + return + } + + // Open /dev/urandom first if needed. + if r.fd == 0 { + fd, err := syscall.Open("/dev/urandom", syscall.O_RDONLY, 0) + if err != nil { + return 0, err + } + r.fd = fd + } + + // Read from the file. + return syscall.Read(r.fd, b) +} diff --git a/src/crypto/rand/util.go b/src/crypto/rand/util.go new file mode 100644 index 000000000..4dd171120 --- /dev/null +++ b/src/crypto/rand/util.go @@ -0,0 +1,143 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rand + +import ( + "errors" + "io" + "math/big" +) + +// smallPrimes is a list of small, prime numbers that allows us to rapidly +// exclude some fraction of composite candidates when searching for a random +// prime. This list is truncated at the point where smallPrimesProduct exceeds +// a uint64. It does not include two because we ensure that the candidates are +// odd by construction. +var smallPrimes = []uint8{ + 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, +} + +// smallPrimesProduct is the product of the values in smallPrimes and allows us +// to reduce a candidate prime by this number and then determine whether it's +// coprime to all the elements of smallPrimes without further big.Int +// operations. +var smallPrimesProduct = new(big.Int).SetUint64(16294579238595022365) + +// Prime returns a number, p, of the given size, such that p is prime +// with high probability. +// Prime will return error for any error returned by rand.Read or if bits < 2. +func Prime(rand io.Reader, bits int) (p *big.Int, err error) { + if bits < 2 { + err = errors.New("crypto/rand: prime size must be at least 2-bit") + return + } + + b := uint(bits % 8) + if b == 0 { + b = 8 + } + + bytes := make([]byte, (bits+7)/8) + p = new(big.Int) + + bigMod := new(big.Int) + + for { + _, err = io.ReadFull(rand, bytes) + if err != nil { + return nil, err + } + + // Clear bits in the first byte to make sure the candidate has a size <= bits. + bytes[0] &= uint8(int(1<= 2 { + bytes[0] |= 3 << (b - 2) + } else { + // Here b==1, because b cannot be zero. + bytes[0] |= 1 + if len(bytes) > 1 { + bytes[1] |= 0x80 + } + } + // Make the value odd since an even number this large certainly isn't prime. + bytes[len(bytes)-1] |= 1 + + p.SetBytes(bytes) + + // Calculate the value mod the product of smallPrimes. If it's + // a multiple of any of these primes we add two until it isn't. + // The probability of overflowing is minimal and can be ignored + // because we still perform Miller-Rabin tests on the result. + bigMod.Mod(p, smallPrimesProduct) + mod := bigMod.Uint64() + + NextDelta: + for delta := uint64(0); delta < 1<<20; delta += 2 { + m := mod + delta + for _, prime := range smallPrimes { + if m%uint64(prime) == 0 && (bits > 6 || m != uint64(prime)) { + continue NextDelta + } + } + + if delta > 0 { + bigMod.SetUint64(delta) + p.Add(p, bigMod) + } + break + } + + // There is a tiny possibility that, by adding delta, we caused + // the number to be one bit too long. Thus we check BitLen + // here. + if p.ProbablyPrime(20) && p.BitLen() == bits { + return + } + } +} + +// Int returns a uniform random value in [0, max). It panics if max <= 0. +func Int(rand io.Reader, max *big.Int) (n *big.Int, err error) { + if max.Sign() <= 0 { + panic("crypto/rand: argument to Int is <= 0") + } + n = new(big.Int) + n.Sub(max, n.SetUint64(1)) + // bitLen is the maximum bit length needed to encode a value < max. + bitLen := n.BitLen() + if bitLen == 0 { + // the only valid result is 0 + return + } + // k is the maximum byte length needed to encode a value < max. + k := (bitLen + 7) / 8 + // b is the number of bits in the most significant byte of max-1. + b := uint(bitLen % 8) + if b == 0 { + b = 8 + } + + bytes := make([]byte, k) + + for { + _, err = io.ReadFull(rand, bytes) + if err != nil { + return nil, err + } + + // Clear bits in the first byte to increase the probability + // that the candidate is < max. + bytes[0] &= uint8(int(1< 1: + // Read with custom repeated value. + err = spi.rx(r, w[0]) + default: + // write/read + err = spi.txrx(w, r) + } + return err +} + +// Write a single byte and read a single byte from TX/RX FIFO. +func (spi SPI) Transfer(w byte) (byte, error) { + var deadline = ticks() + _SPITimeout + for !spi.isWritable() { + if ticks() > deadline { + return 0, ErrSPITimeout + } + } + + spi.Bus.SSPDR.Set(uint32(w)) + + for !spi.isReadable() { + if ticks() > deadline { + return 0, ErrSPITimeout + } + } + return uint8(spi.Bus.SSPDR.Get()), nil +} + +func (spi SPI) SetBaudRate(br uint32) error { + const freqin uint32 = 125 * MHz + const maxBaud uint32 = 66.5 * MHz // max output frequency is 66.5MHz on rp2040. see Note page 527. + // Find smallest prescale value which puts output frequency in range of + // post-divide. Prescale is an even number from 2 to 254 inclusive. + var prescale, postdiv uint32 + for prescale = 2; prescale < 255; prescale += 2 { + if freqin < (prescale+2)*256*br { + break + } + } + if prescale > 254 || br > maxBaud { + return ErrSPIBaud + } + // Find largest post-divide which makes output <= baudrate. Post-divide is + // an integer in the range 1 to 256 inclusive. + for postdiv = 256; postdiv > 1; postdiv-- { + if freqin/(prescale*(postdiv-1)) > br { + break + } + } + spi.Bus.SSPCPSR.Set(prescale) + spi.Bus.SSPCR0.ReplaceBits((postdiv-1)<> rp.SPI0_SSPCR0_SCR_Pos) + 1 + return freqin / (prescale * postdiv) +} + +// Configure is intended to setup/initialize the SPI interface. +// Default baudrate of 115200 is used if Frequency == 0. Default +// word length (data bits) is 8. +// Below is a list of GPIO pins corresponding to SPI0 bus on the rp2040: +// SI : 0, 4, 17 a.k.a RX and MISO (if rp2040 is master) +// SO : 3, 7, 19 a.k.a TX and MOSI (if rp2040 is master) +// SCK: 2, 6, 18 +// SPI1 bus GPIO pins: +// SI : 8, 12 +// SO : 11, 15 +// SCK: 10, 14 +// No pin configuration is needed of SCK, SDO and SDI needed after calling Configure. +func (spi SPI) Configure(config SPIConfig) error { + const defaultBaud uint32 = 115200 + if config.SCK == 0 { + // set default pins if config zero valued or invalid clock pin supplied. + switch spi.Bus { + case rp.SPI0: + config.SCK = SPI0_SCK_PIN + config.SDO = SPI0_SDO_PIN + config.SDI = SPI0_SDI_PIN + case rp.SPI1: + config.SCK = SPI1_SCK_PIN + config.SDO = SPI1_SDO_PIN + config.SDI = SPI1_SDI_PIN + } + } + if config.DataBits < 4 || config.DataBits > 16 { + config.DataBits = 8 + } + if config.Frequency == 0 { + config.Frequency = defaultBaud + } + // SPI pin configuration + config.SCK.setFunc(fnSPI) + config.SDO.setFunc(fnSPI) + config.SDI.setFunc(fnSPI) + + return spi.initSPI(config) +} + +func (spi SPI) initSPI(config SPIConfig) (err error) { + spi.reset() + // LSB-first not supported on PL022: + if config.LSBFirst { + return ErrLSBNotSupported + } + err = spi.SetBaudRate(config.Frequency) + // Set SPI Format (CPHA and CPOL) and frame format (default is Motorola) + spi.setFormat(config.DataBits, config.Mode, rp.XIP_SSI_CTRLR0_SPI_FRF_STD) + + // Always enable DREQ signals -- harmless if DMA is not listening + spi.Bus.SSPDMACR.SetBits(rp.SPI0_SSPDMACR_TXDMAE | rp.SPI0_SSPDMACR_RXDMAE) + // Finally enable the SPI + spi.Bus.SSPCR1.SetBits(rp.SPI0_SSPCR1_SSE) + return err +} + +//go:inline +func (spi SPI) setFormat(databits, mode uint8, frameFormat uint32) { + cpha := uint32(mode) & 1 + cpol := uint32(mode>>1) & 1 + spi.Bus.SSPCR0.ReplaceBits( + (cpha< deadline { + return ErrSPITimeout + } + } + spi.Bus.SSPDR.Set(uint32(tx[i])) + } + // Drain RX FIFO, then wait for shifting to finish (which may be *after* + // TX FIFO drains), then drain RX FIFO again + for spi.isReadable() { + spi.Bus.SSPDR.Get() + } + for spi.isBusy() { + if ticks() > deadline { + return ErrSPITimeout + } + } + for spi.isReadable() { + spi.Bus.SSPDR.Get() + } + // Don't leave overrun flag set + spi.Bus.SSPICR.Set(rp.SPI0_SSPICR_RORIC) + return nil +} + +// rx reads buffer to SPI ignoring x. +// txrepeat is output repeatedly on SO as data is read in from SI. +// Generally this can be 0, but some devices require a specific value here, +// e.g. SD cards expect 0xff +func (spi SPI) rx(rx []byte, txrepeat byte) error { + var deadline = ticks() + _SPITimeout + plen := len(rx) + const fifoDepth = 8 // see txrx + var rxleft, txleft = plen, plen + for txleft != 0 || rxleft != 0 { + if txleft != 0 && spi.isWritable() && rxleft < txleft+fifoDepth { + spi.Bus.SSPDR.Set(uint32(txrepeat)) + txleft-- + } + if rxleft != 0 && spi.isReadable() { + rx[plen-rxleft] = uint8(spi.Bus.SSPDR.Get()) + rxleft-- + continue // if reading succesfully in rx there is no need to check deadline. + } + if ticks() > deadline { + return ErrSPITimeout + } + } + return nil +} + +// Write len bytes from src to SPI. Simultaneously read len bytes from SPI to dst. +// Note this function is guaranteed to exit in a known amount of time (bits sent * time per bit) +func (spi SPI) txrx(tx, rx []byte) error { + var deadline = ticks() + _SPITimeout + plen := len(tx) + if plen != len(rx) { + return ErrTxInvalidSliceSize + } + // Never have more transfers in flight than will fit into the RX FIFO, + // else FIFO will overflow if this code is heavily interrupted. + const fifoDepth = 8 + var rxleft, txleft = plen, plen + for (txleft != 0 || rxleft != 0) && ticks() <= deadline { + if txleft != 0 && spi.isWritable() && rxleft < txleft+fifoDepth { + spi.Bus.SSPDR.Set(uint32(tx[plen-txleft])) + txleft-- + } + if rxleft != 0 && spi.isReadable() { + rx[plen-rxleft] = uint8(spi.Bus.SSPDR.Get()) + rxleft-- + } + } + + if txleft != 0 || rxleft != 0 { + // Transaction ended early due to timeout + return ErrSPITimeout + } + + return nil +} diff --git a/src/machine/machine_rp2040_uart.go b/src/machine/machine_rp2040_uart.go new file mode 100644 index 000000000..e2b5f7337 --- /dev/null +++ b/src/machine/machine_rp2040_uart.go @@ -0,0 +1,133 @@ +// +build rp2040 + +package machine + +import ( + "device/rp" + "runtime/interrupt" +) + +// UART on the RP2040. +type UART struct { + Buffer *RingBuffer + Bus *rp.UART0_Type + Interrupt interrupt.Interrupt +} + +// Configure the UART. +func (uart *UART) Configure(config UARTConfig) error { + initUART(uart) + + // Default baud rate to 115200. + if config.BaudRate == 0 { + config.BaudRate = 115200 + } + + // Use default pins if pins are not set. + if config.TX == 0 && config.RX == 0 { + // use default pins + config.TX = UART_TX_PIN + config.RX = UART_RX_PIN + } + + uart.SetBaudRate(config.BaudRate) + + // default to 8-1-N + uart.SetFormat(8, 1, ParityNone) + + // Enable the UART, both TX and RX + uart.Bus.UARTCR.SetBits(rp.UART0_UARTCR_UARTEN | + rp.UART0_UARTCR_RXE | + rp.UART0_UARTCR_TXE) + + // set GPIO mux to UART for the pins + config.TX.Configure(PinConfig{Mode: PinUART}) + config.RX.Configure(PinConfig{Mode: PinUART}) + + // Enable RX IRQ. + uart.Interrupt.SetPriority(0x80) + uart.Interrupt.Enable() + + // setup interrupt on receive + uart.Bus.UARTIMSC.Set(rp.UART0_UARTIMSC_RXIM) + + return nil +} + +// SetBaudRate sets the baudrate to be used for the UART. +func (uart *UART) SetBaudRate(br uint32) { + div := 8 * 125 * MHz / br + + ibrd := div >> 7 + var fbrd uint32 + + switch { + case ibrd == 0: + ibrd = 1 + fbrd = 0 + case ibrd >= 65535: + ibrd = 65535 + fbrd = 0 + default: + fbrd = ((div & 0x7f) + 1) / 2 + } + + // set PL011 baud divisor registers + uart.Bus.UARTIBRD.Set(ibrd) + uart.Bus.UARTFBRD.Set(fbrd) + + // PL011 needs a (dummy) line control register write. + // See https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/hardware_uart/uart.c#L93-L95 + uart.Bus.UARTLCR_H.SetBits(0) +} + +// WriteByte writes a byte of data to the UART. +func (uart *UART) WriteByte(c byte) error { + // wait until buffer is not full + for uart.Bus.UARTFR.HasBits(rp.UART0_UARTFR_TXFF) { + } + + // write data + uart.Bus.UARTDR.Set(uint32(c)) + return nil +} + +// 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 + if parity != ParityNone { + pen = rp.UART0_UARTLCR_H_PEN + } + if parity == ParityEven { + pev = rp.UART0_UARTLCR_H_EPS + } + uart.Bus.UARTLCR_H.SetBits(uint32((databits-5)< 0 { + buf = append(buf, ':') + } + buf = append(buf, hexDigit[b>>4]) + buf = append(buf, hexDigit[b&0xF]) + } + return string(buf) +} + +// ParseMAC parses s as an IEEE 802 MAC-48, EUI-48, EUI-64, or a 20-octet +// IP over InfiniBand link-layer address using one of the following formats: +// 00:00:5e:00:53:01 +// 02:00:5e:10:00:00:00:01 +// 00:00:00:00:fe:80:00:00:00:00:00:00:02:00:5e:10:00:00:00:01 +// 00-00-5e-00-53-01 +// 02-00-5e-10-00-00-00-01 +// 00-00-00-00-fe-80-00-00-00-00-00-00-02-00-5e-10-00-00-00-01 +// 0000.5e00.5301 +// 0200.5e10.0000.0001 +// 0000.0000.fe80.0000.0000.0000.0200.5e10.0000.0001 +func ParseMAC(s string) (hw HardwareAddr, err error) { + if len(s) < 14 { + goto err + } + + if s[2] == ':' || s[2] == '-' { + if (len(s)+1)%3 != 0 { + goto err + } + n := (len(s) + 1) / 3 + if n != 6 && n != 8 && n != 20 { + goto err + } + hw = make(HardwareAddr, n) + for x, i := 0, 0; i < n; i++ { + var ok bool + if hw[i], ok = xtoi2(s[x:], s[2]); !ok { + goto err + } + x += 3 + } + } else if s[4] == '.' { + if (len(s)+1)%5 != 0 { + goto err + } + n := 2 * (len(s) + 1) / 5 + if n != 6 && n != 8 && n != 20 { + goto err + } + hw = make(HardwareAddr, n) + for x, i := 0, 0; i < n; i += 2 { + var ok bool + if hw[i], ok = xtoi2(s[x:x+2], 0); !ok { + goto err + } + if hw[i+1], ok = xtoi2(s[x+2:], s[4]); !ok { + goto err + } + x += 5 + } + } else { + goto err + } + return hw, nil + +err: + return nil, &AddrError{Err: "invalid MAC address", Addr: s} +} diff --git a/src/net/net.go b/src/net/net.go index 53372e677..d1bea59e0 100644 --- a/src/net/net.go +++ b/src/net/net.go @@ -6,6 +6,159 @@ package net +import "time" + +// Addr represents a network end point address. +// +// The two methods Network and String conventionally return strings +// that can be passed as the arguments to Dial, but the exact form +// and meaning of the strings is up to the implementation. +type Addr interface { + Network() string // name of the network (for example, "tcp", "udp") + String() string // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80") +} + +// Conn is a generic stream-oriented network connection. +// +// Multiple goroutines may invoke methods on a Conn simultaneously. +type Conn interface { + // Read reads data from the connection. + // Read can be made to time out and return an error after a fixed + // time limit; see SetDeadline and SetReadDeadline. + Read(b []byte) (n int, err error) + + // Write writes data to the connection. + // Write can be made to time out and return an error after a fixed + // time limit; see SetDeadline and SetWriteDeadline. + Write(b []byte) (n int, err error) + + // Close closes the connection. + // Any blocked Read or Write operations will be unblocked and return errors. + Close() error + + // LocalAddr returns the local network address. + LocalAddr() Addr + + // RemoteAddr returns the remote network address. + RemoteAddr() Addr + + // SetDeadline sets the read and write deadlines associated + // with the connection. It is equivalent to calling both + // SetReadDeadline and SetWriteDeadline. + // + // A deadline is an absolute time after which I/O operations + // fail instead of blocking. The deadline applies to all future + // and pending I/O, not just the immediately following call to + // Read or Write. After a deadline has been exceeded, the + // connection can be refreshed by setting a deadline in the future. + // + // If the deadline is exceeded a call to Read or Write or to other + // I/O methods will return an error that wraps os.ErrDeadlineExceeded. + // This can be tested using errors.Is(err, os.ErrDeadlineExceeded). + // The error's Timeout method will return true, but note that there + // are other possible errors for which the Timeout method will + // return true even if the deadline has not been exceeded. + // + // An idle timeout can be implemented by repeatedly extending + // the deadline after successful Read or Write calls. + // + // A zero value for t means I/O operations will not time out. + SetDeadline(t time.Time) error + + // SetReadDeadline sets the deadline for future Read calls + // and any currently-blocked Read call. + // A zero value for t means Read will not time out. + SetReadDeadline(t time.Time) error + + // SetWriteDeadline sets the deadline for future Write calls + // and any currently-blocked Write call. + // Even if write times out, it may return n > 0, indicating that + // some of the data was successfully written. + // A zero value for t means Write will not time out. + SetWriteDeadline(t time.Time) error +} + +type conn struct { + // +} + +// A Listener is a generic network listener for stream-oriented protocols. +// +// Multiple goroutines may invoke methods on a Listener simultaneously. +type Listener interface { + // Accept waits for and returns the next connection to the listener. + Accept() (Conn, error) + + // Close closes the listener. + // Any blocked Accept operations will be unblocked and return errors. + Close() error + + // Addr returns the listener's network address. + Addr() Addr +} + +// An Error represents a network error. +type Error interface { + error + Timeout() bool // Is the error a timeout? + Temporary() bool // Is the error temporary? +} + +// OpError is the error type usually returned by functions in the net +// package. It describes the operation, network type, and address of +// an error. +type OpError struct { + // Op is the operation which caused the error, such as + // "read" or "write". + Op string + + // Net is the network type on which this error occurred, + // such as "tcp" or "udp6". + Net string + + // For operations involving a remote network connection, like + // Dial, Read, or Write, Source is the corresponding local + // network address. + Source Addr + + // Addr is the network address for which this error occurred. + // For local operations, like Listen or SetDeadline, Addr is + // the address of the local endpoint being manipulated. + // For operations involving a remote network connection, like + // Dial, Read, or Write, Addr is the remote address of that + // connection. + Addr Addr + + // Err is the error that occurred during the operation. + // The Error method panics if the error is nil. + Err error +} + +func (e *OpError) Unwrap() error { return e.Err } + +func (e *OpError) Error() string { + if e == nil { + return "" + } + s := e.Op + if e.Net != "" { + s += " " + e.Net + } + if e.Source != nil { + s += " " + e.Source.String() + } + if e.Addr != nil { + if e.Source != nil { + s += "->" + } else { + s += " " + } + s += e.Addr.String() + } + s += ": " + e.Err.Error() + return s +} + // A ParseError is the error type of literal network address parsers. type ParseError struct { // Type is the type of string that was expected, such as @@ -36,3 +189,10 @@ func (e *AddrError) Error() string { func (e *AddrError) Timeout() bool { return false } func (e *AddrError) Temporary() bool { return false } + +// ErrClosed is the error returned by an I/O call on a network +// connection that has already been closed, or that is closed by +// another goroutine before the I/O is completed. This may be wrapped +// in another error, and should normally be tested using +// errors.Is(err, net.ErrClosed). +var ErrClosed = errClosed diff --git a/src/net/parse.go b/src/net/parse.go index 1255f918f..2a840c854 100644 --- a/src/net/parse.go +++ b/src/net/parse.go @@ -52,6 +52,18 @@ func xtoi(s string) (n int, i int, ok bool) { return n, i, true } +// xtoi2 converts the next two hex digits of s into a byte. +// If s is longer than 2 bytes then the third byte must be e. +// If the first two bytes of s are not hex digits or the third byte +// does not match e, false is returned. +func xtoi2(s string, e byte) (byte, bool) { + if len(s) > 2 && s[2] != e { + return 0, false + } + n, ei, ok := xtoi(s[:2]) + return byte(n), ok && ei == 2 +} + // Convert unsigned integer to decimal string. func uitoa(val uint) string { if val == 0 { // avoid string allocation diff --git a/src/net/tcpsock.go b/src/net/tcpsock.go new file mode 100644 index 000000000..4af06a857 --- /dev/null +++ b/src/net/tcpsock.go @@ -0,0 +1,11 @@ +package net + +// TCPConn is an implementation of the Conn interface for TCP network +// connections. +type TCPConn struct { + conn +} + +func (c *TCPConn) CloseWrite() error { + return &OpError{"close", "", nil, nil, ErrNotImplemented} +} diff --git a/src/os/exec.go b/src/os/exec.go index 66cc79999..8bc544ba2 100644 --- a/src/os/exec.go +++ b/src/os/exec.go @@ -1,6 +1,18 @@ package os +import "syscall" + type Signal interface { String() string Signal() // to distinguish from other Stringers } + +// Getpid returns the process id of the caller, or -1 if unavailable. +func Getpid() int { + return syscall.Getpid() +} + +// Getppid returns the process id of the caller's parent, or -1 if unavailable. +func Getppid() int { + return syscall.Getppid() +} diff --git a/src/os/file.go b/src/os/file.go index 99977fa4e..4d90bdde5 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -116,6 +116,11 @@ func (f *File) Readdirnames(n int) (names []string, err error) { return nil, &PathError{"readdirnames", f.name, ErrNotImplemented} } +// Seek is a stub, not yet implemented +func (f *File) Seek(offset int64, whence int) (ret int64, err error) { + return 0, &PathError{"seek", f.name, ErrNotImplemented} +} + // Stat is a stub, not yet implemented func (f *File) Stat() (FileInfo, error) { return nil, &PathError{"stat", f.name, ErrNotImplemented} @@ -191,8 +196,3 @@ func Readlink(name string) (string, error) { func TempDir() string { return "/tmp" } - -// Getpid is a stub (for now), always returning 1 -func Getpid() int { - return 1 -} diff --git a/src/os/file_go_other.go b/src/os/file_go_other.go index d8d680ffe..351de7acc 100644 --- a/src/os/file_go_other.go +++ b/src/os/file_go_other.go @@ -44,3 +44,8 @@ const ( func (m FileMode) IsDir() bool { return false } + +// IsRegular is a stub, always returning false +func (m FileMode) IsRegular() bool { + return false +} diff --git a/src/os/proc.go b/src/os/proc.go index d3bfb1270..fe2245f36 100644 --- a/src/os/proc.go +++ b/src/os/proc.go @@ -24,3 +24,31 @@ func runtime_args() []string // in package runtime func Exit(code int) { syscall.Exit(code) } + +// Getuid returns the numeric user id of the caller. +// +// On non-POSIX systems, it returns -1. +func Getuid() int { + return syscall.Getuid() +} + +// Geteuid returns the numeric effective user id of the caller. +// +// On non-POSIX systems, it returns -1. +func Geteuid() int { + return syscall.Geteuid() +} + +// Getgid returns the numeric group id of the caller. +// +// On non-POSIX systems, it returns -1. +func Getgid() int { + return syscall.Getgid() +} + +// Getegid returns the numeric effective group id of the caller. +// +// On non-POSIX systems, it returns -1. +func Getegid() int { + return syscall.Getegid() +} diff --git a/src/os/tempfile.go b/src/os/tempfile.go new file mode 100644 index 000000000..27949f7fa --- /dev/null +++ b/src/os/tempfile.go @@ -0,0 +1,5 @@ +package os + +func CreateTemp(dir, pattern string) (*File, error) { + return nil, &PathError{"createtemp", pattern, ErrNotImplemented} +} diff --git a/src/reflect/makefunc.go b/src/reflect/makefunc.go new file mode 100644 index 000000000..2234f2591 --- /dev/null +++ b/src/reflect/makefunc.go @@ -0,0 +1,5 @@ +package reflect + +func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { + panic("unimplemented: reflect.MakeFunc()") +} diff --git a/src/reflect/value.go b/src/reflect/value.go index 11a54279e..88f8137af 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -840,3 +840,7 @@ func (v Value) FieldByName(name string) Value { func MakeMap(typ Type) Value { panic("unimplemented: reflect.MakeMap()") } + +func (v Value) Call(in []Value) []Value { + panic("unimplemented: (reflect.Value).Call()") +} diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index e2dc4b5bf..19caa619b 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -1,4 +1,4 @@ -// +build arm,!baremetal,!wasm arm,arm7tdmi +// +build arm,!baremetal,!tinygo.wasm arm,arm7tdmi package runtime diff --git a/src/runtime/arch_tinygowasm.go b/src/runtime/arch_tinygowasm.go new file mode 100644 index 000000000..a19a14487 --- /dev/null +++ b/src/runtime/arch_tinygowasm.go @@ -0,0 +1,107 @@ +// +build tinygo.wasm + +package runtime + +import ( + "unsafe" +) + +const GOARCH = "wasm" + +// The bitness of the CPU (e.g. 8, 32, 64). +const TargetBits = 32 + +//go:extern __heap_base +var heapStartSymbol [0]byte + +//go:extern __global_base +var globalsStartSymbol [0]byte + +//export llvm.wasm.memory.size.i32 +func wasm_memory_size(index int32) int32 + +//export llvm.wasm.memory.grow.i32 +func wasm_memory_grow(index int32, delta int32) int32 + +var ( + heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) + heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) + globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol)) + globalsEnd = uintptr(unsafe.Pointer(&heapStartSymbol)) +) + +const wasmPageSize = 64 * 1024 + +func align(ptr uintptr) uintptr { + // Align to 16, which is the alignment of max_align_t: + // https://godbolt.org/z/dYqTsWrGq + const heapAlign = 16 + return (ptr + heapAlign - 1) &^ (heapAlign - 1) +} + +func getCurrentStackPointer() uintptr + +// growHeap tries to grow the heap size. It returns true if it succeeds, false +// otherwise. +func growHeap() bool { + // Grow memory by the available size, which means the heap size is doubled. + memorySize := wasm_memory_size(0) + result := wasm_memory_grow(0, memorySize) + if result == -1 { + // Grow failed. + return false + } + + setHeapEnd(uintptr(wasm_memory_size(0) * wasmPageSize)) + + // Heap has grown successfully. + return true +} + +// The below functions override the default allocator of wasi-libc. +// Most functions are defined but unimplemented to make sure that if there is +// any code using them, they will get an error instead of (incorrectly) using +// the wasi-libc dlmalloc heap implementation instead. If they are needed by any +// program, they can certainly be implemented. + +//export malloc +func libc_malloc(size uintptr) unsafe.Pointer { + return alloc(size) +} + +//export free +func libc_free(ptr unsafe.Pointer) { + free(ptr) +} + +//export calloc +func libc_calloc(nmemb, size uintptr) unsafe.Pointer { + // Note: we could be even more correct here and check that nmemb * size + // doesn't overflow. However the current implementation should normally work + // fine. + return alloc(nmemb * size) +} + +//export realloc +func libc_realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer { + runtimePanic("unimplemented: realloc") + return nil +} + +//export posix_memalign +func libc_posix_memalign(memptr *unsafe.Pointer, alignment, size uintptr) int { + runtimePanic("unimplemented: posix_memalign") + return 0 +} + +//export aligned_alloc +func libc_aligned_alloc(alignment, bytes uintptr) unsafe.Pointer { + runtimePanic("unimplemented: aligned_alloc") + return nil +} + +//export malloc_usable_size +func libc_malloc_usable_size(ptr unsafe.Pointer) uintptr { + runtimePanic("unimplemented: malloc_usable_size") + return 0 +} diff --git a/src/runtime/arch_wasm.go b/src/runtime/arch_wasm.go deleted file mode 100644 index 00dd0deb5..000000000 --- a/src/runtime/arch_wasm.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build wasm - -package runtime - -import ( - "unsafe" -) - -const GOARCH = "wasm" - -// The bitness of the CPU (e.g. 8, 32, 64). -const TargetBits = 32 - -//go:extern __heap_base -var heapStartSymbol [0]byte - -//go:extern __global_base -var globalsStartSymbol [0]byte - -//export llvm.wasm.memory.size.i32 -func wasm_memory_size(index int32) int32 - -//export llvm.wasm.memory.grow.i32 -func wasm_memory_grow(index int32, delta int32) int32 - -var ( - heapStart = uintptr(unsafe.Pointer(&heapStartSymbol)) - heapEnd = uintptr(wasm_memory_size(0) * wasmPageSize) - globalsStart = uintptr(unsafe.Pointer(&globalsStartSymbol)) - globalsEnd = uintptr(unsafe.Pointer(&heapStartSymbol)) -) - -const wasmPageSize = 64 * 1024 - -// Align on word boundary. -func align(ptr uintptr) uintptr { - return (ptr + 3) &^ 3 -} - -func getCurrentStackPointer() uintptr - -// growHeap tries to grow the heap size. It returns true if it succeeds, false -// otherwise. -func growHeap() bool { - // Grow memory by the available size, which means the heap size is doubled. - memorySize := wasm_memory_size(0) - result := wasm_memory_grow(0, memorySize) - if result == -1 { - // Grow failed. - return false - } - - setHeapEnd(uintptr(wasm_memory_size(0) * wasmPageSize)) - - // Heap has grown successfully. - return true -} diff --git a/src/runtime/baremetal.go b/src/runtime/baremetal.go index ce69e338d..5abd13710 100644 --- a/src/runtime/baremetal.go +++ b/src/runtime/baremetal.go @@ -52,3 +52,24 @@ func syscall_Exit(code int) { } const baremetal = true + +// timeOffset is how long the monotonic clock started after the Unix epoch. It +// should be a positive integer under normal operation or zero when it has not +// been set. +var timeOffset int64 + +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + mono = nanotime() + sec = (mono + timeOffset) / (1000 * 1000 * 1000) + nsec = int32((mono + timeOffset) - sec*(1000*1000*1000)) + return +} + +// AdjustTimeOffset adds the given offset to the built-in time offset. A +// positive value adds to the time (skipping some time), a negative value moves +// the clock into the past. +func AdjustTimeOffset(offset int64) { + // TODO: do this atomically? + timeOffset += offset +} diff --git a/src/runtime/extern.go b/src/runtime/extern.go new file mode 100644 index 000000000..12de02840 --- /dev/null +++ b/src/runtime/extern.go @@ -0,0 +1,5 @@ +package runtime + +func Callers(skip int, pc []uintptr) int { + return 0 +} diff --git a/src/runtime/gc_conservative.go b/src/runtime/gc_conservative.go index 7638fb0d6..67fbfdb2c 100644 --- a/src/runtime/gc_conservative.go +++ b/src/runtime/gc_conservative.go @@ -51,7 +51,7 @@ const ( ) var ( - metadataStart unsafe.Pointer // pointer to the start of the heap + metadataStart unsafe.Pointer // pointer to the start of the heap metadata nextAlloc gcBlock // the next block that should be tried by the allocator endBlock gcBlock // the block just past the end of the available space ) @@ -228,6 +228,14 @@ func setHeapEnd(newHeapEnd uintptr) { // This function can be called again when the heap size increases. The caller is // responsible for copying the metadata to the new location. func calculateHeapAddresses() { + if GOARCH == "wasm" { + // This is a workaround for a bug in wasm-ld: wasm-ld doesn't always + // align __heap_base and when this memory is shared through an API, it + // might result in unaligned memory. For details, see: + // https://reviews.llvm.org/D106499 + // It should be removed once we switch to LLVM 13, where this is fixed. + heapStart = align(heapStart) + } totalSize := heapEnd - heapStart // Allocate some memory to keep 2 bits of information about every block. diff --git a/src/runtime/gc_globals_conservative.go b/src/runtime/gc_globals_conservative.go index bee55a88f..564765515 100644 --- a/src/runtime/gc_globals_conservative.go +++ b/src/runtime/gc_globals_conservative.go @@ -1,5 +1,5 @@ // +build gc.conservative gc.extalloc -// +build baremetal wasm +// +build baremetal tinygo.wasm package runtime diff --git a/src/runtime/gc_globals_precise.go b/src/runtime/gc_globals_precise.go index 2d6c8c14b..f79b71fdc 100644 --- a/src/runtime/gc_globals_precise.go +++ b/src/runtime/gc_globals_precise.go @@ -1,5 +1,5 @@ // +build gc.conservative gc.extalloc -// +build !baremetal,!wasm +// +build !baremetal,!tinygo.wasm package runtime diff --git a/src/runtime/gc_stack_portable.go b/src/runtime/gc_stack_portable.go index d48b24972..d4a046374 100644 --- a/src/runtime/gc_stack_portable.go +++ b/src/runtime/gc_stack_portable.go @@ -1,5 +1,5 @@ // +build gc.conservative gc.extalloc -// +build wasm +// +build tinygo.wasm package runtime diff --git a/src/runtime/gc_stack_raw.go b/src/runtime/gc_stack_raw.go index 74be7fe82..01b07d9bb 100644 --- a/src/runtime/gc_stack_raw.go +++ b/src/runtime/gc_stack_raw.go @@ -1,5 +1,5 @@ // +build gc.conservative gc.extalloc -// +build !wasm +// +build !tinygo.wasm package runtime diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go new file mode 100644 index 000000000..d2723fe93 --- /dev/null +++ b/src/runtime/mstats.go @@ -0,0 +1,69 @@ +// +build gc.conservative + +package runtime + +// Memory statistics + +// Subset of memory statistics from upstream Go. +// Works with conservative gc only. + +// A MemStats records statistics about the memory allocator. +type MemStats struct { + // General statistics. + + // Sys is the total bytes of memory obtained from the OS. + // + // Sys is the sum of the XSys fields below. Sys measures the + // address space reserved by the runtime for the + // heap, stacks, and other internal data structures. + Sys uint64 + + // Heap memory statistics. + + // HeapSys is bytes of heap memory, total. + // + // In TinyGo unlike upstream Go, we make no distinction between + // regular heap blocks used by escaped-to-the-heap variables and + // blocks occupied by goroutine stacks, + // all such blocks are marked as in-use, see HeapInuse below. + HeapSys uint64 + + // HeapIdle is bytes in idle (unused) blocks. + HeapIdle uint64 + + // HeapInuse is bytes in in-use blocks. + HeapInuse uint64 + + // HeapReleased is bytes of physical memory returned to the OS. + HeapReleased uint64 + + // Off-heap memory statistics. + // + // The following statistics measure runtime-internal + // structures that are not allocated from heap memory (usually + // because they are part of implementing the heap). + + // GCSys is bytes of memory in garbage collection metadata. + GCSys uint64 +} + +// ReadMemStats populates m with memory statistics. +// +// The returned memory statistics are up to date as of the +// call to ReadMemStats. This would not do GC implicitly for you. +func ReadMemStats(m *MemStats) { + m.HeapIdle = 0 + m.HeapInuse = 0 + for block := gcBlock(0); block < endBlock; block++ { + bstate := block.state() + if bstate == blockStateFree { + m.HeapIdle += uint64(bytesPerBlock) + } else { + m.HeapInuse += uint64(bytesPerBlock) + } + } + m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS. + m.HeapSys = m.HeapInuse + m.HeapIdle + m.GCSys = uint64(heapEnd - uintptr(metadataStart)) + m.Sys = uint64(heapEnd - heapStart) +} diff --git a/src/runtime/os_darwin.go b/src/runtime/os_darwin.go index ac31b67da..be8789098 100644 --- a/src/runtime/os_darwin.go +++ b/src/runtime/os_darwin.go @@ -11,3 +11,9 @@ const ( flag_MAP_PRIVATE = 0x2 flag_MAP_ANONYMOUS = 0x1000 // MAP_ANON ) + +// Source: https://opensource.apple.com/source/Libc/Libc-1439.100.3/include/time.h.auto.html +const ( + clock_REALTIME = 0 + clock_MONOTONIC_RAW = 4 +) diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index 7613134e3..aa056173b 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -11,3 +11,9 @@ const ( flag_MAP_PRIVATE = 0x2 flag_MAP_ANONYMOUS = 0x20 ) + +// Source: https://github.com/torvalds/linux/blob/master/include/uapi/linux/time.h +const ( + clock_REALTIME = 0 + clock_MONOTONIC_RAW = 4 +) diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 7b1b4f5ae..55102411b 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -68,27 +68,6 @@ func nanotime() int64 { return ticksToNanoseconds(ticks()) } -// timeOffset is how long the monotonic clock started after the Unix epoch. It -// should be a positive integer under normal operation or zero when it has not -// been set. -var timeOffset int64 - -//go:linkname now time.now -func now() (sec int64, nsec int32, mono int64) { - mono = nanotime() - sec = (mono + timeOffset) / (1000 * 1000 * 1000) - nsec = int32((mono + timeOffset) - sec*(1000*1000*1000)) - return -} - -// AdjustTimeOffset adds the given offset to the built-in time offset. A -// positive value adds to the time (skipping some time), a negative value moves -// the clock into the past. -func AdjustTimeOffset(offset int64) { - // TODO: do this atomically? - timeOffset += offset -} - // Copied from the Go runtime source code. //go:linkname os_sigpipe os.sigpipe func os_sigpipe() { diff --git a/src/runtime/runtime_nrf_softdevice.go b/src/runtime/runtime_nrf_softdevice.go index 36269e11e..b1c970333 100644 --- a/src/runtime/runtime_nrf_softdevice.go +++ b/src/runtime/runtime_nrf_softdevice.go @@ -10,6 +10,9 @@ import ( //export sd_app_evt_wait func sd_app_evt_wait() +// This is a global variable to avoid a heap allocation in waitForEvents. +var softdeviceEnabled uint8 + func waitForEvents() { // Call into the SoftDevice to sleep. This is necessary here because a // normal wfe will not put the chip in low power mode (it still consumes @@ -18,10 +21,9 @@ func waitForEvents() { // First check whether the SoftDevice is enabled. Unfortunately, // sd_app_evt_wait cannot be called when the SoftDevice is not enabled. - var enabled uint8 - arm.SVCall1(0x12, &enabled) // sd_softdevice_is_enabled + arm.SVCall1(0x12, &softdeviceEnabled) // sd_softdevice_is_enabled - if enabled != 0 { + if softdeviceEnabled != 0 { // Now pick the appropriate SVCall number. Hopefully they won't change // in the future with a different SoftDevice version. if nrf.Device == "nrf51" { diff --git a/src/runtime/runtime_rp2040.go b/src/runtime/runtime_rp2040.go index 162f18f1e..7d3c93100 100644 --- a/src/runtime/runtime_rp2040.go +++ b/src/runtime/runtime_rp2040.go @@ -4,6 +4,8 @@ package runtime import ( "device/arm" + + "machine" ) // machineTicks is provided by package machine. @@ -39,6 +41,7 @@ func waitForEvents() { } func putchar(c byte) { + machine.Serial.WriteByte(c) } // machineInit is provided by package machine. @@ -46,6 +49,8 @@ func machineInit() func init() { machineInit() + + machine.Serial.Configure(machine.UARTConfig{}) } func postinit() {} diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_tinygowasm.go similarity index 87% rename from src/runtime/runtime_wasm.go rename to src/runtime/runtime_tinygowasm.go index 1146f5781..80eaa8d3e 100644 --- a/src/runtime/runtime_wasm.go +++ b/src/runtime/runtime_tinygowasm.go @@ -1,4 +1,4 @@ -// +build wasm +// +build tinygo.wasm package runtime @@ -50,6 +50,14 @@ func putchar(c byte) { } } +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + mono = nanotime() + sec = mono / (1000 * 1000 * 1000) + nsec = int32(mono - sec*(1000*1000*1000)) + return +} + // Abort executes the wasm 'unreachable' instruction. func abort() { trap() diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index c185c4ab7..464d8dfe5 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -38,8 +38,6 @@ type timespec struct { tv_nsec int // long: on Linux and macOS, follows the platform bitness } -const CLOCK_MONOTONIC_RAW = 4 - var stackTop uintptr func postinit() {} @@ -138,19 +136,31 @@ func sleepTicks(d timeUnit) { usleep(uint(d) / 1000) } -// Return monotonic time in nanoseconds. -// -// TODO: noescape -func monotime() uint64 { +func getTime(clock int32) uint64 { ts := timespec{} - clock_gettime(CLOCK_MONOTONIC_RAW, &ts) + clock_gettime(clock, &ts) return uint64(ts.tv_sec)*1000*1000*1000 + uint64(ts.tv_nsec) } +// Return monotonic time in nanoseconds. +func monotime() uint64 { + return getTime(clock_MONOTONIC_RAW) +} + func ticks() timeUnit { return timeUnit(monotime()) } +//go:linkname now time.now +func now() (sec int64, nsec int32, mono int64) { + ts := timespec{} + clock_gettime(clock_REALTIME, &ts) + sec = int64(ts.tv_sec) + nsec = int32(ts.tv_nsec) + mono = nanotime() + return +} + //go:linkname syscall_Exit syscall.Exit func syscall_Exit(code int) { exit(code) diff --git a/src/runtime/runtime_wasm_wasi.go b/src/runtime/runtime_wasm_wasi.go index 76d0a4520..4f0432f7a 100644 --- a/src/runtime/runtime_wasm_wasi.go +++ b/src/runtime/runtime_wasm_wasi.go @@ -1,4 +1,4 @@ -// +build wasm,wasi +// +build tinygo.wasm,wasi package runtime diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index c5286d33b..44b07f75d 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -88,7 +88,7 @@ func addSleepTask(t *task.Task, duration timeUnit) { panic("runtime: addSleepTask: expected next task to be nil") } } - t.Data = uint(duration) // TODO: longer durations + t.Data = uint64(duration) now := ticks() if sleepQueue == nil { scheduleLog(" -> sleep new queue") diff --git a/src/runtime/symtab.go b/src/runtime/symtab.go new file mode 100644 index 000000000..0a3ca3da8 --- /dev/null +++ b/src/runtime/symtab.go @@ -0,0 +1,20 @@ +package runtime + +type Frames struct { + // +} + +type Frame struct { + Function string + + File string + Line int +} + +func CallersFrames(callers []uintptr) *Frames { + return nil +} + +func (ci *Frames) Next() (frame Frame, more bool) { + return Frame{}, false +} diff --git a/src/sync/cond.go b/src/sync/cond.go index e392bc6e2..e65e86ed1 100644 --- a/src/sync/cond.go +++ b/src/sync/cond.go @@ -16,6 +16,10 @@ type earlySignal struct { signaled bool } +func NewCond(l Locker) *Cond { + return &Cond{L: l} +} + func (c *Cond) trySignal() bool { // Pop a blocked task off of the stack, and schedule it if applicable. t := c.blocked.Pop() diff --git a/src/syscall/proc_emulated.go b/src/syscall/proc_emulated.go new file mode 100644 index 000000000..89b22e818 --- /dev/null +++ b/src/syscall/proc_emulated.go @@ -0,0 +1,13 @@ +// +build baremetal wasi wasm + +// This file emulates some process-related functions that are only available +// under a real operating system. + +package syscall + +func Getuid() int { return -1 } +func Geteuid() int { return -1 } +func Getgid() int { return -1 } +func Getegid() int { return -1 } +func Getpid() int { return -1 } +func Getppid() int { return -1 } diff --git a/src/syscall/proc_hosted.go b/src/syscall/proc_hosted.go new file mode 100644 index 000000000..5f52a4ca8 --- /dev/null +++ b/src/syscall/proc_hosted.go @@ -0,0 +1,37 @@ +// +build !baremetal,!wasi,!wasm + +// This file assumes there is a libc available that runs on a real operating +// system. + +package syscall + +func Getuid() int { return int(libc_getuid()) } +func Geteuid() int { return int(libc_geteuid()) } +func Getgid() int { return int(libc_getgid()) } +func Getegid() int { return int(libc_getegid()) } +func Getpid() int { return int(libc_getpid()) } +func Getppid() int { return int(libc_getppid()) } + +// uid_t getuid(void) +//export getuid +func libc_getuid() int32 + +// gid_t getgid(void) +//export getgid +func libc_getgid() int32 + +// uid_t geteuid(void) +//export geteuid +func libc_geteuid() int32 + +// gid_t getegid(void) +//export getegid +func libc_getegid() int32 + +// gid_t getpid(void) +//export getpid +func libc_getpid() int32 + +// gid_t getppid(void) +//export getppid +func libc_getppid() int32 diff --git a/src/syscall/syscall_baremetal.go b/src/syscall/syscall_baremetal.go index 28fa39ba8..4f0c6e530 100644 --- a/src/syscall/syscall_baremetal.go +++ b/src/syscall/syscall_baremetal.go @@ -98,14 +98,8 @@ type ProcAttr struct { type SysProcAttr struct { } -func Getegid() int { return 1 } -func Geteuid() int { return 1 } -func Getgid() int { return 1 } func Getgroups() ([]int, error) { return []int{1}, nil } -func Getppid() int { return 2 } -func Getpid() int { return 3 } func Gettimeofday(tv *Timeval) error { return ENOSYS } -func Getuid() int { return 1 } func Kill(pid int, signum Signal) error { return ENOSYS } func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err error) { return 0, ENOSYS diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 2612938de..bc6359630 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -13,7 +13,7 @@ type sliceHeader struct { } func Close(fd int) (err error) { - if libc_close(fd) < 0 { + if libc_close(int32(fd)) < 0 { err = getErrno() } return @@ -21,7 +21,7 @@ func Close(fd int) (err error) { func Write(fd int, p []byte) (n int, err error) { buf, count := splitSlice(p) - n = libc_write(fd, buf, uint(count)) + n = libc_write(int32(fd), buf, uint(count)) if n < 0 { err = getErrno() } @@ -30,7 +30,7 @@ func Write(fd int, p []byte) (n int, err error) { func Read(fd int, p []byte) (n int, err error) { buf, count := splitSlice(p) - n = libc_read(fd, buf, uint(count)) + n = libc_read(int32(fd), buf, uint(count)) if n < 0 { err = getErrno() } @@ -43,7 +43,7 @@ func Seek(fd int, offset int64, whence int) (off int64, err error) { func Open(path string, flag int, mode uint32) (fd int, err error) { data := append([]byte(path), 0) - fd = libc_open(&data[0], flag, mode) + fd = int(libc_open(&data[0], int32(flag), mode)) if fd < 0 { err = getErrno() } @@ -62,10 +62,6 @@ func Kill(pid int, sig Signal) (err error) { return ENOSYS // TODO } -func Getpid() (pid int) { - panic("unimplemented: getpid") // TODO -} - func Getenv(key string) (value string, found bool) { data := append([]byte(key), 0) raw := libc_getenv(&data[0]) @@ -91,7 +87,7 @@ func splitSlice(p []byte) (buf *byte, len uintptr) { // ssize_t write(int fd, const void *buf, size_t count) //export write -func libc_write(fd int, buf *byte, count uint) int +func libc_write(fd int32, buf *byte, count uint) int // char *getenv(const char *name); //export getenv @@ -99,12 +95,12 @@ func libc_getenv(name *byte) *byte // ssize_t read(int fd, void *buf, size_t count); //export read -func libc_read(fd int, buf *byte, count uint) int +func libc_read(fd int32, buf *byte, count uint) int // int open(const char *pathname, int flags, mode_t mode); //export open -func libc_open(pathname *byte, flags int, mode uint32) int +func libc_open(pathname *byte, flags int32, mode uint32) int32 // int close(int fd) //export close -func libc_close(fd int) int +func libc_close(fd int32) int32 diff --git a/targets/arduino-mkr1000.json b/targets/arduino-mkr1000.json index a5abfca52..89d7bfa69 100644 --- a/targets/arduino-mkr1000.json +++ b/targets/arduino-mkr1000.json @@ -1,6 +1,7 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["arduino_mkr1000"], + "serial": "usb", "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } diff --git a/targets/arduino-nano33.json b/targets/arduino-nano33.json index 45ca81e84..f1797b797 100644 --- a/targets/arduino-nano33.json +++ b/targets/arduino-nano33.json @@ -2,5 +2,6 @@ "inherits": ["atsamd21g18a"], "build-tags": ["arduino_nano33"], "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", + "serial-port": ["acm:2341:8057", "acm:2341:0057"], "flash-1200-bps-reset": "true" } diff --git a/targets/arduino-zero.json b/targets/arduino-zero.json index 50483512e..045bb8c05 100644 --- a/targets/arduino-zero.json +++ b/targets/arduino-zero.json @@ -1,6 +1,7 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["arduino_zero"], + "serial": "usb", "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } diff --git a/targets/arduino.json b/targets/arduino.json index ebe8af51f..91dc49b89 100644 --- a/targets/arduino.json +++ b/targets/arduino.json @@ -6,5 +6,6 @@ "-Wl,--defsym=_stack_size=512" ], "flash-command": "avrdude -c arduino -p atmega328p -P {port} -U flash:w:{hex}:i", + "serial-port": ["acm:2341:0043", "acm:2341:0001", "acm:2a03:0043", "acm:2341:0243"], "emulator": ["simavr", "-m", "atmega328p", "-f", "16000000"] } diff --git a/targets/atmega1280.json b/targets/atmega1280.json index 21324a36d..2b0bfde7b 100644 --- a/targets/atmega1280.json +++ b/targets/atmega1280.json @@ -2,6 +2,7 @@ "inherits": ["avr"], "cpu": "atmega1280", "build-tags": ["atmega1280", "atmega"], + "serial": "uart", "cflags": [ "-mmcu=atmega1280" ], diff --git a/targets/atmega1284p.json b/targets/atmega1284p.json index 2c2d29e18..3fbecd4b7 100644 --- a/targets/atmega1284p.json +++ b/targets/atmega1284p.json @@ -2,6 +2,7 @@ "inherits": ["avr"], "cpu": "atmega1284p", "build-tags": ["atmega1284p", "atmega"], + "serial": "uart", "cflags": [ "-mmcu=atmega1284p" ], diff --git a/targets/atmega2560.json b/targets/atmega2560.json index f460c9c89..9caa088cf 100644 --- a/targets/atmega2560.json +++ b/targets/atmega2560.json @@ -2,6 +2,7 @@ "inherits": ["avr"], "cpu": "atmega2560", "build-tags": ["atmega2560", "atmega"], + "serial": "uart", "cflags": [ "-mmcu=atmega2560" ], diff --git a/targets/atmega328p.json b/targets/atmega328p.json index 7a10eceba..24a272147 100644 --- a/targets/atmega328p.json +++ b/targets/atmega328p.json @@ -2,6 +2,7 @@ "inherits": ["avr"], "cpu": "atmega328p", "build-tags": ["atmega328p", "atmega", "avr5"], + "serial": "uart", "cflags": [ "-mmcu=atmega328p" ], diff --git a/targets/atsamd21e18a.json b/targets/atsamd21e18a.json index 0fcf50675..92b671c9f 100644 --- a/targets/atsamd21e18a.json +++ b/targets/atsamd21e18a.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m0plus"], "build-tags": ["atsamd21e18a", "atsamd21e18", "atsamd21", "sam"], + "serial": "usb", "linkerscript": "targets/atsamd21.ld", "extra-files": [ "src/device/sam/atsamd21e18a.s" diff --git a/targets/atsamd21g18a.json b/targets/atsamd21g18a.json index 956afba54..db46c9628 100644 --- a/targets/atsamd21g18a.json +++ b/targets/atsamd21g18a.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m0plus"], "build-tags": ["atsamd21g18a", "atsamd21g18", "atsamd21", "sam"], + "serial": "usb", "linkerscript": "targets/atsamd21.ld", "extra-files": [ "src/device/sam/atsamd21g18a.s" diff --git a/targets/atsame54-xpro.json b/targets/atsame54-xpro.json index 631df8cfb..1ab7c4eea 100644 --- a/targets/atsame54-xpro.json +++ b/targets/atsame54-xpro.json @@ -1,6 +1,7 @@ { "inherits": ["atsame54p20a"], "build-tags": ["atsame54_xpro"], + "serial": "usb", "flash-method": "openocd", "openocd-interface": "cmsis-dap", "default-stack-size": 4096 diff --git a/targets/bluepill.json b/targets/bluepill.json index 9f1e4b004..752261f4d 100644 --- a/targets/bluepill.json +++ b/targets/bluepill.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m3"], "build-tags": ["bluepill", "stm32f103", "stm32f1", "stm32"], + "serial": "uart", "linkerscript": "targets/stm32.ld", "extra-files": [ "src/device/stm32/stm32f103.s" diff --git a/targets/circuitplay-bluefruit.json b/targets/circuitplay-bluefruit.json index 6a57e1dac..0c1f6ae3b 100644 --- a/targets/circuitplay-bluefruit.json +++ b/targets/circuitplay-bluefruit.json @@ -1,8 +1,10 @@ { "inherits": ["nrf52840"], "build-tags": ["circuitplay_bluefruit","nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8045", "acm:239a:45"], "msd-volume-name": "CPLAYBTBOOT", "msd-firmware-name": "firmware.uf2", "uf2-family-id": "0xADA52840", diff --git a/targets/circuitplay-express.json b/targets/circuitplay-express.json index a2b4143c8..e70dfb93a 100644 --- a/targets/circuitplay-express.json +++ b/targets/circuitplay-express.json @@ -3,6 +3,7 @@ "build-tags": ["circuitplay_express"], "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8018", "acm:239a:18"], "msd-volume-name": "CPLAYBOOT", "msd-firmware-name": "firmware.uf2" } diff --git a/targets/clue-alpha.json b/targets/clue-alpha.json index e2594ea7d..26088d531 100644 --- a/targets/clue-alpha.json +++ b/targets/clue-alpha.json @@ -1,6 +1,8 @@ { "inherits": ["nrf52840"], "build-tags": ["clue_alpha","nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", + "serial-port": ["acm:239a:8072", "acm:239a:0072", "acm:239a:0071", "acm:239a:8071"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "CLUEBOOT", diff --git a/targets/cortex-m.json b/targets/cortex-m.json index eed685c78..0021d03cb 100644 --- a/targets/cortex-m.json +++ b/targets/cortex-m.json @@ -8,7 +8,7 @@ "rtlib": "compiler-rt", "libc": "picolibc", "automatic-stack-size": true, - "default-stack-size": 1024, + "default-stack-size": 2048, "cflags": [ "-Oz", "-mthumb", diff --git a/targets/esp32.json b/targets/esp32.json index 3559e8920..ded861624 100644 --- a/targets/esp32.json +++ b/targets/esp32.json @@ -3,6 +3,7 @@ "cpu": "esp32", "build-tags": ["esp32", "esp"], "scheduler": "tasks", + "serial": "uart", "linker": "xtensa-esp32-elf-ld", "default-stack-size": 2048, "cflags": [ diff --git a/targets/feather-m0.json b/targets/feather-m0.json index 9262833b3..f8070fb10 100644 --- a/targets/feather-m0.json +++ b/targets/feather-m0.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["feather_m0"], + "serial": "usb", + "serial-port": ["acm:239a:801b", "acm:239a:001b", "acm:239a:800b", "acm:239a:000b", "acm:239a:0015"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "FEATHERBOOT", diff --git a/targets/feather-m4-can.json b/targets/feather-m4-can.json index 190d4d8f9..1c5b67e86 100644 --- a/targets/feather-m4-can.json +++ b/targets/feather-m4-can.json @@ -1,6 +1,8 @@ { "inherits": ["atsame51j19a"], "build-tags": ["feather_m4_can"], + "serial": "usb", + "serial-port": ["acm:239a:80cd", "acm:239a:00cd"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "FTHRCANBOOT", diff --git a/targets/feather-m4.json b/targets/feather-m4.json index ccf06ec54..4c33ddd8f 100644 --- a/targets/feather-m4.json +++ b/targets/feather-m4.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd51j19a"], "build-tags": ["feather_m4"], + "serial": "usb", + "serial-port": ["acm:239a:8022", "acm:239a:0022"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "FEATHERBOOT", diff --git a/targets/feather-nrf52840-sense.json b/targets/feather-nrf52840-sense.json new file mode 100644 index 000000000..b39c82105 --- /dev/null +++ b/targets/feather-nrf52840-sense.json @@ -0,0 +1,12 @@ +{ + "inherits": ["nrf52840"], + "build-tags": ["feather_nrf52840_sense","nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", + "serial-port": ["acm:239a:8087", "acm:239a:0087", "acm:239a:0088", "acm:239a:8088"], + "flash-1200-bps-reset": "true", + "flash-method": "msd", + "msd-volume-name": "FTHRSNSBOOT", + "msd-firmware-name": "firmware.uf2", + "uf2-family-id": "0xADA52840", + "linkerscript": "targets/circuitplay-bluefruit.ld" +} diff --git a/targets/feather-nrf52840.json b/targets/feather-nrf52840.json index f0b706186..a4c491956 100644 --- a/targets/feather-nrf52840.json +++ b/targets/feather-nrf52840.json @@ -1,6 +1,8 @@ { "inherits": ["nrf52840"], "build-tags": ["feather_nrf52840","nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", + "serial-port": ["acm:239a:8029", "acm:239a:0029", "acm:239a:002a", "acm:239a:802a"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "FTHR840BOOT", diff --git a/targets/feather-rp2040-boot-stage2.S b/targets/feather-rp2040-boot-stage2.S new file mode 100644 index 000000000..e6e694a4c --- /dev/null +++ b/targets/feather-rp2040-boot-stage2.S @@ -0,0 +1,17 @@ +// Adafruit Feather RP2040 Stage 2 Bootloader + +// +// This file defines the parameters specific to the flash-chip found +// on the Adafruit Feather RP2040. The generic implementation is in +// rp2040-boot-stage2.S +// + +#define BOARD_PICO_FLASH_SPI_CLKDIV 2 +#define BOARD_CMD_READ 0xe7 +#define BOARD_QUAD_OK 1 +#define BOARD_QUAD_ENABLE_STATUS_BYTE 2 +#define BOARD_QUAD_ENABLE_BIT_MASK 2 +#define BOARD_SPLIT_STATUS_WRITE 1 +#define BOARD_WAIT_CYCLES 2 + +#include "rp2040-boot-stage2.S" \ No newline at end of file diff --git a/targets/feather-rp2040.json b/targets/feather-rp2040.json new file mode 100644 index 000000000..955c2a887 --- /dev/null +++ b/targets/feather-rp2040.json @@ -0,0 +1,11 @@ +{ + "inherits": [ + "rp2040" + ], + "serial": "uart", + "build-tags": ["feather_rp2040"], + "linkerscript": "targets/feather-rp2040.ld", + "extra-files": [ + "targets/feather-rp2040-boot-stage2.S" + ] +} diff --git a/targets/feather-rp2040.ld b/targets/feather-rp2040.ld new file mode 100644 index 000000000..d97942f5c --- /dev/null +++ b/targets/feather-rp2040.ld @@ -0,0 +1,10 @@ + +MEMORY +{ + /* Reserve exactly 256 bytes at start of flash for second stage bootloader */ + BOOT2_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = 256 + FLASH_TEXT (rx) : ORIGIN = 0x10000000 + 256, LENGTH = 8192K - 256 + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256k +} + +INCLUDE "targets/rp2040.ld" \ No newline at end of file diff --git a/targets/feather-stm32f405.json b/targets/feather-stm32f405.json index 0e86cd4c5..3d824d1ed 100644 --- a/targets/feather-stm32f405.json +++ b/targets/feather-stm32f405.json @@ -1,8 +1,8 @@ { "inherits": ["cortex-m4"], "build-tags": ["feather_stm32f405", "stm32f405", "stm32f4", "stm32"], + "serial": "uart", "automatic-stack-size": false, - "default-stack-size": 1024, "linkerscript": "targets/stm32f405.ld", "extra-files": [ "src/device/stm32/stm32f405.s" diff --git a/targets/grandcentral-m4.json b/targets/grandcentral-m4.json index 4545dfd9f..392b55502 100644 --- a/targets/grandcentral-m4.json +++ b/targets/grandcentral-m4.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd51p20a"], "build-tags": ["grandcentral_m4"], + "serial": "usb", + "serial-port": ["acm:239a:8031", "acm:239a:0031", "acm:239a:0032"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "GCM4BOOT", diff --git a/targets/hifive1-qemu.json b/targets/hifive1-qemu.json index 9a50cee64..60fe27a4d 100644 --- a/targets/hifive1-qemu.json +++ b/targets/hifive1-qemu.json @@ -1,6 +1,7 @@ { "inherits": ["fe310"], "build-tags": ["hifive1b", "qemu"], + "serial": "uart", "linkerscript": "targets/hifive1-qemu.ld", "emulator": ["qemu-system-riscv32", "-machine", "sifive_e", "-nographic", "-kernel"] } diff --git a/targets/hifive1b.json b/targets/hifive1b.json index bd561ae0b..1084531b3 100644 --- a/targets/hifive1b.json +++ b/targets/hifive1b.json @@ -1,6 +1,7 @@ { "inherits": ["fe310"], "build-tags": ["hifive1b"], + "serial": "uart", "linkerscript": "targets/hifive1b.ld", "flash-method": "msd", "msd-volume-name": "HiFive", diff --git a/targets/itsybitsy-m0.json b/targets/itsybitsy-m0.json index db9dd2cf9..0482dc6cb 100644 --- a/targets/itsybitsy-m0.json +++ b/targets/itsybitsy-m0.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["itsybitsy_m0"], + "serial": "usb", + "serial-port": ["acm:239a:800f", "acm:239a:000f", "acm:239a:8012"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "ITSYBOOT", diff --git a/targets/itsybitsy-m4.json b/targets/itsybitsy-m4.json index 2d6a88eda..af4027a4a 100644 --- a/targets/itsybitsy-m4.json +++ b/targets/itsybitsy-m4.json @@ -1,8 +1,10 @@ { "inherits": ["atsamd51g19a"], "build-tags": ["itsybitsy_m4"], + "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:802b", "acm:239a:002b"], "msd-volume-name": "ITSYM4BOOT", "msd-firmware-name": "firmware.uf2" } diff --git a/targets/itsybitsy-nrf52840.json b/targets/itsybitsy-nrf52840.json index b8637fd00..d657b8a96 100644 --- a/targets/itsybitsy-nrf52840.json +++ b/targets/itsybitsy-nrf52840.json @@ -1,6 +1,8 @@ { "inherits": ["nrf52840"], "build-tags": ["itsybitsy_nrf52840","nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", + "serial-port": ["acm:239A:8052", "acm:239A:0052", "acm:239A:0051", "acm:239A:8051"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "ITSY840BOOT", diff --git a/targets/lgt92.json b/targets/lgt92.json index 82c603a9b..23bc84dd2 100644 --- a/targets/lgt92.json +++ b/targets/lgt92.json @@ -5,6 +5,7 @@ "build-tags": [ "lgt92" ], + "serial": "uart", "linkerscript": "targets/stm32l072czt6.ld", "flash-method": "openocd", "openocd-interface": "stlink-v2", diff --git a/targets/maixbit.json b/targets/maixbit.json index 21322072a..8f6216162 100644 --- a/targets/maixbit.json +++ b/targets/maixbit.json @@ -1,6 +1,7 @@ { "inherits": ["k210"], "build-tags": ["maixbit"], + "serial": "uart", "linkerscript": "targets/maixbit.ld", "flash-command": "kflash -p {port} --noansi --verbose {bin}" } diff --git a/targets/matrixportal-m4.json b/targets/matrixportal-m4.json index cca54a7e5..e9125f0e4 100644 --- a/targets/matrixportal-m4.json +++ b/targets/matrixportal-m4.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd51j19a"], "build-tags": ["atsamd51j19a", "matrixportal_m4"], + "serial": "usb", + "serial-port": ["acm:239a:80c9", "acm:239a:00c9", "acm:239a:80ca"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "MATRIXBOOT", diff --git a/targets/metro-m4-airlift.json b/targets/metro-m4-airlift.json index a282ac55a..33ffd61cf 100644 --- a/targets/metro-m4-airlift.json +++ b/targets/metro-m4-airlift.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd51j19a"], "build-tags": ["metro_m4_airlift"], + "serial": "usb", + "serial-port": ["acm:239A:8037", "acm:239A:0037"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "METROM4BOOT", diff --git a/targets/microbit-v2.json b/targets/microbit-v2.json index 33a9392eb..c8c7461c3 100644 --- a/targets/microbit-v2.json +++ b/targets/microbit-v2.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52833"], "build-tags": ["microbit_v2"], + "serial": "uart", "flash-method": "msd", "openocd-interface": "cmsis-dap", "msd-volume-name": "MICROBIT", diff --git a/targets/microbit.json b/targets/microbit.json index 1b677c5d3..a890100da 100644 --- a/targets/microbit.json +++ b/targets/microbit.json @@ -1,6 +1,7 @@ { "inherits": ["nrf51"], "build-tags": ["microbit"], + "serial": "uart", "flash-method": "msd", "openocd-interface": "cmsis-dap", "msd-volume-name": "MICROBIT", diff --git a/targets/nano-33-ble-sense.json b/targets/nano-33-ble-sense.json new file mode 100644 index 000000000..943829d2d --- /dev/null +++ b/targets/nano-33-ble-sense.json @@ -0,0 +1,3 @@ +{ + "inherits": ["nano-33-ble"] +} diff --git a/targets/nano-33-ble.json b/targets/nano-33-ble.json new file mode 100644 index 000000000..bbf80da52 --- /dev/null +++ b/targets/nano-33-ble.json @@ -0,0 +1,9 @@ +{ + "inherits": ["nrf52840"], + "build-tags": ["nano_33_ble", "nrf52840_reset_bossa"], + "flash-command": "bossac_arduino2 -d -i -e -w -v -R --port={port} {bin}", + "serial-port": ["acm:2341:805a", "acm:2341:005a"], + "serial": "usb", + "flash-1200-bps-reset": "true", + "linkerscript": "targets/nano-33-ble.ld" +} diff --git a/targets/nano-33-ble.ld b/targets/nano-33-ble.ld new file mode 100644 index 000000000..cc533ea90 --- /dev/null +++ b/targets/nano-33-ble.ld @@ -0,0 +1,14 @@ + +/* + See also + https://github.com/arduino/ArduinoCore-mbed/blob/master/variants/ARDUINO_NANO33BLE/linker_script.ld +*/ +MEMORY +{ + FLASH_TEXT (rw) : ORIGIN = 0x10000, LENGTH = 0xf0000 + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 0x40000 +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" diff --git a/targets/nano-rp2040.json b/targets/nano-rp2040.json new file mode 100644 index 000000000..461e0746b --- /dev/null +++ b/targets/nano-rp2040.json @@ -0,0 +1,11 @@ +{ + "inherits": [ + "rp2040" + ], + "serial": "uart", + "build-tags": ["nano_rp2040"], + "linkerscript": "targets/pico.ld", + "extra-files": [ + "targets/pico-boot-stage2.S" + ] +} diff --git a/targets/nicenano.json b/targets/nicenano.json index 3a406ea5c..b18a0c1ca 100644 --- a/targets/nicenano.json +++ b/targets/nicenano.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52840"], "build-tags": ["nicenano","nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "NICENANO", diff --git a/targets/nodemcu.json b/targets/nodemcu.json index c1fdbec06..c8c6c4039 100644 --- a/targets/nodemcu.json +++ b/targets/nodemcu.json @@ -1,4 +1,5 @@ { "inherits": ["esp8266"], - "build-tags": ["nodemcu"] + "build-tags": ["nodemcu"], + "serial": "uart" } diff --git a/targets/nrf52840-mdk-usb-dongle.json b/targets/nrf52840-mdk-usb-dongle.json index fa4c72256..34e76b779 100644 --- a/targets/nrf52840-mdk-usb-dongle.json +++ b/targets/nrf52840-mdk-usb-dongle.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52840"], "build-tags": ["nrf52840_mdk_usb_dongle", "nrf52840_reset_uf2", "softdevice", "s140v6"], + "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "MDK-DONGLE", diff --git a/targets/nrf52840-mdk.json b/targets/nrf52840-mdk.json index 244b3d96d..03528e501 100644 --- a/targets/nrf52840-mdk.json +++ b/targets/nrf52840-mdk.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52840"], "build-tags": ["nrf52840_mdk"], + "serial": "usb", "flash-method": "openocd", "openocd-interface": "cmsis-dap" } diff --git a/targets/nucleo-f103rb.json b/targets/nucleo-f103rb.json index 1c12af847..723c5eef9 100644 --- a/targets/nucleo-f103rb.json +++ b/targets/nucleo-f103rb.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m3"], "build-tags": ["nucleof103rb", "stm32f103", "stm32f1","stm32"], + "serial": "uart", "linkerscript": "targets/stm32f103rb.ld", "extra-files": [ "src/device/stm32/stm32f103.s" diff --git a/targets/nucleo-f722ze.json b/targets/nucleo-f722ze.json index 25a388b9e..f426b332d 100644 --- a/targets/nucleo-f722ze.json +++ b/targets/nucleo-f722ze.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m7"], "build-tags": ["nucleof722ze", "stm32f7x2", "stm32f7", "stm32"], + "serial": "uart", "linkerscript": "targets/stm32f7x2zetx.ld", "extra-files": [ "src/device/stm32/stm32f7x2.s" diff --git a/targets/nucleo-l031k6.json b/targets/nucleo-l031k6.json index 928dd43dc..eed8f38f0 100644 --- a/targets/nucleo-l031k6.json +++ b/targets/nucleo-l031k6.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m0"], "build-tags": ["nucleol031k6", "stm32l031", "stm32l0x1", "stm32l0", "stm32"], + "serial": "uart", "linkerscript": "targets/stm32l031k6.ld", "extra-files": [ "src/device/stm32/stm32l0x1.s" diff --git a/targets/nucleo-l432kc.json b/targets/nucleo-l432kc.json index f13c8bde4..028b034b0 100644 --- a/targets/nucleo-l432kc.json +++ b/targets/nucleo-l432kc.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m4"], "build-tags": ["nucleol432kc", "stm32l432", "stm32l4x2", "stm32l4", "stm32"], + "serial": "uart", "linkerscript": "targets/stm32l4x2.ld", "extra-files": [ "src/device/stm32/stm32l4x2.s" diff --git a/targets/nucleo-l552ze.json b/targets/nucleo-l552ze.json index 56e286ab5..044b58371 100644 --- a/targets/nucleo-l552ze.json +++ b/targets/nucleo-l552ze.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m33"], "build-tags": ["nucleol552ze", "stm32l552", "stm32l5x2", "stm32l5", "stm32"], + "serial": "uart", "linkerscript": "targets/stm32l5x2xe.ld", "extra-files": [ "src/device/stm32/stm32l552.s" diff --git a/targets/particle-3rd-gen.json b/targets/particle-3rd-gen.json index 2229319e7..ef39a7340 100644 --- a/targets/particle-3rd-gen.json +++ b/targets/particle-3rd-gen.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52840"], "build-tags": ["particle_3rd_gen"], + "serial": "uart", "flash-method": "openocd", "openocd-interface": "cmsis-dap" } diff --git a/targets/pca10031.json b/targets/pca10031.json index cccae828a..8af365cc8 100644 --- a/targets/pca10031.json +++ b/targets/pca10031.json @@ -1,6 +1,7 @@ { "inherits": ["nrf51"], "build-tags": ["pca10031"], + "serial": "uart", "flash-command": "nrfjprog -f nrf51 --sectorerase --program {hex} --reset", "openocd-interface": "cmsis-dap" } diff --git a/targets/pca10040.json b/targets/pca10040.json index 7685be427..b751b3bd6 100644 --- a/targets/pca10040.json +++ b/targets/pca10040.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52"], "build-tags": ["pca10040"], + "serial": "uart", "flash-method": "openocd", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "openocd-interface": "jlink", diff --git a/targets/pca10056.json b/targets/pca10056.json index fc0acaa0a..b604624cf 100644 --- a/targets/pca10056.json +++ b/targets/pca10056.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52840"], "build-tags": ["pca10056"], + "serial": "uart", "flash-method": "command", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "msd-volume-name": "JLINK", diff --git a/targets/pca10059.json b/targets/pca10059.json index efab1b6e3..b5286e989 100644 --- a/targets/pca10059.json +++ b/targets/pca10059.json @@ -1,6 +1,8 @@ { "inherits": ["nrf52840"], "build-tags": ["pca10059"], + "serial": "usb", "linkerscript": "targets/pca10059.ld", - "flash-command": "nrfutil pkg generate --hw-version 52 --sd-req 0x0 --application {hex} --application-version 1 /tmp/tinygo_$$.zip && nrfutil dfu usb-serial -pkg /tmp/tinygo_$$.zip -p {port} -b 115200 && rm -f /tmp/tinygo_$$.zip" + "binary-format": "nrf-dfu", + "flash-command": "nrfutil dfu usb-serial -pkg {zip} -p {port} -b 115200" } diff --git a/targets/pico-boot-stage2.S b/targets/pico-boot-stage2.S new file mode 100644 index 000000000..88dd55176 --- /dev/null +++ b/targets/pico-boot-stage2.S @@ -0,0 +1,17 @@ +// Raspberry Pi Pico Stage 2 Bootloader + +// +// This file defines the parameters specific to the flash-chip found +// on the official Pico boards. The generic implementation is in +// rp2040-boot-stage2.S +// + +#define BOARD_PICO_FLASH_SPI_CLKDIV 2 +#define BOARD_CMD_READ 0xeb +#define BOARD_QUAD_OK 1 +#define BOARD_QUAD_ENABLE_STATUS_BYTE 2 +#define BOARD_QUAD_ENABLE_BIT_MASK 2 +#define BOARD_SPLIT_STATUS_WRITE 0 +#define BOARD_WAIT_CYCLES 4 + +#include "rp2040-boot-stage2.S" \ No newline at end of file diff --git a/targets/pico.json b/targets/pico.json index e8e5a06b1..36e3ba30e 100644 --- a/targets/pico.json +++ b/targets/pico.json @@ -3,8 +3,9 @@ "rp2040" ], "build-tags": ["pico"], + "serial": "uart", "linkerscript": "targets/pico.ld", "extra-files": [ - "targets/pico_boot_stage2.S" + "targets/pico-boot-stage2.S" ] } diff --git a/targets/pico.ld b/targets/pico.ld index 267fbc4c9..6ef32a3cb 100644 --- a/targets/pico.ld +++ b/targets/pico.ld @@ -1,31 +1,10 @@ MEMORY { - FLASH_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = 2048k -} - -SECTIONS -{ - /* Second stage bootloader is prepended to the image. It must be 256 bytes big - and checksummed. It is usually built by the boot_stage2 target - in the Raspberry Pi Pico SDK - */ - - .boot2 : { - __boot2_start__ = .; - KEEP (*(.boot2)) - __boot2_end__ = .; - } > FLASH_TEXT - - ASSERT(__boot2_end__ - __boot2_start__ == 256, - "ERROR: Pico second stage bootloader must be 256 bytes in size") - - /* The second stage will always enter the image at the start of .text. - The debugger will use the ELF entry point, which is the _entry_point - symbol if present, otherwise defaults to start of .text. - This can be used to transfer control back to the bootrom on debugger - launches only, to perform proper flash setup. - */ + /* Reserve exactly 256 bytes at start of flash for second stage bootloader */ + BOOT2_TEXT (rx) : ORIGIN = 0x10000000, LENGTH = 256 + FLASH_TEXT (rx) : ORIGIN = 0x10000000 + 256, LENGTH = 2048K - 256 + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256k } INCLUDE "targets/rp2040.ld" diff --git a/targets/pico_boot_stage2.S b/targets/pico_boot_stage2.S deleted file mode 100644 index 4902d3bbb..000000000 --- a/targets/pico_boot_stage2.S +++ /dev/null @@ -1,23 +0,0 @@ -// Padded and checksummed version of: /home/rkanchan/src/pico-sdk/build/src/rp2_common/boot_stage2/bs2_default.bin - -.cpu cortex-m0plus -.thumb - -.section .boot2, "ax" - -.byte 0x00, 0xb5, 0x32, 0x4b, 0x21, 0x20, 0x58, 0x60, 0x98, 0x68, 0x02, 0x21, 0x88, 0x43, 0x98, 0x60 -.byte 0xd8, 0x60, 0x18, 0x61, 0x58, 0x61, 0x2e, 0x4b, 0x00, 0x21, 0x99, 0x60, 0x02, 0x21, 0x59, 0x61 -.byte 0x01, 0x21, 0xf0, 0x22, 0x99, 0x50, 0x2b, 0x49, 0x19, 0x60, 0x01, 0x21, 0x99, 0x60, 0x35, 0x20 -.byte 0x00, 0xf0, 0x44, 0xf8, 0x02, 0x22, 0x90, 0x42, 0x14, 0xd0, 0x06, 0x21, 0x19, 0x66, 0x00, 0xf0 -.byte 0x34, 0xf8, 0x19, 0x6e, 0x01, 0x21, 0x19, 0x66, 0x00, 0x20, 0x18, 0x66, 0x1a, 0x66, 0x00, 0xf0 -.byte 0x2c, 0xf8, 0x19, 0x6e, 0x19, 0x6e, 0x19, 0x6e, 0x05, 0x20, 0x00, 0xf0, 0x2f, 0xf8, 0x01, 0x21 -.byte 0x08, 0x42, 0xf9, 0xd1, 0x00, 0x21, 0x99, 0x60, 0x1b, 0x49, 0x19, 0x60, 0x00, 0x21, 0x59, 0x60 -.byte 0x1a, 0x49, 0x1b, 0x48, 0x01, 0x60, 0x01, 0x21, 0x99, 0x60, 0xeb, 0x21, 0x19, 0x66, 0xa0, 0x21 -.byte 0x19, 0x66, 0x00, 0xf0, 0x12, 0xf8, 0x00, 0x21, 0x99, 0x60, 0x16, 0x49, 0x14, 0x48, 0x01, 0x60 -.byte 0x01, 0x21, 0x99, 0x60, 0x01, 0xbc, 0x00, 0x28, 0x00, 0xd0, 0x00, 0x47, 0x12, 0x48, 0x13, 0x49 -.byte 0x08, 0x60, 0x03, 0xc8, 0x80, 0xf3, 0x08, 0x88, 0x08, 0x47, 0x03, 0xb5, 0x99, 0x6a, 0x04, 0x20 -.byte 0x01, 0x42, 0xfb, 0xd0, 0x01, 0x20, 0x01, 0x42, 0xf8, 0xd1, 0x03, 0xbd, 0x02, 0xb5, 0x18, 0x66 -.byte 0x18, 0x66, 0xff, 0xf7, 0xf2, 0xff, 0x18, 0x6e, 0x18, 0x6e, 0x02, 0xbd, 0x00, 0x00, 0x02, 0x40 -.byte 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x07, 0x00, 0x00, 0x03, 0x5f, 0x00, 0x21, 0x22, 0x00, 0x00 -.byte 0xf4, 0x00, 0x00, 0x18, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0 -.byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0xb2, 0x4e, 0x7a diff --git a/targets/pinetime-devkit0.json b/targets/pinetime-devkit0.json index adbe874ae..8655769ee 100644 --- a/targets/pinetime-devkit0.json +++ b/targets/pinetime-devkit0.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52"], "build-tags": ["pinetime_devkit0"], + "serial": "uart", "flash-method": "openocd", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "openocd-interface": "jlink", diff --git a/targets/pybadge.json b/targets/pybadge.json index 0514f5372..15af8f8a9 100644 --- a/targets/pybadge.json +++ b/targets/pybadge.json @@ -1,8 +1,10 @@ { "inherits": ["atsamd51j19a"], "build-tags": ["pybadge"], + "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8033", "acm:239a:33"], "msd-volume-name": "PYBADGEBOOT", "msd-firmware-name": "arcade.uf2" } diff --git a/targets/pygamer.json b/targets/pygamer.json index 63dc34a18..4f4ab6e33 100644 --- a/targets/pygamer.json +++ b/targets/pygamer.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd51j19a"], "build-tags": ["pygamer"], + "serial": "usb", + "serial-port": ["acm:239a:803d", "acm:239a:003d", "acm:239a:803e"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "PYGAMERBOOT", diff --git a/targets/pyportal.json b/targets/pyportal.json index e03efcb6d..abda3ab18 100644 --- a/targets/pyportal.json +++ b/targets/pyportal.json @@ -1,8 +1,10 @@ { "inherits": ["atsamd51j20a"], "build-tags": ["pyportal"], + "serial": "usb", "flash-1200-bps-reset": "true", "flash-method": "msd", + "serial-port": ["acm:239a:8035", "acm:239a:35", "acm:239a:8036"], "msd-volume-name": "PORTALBOOT", "msd-firmware-name": "firmware.uf2" } diff --git a/targets/qtpy.json b/targets/qtpy.json index 98821876a..53f1221b1 100644 --- a/targets/qtpy.json +++ b/targets/qtpy.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd21e18a"], "build-tags": ["qtpy"], + "serial": "usb", + "serial-port": ["acm:239a:80cb", "acm:239a:00cb", "acm:239a:00cc"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "QTPY_BOOT", diff --git a/targets/reelboard.json b/targets/reelboard.json index 60aeb1cfd..b6da0a449 100644 --- a/targets/reelboard.json +++ b/targets/reelboard.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52840"], "build-tags": ["reelboard"], + "serial": "uart", "flash-method": "msd", "msd-volume-name": "reel-board", "msd-firmware-name": "firmware.hex", diff --git a/targets/rp2040-boot-stage2.S b/targets/rp2040-boot-stage2.S new file mode 100644 index 000000000..8a16b6448 --- /dev/null +++ b/targets/rp2040-boot-stage2.S @@ -0,0 +1,471 @@ +// +// Implementation of RP2040 stage 2 boot loader. This code is derived from the +// Winbond W25Q080 implementation (as found in the Pico) in the official Pico SDK. +// +// This implementation has been made 'stand-alone' by including necessary code / +// symbols from the included files in the reference implementation directly into +// the source. It has also been modified to include the conditional logic from +// the CircuitPython implementation that supports additional flash chips. The +// CiruitPython source is here: +// https://github.com/adafruit/circuitpython/blob/main/ports/raspberrypi/stage2.c.jinja +// +// This file cannot be assembled directly, instead assemble the board-specific file +// (such as pico-boot-stage2.S) which defines the parameters specific to the flash +// chip included on that board. +// +// Care has been taken to preserve ordering and it has been verified the generated +// binary is byte-for-byte identical to the reference code binary when assembled for +// the Pico. +// +// Note: the stage 2 boot loader must be 256 bytes in length and have a checksum +// present. In TinyGo, the linker script is responsible for allocating 256 bytes +// for the .boot2 section and the build logic patches the checksum into the +// binary after linking, controlled by the '.json' flag 'rp2040-boot-patch'. +// +// The stage 2 bootstrap section can be inspected in an elf file using this command: +// objdump -s -j .boot2 .elf +// +// Original Source: +// https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/boot_stage2/boot2_w25q080.S +// + + +// ---------------------------------------------------------------------------- +// Second stage boot code +// Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd. +// SPDX-License-Identifier: BSD-3-Clause +// +// Device: Winbond W25Q080 +// Also supports W25Q16JV (which has some different SR instructions) +// Also supports AT25SF081 +// Also supports S25FL132K0 +// +// Description: Configures W25Q080 to run in Quad I/O continuous read XIP mode +// +// Details: * Check status register 2 to determine if QSPI mode is enabled, +// and perform an SR2 programming cycle if necessary. +// * Use SSI to perform a dummy 0xEB read command, with the mode +// continuation bits set, so that the flash will not require +// 0xEB instruction prefix on subsequent reads. +// * Configure SSI to write address, mode bits, but no instruction. +// SSI + flash are now jointly in a state where continuous reads +// can take place. +// * Jump to exit pointer passed in via lr. Bootrom passes null, +// in which case this code uses a default 256 byte flash offset +// +// Building: * This code must be position-independent, and use stack only +// * The code will be padded to a size of 256 bytes, including a +// 4-byte checksum. Therefore code size cannot exceed 252 bytes. +// ---------------------------------------------------------------------------- + + +// +// Expanded include files +// +#define CMD_WRITE_ENABLE 0x06 +#define CMD_READ_STATUS 0x05 +#define CMD_READ_STATUS2 0x35 +#define CMD_WRITE_STATUS1 0x01 +#define CMD_WRITE_STATUS2 0x31 +#define SREG_DATA 0x02 // Enable quad-SPI mode + +#define XIP_BASE 0x10000000 +#define XIP_SSI_BASE 0x18000000 +#define PADS_QSPI_BASE 0x40020000 +#define PPB_BASE 0xe0000000 + +#define M0PLUS_VTOR_OFFSET 0x0000ed08 + +#define PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB 4 +#define PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS 0x00000001 +#define PADS_QSPI_GPIO_QSPI_SCLK_OFFSET 0x00000004 +#define PADS_QSPI_GPIO_QSPI_SD0_OFFSET 0x00000008 +#define PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS 0x00000002 +#define PADS_QSPI_GPIO_QSPI_SD1_OFFSET 0x0000000c +#define PADS_QSPI_GPIO_QSPI_SD2_OFFSET 0x00000010 +#define PADS_QSPI_GPIO_QSPI_SD3_OFFSET 0x00000014 + +#define SSI_CTRLR0_OFFSET 0x00000000 +#define SSI_CTRLR1_OFFSET 0x00000004 +#define SSI_SSIENR_OFFSET 0x00000008 +#define SSI_BAUDR_OFFSET 0x00000014 +#define SSI_SR_OFFSET 0x00000028 +#define SSI_DR0_OFFSET 0x00000060 +#define SSI_RX_SAMPLE_DLY_OFFSET 0x000000f0 + +#define SSI_CTRLR0_DFS_32_LSB 16 + +#define SSI_CTRLR0_SPI_FRF_VALUE_QUAD 0x2 +#define SSI_CTRLR0_SPI_FRF_LSB 21 + +#define SSI_CTRLR0_TMOD_VALUE_TX_AND_RX 0x0 +#define SSI_CTRLR0_TMOD_VALUE_EEPROM_READ 0x3 +#define SSI_CTRLR0_TMOD_LSB 8 + +#define SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A 0x1 +#define SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A 0x2 + +#define SSI_SPI_CTRLR0_OFFSET 0x000000f4 + +#define SSI_SPI_CTRLR0_INST_L_VALUE_NONE 0x0 +#define SSI_SPI_CTRLR0_INST_L_VALUE_8B 0x2 + +#define SSI_SPI_CTRLR0_TRANS_TYPE_LSB 0 +#define SSI_SPI_CTRLR0_ADDR_L_LSB 2 +#define SSI_SPI_CTRLR0_INST_L_LSB 8 +#define SSI_SPI_CTRLR0_WAIT_CYCLES_LSB 11 +#define SSI_SPI_CTRLR0_XIP_CMD_LSB 24 + +#define SSI_SR_BUSY_BITS 0x00000001 +#define SSI_SR_TFE_BITS 0x00000004 + + +// ---------------------------------------------------------------------------- +// Config section +// ---------------------------------------------------------------------------- +// It should be possible to support most flash devices by modifying this section + +// The serial flash interface will run at clk_sys/PICO_FLASH_SPI_CLKDIV. +// This must be a positive, even integer. +// The bootrom is very conservative with SPI frequency, but here we should be +// as aggressive as possible. + +#define PICO_FLASH_SPI_CLKDIV BOARD_PICO_FLASH_SPI_CLKDIV +#if PICO_FLASH_SPI_CLKDIV & 1 +#error PICO_FLASH_SPI_CLKDIV must be even +#endif + +#if BOARD_QUAD_OK==1 +// Define interface width: single/dual/quad IO +#define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_QUAD +#define TRANSACTION_TYPE SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_2C2A +// Note that the INST_L field is used to select what XIP data gets pushed into +// the TX FIFO: +// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD +// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD +#define INSTRUCTION_LENGTH SSI_SPI_CTRLR0_INST_L_VALUE_NONE +#define READ_INSTRUCTION MODE_CONTINUOUS_READ +#define ADDR_L 8 // 6 for address, 2 for mode +#else +#define FRAME_FORMAT SSI_CTRLR0_SPI_FRF_VALUE_STD +#define TRANSACTION_TYPE SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C1A +#define INSTRUCTION_LENGTH SSI_SPI_CTRLR0_INST_L_VALUE_8B +#define READ_INSTRUCTION BOARD_CMD_READ +#define ADDR_L 6 // * 4 = 24 +#endif + +// The flash-chip specific read isntruction +#define CMD_READ BOARD_CMD_READ + +// "Mode bits" are 8 special bits sent immediately after +// the address bits in a "Read Data Fast Quad I/O" command sequence. +// On W25Q080, the four LSBs are don't care, and if MSBs == 0xa, the +// next read does not require the 0xeb instruction prefix. +#define MODE_CONTINUOUS_READ 0xa0 + +// How many clocks of Hi-Z following the mode bits. For W25Q080, 4 dummy cycles +// are required. +#define WAIT_CYCLES BOARD_WAIT_CYCLES + + +// If defined, we will read status reg, compare to SREG_DATA, and overwrite +// with our value if the SR doesn't match. +// We do a two-byte write to SR1 (01h cmd) rather than a one-byte write to +// SR2 (31h cmd) as the latter command isn't supported by WX25Q080. +// This isn't great because it will remove block protections. +// A better solution is to use a volatile SR write if your device supports it. +#define PROGRAM_STATUS_REG + +.syntax unified +.cpu cortex-m0plus +.thumb +.section .boot2, "ax" + +// The exit point is passed in lr. If entered from bootrom, this will be the +// flash address immediately following this second stage (0x10000100). +// Otherwise it will be a return address -- second stage being called as a +// function by user code, after copying out of XIP region. r3 holds SSI base, +// r0...2 used as temporaries. Other GPRs not used. +.global _stage2_boot +.type _stage2_boot,%function +.thumb_func +_stage2_boot: + push {lr} + + // Set pad configuration: + // - SCLK 8mA drive, no slew limiting + // - SDx disable input Schmitt to reduce delay + + ldr r3, =PADS_QSPI_BASE + movs r0, #(2 << PADS_QSPI_GPIO_QSPI_SCLK_DRIVE_LSB | PADS_QSPI_GPIO_QSPI_SCLK_SLEWFAST_BITS) + str r0, [r3, #PADS_QSPI_GPIO_QSPI_SCLK_OFFSET] + ldr r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET] + movs r1, #PADS_QSPI_GPIO_QSPI_SD0_SCHMITT_BITS + bics r0, r1 +#if BOARD_QUAD_OK==1 + str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD0_OFFSET] +#endif + str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD1_OFFSET] +#if BOARD_QUAD_OK==1 + str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD2_OFFSET] + str r0, [r3, #PADS_QSPI_GPIO_QSPI_SD3_OFFSET] +#endif + + ldr r3, =XIP_SSI_BASE + + // Disable SSI to allow further config + movs r1, #0 + str r1, [r3, #SSI_SSIENR_OFFSET] + + // Set baud rate + movs r1, #PICO_FLASH_SPI_CLKDIV + str r1, [r3, #SSI_BAUDR_OFFSET] + + // Set 1-cycle sample delay. If PICO_FLASH_SPI_CLKDIV == 2 then this means, + // if the flash launches data on SCLK posedge, we capture it at the time that + // the next SCLK posedge is launched. This is shortly before that posedge + // arrives at the flash, so data hold time should be ok. For + // PICO_FLASH_SPI_CLKDIV > 2 this pretty much has no effect. + + movs r1, #1 + movs r2, #SSI_RX_SAMPLE_DLY_OFFSET // == 0xf0 so need 8 bits of offset significance + str r1, [r3, r2] + +// On QSPI parts we usually need a 01h SR-write command to enable QSPI mode +// (i.e. turn WPn and HOLDn into IO2/IO3) +#ifdef PROGRAM_STATUS_REG +program_sregs: +#define CTRL0_SPI_TXRX \ + (7 << SSI_CTRLR0_DFS_32_LSB) | /* 8 bits per data frame */ \ + (SSI_CTRLR0_TMOD_VALUE_TX_AND_RX << SSI_CTRLR0_TMOD_LSB) + + ldr r1, =(CTRL0_SPI_TXRX) + str r1, [r3, #SSI_CTRLR0_OFFSET] + + // Enable SSI and select slave 0 + movs r1, #1 + str r1, [r3, #SSI_SSIENR_OFFSET] + + // Check whether SR needs updating +#if BOARD_QUAD_OK==1 +# if BOARD_QUAD_ENABLE_STATUS_BYTE==1 + movs r0, #CMD_READ_STATUS1 +# elif BOARD_QUAD_ENABLE_STATUS_BYTE==2 + movs r0, #CMD_READ_STATUS2 +# endif + + bl read_flash_sreg + movs r2, #BOARD_QUAD_ENABLE_BIT_MASK + cmp r0, r2 + beq skip_sreg_programming + + // Send write enable command + movs r1, #CMD_WRITE_ENABLE + str r1, [r3, #SSI_DR0_OFFSET] + + // Poll for completion and discard RX + bl wait_ssi_ready + ldr r1, [r3, #SSI_DR0_OFFSET] + + // Send status write command followed by data bytes +# if BOARD_SPLIT_STATUS_WRITE==1 +# if BOARD_QUAD_ENABLE_STATUS_BYTE==1 + movs r1, #CMD_WRITE_STATUS1 +# elif BOARD_QUAD_ENABLE_STATUS_BYTE==2 + movs r1, #CMD_WRITE_STATUS2 +# endif + str r1, [r3, #SSI_DR0_OFFSET] + str r2, [r3, #SSI_DR0_OFFSET] + + bl wait_ssi_ready + //ldr r1, [r3, #SSI_DR0_OFFSET] + ldr r1, [r3, #SSI_DR0_OFFSET] + ldr r1, [r3, #SSI_DR0_OFFSET] + +# else + movs r1, #CMD_WRITE_STATUS1 + str r1, [r3, #SSI_DR0_OFFSET] +# if BOARD_QUAD_ENABLE_STATUS_BYTE==2 + movs r0, #0 + str r0, [r3, #SSI_DR0_OFFSET] +# endif + str r2, [r3, #SSI_DR0_OFFSET] + + bl wait_ssi_ready + ldr r1, [r3, #SSI_DR0_OFFSET] + ldr r1, [r3, #SSI_DR0_OFFSET] +# if BOARD_QUAD_ENABLE_STATUS_BYTE==2 + ldr r1, [r3, #SSI_DR0_OFFSET] +# endif + +# endif + // Poll status register for write completion +1: + movs r0, #CMD_READ_STATUS + bl read_flash_sreg + movs r1, #1 + tst r0, r1 + bne 1b +#endif + +skip_sreg_programming: + + // Disable SSI again so that it can be reconfigured + movs r1, #0 + str r1, [r3, #SSI_SSIENR_OFFSET] +#endif + +// Currently the flash expects an 8 bit serial command prefix on every +// transfer, which is a waste of cycles. Perform a dummy Fast Read Quad I/O +// command, with mode bits set such that the flash will not expect a serial +// command prefix on *subsequent* transfers. We don't care about the results +// of the read, the important part is the mode bits. + +dummy_read: +#define CTRLR0_ENTER_XIP \ + (FRAME_FORMAT /* Quad I/O mode */ \ + << SSI_CTRLR0_SPI_FRF_LSB) | \ + (31 << SSI_CTRLR0_DFS_32_LSB) | /* 32 data bits */ \ + (SSI_CTRLR0_TMOD_VALUE_EEPROM_READ /* Send INST/ADDR, Receive Data */ \ + << SSI_CTRLR0_TMOD_LSB) + + ldr r1, =(CTRLR0_ENTER_XIP) + str r1, [r3, #SSI_CTRLR0_OFFSET] + + movs r1, #0x0 // NDF=0 (single 32b read) + str r1, [r3, #SSI_CTRLR1_OFFSET] + +#if BOARD_QUAD_OK==1 +#define SPI_CTRLR0_ENTER_XIP \ + (ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Address + mode bits */ \ + (WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \ + (SSI_SPI_CTRLR0_INST_L_VALUE_8B \ + << SSI_SPI_CTRLR0_INST_L_LSB) | /* 8-bit instruction */ \ + (SSI_SPI_CTRLR0_TRANS_TYPE_VALUE_1C2A /* Send Command in serial mode then address in Quad I/O mode */ \ + << SSI_SPI_CTRLR0_TRANS_TYPE_LSB) + + ldr r1, =(SPI_CTRLR0_ENTER_XIP) + ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) // SPI_CTRL0 Register + str r1, [r0] + + movs r1, #1 // Re-enable SSI + str r1, [r3, #SSI_SSIENR_OFFSET] + + movs r1, #CMD_READ + str r1, [r3, #SSI_DR0_OFFSET] // Push SPI command into TX FIFO + movs r1, #MODE_CONTINUOUS_READ // 32-bit: 24 address bits (we don't care, so 0) and M[7:4]=1010 + str r1, [r3, #SSI_DR0_OFFSET] // Push Address into TX FIFO - this will trigger the transaction + + // Poll for completion + bl wait_ssi_ready + +// The flash is in a state where we can blast addresses in parallel, and get +// parallel data back. Now configure the SSI to translate XIP bus accesses +// into QSPI transfers of this form. + + movs r1, #0 + str r1, [r3, #SSI_SSIENR_OFFSET] // Disable SSI (and clear FIFO) to allow further config +#endif + +// Note that the INST_L field is used to select what XIP data gets pushed into +// the TX FIFO: +// INST_L_0_BITS {ADDR[23:0],XIP_CMD[7:0]} Load "mode bits" into XIP_CMD +// Anything else {XIP_CMD[7:0],ADDR[23:0]} Load SPI command into XIP_CMD +configure_ssi: +#define SPI_CTRLR0_XIP \ + (READ_INSTRUCTION /* Mode bits to keep flash in continuous read mode */ \ + << SSI_SPI_CTRLR0_XIP_CMD_LSB) | \ + (ADDR_L << SSI_SPI_CTRLR0_ADDR_L_LSB) | /* Total number of address + mode bits */ \ + (WAIT_CYCLES << SSI_SPI_CTRLR0_WAIT_CYCLES_LSB) | /* Hi-Z dummy clocks following address + mode */ \ + (INSTRUCTION_LENGTH /* Do not send a command, instead send XIP_CMD as mode bits after address */ \ + << SSI_SPI_CTRLR0_INST_L_LSB) | \ + (TRANSACTION_TYPE /* Send Address in Quad I/O mode (and Command but that is zero bits long) */ \ + << SSI_SPI_CTRLR0_TRANS_TYPE_LSB) + + ldr r1, =(SPI_CTRLR0_XIP) + + ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0_OFFSET) + str r1, [r0] + + movs r1, #1 + str r1, [r3, #SSI_SSIENR_OFFSET] // Re-enable SSI + +// Bus accesses to the XIP window will now be transparently serviced by the +// external flash on cache miss. We are ready to run code from flash. + + +// +// Helper Includes +// + +// +// #include "boot2_helpers/exit_from_boot2.S" +// + +// If entered from the bootrom, lr (which we earlier pushed) will be 0, +// and we vector through the table at the start of the main flash image. +// Any regular function call will have a nonzero value for lr. +check_return: + pop {r0} + cmp r0, #0 + beq vector_into_flash + bx r0 +vector_into_flash: + ldr r0, =(XIP_BASE + 0x100) + ldr r1, =(PPB_BASE + M0PLUS_VTOR_OFFSET) + str r0, [r1] + ldmia r0, {r0, r1} + msr msp, r0 + bx r1 + +// +// #include "boot2_helpers/wait_ssi_ready.S" +// +wait_ssi_ready: + push {r0, r1, lr} + + // Command is complete when there is nothing left to send + // (TX FIFO empty) and SSI is no longer busy (CSn deasserted) +1: + ldr r1, [r3, #SSI_SR_OFFSET] + movs r0, #SSI_SR_TFE_BITS + tst r1, r0 + beq 1b + movs r0, #SSI_SR_BUSY_BITS + tst r1, r0 + bne 1b + + pop {r0, r1, pc} + + +#ifdef PROGRAM_STATUS_REG + +// +// #include "boot2_helpers/read_flash_sreg.S" +// + +// Pass status read cmd into r0. +// Returns status value in r0. +.global read_flash_sreg +.type read_flash_sreg,%function +.thumb_func +read_flash_sreg: + push {r1, lr} + str r0, [r3, #SSI_DR0_OFFSET] + // Dummy byte: + str r0, [r3, #SSI_DR0_OFFSET] + + bl wait_ssi_ready + // Discard first byte and combine the next two + ldr r0, [r3, #SSI_DR0_OFFSET] + ldr r0, [r3, #SSI_DR0_OFFSET] + + pop {r1, pc} + +#endif + +.global literals +literals: +.ltorg + +.end diff --git a/targets/rp2040.json b/targets/rp2040.json index ce49ba356..c679758e6 100644 --- a/targets/rp2040.json +++ b/targets/rp2040.json @@ -6,6 +6,7 @@ "msd-firmware-name": "firmware.uf2", "binary-format": "uf2", "uf2-family-id": "0xe48bff56", + "rp2040-boot-patch": true, "extra-files": [ "src/device/rp/rp2040.s" ] diff --git a/targets/rp2040.ld b/targets/rp2040.ld index 186db68f6..5aa57ce69 100644 --- a/targets/rp2040.ld +++ b/targets/rp2040.ld @@ -1,9 +1,29 @@ -MEMORY -{ - RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 256k -} - _stack_size = 2K; +SECTIONS +{ + /* Second stage bootloader is prepended to the image. It must be 256 bytes + and checksummed. The gap to the checksum is zero-padded. + */ + .boot2 : { + __boot2_start__ = .; + KEEP (*(.boot2)); + + /* Explicitly allocate space for CRC32 checksum at end of second stage + bootloader + */ + . = __boot2_start__ + 256 - 4; + LONG(0) + } > BOOT2_TEXT = 0x0 + + /* The second stage will always enter the image at the start of .text. + The debugger will use the ELF entry point, which is the _entry_point + symbol if present, otherwise defaults to start of .text. + This can be used to transfer control back to the bootrom on debugger + launches only, to perform proper flash setup. + */ +} + + INCLUDE "targets/arm.ld" diff --git a/targets/stm32f4disco.json b/targets/stm32f4disco.json index 622c5b0d6..4485c41d3 100644 --- a/targets/stm32f4disco.json +++ b/targets/stm32f4disco.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m4"], "build-tags": ["stm32f4disco", "stm32f407", "stm32f4", "stm32"], + "serial": "uart", "linkerscript": "targets/stm32f407.ld", "extra-files": [ "src/device/stm32/stm32f407.s" diff --git a/targets/teensy36.json b/targets/teensy36.json index c341511ec..253e39704 100644 --- a/targets/teensy36.json +++ b/targets/teensy36.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m4"], "build-tags": ["teensy36", "teensy", "mk66f18", "nxp"], + "serial": "uart", "linkerscript": "targets/nxpmk66f18.ld", "extra-files": [ "src/device/nxp/mk66f18.s", diff --git a/targets/teensy40.json b/targets/teensy40.json index d08b8d1dd..c530003b3 100644 --- a/targets/teensy40.json +++ b/targets/teensy40.json @@ -1,6 +1,7 @@ { "inherits": ["cortex-m7"], "build-tags": ["teensy40", "teensy", "mimxrt1062", "nxp"], + "serial": "uart", "automatic-stack-size": false, "default-stack-size": 4096, "linkerscript": "targets/mimxrt1062-teensy40.ld", diff --git a/targets/trinket-m0.json b/targets/trinket-m0.json index af0e450ef..61bfc0a19 100644 --- a/targets/trinket-m0.json +++ b/targets/trinket-m0.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd21e18a"], "build-tags": ["trinket_m0"], + "serial": "usb", + "serial-port": ["acm:239a:801e", "acm:239a:001e"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "TRINKETBOOT", diff --git a/targets/wasi.json b/targets/wasi.json index c24ed8f10..b80b77d1a 100644 --- a/targets/wasi.json +++ b/targets/wasi.json @@ -1,6 +1,6 @@ { "llvm-target": "wasm32--wasi", - "build-tags": ["wasm", "wasi"], + "build-tags": ["tinygo.wasm", "wasi"], "goos": "linux", "goarch": "arm", "linker": "wasm-ld", diff --git a/targets/wasm.json b/targets/wasm.json index 6208eb27f..eb03eb8c8 100644 --- a/targets/wasm.json +++ b/targets/wasm.json @@ -1,6 +1,6 @@ { "llvm-target": "wasm32--wasi", - "build-tags": ["js", "wasm"], + "build-tags": ["tinygo.wasm"], "goos": "js", "goarch": "wasm", "linker": "wasm-ld", diff --git a/targets/wioterminal.json b/targets/wioterminal.json index 093947e16..5564d0761 100644 --- a/targets/wioterminal.json +++ b/targets/wioterminal.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd51p19a"], "build-tags": ["wioterminal"], + "serial": "usb", + "serial-port": ["acm:2886:002d", "acm:2886:802d"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "Arduino", diff --git a/targets/x9pro.json b/targets/x9pro.json index a9ecc0c83..9b966b4b9 100644 --- a/targets/x9pro.json +++ b/targets/x9pro.json @@ -1,6 +1,7 @@ { "inherits": ["nrf52"], "build-tags": ["x9pro"], + "serial": "uart", "flash-method": "openocd", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "openocd-interface": "jlink", diff --git a/targets/xiao.json b/targets/xiao.json index 50dee8f2a..80cc99d38 100644 --- a/targets/xiao.json +++ b/targets/xiao.json @@ -1,6 +1,8 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["xiao"], + "serial": "usb", + "serial-port": ["acm:2886:802f", "acm:2886:002f"], "flash-1200-bps-reset": "true", "flash-method": "msd", "msd-volume-name": "Arduino", diff --git a/testdata/env.go b/testdata/env.go index 115da6d3d..9c5bb5a89 100644 --- a/testdata/env.go +++ b/testdata/env.go @@ -1,6 +1,7 @@ package main import ( + "crypto/rand" "os" ) @@ -20,4 +21,26 @@ func main() { for _, arg := range os.Args[1:] { println("arg:", arg) } + + // Check for crypto/rand support. + checkRand() +} + +func checkRand() { + buf := make([]byte, 500) + n, err := rand.Read(buf) + if n != len(buf) || err != nil { + println("could not read random numbers:", err) + } + + // Very simple test that random numbers are at least somewhat random. + sum := 0 + for _, b := range buf { + sum += int(b) + } + if sum < 95*len(buf) || sum > 159*len(buf) { + println("random numbers don't seem that random, the average byte is", sum/len(buf)) + } else { + println("random number check was successful") + } } diff --git a/testdata/env.txt b/testdata/env.txt index 8ba50a7fd..e392cd5e9 100644 --- a/testdata/env.txt +++ b/testdata/env.txt @@ -3,3 +3,4 @@ ENV2: VALUE2 arg: first arg: second +random number check was successful diff --git a/testdata/init.go b/testdata/init.go index 6e7d9e7ba..5cb7f2d28 100644 --- a/testdata/init.go +++ b/testdata/init.go @@ -13,6 +13,8 @@ func main() { println("v5:", len(v5), v5 == nil) println("v6:", v6) println("v7:", cap(v7), string(v7)) + println("v8:", v8) + println("v9:", len(v9), v9[0], v9[1], v9[2]) println(uint8SliceSrc[0]) println(uint8SliceDst[0]) @@ -35,6 +37,8 @@ var ( v5 = map[string]int{} v6 = float64(v1) < 2.6 v7 = []byte("foo") + v8 string + v9 []int uint8SliceSrc = []uint8{3, 100} uint8SliceDst []uint8 @@ -48,4 +52,15 @@ func init() { intSliceDst = make([]int16, len(intSliceSrc)) copy(intSliceDst, intSliceSrc) + + v8 = sliceString("foobarbaz", 3, 8) + v9 = sliceSlice([]int{0, 1, 2, 3, 4, 5}, 2, 5) +} + +func sliceString(s string, start, end int) string { + return s[start:end] +} + +func sliceSlice(s []int, start, end int) []int { + return s[start:end] } diff --git a/testdata/init.txt b/testdata/init.txt index c421c1151..a6b9736c5 100644 --- a/testdata/init.txt +++ b/testdata/init.txt @@ -7,6 +7,8 @@ v4: 0 true v5: 0 false v6: false v7: 3 foo +v8: barba +v9: 3 2 3 4 3 3 5 diff --git a/testdata/stdlib.go b/testdata/stdlib.go index e6d6677b6..55237ff68 100644 --- a/testdata/stdlib.go +++ b/testdata/stdlib.go @@ -5,6 +5,7 @@ import ( "math/rand" "os" "strings" + "syscall" ) func main() { @@ -13,6 +14,14 @@ func main() { fmt.Println("stdout:", os.Stdout.Name()) fmt.Println("stderr:", os.Stderr.Name()) + // Package syscall, this mostly checks whether the calls don't trigger an error. + syscall.Getuid() + syscall.Geteuid() + syscall.Getgid() + syscall.Getegid() + syscall.Getpid() + syscall.Getppid() + // package math/rand fmt.Println("pseudorandom number:", rand.Int31()) diff --git a/transform/globals.go b/transform/globals.go index 2d0349e9f..d147062bf 100644 --- a/transform/globals.go +++ b/transform/globals.go @@ -11,7 +11,7 @@ import "tinygo.org/x/go-llvm" func ApplyFunctionSections(mod llvm.Module) { llvmFn := mod.FirstFunction() for !llvmFn.IsNil() { - if !llvmFn.IsDeclaration() { + if !llvmFn.IsDeclaration() && llvmFn.Section() == "" { name := llvmFn.Name() llvmFn.SetSection(".text." + name) } diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index 79b3361af..57c8c76d2 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -54,10 +54,14 @@ type signatureInfo struct { // methodName takes a method name like "func String()" and returns only the // name, which is "String" in this case. func (s *signatureInfo) methodName() string { - if !strings.HasPrefix(s.name, "func ") { - panic("signature must start with \"func \"") + var methodName string + if strings.HasPrefix(s.name, "reflect/methods.") { + methodName = s.name[len("reflect/methods."):] + } else if idx := strings.LastIndex(s.name, ".$methods."); idx >= 0 { + methodName = s.name[idx+len(".$methods."):] + } else { + panic("could not find method name") } - methodName := s.name[len("func "):] if openingParen := strings.IndexByte(methodName, '('); openingParen < 0 { panic("no opening paren in signature name") } else { diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 0ced0bfea..c10b3c285 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -105,7 +105,7 @@ func OptimizeStringEqual(mod llvm.Module) { // As of this writing, the (reflect.Type).Interface method has not yet been // implemented so this optimization is critical for the encoding/json package. func OptimizeReflectImplements(mod llvm.Module) { - implementsSignature := mod.NamedGlobal("func Implements(reflect.Type) bool") + implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") if implementsSignature.IsNil() { return } diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll index c67595a2e..2101725a3 100644 --- a/transform/testdata/interface.ll +++ b/transform/testdata/interface.ll @@ -8,11 +8,11 @@ target triple = "armv7m-none-eabi" @"reflect/types.typeid:basic:uint8" = external constant i8 @"reflect/types.typeid:basic:int16" = external constant i8 @"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer -@"func NeverImplementedMethod()" = external constant i8 -@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"] -@"func Double() int" = external constant i8 -@"Doubler$interface" = private constant [1 x i8*] [i8* @"func Double() int"] -@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"func Double() int", i32 ptrtoint (i32 (i8*, i8*)* @"(Number).Double$invoke" to i32) }] +@"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0 +@"Unmatched$interface" = private constant [1 x i8*] [i8* @"reflect/methods.NeverImplementedMethod()"] +@"reflect/methods.Double() int" = linkonce_odr constant i8 0 +@"Doubler$interface" = private constant [1 x i8*] [i8* @"reflect/methods.Double() int"] +@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"reflect/methods.Double() int", i32 ptrtoint (i32 (i8*, i8*)* @"(Number).Double$invoke" to i32) }] @"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0) } declare i1 @runtime.interfaceImplements(i32, i8**) @@ -48,7 +48,7 @@ typeswitch.notUnmatched: br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler typeswitch.Doubler: - %doubler.func = call i32 @runtime.interfaceMethod(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0), i8* nonnull @"func Double() int") + %doubler.func = call i32 @runtime.interfaceMethod(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Double() int") %doubler.func.cast = inttoptr i32 %doubler.func to i32 (i8*, i8*)* %doubler.result = call i32 %doubler.func.cast(i8* %value, i8* null) call void @runtime.printint32(i32 %doubler.result) diff --git a/transform/testdata/reflect-implements.ll b/transform/testdata/reflect-implements.ll index 22c76485d..29f382432 100644 --- a/transform/testdata/reflect-implements.ll +++ b/transform/testdata/reflect-implements.ll @@ -6,11 +6,11 @@ target triple = "i686--linux" @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null } -@"func Error() string" = external constant i8 -@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"] -@"func Align() int" = external constant i8 -@"func Implements(reflect.Type) bool" = external constant i8 -@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"func Align() int", i8* @"func Implements(reflect.Type) bool"] +@"reflect/methods.Error() string" = linkonce_odr constant i8 0 +@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] +@"reflect/methods.Align() int" = linkonce_odr constant i8 0 +@"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0 +@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"] @"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0) } @"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer @"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer @@ -28,7 +28,7 @@ declare i32 @runtime.interfaceMethod(i32, i8**, i8*, i8*, i8*) ; known at compile time (after the interp pass has run). define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context, i8* %parentHandle) { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"func Implements(reflect.Type) bool", i8* undef, i8* null) + %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) %invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)* %result = call i1 %invoke.func.cast(i8* %typ.value, i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:reflect.rawType" to i32), i8* bitcast (%runtime.typecodeID* @"reflect/types.type:named:error" to i8*), i8* undef, i8* undef) ret i1 %result @@ -41,7 +41,7 @@ entry: ; } define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"func Implements(reflect.Type) bool", i8* undef, i8* null) + %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) %invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)* %result = call i1 %invoke.func.cast(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* undef, i8* undef) ret i1 %result diff --git a/transform/testdata/reflect-implements.out.ll b/transform/testdata/reflect-implements.out.ll index 03d977699..f1bc92c11 100644 --- a/transform/testdata/reflect-implements.out.ll +++ b/transform/testdata/reflect-implements.out.ll @@ -6,11 +6,11 @@ target triple = "i686--linux" @"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null } @"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null } -@"func Error() string" = external constant i8 -@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"func Error() string"] -@"func Align() int" = external constant i8 -@"func Implements(reflect.Type) bool" = external constant i8 -@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"func Align() int", i8* @"func Implements(reflect.Type) bool"] +@"reflect/methods.Error() string" = linkonce_odr constant i8 0 +@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] +@"reflect/methods.Align() int" = linkonce_odr constant i8 0 +@"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0 +@"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"] @"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0) } @"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer @"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer @@ -28,7 +28,7 @@ entry: define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"func Implements(reflect.Type) bool", i8* undef, i8* null) + %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) %invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)* %result = call i1 %invoke.func.cast(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* undef, i8* undef) ret i1 %result