-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathruntime.go
More file actions
230 lines (210 loc) · 6.9 KB
/
Copy pathruntime.go
File metadata and controls
230 lines (210 loc) · 6.9 KB
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
package main
import (
"crypto/sha256"
"embed"
"encoding/hex"
"fmt"
"hash"
"io/fs"
"os"
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
"github.com/gofrs/flock"
)
// isHashDir returns true if name is an 8-char hex string (matches shortHash format).
func isHashDir(name string) bool {
if len(name) != 8 {
return false
}
_, err := hex.DecodeString(name)
return err == nil
}
//go:embed runtime
var runtimeFS embed.FS
// runtimeCompileFlags returns the compiler flags used for runtime compilation.
// Used by both compileRuntime and metadataHash to keep them in sync.
func (cfg buildConfig) runtimeCompileFlags() []string {
flags := []string{OPT_LEVEL, C_STD}
if target := cfg.clangTargetFlag(runtime.GOARCH); target != "" {
flags = append(flags, target)
}
if runtime.GOOS != OS_WINDOWS {
flags = append(flags, FPIC)
}
return flags
}
// metadataHash hashes compiler settings and platform that affect runtime compilation.
func metadataHash(h hash.Hash, cfg buildConfig) {
h.Write([]byte(CC))
for _, flag := range cfg.runtimeCompileFlags() {
h.Write([]byte(flag))
}
h.Write([]byte(runtime.GOOS))
h.Write([]byte(runtime.GOARCH))
}
// runtimeInfo computes SHA256 hash and counts top-level .c files.
// Hash includes all files (headers in subdirs matter) but only counts
// top-level .c files since compileRuntime only compiles those.
// Returns short hash (8 chars for directory name) and full hash (for collision check).
func runtimeInfo(cfg buildConfig) (shortHash, fullHash string, srcCount int, err error) {
h := sha256.New()
metadataHash(h, cfg)
err = fs.WalkDir(runtimeFS, RUNTIME_DIR, func(path string, d fs.DirEntry, walkErr error) error {
if walkErr != nil {
return walkErr
}
if !d.IsDir() {
data, readErr := runtimeFS.ReadFile(path)
if readErr != nil {
return readErr
}
h.Write(data)
// Only count top-level .c files (e.g., "runtime/array.c")
if strings.HasSuffix(path, ".c") && filepath.Dir(path) == RUNTIME_DIR {
srcCount++
}
}
return nil
})
if err != nil {
err = fmt.Errorf("walk embedded runtime: %w", err)
fmt.Println(err)
return "", "", 0, err
}
fullHash = hex.EncodeToString(h.Sum(nil))
shortHash = fullHash[:8]
return shortHash, fullHash, srcCount, nil
}
// extractRuntime writes the embedded runtime files to rtDir.
func extractRuntime(rtDir string) error {
if err := os.MkdirAll(rtDir, 0755); err != nil {
return fmt.Errorf("create runtime dir: %w", err)
}
return fs.WalkDir(runtimeFS, RUNTIME_DIR, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("walk %s: %w", path, err)
}
relPath, _ := filepath.Rel(RUNTIME_DIR, path)
destPath := filepath.Join(rtDir, relPath)
if d.IsDir() {
return os.MkdirAll(destPath, 0755)
}
data, err := runtimeFS.ReadFile(path)
if err != nil {
return fmt.Errorf("read embedded %s: %w", path, err)
}
return os.WriteFile(destPath, data, 0644)
})
}
// compileRuntime compiles .c files in rtDir and returns paths to .o files.
func compileRuntime(rtDir string, cfg buildConfig) ([]string, error) {
rtSrcs, err := filepath.Glob(filepath.Join(rtDir, "*.c"))
if err != nil {
return nil, fmt.Errorf("glob runtime sources: %w", err)
}
if len(rtSrcs) == 0 {
return nil, fmt.Errorf("no runtime .c files found under %s", rtDir)
}
var rtObjs []string
for _, src := range rtSrcs {
outObj := filepath.Join(rtDir, filepath.Base(src)+OBJ_SUFFIX)
args := append(cfg.runtimeCompileFlags(), "-I", rtDir, "-c", src, "-o", outObj)
if out, err := exec.Command(CC, args...).CombinedOutput(); err != nil {
return nil, fmt.Errorf("compile %s: %v\n%s", src, err, out)
}
rtObjs = append(rtObjs, outObj)
}
return rtObjs, nil
}
// cleanupOldRuntimes removes old runtime hash directories.
// Only deletes directories older than minAge AND keeps at least 'keep' most recent.
// This prevents deleting runtime dirs that may still be in use by concurrent processes.
func cleanupOldRuntimes(runtimeDir string, keep int, minAge int64) {
entries, err := os.ReadDir(runtimeDir)
if err != nil || len(entries) <= keep {
return
}
// Filter to hash directories (8-char hex names) with their mod times
type dirInfo struct {
name string
mtime int64
}
var dirs []dirInfo
for _, e := range entries {
if e.IsDir() && isHashDir(e.Name()) {
info, err := e.Info()
if err != nil {
fmt.Printf("warning: failed to get info for runtime dir %s: %v\n", e.Name(), err)
continue
}
dirs = append(dirs, dirInfo{e.Name(), info.ModTime().Unix()})
}
}
if len(dirs) <= keep {
return
}
// Sort by mtime ascending (oldest first), remove oldest if older than minAge
cutoff := time.Now().Unix() - minAge
sort.Slice(dirs, func(i, j int) bool { return dirs[i].mtime < dirs[j].mtime })
for i := 0; i < len(dirs)-keep; i++ {
if dirs[i].mtime > cutoff {
continue
}
path := filepath.Join(runtimeDir, dirs[i].name)
if err := os.RemoveAll(path); err != nil {
fmt.Printf("warning: failed to remove old runtime %s: %v\n", path, err)
}
}
}
// prepareRuntime extracts embedded runtime files and compiles them to object files.
// Uses a hash-based directory to cache compiled objects across runs.
// A file lock ensures concurrent processes see either fully compiled runtime or build it.
func prepareRuntime(cacheDir string, cfg buildConfig) ([]string, error) {
runtimeDir := filepath.Join(cacheDir, RUNTIME_DIR)
if err := os.MkdirAll(runtimeDir, 0755); err != nil {
return nil, fmt.Errorf("create runtime dir: %w", err)
}
// Lock the entire operation
lock := flock.New(filepath.Join(runtimeDir, ".lock"))
if err := lock.Lock(); err != nil {
return nil, fmt.Errorf("acquire runtime lock: %w", err)
}
defer lock.Unlock()
shortHash, fullHash, srcCount, err := runtimeInfo(cfg)
if err != nil {
return nil, err
}
rtDir := filepath.Join(runtimeDir, shortHash)
hashFile := filepath.Join(rtDir, ".hash")
// Check if already compiled (verify .o count and full hash match)
if rtObjs, err := filepath.Glob(filepath.Join(rtDir, "*.o")); err == nil && len(rtObjs) == srcCount {
// Verify full hash to detect collisions
if storedHash, err := os.ReadFile(hashFile); err == nil && string(storedHash) == fullHash {
fmt.Printf("Using cached runtime: %s\n", rtDir)
return rtObjs, nil
}
// Hash collision or corrupted cache - rebuild
fmt.Printf("Runtime hash mismatch, rebuilding: %s\n", rtDir)
os.RemoveAll(rtDir)
}
// Cleanup old runtime versions (keep 5 most recent, only delete if older than 1 week)
cleanupOldRuntimes(runtimeDir, 5, 7*24*60*60)
fmt.Printf("Compiling runtime: %s\n", rtDir)
// Extract and compile
if err := extractRuntime(rtDir); err != nil {
return nil, err
}
rtObjs, err := compileRuntime(rtDir, cfg)
if err != nil {
return nil, err
}
// Store full hash after successful compilation (acts as completion marker)
if err := os.WriteFile(hashFile, []byte(fullHash), 0644); err != nil {
return nil, fmt.Errorf("write hash file: %w", err)
}
return rtObjs, nil
}