diff --git a/.gitmodules b/.gitmodules index c9e2db4e..0e29f9f1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,7 @@ [submodule "lib/cmsis-svd"] path = lib/cmsis-svd url = https://github.com/posborne/cmsis-svd +[submodule "lib/compiler-rt"] + path = lib/compiler-rt + url = https://github.com/llvm-mirror/compiler-rt.git + branch = release_70 diff --git a/.travis.yml b/.travis.yml index 3c472f2b..8cf56033 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,8 +8,8 @@ before_install: - echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list - echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list - sudo apt-get update -qq - - sudo apt-get install llvm-7-dev gcc-8 gcc-arm-none-eabi --allow-unauthenticated -y - - sudo ln -s /usr/bin/gcc-8 /usr/local/bin/cc # work around missing -no-pie in old GCC version + - sudo apt-get install llvm-7-dev clang-7 gcc-arm-none-eabi --allow-unauthenticated -y + - sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version install: - go get github.com/aykevl/go-llvm diff --git a/buildcache.go b/buildcache.go new file mode 100644 index 00000000..3c2ea4b3 --- /dev/null +++ b/buildcache.go @@ -0,0 +1,108 @@ +package main + +import ( + "io" + "os" + "path/filepath" + "time" +) + +// Get the cache directory, usually ~/.cache/tinygo +func cacheDir() string { + home := getHomeDir() + dir := filepath.Join(home, ".cache", "tinygo") + return dir +} + +// Return the newest timestamp of all the file paths passed in. Used to check +// for stale caches. +func cacheTimestamp(paths []string) (time.Time, error) { + var timestamp time.Time + for _, path := range paths { + st, err := os.Stat(path) + if err != nil { + return time.Time{}, err + } + if timestamp.IsZero() { + timestamp = st.ModTime() + } else if timestamp.Before(st.ModTime()) { + timestamp = st.ModTime() + } + } + return timestamp, nil +} + +// Try to load a given file from the cache. Return "", nil if no cached file can +// be found (or the file is stale), return the absolute path if there is a cache +// and return an error on I/O errors. +// +// TODO: the configKey is currently ignored. It is supposed to be used as extra +// data for the cache key, like the compiler version and arguments. +func cacheLoad(name, configKey string, sourceFiles []string) (string, error) { + dir := cacheDir() + cachepath := filepath.Join(dir, name) + cacheStat, err := os.Stat(cachepath) + if os.IsNotExist(err) { + return "", nil // does not exist + } else if err != nil { + return "", err // cannot stat cache file + } + + sourceTimestamp, err := cacheTimestamp(sourceFiles) + if err != nil { + return "", err // cannot stat source files + } + + if cacheStat.ModTime().After(sourceTimestamp) { + return cachepath, nil + } else { + os.Remove(cachepath) + // stale cache + return "", nil + } +} + +// Store the file located at tmppath in the cache with the given name. The +// tmppath may or may not be gone afterwards. +// +// Note: the configKey is ignored, see cacheLoad. +func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) { + // get the last modified time + if len(sourceFiles) == 0 { + panic("cache: no source files") + } + + // TODO: check the config key + + dir := cacheDir() + err := os.MkdirAll(dir, 0777) + if err != nil { + return "", err + } + cachepath := filepath.Join(dir, name) + err = os.Rename(tmppath, cachepath) + if err != nil { + inf, err := os.Open(tmppath) + if err != nil { + return "", err + } + defer inf.Close() + outf, err := os.Create(cachepath + ".tmp") + if err != nil { + return "", err + } + + _, err = io.Copy(outf, inf) + if err != nil { + return "", err + } + + err = os.Rename(cachepath+".tmp", cachepath) + if err != nil { + return "", err + } + + return cachepath, outf.Close() + } + return cachepath, nil +} diff --git a/builtins.go b/builtins.go new file mode 100644 index 00000000..8c1817ed --- /dev/null +++ b/builtins.go @@ -0,0 +1,186 @@ +package main + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" +) + +// These are the GENERIC_SOURCES according to CMakeList.txt. +var genericBuiltins = []string{ + "absvdi2", + "absvsi2", + "absvti2", + "adddf3", + "addsf3", + "addtf3", + "addvdi3", + "addvsi3", + "addvti3", + "apple_versioning", + "ashldi3", + "ashlti3", + "ashrdi3", + "ashrti3", + "bswapdi2", + "bswapsi2", + "clzdi2", + "clzsi2", + "clzti2", + "cmpdi2", + "cmpti2", + "comparedf2", + "comparesf2", + "ctzdi2", + "ctzsi2", + "ctzti2", + "divdc3", + "divdf3", + "divdi3", + "divmoddi4", + "divmodsi4", + "divsc3", + "divsf3", + "divsi3", + "divtc3", + "divti3", + "divtf3", + "extendsfdf2", + "extendhfsf2", + "ffsdi2", + "ffssi2", + "ffsti2", + "fixdfdi", + "fixdfsi", + "fixdfti", + "fixsfdi", + "fixsfsi", + "fixsfti", + "fixunsdfdi", + "fixunsdfsi", + "fixunsdfti", + "fixunssfdi", + "fixunssfsi", + "fixunssfti", + "floatdidf", + "floatdisf", + "floatsidf", + "floatsisf", + "floattidf", + "floattisf", + "floatundidf", + "floatundisf", + "floatunsidf", + "floatunsisf", + "floatuntidf", + "floatuntisf", + //"int_util", + "lshrdi3", + "lshrti3", + "moddi3", + "modsi3", + "modti3", + "muldc3", + "muldf3", + "muldi3", + "mulodi4", + "mulosi4", + "muloti4", + "mulsc3", + "mulsf3", + "multi3", + "multf3", + "mulvdi3", + "mulvsi3", + "mulvti3", + "negdf2", + "negdi2", + "negsf2", + "negti2", + "negvdi2", + "negvsi2", + "negvti2", + "os_version_check", + "paritydi2", + "paritysi2", + "parityti2", + "popcountdi2", + "popcountsi2", + "popcountti2", + "powidf2", + "powisf2", + "powitf2", + "subdf3", + "subsf3", + "subvdi3", + "subvsi3", + "subvti3", + "subtf3", + "trampoline_setup", + "truncdfhf2", + "truncdfsf2", + "truncsfhf2", + "ucmpdi2", + "ucmpti2", + "udivdi3", + "udivmoddi4", + "udivmodsi4", + "udivmodti4", + "udivsi3", + "udivti3", + "umoddi3", + "umodsi3", + "umodti3", +} + +// Get the builtins archive, possibly generating it as needed. +func loadBuiltins(target string) (path string, err error) { + outfile := "librt-" + target + ".a" + builtinsDir := filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins") + + srcs := make([]string, len(genericBuiltins)) + for i, name := range genericBuiltins { + srcs[i] = filepath.Join(builtinsDir, name+".c") + } + + if path, err := cacheLoad(outfile, commands["clang"], srcs); path != "" || err != nil { + return path, err + } + + dir, err := ioutil.TempDir("", "tinygo-builtins") + if err != nil { + return "", err + } + defer os.RemoveAll(dir) + + // Compile all builtins. + // TODO: use builtins optimized for a given target if available. + objs := make([]string, 0, len(genericBuiltins)) + for _, name := range genericBuiltins { + objpath := filepath.Join(dir, name+".o") + objs = append(objs, objpath) + srcpath := filepath.Join(builtinsDir, name+".c") + cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "--target="+target, "-o", objpath, srcpath) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = dir + err = cmd.Run() + if err != nil { + return "", err + } + } + + // Put all builtins in an archive to link as a static library. + arpath := filepath.Join(dir, "librt.a") + cmd := exec.Command(commands["ar"], append([]string{"cr", arpath}, objs...)...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = dir + err = cmd.Run() + if err != nil { + return "", err + } + + return cacheStore(arpath, outfile, commands["clang"], srcs) +} diff --git a/docs/installation.rst b/docs/installation.rst index 55e2ca20..455fdc5e 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -27,6 +27,8 @@ final binary and flashing it needs some extra tools. * binutils (``arm-none-eabi-objcopy``) for producing .hex files for flashing. * GCC (``arm-none-eabi-gcc``) for linking object files. + * Clang 7 (``clang-7``) for building the `compiler runtime library + `_. * The flashing tool for the particular chip, like ``openocd`` or ``nrfjprog``. diff --git a/lib/compiler-rt b/lib/compiler-rt new file mode 160000 index 00000000..a4cbb02b --- /dev/null +++ b/lib/compiler-rt @@ -0,0 +1 @@ +Subproject commit a4cbb02bca3b952117e9ccfbad8a485857f25935 diff --git a/main.go b/main.go index 1abaef55..529b19b3 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,11 @@ import ( "github.com/aykevl/tinygo/compiler" ) +var commands = map[string]string{ + "ar": "ar", + "clang": "clang-7", +} + // Helper function for Compiler object. func Compile(pkgName, outpath string, spec *TargetSpec, printIR, dumpSSA, debug bool, printSizes string, action func(string) error) error { config := compiler.Config{ @@ -95,10 +100,24 @@ func Compile(pkgName, outpath string, spec *TargetSpec, printIR, dumpSSA, debug return err } + // Load builtins library from the cache, possibly compiling it on the + // fly. + var cachePath string + if spec.CompilerRT { + librt, err := loadBuiltins(spec.Triple) + if err != nil { + return err + } + cachePath, _ = filepath.Split(librt) + } + // Link the object file with the system compiler. executable := filepath.Join(dir, "main") tmppath := executable // final file args := append(spec.PreLinkArgs, "-o", executable, objfile) + if spec.CompilerRT { + args = append(args, "-L", cachePath, "-lrt-"+spec.Triple) + } cmd := exec.Command(spec.Linker, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -371,6 +390,14 @@ func main() { fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) } + case "clean": + // remove cache directory + dir := cacheDir() + err := os.RemoveAll(dir) + if err != nil { + fmt.Fprintln(os.Stderr, "cannot clean cache:", err) + os.Exit(1) + } case "help": usage() case "run": diff --git a/target.go b/target.go index eb4f48b7..87678e50 100644 --- a/target.go +++ b/target.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "os" + "os/user" "path/filepath" "runtime" "strings" @@ -17,6 +18,7 @@ type TargetSpec struct { Triple string `json:"llvm-target"` BuildTags []string `json:"build-tags"` Linker string `json:"linker"` + CompilerRT bool `json:"compiler-rt"` PreLinkArgs []string `json:"pre-link-args"` Objcopy string `json:"objcopy"` Flasher string `json:"flash"` @@ -72,16 +74,19 @@ func getGopath() string { } // fallback - var home string - if runtime.GOOS == "windows" { - home = os.Getenv("USERPROFILE") - } else { - home = os.Getenv("HOME") - } - if home == "" { - // This is very unlikely, so panic here. - // Not the nicest solution, however. - panic("no $HOME or %USERPROFILE% found") - } + home := getHomeDir() return filepath.Join(home, "go") } + +func getHomeDir() string { + u, err := user.Current() + if err != nil { + panic("cannot get current user: " + err.Error()) + } + if u.HomeDir == "" { + // This is very unlikely, so panic here. + // Not the nicest solution, however. + panic("could not find home directory") + } + return u.HomeDir +} diff --git a/targets/bluepill.json b/targets/bluepill.json index 79c9a48e..ed3079b5 100644 --- a/targets/bluepill.json +++ b/targets/bluepill.json @@ -2,6 +2,7 @@ "llvm-target": "armv7m-none-eabi", "build-tags": ["bluepill", "stm32f103xx", "stm32", "arm", "js", "wasm"], "linker": "arm-none-eabi-gcc", + "compiler-rt": true, "pre-link-args": ["-nostdlib", "-nostartfiles", "-mcpu=cortex-m3", "-mthumb", "-T", "targets/stm32.ld", "-Wl,--gc-sections", "-fno-exceptions", "-fno-unwind-tables", "-ffunction-sections", "-fdata-sections", "-Os", "src/device/stm32/stm32f103xx.s"], "objcopy": "arm-none-eabi-objcopy", "flash": "openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program {hex} reset exit'" diff --git a/targets/microbit.json b/targets/microbit.json index 02f4fc26..03bbe721 100644 --- a/targets/microbit.json +++ b/targets/microbit.json @@ -2,6 +2,7 @@ "llvm-target": "armv6m-none-eabi", "build-tags": ["microbit", "nrf51822", "nrf51", "nrf", "arm", "js", "wasm"], "linker": "arm-none-eabi-gcc", + "compiler-rt": true, "pre-link-args": [ "-nostdlib", "-nostartfiles", @@ -11,6 +12,7 @@ "-Wl,--gc-sections", "-fno-exceptions", "-fno-unwind-tables", "-ffunction-sections", "-fdata-sections", + "-fno-short-enums", "-Os", "-DNRF51", "-Ilib/CMSIS/CMSIS/Include", diff --git a/targets/pca10040.json b/targets/pca10040.json index 0f3ff15c..dd9ba375 100644 --- a/targets/pca10040.json +++ b/targets/pca10040.json @@ -2,6 +2,7 @@ "llvm-target": "armv7em-none-eabi", "build-tags": ["pca10040", "nrf52832", "nrf52", "nrf", "arm", "js", "wasm"], "linker": "arm-none-eabi-gcc", + "compiler-rt": true, "pre-link-args": [ "-nostdlib", "-nostartfiles",