Skip to content

Commit ca1793e

Browse files
committed
builder: refactor compiler-rt library
This refactor makes adding a new library (such as a libc) much easier in the future as it avoids a lot of duplicate code. Additionally, CI should become a little bit faster (~15s) as build-builtins now uses the build cache.
1 parent 4d5dafd commit ca1793e

File tree

4 files changed

+133
-117
lines changed

4 files changed

+133
-117
lines changed

builder/build.go

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,21 +129,18 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
129129
return err
130130
}
131131

132+
// Prepare link command.
133+
executable := filepath.Join(dir, "main")
134+
tmppath := executable // final file
135+
ldflags := append(config.LDFlags(), "-o", executable, objfile)
136+
132137
// Load builtins library from the cache, possibly compiling it on the
133138
// fly.
134-
var librt string
135139
if config.Target.RTLib == "compiler-rt" {
136-
librt, err = loadBuiltins(config.Triple())
140+
librt, err := CompilerRT.Load(config.Triple())
137141
if err != nil {
138142
return err
139143
}
140-
}
141-
142-
// Prepare link command.
143-
executable := filepath.Join(dir, "main")
144-
tmppath := executable // final file
145-
ldflags := append(config.LDFlags(), "-o", executable, objfile)
146-
if config.Target.RTLib == "compiler-rt" {
147144
ldflags = append(ldflags, librt)
148145
}
149146

builder/builtins.go

Lines changed: 15 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
package builder
22

33
import (
4-
"io/ioutil"
5-
"os"
6-
"path/filepath"
74
"strings"
8-
9-
"github.com/tinygo-org/tinygo/goenv"
105
)
116

127
// These are the GENERIC_SOURCES according to CMakeList.txt.
@@ -156,105 +151,20 @@ var aeabiBuiltins = []string{
156151
"arm/aeabi_uldivmod.S",
157152
}
158153

