diff --git a/.circleci/config.yml b/.circleci/config.yml index 4988610277..060d665028 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -37,6 +37,13 @@ commands: sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node rm node-v10.15.1-linux-x64.tar.xz + install-chrome: + steps: + - run: + name: "Install Chrome" + command: | + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + sudo apt install ./google-chrome-stable_current_amd64.deb llvm-source-linux: steps: - restore_cache: @@ -71,6 +78,7 @@ commands: - apt-dependencies: llvm: "<>" - install-node + - install-chrome - restore_cache: keys: - go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} @@ -88,6 +96,7 @@ commands: - run: go test -v -tags=llvm<> ./cgo ./compileopts ./interp ./transform . - run: make gen-device -j4 - run: make smoketest + - run: make wasmtest - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: @@ -204,13 +213,21 @@ commands: - run: name: "Test TinyGo" command: make test + - run: + name: "Install fpm" + command: | + sudo apt-get install ruby ruby-dev + sudo gem install --no-document fpm - run: name: "Build TinyGo release" command: | - make release -j3 + make release deb -j3 cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz + cp -p build/release.deb /tmp/tinygo_amd64.deb - store_artifacts: path: /tmp/tinygo.linux-amd64.tar.gz + - store_artifacts: + path: /tmp/tinygo_amd64.deb - save_cache: key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} paths: @@ -294,11 +311,6 @@ commands: tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo tinygo version - - run: - name: "Download SiFive GNU toolchain" - command: | - curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz - sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz - run: make smoketest AVR=0 - save_cache: key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} diff --git a/Dockerfile b/Dockerfile index 6d958125eb..60eff30f70 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,10 +27,7 @@ COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo COPY --from=tinygo-base /tinygo/src /tinygo/src COPY --from=tinygo-base /tinygo/targets /tinygo/targets -RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ - echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \ - apt-get update && \ - apt-get install -y libllvm10 lld-10 +RUN apt-get install -y libllvm10 lld-10 # tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers. FROM tinygo-base AS tinygo-avr diff --git a/Makefile b/Makefile index bacd9b5d37..d02079a6fa 100644 --- a/Makefile +++ b/Makefile @@ -182,7 +182,7 @@ tinygo-test: .PHONY: smoketest smoketest: $(TINYGO) version - # test all examples + # test all examples (except pwm) $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc @@ -192,7 +192,7 @@ smoketest: $(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/button - @$(MD5SUM) test.hex + @$(MD5SUM) test. $(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo @@ -203,7 +203,7 @@ smoketest: @$(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/pwm + $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial @$(MD5SUM) test.hex @@ -231,6 +231,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit examples/echo @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1 @@ -291,11 +293,17 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1 + @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pygamer examples/blinky1 + @$(MD5SUM) test.hex ifneq ($(AVR), 0) $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=arduino examples/pwm + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1 @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 @@ -310,7 +318,10 @@ endif $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main -release: tinygo gen-device wasi-libc +wasmtest: + $(GO) test ./tests/wasm + +build/release: tinygo gen-device wasi-libc @mkdir -p build/release/tinygo/bin @mkdir -p build/release/tinygo/lib/clang/include @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @@ -345,4 +356,13 @@ release: tinygo gen-device wasi-libc ./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc ./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc ./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc + +release: build/release tar -czf build/release.tar.gz -C build/release tinygo + +deb: build/release + @mkdir -p build/release-deb/usr/local/bin + @mkdir -p build/release-deb/usr/local/lib + cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo + ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo + fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb diff --git a/README.md b/README.md index 6424a42dc3..06002839ee 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 32 microcontroller boards are currently supported: +The following 33 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) @@ -72,6 +72,7 @@ The following 32 microcontroller boards are currently supported: * [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/) * [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/) * [PineTime DevKit](https://www.pine64.org/pinetime/) +* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html) * [SiFIve HiFive1](https://www.sifive.com/boards/hifive1) * [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html) * [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 40a222a16c..346ce4fdae 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -69,7 +69,7 @@ jobs: script: | export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu" unset GOROOT - make release -j4 + make build/release -j4 - publish: $(System.DefaultWorkingDirectory)/build/release/tinygo displayName: Publish zip as artifact artifact: tinygo diff --git a/builder/build.go b/builder/build.go index e67cb2bff9..f8247f8b50 100644 --- a/builder/build.go +++ b/builder/build.go @@ -32,7 +32,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri if err != nil { return err } - mod, extraFiles, errs := compiler.Compile(pkgName, machine, config) + mod, extraFiles, extraLDFlags, errs := compiler.Compile(pkgName, machine, config) if errs != nil { return newMultiError(errs) } @@ -187,6 +187,10 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri ldflags = append(ldflags, outpath) } + if len(extraLDFlags) > 0 { + ldflags = append(ldflags, extraLDFlags...) + } + // Link the object files together. err = link(config.Target.Linker, ldflags...) if err != nil { diff --git a/builder/config.go b/builder/config.go index aabf82ac5a..3a83f7f754 100644 --- a/builder/config.go +++ b/builder/config.go @@ -21,7 +21,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) { if goroot == "" { return nil, errors.New("cannot locate $GOROOT, please set it manually") } - major, minor, err := getGorootVersion(goroot) + major, minor, err := goenv.GetGorootVersion(goroot) if err != nil { return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err) } diff --git a/builder/env.go b/builder/env.go index 995ad2adcf..c59f5df44d 100644 --- a/builder/env.go +++ b/builder/env.go @@ -1,71 +1,13 @@ package builder import ( - "errors" - "fmt" - "io" "io/ioutil" "os" "os/exec" "path/filepath" - "regexp" "sort" - "strings" ) -// getGorootVersion returns the major and minor version for a given GOROOT path. -// If the goroot cannot be determined, (0, 0) is returned. -func getGorootVersion(goroot string) (major, minor int, err error) { - s, err := GorootVersionString(goroot) - if err != nil { - return 0, 0, err - } - - if s == "" || s[:2] != "go" { - return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") - } - - parts := strings.Split(s[2:], ".") - if len(parts) < 2 { - return 0, 0, errors.New("could not parse Go version: version has less than two parts") - } - - // Ignore the errors, we don't really handle errors here anyway. - var trailing string - n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing) - if n == 2 && err == io.EOF { - // Means there were no trailing characters (i.e., not an alpha/beta) - err = nil - } - if err != nil { - return 0, 0, fmt.Errorf("failed to parse version: %s", err) - } - return -} - -// GorootVersionString returns the version string as reported by the Go -// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but -// can have some variations (for beta releases, for example). -func GorootVersionString(goroot string) (string, error) { - if data, err := ioutil.ReadFile(filepath.Join( - goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil { - - r := regexp.MustCompile("const TheVersion = `(.*)`") - matches := r.FindSubmatch(data) - if len(matches) != 2 { - return "", errors.New("Invalid go version output:\n" + string(data)) - } - - return string(matches[1]), nil - - } else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { - return string(data), nil - - } else { - return "", err - } -} - // getClangHeaderPath returns the path to the built-in Clang headers. It tries // multiple locations, which should make it find the directory when installed in // various ways. diff --git a/builder/library.go b/builder/library.go index d85e4d365e..7bb506c765 100644 --- a/builder/library.go +++ b/builder/library.go @@ -69,7 +69,7 @@ func (l *Library) Load(target string) (path string, err error) { // Precalculate the flags to the compiler invocation. args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir) if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") { - args = append(args, "-fshort-enums", "-fomit-frame-pointer") + args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft") } if strings.HasPrefix(target, "riscv32-") { args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128") diff --git a/builder/objcopy.go b/builder/objcopy.go index 3ab0eaeff9..e5b245c675 100644 --- a/builder/objcopy.go +++ b/builder/objcopy.go @@ -123,8 +123,7 @@ func objcopy(infile, outfile string) error { if err != nil { return objcopyError{"failed to create .hex file", err} } - mem.DumpIntelHex(f, 16) // TODO: handle error - return nil + return mem.DumpIntelHex(f, 16) default: panic("unreachable") } diff --git a/builder/picolibc.go b/builder/picolibc.go index d3a095d636..436c34815a 100644 --- a/builder/picolibc.go +++ b/builder/picolibc.go @@ -12,7 +12,7 @@ var Picolibc = Library{ name: "picolibc", cflags: func() []string { picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc") - return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "--sysroot=" + picolibcDir, "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} + return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"} }, sourceDir: "lib/picolibc/newlib/libc", sources: func(target string) []string { diff --git a/cgo/cgo.go b/cgo/cgo.go index d6470a5229..7cc61b9ce2 100644 --- a/cgo/cgo.go +++ b/cgo/cgo.go @@ -41,6 +41,7 @@ type cgoPackage struct { elaboratedTypes map[string]*elaboratedTypeInfo enums map[string]enumInfo anonStructNum int + ldflags []string } // constantInfo stores some information about a CGo constant found by libclang @@ -156,7 +157,7 @@ typedef unsigned long long _Cgo_ulonglong; // newly created *ast.File that should be added to the list of to-be-parsed // files. If there is one or more error, it returns these in the []error slice // but still modifies the AST. -func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) { +func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) { p := &cgoPackage{ dir: dir, fset: fset, @@ -183,7 +184,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string // Find the absolute path for this package. packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name()) if err != nil { - return nil, []error{ + return nil, nil, []error{ scanner.Error{ Pos: fset.Position(files[0].Pos()), Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error @@ -359,6 +360,19 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string } makePathsAbsolute(flags, packagePath) cflags = append(cflags, flags...) + case "LDFLAGS": + flags, err := shlex.Split(value) + if err != nil { + // TODO: find the exact location where the error happened. + p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error()) + continue + } + if err := checkLinkerFlags(name, flags); err != nil { + p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error()) + continue + } + makePathsAbsolute(flags, packagePath) + p.ldflags = append(p.ldflags, flags...) default: startPos := strings.LastIndex(line[4:colon], name) + 4 p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name) @@ -412,7 +426,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string // Print the newly generated in-memory AST, for debugging. //ast.Print(fset, p.generated) - return p.generated, p.errors + return p.generated, p.ldflags, p.errors } // makePathsAbsolute converts some common path compiler flags (-I, -L) from diff --git a/cgo/cgo_test.go b/cgo/cgo_test.go index 5673ebad8d..745824154b 100644 --- a/cgo/cgo_test.go +++ b/cgo/cgo_test.go @@ -50,7 +50,7 @@ func TestCGo(t *testing.T) { } // Process the AST with CGo. - cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags) + cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags) // Check the AST for type errors. var typecheckErrors []error diff --git a/cgo/libclang.go b/cgo/libclang.go index 3d7695b5e6..649494f120 100644 --- a/cgo/libclang.go +++ b/cgo/libclang.go @@ -246,9 +246,9 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient } value := source[len(name):] // Try to convert this #define into a Go constant expression. - expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value) - if err != nil { - p.errors = append(p.errors, err) + expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value) + if scannerError != nil { + p.errors = append(p.errors, *scannerError) } if expr != nil { // Parsing was successful. diff --git a/cgo/testdata/flags.go b/cgo/testdata/flags.go index d52bbc5b96..012fad78da 100644 --- a/cgo/testdata/flags.go +++ b/cgo/testdata/flags.go @@ -21,6 +21,13 @@ package main #if defined(NOTDEFINED) #warning flag must not be defined #endif + +// Check Compiler flags +#cgo LDFLAGS: -lc + +// This flag is not valid ldflags +#cgo LDFLAGS: -does-not-exists + */ import "C" diff --git a/cgo/testdata/flags.out.go b/cgo/testdata/flags.out.go index 0bcad0d5a7..4eb70112a1 100644 --- a/cgo/testdata/flags.out.go +++ b/cgo/testdata/flags.out.go @@ -1,6 +1,7 @@ // CGo errors: // testdata/flags.go:5:7: invalid #cgo line: NOFLAGS // testdata/flags.go:8:13: invalid flag: -fdoes-not-exist +// testdata/flags.go:29:14: invalid flag: -does-not-exists package main diff --git a/compileopts/config.go b/compileopts/config.go index 9ec71bf8f7..24a12a9f33 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -129,8 +129,8 @@ func (c *Config) NeedsStackObjects() bool { } } -// Scheduler returns the scheduler implementation. Valid values are "coroutines" -// and "tasks". +// Scheduler returns the scheduler implementation. Valid values are "none", +//"coroutines" and "tasks". func (c *Config) Scheduler() string { if c.Options.Scheduler != "" { return c.Options.Scheduler @@ -173,7 +173,7 @@ func (c *Config) CFlags() []string { } if c.Target.Libc == "picolibc" { root := goenv.Get("TINYGOROOT") - cflags = append(cflags, "--sysroot="+filepath.Join(root, "lib", "picolibc", "newlib", "libc")) + 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")) } return cflags @@ -272,6 +272,15 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) { return args, nil } +// CodeModel returns the code model used on this platform. +func (c *Config) CodeModel() string { + if c.Target.CodeModel != "" { + return c.Target.CodeModel + } + + return "default" +} + type TestConfig struct { CompileTestBinary bool // TODO: Filter the test functions to run, include verbose flag, etc diff --git a/compileopts/options.go b/compileopts/options.go index 7336ee3fc3..ae2c2cecd3 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -1,5 +1,17 @@ package compileopts +import ( + "fmt" + "strings" +) + +var ( + validGCOptions = []string{"none", "leaking", "extalloc", "conservative"} + validSchedulerOptions = []string{"none", "tasks", "coroutines"} + validPrintSizeOptions = []string{"none", "short", "full"} + validPanicStrategyOptions = []string{"print", "trap"} +) + // Options contains extra options to give to the compiler. These options are // usually passed from the command line. type Options struct { @@ -21,3 +33,53 @@ type Options struct { TestConfig TestConfig Programmer string } + +// Verify performs a validation on the given options, raising an error if options are not valid. +func (o *Options) Verify() error { + if o.GC != "" { + valid := isInArray(validGCOptions, o.GC) + if !valid { + return fmt.Errorf(`invalid gc option '%s': valid values are %s`, + o.GC, + strings.Join(validGCOptions, ", ")) + } + } + + if o.Scheduler != "" { + valid := isInArray(validSchedulerOptions, o.Scheduler) + if !valid { + return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`, + o.Scheduler, + strings.Join(validSchedulerOptions, ", ")) + } + } + + if o.PrintSizes != "" { + valid := isInArray(validPrintSizeOptions, o.PrintSizes) + if !valid { + return fmt.Errorf(`invalid size option '%s': valid values are %s`, + o.PrintSizes, + strings.Join(validPrintSizeOptions, ", ")) + } + } + + if o.PanicStrategy != "" { + valid := isInArray(validPanicStrategyOptions, o.PanicStrategy) + if !valid { + return fmt.Errorf(`invalid panic option '%s': valid values are %s`, + o.PanicStrategy, + strings.Join(validPanicStrategyOptions, ", ")) + } + } + + return nil +} + +func isInArray(arr []string, item string) bool { + for _, i := range arr { + if i == item { + return true + } + } + return false +} diff --git a/compileopts/options_test.go b/compileopts/options_test.go new file mode 100644 index 0000000000..1ff532cc4f --- /dev/null +++ b/compileopts/options_test.go @@ -0,0 +1,138 @@ +package compileopts_test + +import ( + "errors" + "testing" + + "github.com/tinygo-org/tinygo/compileopts" +) + +func TestVerifyOptions(t *testing.T) { + + expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`) + expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`) + expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`) + expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`) + + testCases := []struct { + name string + opts compileopts.Options + expectedError error + }{ + { + name: "OptionsEmpty", + opts: compileopts.Options{}, + }, + { + name: "InvalidGCOption", + opts: compileopts.Options{ + GC: "incorrect", + }, + expectedError: expectedGCError, + }, + { + name: "GCOptionNone", + opts: compileopts.Options{ + GC: "none", + }, + }, + { + name: "GCOptionLeaking", + opts: compileopts.Options{ + GC: "leaking", + }, + }, + { + name: "GCOptionExtalloc", + opts: compileopts.Options{ + GC: "extalloc", + }, + }, + { + name: "GCOptionConservative", + opts: compileopts.Options{ + GC: "conservative", + }, + }, + { + name: "InvalidSchedulerOption", + opts: compileopts.Options{ + Scheduler: "incorrect", + }, + expectedError: expectedSchedulerError, + }, + { + name: "SchedulerOptionNone", + opts: compileopts.Options{ + Scheduler: "none", + }, + }, + { + name: "SchedulerOptionTasks", + opts: compileopts.Options{ + Scheduler: "tasks", + }, + }, + { + name: "SchedulerOptionCoroutines", + opts: compileopts.Options{ + Scheduler: "coroutines", + }, + }, + { + name: "InvalidPrintSizeOption", + opts: compileopts.Options{ + PrintSizes: "incorrect", + }, + expectedError: expectedPrintSizeError, + }, + { + name: "PrintSizeOptionNone", + opts: compileopts.Options{ + PrintSizes: "none", + }, + }, + { + name: "PrintSizeOptionShort", + opts: compileopts.Options{ + PrintSizes: "short", + }, + }, + { + name: "PrintSizeOptionFull", + opts: compileopts.Options{ + PrintSizes: "full", + }, + }, + { + name: "InvalidPanicOption", + opts: compileopts.Options{ + PanicStrategy: "incorrect", + }, + expectedError: expectedPanicStrategyError, + }, + { + name: "PanicOptionPrint", + opts: compileopts.Options{ + PanicStrategy: "print", + }, + }, + { + name: "PanicOptionTrap", + opts: compileopts.Options{ + PanicStrategy: "trap", + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.Verify() + if tc.expectedError != err { + if tc.expectedError.Error() != err.Error() { + t.Errorf("expected %v, got %v", tc.expectedError, err) + } + } + }) + } +} diff --git a/compileopts/target.go b/compileopts/target.go index 2071c73a53..5d5e2f1b3d 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -49,6 +49,7 @@ type TargetSpec struct { OpenOCDTarget string `json:"openocd-target"` OpenOCDTransport string `json:"openocd-transport"` JLinkDevice string `json:"jlink-device"` + CodeModel string `json:"code-model"` } // copyProperties copies all properties that are set in spec2 into itself. @@ -130,6 +131,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) { if spec2.JLinkDevice != "" { spec.JLinkDevice = spec2.JLinkDevice } + if spec2.CodeModel != "" { + spec.CodeModel = spec2.CodeModel + } } // load reads a target specification from the JSON in the given io.Reader. It diff --git a/compiler/atomic.go b/compiler/atomic.go new file mode 100644 index 0000000000..99e8385ad1 --- /dev/null +++ b/compiler/atomic.go @@ -0,0 +1,57 @@ +package compiler + +import ( + "golang.org/x/tools/go/ssa" + "tinygo.org/x/go-llvm" +) + +// createAtomicOp lowers an atomic library call by lowering it as an LLVM atomic +// operation. It returns the result of the operation and true if the call could +// be lowered inline, and false otherwise. +func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) { + name := call.Value.(*ssa.Function).Name() + switch name { + case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": + ptr := b.getValue(call.Args[0]) + val := b.getValue(call.Args[1]) + oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + // Return the new value, not the original value returned by atomicrmw. + return b.CreateAdd(oldVal, val, ""), true + case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer": + ptr := b.getValue(call.Args[0]) + val := b.getValue(call.Args[1]) + isPointer := val.Type().TypeKind() == llvm.PointerTypeKind + if isPointer { + // atomicrmw only supports integers, so cast to an integer. + val = b.CreatePtrToInt(val, b.uintptrType, "") + ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "") + } + oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) + if isPointer { + oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "") + } + return oldVal, true + case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer": + ptr := b.getValue(call.Args[0]) + old := b.getValue(call.Args[1]) + newVal := b.getValue(call.Args[2]) + tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true) + swapped := b.CreateExtractValue(tuple, 1, "") + return swapped, true + case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer": + ptr := b.getValue(call.Args[0]) + val := b.CreateLoad(ptr, "") + val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) + val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required + return val, true + case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer": + ptr := b.getValue(call.Args[0]) + val := b.getValue(call.Args[1]) + store := b.CreateStore(val, ptr) + store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) + store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required + return store, true + default: + return llvm.Value{}, false + } +} diff --git a/compiler/channel.go b/compiler/channel.go index 3686c98f37..6e2d4319dd 100644 --- a/compiler/channel.go +++ b/compiler/channel.go @@ -12,7 +12,7 @@ import ( ) func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { - elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem())) + elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem())) elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false) bufSize := b.getValue(expr.Size) b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos()) @@ -35,27 +35,37 @@ func (b *builder) createChanSend(instr *ssa.Send) { valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value") b.CreateStore(chanValue, valueAlloca) + // Allocate blockedlist buffer. + channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList") + channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") + // Do the send. - b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "") + b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "") - // End the lifetime of the alloca. + // End the lifetime of the allocas. // This also works around a bug in CoroSplit, at least in LLVM 8: // https://bugs.llvm.org/show_bug.cgi?id=41742 + b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) } // createChanRecv emits a pseudo chan receive operation. It is lowered to the // actual channel receive operation during goroutine lowering. func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { - valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem()) + valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem()) ch := b.getValue(unop.X) // Allocate memory to receive into. valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value") + // Allocate blockedlist buffer. + channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList") + channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") + // Do the receive. - commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "") + commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "") received := b.CreateLoad(valueAlloca, "chan.received") + b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) if unop.CommaOk { @@ -117,7 +127,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value { switch state.Dir { case types.RecvOnly: // Make sure the receive buffer is big enough and has the correct alignment. - llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem()) + llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem()) if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize { recvbufSize = size } diff --git a/compiler/compiler.go b/compiler/compiler.go index 6ab553b43c..fe45eb3fc6 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -91,7 +91,25 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) { return llvm.TargetMachine{}, err } features := strings.Join(config.Features(), ",") - machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault) + + var codeModel llvm.CodeModel + + switch config.CodeModel() { + case "default": + codeModel = llvm.CodeModelDefault + case "tiny": + codeModel = llvm.CodeModelTiny + case "small": + codeModel = llvm.CodeModelSmall + case "kernel": + codeModel = llvm.CodeModelKernel + case "medium": + codeModel = llvm.CodeModelMedium + case "large": + codeModel = llvm.CodeModelLarge + } + + machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, codeModel) return machine, nil } @@ -103,7 +121,7 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) { // violation. Eventually, this Compile function should only compile a single // package and not the whole program, and loading of the program (including CGo // processing) should be moved outside the compiler package. -func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []string, []error) { +func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (mod llvm.Module, extrafiles []string, extraldflags []string, errors []error) { c := &compilerContext{ Config: config, difiles: make(map[string]llvm.Metadata), @@ -137,64 +155,26 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() dummyFunc.EraseFromParentAsFunction() - // Prefix the GOPATH with the system GOROOT, as GOROOT is already set to - // the TinyGo root. - overlayGopath := goenv.Get("GOPATH") - if overlayGopath == "" { - overlayGopath = goenv.Get("GOROOT") - } else { - overlayGopath = goenv.Get("GOROOT") + string(filepath.ListSeparator) + overlayGopath - } - wd, err := os.Getwd() if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} + } + goroot, err := loader.GetCachedGoroot(c.Config) + if err != nil { + return c.mod, nil, nil, []error{err} } lprogram := &loader.Program{ Build: &build.Context{ GOARCH: c.GOARCH(), GOOS: c.GOOS(), - GOROOT: goenv.Get("GOROOT"), + GOROOT: goroot, GOPATH: goenv.Get("GOPATH"), CgoEnabled: c.CgoEnabled(), UseAllFiles: false, Compiler: "gc", // must be one of the recognized compilers BuildTags: c.BuildTags(), }, - OverlayBuild: &build.Context{ - GOARCH: c.GOARCH(), - GOOS: c.GOOS(), - GOROOT: goenv.Get("TINYGOROOT"), - GOPATH: overlayGopath, - CgoEnabled: c.CgoEnabled(), - UseAllFiles: false, - Compiler: "gc", // must be one of the recognized compilers - BuildTags: c.BuildTags(), - }, - OverlayPath: func(path string) string { - // Return the (overlay) import path when it should be overlaid, and - // "" if it should not. - if strings.HasPrefix(path, tinygoPath+"/src/") { - // Avoid issues with packages that are imported twice, one from - // GOPATH and one from TINYGOPATH. - path = path[len(tinygoPath+"/src/"):] - } - switch path { - case "machine", "os", "reflect", "runtime", "runtime/interrupt", "runtime/volatile", "sync", "testing", "internal/reflectlite", "internal/task": - return path - default: - if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { - return path - } else if path == "syscall" { - for _, tag := range c.BuildTags() { - if tag == "baremetal" || tag == "darwin" { - return path - } - } - } - } - return "" - }, + Tests: c.TestConfig.CompileTestBinary, TypeChecker: types.Config{ Sizes: &stdSizes{ IntSize: int64(c.targetData.TypeAllocSize(c.intType)), @@ -208,38 +188,22 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con ClangHeaders: c.ClangHeaders, } - if strings.HasSuffix(pkgName, ".go") { - _, err = lprogram.ImportFile(pkgName) - if err != nil { - return c.mod, nil, []error{err} - } - } else { - _, err = lprogram.Import(pkgName, wd, token.Position{ - Filename: "build command-line-arguments", - }) - if err != nil { - return c.mod, nil, []error{err} - } - } - - _, err = lprogram.Import("runtime", "", token.Position{ - Filename: "build default import", - }) + err = lprogram.Load(pkgName) if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } - err = lprogram.Parse(c.TestConfig.CompileTestBinary) + err = lprogram.Parse() if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } - c.ir = ir.NewProgram(lprogram, pkgName) + c.ir = ir.NewProgram(lprogram) // Run a simple dead code elimination pass. err = c.ir.SimpleDCE() if err != nil { - return c.mod, nil, []error{err} + return c.mod, nil, nil, []error{err} } // Initialize debug information. @@ -378,12 +342,15 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con // Gather the list of (C) file paths that should be included in the build. var extraFiles []string for _, pkg := range c.ir.LoaderProgram.Sorted() { - for _, file := range pkg.CFiles { - extraFiles = append(extraFiles, filepath.Join(pkg.Package.Dir, file)) + for _, file := range pkg.OtherFiles { + switch strings.ToLower(filepath.Ext(file)) { + case ".c": + extraFiles = append(extraFiles, file) + } } } - return c.mod, extraFiles, c.diagnostics + return c.mod, extraFiles, lprogram.LDFlags, c.diagnostics } // getLLVMRuntimeType obtains a named type from the runtime package and returns @@ -1104,7 +1071,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) { // goroutine: // * The function context, for closures. // * The function pointer (for tasks). - funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().(*types.Signature)) + funcPtr, context := b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature)) params = append(params, context) // context parameter switch b.Scheduler() { case "none", "coroutines": @@ -1201,9 +1168,7 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos var llvmCap llvm.Value switch args[0].Type().(type) { case *types.Chan: - // Channel. Buffered channels haven't been implemented yet so always - // return 0. - llvmCap = llvm.ConstInt(b.intType, 0, false) + llvmCap = b.createRuntimeCall("chanCap", []llvm.Value{value}, "cap") case *types.Slice: llvmCap = b.CreateExtractValue(value, 2, "cap") default: @@ -1259,9 +1224,7 @@ func (b *builder) createBuiltin(args []ssa.Value, callName string, pos token.Pos // string or slice llvmLen = b.CreateExtractValue(value, 1, "len") case *types.Chan: - // Channel. Buffered channels haven't been implemented yet so always - // return 0. - llvmLen = llvm.ConstInt(b.intType, 0, false) + llvmLen = b.createRuntimeCall("chanLen", []llvm.Value{value}, "len") case *types.Map: llvmLen = b.createRuntimeCall("hashmapLen", []llvm.Value{value}, "len") default: @@ -1364,11 +1327,9 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.createMemoryCopyCall(fn, instr.Args) case name == "runtime.memzero": return b.createMemoryZeroCall(instr.Args) - case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister": - return b.createReadRegister(name, instr.Args) - case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": + case name == "device.Asm" || name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": return b.createInlineAsm(instr.Args) - case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": + case name == "device.AsmFull" || name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": return b.createInlineAsmFull(instr) case strings.HasPrefix(name, "device/arm.SVCall"): return b.emitSVCall(instr.Args) @@ -1380,6 +1341,14 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) return b.createVolatileLoad(instr) case strings.HasPrefix(name, "runtime/volatile.Store"): return b.createVolatileStore(instr) + case strings.HasPrefix(name, "sync/atomic."): + val, ok := b.createAtomicOp(instr) + if ok { + // This call could be lowered as an atomic operation. + return val, nil + } + // This call couldn't be lowered as an atomic operation, it's + // probably something else. Continue as usual. case name == "runtime/interrupt.New": return b.createInterruptGlobal(instr) } @@ -1573,7 +1542,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { index := b.getValue(expr.Index) // Check bounds. - arrayLen := expr.X.Type().(*types.Array).Len() + arrayLen := expr.X.Type().Underlying().(*types.Array).Len() arrayLenLLVM := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false) b.createLookupBoundsCheck(arrayLenLLVM, index, expr.Index.Type()) @@ -1685,8 +1654,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) { } // Bounds checking. - lenType := expr.Len.Type().(*types.Basic) - capType := expr.Cap.Type().(*types.Basic) + lenType := expr.Len.Type().Underlying().(*types.Basic) + capType := expr.Cap.Type().Underlying().(*types.Basic) b.createSliceBoundsCheck(maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType) // Allocate the backing array. diff --git a/compiler/inlineasm.go b/compiler/inlineasm.go index 2727228a18..6089f096a7 100644 --- a/compiler/inlineasm.go +++ b/compiler/inlineasm.go @@ -13,27 +13,6 @@ import ( "tinygo.org/x/go-llvm" ) -// This is a compiler builtin, which reads the given register by name: -// -// func ReadRegister(name string) uintptr -// -// The register name must be a constant, for example "sp". -func (b *builder) createReadRegister(name string, args []ssa.Value) (llvm.Value, error) { - fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{}, false) - regname := constant.StringVal(args[0].(*ssa.Const).Value) - var asm string - switch name { - case "device/arm.ReadRegister": - asm = "mov $0, " + regname - case "device/riscv.ReadRegister": - asm = "mv $0, " + regname - default: - panic("unknown architecture") - } - target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0) - return b.CreateCall(target, nil, ""), nil -} - // This is a compiler builtin, which emits a piece of inline assembly with no // operands or return values. It is useful for trivial instructions, like wfi in // ARM or sleep in AVR. @@ -52,7 +31,7 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { // This is a compiler builtin, which allows assembly to be called in a flexible // way. // -// func AsmFull(asm string, regs map[string]interface{}) +// func AsmFull(asm string, regs map[string]interface{}) uintptr // // The asm parameter must be a constant string. The regs parameter must be // provided immediately. For example: @@ -66,24 +45,24 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) { func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) { asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value) registers := map[string]llvm.Value{} - registerMap := instr.Args[1].(*ssa.MakeMap) - for _, r := range *registerMap.Referrers() { - switch r := r.(type) { - case *ssa.DebugRef: - // ignore - case *ssa.MapUpdate: - if r.Block() != registerMap.Block() { - return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") - } - key := constant.StringVal(r.Key.(*ssa.Const).Value) - //println("value:", r.Value.(*ssa.MakeInterface).X.String()) - registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) - case *ssa.Call: - if r.Common() == instr { - break + if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok { + for _, r := range *registerMap.Referrers() { + switch r := r.(type) { + case *ssa.DebugRef: + // ignore + case *ssa.MapUpdate: + if r.Block() != registerMap.Block() { + return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block") + } + key := constant.StringVal(r.Key.(*ssa.Const).Value) + registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X) + case *ssa.Call: + if r.Common() == instr { + break + } + default: + return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) } - default: - return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String()) } } // TODO: handle dollar signs in asm string @@ -92,6 +71,15 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) argTypes := []llvm.Type{} args := []llvm.Value{} constraints := []string{} + hasOutput := false + asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string { + hasOutput = true + return "$0" + }) + if hasOutput { + constraints = append(constraints, "=&r") + registerNumbers[""] = 0 + } asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string { // TODO: skip strings like {r4} etc. that look like ARM push/pop // instructions. @@ -121,9 +109,21 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) if err != nil { return llvm.Value{}, err } - fnType := llvm.FunctionType(b.ctx.VoidType(), argTypes, false) + var outputType llvm.Type + if hasOutput { + outputType = b.uintptrType + } else { + outputType = b.ctx.VoidType() + } + fnType := llvm.FunctionType(outputType, argTypes, false) target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0) - return b.CreateCall(target, args, ""), nil + result := b.CreateCall(target, args, "") + if hasOutput { + return result, nil + } else { + // Make sure we return something valid. + return llvm.ConstInt(b.uintptrType, 0, false), nil + } } // This is a compiler builtin which emits an inline SVCall instruction. It can diff --git a/go.mod b/go.mod index b668cf69e1..688793696d 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,11 @@ go 1.11 require ( github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 + github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac + github.com/chromedp/chromedp v0.5.3 github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf - github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 + github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 go.bug.st/serial v1.0.0 golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 - google.golang.org/appengine v1.4.0 // indirect - tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a + tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d ) diff --git a/go.sum b/go.sum index 3133c874f8..c24491ef21 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,26 @@ github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI= github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= +github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4= +github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g= +github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg= +github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w= github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0= github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= -github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo= -github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= +github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs= +github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0= +github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw= +github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -17,43 +30,21 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU= golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids= -golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig= golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw= -tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w= -tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU= -tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o= -tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0= -tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc= -tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0= -tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY= -tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d h1:mtgZh/e8a3wxneQFuLXoQYO//1mvlki02yZ1JCwMKp4= -tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= -tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a h1:Ugje2Lxuv8CFncHzs5W+hWfJvPsM+W4K0zRvzFbLvoE= -tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= +tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d h1:hcX7vpB067GWM/EH4sGGOti0PMgIx+0bbZwUXctOIvE= +tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE= diff --git a/goenv/version.go b/goenv/version.go new file mode 100644 index 0000000000..5114470536 --- /dev/null +++ b/goenv/version.go @@ -0,0 +1,68 @@ +package goenv + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "path/filepath" + "regexp" + "strings" +) + +// Version of TinyGo. +// Update this value before release of new version of software. +const Version = "0.14.0-dev" + +// GetGorootVersion returns the major and minor version for a given GOROOT path. +// If the goroot cannot be determined, (0, 0) is returned. +func GetGorootVersion(goroot string) (major, minor int, err error) { + s, err := GorootVersionString(goroot) + if err != nil { + return 0, 0, err + } + + if s == "" || s[:2] != "go" { + return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix") + } + + parts := strings.Split(s[2:], ".") + if len(parts) < 2 { + return 0, 0, errors.New("could not parse Go version: version has less than two parts") + } + + // Ignore the errors, we don't really handle errors here anyway. + var trailing string + n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing) + if n == 2 && err == io.EOF { + // Means there were no trailing characters (i.e., not an alpha/beta) + err = nil + } + if err != nil { + return 0, 0, fmt.Errorf("failed to parse version: %s", err) + } + return +} + +// GorootVersionString returns the version string as reported by the Go +// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but +// can have some variations (for beta releases, for example). +func GorootVersionString(goroot string) (string, error) { + if data, err := ioutil.ReadFile(filepath.Join( + goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil { + + r := regexp.MustCompile("const TheVersion = `(.*)`") + matches := r.FindSubmatch(data) + if len(matches) != 2 { + return "", errors.New("Invalid go version output:\n" + string(data)) + } + + return string(matches[1]), nil + + } else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil { + return string(data), nil + + } else { + return "", err + } +} diff --git a/ir/ir.go b/ir/ir.go index 7506400dfa..24bdd0ff3a 100644 --- a/ir/ir.go +++ b/ir/ir.go @@ -63,73 +63,14 @@ const ( ) // Create and initialize a new *Program from a *ssa.Program. -func NewProgram(lprogram *loader.Program, mainPath string) *Program { +func NewProgram(lprogram *loader.Program) *Program { program := lprogram.LoadSSA() program.Build() - // Find the main package, which is a bit difficult when running a .go file - // directly. - mainPkg := program.ImportedPackage(mainPath) - if mainPkg == nil { - for _, pkgInfo := range program.AllPackages() { - if pkgInfo.Pkg.Name() == "main" { - if mainPkg != nil { - panic("more than one main package found") - } - mainPkg = pkgInfo - } - } - } + mainPkg := program.ImportedPackage(lprogram.MainPkg.PkgPath) if mainPkg == nil { panic("could not find main package") } - - // Make a list of packages in import order. - packageList := []*ssa.Package{} - packageSet := map[string]struct{}{} - worklist := []string{"runtime", mainPath} - for len(worklist) != 0 { - pkgPath := worklist[0] - var pkg *ssa.Package - if pkgPath == mainPath { - pkg = mainPkg // necessary for compiling individual .go files - } else { - pkg = program.ImportedPackage(pkgPath) - } - if pkg == nil { - // Non-SSA package (e.g. cgo). - packageSet[pkgPath] = struct{}{} - worklist = worklist[1:] - continue - } - if _, ok := packageSet[pkgPath]; ok { - // Package already in the final package list. - worklist = worklist[1:] - continue - } - - unsatisfiedImports := make([]string, 0) - imports := pkg.Pkg.Imports() - for _, pkg := range imports { - if _, ok := packageSet[pkg.Path()]; ok { - continue - } - unsatisfiedImports = append(unsatisfiedImports, pkg.Path()) - } - if len(unsatisfiedImports) == 0 { - // All dependencies of this package are satisfied, so add this - // package to the list. - packageList = append(packageList, pkg) - packageSet[pkgPath] = struct{}{} - worklist = worklist[1:] - } else { - // Prepend all dependencies to the worklist and reconsider this - // package (by not removing it from the worklist). At that point, it - // must be possible to add it to packageList. - worklist = append(unsatisfiedImports, worklist...) - } - } - p := &Program{ Program: program, LoaderProgram: lprogram, @@ -137,8 +78,8 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program { functionMap: make(map[*ssa.Function]*Function), } - for _, pkg := range packageList { - p.AddPackage(pkg) + for _, pkg := range lprogram.Sorted() { + p.AddPackage(program.ImportedPackage(pkg.PkgPath)) } return p diff --git a/loader/errors.go b/loader/errors.go index c25652ee19..e0414204b8 100644 --- a/loader/errors.go +++ b/loader/errors.go @@ -1,10 +1,5 @@ package loader -import ( - "go/token" - "strings" -) - // Errors contains a list of parser errors or a list of typechecker errors for // the given package. type Errors struct { @@ -15,25 +10,3 @@ type Errors struct { func (e Errors) Error() string { return "could not compile: " + e.Errs[0].Error() } - -// ImportCycleErrors is returned when encountering an import cycle. The list of -// packages is a list from the root package to the leaf package that imports one -// of the packages in the list. -type ImportCycleError struct { - Packages []string - ImportPositions []token.Position -} - -func (e *ImportCycleError) Error() string { - var msg strings.Builder - msg.WriteString("import cycle:\n\t") - msg.WriteString(strings.Join(e.Packages, "\n\t")) - msg.WriteString("\n at ") - for i, pos := range e.ImportPositions { - if i > 0 { - msg.WriteString(", ") - } - msg.WriteString(pos.String()) - } - return msg.String() -} diff --git a/loader/goroot.go b/loader/goroot.go new file mode 100644 index 0000000000..7947d15e21 --- /dev/null +++ b/loader/goroot.go @@ -0,0 +1,240 @@ +package loader + +// This file constructs a new temporary GOROOT directory by merging both the +// standard Go GOROOT and the GOROOT from TinyGo using symlinks. + +import ( + "crypto/sha512" + "encoding/hex" + "errors" + "fmt" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strconv" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/goenv" +) + +// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and +// the GOROOT from TinyGo using lots of symbolic links. +func GetCachedGoroot(config *compileopts.Config) (string, error) { + goroot := goenv.Get("GOROOT") + if goroot == "" { + return "", errors.New("could not determine GOROOT") + } + tinygoroot := goenv.Get("TINYGOROOT") + if tinygoroot == "" { + return "", errors.New("could not determine TINYGOROOT") + } + + // Determine the location of the cached GOROOT. + version, err := goenv.GorootVersionString(goroot) + if err != nil { + return "", err + } + // This hash is really a cache key, that contains (hopefully) enough + // information to make collisions unlikely during development. + // By including the Go version and TinyGo version, cache collisions should + // not happen outside of development. + hash := sha512.New512_256() + fmt.Fprintln(hash, goroot) + fmt.Fprintln(hash, version) + fmt.Fprintln(hash, goenv.Version) + fmt.Fprintln(hash, tinygoroot) + gorootsHash := hash.Sum(nil) + gorootsHashHex := hex.EncodeToString(gorootsHash[:]) + cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), "goroot-"+version+"-"+gorootsHashHex) + if needsSyscallPackage(config.BuildTags()) { + cachedgoroot += "-syscall" + } + + if _, err := os.Stat(cachedgoroot); err == nil { + return cachedgoroot, nil + } + tmpgoroot := cachedgoroot + ".tmp" + strconv.Itoa(rand.Int()) + err = os.MkdirAll(tmpgoroot, 0777) + if err != nil { + return "", err + } + + // Remove the temporary directory if it wasn't moved to the right place + // (for example, when there was an error). + defer os.RemoveAll(tmpgoroot) + + for _, name := range []string{"bin", "lib", "pkg"} { + err = symlink(filepath.Join(goroot, name), filepath.Join(tmpgoroot, name)) + if err != nil { + return "", err + } + } + err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage(config.BuildTags()))) + if err != nil { + return "", err + } + err = os.Rename(tmpgoroot, cachedgoroot) + if err != nil { + if os.IsExist(err) { + // Another invocation of TinyGo also seems to have created a GOROOT. + // Use that one instead. Our new GOROOT will be automatically + // deleted by the defer above. + return cachedgoroot, nil + } + return "", err + } + return cachedgoroot, nil +} + +// mergeDirectory merges two roots recursively. The tmpgoroot is the directory +// that will be created by this call by either symlinking the directory from +// goroot or tinygoroot, or by creating the directory and merging the contents. +func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides map[string]bool) error { + if mergeSubdirs, ok := overrides[importPath+"/"]; ok { + if !mergeSubdirs { + // This directory and all subdirectories should come from the TinyGo + // root, so simply make a symlink. + newname := filepath.Join(tmpgoroot, "src", importPath) + oldname := filepath.Join(tinygoroot, "src", importPath) + return symlink(oldname, newname) + } + + // Merge subdirectories. Start by making the directory to merge. + err := os.Mkdir(filepath.Join(tmpgoroot, "src", importPath), 0777) + if err != nil { + return err + } + + // Symlink all files from TinyGo, and symlink directories from TinyGo + // that need to be overridden. + tinygoEntries, err := ioutil.ReadDir(filepath.Join(tinygoroot, "src", importPath)) + if err != nil { + return err + } + for _, e := range tinygoEntries { + if e.IsDir() { + // A directory, so merge this thing. + err := mergeDirectory(goroot, tinygoroot, tmpgoroot, path.Join(importPath, e.Name()), overrides) + if err != nil { + return err + } + } else { + // A file, so symlink this. + newname := filepath.Join(tmpgoroot, "src", importPath, e.Name()) + oldname := filepath.Join(tinygoroot, "src", importPath, e.Name()) + err := symlink(oldname, newname) + if err != nil { + return err + } + } + } + + // Symlink all directories from $GOROOT that are not part of the TinyGo + // overrides. + gorootEntries, err := ioutil.ReadDir(filepath.Join(goroot, "src", importPath)) + if err != nil { + 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 + } + } + } + return nil +} + +// needsSyscallPackage returns whether the syscall package should be overriden +// with the TinyGo version. This is the case on some targets. +func needsSyscallPackage(buildTags []string) bool { + for _, tag := range buildTags { + if tag == "baremetal" || tag == "darwin" { + return true + } + } + return false +} + +// The boolean indicates whether to merge the subdirs. True means merge, false +// means use the TinyGo version. +func pathsToOverride(needsSyscallPackage bool) map[string]bool { + paths := map[string]bool{ + "/": true, + "device/": false, + "examples/": false, + "internal/": true, + "internal/bytealg/": false, + "internal/reflectlite/": false, + "internal/task/": false, + "machine/": false, + "os/": true, + "reflect/": false, + "runtime/": false, + "sync/": true, + "testing/": false, + } + if needsSyscallPackage { + paths["syscall/"] = true // include syscall/js + } + return paths +} + +// symlink creates a symlink or something similar. On Unix-like systems, it +// always creates a symlink. On Windows, it tries to create a symlink and if +// that fails, creates a hardlink or directory junction instead. +// +// Note that while Windows 10 does support symlinks and allows them to be +// created using os.Symlink, it requires developer mode to be enabled. +// Therefore provide a fallback for when symlinking is not possible. +// Unfortunately this fallback only works when TinyGo is installed on the same +// filesystem as the TinyGo cache and the Go installation (which is usually the +// C drive). +func symlink(oldname, newname string) error { + symlinkErr := os.Symlink(oldname, newname) + if runtime.GOOS == "windows" && symlinkErr != nil { + // Fallback for when developer mode is disabled. + // Note that we return the symlink error even if something else fails + // later on. This is because symlinks are the easiest to support + // (they're also used on Linux and MacOS) and enabling them is easy: + // just enable developer mode. + st, err := os.Stat(oldname) + if err != nil { + return symlinkErr + } + if st.IsDir() { + // Make a directory junction. There may be a way to do this + // programmatically, but it involves a lot of magic. Use the mklink + // command built into cmd instead (mklink is a builtin, not an + // external command). + err := exec.Command("cmd", "/k", "mklink", "/J", newname, oldname).Run() + if err != nil { + return symlinkErr + } + } else { + // Make a hard link. + err := os.Link(oldname, newname) + if err != nil { + return symlinkErr + } + } + return nil // success + } + return symlinkErr +} diff --git a/loader/loader.go b/loader/loader.go index 5682bfa4a8..baeecc0416 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -3,6 +3,7 @@ package loader import ( "bytes" "errors" + "fmt" "go/ast" "go/build" "go/parser" @@ -12,114 +13,191 @@ import ( "os" "path/filepath" "sort" + "strconv" "strings" "text/template" "github.com/tinygo-org/tinygo/cgo" + "github.com/tinygo-org/tinygo/goenv" + "golang.org/x/tools/go/packages" ) // Program holds all packages and some metadata about the program as a whole. type Program struct { - mainPkg string Build *build.Context - OverlayBuild *build.Context - OverlayPath func(path string) string + Tests bool Packages map[string]*Package + MainPkg *Package sorted []*Package fset *token.FileSet TypeChecker types.Config Dir string // current working directory (for error reporting) TINYGOROOT string // root of the TinyGo installation or root of the source code CFlags []string + LDFlags []string ClangHeaders string } // Package holds a loaded package, its imports, and its parsed files. type Package struct { *Program - *build.Package - Imports map[string]*Package - Importing bool - Files []*ast.File - Pkg *types.Package + *packages.Package + Files []*ast.File + Pkg *types.Package types.Info } -// Import loads the given package relative to srcDir (for the vendor directory). -// It only loads the current package without recursion. -func (p *Program) Import(path, srcDir string, pos token.Position) (*Package, error) { +// Load loads the given package with all dependencies (including the runtime +// package). Call .Parse() afterwards to parse all Go files (including CGo +// processing, if necessary). +func (p *Program) Load(importPath string) error { if p.Packages == nil { p.Packages = make(map[string]*Package) } - // Load this package. - ctx := p.Build - if newPath := p.OverlayPath(path); newPath != "" { - ctx = p.OverlayBuild - path = newPath - } - buildPkg, err := ctx.Import(path, srcDir, build.ImportComment) + err := p.loadPackage(importPath) if err != nil { - return nil, scanner.Error{ - Pos: pos, - Msg: err.Error(), // TODO: define a new error type that will wrap the inner error - } - } - if existingPkg, ok := p.Packages[buildPkg.ImportPath]; ok { - // Already imported, or at least started the import. - return existingPkg, nil + return err } - p.sorted = nil // invalidate the sorted order of packages - pkg := p.newPackage(buildPkg) - p.Packages[buildPkg.ImportPath] = pkg - - if p.mainPkg == "" { - p.mainPkg = buildPkg.ImportPath + p.MainPkg = p.sorted[len(p.sorted)-1] + if _, ok := p.Packages["runtime"]; !ok { + // The runtime package wasn't loaded. Although `go list -deps` seems to + // return the full dependency list, there is no way to get those + // packages from the go/packages package. Therefore load the runtime + // manually and add it to the list of to-be-compiled packages + // (duplicates are already filtered). + return p.loadPackage("runtime") } - - return pkg, nil + return nil } -// ImportFile loads and parses the import statements in the given path and -// creates a pseudo-package out of it. -func (p *Program) ImportFile(path string) (*Package, error) { - if p.Packages == nil { - p.Packages = make(map[string]*Package) +func (p *Program) loadPackage(importPath string) error { + cgoEnabled := "0" + if p.Build.CgoEnabled { + cgoEnabled = "1" + } + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps, + Env: append(os.Environ(), "GOROOT="+p.Build.GOROOT, "GOOS="+p.Build.GOOS, "GOARCH="+p.Build.GOARCH, "CGO_ENABLED="+cgoEnabled), + BuildFlags: []string{"-tags", strings.Join(p.Build.BuildTags, " ")}, + Tests: p.Tests, + }, importPath) + if err != nil { + return err } - if _, ok := p.Packages[path]; ok { - // unlikely - return nil, errors.New("loader: cannot import file that is already imported as package: " + path) + var pkg *packages.Package + if p.Tests { + // We need the second package. Quoting from the docs: + // > For example, when using the go command, loading "fmt" with Tests=true + // > returns four packages, with IDs "fmt" (the standard package), + // > "fmt [fmt.test]" (the package as compiled for the test), + // > "fmt_test" (the test functions from source files in package fmt_test), + // > and "fmt.test" (the test binary). + pkg = pkgs[1] + } else { + if len(pkgs) != 1 { + return fmt.Errorf("expected exactly one package while importing %s, got %d", importPath, len(pkgs)) + } + pkg = pkgs[0] } + var importError *Errors + var addPackages func(pkg *packages.Package) + addPackages = func(pkg *packages.Package) { + if _, ok := p.Packages[pkg.PkgPath]; ok { + return + } + pkg2 := p.newPackage(pkg) + p.Packages[pkg.PkgPath] = pkg2 + if len(pkg.Errors) != 0 { + if importError != nil { + // There was another error reported already. Do not report + // errors from multiple packages at once. + return + } + importError = &Errors{ + Pkg: pkg2, + } + for _, err := range pkg.Errors { + pos := token.Position{} + fields := strings.Split(err.Pos, ":") + if len(fields) >= 2 { + // There is some file/line/column information. + if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil { + // Format: filename.go:line:colum + pos.Filename = strings.Join(fields[:len(fields)-2], ":") + pos.Line = n + pos.Column, _ = strconv.Atoi(fields[len(fields)-1]) + } else { + // Format: filename.go:line + pos.Filename = strings.Join(fields[:len(fields)-1], ":") + pos.Line, _ = strconv.Atoi(fields[len(fields)-1]) + } + pos.Filename = p.getOriginalPath(pos.Filename) + } + importError.Errs = append(importError.Errs, scanner.Error{ + Pos: pos, + Msg: err.Msg, + }) + } + return + } - file, err := p.parseFile(path, parser.ImportsOnly) - if err != nil { - return nil, err - } - buildPkg := &build.Package{ - Dir: filepath.Dir(path), - ImportPath: path, - GoFiles: []string{filepath.Base(path)}, + // Get the list of imports (sorted alphabetically). + names := make([]string, 0, len(pkg.Imports)) + for name := range pkg.Imports { + names = append(names, name) + } + sort.Strings(names) + + // Add all the imports. + for _, name := range names { + addPackages(pkg.Imports[name]) + } + + p.sorted = append(p.sorted, pkg2) } - for _, importSpec := range file.Imports { - buildPkg.Imports = append(buildPkg.Imports, importSpec.Path.Value[1:len(importSpec.Path.Value)-1]) + addPackages(pkg) + if importError != nil { + return *importError } - p.sorted = nil // invalidate the sorted order of packages - pkg := p.newPackage(buildPkg) - p.Packages[buildPkg.ImportPath] = pkg + return nil +} - if p.mainPkg == "" { - p.mainPkg = buildPkg.ImportPath +// getOriginalPath looks whether this path is in the generated GOROOT and if so, +// replaces the path with the original path (in GOROOT or TINYGOROOT). Otherwise +// the input path is returned. +func (p *Program) getOriginalPath(path string) string { + originalPath := path + if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) { + // If this file is part of the synthetic GOROOT, try to infer the + // original path. + relpath := path[len(filepath.Join(p.Build.GOROOT, "src"))+1:] + realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath) + if _, err := os.Stat(realgorootPath); err == nil { + originalPath = realgorootPath + } + maybeInTinyGoRoot := false + for prefix := range pathsToOverride(needsSyscallPackage(p.Build.BuildTags)) { + if !strings.HasPrefix(relpath, prefix) { + continue + } + maybeInTinyGoRoot = true + } + if maybeInTinyGoRoot { + tinygoPath := filepath.Join(p.TINYGOROOT, "src", relpath) + if _, err := os.Stat(tinygoPath); err == nil { + originalPath = tinygoPath + } + } } - - return pkg, nil + return originalPath } // newPackage instantiates a new *Package object with initialized members. -func (p *Program) newPackage(pkg *build.Package) *Package { +func (p *Program) newPackage(pkg *packages.Package) *Package { return &Package{ Program: p, Package: pkg, - Imports: make(map[string]*Package, len(pkg.Imports)), Info: types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), @@ -134,87 +212,25 @@ func (p *Program) newPackage(pkg *build.Package) *Package { // Sorted returns a list of all packages, sorted in a way that no packages come // before the packages they depend upon. func (p *Program) Sorted() []*Package { - if p.sorted == nil { - p.sort() - } return p.sorted } -func (p *Program) sort() { - p.sorted = nil - packageList := make([]*Package, 0, len(p.Packages)) - packageSet := make(map[string]struct{}, len(p.Packages)) - worklist := make([]string, 0, len(p.Packages)) - for path := range p.Packages { - worklist = append(worklist, path) - } - sort.Strings(worklist) - for len(worklist) != 0 { - pkgPath := worklist[0] - pkg := p.Packages[pkgPath] - - if _, ok := packageSet[pkgPath]; ok { - // Package already in the final package list. - worklist = worklist[1:] - continue - } - - unsatisfiedImports := make([]string, 0) - for _, pkg := range pkg.Imports { - if _, ok := packageSet[pkg.ImportPath]; ok { - continue - } - unsatisfiedImports = append(unsatisfiedImports, pkg.ImportPath) - } - sort.Strings(unsatisfiedImports) - if len(unsatisfiedImports) == 0 { - // All dependencies of this package are satisfied, so add this - // package to the list. - packageList = append(packageList, pkg) - packageSet[pkgPath] = struct{}{} - worklist = worklist[1:] - } else { - // Prepend all dependencies to the worklist and reconsider this - // package (by not removing it from the worklist). At that point, it - // must be possible to add it to packageList. - worklist = append(unsatisfiedImports, worklist...) - } - } - - p.sorted = packageList -} - -// Parse recursively imports all packages, parses them, and typechecks them. +// Parse parses all packages and typechecks them. // // The returned error may be an Errors error, which contains a list of errors. // // Idempotent. -func (p *Program) Parse(compileTestBinary bool) error { - includeTests := compileTestBinary - - // Load all imports - for _, pkg := range p.Sorted() { - err := pkg.importRecursively(includeTests) - if err != nil { - if err, ok := err.(*ImportCycleError); ok { - if pkg.ImportPath != err.Packages[0] { - err.Packages = append([]string{pkg.ImportPath}, err.Packages...) - } - } - return err - } - } - +func (p *Program) Parse() error { // Parse all packages. for _, pkg := range p.Sorted() { - err := pkg.Parse(includeTests) + err := pkg.Parse() if err != nil { return err } } - if compileTestBinary { - err := p.SwapTestMain() + if p.Tests { + err := p.swapTestMain() if err != nil { return err } @@ -231,7 +247,7 @@ func (p *Program) Parse(compileTestBinary bool) error { return nil } -func (p *Program) SwapTestMain() error { +func (p *Program) swapTestMain() error { var tests []string isTestFunc := func(f *ast.FuncDecl) bool { @@ -241,8 +257,7 @@ func (p *Program) SwapTestMain() error { } return false } - mainPkg := p.Packages[p.mainPkg] - for _, f := range mainPkg.Files { + for _, f := range p.MainPkg.Files { for i, d := range f.Decls { switch v := d.(type) { case *ast.FuncDecl: @@ -293,7 +308,7 @@ func main () { if err != nil { return err } - path := filepath.Join(p.mainPkg, "$testmain.go") + path := filepath.Join(p.MainPkg.Dir, "$testmain.go") if p.fset == nil { p.fset = token.NewFileSet() @@ -303,7 +318,7 @@ func main () { if err != nil { return err } - mainPkg.Files = append(mainPkg.Files, newMain) + p.MainPkg.Files = append(p.MainPkg.Files, newMain) return nil } @@ -319,34 +334,27 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) { return nil, err } defer rd.Close() - relpath := path - if filepath.IsAbs(path) { - rp, err := filepath.Rel(p.Dir, path) - if err == nil { - relpath = rp - } - } - return parser.ParseFile(p.fset, relpath, rd, mode) + return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode) } // Parse parses and typechecks this package. // // Idempotent. -func (p *Package) Parse(includeTests bool) error { +func (p *Package) Parse() error { if len(p.Files) != 0 { return nil } // Load the AST. // TODO: do this in parallel. - if p.ImportPath == "unsafe" { + if p.PkgPath == "unsafe" { // Special case for the unsafe package. Don't even bother loading // the files. p.Pkg = types.Unsafe return nil } - files, err := p.parseFiles(includeTests) + files, err := p.parseFiles() if err != nil { return err } @@ -373,7 +381,7 @@ func (p *Package) Check() error { // Do typechecking of the package. checker.Importer = p - typesPkg, err := checker.Check(p.ImportPath, p.fset, p.Files, &p.Info) + typesPkg, err := checker.Check(p.PkgPath, p.fset, p.Files, &p.Info) if err != nil { if err, ok := err.(Errors); ok { return err @@ -385,22 +393,14 @@ func (p *Package) Check() error { } // parseFiles parses the loaded list of files and returns this list. -func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) { +func (p *Package) parseFiles() ([]*ast.File, error) { // TODO: do this concurrently. var files []*ast.File var fileErrs []error - var gofiles []string - if includeTests { - gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles)) - gofiles = append(gofiles, p.GoFiles...) - gofiles = append(gofiles, p.TestGoFiles...) - } else { - gofiles = p.GoFiles - } - - for _, file := range gofiles { - f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments) + var cgoFiles []*ast.File + for _, file := range p.GoFiles { + f, err := p.parseFile(file, parser.ParseComments) if err != nil { fileErrs = append(fileErrs, err) continue @@ -409,27 +409,24 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) { fileErrs = append(fileErrs, err) continue } - files = append(files, f) - } - for _, file := range p.CgoFiles { - path := filepath.Join(p.Package.Dir, file) - f, err := p.parseFile(path, parser.ParseComments) - if err != nil { - fileErrs = append(fileErrs, err) - continue + for _, importSpec := range f.Imports { + if importSpec.Path.Value == `"C"` { + cgoFiles = append(cgoFiles, f) + } } files = append(files, f) } - if len(p.CgoFiles) != 0 { - cflags := append(p.CFlags, "-I"+p.Package.Dir) + if len(cgoFiles) != 0 { + cflags := append(p.CFlags, "-I"+filepath.Dir(p.GoFiles[0])) if p.ClangHeaders != "" { cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders) } - generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags) + generated, ldflags, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags) if errs != nil { fileErrs = append(fileErrs, errs...) } files = append(files, generated) + p.LDFlags = append(p.LDFlags, ldflags...) } if len(fileErrs) != 0 { return nil, Errors{p, fileErrs} @@ -445,58 +442,8 @@ func (p *Package) Import(to string) (*types.Package, error) { return types.Unsafe, nil } if _, ok := p.Imports[to]; ok { - return p.Imports[to].Pkg, nil + return p.Packages[p.Imports[to].PkgPath].Pkg, nil } else { return nil, errors.New("package not imported: " + to) } } - -// importRecursively calls Program.Import() on all imported packages, and calls -// importRecursively() on the imported packages as well. -// -// Idempotent. -func (p *Package) importRecursively(includeTests bool) error { - p.Importing = true - - imports := p.Package.Imports - if includeTests { - imports = append(imports, p.Package.TestImports...) - } - - for _, to := range imports { - if to == "C" { - // Do CGo processing in a later stage. - continue - } - if _, ok := p.Imports[to]; ok { - continue - } - // Find error location. - var pos token.Position - if len(p.Package.ImportPos[to]) > 0 { - pos = p.Package.ImportPos[to][0] - } else { - pos = token.Position{Filename: p.Package.ImportPath} - } - importedPkg, err := p.Program.Import(to, p.Package.Dir, pos) - if err != nil { - if err, ok := err.(*ImportCycleError); ok { - err.Packages = append([]string{p.ImportPath}, err.Packages...) - } - return err - } - if importedPkg.Importing { - return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]} - } - err = importedPkg.importRecursively(false) - if err != nil { - if err, ok := err.(*ImportCycleError); ok { - err.Packages = append([]string{p.ImportPath}, err.Packages...) - } - return err - } - p.Imports[to] = importedPkg - } - p.Importing = false - return nil -} diff --git a/main.go b/main.go index 9af1a76d11..f77c5612b0 100644 --- a/main.go +++ b/main.go @@ -124,6 +124,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error { // Test runs the tests in the given package. func Test(pkgName string, options *compileopts.Options) error { + options.TestConfig.CompileTestBinary = true config, err := builder.NewConfig(options) if err != nil { return err @@ -135,7 +136,6 @@ func Test(pkgName string, options *compileopts.Options) error { // For details: https://github.com/golang/go/issues/21360 config.Target.BuildTags = append(config.Target.BuildTags, "test") - options.TestConfig.CompileTestBinary = true return builder.Build(pkgName, ".elf", config, func(tmppath string) error { cmd := exec.Command(tmppath) cmd.Stdout = os.Stdout @@ -479,6 +479,13 @@ func touchSerialPortAt1200bps(port string) (err error) { // Open port p, e := serial.Open(port, &serial.Mode{BaudRate: 1200}) if e != nil { + if runtime.GOOS == `windows` { + se, ok := e.(*serial.PortError) + if ok && se.Code() == serial.InvalidSerialPort { + // InvalidSerialPort error occurs when transitioning to boot + return nil + } + } time.Sleep(1 * time.Second) err = e continue @@ -491,6 +498,8 @@ func touchSerialPortAt1200bps(port string) (err error) { return fmt.Errorf("opening port: %s", err) } +const maxMSDRetries = 10 + func flashUF2UsingMSD(volume, tmppath string) error { // find standard UF2 info path var infoPath string @@ -507,15 +516,12 @@ func flashUF2UsingMSD(volume, tmppath string) error { infoPath = path + "/INFO_UF2.TXT" } - d, err := filepath.Glob(infoPath) + d, err := locateDevice(volume, infoPath) if err != nil { return err } - if d == nil { - return errors.New("unable to locate UF2 device: " + volume) - } - return moveFile(tmppath, filepath.Dir(d[0])+"/flash.uf2") + return moveFile(tmppath, filepath.Dir(d)+"/flash.uf2") } func flashHexUsingMSD(volume, tmppath string) error { @@ -534,15 +540,31 @@ func flashHexUsingMSD(volume, tmppath string) error { destPath = path + "/" } - d, err := filepath.Glob(destPath) + d, err := locateDevice(volume, destPath) if err != nil { return err } + + return moveFile(tmppath, d+"/flash.hex") +} + +func locateDevice(volume, path string) (string, error) { + var d []string + var err error + for i := 0; i < maxMSDRetries; i++ { + d, err = filepath.Glob(path) + if err != nil { + return "", err + } + if d != nil { + break + } + time.Sleep(500 * time.Millisecond) + } if d == nil { - return errors.New("unable to locate device: " + volume) + return "", errors.New("unable to locate device: " + volume) } - - return moveFile(tmppath, d[0]+"/flash.hex") + return d[0], nil } func windowsFindUSBDrive(volume string) (string, error) { @@ -603,29 +625,18 @@ func getDefaultPort() (port string, err error) { case "freebsd": portPath = "/dev/cuaU*" case "windows": - cmd := exec.Command("wmic", - "PATH", "Win32_SerialPort", "WHERE", "Caption LIKE 'USB Serial%'", "GET", "DeviceID") - - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() + ports, err := serial.GetPortsList() if err != nil { return "", err } - if out.String() == "No Instance(s) Available." { + if len(ports) == 0 { return "", errors.New("no serial ports available") + } else if len(ports) > 1 { + return "", errors.New("multiple serial ports available - use -port flag") } - for _, line := range strings.Split(out.String(), "\n") { - words := strings.Fields(line) - if len(words) == 1 { - if strings.Contains(words[0], "COM") { - return words[0], nil - } - } - } - return "", errors.New("unable to locate a serial port") + return ports[0], nil default: return "", errors.New("unable to search for a default USB device to be flashed on this OS") } @@ -641,9 +652,39 @@ func getDefaultPort() (port string, err error) { return d[0], nil } +// runGoList runs the `go list` command but using the configuration used for +// TinyGo. +func runGoList(config *compileopts.Config, flagJSON, flagDeps bool, pkgs []string) error { + goroot, err := loader.GetCachedGoroot(config) + if err != nil { + return err + } + args := []string{"list"} + if flagJSON { + args = append(args, "-json") + } + if flagDeps { + args = append(args, "-deps") + } + if len(config.BuildTags()) != 0 { + args = append(args, "-tags", strings.Join(config.BuildTags(), " ")) + } + args = append(args, pkgs...) + cgoEnabled := "0" + if config.CgoEnabled() { + cgoEnabled = "1" + } + cmd := exec.Command("go", args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled) + cmd.Run() + return nil +} + func usage() { fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") - fmt.Fprintln(os.Stderr, "version:", version) + fmt.Fprintln(os.Stderr, "version:", goenv.Version) fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=] -o \n", os.Args[0]) fmt.Fprintln(os.Stderr, "\ncommands:") fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") @@ -652,12 +693,27 @@ func usage() { fmt.Fprintln(os.Stderr, " flash: compile and flash to the device") fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB") fmt.Fprintln(os.Stderr, " env: list environment variables used during build") + fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root") fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")") fmt.Fprintln(os.Stderr, " help: print this help text") fmt.Fprintln(os.Stderr, "\nflags:") flag.PrintDefaults() } +// try to make the path relative to the current working directory. If any error +// occurs, this error is ignored and the absolute path is returned instead. +func tryToMakePathRelative(dir string) string { + wd, err := os.Getwd() + if err != nil { + return dir + } + relpath, err := filepath.Rel(wd, dir) + if err != nil { + return dir + } + return relpath +} + // printCompilerError prints compiler errors using the provided logger function // (similar to fmt.Println). // @@ -665,8 +721,24 @@ func usage() { // to limitations in the LLVM bindings. func printCompilerError(logln func(...interface{}), err error) { switch err := err.(type) { - case types.Error, scanner.Error: + case types.Error: + printCompilerError(logln, scanner.Error{ + Pos: err.Fset.Position(err.Pos), + Msg: err.Msg, + }) + case scanner.Error: + if !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("GOROOT"), "src")) && !strings.HasPrefix(err.Pos.Filename, filepath.Join(goenv.Get("TINYGOROOT"), "src")) { + // This file is not from the standard library (either the GOROOT or + // the TINYGOROOT). Make the path relative, for easier reading. + // Ignore any errors in the process (falling back to the absolute + // path). + err.Pos.Filename = tryToMakePathRelative(err.Pos.Filename) + } logln(err) + case scanner.ErrorList: + for _, scannerErr := range err { + printCompilerError(logln, *scannerErr) + } case *interp.Error: logln("#", err.ImportPath) logln(err.Error()) @@ -683,13 +755,13 @@ func printCompilerError(logln func(...interface{}), err error) { } } case loader.Errors: - logln("#", err.Pkg.ImportPath) + logln("#", err.Pkg.PkgPath) for _, err := range err.Errs { - logln(err) + printCompilerError(logln, err) } case *builder.MultiError: for _, err := range err.Errs { - logln(err) + printCompilerError(logln, err) } default: logln("error:", err) @@ -706,11 +778,18 @@ func handleCompilerError(err error) { } func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") + usage() + os.Exit(1) + } + command := os.Args[1] + outpath := flag.String("o", "", "output filename") opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") 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 (coroutines, tasks)") + scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)") 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") @@ -726,12 +805,11 @@ func main() { wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic") heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)") - if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") - usage() - os.Exit(1) + var flagJSON, flagDeps *bool + if command == "list" { + flagJSON = flag.Bool("json", false, "print data in JSON format") + flagDeps = flag.Bool("deps", false, "") } - command := os.Args[1] // Early command processing, before commands are interpreted by the Go flag // library. @@ -770,12 +848,6 @@ func main() { options.LDFlags = strings.Split(*ldFlags, " ") } - if *panicStrategy != "print" && *panicStrategy != "trap" { - fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.") - usage() - os.Exit(1) - } - var err error if options.HeapSize, err = parseSize(*heapSize); err != nil { fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize) @@ -785,6 +857,13 @@ func main() { os.Setenv("CC", "clang -target="+*target) + err = options.Verify() + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + usage() + os.Exit(1) + } + switch command { case "build": if *outpath == "" { @@ -803,6 +882,7 @@ func main() { if options.Target == "" && filepath.Ext(*outpath) == ".wasm" { options.Target = "wasm" } + err := Build(pkgName, *outpath, options) handleCompilerError(err) case "build-library": @@ -895,6 +975,18 @@ func main() { fmt.Printf("build tags: %s\n", strings.Join(config.BuildTags(), " ")) fmt.Printf("garbage collector: %s\n", config.GC()) fmt.Printf("scheduler: %s\n", config.Scheduler()) + case "list": + config, err := builder.NewConfig(options) + if err != nil { + fmt.Fprintln(os.Stderr, err) + usage() + os.Exit(1) + } + err = runGoList(config, *flagJSON, *flagDeps, flag.Args()) + if err != nil { + fmt.Fprintln(os.Stderr, "failed to run `go list`:", err) + os.Exit(1) + } case "clean": // remove cache directory err := os.RemoveAll(goenv.Get("GOCACHE")) @@ -906,10 +998,10 @@ func main() { usage() case "version": goversion := "" - if s, err := builder.GorootVersionString(goenv.Get("GOROOT")); err == nil { + if s, err := goenv.GorootVersionString(goenv.Get("GOROOT")); err == nil { goversion = s } - fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version) + fmt.Printf("tinygo version %s %s/%s (using go version %s and LLVM version %s)\n", goenv.Version, runtime.GOOS, runtime.GOARCH, goversion, llvm.Version) case "env": if flag.NArg() == 0 { // Show all environment variables. diff --git a/main_test.go b/main_test.go index ec7f708542..f3f10fd258 100644 --- a/main_test.go +++ b/main_test.go @@ -73,7 +73,7 @@ func TestCompiler(t *testing.T) { t.Run("ARM64Linux", func(t *testing.T) { runPlatTests("aarch64--linux-gnu", matches, t) }) - goVersion, err := builder.GorootVersionString(goenv.Get("GOROOT")) + goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT")) if err != nil { t.Error("could not get Go version:", err) return diff --git a/src/device/arm/arm.go b/src/device/arm/arm.go index 21ba3bbd95..a806bbc6b1 100644 --- a/src/device/arm/arm.go +++ b/src/device/arm/arm.go @@ -52,11 +52,10 @@ func Asm(asm string) // "value": 1 // "result": &dest, // }) -func AsmFull(asm string, regs map[string]interface{}) - -// ReadRegister returns the contents of the specified register. The register -// must be a processor register, reachable with the "mov" instruction. -func ReadRegister(name string) uintptr +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr // Run the following system call (SVCall) with 0 arguments. func SVCall0(num uintptr) uintptr @@ -192,22 +191,21 @@ func SetPriority(irq uint32, priority uint32) { NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority) } -// DisableInterrupts disables all interrupts, and returns the old state. -// -// TODO: it doesn't actually return the old state, meaning that it cannot be -// nested. +// DisableInterrupts disables all interrupts, and returns the old interrupt +// state. func DisableInterrupts() uintptr { - Asm("cpsid if") - return 0 + return AsmFull(` + mrs {}, PRIMASK + cpsid if + `, nil) } // EnableInterrupts enables all interrupts again. The value passed in must be // the mask returned by DisableInterrupts. -// -// TODO: it doesn't actually use the old state, meaning that it cannot be -// nested. func EnableInterrupts(mask uintptr) { - Asm("cpsie if") + AsmFull("msr PRIMASK, {mask}", map[string]interface{}{ + "mask": mask, + }) } // SystemReset performs a hard system reset. diff --git a/src/device/asm.go b/src/device/asm.go new file mode 100644 index 0000000000..f441b6ae29 --- /dev/null +++ b/src/device/asm.go @@ -0,0 +1,21 @@ +package device + +// Run the given assembly code. The code will be marked as having side effects, +// as it doesn't produce output and thus would normally be eliminated by the +// optimizer. +func Asm(asm string) + +// Run the given inline assembly. The code will be marked as having side +// effects, as it would otherwise be optimized away. The inline assembly string +// recognizes template values in the form {name}, like so: +// +// arm.AsmFull( +// "str {value}, {result}", +// map[string]interface{}{ +// "value": 1 +// "result": &dest, +// }) +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr diff --git a/src/device/avr/avr.go b/src/device/avr/avr.go index 7fa6bf9552..12f145dd7d 100644 --- a/src/device/avr/avr.go +++ b/src/device/avr/avr.go @@ -15,4 +15,7 @@ func Asm(asm string) // "value": 1 // "result": &dest, // }) -func AsmFull(asm string, regs map[string]interface{}) +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr diff --git a/src/device/riscv/riscv.go b/src/device/riscv/riscv.go index 947b3f181c..e4f9254b7d 100644 --- a/src/device/riscv/riscv.go +++ b/src/device/riscv/riscv.go @@ -5,6 +5,33 @@ package riscv // optimizer. func Asm(asm string) -// ReadRegister returns the contents of the specified register. The register -// must be a processor register, reachable with the "mov" instruction. -func ReadRegister(name string) uintptr +// Run the given inline assembly. The code will be marked as having side +// effects, as it would otherwise be optimized away. The inline assembly string +// recognizes template values in the form {name}, like so: +// +// arm.AsmFull( +// "st {value}, {result}", +// map[string]interface{}{ +// "value": 1 +// "result": &dest, +// }) +// +// You can use {} in the asm string (which expands to a register) to set the +// return value. +func AsmFull(asm string, regs map[string]interface{}) uintptr + +// DisableInterrupts disables all interrupts, and returns the old interrupt +// state. +func DisableInterrupts() uintptr { + // Note: this can be optimized with a CSRRW instruction, which atomically + // swaps the value and returns the old value. + mask := MIE.Get() + MIE.Set(0) + return mask +} + +// EnableInterrupts enables all interrupts again. The value passed in must be +// the mask returned by DisableInterrupts. +func EnableInterrupts(mask uintptr) { + MIE.Set(mask) +} diff --git a/src/examples/button/button.go b/src/examples/button/button.go index 8c245cdae6..698aa7a5d0 100644 --- a/src/examples/button/button.go +++ b/src/examples/button/button.go @@ -5,16 +5,14 @@ import ( "time" ) -// This example assumes that the button is connected to pin 8. Change the value -// below to use a different pin. const ( led = machine.LED - button = machine.Pin(8) + button = machine.BUTTON ) func main() { led.Configure(machine.PinConfig{Mode: machine.PinOutput}) - button.Configure(machine.PinConfig{Mode: machine.PinInput}) + button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) for { if button.Get() { diff --git a/src/examples/pininterrupt/circuitplay-express.go b/src/examples/pininterrupt/circuitplay-express.go new file mode 100644 index 0000000000..e37105c2c7 --- /dev/null +++ b/src/examples/pininterrupt/circuitplay-express.go @@ -0,0 +1,10 @@ +// +build circuitplay_express + +package main + +import "machine" + +const ( + buttonMode = machine.PinInputPulldown + buttonPinChange = machine.PinFalling +) diff --git a/src/examples/pininterrupt/pca10040.go b/src/examples/pininterrupt/pca10040.go new file mode 100644 index 0000000000..828252746e --- /dev/null +++ b/src/examples/pininterrupt/pca10040.go @@ -0,0 +1,10 @@ +// +build pca10040 + +package main + +import "machine" + +const ( + buttonMode = machine.PinInputPullup + buttonPinChange = machine.PinRising +) diff --git a/src/examples/pininterrupt/pininterrupt.go b/src/examples/pininterrupt/pininterrupt.go new file mode 100644 index 0000000000..0cb29bc854 --- /dev/null +++ b/src/examples/pininterrupt/pininterrupt.go @@ -0,0 +1,52 @@ +package main + +// This example demonstrates how to use pin change interrupts. +// +// This is only an example and should not be copied directly in any serious +// circuit, because it lacks an important feature: debouncing. +// See: https://en.wikipedia.org/wiki/Switch#Contact_bounce + +import ( + "machine" + "runtime/volatile" + "time" +) + +const ( + button = machine.BUTTON + led = machine.LED +) + +func main() { + var lightLed volatile.Register8 + lightLed.Set(0) + + // Configure the LED, defaulting to on (usually setting the pin to low will + // turn the LED on). + led.Configure(machine.PinConfig{Mode: machine.PinOutput}) + led.Low() + + // Make sure the pin is configured as a pullup to avoid floating inputs. + // Pullup works for most buttons, as most buttons short to ground when + // pressed. + button.Configure(machine.PinConfig{Mode: buttonMode}) + + // Set an interrupt on this pin. + err := button.SetInterrupt(buttonPinChange, func(machine.Pin) { + if lightLed.Get() != 0 { + lightLed.Set(0) + led.Low() + } else { + lightLed.Set(1) + led.High() + } + }) + if err != nil { + println("could not configure pin interrupt:", err.Error()) + } + + // Make sure the program won't exit. + for { + time.Sleep(time.Hour) + } +} diff --git a/src/examples/pininterrupt/wioterminal.go b/src/examples/pininterrupt/wioterminal.go new file mode 100644 index 0000000000..a9fbfc8492 --- /dev/null +++ b/src/examples/pininterrupt/wioterminal.go @@ -0,0 +1,10 @@ +// +build wioterminal + +package main + +import "machine" + +const ( + buttonMode = machine.PinInput + buttonPinChange = machine.PinFalling +) diff --git a/src/examples/pwm/pwm.go b/src/examples/pwm/pwm.go index 104934e569..380fbaa2e6 100644 --- a/src/examples/pwm/pwm.go +++ b/src/examples/pwm/pwm.go @@ -8,9 +8,9 @@ import ( // This example assumes that an RGB LED is connected to pins 3, 5 and 6 on an Arduino. // Change the values below to use different pins. const ( - redPin = 3 - greenPin = 5 - bluePin = 6 + redPin = machine.D3 + greenPin = machine.D5 + bluePin = machine.D6 ) // cycleColor is just a placeholder until math/rand or some equivalent is working. @@ -28,13 +28,16 @@ func main() { machine.InitPWM() red := machine.PWM{redPin} - red.Configure() + err := red.Configure() + checkError(err, "failed to configure red pin") green := machine.PWM{greenPin} - green.Configure() + err = green.Configure() + checkError(err, "failed to configure green pin") blue := machine.PWM{bluePin} - blue.Configure() + err = blue.Configure() + checkError(err, "failed to configure blue pin") var rc uint8 var gc uint8 = 20 @@ -52,3 +55,10 @@ func main() { time.Sleep(time.Millisecond * 500) } } + +func checkError(err error, msg string) { + if err != nil { + print(msg, ": ", err.Error()) + println() + } +} diff --git a/src/internal/bytealg/bytealg.go b/src/internal/bytealg/bytealg.go new file mode 100644 index 0000000000..f52b507b70 --- /dev/null +++ b/src/internal/bytealg/bytealg.go @@ -0,0 +1,126 @@ +package bytealg + +const ( + // Index can search any valid length of string. + + MaxLen = int(-1) >> 31 + MaxBruteForce = MaxLen +) + +// Compare two byte slices. +// Returns -1 if the first differing byte is lower in a, or 1 if the first differing byte is greater in b. +// If the byte slices are equal, returns 0. +// If the lengths are different and there are no differing bytes, compares based on length. +func Compare(a, b []byte) int { + // Compare for differing bytes. + for i := 0; i < len(a) && i < len(b); i++ { + switch { + case a[0] < b[0]: + return -1 + case a[0] > b[0]: + return 1 + } + } + + // Compare lengths. + switch { + case len(a) > len(b): + return 1 + case len(a) < len(b): + return -1 + default: + return 0 + } +} + +// Count the number of instances of a byte in a slice. +func Count(b []byte, c byte) int { + // Use a simple implementation, as there is no intrinsic that does this like we want. + n := 0 + for _, v := range b { + if v == c { + n++ + } + } + return n +} + +// Count the number of instances of a byte in a string. +func CountString(s string, c byte) int { + // Use a simple implementation, as there is no intrinsic that does this like we want. + // Currently, the compiler does not generate zero-copy byte-string conversions, so this needs to be seperate from Count. + n := 0 + for i := 0; i < len(s); i++ { + if s[i] == c { + n++ + } + } + return n +} + +// Cutover is not reachable in TinyGo, but must exist as it is referenced. +func Cutover(n int) int { + // Setting MaxLen and MaxBruteForce should force a different path to be taken. + // This should never be called. + panic("cutover is unreachable") +} + +// Equal checks if two byte slices are equal. +// It is equivalent to bytes.Equal. +func Equal(a, b []byte) bool { + if len(a) != len(b) { + return false + } + + for i, v := range a { + if v != b[i] { + return false + } + } + + return true +} + +// Index finds the base index of the first instance of the byte sequence b in a. +// If a does not contain b, this returns -1. +func Index(a, b []byte) int { + for i := 0; i <= len(a)-len(b); i++ { + if Equal(a[i:i+len(b)], b) { + return i + } + } + return -1 +} + +// Index finds the index of the first instance of the specified byte in the slice. +// If the byte is not found, this returns -1. +func IndexByte(b []byte, c byte) int { + for i, v := range b { + if v == c { + return i + } + } + return -1 +} + +// Index finds the index of the first instance of the specified byte in the string. +// If the byte is not found, this returns -1. +func IndexByteString(s string, c byte) int { + for i := 0; i < len(s); i++ { + if s[i] == c { + return i + } + } + return -1 +} + +// Index finds the base index of the first instance of a substring in a string. +// If the substring is not found, this returns -1. +func IndexString(str, sub string) int { + for i := 0; i <= len(str)-len(sub); i++ { + if str[i:i+len(sub)] == sub { + return i + } + } + return -1 +} diff --git a/src/internal/task/queue.go b/src/internal/task/queue.go index c86bc596cb..4b0a22bf9c 100644 --- a/src/internal/task/queue.go +++ b/src/internal/task/queue.go @@ -68,8 +68,8 @@ func (s *Stack) Pop() *Task { t := s.top if t != nil { s.top = t.Next + t.Next = nil } - t.Next = nil return t } diff --git a/src/machine/board_arduino.go b/src/machine/board_arduino.go index 92f17dac40..001ba9c70c 100644 --- a/src/machine/board_arduino.go +++ b/src/machine/board_arduino.go @@ -7,21 +7,39 @@ func CPUFrequency() uint32 { return 16000000 } +// Digital pins, marked as plain numbers on the board. +const ( + D0 = PD0 // RX + D1 = PD1 // TX + D2 = PD2 + D3 = PD3 + D4 = PD4 + D5 = PD5 + D6 = PD6 + D7 = PD7 + D8 = PB0 + D9 = PB1 + D10 = PB2 + D11 = PB3 + D12 = PB4 + D13 = PB5 +) + // LED on the Arduino -const LED Pin = 13 +const LED Pin = D13 // ADC on the Arduino const ( - ADC0 Pin = 0 - ADC1 Pin = 1 - ADC2 Pin = 2 - ADC3 Pin = 3 - ADC4 Pin = 4 // Used by TWI for SDA - ADC5 Pin = 5 // Used by TWI for SCL + ADC0 Pin = PC0 + ADC1 Pin = PC1 + ADC2 Pin = PC2 + ADC3 Pin = PC3 + ADC4 Pin = PC4 // Used by TWI for SDA + ADC5 Pin = PC5 // Used by TWI for SCL ) // UART pins const ( - UART_TX_PIN Pin = 1 - UART_RX_PIN Pin = 0 + UART_TX_PIN Pin = PD1 + UART_RX_PIN Pin = PD0 ) diff --git a/src/machine/board_arduino_nano.go b/src/machine/board_arduino_nano.go index 1e96c95f28..d84b23f365 100644 --- a/src/machine/board_arduino_nano.go +++ b/src/machine/board_arduino_nano.go @@ -7,21 +7,39 @@ func CPUFrequency() uint32 { return 16000000 } +// Digital pins. +const ( + D0 = PD0 // RX0 + D1 = PD1 // TX1 + D2 = PD2 + D3 = PD3 + D4 = PD4 + D5 = PD5 + D6 = PD6 + D7 = PD7 + D8 = PB0 + D9 = PB1 + D10 = PB2 + D11 = PB3 + D12 = PB4 + D13 = PB5 +) + // LED on the Arduino -const LED Pin = 13 +const LED Pin = D13 // ADC on the Arduino const ( - ADC0 Pin = 0 - ADC1 Pin = 1 - ADC2 Pin = 2 - ADC3 Pin = 3 - ADC4 Pin = 4 // Used by TWI for SDA - ADC5 Pin = 5 // Used by TWI for SCL + ADC0 Pin = PC0 + ADC1 Pin = PC1 + ADC2 Pin = PC2 + ADC3 Pin = PC3 + ADC4 Pin = PC4 // Used by TWI for SDA + ADC5 Pin = PC5 // Used by TWI for SCL ) // UART pins const ( - UART_TX_PIN Pin = 1 - UART_RX_PIN Pin = 0 + UART_TX_PIN Pin = PD1 + UART_RX_PIN Pin = PD0 ) diff --git a/src/machine/board_atmega328p.go b/src/machine/board_atmega328p.go new file mode 100644 index 0000000000..030c78fe99 --- /dev/null +++ b/src/machine/board_atmega328p.go @@ -0,0 +1,37 @@ +// +build avr,atmega328p arduino arduino_nano + +package machine + +const ( + // Note: start at port B because there is no port A. + portB Pin = iota * 8 + portC + portD +) + +const ( + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 +) diff --git a/src/machine/board_feather-m4.go b/src/machine/board_feather-m4.go index fee8c2e4ad..511834e23d 100644 --- a/src/machine/board_feather-m4.go +++ b/src/machine/board_feather-m4.go @@ -1,9 +1,7 @@ -// +build sam,atsamd51,feather_m4 +// +build feather_m4 package machine -import "device/sam" - // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -47,13 +45,11 @@ const ( USBCDC_DP_PIN = PA25 ) -// UART1 pins const ( UART_TX_PIN = D1 UART_RX_PIN = D0 ) -// UART2 pins const ( UART2_TX_PIN = A4 UART2_RX_PIN = A5 @@ -65,14 +61,6 @@ const ( SCL_PIN = D21 // SCL: SERCOM2/PAD[1] ) -// I2C on the Feather M4. -var ( - I2C0 = I2C{ - Bus: sam.SERCOM2_I2CM, - SERCOM: 2, - } -) - // SPI pins const ( SPI0_SCK_PIN = D25 // SCK: SERCOM1/PAD[1] @@ -80,14 +68,6 @@ const ( SPI0_MISO_PIN = D23 // MISO: SERCOM1/PAD[2] ) -// SPI on the Feather M4. -var ( - SPI0 = SPI{ - Bus: sam.SERCOM1_SPIM, - SERCOM: 1, - } -) - // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Feather M4" diff --git a/src/machine/board_feather-m4_baremetal.go b/src/machine/board_feather-m4_baremetal.go new file mode 100644 index 0000000000..8492173994 --- /dev/null +++ b/src/machine/board_feather-m4_baremetal.go @@ -0,0 +1,43 @@ +// +build sam,atsamd51,feather_m4 + +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM5_USART_INT, + SERCOM: 5, + } + + UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM0_USART_INT, + SERCOM: 0, + } +) + +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt) +} + +// I2C on the Feather M4. +var ( + I2C0 = I2C{ + Bus: sam.SERCOM2_I2CM, + SERCOM: 2, + } +) + +// SPI on the Feather M4. +var ( + SPI0 = SPI{ + Bus: sam.SERCOM1_SPIM, + SERCOM: 1, + } +) diff --git a/src/machine/board_itsybitsy-m4.go b/src/machine/board_itsybitsy-m4.go index c6d3390b0d..43c9a4ba50 100644 --- a/src/machine/board_itsybitsy-m4.go +++ b/src/machine/board_itsybitsy-m4.go @@ -1,9 +1,7 @@ -// +build sam,atsamd51,itsybitsy_m4 +// +build itsybitsy_m4 package machine -import "device/sam" - // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -51,30 +49,17 @@ const ( UART_RX_PIN = D0 ) -// UART1 var is on SERCOM3, defined in atsamd51.go - -// UART2 pins const ( UART2_TX_PIN = A4 UART2_RX_PIN = D2 ) -// UART2 var is on SERCOM0, defined in atsamd51.go - // I2C pins const ( SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] ) -// I2C on the ItsyBitsy M4. -var ( - I2C0 = I2C{ - Bus: sam.SERCOM2_I2CM, - SERCOM: 2, - } -) - // SPI pins const ( SPI0_SCK_PIN = PA01 // SCK: SERCOM1/PAD[1] @@ -82,14 +67,6 @@ const ( SPI0_MISO_PIN = PB23 // MISO: SERCOM1/PAD[3] ) -// SPI on the ItsyBitsy M4. -var ( - SPI0 = SPI{ - Bus: sam.SERCOM1_SPIM, - SERCOM: 1, - } -) - // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit ItsyBitsy M4" diff --git a/src/machine/board_itsybitsy-m4_baremetal.go b/src/machine/board_itsybitsy-m4_baremetal.go new file mode 100644 index 0000000000..88097bca85 --- /dev/null +++ b/src/machine/board_itsybitsy-m4_baremetal.go @@ -0,0 +1,43 @@ +// +build sam,atsamd51,itsybitsy_m4 + +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM3_USART_INT, + SERCOM: 3, + } + + UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM0_USART_INT, + SERCOM: 0, + } +) + +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt) +} + +// I2C on the ItsyBitsy M4. +var ( + I2C0 = I2C{ + Bus: sam.SERCOM2_I2CM, + SERCOM: 2, + } +) + +// SPI on the ItsyBitsy M4. +var ( + SPI0 = SPI{ + Bus: sam.SERCOM1_SPIM, + SERCOM: 1, + } +) diff --git a/src/machine/board_metro-m4-airlift.go b/src/machine/board_metro-m4-airlift.go index a62c4c2443..c1f49a1906 100644 --- a/src/machine/board_metro-m4-airlift.go +++ b/src/machine/board_metro-m4-airlift.go @@ -1,9 +1,7 @@ -// +build sam,atsamd51,metro_m4_airlift +// +build metro_m4_airlift package machine -import "device/sam" - // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -53,7 +51,10 @@ const ( UART_RX_PIN = D0 ) -// Note: UART1 is on SERCOM3, defined in machine_atsamd51.go +const ( + UART2_TX_PIN = PA04 + UART2_RX_PIN = PA07 +) const ( NINA_CS = PA15 @@ -66,23 +67,12 @@ const ( NINA_RTS = PB23 ) -// UART2 is on SERCOM0, defined in machine_atsamd51.go, and connects to the -// onboard ESP32-WROOM chip. - // I2C pins const ( SDA_PIN = PB02 // SDA: SERCOM5/PAD[0] SCL_PIN = PB03 // SCL: SERCOM5/PAD[1] ) -// I2C on the Metro M4. -var ( - I2C0 = I2C{ - Bus: sam.SERCOM5_I2CM, - SERCOM: 5, - } -) - // SPI pins const ( SPI0_SCK_PIN = PA13 // SCK: SERCOM2/PAD[1] @@ -94,29 +84,12 @@ const ( NINA_SCK = SPI0_SCK_PIN ) -// SPI on the Metro M4. -var ( - SPI0 = SPI{ - Bus: sam.SERCOM2_SPIM, - SERCOM: 2, - } - NINA_SPI = SPI0 -) - const ( SPI1_SCK_PIN = D12 // MISO: SERCOM1/PAD[1] SPI1_MOSI_PIN = D11 // MOSI: SERCOM1/PAD[3] SPI1_MISO_PIN = D13 // SCK: SERCOM1/PAD[0] ) -// SPI1 on the Metro M4 on pins 11,12,13 -var ( - SPI1 = SPI{ - Bus: sam.SERCOM1_SPIM, - SERCOM: 1, - } -) - // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit Metro M4 Airlift Lite" diff --git a/src/machine/board_metro-m4-airlift_baremetal.go b/src/machine/board_metro-m4-airlift_baremetal.go new file mode 100644 index 0000000000..b04f13cc3b --- /dev/null +++ b/src/machine/board_metro-m4-airlift_baremetal.go @@ -0,0 +1,52 @@ +// +build sam,atsamd51,metro_m4_airlift + +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM3_USART_INT, + SERCOM: 3, + } + + UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM0_USART_INT, + SERCOM: 0, + } +) + +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt) +} + +// I2C on the Metro M4. +var ( + I2C0 = I2C{ + Bus: sam.SERCOM5_I2CM, + SERCOM: 5, + } +) + +// SPI on the Metro M4. +var ( + SPI0 = SPI{ + Bus: sam.SERCOM2_SPIM, + SERCOM: 2, + } + NINA_SPI = SPI0 +) + +// SPI1 on the Metro M4 on pins 11,12,13 +var ( + SPI1 = SPI{ + Bus: sam.SERCOM1_SPIM, + SERCOM: 1, + } +) diff --git a/src/machine/board_pybadge.go b/src/machine/board_pybadge.go index 84ccb7ba8f..2b830197a7 100644 --- a/src/machine/board_pybadge.go +++ b/src/machine/board_pybadge.go @@ -1,9 +1,7 @@ -// +build sam,atsamd51,pybadge +// +build pybadge package machine -import "device/sam" - // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -80,30 +78,17 @@ const ( UART_RX_PIN = D0 ) -// UART1 var is on SERCOM3, defined in atsamd51.go - -// UART2 pins const ( UART2_TX_PIN = A4 UART2_RX_PIN = A5 ) -// UART2 var is on SERCOM0, defined in atsamd51.go - // I2C pins const ( SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] ) -// I2C on the ItsyBitsy M4. -var ( - I2C0 = I2C{ - Bus: sam.SERCOM2_I2CM, - SERCOM: 2, - } -) - // SPI pins const ( SPI0_SCK_PIN = PA17 // SCK: SERCOM1/PAD[1] @@ -111,14 +96,6 @@ const ( SPI0_MISO_PIN = PB22 // MISO: SERCOM1/PAD[2] ) -// SPI on the PyBadge. -var ( - SPI0 = SPI{ - Bus: sam.SERCOM1_SPIM, - SERCOM: 1, - } -) - // TFT SPI pins const ( SPI1_SCK_PIN = PB13 // SCK: SERCOM4/PAD[1] @@ -126,14 +103,6 @@ const ( SPI1_MISO_PIN = NoPin ) -// TFT SPI on the PyBadge. -var ( - SPI1 = SPI{ - Bus: sam.SERCOM4_SPIM, - SERCOM: 4, - } -) - // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit pyBadge M4" diff --git a/src/machine/board_pybadge_baremetal.go b/src/machine/board_pybadge_baremetal.go new file mode 100644 index 0000000000..e3fd82e6f9 --- /dev/null +++ b/src/machine/board_pybadge_baremetal.go @@ -0,0 +1,51 @@ +// +build sam,atsamd51,pybadge + +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM5_USART_INT, + SERCOM: 5, + } + + UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM0_USART_INT, + SERCOM: 0, + } +) + +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM5_2, UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt) +} + +// I2C on the ItsyBitsy M4. +var ( + I2C0 = I2C{ + Bus: sam.SERCOM2_I2CM, + SERCOM: 2, + } +) + +// SPI on the PyBadge. +var ( + SPI0 = SPI{ + Bus: sam.SERCOM1_SPIM, + SERCOM: 1, + } +) + +// TFT SPI on the PyBadge. +var ( + SPI1 = SPI{ + Bus: sam.SERCOM4_SPIM, + SERCOM: 4, + } +) diff --git a/src/machine/board_pygamer.go b/src/machine/board_pygamer.go new file mode 100644 index 0000000000..6719f0d069 --- /dev/null +++ b/src/machine/board_pygamer.go @@ -0,0 +1,147 @@ +// +build sam,atsamd51,pygamer + +package machine + +import "device/sam" + +// used to reset into bootloader +const RESET_MAGIC_VALUE = 0xf01669ef + +// GPIO Pins +const ( + D0 = PB17 // UART0 RX/PWM available + D1 = PB16 // UART0 TX/PWM available + D2 = PB03 + D3 = PB02 + D4 = PA14 // PWM available + D5 = PA16 // PWM available + D6 = PA18 // PWM available + D7 = PB14 // CS for microSD card slot + D8 = PA15 // built-in neopixel + D9 = PA19 // PWM available + D10 = PA20 // can be used for PWM or UART1 TX + D11 = PA21 // can be used for PWM or UART1 RX + D12 = PA22 // PWM available + D13 = PA23 // PWM available +) + +// Analog pins +const ( + A0 = PA02 // ADC/AIN[0] + A1 = PA05 // ADC/AIN[2] + A2 = PB08 // ADC/AIN[3] + A3 = PB09 // ADC/AIN[4] + A4 = PA04 // ADC/AIN[5] + A5 = PA06 // ADC/AIN[6] + A6 = PB01 // ADC/AIN[12]/VMEAS + A7 = PB04 // ADC/AIN[6]/LIGHT + A8 = D2 // ADC/AIN[14] + A9 = D3 // ADC/AIN[15] +) + +const ( + LED = D13 + NEOPIXELS = D8 + + SD_CS = D7 + + LIGHTSENSOR = A7 + + BUTTON_LATCH = PB00 + BUTTON_OUT = PB30 + BUTTON_CLK = PB31 + + JOYY = PB06 + JOYX = PB07 + + TFT_DC = PB05 + TFT_CS = PB12 + TFT_RST = PA00 + TFT_LITE = PA01 + + SPEAKER_ENABLE = PA27 +) + +const ( + BUTTON_SELECT_MASK = 16 + BUTTON_START_MASK = 32 + BUTTON_A_MASK = 64 + BUTTON_B_MASK = 128 +) + +// UART0 aka USBCDC pins +const ( + USBCDC_DM_PIN = PA24 + USBCDC_DP_PIN = PA25 +) + +// UART1 pins +const ( + UART_TX_PIN = D1 + UART_RX_PIN = D0 +) + +// UART1 var is on SERCOM3, defined in atsamd51.go + +// UART2 pins +const ( + UART2_TX_PIN = A4 + UART2_RX_PIN = A5 +) + +// UART2 var is on SERCOM0, defined in atsamd51.go + +// I2C pins +const ( + SDA_PIN = PA12 // SDA: SERCOM2/PAD[0] + SCL_PIN = PA13 // SCL: SERCOM2/PAD[1] +) + +// I2C on the PyGamer. +var ( + I2C0 = I2C{ + Bus: sam.SERCOM2_I2CM, + SERCOM: 2, + } +) + +// SPI pins +const ( + SPI0_SCK_PIN = PA17 // SCK: SERCOM1/PAD[1] + SPI0_MOSI_PIN = PB23 // MOSI: SERCOM1/PAD[3] + SPI0_MISO_PIN = PB22 // MISO: SERCOM1/PAD[2] +) + +// SPI on the PyGamer. +var ( + SPI0 = SPI{ + Bus: sam.SERCOM1_SPIM, + SERCOM: 1, + } +) + +// TFT SPI pins +const ( + SPI1_SCK_PIN = PB13 // SCK: SERCOM4/PAD[1] + SPI1_MOSI_PIN = PB15 // MOSI: SERCOM4/PAD[3] + SPI1_MISO_PIN = NoPin +) + +// TFT SPI on the PyGamer. +var ( + SPI1 = SPI{ + Bus: sam.SERCOM4_SPIM, + SERCOM: 4, + } +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Adafruit pyGamer M4" + usb_STRING_MANUFACTURER = "Adafruit" +) + +var ( + usb_VID uint16 = 0x239A + usb_PID uint16 = 0x8033 +) diff --git a/src/machine/board_pyportal.go b/src/machine/board_pyportal.go index 7000631ef1..2eaf0b880b 100644 --- a/src/machine/board_pyportal.go +++ b/src/machine/board_pyportal.go @@ -1,11 +1,7 @@ -// +build sam,atsamd51,pyportal +// +build pyportal package machine -import ( - "device/sam" -) - // used to reset into bootloader const RESET_MAGIC_VALUE = 0xf01669ef @@ -104,10 +100,10 @@ const ( USBCDC_DP_PIN = PA25 ) -// TODO: add configuration for UART on SERCOM4 that is connected to TX/RX of ESP32 +// UART1 aka NINA_TX/NINA_RX const ( - UART_TX_PIN = NoPin - UART_RX_PIN = NoPin + UART_TX_PIN = D1 + UART_RX_PIN = D0 ) // I2C pins @@ -116,14 +112,6 @@ const ( SCL_PIN = PB03 // SCL: SERCOM2/PAD[1] ) -// I2C on the PyPortal. -var ( - I2C0 = I2C{ - Bus: sam.SERCOM5_I2CM, - SERCOM: 5, - } -) - // SPI pins const ( SPI0_SCK_PIN = PA13 // SCK: SERCOM1/PAD[1] @@ -135,15 +123,6 @@ const ( NINA_SCK = SPI0_SCK_PIN ) -// SPI on the PyPortal. -var ( - SPI0 = SPI{ - Bus: sam.SERCOM2_SPIM, - SERCOM: 2, - } - NINA_SPI = SPI0 -) - // USB CDC identifiers const ( usb_STRING_PRODUCT = "Adafruit PyPortal M4" diff --git a/src/machine/board_pyportal_baremetal.go b/src/machine/board_pyportal_baremetal.go new file mode 100644 index 0000000000..24ce6d38e1 --- /dev/null +++ b/src/machine/board_pyportal_baremetal.go @@ -0,0 +1,37 @@ +// +build sam,atsamd51,pyportal + +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM4_USART_INT, + SERCOM: 4, + } +) + +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM4_2, UART1.handleInterrupt) +} + +// I2C on the PyPortal. +var ( + I2C0 = I2C{ + Bus: sam.SERCOM5_I2CM, + SERCOM: 5, + } +) + +// SPI on the PyPortal. +var ( + SPI0 = SPI{ + Bus: sam.SERCOM2_SPIM, + SERCOM: 2, + } + NINA_SPI = SPI0 +) diff --git a/src/machine/board_wioterminal.go b/src/machine/board_wioterminal.go new file mode 100644 index 0000000000..14f0a494fa --- /dev/null +++ b/src/machine/board_wioterminal.go @@ -0,0 +1,392 @@ +// +build wioterminal + +package machine + +// used to reset into bootloader +const RESET_MAGIC_VALUE = 0xf01669ef + +const ( + ADC0 = A0 + ADC1 = A1 + ADC2 = A2 + ADC3 = A3 + ADC4 = A4 + ADC5 = A5 + ADC6 = A6 + ADC7 = A7 + ADC8 = A8 + + LED = PIN_LED + BUTTON = BUTTON_1 +) + +const ( + // https://github.com/Seeed-Studio/ArduinoCore-samd/blob/master/variants/wio_terminal/variant.h + + // LEDs + PIN_LED_13 = PA15 + PIN_LED_RXL = PA15 + PIN_LED_TXL = PA15 + PIN_LED = PIN_LED_13 + PIN_LED2 = PIN_LED_RXL + PIN_LED3 = PIN_LED_TXL + LED_BUILTIN = PIN_LED_13 + PIN_NEOPIXEL = PA15 + + //Digital PINs + D0 = PB08 + D1 = PB09 + D2 = PA07 + D3 = PB04 + D4 = PB05 + D5 = PB06 + D6 = PA04 + D7 = PB07 + D8 = PA06 + + //Analog PINs + A0 = PB08 // ADC/AIN[0] + A1 = PB09 // ADC/AIN[2] + A2 = PA07 // ADC/AIN[3] + A3 = PB04 // ADC/AIN[4] + A4 = PB05 // ADC/AIN[5] + A5 = PB06 // ADC/AIN[10] + A6 = PA04 // ADC/AIN[10] + A7 = PB07 // ADC/AIN[10] + A8 = PA06 // ADC/AIN[10] + + // 3.3V || 5V + // BCM2 || 5V + // BCM3 || GND + // BCM4 || BCM14 + // GND || BCM15 + // BCM17 || BCM18 + // BCM27 || GND + // BCM22 || BCM23 + // GND || BCM24 + // BCM10 || GND + // BCM9 || BCM25 + // BCM11 || BCM8 + // GND || BCM7 + // BCM0 || BCM1 + // BCM5 || GND + // BCM6 || BCM12 + // BCM13 || GND + // BCM19 || BCM16 + // BCM26 || BCM20 + // GND || BCM21 + + //PIN DEFINE FOR RPI + BCM0 = PA13 // I2C Wire1 + BCM1 = PA12 // I2C Wire1 + BCM2 = PA17 // I2C Wire2 + BCM3 = PA16 // I2C Wire2 + BCM4 = PB14 // GCLK + BCM5 = PB12 // GCLK + BCM6 = PB13 // GCLK + BCM7 = PA05 // DAC1 + BCM8 = PB01 // SPI SS + BCM9 = PB00 // SPI MISO + BCM10 = PB02 // SPI MOSI + BCM11 = PB03 // SPI SCK + BCM12 = PB06 + BCM13 = PA07 + BCM14 = PB27 // UART Serial1 + BCM15 = PB26 // UART Serial1 + BCM16 = PB07 + BCM17 = PA02 // DAC0 + BCM18 = PB28 // FPC Digital & AD pins + BCM19 = PA20 // WIO_IR + BCM20 = PA21 // I2S SDO + BCM21 = PA22 // I2S SDI + BCM22 = PB09 + BCM23 = PA07 + BCM24 = PB04 + BCM25 = PB05 + BCM26 = PA06 + BCM27 = PB08 + + // FPC NEW DEFINE + FPC1 = PB28 // FPC Digital & AD pins + FPC2 = PB17 + FPC3 = PB29 + FPC4 = PA14 + FPC5 = PC01 + FPC6 = PC02 + FPC7 = PC03 + FPC8 = PC04 + FPC9 = PC31 + FPC10 = PD00 + + // RPI Analog RPIs + RPI_A0 = PB08 + RPI_A1 = PB09 + RPI_A2 = PA07 + RPI_A3 = PB04 + RPI_A4 = PB05 + RPI_A5 = PB06 + RPI_A6 = PA04 + RPI_A7 = PB07 + RPI_A8 = PA06 + + PIN_DAC0 = PA02 + PIN_DAC1 = PA05 + + DAC0 = PIN_DAC0 + DAC1 = PIN_DAC1 + + // FPO Analog RPIs + //FPC_A7 = FPC_D7 + //FPC_A8 = FPC_D8 + //FPC_A9 = FPC_D9 + //FPC_A11 = FPC_D11 + //FPC_A12 = FPC_D12 + //FPC_A13 = FPC_D13 + + // USB + PIN_USB_DM = PA24 + PIN_USB_DP = PA25 + PIN_USB_HOST_ENABLE = PA27 + + // BUTTON + BUTTON_1 = PC26 + BUTTON_2 = PC27 + BUTTON_3 = PC28 + WIO_KEY_A = PC26 + WIO_KEY_B = PC27 + WIO_KEY_C = PC28 + + // SWITCH + SWITCH_X = PD20 + SWITCH_Y = PD12 + SWITCH_Z = PD09 + SWITCH_B = PD08 + SWITCH_U = PD10 + + WIO_5S_UP = PD20 + WIO_5S_LEFT = PD12 + WIO_5S_RIGHT = PD09 + WIO_5S_DOWN = PD08 + WIO_5S_PRESS = PD10 + + // IRQ0 : RTL8720D + IRQ0 = PC20 + + // BUZZER_CTR + BUZZER_CTR = PD11 + WIO_BUZZER = PD11 + + // MIC_INPUT + MIC_INPUT = PC30 + WIO_MIC = PC30 + + // GCLK + GCLK0 = PB14 + GCLK1 = PB12 + GCLK2 = PB13 + + // Serial interfaces + // Serial1 + PIN_SERIAL1_RX = PB27 + PIN_SERIAL1_TX = PB26 + + // Serial2 : RTL8720D + PIN_SERIAL2_RX = PC23 + PIN_SERIAL2_TX = PC22 + + // Wire Interfaces + // I2C Wire2 + // I2C1 + PIN_WIRE_SDA = PA17 + PIN_WIRE_SCL = PA16 + SDA = PIN_WIRE_SDA + SCL = PIN_WIRE_SCL + + // I2C Wire1 + // I2C0 : LIS3DHTR and ATECC608 + PIN_WIRE1_SDA = PA13 + PIN_WIRE1_SCL = PA12 + + SDA1 = PIN_WIRE1_SDA + SCL1 = PIN_WIRE1_SCL + + PIN_GYROSCOPE_WIRE_SDA = PIN_WIRE1_SDA + PIN_GYROSCOPE_WIRE_SCL = PIN_WIRE1_SCL + GYROSCOPE_INT1 = PC21 + + WIO_LIS3DH_SDA = PIN_WIRE1_SDA + WIO_LIS3DH_SCL = PIN_WIRE1_SCL + WIO_LIS3DH_INT = PC21 + + // SPI + PIN_SPI_MISO = PB00 + PIN_SPI_MOSI = PB02 + PIN_SPI_SCK = PB03 + PIN_SPI_SS = PB01 + + SS = PIN_SPI_SS + MOSI = PIN_SPI_MOSI + MISO = PIN_SPI_MISO + SCK = PIN_SPI_SCK + + // SPI1 RTL8720D_SPI + PIN_SPI1_MISO = PC24 + PIN_SPI1_MOSI = PB24 + PIN_SPI1_SCK = PB25 + PIN_SPI1_SS = PC25 + + SS1 = PIN_SPI1_SS + MOSI1 = PIN_SPI1_MOSI + MISO1 = PIN_SPI1_MISO + SCK1 = PIN_SPI1_SCK + + // SPI2 SD_SPI + PIN_SPI2_MISO = PC18 + PIN_SPI2_MOSI = PC16 + PIN_SPI2_SCK = PC17 + PIN_SPI2_SS = PC19 + + SS2 = PIN_SPI2_SS + MOSI2 = PIN_SPI2_MOSI + MISO2 = PIN_SPI2_MISO + SCK2 = PIN_SPI2_SCK + + // SPI3 LCD_SPI + PIN_SPI3_MISO = PB18 + PIN_SPI3_MOSI = PB19 + PIN_SPI3_SCK = PB20 + PIN_SPI3_SS = PB21 + + SS3 = PIN_SPI3_SS + MOSI3 = PIN_SPI3_MOSI + MISO3 = PIN_SPI3_MISO + SCK3 = PIN_SPI3_SCK + + // Needed for SD library + SDCARD_MISO_PIN = PIN_SPI2_MISO + SDCARD_MOSI_PIN = PIN_SPI2_MOSI + SDCARD_SCK_PIN = PIN_SPI2_SCK + SDCARD_SS_PIN = PIN_SPI2_SS + SDCARD_DET_PIN = PD21 + + LCD_MISO_PIN = PIN_SPI3_MISO + LCD_MOSI_PIN = PIN_SPI3_MOSI + LCD_SCK_PIN = PIN_SPI3_SCK + LCD_SS_PIN = PIN_SPI3_SS + LCD_DC = PC06 + LCD_RESET = PC07 + LCD_BACKLIGHT = PC05 + + // 4 WIRE LCD TOUCH + LCD_XL = PC10 + LCD_YU = PC11 + LCD_XR = PC12 + LCD_YD = PC13 + + // Needed for RTL8720D + RTL8720D_MISO_PIN = PIN_SPI1_MISO + RTL8720D_MOSI_PIN = PIN_SPI1_MOSI + RTL8720D_SCK_PIN = PIN_SPI1_SCK + RTL8720D_SS_PIN = PIN_SPI1_SS + + //QSPI Pins + PIN_QSPI_IO0 = PA08 + PIN_QSPI_IO1 = PA09 + PIN_QSPI_IO2 = PA10 + PIN_QSPI_IO3 = PA11 + PIN_QSPI_SCK = PB10 + PIN_QSPI_CS = PB11 + + // I2S Interfaces + PIN_I2S_FS = PA20 + PIN_I2S_SCK = PB16 + PIN_I2S_SDO = PA22 + PIN_I2S_SDI = PA21 + + I2S_LRCLK = PA20 + I2S_BLCK = PB16 + I2S_SDOUT = PA22 + I2S_SDIN = PA21 + + // RTL8720D Interfaces + RTL8720D_CHIP_PU = PA18 + RTL8720D_GPIO0 = PA19 // SYNC + + // SWD + SWDCLK = PA30 + SWDIO = PA31 + SWO = PB30 + + // light sensor + WIO_LIGHT = PD01 + + // ir sensor + WIO_IR = PB31 + + // OUTPUT_CTR + OUTPUT_CTR_5V = PC14 + OUTPUT_CTR_3V3 = PC15 +) + +// UART0 aka USBCDC pins +const ( + USBCDC_DM_PIN = PIN_USB_DM + USBCDC_DP_PIN = PIN_USB_DP +) + +// UART1 pins +const ( + UART_TX_PIN = PIN_SERIAL1_TX + UART_RX_PIN = PIN_SERIAL1_RX +) + +// UART2 pins RTL8720D +const ( + UART2_TX_PIN = PIN_SERIAL2_TX + UART2_RX_PIN = PIN_SERIAL2_RX +) + +// I2C pins +const ( + SDA0_PIN = PIN_WIRE_SDA // SDA: SERCOM3/PAD[0] + SCL0_PIN = PIN_WIRE_SCL // SCL: SERCOM3/PAD[1] + + SDA1_PIN = PIN_WIRE1_SDA // SDA: SERCOM4/PAD[0] + SCL1_PIN = PIN_WIRE1_SCL // SCL: SERCOM4/PAD[1] + + SDA_PIN = SDA0_PIN + SCL_PIN = SCL0_PIN +) + +// SPI pins +const ( + SPI0_SCK_PIN = SCK // SCK: SERCOM5/PAD[1] + SPI0_MOSI_PIN = MOSI // MOSI: SERCOM5/PAD[0] + SPI0_MISO_PIN = MISO // MISO: SERCOM5/PAD[2] + + // RTL8720D + SPI1_SCK_PIN = SCK1 // SCK: SERCOM0/PAD[1] + SPI1_MOSI_PIN = MOSI1 // MOSI: SERCOM0/PAD[0] + SPI1_MISO_PIN = MISO1 // MISO: SERCOM0/PAD[2] + + // SD + SPI2_SCK_PIN = SCK2 // SCK: SERCOM6/PAD[1] + SPI2_MOSI_PIN = MOSI2 // MOSI: SERCOM6/PAD[0] + SPI2_MISO_PIN = MISO2 // MISO: SERCOM6/PAD[2] + + // LCD + SPI3_SCK_PIN = SCK3 // SCK: SERCOM7/PAD[1] + SPI3_MOSI_PIN = MOSI3 // MOSI: SERCOM7/PAD[3] + SPI3_MISO_PIN = MISO3 // MISO: SERCOM7/PAD[2] +) + +// USB CDC identifiers +const ( + usb_STRING_PRODUCT = "Seeed Wio Terminal" + usb_STRING_MANUFACTURER = "Seeed" +) + +var ( + usb_VID uint16 = 0x2886 + usb_PID uint16 = 0x802D +) diff --git a/src/machine/board_wioterminal_baremetal.go b/src/machine/board_wioterminal_baremetal.go new file mode 100644 index 0000000000..8fbc025dce --- /dev/null +++ b/src/machine/board_wioterminal_baremetal.go @@ -0,0 +1,67 @@ +// +build sam,atsamd51,wioterminal + +package machine + +import ( + "device/sam" + "runtime/interrupt" +) + +var ( + UART1 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM2_USART_INT, + SERCOM: 2, + } + + // RTL8720D + UART2 = UART{ + Buffer: NewRingBuffer(), + Bus: sam.SERCOM1_USART_INT, + SERCOM: 1, + } +) + +func init() { + UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM2_2, UART1.handleInterrupt) + UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM1_2, UART2.handleInterrupt) +} + +// I2C on the Wio Terminal +var ( + I2C0 = I2C{ + Bus: sam.SERCOM4_I2CM, + SERCOM: 4, + } + + I2C1 = I2C{ + Bus: sam.SERCOM4_I2CM, + SERCOM: 4, + } +) + +// SPI on the Wio Terminal +var ( + SPI0 = SPI{ + Bus: sam.SERCOM5_SPIM, + SERCOM: 5, + } + + // RTL8720D + SPI1 = SPI{ + Bus: sam.SERCOM0_SPIM, + SERCOM: 0, + } + + // SD + SPI2 = SPI{ + Bus: sam.SERCOM6_SPIM, + SERCOM: 6, + } + + // LCD + SPI3 = SPI{ + Bus: sam.SERCOM7_SPIM, + SERCOM: 7, + } +) diff --git a/src/machine/machine.go b/src/machine/machine.go index a7863d8ff6..e4e1dcb70b 100644 --- a/src/machine/machine.go +++ b/src/machine/machine.go @@ -3,10 +3,11 @@ package machine import "errors" var ( - ErrInvalidInputPin = errors.New("machine: invalid input pin") - ErrInvalidOutputPin = errors.New("machine: invalid output pin") - ErrInvalidClockPin = errors.New("machine: invalid clock pin") - ErrInvalidDataPin = errors.New("machine: invalid data pin") + ErrInvalidInputPin = errors.New("machine: invalid input pin") + ErrInvalidOutputPin = errors.New("machine: invalid output pin") + ErrInvalidClockPin = errors.New("machine: invalid clock pin") + ErrInvalidDataPin = errors.New("machine: invalid data pin") + ErrNoPinChangeChannel = errors.New("machine: no channel available for pin interrupt") ) type PinConfig struct { diff --git a/src/machine/machine_atmega.go b/src/machine/machine_atmega.go index cac2ce6ecd..147d80ffe9 100644 --- a/src/machine/machine_atmega.go +++ b/src/machine/machine_atmega.go @@ -7,72 +7,6 @@ import ( "runtime/interrupt" ) -// InitPWM initializes the registers needed for PWM. -func InitPWM() { - // use waveform generation - avr.TCCR0A.SetBits(avr.TCCR0A_WGM00) - - // set timer 0 prescale factor to 64 - avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00) - - // set timer 1 prescale factor to 64 - avr.TCCR1B.SetBits(avr.TCCR1B_CS11) - - // put timer 1 in 8-bit phase correct pwm mode - avr.TCCR1A.SetBits(avr.TCCR1A_WGM10) - - // set timer 2 prescale factor to 64 - avr.TCCR2B.SetBits(avr.TCCR2B_CS22) - - // configure timer 2 for phase correct pwm (8-bit) - avr.TCCR2A.SetBits(avr.TCCR2A_WGM20) -} - -// Configure configures a PWM pin for output. -func (pwm PWM) Configure() { - if pwm.Pin < 8 { - avr.DDRD.SetBits(1 << uint8(pwm.Pin)) - } else { - avr.DDRB.SetBits(1 << uint8(pwm.Pin-8)) - } -} - -// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a -// 8-bit value ranging from 0 to 255. -func (pwm PWM) Set(value uint16) { - value8 := uint8(value >> 8) - switch pwm.Pin { - case 3: - // connect pwm to pin on timer 2, channel B - avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) - avr.OCR2B.Set(value8) // set pwm duty - case 5: - // connect pwm to pin on timer 0, channel B - avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) - avr.OCR0B.Set(value8) // set pwm duty - case 6: - // connect pwm to pin on timer 0, channel A - avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) - avr.OCR0A.Set(value8) // set pwm duty - case 9: - // connect pwm to pin on timer 1, channel A - avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) - // this is a 16-bit value, but we only currently allow the low order bits to be set - avr.OCR1AL.Set(value8) // set pwm duty - case 10: - // connect pwm to pin on timer 1, channel B - avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) - // this is a 16-bit value, but we only currently allow the low order bits to be set - avr.OCR1BL.Set(value8) // set pwm duty - case 11: - // connect pwm to pin on timer 2, channel A - avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) - avr.OCR2A.Set(value8) // set pwm duty - default: - panic("Invalid PWM pin") - } -} - // I2CConfig is used to store config info for I2C. type I2CConfig struct { Frequency uint32 diff --git a/src/machine/machine_atmega1284p.go b/src/machine/machine_atmega1284p.go index bc25f6743d..c80b0e3a8d 100644 --- a/src/machine/machine_atmega1284p.go +++ b/src/machine/machine_atmega1284p.go @@ -14,12 +14,58 @@ func CPUFrequency() uint32 { return 20000000 } +const ( + portA Pin = iota * 8 + portB + portC + portD +) + +const ( + PA0 = portA + 0 + PA1 = portA + 1 + PA2 = portA + 2 + PA3 = portA + 3 + PA4 = portA + 4 + PA5 = portA + 5 + PA6 = portA + 6 + PA7 = portA + 7 + PB0 = portB + 0 + PB1 = portB + 1 + PB2 = portB + 2 + PB3 = portB + 3 + PB4 = portB + 4 + PB5 = portB + 5 + PB6 = portB + 6 + PB7 = portB + 7 + PC0 = portC + 0 + PC1 = portC + 1 + PC2 = portC + 2 + PC3 = portC + 3 + PC4 = portC + 4 + PC5 = portC + 5 + PC6 = portC + 6 + PC7 = portC + 7 + PD0 = portD + 0 + PD1 = portD + 1 + PD2 = portD + 2 + PD3 = portD + 3 + PD4 = portD + 4 + PD5 = portD + 5 + PD6 = portD + 6 + PD7 = portD + 7 +) + +// getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { - if p < 8 { - return avr.PORTD, 1 << uint8(p) - } else if p < 14 { - return avr.PORTB, 1 << uint8(p-8) - } else { - return avr.PORTC, 1 << uint8(p-14) + switch { + case p >= PA0 && p <= PA7: + return avr.PORTA, 1 << uint8(p-portA) + case p >= PB0 && p <= PB7: + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: + return avr.PORTC, 1 << uint8(p-portC) + default: + return avr.PORTD, 1 << uint8(p-portD) } } diff --git a/src/machine/machine_atmega2560.go b/src/machine/machine_atmega2560.go index e4c3f01077..74a83f02f8 100644 --- a/src/machine/machine_atmega2560.go +++ b/src/machine/machine_atmega2560.go @@ -96,53 +96,32 @@ const ( PL7 = portE + 7 ) -// Configure sets the pin to input or output. -func (p Pin) Configure(config PinConfig) { - register, _, mask := p.getRegisterPortMask() - if config.Mode == PinOutput { // set output bit - register.SetBits(mask) - } else { // configure input: clear output bit - register.ClearBits(mask) - } -} - -// Get returns the current value of a GPIO pin. -func (p Pin) Get() bool { - _, port, mask := p.getRegisterPortMask() - return (port.Get() & mask) > 0 -} - +// getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { - _, port, mask := p.getRegisterPortMask() - return port, mask -} - -// getRegisterPortMask returns the register, port, and mask for the pin -func (p Pin) getRegisterPortMask() (*volatile.Register8, *volatile.Register8, uint8) { switch { case p >= PA0 && p <= PA7: - return avr.DDRA, avr.PORTA, 1 << uint8(p-portA) + return avr.PORTA, 1 << uint8(p-portA) case p >= PB0 && p <= PB7: - return avr.DDRB, avr.PORTB, 1 << uint8(p-portB) + return avr.PORTB, 1 << uint8(p-portB) case p >= PC0 && p <= PC7: - return avr.DDRC, avr.PORTC, 1 << uint8(p-portC) + return avr.PORTC, 1 << uint8(p-portC) case p >= PD0 && p <= PD7: - return avr.DDRD, avr.PORTD, 1 << uint8(p-portD) + return avr.PORTD, 1 << uint8(p-portD) case p >= PE0 && p <= PE6: - return avr.DDRE, avr.PORTE, 1 << uint8(p-portE) + return avr.PORTE, 1 << uint8(p-portE) case p >= PF0 && p <= PF7: - return avr.DDRF, avr.PORTF, 1 << uint8(p-portF) + return avr.PORTF, 1 << uint8(p-portF) case p >= PG0 && p <= PG5: - return avr.DDRG, avr.PORTG, 1 << uint8(p-portG) + return avr.PORTG, 1 << uint8(p-portG) case p >= PH0 && p <= PH6: - return avr.DDRH, avr.PORTH, 1 << uint8(p-portH) + return avr.PORTH, 1 << uint8(p-portH) case p >= PJ0 && p <= PJ1: - return avr.DDRJ, avr.PORTJ, 1 << uint8(p-portJ) + return avr.PORTJ, 1 << uint8(p-portJ) case p >= PK0 && p <= PK7: - return avr.DDRK, avr.PORTK, 1 << uint8(p-portK) + return avr.PORTK, 1 << uint8(p-portK) case p >= PL0 && p <= PL7: - return avr.DDRL, avr.PORTL, 1 << uint8(p-portL) + return avr.PORTL, 1 << uint8(p-portL) default: - return avr.DDRB, avr.PORTA, 255 + return avr.PORTA, 255 } } diff --git a/src/machine/machine_atmega328p.go b/src/machine/machine_atmega328p.go index 60c36a37cc..ae1356e8ce 100644 --- a/src/machine/machine_atmega328p.go +++ b/src/machine/machine_atmega328p.go @@ -9,47 +9,82 @@ import ( const irq_USART0_RX = avr.IRQ_USART_RX -// Configure sets the pin to input or output. -func (p Pin) Configure(config PinConfig) { - if config.Mode == PinOutput { // set output bit - if p < 8 { - avr.DDRD.SetBits(1 << uint8(p)) - } else if p < 14 { - avr.DDRB.SetBits(1 << uint8(p-8)) - } else { - avr.DDRC.SetBits(1 << uint8(p-14)) - } - } else { // configure input: clear output bit - if p < 8 { - avr.DDRD.ClearBits(1 << uint8(p)) - } else if p < 14 { - avr.DDRB.ClearBits(1 << uint8(p-8)) - } else { - avr.DDRC.ClearBits(1 << uint8(p-14)) - } +// getPortMask returns the PORTx register and mask for the pin. +func (p Pin) getPortMask() (*volatile.Register8, uint8) { + switch { + case p >= PB0 && p <= PB7: // port B + return avr.PORTB, 1 << uint8(p-portB) + case p >= PC0 && p <= PC7: // port C + return avr.PORTC, 1 << uint8(p-portC) + default: // port D + return avr.PORTD, 1 << uint8(p-portD) } } -// Get returns the current value of a GPIO pin. -func (p Pin) Get() bool { - if p < 8 { - val := avr.PIND.Get() & (1 << uint8(p)) - return (val > 0) - } else if p < 14 { - val := avr.PINB.Get() & (1 << uint8(p-8)) - return (val > 0) - } else { - val := avr.PINC.Get() & (1 << uint8(p-14)) - return (val > 0) +// InitPWM initializes the registers needed for PWM. +func InitPWM() { + // use waveform generation + avr.TCCR0A.SetBits(avr.TCCR0A_WGM00) + + // set timer 0 prescale factor to 64 + avr.TCCR0B.SetBits(avr.TCCR0B_CS01 | avr.TCCR0B_CS00) + + // set timer 1 prescale factor to 64 + avr.TCCR1B.SetBits(avr.TCCR1B_CS11) + + // put timer 1 in 8-bit phase correct pwm mode + avr.TCCR1A.SetBits(avr.TCCR1A_WGM10) + + // set timer 2 prescale factor to 64 + avr.TCCR2B.SetBits(avr.TCCR2B_CS22) + + // configure timer 2 for phase correct pwm (8-bit) + avr.TCCR2A.SetBits(avr.TCCR2A_WGM20) +} + +// Configure configures a PWM pin for output. +func (pwm PWM) Configure() error { + switch pwm.Pin / 8 { + case 0: // port B + avr.DDRB.SetBits(1 << uint8(pwm.Pin)) + case 2: // port D + avr.DDRD.SetBits(1 << uint8(pwm.Pin-16)) } + return nil } -func (p Pin) getPortMask() (*volatile.Register8, uint8) { - if p < 8 { - return avr.PORTD, 1 << uint8(p) - } else if p < 14 { - return avr.PORTB, 1 << uint8(p-8) - } else { - return avr.PORTC, 1 << uint8(p-14) +// Set turns on the duty cycle for a PWM pin using the provided value. On the AVR this is normally a +// 8-bit value ranging from 0 to 255. +func (pwm PWM) Set(value uint16) { + value8 := uint8(value >> 8) + switch pwm.Pin { + case PD3: + // connect pwm to pin on timer 2, channel B + avr.TCCR2A.SetBits(avr.TCCR2A_COM2B1) + avr.OCR2B.Set(value8) // set pwm duty + case PD5: + // connect pwm to pin on timer 0, channel B + avr.TCCR0A.SetBits(avr.TCCR0A_COM0B1) + avr.OCR0B.Set(value8) // set pwm duty + case PD6: + // connect pwm to pin on timer 0, channel A + avr.TCCR0A.SetBits(avr.TCCR0A_COM0A1) + avr.OCR0A.Set(value8) // set pwm duty + case PB1: + // connect pwm to pin on timer 1, channel A + avr.TCCR1A.SetBits(avr.TCCR1A_COM1A1) + // this is a 16-bit value, but we only currently allow the low order bits to be set + avr.OCR1AL.Set(value8) // set pwm duty + case PB2: + // connect pwm to pin on timer 1, channel B + avr.TCCR1A.SetBits(avr.TCCR1A_COM1B1) + // this is a 16-bit value, but we only currently allow the low order bits to be set + avr.OCR1BL.Set(value8) // set pwm duty + case PB3: + // connect pwm to pin on timer 2, channel A + avr.TCCR2A.SetBits(avr.TCCR2A_COM2A1) + avr.OCR2A.Set(value8) // set pwm duty + default: + panic("Invalid PWM pin") } } diff --git a/src/machine/machine_atsamd21.go b/src/machine/machine_atsamd21.go index cd83b7b4df..0f2305a205 100644 --- a/src/machine/machine_atsamd21.go +++ b/src/machine/machine_atsamd21.go @@ -33,6 +33,26 @@ const ( PinInputPulldown PinMode = 12 ) +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = sam.EIC_CONFIG_SENSE0_RISE + PinFalling PinChange = sam.EIC_CONFIG_SENSE0_FALL + PinToggle PinChange = sam.EIC_CONFIG_SENSE0_BOTH +) + +// Callbacks to be called for pins configured with SetInterrupt. Unfortunately, +// we also need to keep track of which interrupt channel is used by which pin, +// as the only alternative would be iterating through all pins. +// +// We're using the magic constant 16 here because the SAM D21 has 16 interrupt +// channels configurable for pins. +var ( + interruptPins [16]Pin // warning: the value is invalid when pinCallbacks[i] is not set! + pinCallbacks [16]func(Pin) +) + const ( pinPadMapSERCOM0Pad0 byte = (0x10 << 1) | 0x00 pinPadMapSERCOM1Pad0 byte = (0x20 << 1) | 0x00 @@ -144,6 +164,114 @@ func findPinPadMapping(sercom uint8, pin Pin) (pinMode PinMode, pad uint32, ok b return } +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + // Most pins follow a common pattern where the EXTINT value is the pin + // number modulo 16. However, there are a few exceptions, as you can see + // below. + extint := uint8(0) + switch p { + case PA08: + // Connected to NMI. This is not currently supported. + return ErrInvalidInputPin + case PA24: + extint = 12 + case PA25: + extint = 13 + case PA27: + extint = 15 + case PA28: + extint = 8 + case PA30: + extint = 10 + case PA31: + extint = 11 + default: + // All other pins follow a normal pattern. + extint = uint8(p) % 16 + } + + if callback == nil { + // Disable this pin interrupt (if it was enabled). + sam.EIC.INTENCLR.Set(1 << extint) + if pinCallbacks[extint] != nil { + pinCallbacks[extint] = nil + } + return nil + } + + if pinCallbacks[extint] != nil { + // The pin was already configured. + // To properly re-configure a pin, unset it first and set a new + // configuration. + return ErrNoPinChangeChannel + } + pinCallbacks[extint] = callback + interruptPins[extint] = p + + if sam.EIC.CTRL.Get() == 0 { + // EIC peripheral has not yet been initialized. Initialize it now. + + // The EIC needs two clocks: CLK_EIC_APB and GCLK_EIC. CLK_EIC_APB is + // enabled by default, so doesn't have to be re-enabled. The other is + // required for detecting edges and must be enabled manually. + sam.GCLK.CLKCTRL.Set(sam.GCLK_CLKCTRL_ID_EIC<= 8 { + addr = &sam.EIC.CONFIG1 + } + pos := (extint % 8) * 4 // bit position in register + addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)< 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXE_Msk + p.setPMux(val | (sam.PORT_PMUX0_PMUXO_A << sam.PORT_PMUX0_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_PMUX0_PMUXO_Msk + p.setPMux(val | (sam.PORT_PMUX0_PMUXE_A << sam.PORT_PMUX0_PMUXE_Pos)) + } + + interrupt.New(sam.IRQ_EIC, func(interrupt.Interrupt) { + flags := sam.EIC.INTFLAG.Get() + sam.EIC.INTFLAG.Set(flags) // clear interrupt + for i := uint(0); i < 16; i++ { // there are 16 channels + if flags&(1<= 8 { + addr = &sam.EIC.CONFIG[1] + } + pos := (extint % 8) * 4 // bit position in register + addr.Set((addr.Get() &^ (0xf << pos)) | uint32(change)< 0 { + // odd pin, so save the even pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXE_Msk + p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXO_Pos)) + } else { + // even pin, so save the odd pins + val := p.getPMux() & sam.PORT_GROUP_PMUX_PMUXO_Msk + p.setPMux(val | (0 << sam.PORT_GROUP_PMUX_PMUXE_Pos)) + } + + handleEICInterrupt := func(interrupt.Interrupt) { + flags := sam.EIC.INTFLAG.Get() + sam.EIC.INTFLAG.Set(flags) // clear interrupt + for i := uint(0); i < 16; i++ { // there are 16 channels + if flags&(1<= PB04 && a.Pin <= PB07 { + if (a.Pin >= PB04 && a.Pin <= PB07) || (a.Pin >= PC00) { return sam.ADC1 } return sam.ADC0 @@ -629,6 +869,24 @@ func (a ADC) getADCChannel() uint8 { return 8 case PB07: return 9 + + case PC00: + return 10 + case PC01: + return 11 + case PC02: + return 4 + case PC03: + return 5 + case PC30: + return 12 + case PC31: + return 13 + + case PD00: + return 14 + case PD01: + return 15 default: panic("Invalid ADC pin") } @@ -645,28 +903,8 @@ type UART struct { var ( // UART0 is actually a USB CDC interface. UART0 = USBCDC{Buffer: NewRingBuffer()} - - // The first hardware serial port on the SAMD51. Uses the SERCOM3 interface. - UART1 = UART{ - Buffer: NewRingBuffer(), - Bus: sam.SERCOM3_USART_INT, - SERCOM: 3, - } - - // The second hardware serial port on the SAMD51. Uses the SERCOM0 interface. - UART2 = UART{ - Buffer: NewRingBuffer(), - Bus: sam.SERCOM0_USART_INT, - SERCOM: 0, - } ) -func init() { - // Register RXC interrupts. - UART1.Interrupt = interrupt.New(sam.IRQ_SERCOM3_2, UART1.handleInterrupt) - UART2.Interrupt = interrupt.New(sam.IRQ_SERCOM0_2, UART2.handleInterrupt) -} - const ( sampleRate16X = 16 lsbFirst = 1 @@ -1216,7 +1454,7 @@ const ( const period = 0xFFFF // Configure configures a PWM pin for output. -func (pwm PWM) Configure() { +func (pwm PWM) Configure() error { // Set pin as output sam.PORT.GROUP[0].DIRSET.Set(1 << uint8(pwm.Pin)) // Set pin to low @@ -1240,6 +1478,9 @@ func (pwm PWM) Configure() { // figure out which TCCX timer for this pin timer := pwm.getTimer() + if timer == nil { + return ErrInvalidOutputPin + } // disable timer timer.CTRLA.ClearBits(sam.TCC_CTRLA_ENABLE) @@ -1264,7 +1505,7 @@ func (pwm PWM) Configure() { // Set the initial value // TCCx->CC[tcChannel].reg = (uint32_t) value; - pwm.setChannel(0) + pwm.setChannel(timer, 0) for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) || timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) { @@ -1282,12 +1523,19 @@ func (pwm PWM) Configure() { // Wait for synchronization for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_ENABLE) { } + + return nil } // Set turns on the duty cycle for a PWM pin using the provided value. func (pwm PWM) Set(value uint16) { // figure out which TCCX timer for this pin timer := pwm.getTimer() + if timer == nil { + // The Configure call above cannot have succeeded, so simply ignore this + // error. + return + } // Wait for synchronization for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CTRLB) { @@ -1297,7 +1545,7 @@ func (pwm PWM) Set(value uint16) { } // TCCx->CCBUF[tcChannel].reg = (uint32_t) value; - pwm.setChannelBuffer(uint32(value)) + pwm.setChannelBuffer(timer, uint32(value)) for timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC0) || timer.SYNCBUSY.HasBits(sam.TCC_SYNCBUSY_CC1) { @@ -1329,61 +1577,61 @@ func (pwm PWM) setPinCfg(val uint8) { pwm.Pin.setPinCfg(val) } -// setChannel sets the value for the correct channel for PWM on this pin -func (pwm PWM) setChannel(val uint32) { +// setChannel sets the value for the correct channel for PWM on this pin. +func (pwm PWM) setChannel(timer *sam.TCC_Type, val uint32) { switch pwm.Pin { case PA16: - pwm.getTimer().CC[0].Set(val) + timer.CC[0].Set(val) case PA17: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) case PA14: - pwm.getTimer().CC[0].Set(val) + timer.CC[0].Set(val) case PA15: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) case PA18: - pwm.getTimer().CC[2].Set(val) + timer.CC[2].Set(val) case PA19: - pwm.getTimer().CC[3].Set(val) + timer.CC[3].Set(val) case PA20: - pwm.getTimer().CC[0].Set(val) + timer.CC[0].Set(val) case PA21: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) case PA23: - pwm.getTimer().CC[3].Set(val) + timer.CC[3].Set(val) case PA22: - pwm.getTimer().CC[2].Set(val) + timer.CC[2].Set(val) case PB31: - pwm.getTimer().CC[1].Set(val) + timer.CC[1].Set(val) default: return // not supported on this pin } } // setChannelBuffer sets the value for the correct channel buffer for PWM on this pin -func (pwm PWM) setChannelBuffer(val uint32) { +func (pwm PWM) setChannelBuffer(timer *sam.TCC_Type, val uint32) { switch pwm.Pin { case PA16: - pwm.getTimer().CCBUF[0].Set(val) + timer.CCBUF[0].Set(val) case PA17: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) case PA14: - pwm.getTimer().CCBUF[0].Set(val) + timer.CCBUF[0].Set(val) case PA15: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) case PA18: - pwm.getTimer().CCBUF[2].Set(val) + timer.CCBUF[2].Set(val) case PA19: - pwm.getTimer().CCBUF[3].Set(val) + timer.CCBUF[3].Set(val) case PA20: - pwm.getTimer().CCBUF[0].Set(val) + timer.CCBUF[0].Set(val) case PA21: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) case PA23: - pwm.getTimer().CCBUF[3].Set(val) + timer.CCBUF[3].Set(val) case PA22: - pwm.getTimer().CCBUF[2].Set(val) + timer.CCBUF[2].Set(val) case PB31: - pwm.getTimer().CCBUF[1].Set(val) + timer.CCBUF[1].Set(val) default: return // not supported on this pin } diff --git a/src/machine/machine_atsamd51p19.go b/src/machine/machine_atsamd51p19.go new file mode 100644 index 0000000000..2bb5940814 --- /dev/null +++ b/src/machine/machine_atsamd51p19.go @@ -0,0 +1,58 @@ +// +build sam,atsamd51,atsamd51p19 + +// Peripheral abstraction layer for the atsamd51. +// +// Datasheet: +// http://ww1.microchip.com/downloads/en/DeviceDoc/60001507C.pdf +// +package machine + +import "device/sam" + +const HSRAM_SIZE = 0x00030000 + +// InitPWM initializes the PWM interface. +func InitPWM() { + // turn on timer clocks used for PWM + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_TCC0_ | sam.MCLK_APBBMASK_TCC1_) + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_TCC2_) + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_TCC4_) + + //use clock generator 0 + sam.GCLK.PCHCTRL[25].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + sam.GCLK.PCHCTRL[29].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + sam.GCLK.PCHCTRL[38].Set((sam.GCLK_PCHCTRL_GEN_GCLK0 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) +} + +// getTimer returns the timer to be used for PWM on this pin +func (pwm PWM) getTimer() *sam.TCC_Type { + switch pwm.Pin { + case PA16: + return sam.TCC1 + case PA17: + return sam.TCC1 + case PA14: + return sam.TCC2 + case PA15: + return sam.TCC2 + case PA18: + return sam.TCC1 + case PA19: + return sam.TCC1 + case PA20: + return sam.TCC0 + case PA21: + return sam.TCC0 + case PA23: + return sam.TCC0 + case PA22: + return sam.TCC0 + case PB31: + return sam.TCC4 + default: + return nil // not supported on this pin + } +} diff --git a/src/machine/machine_attiny85.go b/src/machine/machine_attiny85.go index b3de12fc3b..5a6c37c7b5 100644 --- a/src/machine/machine_attiny85.go +++ b/src/machine/machine_attiny85.go @@ -16,21 +16,8 @@ const ( PB5 ) -// Configure sets the pin to input or output. -func (p Pin) Configure(config PinConfig) { - if config.Mode == PinOutput { // set output bit - avr.DDRB.SetBits(1 << uint8(p)) - } else { // configure input: clear output bit - avr.DDRB.ClearBits(1 << uint8(p)) - } -} - +// getPortMask returns the PORTx register and mask for the pin. func (p Pin) getPortMask() (*volatile.Register8, uint8) { + // Very simple for the attiny85, which only has a single port. return avr.PORTB, 1 << uint8(p) } - -// Get returns the current value of a GPIO pin. -func (p Pin) Get() bool { - val := avr.PINB.Get() & (1 << uint8(p)) - return (val > 0) -} diff --git a/src/machine/machine_avr.go b/src/machine/machine_avr.go index 7d63b04061..f1dbf89d10 100644 --- a/src/machine/machine_avr.go +++ b/src/machine/machine_avr.go @@ -5,15 +5,76 @@ package machine import ( "device/avr" "runtime/volatile" + "unsafe" ) type PinMode uint8 const ( PinInput PinMode = iota + PinInputPullup PinOutput ) +// In all the AVRs I've looked at, the PIN/DDR/PORT registers followed a regular +// pattern: PINx, DDRx, PORTx in this order without registers in between. +// Therefore, if you know any of them, you can calculate the other two. +// +// For now, I've chosen to let the PORTx register be the one that is returned +// for each specific chip and to calculate the others from that one. Setting an +// output port (done using PORTx) is likely the most common operation and the +// one that is the most time critical. For others, the PINx and DDRx register +// can trivially be calculated using a subtraction. + +// Configure sets the pin to input or output. +func (p Pin) Configure(config PinConfig) { + port, mask := p.getPortMask() + // The DDRx register can be found by subtracting one from the PORTx + // register, as this appears to be the case for many (most? all?) AVR chips. + ddr := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 1)) + if config.Mode == PinOutput { + // set output bit + ddr.SetBits(mask) + + // Note: if the pin was PinInputPullup before, it'll now be high. + // Otherwise it will be low. + } else { + // configure input: clear output bit + ddr.ClearBits(mask) + + if config.Mode == PinInput { + // No pullup (floating). + // The transition may be one of the following: + // output high -> input pullup -> input (safe: output high and input pullup are similar) + // output low -> input -> input (safe: no extra transition) + port.ClearBits(mask) + } else { + // Pullup. + // The transition may be one of the following: + // output high -> input pullup -> input pullup (safe: no extra transition) + // output low -> input -> input pullup (possibly problematic) + // For the last transition (output low -> input -> input pullup), + // the transition may be problematic in some cases because there is + // an intermediate floating state (which may cause irratic + // interrupts, for example). If this is a problem, the application + // should set the pin high before configuring it as PinInputPullup. + // We can't do that here because setting it to high as an + // intermediate state may have other problems. + port.SetBits(mask) + } + } +} + +// Get returns the current value of a GPIO pin. +func (p Pin) Get() bool { + port, mask := p.getPortMask() + // As noted above, the PINx register is always two registers below the PORTx + // register, so we can find it simply by subtracting two from the PORTx + // register address. + pin := (*volatile.Register8)(unsafe.Pointer(uintptr(unsafe.Pointer(port)) - 2)) // PINA, PINB, etc + return (pin.Get() & mask) > 0 +} + // Set changes the value of the GPIO pin. The pin must be configured as output. func (p Pin) Set(value bool) { if value { // set bits diff --git a/src/machine/machine_fe310.go b/src/machine/machine_fe310.go index f2de109247..6bd2384213 100644 --- a/src/machine/machine_fe310.go +++ b/src/machine/machine_fe310.go @@ -48,7 +48,6 @@ func (p Pin) Set(high bool) { // Get returns the current value of a GPIO pin. func (p Pin) Get() bool { val := sifive.GPIO0.VALUE.Get() & (1 << uint8(p)) - println(sifive.GPIO0.VALUE.Get()) return (val > 0) } diff --git a/src/machine/machine_generic.go b/src/machine/machine_generic.go index a0b47837e4..ace5b1cb4f 100644 --- a/src/machine/machine_generic.go +++ b/src/machine/machine_generic.go @@ -90,7 +90,8 @@ func InitPWM() { } // Configure configures a PWM pin for output. -func (pwm PWM) Configure() { +func (pwm PWM) Configure() error { + return nil } // Set turns on the duty cycle for a PWM pin using the provided value. diff --git a/src/machine/machine_nrf.go b/src/machine/machine_nrf.go index 663bf02405..3d65ad578d 100644 --- a/src/machine/machine_nrf.go +++ b/src/machine/machine_nrf.go @@ -21,6 +21,18 @@ const ( PinOutput PinMode = (nrf.GPIO_PIN_CNF_DIR_Output << nrf.GPIO_PIN_CNF_DIR_Pos) | (nrf.GPIO_PIN_CNF_INPUT_Disconnect << nrf.GPIO_PIN_CNF_INPUT_Pos) ) +type PinChange uint8 + +// Pin change interrupt constants for SetInterrupt. +const ( + PinRising PinChange = nrf.GPIOTE_CONFIG_POLARITY_LoToHi + PinFalling PinChange = nrf.GPIOTE_CONFIG_POLARITY_HiToLo + PinToggle PinChange = nrf.GPIOTE_CONFIG_POLARITY_Toggle +) + +// Callbacks to be called for pins configured with SetInterrupt. +var pinCallbacks [len(nrf.GPIOTE.CONFIG)]func(Pin) + // Configure this pin with the given configuration. func (p Pin) Configure(config PinConfig) { cfg := config.Mode | nrf.GPIO_PIN_CNF_DRIVE_S0S1 | nrf.GPIO_PIN_CNF_SENSE_Disabled @@ -59,6 +71,65 @@ func (p Pin) Get() bool { return (port.IN.Get()>>pin)&1 != 0 } +// SetInterrupt sets an interrupt to be executed when a particular pin changes +// state. +// +// This call will replace a previously set callback on this pin. You can pass a +// nil func to unset the pin change interrupt. If you do so, the change +// parameter is ignored and can be set to any value (such as 0). +func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error { + // Some variables to easily check whether a channel was already configured + // as an event channel for the given pin. + // This is not just an optimization, this is requred: the datasheet says + // that configuring more than one channel for a given pin results in + // unpredictable behavior. + expectedConfigMask := uint32(nrf.GPIOTE_CONFIG_MODE_Msk | nrf.GPIOTE_CONFIG_PSEL_Msk) + expectedConfig := nrf.GPIOTE_CONFIG_MODE_Event<> nrf.GPIOTE_CONFIG_PSEL_Pos) + pinCallbacks[i](pin) + } + } + }).Enable() + + // Everything was configured correctly. + return nil +} + // UART on the NRF. type UART struct { Buffer *RingBuffer @@ -372,13 +443,13 @@ func (spi SPI) Tx(w, r []byte) error { spi.Bus.TXD.Set(uint32(b)) for spi.Bus.EVENTS_READY.Get() == 0 { } - _ = spi.Bus.RXD.Get() spi.Bus.EVENTS_READY.Set(0) + _ = spi.Bus.RXD.Get() } for spi.Bus.EVENTS_READY.Get() == 0 { } - _ = spi.Bus.RXD.Get() spi.Bus.EVENTS_READY.Set(0) + _ = spi.Bus.RXD.Get() default: // write/read diff --git a/src/machine/machine_nrf52.go b/src/machine/machine_nrf52.go index caa0c4e78f..003be7a69d 100644 --- a/src/machine/machine_nrf52.go +++ b/src/machine/machine_nrf52.go @@ -159,7 +159,8 @@ func InitPWM() { } // Configure configures a PWM pin for output. -func (pwm PWM) Configure() { +func (pwm PWM) Configure() error { + return nil } // Set turns on the duty cycle for a PWM pin using the provided value. diff --git a/src/machine/machine_nrf52840.go b/src/machine/machine_nrf52840.go index 2a76ac27bd..c7e608269d 100644 --- a/src/machine/machine_nrf52840.go +++ b/src/machine/machine_nrf52840.go @@ -104,8 +104,8 @@ func InitADC() { } // Configure configures an ADC pin to be able to read analog data. -func (a ADC) Configure() { - return // no pin specific setup on nrf52840 machine. +func (a ADC) Configure() error { + return nil // no pin specific setup on nrf52840 machine. } // Get returns the current value of a ADC pin in the range 0..0xffff. diff --git a/src/os/file.go b/src/os/file.go index de971fb79d..6f4a8cc140 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -10,49 +10,119 @@ import ( ) // Portable analogs of some common system call errors. +// Note that these are exported for use in the Filesystem interface. var ( - errUnsupported = errors.New("operation not supported") - notImplemented = errors.New("os: not implemented") + ErrUnsupported = errors.New("operation not supported") + ErrNotImplemented = errors.New("operation not implemented") + ErrNotExist = errors.New("file not found") + ErrExist = errors.New("file exists") ) -// Stdin, Stdout, and Stderr are open Files pointing to the standard input, -// standard output, and standard error file descriptors. -var ( - Stdin = &File{0, "/dev/stdin"} - Stdout = &File{1, "/dev/stdout"} - Stderr = &File{2, "/dev/stderr"} -) +// Mkdir creates a directory. If the operation fails, it will return an error of +// type *PathError. +func Mkdir(path string, perm FileMode) error { + fs, suffix := findMount(path) + if fs == nil { + return &PathError{"mkdir", path, ErrNotExist} + } + err := fs.Mkdir(suffix, perm) + if err != nil { + return &PathError{"mkdir", path, err} + } + return nil +} + +// Remove removes a file or (empty) directory. If the operation fails, it will +// return an error of type *PathError. +func Remove(path string) error { + fs, suffix := findMount(path) + if fs == nil { + return &PathError{"remove", path, ErrNotExist} + } + err := fs.Remove(suffix) + if err != nil { + return &PathError{"remove", path, err} + } + return nil +} // File represents an open file descriptor. type File struct { - fd uintptr - name string + handle FileHandle + name string +} + +// Name returns the name of the file with which it was opened. +func (f *File) Name() string { + return f.name +} + +// OpenFile opens the named file. If the operation fails, the returned error +// will be of type *PathError. +func OpenFile(name string, flag int, perm FileMode) (*File, error) { + fs, suffix := findMount(name) + if fs == nil { + return nil, &PathError{"open", name, ErrNotExist} + } + handle, err := fs.OpenFile(suffix, flag, perm) + if err != nil { + return nil, &PathError{"open", name, err} + } + return &File{name: name, handle: handle}, nil +} + +// Open opens the file named for reading. +func Open(name string) (*File, error) { + return OpenFile(name, O_RDONLY, 0) +} + +// Create creates the named file, overwriting it if it already exists. +func Create(name string) (*File, error) { + return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666) +} + +// Read reads up to len(b) bytes from the File. It returns the number of bytes +// read and any error encountered. At end of file, Read returns 0, io.EOF. +func (f *File) Read(b []byte) (n int, err error) { + n, err = f.handle.Read(b) + if err != nil { + err = &PathError{"read", f.name, err} + } + return +} + +// Write writes len(b) bytes to the File. It returns the number of bytes written +// and an error, if any. Write returns a non-nil error when n != len(b). +func (f *File) Write(b []byte) (n int, err error) { + n, err = f.handle.Write(b) + if err != nil { + err = &PathError{"write", f.name, err} + } + return +} + +// Close closes the File, rendering it unusable for I/O. +func (f *File) Close() (err error) { + err = f.handle.Close() + if err != nil { + err = &PathError{"close", f.name, err} + } + return } // Readdir is a stub, not yet implemented func (f *File) Readdir(n int) ([]FileInfo, error) { - return nil, notImplemented + return nil, &PathError{"readdir", f.name, ErrNotImplemented} } // Readdirnames is a stub, not yet implemented func (f *File) Readdirnames(n int) (names []string, err error) { - return nil, notImplemented + return nil, &PathError{"readdirnames", f.name, ErrNotImplemented} } // Stat is a stub, not yet implemented func (f *File) Stat() (FileInfo, error) { - return nil, notImplemented -} - -// NewFile returns a new File with the given file descriptor and name. -func NewFile(fd uintptr, name string) *File { - return &File{fd, name} -} - -// Fd returns the integer Unix file descriptor referencing the open file. The -// file descriptor is valid only until f.Close is called. -func (f *File) Fd() uintptr { - return f.fd + return nil, &PathError{"stat", f.name, ErrNotImplemented} } const ( @@ -72,32 +142,8 @@ type PathError struct { Err error } -func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } - -// Open is a super simple stub function (for now), only capable of opening stdin, stdout, and stderr -func Open(name string) (*File, error) { - fd := uintptr(999) - switch name { - case "/dev/stdin": - fd = 0 - case "/dev/stdout": - fd = 1 - case "/dev/stderr": - fd = 2 - default: - return nil, &PathError{"open", name, notImplemented} - } - return &File{fd, name}, nil -} - -// OpenFile is a stub, passing through to the stub Open() call -func OpenFile(name string, flag int, perm FileMode) (*File, error) { - return Open(name) -} - -// Create is a stub, passing through to the stub Open() call -func Create(name string) (*File, error) { - return Open(name) +func (e *PathError) Error() string { + return e.Op + " " + e.Path + ": " + e.Err.Error() } type FileMode uint32 @@ -155,12 +201,12 @@ type FileInfo interface { // Stat is a stub, not yet implemented func Stat(name string) (FileInfo, error) { - return nil, notImplemented + return nil, &PathError{"stat", name, ErrNotImplemented} } // Lstat is a stub, not yet implemented func Lstat(name string) (FileInfo, error) { - return nil, notImplemented + return nil, &PathError{"lstat", name, ErrNotImplemented} } // Getwd is a stub (for now), always returning an empty string @@ -178,11 +224,6 @@ func TempDir() string { return "/tmp" } -// Mkdir is a stub, not yet implemented -func Mkdir(name string, perm FileMode) error { - return notImplemented -} - // IsExist is a stub (for now), always returning false func IsExist(err error) bool { return false diff --git a/src/os/file_other.go b/src/os/file_other.go index 50bfa226f8..be98cc693a 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -6,28 +6,44 @@ import ( _ "unsafe" ) +// Stdin, Stdout, and Stderr are open Files pointing to the standard input, +// standard output, and standard error file descriptors. +var ( + Stdin = &File{stdioFileHandle(0), "/dev/stdin"} + Stdout = &File{stdioFileHandle(1), "/dev/stdout"} + Stderr = &File{stdioFileHandle(2), "/dev/stderr"} +) + +// isOS indicates whether we're running on a real operating system with +// filesystem support. +const isOS = false + +// stdioFileHandle represents one of stdin, stdout, or stderr depending on the +// number. It implements the FileHandle interface. +type stdioFileHandle uint8 + // Read is unsupported on this system. -func (f *File) Read(b []byte) (n int, err error) { - return 0, errUnsupported +func (f stdioFileHandle) Read(b []byte) (n int, err error) { + return 0, ErrUnsupported } // Write writes len(b) bytes to the output. It returns the number of bytes // written or an error if this file is not stdout or stderr. -func (f *File) Write(b []byte) (n int, err error) { - switch f.fd { - case Stdout.fd, Stderr.fd: +func (f stdioFileHandle) Write(b []byte) (n int, err error) { + switch f { + case 1, 2: // stdout, stderr for _, c := range b { putchar(c) } return len(b), nil default: - return 0, errUnsupported + return 0, ErrUnsupported } } // Close is unsupported on this system. -func (f *File) Close() error { - return errUnsupported +func (f stdioFileHandle) Close() error { + return ErrUnsupported } //go:linkname putchar runtime.putchar diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 9f2a31e9d7..e77d6773fb 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -6,19 +6,105 @@ import ( "syscall" ) +func init() { + // Mount the host filesystem at the root directory. This is what most + // programs will be expecting. + Mount("/", unixFilesystem{}) +} + +// Stdin, Stdout, and Stderr are open Files pointing to the standard input, +// standard output, and standard error file descriptors. +var ( + Stdin = &File{unixFileHandle(0), "/dev/stdin"} + Stdout = &File{unixFileHandle(1), "/dev/stdout"} + Stderr = &File{unixFileHandle(2), "/dev/stderr"} +) + +// isOS indicates whether we're running on a real operating system with +// filesystem support. +const isOS = true + +// unixFilesystem is an empty handle for a Unix/Linux filesystem. All operations +// are relative to the current working directory. +type unixFilesystem struct { +} + +func (fs unixFilesystem) Mkdir(path string, perm FileMode) error { + return handleSyscallError(syscall.Mkdir(path, uint32(perm))) +} + +func (fs unixFilesystem) Remove(path string) error { + return handleSyscallError(syscall.Unlink(path)) +} + +func (fs unixFilesystem) OpenFile(path string, flag int, perm FileMode) (FileHandle, error) { + // Map os package flags to syscall flags. + syscallFlag := 0 + if flag&O_RDONLY != 0 { + syscallFlag |= syscall.O_RDONLY + } + if flag&O_WRONLY != 0 { + syscallFlag |= syscall.O_WRONLY + } + if flag&O_RDWR != 0 { + syscallFlag |= syscall.O_RDWR + } + if flag&O_APPEND != 0 { + syscallFlag |= syscall.O_APPEND + } + if flag&O_CREATE != 0 { + syscallFlag |= syscall.O_CREAT + } + if flag&O_EXCL != 0 { + syscallFlag |= syscall.O_EXCL + } + if flag&O_SYNC != 0 { + syscallFlag |= syscall.O_SYNC + } + if flag&O_TRUNC != 0 { + syscallFlag |= syscall.O_TRUNC + } + fp, err := syscall.Open(path, syscallFlag, uint32(perm)) + return unixFileHandle(fp), handleSyscallError(err) +} + +// unixFileHandle is a Unix file pointer with associated methods that implement +// the FileHandle interface. +type unixFileHandle uintptr + // Read reads up to len(b) bytes from the File. It returns the number of bytes // read and any error encountered. At end of file, Read returns 0, io.EOF. -func (f *File) Read(b []byte) (n int, err error) { - return syscall.Read(int(f.fd), b) +func (f unixFileHandle) Read(b []byte) (n int, err error) { + n, err = syscall.Read(int(f), b) + err = handleSyscallError(err) + return } // Write writes len(b) bytes to the File. It returns the number of bytes written // and an error, if any. Write returns a non-nil error when n != len(b). -func (f *File) Write(b []byte) (n int, err error) { - return syscall.Write(int(f.fd), b) +func (f unixFileHandle) Write(b []byte) (n int, err error) { + n, err = syscall.Write(int(f), b) + err = handleSyscallError(err) + return } // Close closes the File, rendering it unusable for I/O. -func (f *File) Close() error { - return syscall.Close(int(f.fd)) +func (f unixFileHandle) Close() error { + return handleSyscallError(syscall.Close(int(f))) +} + +// handleSyscallError converts syscall errors into regular os package errors. +// The err parameter must be either nil or of type syscall.Errno. +func handleSyscallError(err error) error { + if err == nil { + return nil + } + switch err.(syscall.Errno) { + case syscall.EEXIST: + return ErrExist + case syscall.ENOENT: + return ErrNotExist + default: + return err + } } diff --git a/src/os/filesystem.go b/src/os/filesystem.go new file mode 100644 index 0000000000..b0719c6cd2 --- /dev/null +++ b/src/os/filesystem.go @@ -0,0 +1,85 @@ +package os + +import ( + "strings" +) + +// mounts lists the mount points currently mounted in the filesystem provided by +// the os package. To resolve a path to a mount point, it is scanned from top to +// bottom looking for the first prefix match. +var mounts []mountPoint + +type mountPoint struct { + // prefix is a filesystem prefix, that always starts and ends with a forward + // slash. To denote the root filesystem, use a single slash: "/". + // This allows fast checking whether a path lies within a mount point. + prefix string + + // filesystem is the Filesystem implementation that is mounted at this mount + // point. + filesystem Filesystem +} + +// Filesystem provides an interface for generic filesystem drivers mounted in +// the os package. The errors returned must be one of the os.Err* errors, or a +// custom error if one doesn't exist. It should not be a *PathError because +// errors will be wrapped with a *PathError by the filesystem abstraction. +// +// WARNING: this interface is not finalized and may change in a future version. +type Filesystem interface { + // OpenFile opens the named file. + OpenFile(name string, flag int, perm FileMode) (FileHandle, error) + + // Mkdir creates a new directoy with the specified permission (before + // umask). Some filesystems may not support directories or permissions. + Mkdir(name string, perm FileMode) error + + // Remove removes the named file or (empty) directory. + Remove(name string) error +} + +// FileHandle is an interface that should be implemented by filesystems +// implementing the Filesystem interface. +// +// WARNING: this interface is not finalized and may change in a future version. +type FileHandle interface { + // Read reads up to len(b) bytes from the file. + Read(b []byte) (n int, err error) + + // Write writes up to len(b) bytes to the file. + Write(b []byte) (n int, err error) + + // Close closes the file, making it unusable for further writes. + Close() (err error) +} + +// findMount returns the appropriate (mounted) filesystem to use for a given +// filename plus the path relative to that filesystem. +func findMount(path string) (Filesystem, string) { + for i := len(mounts) - 1; i >= 0; i-- { + mount := mounts[i] + if strings.HasPrefix(path, mount.prefix) { + return mount.filesystem, path[len(mount.prefix)-1:] + } + } + if isOS { + // Assume that the first entry in the mounts slice is the OS filesystem + // at the root of the directory tree. Use it as-is, to support relative + // paths. + return mounts[0].filesystem, path + } + return nil, path +} + +// Mount mounts the given filesystem in the filesystem abstraction layer of the +// os package. It is not possible to unmount filesystems. Filesystems added +// later will override earlier filesystems. +// +// The provided prefix must start and end with a forward slash. This is true for +// the root directory ("/") for example. +func Mount(prefix string, filesystem Filesystem) { + if prefix[0] != '/' || prefix[len(prefix)-1] != '/' { + panic("os.Mount: invalid prefix") + } + mounts = append(mounts, mountPoint{prefix, filesystem}) +} diff --git a/src/os/proc.go b/src/os/proc.go index a7718c4365..d3bfb12708 100644 --- a/src/os/proc.go +++ b/src/os/proc.go @@ -9,6 +9,15 @@ import ( "syscall" ) +// Args hold the command-line arguments, starting with the program name. +var Args []string + +func init() { + Args = runtime_args() +} + +func runtime_args() []string // in package runtime + // Exit causes the current program to exit with the given status code. // Conventionally, code zero indicates success, non-zero an error. // The program terminates immediately; deferred functions are not run. diff --git a/src/reflect/value.go b/src/reflect/value.go index a1c69370e0..e71eaee8b9 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -304,29 +304,48 @@ func (v Value) Slice(i, j int) Value { panic("unimplemented: (reflect.Value).Slice()") } +//go:linkname maplen runtime.hashmapLenUnsafePointer +func maplen(p unsafe.Pointer) int + +//go:linkname chanlen runtime.chanLenUnsafePointer +func chanlen(p unsafe.Pointer) int + // Len returns the length of this value for slices, strings, arrays, channels, -// and maps. For oter types, it panics. +// and maps. For other types, it panics. func (v Value) Len() int { t := v.Type() switch t.Kind() { + case Array: + return v.Type().Len() + case Chan: + return chanlen(v.value) + case Map: + return maplen(v.value) case Slice: return int((*SliceHeader)(v.value).Len) case String: return int((*StringHeader)(v.value).Len) - case Array: - return v.Type().Len() - default: // Chan, Map - panic("unimplemented: (reflect.Value).Len()") + default: + panic(&ValueError{"Len"}) } } +//go:linkname chancap runtime.chanCapUnsafePointer +func chancap(p unsafe.Pointer) int + +// Cap returns the capacity of this value for arrays, channels and slices. +// For other types, it panics. func (v Value) Cap() int { t := v.Type() switch t.Kind() { + case Array: + return v.Type().Len() + case Chan: + return chancap(v.value) case Slice: return int((*SliceHeader)(v.value).Cap) - default: // Array, Chan - panic("unimplemented: (reflect.Value).Cap()") + default: + panic(&ValueError{"Cap"}) } } diff --git a/src/runtime/arch_arm.go b/src/runtime/arch_arm.go index 995e6d2469..0a98f6d532 100644 --- a/src/runtime/arch_arm.go +++ b/src/runtime/arch_arm.go @@ -15,5 +15,5 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return arm.ReadRegister("sp") + return arm.AsmFull("mov {}, sp", nil) } diff --git a/src/runtime/arch_cortexm.go b/src/runtime/arch_cortexm.go index 7a0aeffdc4..3359eb6671 100644 --- a/src/runtime/arch_cortexm.go +++ b/src/runtime/arch_cortexm.go @@ -17,5 +17,88 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return arm.ReadRegister("sp") + return arm.AsmFull("mov {}, sp", nil) +} + +// Documentation: +// * https://llvm.org/docs/Atomics.html +// * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html +// +// In the case of Cortex-M, some atomic operations are emitted inline while +// others are emitted as libcalls. How many are emitted as libcalls depends on +// the MCU core variant (M3 and higher support some 32-bit atomic operations +// while M0 and M0+ do not). + +//export __sync_fetch_and_add_4 +func __sync_fetch_and_add_4(ptr *uint32, value uint32) uint32 { + mask := arm.DisableInterrupts() + oldValue := *ptr + *ptr = oldValue + value + arm.EnableInterrupts(mask) + return oldValue +} + +//export __sync_fetch_and_add_8 +func __sync_fetch_and_add_8(ptr *uint64, value uint64) uint64 { + mask := arm.DisableInterrupts() + oldValue := *ptr + *ptr = oldValue + value + arm.EnableInterrupts(mask) + return oldValue +} + +//export __sync_lock_test_and_set_4 +func __sync_lock_test_and_set_4(ptr *uint32, value uint32) uint32 { + mask := arm.DisableInterrupts() + oldValue := *ptr + *ptr = value + arm.EnableInterrupts(mask) + return oldValue +} + +//export __sync_lock_test_and_set_8 +func __sync_lock_test_and_set_8(ptr *uint64, value uint64) uint64 { + mask := arm.DisableInterrupts() + oldValue := *ptr + *ptr = value + arm.EnableInterrupts(mask) + return oldValue +} + +//export __sync_val_compare_and_swap_4 +func __sync_val_compare_and_swap_4(ptr *uint32, expected, desired uint32) uint32 { + mask := arm.DisableInterrupts() + oldValue := *ptr + if oldValue == expected { + *ptr = desired + } + arm.EnableInterrupts(mask) + return oldValue +} + +//export __sync_val_compare_and_swap_8 +func __sync_val_compare_and_swap_8(ptr *uint64, expected, desired uint64) uint64 { + mask := arm.DisableInterrupts() + oldValue := *ptr + if oldValue == expected { + *ptr = desired + } + arm.EnableInterrupts(mask) + return oldValue +} + +// The safest thing to do here would just be to disable interrupts for +// procPin/procUnpin. Note that a global variable is safe in this case, as any +// access to procPinnedMask will happen with interrupts disabled. + +var procPinnedMask uintptr + +//go:linkname procPin sync/atomic.runtime_procPin +func procPin() { + procPinnedMask = arm.DisableInterrupts() +} + +//go:linkname procUnpin sync/atomic.runtime_procUnpin +func procUnpin() { + arm.EnableInterrupts(procPinnedMask) } diff --git a/src/runtime/arch_tinygoriscv.go b/src/runtime/arch_tinygoriscv.go index 1c5d8b78f4..98fbed70ac 100644 --- a/src/runtime/arch_tinygoriscv.go +++ b/src/runtime/arch_tinygoriscv.go @@ -15,5 +15,78 @@ func align(ptr uintptr) uintptr { } func getCurrentStackPointer() uintptr { - return riscv.ReadRegister("sp") + return riscv.AsmFull("mv {}, sp", nil) +} + +// Documentation: +// * https://llvm.org/docs/Atomics.html +// * https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html +// +// In the case of RISC-V, some operations may be implemented with libcalls if +// the operation is too big to be handled by assembly. Officially, these calls +// should be implemented with a lock-free algorithm but as (as of this time) all +// supported RISC-V chips have a single hart, we can simply disable interrupts +// to get the same behavior. + +//export __atomic_load_8 +func __atomic_load_8(ptr *uint64, ordering int32) uint64 { + mask := riscv.DisableInterrupts() + value := *ptr + riscv.EnableInterrupts(mask) + return value +} + +//export __atomic_store_8 +func __atomic_store_8(ptr *uint64, value uint64, ordering int32) { + mask := riscv.DisableInterrupts() + *ptr = value + riscv.EnableInterrupts(mask) +} + +//export __atomic_exchange_8 +func __atomic_exchange_8(ptr *uint64, value uint64, ordering int32) uint64 { + mask := riscv.DisableInterrupts() + oldValue := *ptr + *ptr = value + riscv.EnableInterrupts(mask) + return oldValue +} + +//export __atomic_compare_exchange_8 +func __atomic_compare_exchange_8(ptr, expected *uint64, desired uint64, success_ordering, failure_ordering int32) bool { + mask := riscv.DisableInterrupts() + oldValue := *ptr + success := oldValue == *expected + if success { + *ptr = desired + } else { + *expected = oldValue + } + riscv.EnableInterrupts(mask) + return success +} + +//export __atomic_fetch_add_8 +func __atomic_fetch_add_8(ptr *uint64, value uint64, ordering int32) uint64 { + mask := riscv.DisableInterrupts() + oldValue := *ptr + *ptr = oldValue + value + riscv.EnableInterrupts(mask) + return oldValue +} + +// The safest thing to do here would just be to disable interrupts for +// procPin/procUnpin. Note that a global variable is safe in this case, as any +// access to procPinnedMask will happen with interrupts disabled. + +var procPinnedMask uintptr + +//go:linkname procPin sync/atomic.runtime_procPin +func procPin() { + procPinnedMask = riscv.DisableInterrupts() +} + +//go:linkname procUnpin sync/atomic.runtime_procUnpin +func procUnpin() { + riscv.EnableInterrupts(procPinnedMask) } diff --git a/src/runtime/atomic.go b/src/runtime/atomic.go deleted file mode 100644 index f9ae3031e9..0000000000 --- a/src/runtime/atomic.go +++ /dev/null @@ -1,24 +0,0 @@ -package runtime - -// This file contains implementations for the sync/atomic package. - -// All implementations assume there are no goroutines, threads or interrupts. - -//go:linkname loadUint64 sync/atomic.LoadUint64 -func loadUint64(addr *uint64) uint64 { - return *addr -} - -//go:linkname storeUint32 sync/atomic.StoreUint32 -func storeUint32(addr *uint32, val uint32) { - *addr = val -} - -//go:linkname compareAndSwapUint64 sync/atomic.CompareAndSwapUint64 -func compareAndSwapUint64(addr *uint64, old, new uint64) bool { - if *addr == old { - *addr = new - return true - } - return false -} diff --git a/src/runtime/bytes.go b/src/runtime/bytes.go deleted file mode 100644 index 6b3d6be73c..0000000000 --- a/src/runtime/bytes.go +++ /dev/null @@ -1,11 +0,0 @@ -package runtime - -//go:linkname indexBytePortable internal/bytealg.IndexByte -func indexBytePortable(s []byte, c byte) int { - for i, b := range s { - if b == c { - return i - } - } - return -1 -} diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 929b8c7300..37ad154a65 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -136,6 +136,38 @@ func chanMake(elementSize uintptr, bufSize uintptr) *channel { } } +// Return the number of entries in this chan, called from the len builtin. +// A nil chan is defined as having length 0. +//go:inline +func chanLen(c *channel) int { + if c == nil { + return 0 + } + return int(c.bufUsed) +} + +// wrapper for use in reflect +func chanLenUnsafePointer(p unsafe.Pointer) int { + c := (*channel)(p) + return chanLen(c) +} + +// Return the capacity of this chan, called from the cap builtin. +// A nil chan is defined as having capacity 0. +//go:inline +func chanCap(c *channel) int { + if c == nil { + return 0 + } + return int(c.bufSize) +} + +// wrapper for use in reflect +func chanCapUnsafePointer(p unsafe.Pointer) int { + c := (*channel)(p) + return chanCap(c) +} + // resumeRX resumes the next receiver and returns the destination pointer. // If the ok value is true, then the caller is expected to store a value into this pointer. func (ch *channel) resumeRX(ok bool) unsafe.Pointer { @@ -414,7 +446,7 @@ type chanSelectState struct { // chanSend sends a single value over the channel. // This operation will block unless a value is immediately available. // May panic if the channel is closed. -func chanSend(ch *channel, value unsafe.Pointer) { +func chanSend(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) { if ch.trySend(value) { // value immediately sent chanDebug(ch) @@ -430,10 +462,11 @@ func chanSend(ch *channel, value unsafe.Pointer) { sender := task.Current() ch.state = chanStateSend sender.Ptr = value - ch.blocked = &channelBlockedList{ + *blockedlist = channelBlockedList{ next: ch.blocked, t: sender, } + ch.blocked = blockedlist chanDebug(ch) task.Pause() sender.Ptr = nil @@ -443,7 +476,7 @@ func chanSend(ch *channel, value unsafe.Pointer) { // It blocks if there is no available value to recieve. // The recieved value is copied into the value pointer. // Returns the comma-ok value. -func chanRecv(ch *channel, value unsafe.Pointer) bool { +func chanRecv(ch *channel, value unsafe.Pointer, blockedlist *channelBlockedList) bool { if rx, ok := ch.tryRecv(value); rx { // value immediately available chanDebug(ch) @@ -459,10 +492,11 @@ func chanRecv(ch *channel, value unsafe.Pointer) bool { receiver := task.Current() ch.state = chanStateRecv receiver.Ptr, receiver.Data = value, 1 - ch.blocked = &channelBlockedList{ + *blockedlist = channelBlockedList{ next: ch.blocked, t: receiver, } + ch.blocked = blockedlist chanDebug(ch) task.Pause() ok := receiver.Data == 1 diff --git a/src/runtime/gc_extalloc.go b/src/runtime/gc_extalloc.go index 95b06a4924..296ac7a096 100644 --- a/src/runtime/gc_extalloc.go +++ b/src/runtime/gc_extalloc.go @@ -602,3 +602,11 @@ func alloc(size uintptr) unsafe.Pointer { func free(ptr unsafe.Pointer) { // Currently unimplemented due to bugs in coroutine lowering. } + +func KeepAlive(x interface{}) { + // Unimplemented. Only required with SetFinalizer(). +} + +func SetFinalizer(obj interface{}, finalizer interface{}) { + // Unimplemented. +} diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index bd940c759f..d7d60c89fc 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -1,7 +1,7 @@ package runtime // This is a hashmap implementation for the map[T]T type. -// It is very rougly based on the implementation of the Go hashmap: +// It is very roughly based on the implementation of the Go hashmap: // // https://golang.org/src/runtime/map.go @@ -80,6 +80,7 @@ func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap { // Return the number of entries in this hashmap, called from the len builtin. // A nil hashmap is defined as having length 0. +//go:inline func hashmapLen(m *hashmap) int { if m == nil { return 0 @@ -87,6 +88,12 @@ func hashmapLen(m *hashmap) int { return int(m.count) } +// wrapper for use in reflect +func hashmapLenUnsafePointer(p unsafe.Pointer) int { + m := (*hashmap)(p) + return hashmapLen(m) +} + // Set a specified key to a given value. Grow the map if necessary. //go:nobounds func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { diff --git a/src/runtime/interrupt/interrupt_avr.go b/src/runtime/interrupt/interrupt_avr.go new file mode 100644 index 0000000000..6eb6b28fa4 --- /dev/null +++ b/src/runtime/interrupt/interrupt_avr.go @@ -0,0 +1,36 @@ +// +build avr + +package interrupt + +import "device" + +// State represents the previous global interrupt state. +type State uint8 + +// Disable disables all interrupts and returns the previous interrupt state. It +// can be used in a critical section like this: +// +// state := interrupt.Disable() +// // critical section +// interrupt.Restore(state) +// +// Critical sections can be nested. Make sure to call Restore in the same order +// as you called Disable (this happens naturally with the pattern above). +func Disable() (state State) { + // SREG is at I/O address 0x3f. + return State(device.AsmFull(` + in {}, 0x3f + cli + `, nil)) +} + +// Restore restores interrupts to what they were before. Give the previous state +// returned by Disable as a parameter. If interrupts were disabled before +// calling Disable, this will not re-enable interrupts, allowing for nested +// cricital sections. +func Restore(state State) { + // SREG is at I/O address 0x3f. + device.AsmFull("out 0x3f, {state}", map[string]interface{}{ + "state": state, + }) +} diff --git a/src/runtime/interrupt/interrupt_cortexm.go b/src/runtime/interrupt/interrupt_cortexm.go index 5354be9e00..c23f736d80 100644 --- a/src/runtime/interrupt/interrupt_cortexm.go +++ b/src/runtime/interrupt/interrupt_cortexm.go @@ -21,3 +21,27 @@ func (irq Interrupt) Enable() { func (irq Interrupt) SetPriority(priority uint8) { arm.SetPriority(uint32(irq.num), uint32(priority)) } + +// State represents the previous global interrupt state. +type State uintptr + +// Disable disables all interrupts and returns the previous interrupt state. It +// can be used in a critical section like this: +// +// state := interrupt.Disable() +// // critical section +// interrupt.Restore(state) +// +// Critical sections can be nested. Make sure to call Restore in the same order +// as you called Disable (this happens naturally with the pattern above). +func Disable() (state State) { + return State(arm.DisableInterrupts()) +} + +// Restore restores interrupts to what they were before. Give the previous state +// returned by Disable as a parameter. If interrupts were disabled before +// calling Disable, this will not re-enable interrupts, allowing for nested +// cricital sections. +func Restore(state State) { + arm.EnableInterrupts(uintptr(state)) +} diff --git a/src/runtime/interrupt/interrupt_gameboyadvance.go b/src/runtime/interrupt/interrupt_gameboyadvance.go index c25afc14b9..e4b202937a 100644 --- a/src/runtime/interrupt/interrupt_gameboyadvance.go +++ b/src/runtime/interrupt/interrupt_gameboyadvance.go @@ -34,3 +34,32 @@ func handleInterrupt() { // appropriate interrupt handler for the given interrupt ID. //go:linkname callInterruptHandler runtime.callInterruptHandler func callInterruptHandler(id int) + +// State represents the previous global interrupt state. +type State uint8 + +// Disable disables all interrupts and returns the previous interrupt state. It +// can be used in a critical section like this: +// +// state := interrupt.Disable() +// // critical section +// interrupt.Restore(state) +// +// Critical sections can be nested. Make sure to call Restore in the same order +// as you called Disable (this happens naturally with the pattern above). +func Disable() (state State) { + // Save the previous interrupt state. + state = State(regInterruptMasterEnable.Get()) + // Disable all interrupts. + regInterruptMasterEnable.Set(0) + return +} + +// Restore restores interrupts to what they were before. Give the previous state +// returned by Disable as a parameter. If interrupts were disabled before +// calling Disable, this will not re-enable interrupts, allowing for nested +// cricital sections. +func Restore(state State) { + // Restore interrupts to the previous state. + regInterruptMasterEnable.Set(uint16(state)) +} diff --git a/src/runtime/interrupt/interrupt_tinygoriscv.go b/src/runtime/interrupt/interrupt_tinygoriscv.go new file mode 100644 index 0000000000..0161018da5 --- /dev/null +++ b/src/runtime/interrupt/interrupt_tinygoriscv.go @@ -0,0 +1,29 @@ +// +build tinygo.riscv + +package interrupt + +import "device/riscv" + +// State represents the previous global interrupt state. +type State uintptr + +// Disable disables all interrupts and returns the previous interrupt state. It +// can be used in a critical section like this: +// +// state := interrupt.Disable() +// // critical section +// interrupt.Restore(state) +// +// Critical sections can be nested. Make sure to call Restore in the same order +// as you called Disable (this happens naturally with the pattern above). +func Disable() (state State) { + return State(riscv.DisableInterrupts()) +} + +// Restore restores interrupts to what they were before. Give the previous state +// returned by Disable as a parameter. If interrupts were disabled before +// calling Disable, this will not re-enable interrupts, allowing for nested +// cricital sections. +func Restore(state State) { + riscv.EnableInterrupts(uintptr(state)) +} diff --git a/src/runtime/print.go b/src/runtime/print.go index 37a6a58214..e4d715176b 100644 --- a/src/runtime/print.go +++ b/src/runtime/print.go @@ -47,23 +47,8 @@ func printint16(n int16) { printint32(int32(n)) } -//go:nobounds func printuint32(n uint32) { - digits := [10]byte{} // enough to hold (2^32)-1 - // Fill in all 10 digits. - firstdigit := 9 // digit index that isn't zero (by default, the last to handle '0' correctly) - for i := 9; i >= 0; i-- { - digit := byte(n%10 + '0') - digits[i] = digit - if digit != '0' { - firstdigit = i - } - n /= 10 - } - // Print digits without the leading zeroes. - for i := firstdigit; i < 10; i++ { - putchar(digits[i]) - } + printuint64(uint64(n)) } func printint32(n int32) { @@ -76,12 +61,23 @@ func printint32(n int32) { printuint32(uint32(n)) } +//go:nobounds func printuint64(n uint64) { - prevdigits := n / 10 - if prevdigits != 0 { - printuint64(prevdigits) + digits := [20]byte{} // enough to hold (2^64)-1 + // Fill in all 10 digits. + firstdigit := 19 // digit index that isn't zero (by default, the last to handle '0' correctly) + for i := 19; i >= 0; i-- { + digit := byte(n%10 + '0') + digits[i] = digit + if digit != '0' { + firstdigit = i + } + n /= 10 + } + // Print digits without the leading zeroes. + for i := firstdigit; i < 20; i++ { + putchar(digits[i]) } - putchar(byte((n % 10) + '0')) } func printint64(n int64) { diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go index 61d21029e8..3a4a8cd8d6 100644 --- a/src/runtime/runtime.go +++ b/src/runtime/runtime.go @@ -24,9 +24,12 @@ func GOROOT() string { return "/usr/local/go" } +// TODO: fill with real args. +var args = []string{"/proc/self/exe"} + //go:linkname os_runtime_args os.runtime_args func os_runtime_args() []string { - return nil + return args } // Copy size bytes from src to dst. The memory areas must not overlap. @@ -58,7 +61,7 @@ func memequal(x, y unsafe.Pointer, n uintptr) bool { } func nanotime() int64 { - return int64(ticks()) * tickMicros + return ticksToNanoseconds(ticks()) } // timeOffset is how long the monotonic clock started after the Unix epoch. It diff --git a/src/runtime/runtime_arm7tdmi.go b/src/runtime/runtime_arm7tdmi.go index 0c36be540f..70b764042b 100644 --- a/src/runtime/runtime_arm7tdmi.go +++ b/src/runtime/runtime_arm7tdmi.go @@ -9,8 +9,6 @@ import ( type timeUnit int64 -const tickMicros = 1 - func putchar(c byte) { // dummy, TODO } @@ -60,6 +58,14 @@ func preinit() { } } +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + func ticks() timeUnit { // TODO return 0 diff --git a/src/runtime/runtime_atsamd21.go b/src/runtime/runtime_atsamd21.go index 17bba38025..1055db0c66 100644 --- a/src/runtime/runtime_atsamd21.go +++ b/src/runtime/runtime_atsamd21.go @@ -231,9 +231,6 @@ func waitForSync() { } } -// treat all ticks params coming from runtime as being in microseconds -const tickMicros = 1000 - var ( timestamp timeUnit // ticks since boottime timerLastCounter uint64 @@ -243,6 +240,22 @@ var timerWakeup volatile.Register8 const asyncScheduler = false +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} + // sleepTicks should sleep for d number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { @@ -259,22 +272,22 @@ func ticks() timeUnit { sam.RTC_MODE0.READREQ.Set(sam.RTC_MODE0_READREQ_RREQ) waitForSync() - rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us - offset := (rtcCounter - timerLastCounter) // change since last measurement + rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) // each counter tick == 30.5us + offset := (rtcCounter - timerLastCounter) // change since last measurement timerLastCounter = rtcCounter - timestamp += timeUnit(offset) // TODO: not precise + timestamp += timeUnit(offset) return timestamp } // ticks are in microseconds func timerSleep(ticks uint32) { timerWakeup.Set(0) - if ticks < 214 { - // due to around 183us delay waiting for the register value to sync, the minimum sleep value - // for the SAMD21 is 214us. + if ticks < 7 { + // Due to around 6 clock ticks delay waiting for the register value to + // sync, the minimum sleep value for the SAMD21 is 214us. // For related info, see: // https://community.atmel.com/comment/2507091#comment-2507091 - ticks = 214 + ticks = 7 } // request read of count @@ -283,7 +296,7 @@ func timerSleep(ticks uint32) { // set compare value cnt := sam.RTC_MODE0.COUNT.Get() - sam.RTC_MODE0.COMP0.Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us + sam.RTC_MODE0.COMP0.Set(uint32(cnt) + ticks) waitForSync() // enable IRQ for CMP0 compare diff --git a/src/runtime/runtime_atsamd51.go b/src/runtime/runtime_atsamd51.go index 93ae1216a8..dd9da121f4 100644 --- a/src/runtime/runtime_atsamd51.go +++ b/src/runtime/runtime_atsamd51.go @@ -219,9 +219,6 @@ func waitForSync() { } } -// treat all ticks params coming from runtime as being in microseconds -const tickMicros = 1000 - var ( timestamp timeUnit // ticks since boottime timerLastCounter uint64 @@ -231,6 +228,22 @@ var timerWakeup volatile.Register8 const asyncScheduler = false +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} + // sleepTicks should sleep for d number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { @@ -245,22 +258,22 @@ func sleepTicks(d timeUnit) { func ticks() timeUnit { waitForSync() - rtcCounter := (uint64(sam.RTC_MODE0.COUNT.Get()) * 305) / 10 // each counter tick == 30.5us - offset := (rtcCounter - timerLastCounter) // change since last measurement + rtcCounter := uint64(sam.RTC_MODE0.COUNT.Get()) + offset := (rtcCounter - timerLastCounter) // change since last measurement timerLastCounter = rtcCounter - timestamp += timeUnit(offset) // TODO: not precise + timestamp += timeUnit(offset) return timestamp } // ticks are in microseconds func timerSleep(ticks uint32) { timerWakeup.Set(0) - if ticks < 260 { + if ticks < 8 { // due to delay waiting for the register value to sync, the minimum sleep value // for the SAMD51 is 260us. // For related info for SAMD21, see: // https://community.atmel.com/comment/2507091#comment-2507091 - ticks = 260 + ticks = 8 } // request read of count @@ -269,7 +282,7 @@ func timerSleep(ticks uint32) { // set compare value cnt := sam.RTC_MODE0.COUNT.Get() - sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + (ticks * 10 / 305)) // each counter tick == 30.5us + sam.RTC_MODE0.COMP[0].Set(uint32(cnt) + ticks) // enable IRQ for CMP0 compare sam.RTC_MODE0.INTENSET.SetBits(sam.RTC_MODE0_INTENSET_CMP0) diff --git a/src/runtime/runtime_atsamd51p19.go b/src/runtime/runtime_atsamd51p19.go new file mode 100644 index 0000000000..8821147218 --- /dev/null +++ b/src/runtime/runtime_atsamd51p19.go @@ -0,0 +1,53 @@ +// +build sam,atsamd51,atsamd51p19 + +package runtime + +import ( + "device/sam" +) + +func initSERCOMClocks() { + // Turn on clock to SERCOM0 for UART0 + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM0_) + sam.GCLK.PCHCTRL[7].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // sets the "slow" clock shared by all SERCOM + sam.GCLK.PCHCTRL[3].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // Turn on clock to SERCOM1 + sam.MCLK.APBAMASK.SetBits(sam.MCLK_APBAMASK_SERCOM1_) + sam.GCLK.PCHCTRL[8].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // Turn on clock to SERCOM2 + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM2_) + sam.GCLK.PCHCTRL[23].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // Turn on clock to SERCOM3 + sam.MCLK.APBBMASK.SetBits(sam.MCLK_APBBMASK_SERCOM3_) + sam.GCLK.PCHCTRL[24].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // Turn on clock to SERCOM4 + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM4_) + sam.GCLK.PCHCTRL[34].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // Turn on clock to SERCOM5 + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM5_) + sam.GCLK.PCHCTRL[35].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // Turn on clock to SERCOM6 + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM6_) + sam.GCLK.PCHCTRL[36].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) + + // Turn on clock to SERCOM7 + sam.MCLK.APBDMASK.SetBits(sam.MCLK_APBDMASK_SERCOM7_) + sam.GCLK.PCHCTRL[37].Set((sam.GCLK_PCHCTRL_GEN_GCLK1 << sam.GCLK_PCHCTRL_GEN_Pos) | + sam.GCLK_PCHCTRL_CHEN) +} diff --git a/src/runtime/runtime_avr.go b/src/runtime/runtime_avr.go index 18a7639660..ae8a4d41e6 100644 --- a/src/runtime/runtime_avr.go +++ b/src/runtime/runtime_avr.go @@ -14,8 +14,6 @@ type timeUnit uint32 var currentTime timeUnit -const tickMicros = 1024 * 16384 - // Watchdog timer periods. These can be off by a large margin (hence the jump // between 64ms and 125ms which is not an exact double), so don't rely on this // for accurate time keeping. @@ -71,6 +69,16 @@ func putchar(c byte) { const asyncScheduler = false +const tickNanos = 1024 * 16384 // roughly 16ms in nanoseconds + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * tickNanos +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / tickNanos) +} + // Sleep this number of ticks of 16ms. // // TODO: not very accurate. Improve accuracy by calibrating on startup and every diff --git a/src/runtime/runtime_cortexm_qemu.go b/src/runtime/runtime_cortexm_qemu.go index 1686c2d8e3..eed2015637 100644 --- a/src/runtime/runtime_cortexm_qemu.go +++ b/src/runtime/runtime_cortexm_qemu.go @@ -13,8 +13,6 @@ import ( type timeUnit int64 -const tickMicros = 1 - var timestamp timeUnit func postinit() {} @@ -29,6 +27,14 @@ func main() { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + func sleepTicks(d timeUnit) { // TODO: actually sleep here for the given time. timestamp += d diff --git a/src/runtime/runtime_fe310.go b/src/runtime/runtime_fe310.go index de0d7ec465..437f03bf58 100644 --- a/src/runtime/runtime_fe310.go +++ b/src/runtime/runtime_fe310.go @@ -24,6 +24,9 @@ func main() { sifive.PLIC.ENABLE[0].Set(0) sifive.PLIC.ENABLE[1].Set(0) + // Zero the threshold value to allow all priorities of interrupts. + sifive.PLIC.THRESHOLD.Set(0) + // Set the interrupt address. // Note that this address must be aligned specially, otherwise the MODE bits // of MTVEC won't be zero. diff --git a/src/runtime/runtime_fe310_baremetal.go b/src/runtime/runtime_fe310_baremetal.go index e04560c4a2..4fa62e69ae 100644 --- a/src/runtime/runtime_fe310_baremetal.go +++ b/src/runtime/runtime_fe310_baremetal.go @@ -6,7 +6,21 @@ import ( "device/riscv" ) -const tickMicros = 32768 // RTC clock runs at 32.768kHz +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} func abort() { // lock up forever diff --git a/src/runtime/runtime_fe310_qemu.go b/src/runtime/runtime_fe310_qemu.go index 132515e795..ce14abfdd1 100644 --- a/src/runtime/runtime_fe310_qemu.go +++ b/src/runtime/runtime_fe310_qemu.go @@ -7,12 +7,18 @@ import ( "unsafe" ) -const tickMicros = 100 // CLINT.MTIME increments every 100ns - // Special memory-mapped device to exit tests, created by SiFive. var testExit = (*volatile.Register32)(unsafe.Pointer(uintptr(0x100000))) -var timestamp timeUnit +// ticksToNanoseconds converts CLINT ticks (at 100ns per tick) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 100 +} + +// nanosecondsToTicks converts nanoseconds to CLINT ticks (at 100ns per tick). +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 100) +} func abort() { // Signal a successful exit. diff --git a/src/runtime/runtime_nrf.go b/src/runtime/runtime_nrf.go index c3cde00264..9a0678c6d6 100644 --- a/src/runtime/runtime_nrf.go +++ b/src/runtime/runtime_nrf.go @@ -12,8 +12,6 @@ import ( type timeUnit int64 -const tickMicros = 1024 * 32 - //go:linkname systemInit SystemInit func systemInit() @@ -64,7 +62,7 @@ func sleepTicks(d timeUnit) { for d != 0 { ticks() // update timestamp ticks := uint32(d) & 0x7fffff // 23 bits (to be on the safe side) - rtc_sleep(ticks) // TODO: not accurate (must be d / 30.5175...) + rtc_sleep(ticks) d -= timeUnit(ticks) } } @@ -74,6 +72,22 @@ var ( rtcLastCounter uint32 // 24 bits ticks ) +// ticksToNanoseconds converts RTC ticks (at 32768Hz) to nanoseconds. +func ticksToNanoseconds(ticks timeUnit) int64 { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ticks * 1e9 / 32768 + return int64(ticks) * 1953125 / 64 +} + +// nanosecondsToTicks converts nanoseconds to RTC ticks (running at 32768Hz). +func nanosecondsToTicks(ns int64) timeUnit { + // The following calculation is actually the following, but with both sides + // reduced to reduce the risk of overflow: + // ns * 32768 / 1e9 + return timeUnit(ns * 64 / 1953125) +} + // Monotonically increasing numer of ticks since start. // // Note: very long pauses between measurements (more than 8 minutes) may @@ -83,7 +97,7 @@ func ticks() timeUnit { rtcCounter := uint32(nrf.RTC1.COUNTER.Get()) offset := (rtcCounter - rtcLastCounter) & 0xffffff // change since last measurement rtcLastCounter = rtcCounter - timestamp += timeUnit(offset) // TODO: not precise + timestamp += timeUnit(offset) return timestamp } diff --git a/src/runtime/runtime_stm32f103xx.go b/src/runtime/runtime_stm32f103xx.go index 8c29c264b8..7407fa738a 100644 --- a/src/runtime/runtime_stm32f103xx.go +++ b/src/runtime/runtime_stm32f103xx.go @@ -53,8 +53,6 @@ func initCLK() { } } -const tickMicros = 1000 - var ( timestamp timeUnit // microseconds since boottime timerLastCounter uint64 @@ -109,6 +107,14 @@ func initTIM() { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 1000 +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 1000) +} + // sleepTicks should sleep for specific number of microseconds. func sleepTicks(d timeUnit) { for d != 0 { diff --git a/src/runtime/runtime_stm32f407.go b/src/runtime/runtime_stm32f407.go index fd1be5b43d..626dabd94c 100644 --- a/src/runtime/runtime_stm32f407.go +++ b/src/runtime/runtime_stm32f407.go @@ -109,8 +109,6 @@ func initCLK() { } -const tickMicros = 1000 - var ( // tick in milliseconds tickCount timeUnit @@ -118,6 +116,14 @@ var ( var timerWakeup volatile.Register8 +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) * 1000 +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns / 1000) +} + // Enable the TIM3 clock.(sleep count) func initTIM3() { stm32.RCC.APB1ENR.SetBits(stm32.RCC_APB1ENR_TIM3EN) diff --git a/src/runtime/runtime_tinygoriscv_qemu.go b/src/runtime/runtime_tinygoriscv_qemu.go index 1155d553eb..27caa398ad 100644 --- a/src/runtime/runtime_tinygoriscv_qemu.go +++ b/src/runtime/runtime_tinygoriscv_qemu.go @@ -13,8 +13,6 @@ import ( type timeUnit int64 -const tickMicros = 1 - var timestamp timeUnit func postinit() {} @@ -28,6 +26,14 @@ func main() { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + func sleepTicks(d timeUnit) { // TODO: actually sleep here for the given time. timestamp += d diff --git a/src/runtime/runtime_unix.go b/src/runtime/runtime_unix.go index a23d9e0e6c..acb8309467 100644 --- a/src/runtime/runtime_unix.go +++ b/src/runtime/runtime_unix.go @@ -26,8 +26,6 @@ func clock_gettime(clk_id int32, ts *timespec) type timeUnit int64 -const tickMicros = 1 - // Note: tv_sec and tv_nsec vary in size by platform. They are 32-bit on 32-bit // systems and 64-bit on 64-bit systems (at least on macOS/Linux), so we can // simply use the 'int' type which does the same. @@ -57,7 +55,18 @@ func putchar(c byte) { const asyncScheduler = false +func ticksToNanoseconds(ticks timeUnit) int64 { + // The OS API works in nanoseconds so no conversion necessary. + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + // The OS API works in nanoseconds so no conversion necessary. + return timeUnit(ns) +} + func sleepTicks(d timeUnit) { + // timeUnit is in nanoseconds, so need to convert to microseconds here. usleep(uint(d) / 1000) } @@ -85,3 +94,14 @@ func extalloc(size uintptr) unsafe.Pointer { //export free func extfree(ptr unsafe.Pointer) + +// TinyGo does not yet support any form of parallelism on an OS, so these can be +// left empty. + +//go:linkname procPin sync/atomic.runtime_procPin +func procPin() { +} + +//go:linkname procUnpin sync/atomic.runtime_procUnpin +func procUnpin() { +} diff --git a/src/runtime/runtime_wasm.go b/src/runtime/runtime_wasm.go index 02d34ffda8..aaf95d7846 100644 --- a/src/runtime/runtime_wasm.go +++ b/src/runtime/runtime_wasm.go @@ -6,8 +6,6 @@ import "unsafe" type timeUnit float64 // time in milliseconds, just like Date.now() in JavaScript -const tickMicros = 1000000 - // Implements __wasi_ciovec_t and __wasi_iovec_t. type wasiIOVec struct { buf unsafe.Pointer @@ -58,6 +56,7 @@ func resume() { go func() { handleEvent() }() + scheduler() } //export go_scheduler @@ -67,6 +66,19 @@ func go_scheduler() { const asyncScheduler = true +func ticksToNanoseconds(ticks timeUnit) int64 { + // The JavaScript API works in float64 milliseconds, so convert to + // nanoseconds first before converting to a timeUnit (which is a float64), + // to avoid precision loss. + return int64(ticks * 1e6) +} + +func nanosecondsToTicks(ns int64) timeUnit { + // The JavaScript API works in float64 milliseconds, so convert to timeUnit + // (which is a float64) first before dividing, to avoid precision loss. + return timeUnit(ns) / 1e6 +} + // This function is called by the scheduler. // Schedule a call to runtime.scheduler, do not actually sleep. //export runtime.sleepTicks @@ -79,3 +91,14 @@ func ticks() timeUnit func abort() { trap() } + +// TinyGo does not yet support any form of parallelism on WebAssembly, so these +// can be left empty. + +//go:linkname procPin sync/atomic.runtime_procPin +func procPin() { +} + +//go:linkname procUnpin sync/atomic.runtime_procUnpin +func procUnpin() { +} diff --git a/src/runtime/scheduler.go b/src/runtime/scheduler.go index 49cf8e4027..a8c9f16b1f 100644 --- a/src/runtime/scheduler.go +++ b/src/runtime/scheduler.go @@ -75,14 +75,14 @@ func runqueuePushBack(t *task.Task) { } // Add this task to the sleep queue, assuming its state is set to sleeping. -func addSleepTask(t *task.Task, duration int64) { +func addSleepTask(t *task.Task, duration timeUnit) { if schedulerDebug { - println(" set sleep:", t, uint(duration/tickMicros)) + println(" set sleep:", t, duration) if t.Next != nil { panic("runtime: addSleepTask: expected next task to be nil") } } - t.Data = uint(duration / tickMicros) // TODO: longer durations + t.Data = uint(duration) // TODO: longer durations now := ticks() if sleepQueue == nil { scheduleLog(" -> sleep new queue") diff --git a/src/runtime/scheduler_any.go b/src/runtime/scheduler_any.go index 41a904535d..fb7ca6e964 100644 --- a/src/runtime/scheduler_any.go +++ b/src/runtime/scheduler_any.go @@ -7,7 +7,7 @@ import "internal/task" // Pause the current task for a given time. //go:linkname sleep time.Sleep func sleep(duration int64) { - addSleepTask(task.Current(), duration) + addSleepTask(task.Current(), nanosecondsToTicks(duration)) task.Pause() } diff --git a/src/runtime/scheduler_none.go b/src/runtime/scheduler_none.go index d462ca15c5..e40615fe83 100644 --- a/src/runtime/scheduler_none.go +++ b/src/runtime/scheduler_none.go @@ -4,7 +4,7 @@ package runtime //go:linkname sleep time.Sleep func sleep(duration int64) { - sleepTicks(timeUnit(duration / tickMicros)) + sleepTicks(nanosecondsToTicks(duration)) } // getSystemStackPointer returns the current stack pointer of the system stack. diff --git a/src/runtime/scheduler_tinygoriscv.S b/src/runtime/scheduler_tinygoriscv.S index 60924f1d0d..3766bae754 100644 --- a/src/runtime/scheduler_tinygoriscv.S +++ b/src/runtime/scheduler_tinygoriscv.S @@ -22,6 +22,9 @@ tinygo_scanCurrentStack: mv a0, sp call tinygo_scanstack + // Restore return address. + lw ra, 60(sp) + // Restore stack state. addi sp, sp, 64 diff --git a/src/runtime/string.go b/src/runtime/string.go index 30736d3056..e0d0a226eb 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -204,15 +204,3 @@ func decodeUTF8(s string, index uintptr) (rune, uintptr) { return 0xfffd, 1 } } - -// indexByteString returns the index of the first instance of c in s, or -1 if c -// is not present in s. -//go:linkname indexByteString internal/bytealg.IndexByteString -func indexByteString(s string, c byte) int { - for i := 0; i < len(s); i++ { - if s[i] == c { - return i - } - } - return -1 -} diff --git a/src/runtime/string_count.go b/src/runtime/string_count.go deleted file mode 100644 index 6b4f8d42f4..0000000000 --- a/src/runtime/string_count.go +++ /dev/null @@ -1,23 +0,0 @@ -// +build amd64 arm,go1.13 arm64 ppc64le ppc64 s390x - -package runtime - -// This file implements the string counting functions used by the strings -// package, for example. It must be reimplemented here as a replacement for the -// Go stdlib asm implementations, but only when the asm implementations are used -// (this varies by Go version). -// Track this file for updates: -// https://github.com/golang/go/blob/master/src/internal/bytealg/count_native.go - -// countString copies the implementation from -// https://github.com/golang/go/blob/67f181bfd84dfd5942fe9a29d8a20c9ce5eb2fea/src/internal/bytealg/count_generic.go#L1 -//go:linkname countString internal/bytealg.CountString -func countString(s string, c byte) int { - n := 0 - for i := 0; i < len(s); i++ { - if s[i] == c { - n++ - } - } - return n -} diff --git a/src/runtime/strings_go111.go b/src/runtime/strings_go111.go index b13ae7c088..866cb92a88 100644 --- a/src/runtime/strings_go111.go +++ b/src/runtime/strings_go111.go @@ -2,11 +2,13 @@ package runtime +import "internal/bytealg" + // indexByte provides compatibility with Go 1.11. // See the following: // https://github.com/tinygo-org/tinygo/issues/351 // https://github.com/golang/go/commit/ad4a58e31501bce5de2aad90a620eaecdc1eecb8 //go:linkname indexByte strings.IndexByte func indexByte(s string, c byte) int { - return indexByteString(s, c) + return bytealg.IndexByteString(s, c) } diff --git a/src/runtime/volatile/register.go b/src/runtime/volatile/register.go index 5be97e7fde..adfe372eee 100644 --- a/src/runtime/volatile/register.go +++ b/src/runtime/volatile/register.go @@ -1,6 +1,6 @@ package volatile -// This file defines Register{8,16,32} types, which are convenience types for +// This file defines Register{8,16,32,64} types, which are convenience types for // volatile register accesses. // Special types that causes loads/stores to be volatile (necessary for @@ -190,3 +190,65 @@ func (r *Register32) HasBits(value uint32) bool { func (r *Register32) ReplaceBits(value uint32, mask uint32, pos uint8) { StoreUint32(&r.Reg, LoadUint32(&r.Reg)&^(mask< 0 +// +//go:inline +func (r *Register64) HasBits(value uint64) bool { + return (r.Get() & value) > 0 +} + +// ReplaceBits is a helper to simplify setting multiple bits high and/or low at +// once. It is the volatile equivalent of: +// +// r.Reg = (r.Reg & ^(mask << pos)) | value << pos +// +// go:inline +func (r *Register64) ReplaceBits(value uint64, mask uint64, pos uint8) { + StoreUint64(&r.Reg, LoadUint64(&r.Reg)&^(mask< 0 { + // Check for overflow. + if uint(delta) > (^uint(0))-wg.counter { + panic("sync: WaitGroup counter overflowed") + } + + // Add to the counter. + wg.counter += uint(delta) + } else { + // Check for underflow. + if uint(-delta) > wg.counter { + panic("sync: negative WaitGroup counter") + } + + // Subtract from the counter. + wg.counter -= uint(-delta) + + // If the counter is zero, everything is done and the waiters should be resumed. + // This code assumes that the waiters cannot wake up until after this function returns. + // In the current implementation, this is always correct. + if wg.counter == 0 { + for t := wg.waiters.Pop(); t != nil; t = wg.waiters.Pop() { + scheduleTask(t) + } + } + } +} + +func (wg *WaitGroup) Done() { + wg.Add(-1) +} + +func (wg *WaitGroup) Wait() { + if wg.counter == 0 { + // Everything already finished. + return + } + + // Push the current goroutine onto the waiter stack. + wg.waiters.Push(task.Current()) + + // Pause until the waiters are awoken by Add/Done. + task.Pause() +} diff --git a/src/syscall/syscall_darwin.go b/src/syscall/syscall_darwin.go index 176fe61dde..e2e7f408d4 100644 --- a/src/syscall/syscall_darwin.go +++ b/src/syscall/syscall_darwin.go @@ -1,8 +1,7 @@ package syscall // This file defines errno and constants to match the darwin libsystem ABI. -// Values have been determined experimentally by compiling some C code on macOS -// with Clang and looking at the resulting LLVM IR. +// Values have been copied from src/syscall/zerrors_darwin_amd64.go. // This function returns the error location in the darwin ABI. // Discovered by compiling the following code using Clang: @@ -24,28 +23,34 @@ func getErrno() Errno { } const ( - ENOENT Errno = 2 - EINTR Errno = 4 - EMFILE Errno = 24 - EAGAIN Errno = 35 - ETIMEDOUT Errno = 60 - ENOSYS Errno = 78 + ENOENT Errno = 0x2 + EEXIST Errno = 0x11 + EINTR Errno = 0x4 + EMFILE Errno = 0x18 + EAGAIN Errno = 0x23 + ETIMEDOUT Errno = 0x3c + ENOSYS Errno = 0x4e EWOULDBLOCK Errno = EAGAIN ) type Signal int const ( - SIGCHLD Signal = 20 - SIGINT Signal = 2 - SIGKILL Signal = 9 - SIGTRAP Signal = 5 - SIGQUIT Signal = 3 - SIGTERM Signal = 15 + SIGCHLD Signal = 0x14 + SIGINT Signal = 0x2 + SIGKILL Signal = 0x9 + SIGTRAP Signal = 0x5 + SIGQUIT Signal = 0x3 + SIGTERM Signal = 0xf ) const ( - O_RDONLY = 0 - O_WRONLY = 1 - O_RDWR = 2 + O_RDONLY = 0x0 + O_WRONLY = 0x1 + O_RDWR = 0x2 + O_APPEND = 0x8 + O_SYNC = 0x80 + O_CREAT = 0x200 + O_TRUNC = 0x400 + O_EXCL = 0x800 ) diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 7838880492..3f24ddb70c 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -31,6 +31,14 @@ func Open(path string, mode int, perm uint32) (fd int, err error) { return 0, ENOSYS // TODO } +func Mkdir(path string, mode uint32) (err error) { + return ENOSYS // TODO +} + +func Unlink(path string) (err error) { + return ENOSYS // TODO +} + func Kill(pid int, sig Signal) (err error) { return ENOSYS // TODO } diff --git a/src/testing/testing.go b/src/testing/testing.go index f476514f66..ca353c6085 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -20,10 +20,10 @@ import ( type common struct { output io.Writer - failed bool // Test or benchmark has failed. - skipped bool // Test of benchmark has been skipped. - finished bool // Test function has completed. - name string // Name of test or benchmark. + failed bool // Test or benchmark has failed. + skipped bool // Test of benchmark has been skipped. + finished bool // Test function has completed. + name string // Name of test or benchmark. } // TB is the interface common to T and B. diff --git a/targets/arduino-nano33.json b/targets/arduino-nano33.json index 788fa40769..fe41338e67 100644 --- a/targets/arduino-nano33.json +++ b/targets/arduino-nano33.json @@ -1,6 +1,6 @@ { "inherits": ["atsamd21g18a"], "build-tags": ["sam", "atsamd21g18a", "arduino_nano33"], - "flash-command": "bossac -d -i -e -w -v -R --port={port} --offset=0x2000 {bin}", + "flash-command": "bossac -i -e -w -v -R -U --port={port} --offset=0x2000 {bin}", "flash-1200-bps-reset": "true" } diff --git a/targets/atsamd51g19a.json b/targets/atsamd51g19a.json index 59e2cd58cf..c3ac816089 100644 --- a/targets/atsamd51g19a.json +++ b/targets/atsamd51g19a.json @@ -1,9 +1,7 @@ { - "inherits": ["cortex-m"], - "llvm-target": "armv7em-none-eabi", + "inherits": ["cortex-m4"], "build-tags": ["atsamd51g19", "atsamd51", "sam"], "cflags": [ - "--target=armv7em-none-eabi", "-Qunused-arguments" ], "linkerscript": "targets/atsamd51.ld", diff --git a/targets/atsamd51j19a.json b/targets/atsamd51j19a.json index 9ddde66fee..1bd42a0ad6 100644 --- a/targets/atsamd51j19a.json +++ b/targets/atsamd51j19a.json @@ -1,9 +1,7 @@ { - "inherits": ["cortex-m"], - "llvm-target": "armv7em-none-eabi", + "inherits": ["cortex-m4"], "build-tags": ["atsamd51j19", "atsamd51", "sam"], "cflags": [ - "--target=armv7em-none-eabi", "-Qunused-arguments" ], "linkerscript": "targets/atsamd51.ld", diff --git a/targets/atsamd51j20a.json b/targets/atsamd51j20a.json index cab3a624ab..2089b5d6e9 100644 --- a/targets/atsamd51j20a.json +++ b/targets/atsamd51j20a.json @@ -1,12 +1,10 @@ { - "inherits": ["cortex-m"], - "llvm-target": "armv7em-none-eabi", + "inherits": ["cortex-m4"], "build-tags": ["sam", "atsamd51", "atsamd51j20", "atsamd51j20a"], "cflags": [ - "--target=armv7em-none-eabi", "-Qunused-arguments" ], - "linkerscript": "targets/atsamd51.ld", + "linkerscript": "targets/atsamd51j20a.ld", "extra-files": [ "src/device/sam/atsamd51j20a.s" ] diff --git a/targets/atsamd51j20a.ld b/targets/atsamd51j20a.ld new file mode 100644 index 0000000000..5b8ce97190 --- /dev/null +++ b/targets/atsamd51j20a.ld @@ -0,0 +1,10 @@ + +MEMORY +{ + FLASH_TEXT (rw) : ORIGIN = 0x00000000+0x4000, LENGTH = 0x00100000-0x4000 /* First 16KB used by bootloader */ + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 0x00040000 +} + +_stack_size = 4K; + +INCLUDE "targets/arm.ld" diff --git a/targets/atsamd51p19a.json b/targets/atsamd51p19a.json new file mode 100644 index 0000000000..23c1777ca6 --- /dev/null +++ b/targets/atsamd51p19a.json @@ -0,0 +1,11 @@ +{ + "inherits": ["cortex-m4"], + "build-tags": ["atsamd51p19", "atsamd51", "sam"], + "cflags": [ + "-Qunused-arguments" + ], + "linkerscript": "targets/atsamd51.ld", + "extra-files": [ + "src/device/sam/atsamd51p19a.s" + ] +} diff --git a/targets/cortex-m4.json b/targets/cortex-m4.json new file mode 100644 index 0000000000..1d5ce2b198 --- /dev/null +++ b/targets/cortex-m4.json @@ -0,0 +1,8 @@ +{ + "inherits": ["cortex-m"], + "llvm-target": "armv7em-none-eabi", + "cflags": [ + "--target=armv7em-none-eabi", + "-mfloat-abi=soft" + ] +} diff --git a/targets/fe310.json b/targets/fe310.json index 36bb7452d7..76f7497053 100644 --- a/targets/fe310.json +++ b/targets/fe310.json @@ -1,5 +1,5 @@ { - "inherits": ["riscv"], + "inherits": ["riscv32"], "features": ["+a", "+c", "+m"], "build-tags": ["fe310", "sifive"] } diff --git a/targets/microbit-s110v8.json b/targets/microbit-s110v8.json new file mode 100644 index 0000000000..02c90153ca --- /dev/null +++ b/targets/microbit-s110v8.json @@ -0,0 +1,3 @@ +{ + "inherits": ["microbit", "nrf51-s110v8"] +} diff --git a/targets/nrf51-s110v8.json b/targets/nrf51-s110v8.json new file mode 100644 index 0000000000..5f3ad3ff93 --- /dev/null +++ b/targets/nrf51-s110v8.json @@ -0,0 +1,4 @@ +{ + "build-tags": ["softdevice", "s110v8"], + "linkerscript": "targets/nrf51-s110v8.ld" +} diff --git a/targets/nrf51-s110v8.ld b/targets/nrf51-s110v8.ld new file mode 100644 index 0000000000..44b4082874 --- /dev/null +++ b/targets/nrf51-s110v8.ld @@ -0,0 +1,12 @@ + +MEMORY +{ + /* This SoftDevice requires 96K flash and 8K RAM according to the release + * notes of version 8.0.0 */ + FLASH_TEXT (rw) : ORIGIN = 0x00000000 + 96K, LENGTH = 256K - 96K + RAM (xrw) : ORIGIN = 0x20000000 + 8K, LENGTH = 16K - 8K +} + +_stack_size = 2K; + +INCLUDE "targets/arm.ld" diff --git a/targets/nrf52-s132v6.ld b/targets/nrf52-s132v6.ld index 2515fca8cb..febac4746e 100644 --- a/targets/nrf52-s132v6.ld +++ b/targets/nrf52-s132v6.ld @@ -7,4 +7,7 @@ MEMORY _stack_size = 4K; +/* This value is needed by the Nordic SoftDevice. */ +__app_ram_base = ORIGIN(RAM); + INCLUDE "targets/arm.ld" diff --git a/targets/nrf52.json b/targets/nrf52.json index d48c9d33c2..58f42c29e0 100644 --- a/targets/nrf52.json +++ b/targets/nrf52.json @@ -1,10 +1,7 @@ { - "inherits": ["cortex-m"], - "llvm-target": "armv7em-none-eabi", + "inherits": ["cortex-m4"], "build-tags": ["nrf52", "nrf"], "cflags": [ - "--target=armv7em-none-eabi", - "-mfloat-abi=soft", "-Qunused-arguments", "-DNRF52832_XXAA", "-I{root}/lib/CMSIS/CMSIS/Include", diff --git a/targets/nrf52840-s140v7.ld b/targets/nrf52840-s140v7.ld index dde80182bd..0ff135bf61 100644 --- a/targets/nrf52840-s140v7.ld +++ b/targets/nrf52840-s140v7.ld @@ -7,4 +7,7 @@ MEMORY _stack_size = 4K; +/* This value is needed by the Nordic SoftDevice. */ +__app_ram_base = ORIGIN(RAM); + INCLUDE "targets/arm.ld" diff --git a/targets/nrf52840.json b/targets/nrf52840.json index 2c2eff7ca5..4ab30a2082 100644 --- a/targets/nrf52840.json +++ b/targets/nrf52840.json @@ -1,10 +1,7 @@ { - "inherits": ["cortex-m"], - "llvm-target": "armv7em-none-eabi", + "inherits": ["cortex-m4"], "build-tags": ["nrf52840", "nrf"], "cflags": [ - "--target=armv7em-none-eabi", - "-mfloat-abi=soft", "-Qunused-arguments", "-DNRF52840_XXAA", "-I{root}/lib/CMSIS/CMSIS/Include", diff --git a/targets/pca10056.json b/targets/pca10056.json index 1e35e51e97..fc0acaa0a2 100644 --- a/targets/pca10056.json +++ b/targets/pca10056.json @@ -4,5 +4,7 @@ "flash-method": "command", "flash-command": "nrfjprog -f nrf52 --sectorerase --program {hex} --reset", "msd-volume-name": "JLINK", - "msd-firmware-name": "firmware.hex" + "msd-firmware-name": "firmware.hex", + "openocd-interface": "jlink", + "openocd-transport": "swd" } diff --git a/targets/pygamer.json b/targets/pygamer.json new file mode 100644 index 0000000000..a14460d349 --- /dev/null +++ b/targets/pygamer.json @@ -0,0 +1,8 @@ +{ + "inherits": ["atsamd51j19a"], + "build-tags": ["sam", "atsamd51j19a", "pygamer"], + "flash-1200-bps-reset": "true", + "flash-method": "msd", + "msd-volume-name": "PYGAMERBOOT", + "msd-firmware-name": "arcade.uf2" +} diff --git a/targets/riscv-qemu.json b/targets/riscv-qemu.json index 9288e4784a..4f2c695fe6 100644 --- a/targets/riscv-qemu.json +++ b/targets/riscv-qemu.json @@ -1,5 +1,5 @@ { - "inherits": ["riscv"], + "inherits": ["riscv32"], "features": ["+a", "+c", "+m"], "build-tags": ["virt", "qemu"], "linkerscript": "targets/riscv-qemu.ld", diff --git a/targets/riscv.json b/targets/riscv.json index 8874396d1d..313aa85811 100644 --- a/targets/riscv.json +++ b/targets/riscv.json @@ -1,5 +1,4 @@ { - "llvm-target": "riscv32--none", "goos": "linux", "goarch": "arm", "build-tags": ["tinygo.riscv", "baremetal", "linux", "arm"], @@ -9,16 +8,12 @@ "rtlib": "compiler-rt", "libc": "picolibc", "cflags": [ - "--target=riscv32--none", - "-march=rv32imac", - "-mabi=ilp32", "-Os", "-Werror", "-fno-exceptions", "-fno-unwind-tables", "-ffunction-sections", "-fdata-sections" ], "ldflags": [ - "-melf32lriscv", "--gc-sections" ], "extra-files": [ diff --git a/targets/riscv32.json b/targets/riscv32.json new file mode 100644 index 0000000000..dc07c209c3 --- /dev/null +++ b/targets/riscv32.json @@ -0,0 +1,12 @@ +{ + "inherits": ["riscv"], + "llvm-target": "riscv32--none", + "cflags": [ + "--target=riscv32--none", + "-march=rv32imac", + "-mabi=ilp32" + ], + "ldflags": [ + "-melf32lriscv" + ] +} diff --git a/targets/riscv64.json b/targets/riscv64.json new file mode 100644 index 0000000000..a2a0641f98 --- /dev/null +++ b/targets/riscv64.json @@ -0,0 +1,13 @@ +{ + "inherits": ["riscv"], + "llvm-target": "riscv64--none", + "build-tags": ["tinygo.riscv64"], + "cflags": [ + "--target=riscv64--none", + "-march=rv64gc", + "-mabi=lp64" + ], + "ldflags": [ + "-melf64lriscv" + ] +} diff --git a/targets/stm32f4disco.json b/targets/stm32f4disco.json index c142ab23ba..889d7c5135 100644 --- a/targets/stm32f4disco.json +++ b/targets/stm32f4disco.json @@ -1,9 +1,7 @@ { - "inherits": ["cortex-m"], - "llvm-target": "armv7em-none-eabi", + "inherits": ["cortex-m4"], "build-tags": ["stm32f4disco", "stm32f407", "stm32"], "cflags": [ - "--target=armv7em-none-eabi", "-Qunused-arguments" ], "linkerscript": "targets/stm32f407.ld", diff --git a/targets/wioterminal.json b/targets/wioterminal.json new file mode 100644 index 0000000000..04a80dda88 --- /dev/null +++ b/targets/wioterminal.json @@ -0,0 +1,8 @@ +{ + "inherits": ["atsamd51p19a"], + "build-tags": ["sam", "atsamd51p19a", "wioterminal"], + "flash-1200-bps-reset": "true", + "flash-method": "msd", + "msd-volume-name": "Arduino", + "msd-firmware-name": "firmware.uf2" +} diff --git a/testdata/atomic.go b/testdata/atomic.go new file mode 100644 index 0000000000..f99a39bb51 --- /dev/null +++ b/testdata/atomic.go @@ -0,0 +1,95 @@ +package main + +import ( + "sync/atomic" + "unsafe" +) + +func main() { + i32 := int32(-5) + println("AddInt32:", atomic.AddInt32(&i32, 8), i32) + + i64 := int64(-5) + println("AddInt64:", atomic.AddInt64(&i64, 8), i64) + + u32 := uint32(5) + println("AddUint32:", atomic.AddUint32(&u32, 8), u32) + + u64 := uint64(5) + println("AddUint64:", atomic.AddUint64(&u64, 8), u64) + + uptr := uintptr(5) + println("AddUintptr:", uint64(atomic.AddUintptr(&uptr, 8)), uint64(uptr)) + + println("SwapInt32:", atomic.SwapInt32(&i32, 33), i32) + println("SwapInt64:", atomic.SwapInt64(&i64, 33), i64) + println("SwapUint32:", atomic.SwapUint32(&u32, 33), u32) + println("SwapUint64:", atomic.SwapUint64(&u64, 33), u64) + println("SwapUintptr:", uint64(atomic.SwapUintptr(&uptr, 33)), uint64(uptr)) + ptr := unsafe.Pointer(&i32) + println("SwapPointer:", atomic.SwapPointer(&ptr, unsafe.Pointer(&u32)) == unsafe.Pointer(&i32), ptr == unsafe.Pointer(&u32)) + + i32 = int32(-5) + println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, 5, 3), i32) + println("CompareAndSwapInt32:", atomic.CompareAndSwapInt32(&i32, -5, 3), i32) + + i64 = int64(-5) + println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, 5, 3), i64) + println("CompareAndSwapInt64:", atomic.CompareAndSwapInt64(&i64, -5, 3), i64) + + u32 = uint32(5) + println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 4, 3), u32) + println("CompareAndSwapUint32:", atomic.CompareAndSwapUint32(&u32, 5, 3), u32) + + u64 = uint64(5) + println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 4, 3), u64) + println("CompareAndSwapUint64:", atomic.CompareAndSwapUint64(&u64, 5, 3), u64) + + uptr = uintptr(5) + println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 4, 3), uint64(uptr)) + println("CompareAndSwapUintptr:", atomic.CompareAndSwapUintptr(&uptr, 5, 3), uint64(uptr)) + + ptr = unsafe.Pointer(&i32) + println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&u32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i32)) + println("CompareAndSwapPointer:", atomic.CompareAndSwapPointer(&ptr, unsafe.Pointer(&i32), unsafe.Pointer(&i64)), ptr == unsafe.Pointer(&i64)) + + println("LoadInt32:", atomic.LoadInt32(&i32)) + println("LoadInt64:", atomic.LoadInt64(&i64)) + println("LoadUint32:", atomic.LoadUint32(&u32)) + println("LoadUint64:", atomic.LoadUint64(&u64)) + println("LoadUintptr:", uint64(atomic.LoadUintptr(&uptr))) + println("LoadPointer:", atomic.LoadPointer(&ptr) == unsafe.Pointer(&i64)) + + atomic.StoreInt32(&i32, -20) + println("StoreInt32:", i32) + + atomic.StoreInt64(&i64, -20) + println("StoreInt64:", i64) + + atomic.StoreUint32(&u32, 20) + println("StoreUint32:", u32) + + atomic.StoreUint64(&u64, 20) + println("StoreUint64:", u64) + + atomic.StoreUintptr(&uptr, 20) + println("StoreUintptr:", uint64(uptr)) + + atomic.StorePointer(&ptr, unsafe.Pointer(&uptr)) + println("StorePointer:", ptr == unsafe.Pointer(&uptr)) + + // test atomic.Value load/store operations + testValue(int(3), int(-2)) + testValue("", "foobar", "baz") +} + +func testValue(values ...interface{}) { + var av atomic.Value + for _, val := range values { + av.Store(val) + loadedVal := av.Load() + if loadedVal != val { + println("val store/load didn't work, expected", val, "but got", loadedVal) + } + } +} diff --git a/testdata/atomic.txt b/testdata/atomic.txt new file mode 100644 index 0000000000..d1f2ab2937 --- /dev/null +++ b/testdata/atomic.txt @@ -0,0 +1,35 @@ +AddInt32: 3 3 +AddInt64: 3 3 +AddUint32: 13 13 +AddUint64: 13 13 +AddUintptr: 13 13 +SwapInt32: 3 33 +SwapInt64: 3 33 +SwapUint32: 13 33 +SwapUint64: 13 33 +SwapUintptr: 13 33 +SwapPointer: true true +CompareAndSwapInt32: false -5 +CompareAndSwapInt32: true 3 +CompareAndSwapInt64: false -5 +CompareAndSwapInt64: true 3 +CompareAndSwapUint32: false 5 +CompareAndSwapUint32: true 3 +CompareAndSwapUint64: false 5 +CompareAndSwapUint64: true 3 +CompareAndSwapUintptr: false 5 +CompareAndSwapUintptr: true 3 +CompareAndSwapPointer: false true +CompareAndSwapPointer: true true +LoadInt32: 3 +LoadInt64: 3 +LoadUint32: 3 +LoadUint64: 3 +LoadUintptr: 3 +LoadPointer: true +StoreInt32: -20 +StoreInt64: -20 +StoreUint32: 20 +StoreUint64: 20 +StoreUintptr: 20 +StorePointer: true diff --git a/testdata/channel.go b/testdata/channel.go index 63011d50f6..6a7945e5d1 100644 --- a/testdata/channel.go +++ b/testdata/channel.go @@ -2,45 +2,23 @@ package main import ( "runtime" + "sync" "time" ) -// waitGroup is a small type reimplementing some of the behavior of sync.WaitGroup -type waitGroup uint +var wg sync.WaitGroup -func (wg *waitGroup) wait() { - n := 0 - for *wg != 0 { - // pause and wait to be rescheduled - runtime.Gosched() - - if n > 100 { - // if something is using the sleep queue, this may be necessary - time.Sleep(time.Millisecond) - } - - n++ - } -} - -func (wg *waitGroup) add(n uint) { - *wg += waitGroup(n) -} - -func (wg *waitGroup) done() { - if *wg == 0 { - panic("wait group underflow") - } - *wg-- -} - -var wg waitGroup +type intchan chan int func main() { - ch := make(chan int) + ch := make(chan int, 2) + ch <- 1 + println("len, cap of channel:", len(ch), cap(ch), ch == nil) + + ch = make(chan int) println("len, cap of channel:", len(ch), cap(ch), ch == nil) - wg.add(1) + wg.Add(1) go sender(ch) n, ok := <-ch @@ -50,7 +28,7 @@ func main() { println("received num:", n) } - wg.wait() + wg.Wait() n, ok = <-ch println("recv from closed channel:", n, ok) @@ -64,57 +42,66 @@ func main() { _ = make(chan int, uint32(2)) _ = make(chan int, uint64(2)) + // Test that named channels don't crash the compiler. + named := make(intchan, 1) + named <- 3 + <-named + select { + case <-named: + default: + } + // Test bigger values ch2 := make(chan complex128) - wg.add(1) + wg.Add(1) go sendComplex(ch2) println("complex128:", <-ch2) - wg.wait() + wg.Wait() // Test multi-sender. ch = make(chan int) - wg.add(3) + wg.Add(3) go fastsender(ch, 10) go fastsender(ch, 23) go fastsender(ch, 40) slowreceiver(ch) - wg.wait() + wg.Wait() // Test multi-receiver. ch = make(chan int) - wg.add(3) + wg.Add(3) go fastreceiver(ch) go fastreceiver(ch) go fastreceiver(ch) slowsender(ch) - wg.wait() + wg.Wait() // Test iterator style channel. ch = make(chan int) - wg.add(1) + wg.Add(1) go iterator(ch, 100) sum := 0 for i := range ch { sum += i } - wg.wait() + wg.Wait() println("sum(100):", sum) // Test simple selects. go selectDeadlock() // cannot use waitGroup here - never terminates - wg.add(1) + wg.Add(1) go selectNoOp() - wg.wait() + wg.Wait() // Test select with a single send operation (transformed into chan send). ch = make(chan int) - wg.add(1) + wg.Add(1) go fastreceiver(ch) select { case ch <- 5: } close(ch) - wg.wait() + wg.Wait() println("did send one") // Test select with a single recv operation (transformed into chan recv). @@ -125,11 +112,11 @@ func main() { // Test select recv with channel that has one entry. ch = make(chan int) - wg.add(1) + wg.Add(1) go func(ch chan int) { runtime.Gosched() ch <- 55 - wg.done() + wg.Done() }(ch) select { case make(chan int) <- 3: @@ -141,7 +128,7 @@ func main() { case n := <-make(chan int): println("unreachable:", n) } - wg.wait() + wg.Wait() // Test select recv with closed channel. close(ch) @@ -156,7 +143,7 @@ func main() { // Test select send. ch = make(chan int) - wg.add(1) + wg.Add(1) go fastreceiver(ch) select { case ch <- 235: @@ -165,7 +152,7 @@ func main() { println("unreachable:", n) } close(ch) - wg.wait() + wg.Wait() // test non-concurrent buffered channels ch = make(chan int, 2) @@ -183,7 +170,7 @@ func main() { println("closed buffered channel recieve:", <-ch) // test using buffered channels as regular channels with special properties - wg.add(6) + wg.Add(6) ch = make(chan int, 2) go send(ch) go send(ch) @@ -191,7 +178,7 @@ func main() { go send(ch) go receive(ch) go receive(ch) - wg.wait() + wg.Wait() close(ch) var count int for range ch { @@ -204,19 +191,19 @@ func main() { sch1 := make(chan int) sch2 := make(chan int) sch3 := make(chan int) - wg.add(3) + wg.Add(3) go func() { - defer wg.done() + defer wg.Done() time.Sleep(time.Millisecond) sch1 <- 1 }() go func() { - defer wg.done() + defer wg.Done() time.Sleep(time.Millisecond) sch2 <- 2 }() go func() { - defer wg.done() + defer wg.Done() // merge sch2 and sch3 into ch for i := 0; i < 2; i++ { var v int @@ -240,18 +227,18 @@ func main() { sum += v } } - wg.wait() + wg.Wait() println("blocking select sum:", sum) } func send(ch chan<- int) { ch <- 1 - wg.done() + wg.Done() } func receive(ch <-chan int) { <-ch - wg.done() + wg.Done() } func sender(ch chan int) { @@ -263,18 +250,18 @@ func sender(ch chan int) { ch <- i } close(ch) - wg.done() + wg.Done() } func sendComplex(ch chan complex128) { ch <- 7 + 10.5i - wg.done() + wg.Done() } func fastsender(ch chan int, n int) { ch <- n ch <- n + 1 - wg.done() + wg.Done() } func slowreceiver(ch chan int) { @@ -300,7 +287,7 @@ func fastreceiver(ch chan int) { sum += n } println("sum:", sum) - wg.done() + wg.Done() } func iterator(ch chan int, top int) { @@ -308,7 +295,7 @@ func iterator(ch chan int, top int) { ch <- i } close(ch) - wg.done() + wg.Done() } func selectDeadlock() { @@ -323,5 +310,5 @@ func selectNoOp() { default: } println("after no-op") - wg.done() + wg.Done() } diff --git a/testdata/channel.txt b/testdata/channel.txt index b7036f2b27..883a547a64 100644 --- a/testdata/channel.txt +++ b/testdata/channel.txt @@ -1,3 +1,4 @@ +len, cap of channel: 1 2 false len, cap of channel: 0 0 false recv from open channel: 1 true received num: 2 diff --git a/testdata/coroutines.go b/testdata/coroutines.go index 77e14d0e0a..bb8acdbc60 100644 --- a/testdata/coroutines.go +++ b/testdata/coroutines.go @@ -1,6 +1,9 @@ package main -import "time" +import ( + "sync" + "time" +) func main() { println("main 1") @@ -51,6 +54,31 @@ func main() { println("closure go call result:", x) time.Sleep(2 * time.Millisecond) + + var m sync.Mutex + m.Lock() + println("pre-acquired mutex") + go acquire(&m) + time.Sleep(2 * time.Millisecond) + println("releasing mutex") + m.Unlock() + time.Sleep(2 * time.Millisecond) + m.Lock() + println("re-acquired mutex") + m.Unlock() + println("done") + + startSimpleFunc(emptyFunc) + + time.Sleep(2 * time.Millisecond) +} + +func acquire(m *sync.Mutex) { + m.Lock() + println("acquired mutex from goroutine") + time.Sleep(2 * time.Millisecond) + m.Unlock() + println("released mutex from goroutine") } func sub() { @@ -74,6 +102,11 @@ func sleepFuncValue(fn func(int)) { go fn(8) } +func startSimpleFunc(fn simpleFunc) { + // Test that named function types don't crash the compiler. + go fn() +} + func nowait() { println("non-blocking goroutine") } @@ -89,3 +122,8 @@ func (i *myPrinter) Print() { time.Sleep(time.Millisecond) println("async interface method call") } + +type simpleFunc func() + +func emptyFunc() { +} diff --git a/testdata/coroutines.txt b/testdata/coroutines.txt index e296f8e0ce..1e29558afc 100644 --- a/testdata/coroutines.txt +++ b/testdata/coroutines.txt @@ -14,3 +14,9 @@ async interface method call slept inside func pointer 8 slept inside closure, with value: 20 8 closure go call result: 1 +pre-acquired mutex +releasing mutex +acquired mutex from goroutine +released mutex from goroutine +re-acquired mutex +done diff --git a/testdata/float.go b/testdata/float.go index 4063281b71..f2b7c52e32 100644 --- a/testdata/float.go +++ b/testdata/float.go @@ -58,14 +58,14 @@ func main() { println(complex128(c64)) // binops on complex numbers - c64 = 5+2i - println("complex64 add: ", c64 + -3+8i) - println("complex64 sub: ", c64 - -3+8i) - println("complex64 mul: ", c64 * -3+8i) - println("complex64 div: ", c64 / -3+8i) - c128 = -5+2i - println("complex128 add:", c128 + 2+6i) - println("complex128 sub:", c128 - 2+6i) - println("complex128 mul:", c128 * 2+6i) - println("complex128 div:", c128 / 2+6i) + c64 = 5 + 2i + println("complex64 add: ", c64+-3+8i) + println("complex64 sub: ", c64 - -3 + 8i) + println("complex64 mul: ", c64*-3+8i) + println("complex64 div: ", c64/-3+8i) + c128 = -5 + 2i + println("complex128 add:", c128+2+6i) + println("complex128 sub:", c128-2+6i) + println("complex128 mul:", c128*2+6i) + println("complex128 div:", c128/2+6i) } diff --git a/testdata/init.go b/testdata/init.go index 8f81668c53..6e7d9e7baf 100644 --- a/testdata/init.go +++ b/testdata/init.go @@ -38,8 +38,8 @@ var ( uint8SliceSrc = []uint8{3, 100} uint8SliceDst []uint8 - intSliceSrc = []int16{5, 123, 1024} - intSliceDst []int16 + intSliceSrc = []int16{5, 123, 1024} + intSliceDst []int16 ) func init() { diff --git a/testdata/slice.go b/testdata/slice.go index d20de76d3b..b5e5436714 100644 --- a/testdata/slice.go +++ b/testdata/slice.go @@ -31,6 +31,7 @@ func main() { assert(len(make([]int, makeUint32(2), makeUint32(3))) == 2) assert(len(make([]int, makeUint64(2), makeUint64(3))) == 2) assert(len(make([]int, makeUintptr(2), makeUintptr(3))) == 2) + assert(len(make([]int, makeMyUint8(2), makeMyUint8(3))) == 2) // indexing into a slice with uncommon index types assert(foo[int(2)] == 4) @@ -131,6 +132,9 @@ func main() { var named MySlice assert(len(unnamed[:]) == 32) assert(len(named[:]) == 32) + for _, c := range named { + assert(c == 0) + } } func printslice(name string, s []int) { @@ -169,3 +173,4 @@ func makeUint16(x uint16) uint16 { return x } func makeUint32(x uint32) uint32 { return x } func makeUint64(x uint64) uint64 { return x } func makeUintptr(x uintptr) uintptr { return x } +func makeMyUint8(x myUint8) myUint8 { return x } diff --git a/testdata/stdlib.go b/testdata/stdlib.go index b0f2ce396c..b380e80398 100644 --- a/testdata/stdlib.go +++ b/testdata/stdlib.go @@ -9,9 +9,9 @@ import ( func main() { // package os, fmt - fmt.Println("stdin: ", os.Stdin.Fd()) - fmt.Println("stdout:", os.Stdout.Fd()) - fmt.Println("stderr:", os.Stderr.Fd()) + fmt.Println("stdin: ", os.Stdin.Name()) + fmt.Println("stdout:", os.Stdout.Name()) + fmt.Println("stderr:", os.Stderr.Name()) // package math/rand fmt.Println("pseudorandom number:", rand.Int31()) diff --git a/testdata/stdlib.txt b/testdata/stdlib.txt index ecf4402dd8..1862d0d220 100644 --- a/testdata/stdlib.txt +++ b/testdata/stdlib.txt @@ -1,6 +1,6 @@ -stdin: 0 -stdout: 1 -stderr: 2 +stdin: /dev/stdin +stdout: /dev/stdout +stderr: /dev/stderr pseudorandom number: 1298498081 strings.IndexByte: 2 strings.Replace: An-example-string diff --git a/testdata/zeroalloc.go b/testdata/zeroalloc.go index 53c6fa0bcb..e5893227fa 100644 --- a/testdata/zeroalloc.go +++ b/testdata/zeroalloc.go @@ -1,4 +1,5 @@ package main + func main() { p := []byte{} for len(p) >= 1 { diff --git a/tests/wasm/chan_test.go b/tests/wasm/chan_test.go new file mode 100644 index 0000000000..1cd08e664d --- /dev/null +++ b/tests/wasm/chan_test.go @@ -0,0 +1,34 @@ +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestChan(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/chan.wasm -target wasm testdata/chan.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=chan.wasm"), + waitLog(`1 +2 +4 +3 +true`), + ) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/event_test.go b/tests/wasm/event_test.go new file mode 100644 index 0000000000..d2b8340ce2 --- /dev/null +++ b/tests/wasm/event_test.go @@ -0,0 +1,47 @@ +// +build go1.14 + +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestEvent(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/event.wasm -target wasm testdata/event.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1, log2 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=event.wasm"), + chromedp.WaitVisible("#log"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`1 +4`), + chromedp.Click("#testbtn"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log2), + waitLog(`1 +4 +2 +3 +true`), + ) + t.Logf("log1: %s", log1) + t.Logf("log2: %s", log2) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/fmt_test.go b/tests/wasm/fmt_test.go new file mode 100644 index 0000000000..8b4fe8c751 --- /dev/null +++ b/tests/wasm/fmt_test.go @@ -0,0 +1,43 @@ +// +build go1.14 + +package wasm + +// NOTE: this should work in go1.13 but panics with: +// panic: syscall/js: call of Value.Get on string +// which is coming from here: https://github.com/golang/go/blob/release-branch.go1.13/src/syscall/js/js.go#L252 +// But I'm not sure how import "fmt" results in this. +// To reproduce, install Go 1.13.x and change the build tag above +// to go1.13 and run this test. + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestFmt(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/fmt.wasm -target wasm testdata/fmt.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=fmt.wasm"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`did not panic`), + ) + t.Logf("log1: %s", log1) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/fmtprint_test.go b/tests/wasm/fmtprint_test.go new file mode 100644 index 0000000000..ebd1ffa997 --- /dev/null +++ b/tests/wasm/fmtprint_test.go @@ -0,0 +1,39 @@ +// +build go1.14 + +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestFmtprint(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/fmtprint.wasm -target wasm testdata/fmtprint.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=fmtprint.wasm"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLog(`test from fmtprint 1 +test from fmtprint 2 +test from fmtprint 3 +test from fmtprint 4`), + ) + t.Logf("log1: %s", log1) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/log_test.go b/tests/wasm/log_test.go new file mode 100644 index 0000000000..a9cc1befaa --- /dev/null +++ b/tests/wasm/log_test.go @@ -0,0 +1,44 @@ +// +build go1.14 + +package wasm + +import ( + "testing" + "time" + + "github.com/chromedp/chromedp" +) + +func TestLog(t *testing.T) { + + t.Parallel() + + err := run("tinygo build -o " + wasmTmpDir + "/log.wasm -target wasm testdata/log.go") + if err != nil { + t.Fatal(err) + } + + ctx, cancel := chromectx(5 * time.Second) + defer cancel() + + var log1 string + err = chromedp.Run(ctx, + chromedp.Navigate("http://localhost:8826/run?file=log.wasm"), + chromedp.Sleep(time.Second), + chromedp.InnerHTML("#log", &log1), + waitLogRe(`^..../../.. ..:..:.. log 1 +..../../.. ..:..:.. log 2 +..../../.. ..:..:.. log 3 +println 4 +fmt.Println 5 +..../../.. ..:..:.. log 6 +in func 1 +..../../.. ..:..:.. in func 2 +$`), + ) + t.Logf("log1: %s", log1) + if err != nil { + t.Fatal(err) + } + +} diff --git a/tests/wasm/setup_test.go b/tests/wasm/setup_test.go new file mode 100644 index 0000000000..a38b426545 --- /dev/null +++ b/tests/wasm/setup_test.go @@ -0,0 +1,201 @@ +package wasm + +import ( + "context" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/exec" + "regexp" + "strings" + "testing" + "time" + + "github.com/chromedp/cdproto/cdp" + "github.com/chromedp/chromedp" +) + +var addr = flag.String("addr", ":8826", "Host:port to listen on for wasm test server") + +var wasmTmpDir string // set in TestMain to a temp directory for build output + +func TestMain(m *testing.M) { + flag.Parse() + + os.Exit(func() int { + + var err error + wasmTmpDir, err = ioutil.TempDir("", "wasm_test") + if err != nil { + log.Fatalf("unable to create temp dir: %v", err) + } + defer os.RemoveAll(wasmTmpDir) // cleanup even on panic and before os.Exit + + startServer(wasmTmpDir) + + return m.Run() + }()) + +} + +func run(cmdline string) error { + args := strings.Fields(cmdline) + return runargs(args...) +} + +func runargs(args ...string) error { + cmd := exec.Command(args[0], args[1:]...) + b, err := cmd.CombinedOutput() + log.Printf("Command: %s; err=%v; full output:\n%s", strings.Join(args, " "), err, b) + if err != nil { + return err + } + return nil +} + +func chromectx(timeout time.Duration) (context.Context, context.CancelFunc) { + + var ctx context.Context + + // looks for locally installed Chrome + ctx, _ = chromedp.NewContext(context.Background()) + + ctx, cancel := context.WithTimeout(ctx, timeout) + + return ctx, cancel +} + +func startServer(tmpDir string) { + + fsh := http.FileServer(http.Dir(tmpDir)) + h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + if r.URL.Path == "/wasm_exec.js" { + http.ServeFile(w, r, "../../targets/wasm_exec.js") + return + } + + if r.URL.Path == "/run" { + fmt.Fprintf(w, ` + + +Test + + + +
+

