1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
package builder
import (
"errors"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// Library is a container for information about a single C library, such as a
// compiler runtime or libc.
type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
// makeHeaders creates a header include dir for the library
makeHeaders func(target, includeDir string) error
// cflags returns the C flags specific to this library
cflags func(target, headerPath string) []string
// The source directory.
sourceDir func() string
// The source files, relative to sourceDir.
librarySources func(target string) ([]string, error)
// The source code for the crt1.o file, relative to sourceDir.
crt1Source string
}
// load returns a compile job to build this library file for the given target
// and CPU. It may return a dummy compileJob if the library build is already
// cached. The path is stored as job.result but is only valid after the job has
// been run.
// The provided tmpdir will be used to store intermediary files and possibly the
// output archive file, it is expected to be removed after use.
// As a side effect, this call creates the library header files if they didn't
// exist yet.
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) {
outdir, precompiled := config.LibcPath(l.name)
archiveFilePath := filepath.Join(outdir, "lib.a")
if precompiled {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return dummyCompileJob(archiveFilePath), func() {}, nil
}
// Create a lock on the output (if supported).
// This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build.
outname := filepath.Base(outdir)
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), outname+".lock"))
var ok bool
defer func() {
if !ok {
unlock()
}
}()
// Try to fetch this library from the cache.
if _, err := os.Stat(archiveFilePath); err == nil {
return dummyCompileJob(archiveFilePath), func() {}, nil
}
// Cache miss, build it now.
// Create the destination directory where the components of this library
// (lib.a file, include directory) are placed.
err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777)
if err != nil {
// Could not create directory (and not because it already exists).
return nil, nil, err
}
// Make headers if needed.
headerPath := filepath.Join(outdir, "include")
target := config.Triple()
if l.makeHeaders != nil {
if _, err = os.Stat(headerPath); err != nil {
temporaryHeaderPath, err := os.MkdirTemp(outdir, "include.tmp*")
if err != nil {
return nil, nil, err
}
defer os.RemoveAll(temporaryHeaderPath)
err = l.makeHeaders(target, temporaryHeaderPath)
if err != nil {
return nil, nil, err
}
err = os.Chmod(temporaryHeaderPath, 0o755) // TempDir uses 0o700 by default
if err != nil {
return nil, nil, err
}
err = os.Rename(temporaryHeaderPath, headerPath)
if err != nil {
switch {
case errors.Is(err, fs.ErrExist):
// Another invocation of TinyGo also seems to have already created the headers.
case runtime.GOOS == "windows" && errors.Is(err, fs.ErrPermission):
// On Windows, a rename with a destination directory that already
// exists does not result in an IsExist error, but rather in an
// access denied error. To be sure, check for this case by checking
// whether the target directory exists.
if _, err := os.Stat(headerPath); err == nil {
break
}
fallthrough
default:
return nil, nil, err
}
}
}
}
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
err = os.Mkdir(dir, 0777)
if err != nil {
return nil, nil, err
}
// Precalculate the flags to the compiler invocation.
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run.
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
resourceDir := goenv.ClangResourceDir(false)
if resourceDir != "" {
args = append(args, "-resource-dir="+resourceDir)
}
cpu := config.CPU()
if cpu != "" {
// X86 has deprecated the -mcpu flag, so we need to use -march instead.
// However, ARM has not done this.
if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") {
args = append(args, "-march="+cpu)
} else if strings.HasPrefix(target, "avr") {
args = append(args, "-mmcu="+cpu)
} else {
args = append(args, "-mcpu="+cpu)
}
}
if config.ABI() != "" {
args = append(args, "-mabi="+config.ABI())
}
switch compileopts.CanonicalArchName(target) {
case "arm":
if strings.Split(target, "-")[2] == "linux" {
args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
} else {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
}
case "avr":
// AVR defaults to C float and double both being 32-bit. This deviates
// from what most code (and certainly compiler-rt) expects. So we need
// to force the compiler to use 64-bit floating point numbers for
// double.
args = append(args, "-mdouble=64")
case "riscv32":
args = append(args, "-march=rv32imac", "-fforce-enable-int128")
case "riscv64":
args = append(args, "-march=rv64gc")
case "mips":
args = append(args, "-fno-pic")
}
var once sync.Once
// Create job to put all the object files in a single archive. This archive
// file is the (static) library file.
var objs []string
job = &compileJob{
description: "ar " + l.name + "/lib.a",
result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"),
run: func(*compileJob) error {
defer once.Do(unlock)
// Create an archive of all object files.
f, err := os.CreateTemp(outdir, "libc.a.tmp*")
if err != nil {
return err
}
err = makeArchive(f, objs)
if err != nil {
return err
}
err = f.Close()
if err != nil {
return err
}
err = os.Chmod(f.Name(), 0o644) // TempFile uses 0o600 by default
if err != nil {
return err
}
// Store this archive in the cache.
return os.Rename(f.Name(), archiveFilePath)
},
}
sourceDir := l.sourceDir()
// Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first.
paths, err := l.librarySources(target)
if err != nil {
return nil, nil, err
}
for _, path := range paths {
// Strip leading "../" parts off the path.
cleanpath := path
for strings.HasPrefix(cleanpath, "../") {
cleanpath = cleanpath[3:]
}
srcpath := filepath.Join(sourceDir, path)
objpath := filepath.Join(dir, cleanpath+".o")
os.MkdirAll(filepath.Dir(objpath), 0o777)
objs = append(objs, objpath)
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", compileArgs...)
}
err := runCCompiler(compileArgs...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
return nil
},
})
}
// Create crt1.o job, if needed.
// Add this as a (fake) dependency to the ar file so it gets compiled.
// (It could be done in parallel with creating the ar file, but it probably
// won't make much of a difference in speed).
if l.crt1Source != "" {
srcpath := filepath.Join(sourceDir, l.crt1Source)
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
tmpfile, err := os.CreateTemp(outdir, "crt1.o.tmp*")
if err != nil {
return err
}
tmpfile.Close()
compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", compileArgs...)
}
err = runCCompiler(compileArgs...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
},
})
}
ok = true
return job, func() {
once.Do(unlock)
}, nil
}
|