159-
func builtinFiles(target string) []string {
160-
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
161-
if strings.HasPrefix(target, "arm") {
162-
builtins = append(builtins, aeabiBuiltins...)
163-
}
164-
return builtins
165-
}
166-
167-
// builtinsDir returns the directory where the sources for compiler-rt are kept.
168-
func builtinsDir() string {
169-
return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
170-
}
171-
172-
// Get the builtins archive, possibly generating it as needed.
173-
func loadBuiltins(target string) (path string, err error) {
174-
// Try to load a precompiled compiler-rt library.
175-
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
176-
if _, err := os.Stat(precompiledPath); err == nil {
177-
// Found a precompiled compiler-rt for this OS/architecture. Return the
178-
// path directly.
179-
return precompiledPath, nil
180-
}
181-
182-
outfile := "librt-" + target + ".a"
183-
builtinsDir := builtinsDir()
184-
185-
builtins := builtinFiles(target)
186-
srcs := make([]string, len(builtins))
187-
for i, name := range builtins {
188-
srcs[i] = filepath.Join(builtinsDir, name)
189-
}
190-
191-
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
192-
return path, err
193-
}
194-
195-
var cachepath string
196-
err = CompileBuiltins(target, func(path string) error {
197-
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
198-
cachepath = path
199-
return err
200-
})
201-
return cachepath, err
202-
}
203-
204-
// CompileBuiltins compiles builtins from compiler-rt into a static library.
205-
// When it succeeds, it will call the callback with the resulting path. The path
206-
// will be removed after callback returns. If callback returns an error, this is
207-
// passed through to the return value of this function.
208-
func CompileBuiltins(target string, callback func(path string) error) error {
209-
builtinsDir := builtinsDir()
210-
211-
builtins := builtinFiles(target)
212-
srcs := make([]string, len(builtins))
213-
for i, name := range builtins {
214-
srcs[i] = filepath.Join(builtinsDir, name)
215-
}
216-
217-
dirPrefix := "tinygo-builtins"
218-
remapDir := filepath.Join(os.TempDir(), dirPrefix)
219-
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
220-
if err != nil {
221-
return err
222-
}
223-
defer os.RemoveAll(dir)
224-
225-
// Compile all builtins.
226-
// TODO: use builtins optimized for a given target if available.
227-
objs := make([]string, 0, len(builtins))
228-
for _, name := range builtins {
229-
objname := name
230-
if strings.LastIndexByte(objname, '/') >= 0 {
231-
objname = objname[strings.LastIndexByte(objname, '/'):]
154+
// CompilerRT is a library with symbols required by programs compiled with LLVM.
155+
// These symbols are for operations that cannot be emitted with a single
156+
// instruction or a short sequence of instructions for that target.
157+
//
158+
// For more information, see: https://compiler-rt.llvm.org/
159+
var CompilerRT = Library{
160+
name: "compiler-rt",
161+
cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc"} },
162+
sourceDir: "lib/compiler-rt/lib/builtins",
163+
sources: func(target string) []string {
164+
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
165+
if strings.HasPrefix(target, "arm") {
166+
builtins = append(builtins, aeabiBuiltins...)
232167
}
233-
objpath := filepath.Join(dir, objname+".o")
234-
objs = append(objs, objpath)
235-
srcpath := filepath.Join(builtinsDir, name)
236-
// Note: -fdebug-prefix-map is necessary to make the output archive
237-
// reproducible. Otherwise the temporary directory is stored in the
238-
// archive itself, which varies each run.
239-
args := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
240-
if strings.HasPrefix(target, "riscv32-") {
241-
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
242-
}
243-
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
244-
if err != nil {
245-
return &commandError{"failed to build", srcpath, err}
246-
}
247-
}
248-
249-
// Put all the object files in a single archive. This archive file will be
250-
// used to statically link compiler-rt.
251-
arpath := filepath.Join(dir, "librt.a")
252-
err = makeArchive(arpath, objs)
253-
if err != nil {
254-
return err
255-
}
256-
257-
// Give the caller the resulting file. The callback must copy the file,
258-
// because after it returns the temporary directory will be removed.
259-
return callback(arpath)
168+
return builtins
169+
},
260170
}

builder/library.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package builder
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/tinygo-org/tinygo/goenv"
10+
)
11+
12+
// Library is a container for information about a single C library, such as a
13+
// compiler runtime or libc.
14+
type Library struct {
15+
// The library name, such as compiler-rt or picolibc.
16+
name string
17+
18+
cflags func() []string
19+
20+
// The source directory, relative to TINYGOROOT.
21+
sourceDir string
22+
23+
// The source files, relative to sourceDir.
24+
sources func(target string) []string
25+
}
26+
27+
// fullPath returns the full path to the source directory.
28+
func (l *Library) fullPath() string {
29+
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
30+
}
31+
32+
// sourcePaths returns a slice with the full paths to the source files.
33+
func (l *Library) sourcePaths(target string) []string {
34+
sources := l.sources(target)
35+
paths := make([]string, len(sources))
36+
for i, name := range sources {
37+
paths[i] = filepath.Join(l.fullPath(), name)
38+
}
39+
return paths
40+
}
41+
42+
// Load the library archive, possibly generating and caching it if needed.
43+
func (l *Library) Load(target string) (path string, err error) {
44+
// Try to load a precompiled library.
45+
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
46+
if _, err := os.Stat(precompiledPath); err == nil {
47+
// Found a precompiled library for this OS/architecture. Return the path
48+
// directly.
49+
return precompiledPath, nil
50+
}
51+
52+
outfile := l.name + "-" + target + ".a"
53+
54+
// Try to fetch this library from the cache.
55+
if path, err := cacheLoad(outfile, commands["clang"][0], l.sourcePaths(target)); path != "" || err != nil {
56+
// Cache hit.
57+
return path, err
58+
}
59+
// Cache miss, build it now.
60+
61+
dirPrefix := "tinygo-" + l.name
62+
remapDir := filepath.Join(os.TempDir(), dirPrefix)
63+
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
64+
if err != nil {
65+
return "", err
66+
}
67+
defer os.RemoveAll(dir)
68+
69+
// Precalculate the flags to the compiler invocation.
70+
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
71+
if strings.HasPrefix(target, "riscv32-") {
72+
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
73+
}
74+
75+
// Compile all sources.
76+
var objs []string
77+
for _, srcpath := range l.sourcePaths(target) {
78+
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
79+
objs = append(objs, objpath)
80+
// Note: -fdebug-prefix-map is necessary to make the output archive
81+
// reproducible. Otherwise the temporary directory is stored in the
82+
// archive itself, which varies each run.
83+
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
84+
if err != nil {
85+
return "", &commandError{"failed to build", srcpath, err}
86+
}
87+
}
88+
89+
// Put all the object files in a single archive. This archive file will be
90+
// used to statically link this library.
91+
arpath := filepath.Join(dir, l.name+".a")
92+
err = makeArchive(arpath, objs)
93+
if err != nil {
94+
return "", err
95+
}
96+
97+
// Store this archive in the cache.
98+
return cacheStore(arpath, outfile, commands["clang"][0], l.sourcePaths(target))
99+
}

main.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ func moveFile(src, dst string) error {
5050
}
5151
// Failed to move, probably a different filesystem.
5252
// Do a copy + remove.
53+
err = copyFile(src, dst)
54+
if err != nil {
55+
return err
56+
}
57+
return os.Remove(src)
58+
}
59+
60+
// copyFile copies the given file from src to dst. It copies first to a .tmp
61+
// file which is then moved over a possibly already existing file at the
62+
// destination.
63+
func copyFile(src, dst string) error {
5364
inf, err := os.Open(src)
5465
if err != nil {
5566
return err
@@ -768,10 +779,9 @@ func main() {
768779
if *target == "" {
769780
fmt.Fprintln(os.Stderr, "No target (-target).")
770781
}
771-
err := builder.CompileBuiltins(*target, func(path string) error {
772-
return moveFile(path, *outpath)
773-
})
782+
path, err := builder.CompilerRT.Load(*target)
774783
handleCompilerError(err)
784+
copyFile(path, *outpath)
775785
case "flash", "gdb":
776786
if *outpath != "" {
777787
fmt.Fprintln(os.Stderr, "Output cannot be specified with the flash command.")

0 commit comments

Comments
 (0)