+
+
+
+
+`, r.FormValue("file"))
+			return
+		}
+
+		fsh.ServeHTTP(w, r)
+	})
+
+	log.Printf("Starting server at %q for dir: %s", *addr, tmpDir)
+	go func() {
+		log.Fatal(http.ListenAndServe(*addr, h))
+	}()
+
+}
+
+// waitLog blocks until the log output equals the text provided (ignoring whitespace before and after)
+func waitLog(logText string) chromedp.QueryAction {
+	return waitInnerTextTrimEq("#log", strings.TrimSpace(logText))
+}
+
+// waitLogRe blocks until the log output matches this regular expression
+func waitLogRe(restr string) chromedp.QueryAction {
+	return waitInnerTextMatch("#log", regexp.MustCompile(restr))
+}
+
+// waitInnerTextTrimEq will wait for the innerText of the specified element to match a specific text pattern (ignoring whitespace before and after)
+func waitInnerTextTrimEq(sel string, innerText string) chromedp.QueryAction {
+	return waitInnerTextMatch(sel, regexp.MustCompile(`^\s*`+regexp.QuoteMeta(innerText)+`\s*$`))
+}
+
+// waitInnerTextMatch will wait for the innerText of the specified element to match a specific regexp pattern
+func waitInnerTextMatch(sel string, re *regexp.Regexp) chromedp.QueryAction {
+
+	return chromedp.Query(sel, func(s *chromedp.Selector) {
+
+		chromedp.WaitFunc(func(ctx context.Context, cur *cdp.Frame, ids ...cdp.NodeID) ([]*cdp.Node, error) {
+
+			nodes := make([]*cdp.Node, len(ids))
+			cur.RLock()
+			for i, id := range ids {
+				nodes[i] = cur.Nodes[id]
+				if nodes[i] == nil {
+					cur.RUnlock()
+					// not yet ready
+					return nil, nil
+				}
+			}
+			cur.RUnlock()
+
+			var ret string
+			err := chromedp.EvaluateAsDevTools("document.querySelector('"+sel+"').innerText", &ret).Do(ctx)
+			if err != nil {
+				return nodes, err
+			}
+			if !re.MatchString(ret) {
+				// log.Printf("found text: %s", ret)
+				return nodes, errors.New("unexpected value: " + ret)
+			}
+
+			// log.Printf("NodeValue: %#v", nodes[0])
+
+			// return nil, errors.New("not ready yet")
+			return nodes, nil
+		})(s)
+
+	})
+
+}
diff --git a/tests/wasm/testdata/chan.go b/tests/wasm/testdata/chan.go
new file mode 100644
index 0000000000..8f10d1f8d8
--- /dev/null
+++ b/tests/wasm/testdata/chan.go
@@ -0,0 +1,16 @@
+package main
+
+func main() {
+
+	ch := make(chan bool, 1)
+	println("1")
+	go func() {
+		println("2")
+		ch <- true
+		println("3")
+	}()
+	println("4")
+	v := <-ch
+	println(v)
+
+}
diff --git a/tests/wasm/testdata/event.go b/tests/wasm/testdata/event.go
new file mode 100644
index 0000000000..4153774ff1
--- /dev/null
+++ b/tests/wasm/testdata/event.go
@@ -0,0 +1,31 @@
+package main
+
+import "syscall/js"
+
+func main() {
+
+	ch := make(chan bool, 1)
+
+	println("1")
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#main").
+		Set("innerHTML", ``)
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#testbtn").
+		Call("addEventListener", "click",
+			js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+				println("2")
+				ch <- true
+				println("3")
+				return nil
+			}))
+
+	println("4")
+	v := <-ch
+	println(v)
+
+}
diff --git a/tests/wasm/testdata/fmt.go b/tests/wasm/testdata/fmt.go
new file mode 100644
index 0000000000..b51c564cd5
--- /dev/null
+++ b/tests/wasm/testdata/fmt.go
@@ -0,0 +1,8 @@
+package main
+
+import "fmt"
+
+func main() {
+	var _ fmt.Stringer
+	println("did not panic")
+}
diff --git a/tests/wasm/testdata/fmtprint.go b/tests/wasm/testdata/fmtprint.go
new file mode 100644
index 0000000000..1bad3361c7
--- /dev/null
+++ b/tests/wasm/testdata/fmtprint.go
@@ -0,0 +1,11 @@
+package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("test from fmtprint 1")
+	fmt.Print("test from fmtprint 2\n")
+	fmt.Print("test from fmtp")
+	fmt.Print("rint 3\n")
+	fmt.Printf("test from fmtprint %d\n", 4)
+}
diff --git a/tests/wasm/testdata/log.go b/tests/wasm/testdata/log.go
new file mode 100644
index 0000000000..c16fe1d015
--- /dev/null
+++ b/tests/wasm/testdata/log.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"syscall/js"
+)
+
+func main() {
+
+	// try various log and other output directly
+	log.Println("log 1")
+	log.Print("log 2")
+	log.Printf("log %d\n", 3)
+	println("println 4")
+	fmt.Println("fmt.Println 5")
+	log.Printf("log %s", "6")
+
+	// now set up some log output in a button click callback
+	js.Global().
+		Get("document").
+		Call("querySelector", "#main").
+		Set("innerHTML", ``)
+
+	js.Global().
+		Get("document").
+		Call("querySelector", "#testbtn").
+		Call("addEventListener", "click",
+			js.FuncOf(func(this js.Value, args []js.Value) interface{} {
+				println("in func 1")
+				log.Printf("in func 2")
+				return nil
+			}))
+
+	// click the button
+	js.Global().
+		Get("document").
+		Call("querySelector", "#testbtn").
+		Call("click")
+
+}
diff --git a/tools/gen-device-svd/gen-device-svd.go b/tools/gen-device-svd/gen-device-svd.go
index 2e64651e58..e63b026a02 100755
--- a/tools/gen-device-svd/gen-device-svd.go
+++ b/tools/gen-device-svd/gen-device-svd.go
@@ -342,6 +342,11 @@ func readSVD(path, sourceURL string) (*Device, error) {
 				firstAddress := clusterRegisters[0].address
 				dimIncrement = int(lastAddress - firstAddress)
 			}
+
+			if !unicode.IsUpper(rune(clusterName[0])) && !unicode.IsDigit(rune(clusterName[0])) {
+				clusterName = strings.ToUpper(clusterName)
+			}
+
 			p.registers = append(p.registers, &PeripheralField{
 				name:        clusterName,
 				address:     baseAddress + clusterOffset,
@@ -681,6 +686,8 @@ var (
 
 			var regType string
 			switch register.elementSize {
+			case 8:
+				regType = "volatile.Register64"
 			case 4:
 				regType = "volatile.Register32"
 			case 2:
@@ -710,6 +717,8 @@ var (
 				for _, subregister := range register.registers {
 					var subregType string
 					switch subregister.elementSize {
+					case 8:
+						subregType = "volatile.Register64"
 					case 4:
 						subregType = "volatile.Register32"
 					case 2:
diff --git a/transform/func-lowering.go b/transform/func-lowering.go
index caef38427f..03d1f440d0 100644
--- a/transform/func-lowering.go
+++ b/transform/func-lowering.go
@@ -225,6 +225,10 @@ func addFuncLoweringSwitch(mod llvm.Module, builder llvm.Builder, funcID, call l
 	// in this gap.
 	nextBlock := llvmutil.SplitBasicBlock(builder, sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
 
+	// Temporarily set the insert point to set the correct debug insert location
+	// for the builder. It got destroyed by the SplitBasicBlock call.
+	builder.SetInsertPointBefore(call)
+
 	// The 0 case, which is actually a nil check.
 	nilBlock := ctx.InsertBasicBlock(nextBlock, "func.nil")
 	builder.SetInsertPointAtEnd(nilBlock)
diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go
index 53e248d3a6..6aa3bb1347 100644
--- a/transform/interface-lowering.go
+++ b/transform/interface-lowering.go
@@ -292,58 +292,40 @@ func (p *lowerInterfacesPass) run() error {
 
 		methodSet := use.Operand(1).Operand(0) // global variable
 		itf := p.interfaces[methodSet.Name()]
-		if len(itf.types) == 0 {
-			// This method call is impossible: no type implements this
-			// interface. In fact, the previous type assert that got this
-			// interface value should already have returned false.
-			// Replace the function pointer with undef (which will then be
-			// called), indicating to the optimizer this code is unreachable.
-			use.ReplaceAllUsesWith(llvm.Undef(p.uintptrType))
-			use.EraseFromParentAsInstruction()
-		} else if len(itf.types) == 1 {
-			// There is only one implementation of the given type.
-			// Call that function directly.
-			err := p.replaceInvokeWithCall(use, itf.types[0], signature)
-			if err != nil {
-				return err
-			}
-		} else {
-			// There are multiple types implementing this interface, thus there
-			// are multiple possible functions to call. Delegate calling the
-			// right function to a special wrapper function.
-			inttoptrs := getUses(use)
-			if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
-				return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod")
+
+		// Delegate calling the right function to a special wrapper function.
+		inttoptrs := getUses(use)
+		if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
+			return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod")
+		}
+		inttoptr := inttoptrs[0]
+		calls := getUses(inttoptr)
+		for _, call := range calls {
+			// Set up parameters for the call. First copy the regular params...
+			params := make([]llvm.Value, call.OperandsCount())
+			paramTypes := make([]llvm.Type, len(params))
+			for i := 0; i < len(params)-1; i++ {
+				params[i] = call.Operand(i)
+				paramTypes[i] = params[i].Type()
 			}
-			inttoptr := inttoptrs[0]
-			calls := getUses(inttoptr)
-			for _, call := range calls {
-				// Set up parameters for the call. First copy the regular params...
-				params := make([]llvm.Value, call.OperandsCount())
-				paramTypes := make([]llvm.Type, len(params))
-				for i := 0; i < len(params)-1; i++ {
-					params[i] = call.Operand(i)
-					paramTypes[i] = params[i].Type()
-				}
-				// then add the typecode to the end of the list.
-				params[len(params)-1] = typecode
-				paramTypes[len(params)-1] = p.uintptrType
-
-				// Create a function that redirects the call to the destination
-				// call, after selecting the right concrete type.
-				redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
-
-				// Replace the old lookup/inttoptr/call with the new call.
-				p.builder.SetInsertPointBefore(call)
-				retval := p.builder.CreateCall(redirector, append(params, llvm.ConstNull(llvm.PointerType(p.ctx.Int8Type(), 0))), "")
-				if retval.Type().TypeKind() != llvm.VoidTypeKind {
-					call.ReplaceAllUsesWith(retval)
-				}
-				call.EraseFromParentAsInstruction()
+			// then add the typecode to the end of the list.
+			params[len(params)-1] = typecode
+			paramTypes[len(params)-1] = p.uintptrType
+
+			// Create a function that redirects the call to the destination
+			// call, after selecting the right concrete type.
+			redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
+
+			// Replace the old lookup/inttoptr/call with the new call.
+			p.builder.SetInsertPointBefore(call)
+			retval := p.builder.CreateCall(redirector, append(params, llvm.ConstNull(llvm.PointerType(p.ctx.Int8Type(), 0))), "")
+			if retval.Type().TypeKind() != llvm.VoidTypeKind {
+				call.ReplaceAllUsesWith(retval)
 			}
-			inttoptr.EraseFromParentAsInstruction()
-			use.EraseFromParentAsInstruction()
+			call.EraseFromParentAsInstruction()
 		}
+		inttoptr.EraseFromParentAsInstruction()
+		use.EraseFromParentAsInstruction()
 	}
 
 	// Replace all typeasserts on interface types with matches on their concrete
@@ -634,10 +616,19 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
 	// Create entry block.
 	entry := p.ctx.AddBasicBlock(fn, "entry")
 
-	// Create default block and make it unreachable (which it is, because all
-	// possible types are checked).
+	// Create default block and call runtime.nilPanic.
+	// The only other possible value remaining is nil for nil interfaces. We
+	// could panic with a different message here such as "nil interface" but
+	// that would increase code size and "nil panic" is close enough. Most
+	// importantly, it avoids undefined behavior when accidentally calling a
+	// method on a nil interface.
 	defaultBlock := p.ctx.AddBasicBlock(fn, "default")
 	p.builder.SetInsertPointAtEnd(defaultBlock)
+	nilPanic := p.mod.NamedFunction("runtime.nilPanic")
+	p.builder.CreateCall(nilPanic, []llvm.Value{
+		llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
+		llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
+	}, "")
 	p.builder.CreateUnreachable()
 
 	// Create type switch in entry block.
diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll
index ee059c959a..1e2ce61b6e 100644
--- a/transform/testdata/interface.ll
+++ b/transform/testdata/interface.ll
@@ -24,6 +24,7 @@ declare void @runtime.printuint8(i8)
 declare void @runtime.printint32(i32)
 declare void @runtime.printptr(i32)
 declare void @runtime.printnl()
+declare void @runtime.nilPanic(i8*, i8*)
 
 define void @printInterfaces() {
   call void @printInterface(i32 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*))
diff --git a/transform/testdata/interface.out.ll b/transform/testdata/interface.out.ll
index 9ae8b48867..4bb9ec1bbc 100644
--- a/transform/testdata/interface.out.ll
+++ b/transform/testdata/interface.out.ll
@@ -25,6 +25,8 @@ declare void @runtime.printptr(i32)
 
 declare void @runtime.printnl()
 
+declare void @runtime.nilPanic(i8*, i8*)
+
 define void @printInterfaces() {
   call void @printInterface(i32 4, i8* inttoptr (i32 5 to i8*))
   call void @printInterface(i32 16, i8* inttoptr (i8 120 to i8*))
@@ -47,8 +49,8 @@ typeswitch.notUnmatched:                          ; preds = %0
   br i1 %typeassert.ok, label %typeswitch.Doubler, label %typeswitch.notDoubler
 
 typeswitch.Doubler:                               ; preds = %typeswitch.notUnmatched
-  %doubler.result = call i32 @"(Number).Double$invoke"(i8* %value, i8* null)
-  call void @runtime.printint32(i32 %doubler.result)
+  %1 = call i32 @"(Doubler).Double"(i8* %value, i8* null, i32 %typecode, i8* null)
+  call void @runtime.printint32(i32 %1)
   ret void
 
 typeswitch.notDoubler:                            ; preds = %typeswitch.notUnmatched
@@ -76,6 +78,21 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) {
   ret i32 %ret
 }
 
+define internal i32 @"(Doubler).Double"(i8* %0, i8* %1, i32 %actualType, i8* %parentHandle) unnamed_addr {
+entry:
+  switch i32 %actualType, label %default [
+    i32 68, label %"reflect/types.type:named:Number"
+  ]
+
+default:                                          ; preds = %entry
+  call void @runtime.nilPanic(i8* undef, i8* undef)
+  unreachable
+
+"reflect/types.type:named:Number":                ; preds = %entry
+  %2 = call i32 @"(Number).Double$invoke"(i8* %0, i8* %1)
+  ret i32 %2
+}
+
 define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr {
 entry:
   switch i32 %actualType, label %else [
diff --git a/version.go b/version.go
deleted file mode 100644
index 44e4994c39..0000000000
--- a/version.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package main
-
-// version of this package.
-// Update this value before release of new version of software.
-const version = "0.13.1"