bootstrap.go raw
1 package builder
2
3 import (
4 "fmt"
5 "go/types"
6 "os"
7 "path/filepath"
8 "strings"
9 "sync"
10 "unsafe"
11
12 "github.com/ebitengine/purego"
13 "moxie/loader"
14 "tinygo.org/x/go-llvm"
15 )
16
17 var (
18 bootstrapOnce sync.Once
19 bootstrapLib uintptr
20 bootstrapCompile func(uintptr, int32, uintptr, int32, uintptr, int32) int32
21 bootstrapIRLen func(int32) int32
22 bootstrapIRCopy func(int32, uintptr, int32) int32
23 bootstrapIRFree func(int32)
24 bootstrapRegPkg func(uintptr, int32, uintptr, int32)
25 bootstrapRegFunc func(uintptr, int32, uintptr, int32, uintptr, int32)
26 bootstrapRegVar func(uintptr, int32, uintptr, int32, uintptr, int32)
27 bootstrapClearImport func()
28 bootstrapErr error
29 )
30
31 func initBootstrap() {
32 bootstrapOnce.Do(func() {
33 soPath := os.Getenv("MOXIE_COMPILE_SO")
34 if soPath == "" {
35 root := os.Getenv("MOXIEROOT")
36 if root == "" {
37 root, _ = os.Executable()
38 root = filepath.Dir(root)
39 }
40 soPath = filepath.Join(root, "bootstrap", "compile.so")
41 }
42 if _, err := os.Stat(soPath); err != nil {
43 bootstrapErr = err
44 return
45 }
46 lib, err := purego.Dlopen(soPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
47 if err != nil {
48 bootstrapErr = fmt.Errorf("dlopen %s: %w", soPath, err)
49 return
50 }
51 bootstrapLib = lib
52 purego.RegisterLibFunc(&bootstrapCompile, lib, "moxie_compile_to_ir")
53 purego.RegisterLibFunc(&bootstrapIRLen, lib, "moxie_compile_ir_len")
54 purego.RegisterLibFunc(&bootstrapIRCopy, lib, "moxie_compile_ir_copy")
55 purego.RegisterLibFunc(&bootstrapIRFree, lib, "moxie_compile_ir_free")
56 purego.RegisterLibFunc(&bootstrapRegPkg, lib, "moxie_register_package")
57 purego.RegisterLibFunc(&bootstrapRegFunc, lib, "moxie_register_func")
58 purego.RegisterLibFunc(&bootstrapRegVar, lib, "moxie_register_var")
59 purego.RegisterLibFunc(&bootstrapClearImport, lib, "moxie_clear_imports")
60 })
61 }
62
63 func bootstrapAvailable() bool {
64 initBootstrap()
65 return bootstrapErr == nil && bootstrapLib != 0
66 }
67
68 func bootstrapRegisterStr(fn func(uintptr, int32, uintptr, int32), a, b string) {
69 ab := []byte(a)
70 bb := []byte(b)
71 fn(uintptr(unsafe.Pointer(&ab[0])), int32(len(ab)), uintptr(unsafe.Pointer(&bb[0])), int32(len(bb)))
72 }
73
74 func bootstrapRegisterStr3(fn func(uintptr, int32, uintptr, int32, uintptr, int32), a, b, c string) {
75 ab := []byte(a)
76 bb := []byte(b)
77 cb := []byte(c)
78 fn(uintptr(unsafe.Pointer(&ab[0])), int32(len(ab)),
79 uintptr(unsafe.Pointer(&bb[0])), int32(len(bb)),
80 uintptr(unsafe.Pointer(&cb[0])), int32(len(cb)))
81 }
82
83 func bootstrapTypeDesc(t types.Type) string {
84 switch t := t.Underlying().(type) {
85 case *types.Basic:
86 switch t.Kind() {
87 case types.Bool:
88 return "bool"
89 case types.Int8:
90 return "int8"
91 case types.Int16:
92 return "int16"
93 case types.Int32, types.Int:
94 return "int32"
95 case types.Int64:
96 return "int64"
97 case types.Uint8:
98 return "uint8"
99 case types.Uint16:
100 return "uint16"
101 case types.Uint32, types.Uint:
102 return "uint32"
103 case types.Uint64:
104 return "uint64"
105 case types.Float32:
106 return "float32"
107 case types.Float64:
108 return "float64"
109 case types.String:
110 return "string"
111 case types.UnsafePointer, types.Uintptr:
112 return "ptr"
113 }
114 case *types.Slice:
115 return "[]" + bootstrapTypeDesc(t.Elem())
116 case *types.Pointer:
117 return "*" + bootstrapTypeDesc(t.Elem())
118 }
119 return "int32"
120 }
121
122 func bootstrapSigDesc(sig *types.Signature) string {
123 var parts []string
124 params := sig.Params()
125 for i := 0; i < params.Len(); i++ {
126 parts = append(parts, bootstrapTypeDesc(params.At(i).Type()))
127 }
128 paramStr := strings.Join(parts, ",")
129
130 results := sig.Results()
131 if results.Len() == 0 {
132 return paramStr
133 }
134 var rParts []string
135 for i := 0; i < results.Len(); i++ {
136 rParts = append(rParts, bootstrapTypeDesc(results.At(i).Type()))
137 }
138 resultStr := strings.Join(rParts, ",")
139 if paramStr == "" {
140 return "->" + resultStr
141 }
142 return paramStr + "->" + resultStr
143 }
144
145 func bootstrapRegisterImports(pkg *loader.Package) {
146 bootstrapClearImport()
147 if pkg.Pkg == nil {
148 return
149 }
150 for _, imp := range pkg.Pkg.Imports() {
151 bootstrapRegisterStr(bootstrapRegPkg, imp.Path(), imp.Name())
152 scope := imp.Scope()
153 for _, name := range scope.Names() {
154 obj := scope.Lookup(name)
155 if !obj.Exported() {
156 continue
157 }
158 switch obj := obj.(type) {
159 case *types.Func:
160 sig := obj.Type().(*types.Signature)
161 bootstrapRegisterStr3(bootstrapRegFunc, imp.Path(), name, bootstrapSigDesc(sig))
162 case *types.Var:
163 bootstrapRegisterStr3(bootstrapRegVar, imp.Path(), name, bootstrapTypeDesc(obj.Type()))
164 }
165 }
166 }
167 }
168
169 func bootstrapConcatSources(pkg *loader.Package) ([]byte, error) {
170 imports := map[string]bool{}
171 var bodies [][]byte
172 for _, f := range pkg.GoFiles {
173 path := filepath.Join(pkg.Dir, f)
174 data, err := os.ReadFile(path)
175 if err != nil {
176 return nil, fmt.Errorf("bootstrap: read %s: %w", path, err)
177 }
178 lines := strings.Split(string(data), "\n")
179 var body []string
180 i := 0
181 for i < len(lines) {
182 line := strings.TrimSpace(lines[i])
183 if strings.HasPrefix(line, "package ") {
184 i++
185 continue
186 }
187 if line == "import (" {
188 i++
189 for i < len(lines) {
190 imp := strings.TrimSpace(lines[i])
191 i++
192 if imp == ")" {
193 break
194 }
195 if imp != "" {
196 imports[imp] = true
197 }
198 }
199 continue
200 }
201 if strings.HasPrefix(line, "import ") && !strings.HasPrefix(line, "import (") {
202 imp := strings.TrimPrefix(line, "import ")
203 imports[imp] = true
204 i++
205 continue
206 }
207 body = append(body, lines[i])
208 i++
209 }
210 bodies = append(bodies, []byte(strings.Join(body, "\n")))
211 }
212 var out []byte
213 out = append(out, []byte("package "+pkg.Pkg.Name()+"\n")...)
214 if len(imports) > 0 {
215 out = append(out, []byte("import (\n")...)
216 for imp := range imports {
217 out = append(out, '\t')
218 out = append(out, []byte(imp)...)
219 out = append(out, '\n')
220 }
221 out = append(out, []byte(")\n")...)
222 }
223 for _, b := range bodies {
224 out = append(out, b...)
225 out = append(out, '\n')
226 }
227 return out, nil
228 }
229
230 func bootstrapCompilePackage(pkg *loader.Package, triple string) (llvm.Module, error) {
231 initBootstrap()
232 if bootstrapErr != nil {
233 return llvm.Module{}, bootstrapErr
234 }
235
236 bootstrapRegisterImports(pkg)
237
238 src, err := bootstrapConcatSources(pkg)
239 if err != nil {
240 return llvm.Module{}, err
241 }
242 if len(src) == 0 {
243 return llvm.Module{}, fmt.Errorf("bootstrap: no source files for %s", pkg.ImportPath)
244 }
245
246 name := []byte(pkg.Pkg.Path())
247 tripleBytes := []byte(triple)
248
249 h := bootstrapCompile(
250 uintptr(unsafe.Pointer(&src[0])), int32(len(src)),
251 uintptr(unsafe.Pointer(&name[0])), int32(len(name)),
252 uintptr(unsafe.Pointer(&tripleBytes[0])), int32(len(tripleBytes)),
253 )
254 if h < 0 {
255 return llvm.Module{}, fmt.Errorf("bootstrap: compile failed for %s", pkg.ImportPath)
256 }
257
258 n := bootstrapIRLen(h)
259 if n <= 0 {
260 bootstrapIRFree(h)
261 return llvm.Module{}, fmt.Errorf("bootstrap: empty IR for %s", pkg.ImportPath)
262 }
263 irBuf := make([]byte, n)
264 bootstrapIRCopy(h, uintptr(unsafe.Pointer(&irBuf[0])), n)
265 bootstrapIRFree(h)
266
267 if dp := os.Getenv("MOXIE_BOOTSTRAP_DUMP_IR"); dp != "" {
268 os.WriteFile(dp, irBuf, 0644)
269 }
270
271 ctx := llvm.NewContext()
272 membuf := llvm.NewMemoryBufferFromBytes(irBuf, pkg.ImportPath+".ll")
273 mod, err := ctx.ParseIR(membuf)
274 if err != nil {
275 ctx.Dispose()
276 return llvm.Module{}, fmt.Errorf("bootstrap: parse IR for %s: %w", pkg.ImportPath, err)
277 }
278 return mod, nil
279 }
280