main.mx raw
1 package main
2
3 import (
4 "os"
5 "unsafe"
6 )
7
8 //export mxc_run
9 func cRun(argv unsafe.Pointer, argc int32) int32
10
11 //export mxc_getenv
12 func cGetenv(name unsafe.Pointer, nameLen int32, buf unsafe.Pointer, bufCap int32) int32
13
14 //export mxc_listdir
15 func cListdir(dir unsafe.Pointer, dirLen int32, buf unsafe.Pointer, bufCap int32) int32
16
17 //export write
18 func cWrite(fd int32, buf unsafe.Pointer, count uint32) int32
19
20 //export mxc_writefile
21 func cWritefile(path unsafe.Pointer, pathLen int32, data unsafe.Pointer, dataLen int32, mode uint32) int32
22
23 //export mxc_mkdir
24 func cMkdir(path unsafe.Pointer, pathLen int32, mode uint32) int32
25
26 //export mxc_readfile
27 func cReadfile(path unsafe.Pointer, pathLen int32, buf unsafe.Pointer, bufCap int32) int32
28
29 //export mxc_filesize
30 func cFilesize(path unsafe.Pointer, pathLen int32) int32
31
32 func writeStr(fd int32, s string) {
33 b := []byte(s)
34 if len(b) > 0 {
35 cWrite(fd, unsafe.Pointer(unsafe.SliceData(b)), uint32(len(b)))
36 }
37 }
38
39 func writeFile(path string, data []byte, mode uint32) {
40 if len(path) == 0 {
41 return
42 }
43 cWritefile(unsafe.Pointer(unsafe.SliceData([]byte(path))), int32(len(path)),
44 unsafe.Pointer(unsafe.SliceData(data)), int32(len(data)), mode)
45 }
46
47 func mkdirAll(path string, mode uint32) {
48 if len(path) == 0 {
49 return
50 }
51 cMkdir(unsafe.Pointer(unsafe.SliceData([]byte(path))), int32(len(path)), mode)
52 }
53
54 func readFile(path string) ([]byte, bool) {
55 if len(path) == 0 {
56 return nil, false
57 }
58 pb := []byte(path)
59 sz := cFilesize(unsafe.Pointer(unsafe.SliceData(pb)), int32(len(pb)))
60 if sz < 0 {
61 return nil, false
62 }
63 if sz == 0 {
64 sz = 65536
65 }
66 buf := []byte{:sz}
67 n := cReadfile(unsafe.Pointer(unsafe.SliceData(pb)), int32(len(pb)),
68 unsafe.Pointer(unsafe.SliceData(buf)), sz)
69 if n < 0 {
70 return nil, false
71 }
72 return buf[:n], true
73 }
74
75 func fatal(msg string) {
76 writeStr(2, "mxc: " | msg | "\n")
77 os.Exit(1)
78 }
79
80 func getenv(name string) string {
81 nb := []byte(name)
82 buf := []byte{:4096}
83 n := cGetenv(unsafe.Pointer(unsafe.SliceData(nb)), int32(len(nb)), unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
84 if n <= 0 {
85 return ""
86 }
87 return string(buf[:n])
88 }
89
90 func getArgs() []string {
91 data, ok := readFile("/proc/self/cmdline")
92 if !ok {
93 return nil
94 }
95 var args []string
96 start := int32(0)
97 for i := int32(0); i < int32(len(data)); i++ {
98 if data[i] == 0 {
99 if i > start {
100 args = append(args, string(data[start:i]))
101 }
102 start = i + 1
103 }
104 }
105 if start < int32(len(data)) {
106 args = append(args, string(data[start:]))
107 }
108 return args
109 }
110
111 func listFiles(dir, ext string) []string {
112 db := []byte(dir)
113 buf := []byte{:65536}
114 n := cListdir(unsafe.Pointer(unsafe.SliceData(db)), int32(len(db)),
115 unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
116 if n <= 0 {
117 return nil
118 }
119 var result []string
120 start := int32(0)
121 for i := int32(0); i < n; i++ {
122 if buf[i] == 0 {
123 name := string(buf[start:i])
124 if hasSuffix(name, ext) {
125 result = append(result, name)
126 }
127 start = i + 1
128 }
129 }
130 return result
131 }
132
133 func run(args []string) int32 {
134 argv := []unsafe.Pointer{:len(args) + 1}
135 cstrs := [][]byte{:len(args)}
136 for i, a := range args {
137 b := []byte{:len(a) + 1}
138 copy(b, a)
139 b[len(a)] = 0
140 cstrs[i] = b
141 argv[i] = unsafe.Pointer(unsafe.SliceData(b))
142 }
143 return cRun(unsafe.Pointer(unsafe.SliceData(argv)), int32(len(args)))
144 }
145
146 func splitLines(s string) []string {
147 var result []string
148 start := int32(0)
149 for i := int32(0); i < int32(len(s)); i++ {
150 if s[i] == '\n' {
151 if i > start {
152 result = append(result, s[start:i])
153 }
154 start = i + 1
155 }
156 }
157 if start < int32(len(s)) {
158 result = append(result, s[start:])
159 }
160 return result
161 }
162
163 func hasPrefix(s, prefix string) bool {
164 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
165 }
166
167 func hasSuffix(s, suffix string) bool {
168 return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
169 }
170
171 func trimSpace(s string) string {
172 i := int32(0)
173 for i < int32(len(s)) && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r') {
174 i++
175 }
176 j := int32(len(s))
177 for j > i && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n' || s[j-1] == '\r') {
178 j--
179 }
180 return s[i:j]
181 }
182
183 func joinPath(a, b string) string {
184 if len(a) == 0 {
185 return b
186 }
187 if a[len(a)-1] == '/' {
188 return a | b
189 }
190 return a | "/" | b
191 }
192
193 func pathBase(path string) string {
194 for i := int32(len(path)) - 1; i >= 0; i-- {
195 if path[i] == '/' {
196 return path[i+1:]
197 }
198 }
199 return path
200 }
201
202 func joinStrings(ss []string, sep string) string {
203 if len(ss) == 0 {
204 return ""
205 }
206 n := int32(0)
207 for _, s := range ss {
208 n += int32(len(s))
209 }
210 n += int32(len(sep)) * (int32(len(ss)) - 1)
211 buf := []byte{:0:n}
212 for i, s := range ss {
213 if i > 0 {
214 buf = append(buf, sep...)
215 }
216 buf = append(buf, s...)
217 }
218 return string(buf)
219 }
220
221 func appendUniq(ss []string, s string) []string {
222 for _, x := range ss {
223 if x == s {
224 return ss
225 }
226 }
227 return append(ss, s)
228 }
229
230 func fnvHash(data []byte) string {
231 h := uint32(2166136261)
232 for i := int32(0); i < int32(len(data)); i++ {
233 h ^= uint32(data[i])
234 h *= uint32(16777619)
235 }
236 hex := "0123456789abcdef"
237 buf := []byte{:8}
238 for i := int32(7); i >= 0; i-- {
239 buf[i] = hex[h&0xf]
240 h >>= 4
241 }
242 return string(buf)
243 }
244
245 func pkgCacheDir() string {
246 home := getenv("HOME")
247 if home == "" {
248 home = "/tmp"
249 }
250 return joinPath(home, ".cache/moxie/pkg")
251 }
252
253 func pkgCacheKey(dir string, files []string) string {
254 var sorted []string
255 for _, f := range files {
256 sorted = append(sorted, f)
257 }
258 for i := int32(1); i < int32(len(sorted)); i++ {
259 for j := i; j > 0 && sorted[j] < sorted[j-1]; j-- {
260 sorted[j], sorted[j-1] = sorted[j-1], sorted[j]
261 }
262 }
263 var all []byte
264 for _, f := range sorted {
265 p := joinPath(dir, f)
266 data, ok := readFile(p)
267 if !ok {
268 continue
269 }
270 all = append(all, data...)
271 all = append(all, 0)
272 }
273 return fnvHash(all)
274 }
275
276 func cachedBcPath(cacheBase, pkgPath, hash string) string {
277 return joinPath(joinPath(cacheBase, safeName(pkgPath)), hash | ".bc")
278 }
279
280 func isStdlib(path string) bool {
281 return !hasPrefix(path, "/") && !hasPrefix(path, "./") && !hasPrefix(path, "../")
282 }
283
284 var builtinOnly []string
285
286 func isBuiltinOnly(path string) bool {
287 for _, b := range builtinOnly {
288 if path == b {
289 return true
290 }
291 }
292 return false
293 }
294
295 func loadSysroot(path string) {
296 data, ok := readFile(path)
297 if !ok {
298 return
299 }
300 for _, line := range splitLines(string(data)) {
301 line = trimSpace(line)
302 if len(line) == 0 || line[0] == '#' {
303 continue
304 }
305 eq := int32(-1)
306 for i := int32(0); i < int32(len(line)); i++ {
307 if line[i] == '=' {
308 eq = i
309 break
310 }
311 }
312 if eq < 0 {
313 continue
314 }
315 key := line[:eq]
316 val := line[eq+1:]
317 os.Setenv(key, val)
318 }
319 }
320
321 func splitBySpace(s string) []string {
322 var result []string
323 start := int32(0)
324 inWord := false
325 for i := int32(0); i < int32(len(s)); i++ {
326 if s[i] == ' ' || s[i] == '\t' {
327 if inWord {
328 result = append(result, s[start:i])
329 inWord = false
330 }
331 } else {
332 if !inWord {
333 start = i
334 inWord = true
335 }
336 }
337 }
338 if inWord {
339 result = append(result, s[start:])
340 }
341 return result
342 }
343
344 func safeName(pkg string) string {
345 b := []byte{:len(pkg)}
346 for i := int32(0); i < int32(len(pkg)); i++ {
347 if pkg[i] == '/' {
348 b[i] = '_'
349 } else {
350 b[i] = pkg[i]
351 }
352 }
353 return string(b)
354 }
355
356 func extractPkgName(data []byte) string {
357 for i := int32(0); i < int32(len(data)); i++ {
358 if i+8 < int32(len(data)) && string(data[i:i+8]) == "package " {
359 j := i + 8
360 for j < int32(len(data)) && data[j] != '\n' && data[j] != '\r' && data[j] != ' ' {
361 j++
362 }
363 return string(data[i+8 : j])
364 }
365 if data[i] == '\n' {
366 continue
367 }
368 if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
369 for i < int32(len(data)) && data[i] != '\n' {
370 i++
371 }
372 continue
373 }
374 break
375 }
376 return "main"
377 }
378
379 func extractImports(data []byte) []string {
380 var imports []string
381 lines := splitLines(string(data))
382 inBlock := false
383 for _, line := range lines {
384 line = trimSpace(line)
385 if line == "import (" {
386 inBlock = true
387 continue
388 }
389 if inBlock && line == ")" {
390 inBlock = false
391 continue
392 }
393 if inBlock {
394 imp := extractQuoted(line)
395 if imp != "" {
396 imports = appendUniq(imports, imp)
397 }
398 }
399 if hasPrefix(line, "import \"") {
400 imp := extractQuoted(line[7:])
401 if imp != "" {
402 imports = appendUniq(imports, imp)
403 }
404 }
405 }
406 return imports
407 }
408
409 func extractQuoted(s string) string {
410 start := int32(-1)
411 for i := int32(0); i < int32(len(s)); i++ {
412 if s[i] == '"' {
413 if start < 0 {
414 start = i + 1
415 } else {
416 return s[start:i]
417 }
418 }
419 }
420 return ""
421 }
422
423 func concatSources(dir string, files []string) []byte {
424 imports := map[string]bool{}
425 var bodies [][]byte
426 pkgName := ""
427 for _, f := range files {
428 data, ok := readFile(joinPath(dir, f))
429 if !ok {
430 continue
431 }
432 if pkgName == "" {
433 pkgName = extractPkgName(data)
434 }
435 lines := splitLines(string(data))
436 var body []string
437 i := int32(0)
438 for i < int32(len(lines)) {
439 line := trimSpace(lines[i])
440 if hasPrefix(line, "package ") {
441 i++
442 continue
443 }
444 if line == "import (" {
445 i++
446 for i < int32(len(lines)) {
447 imp := trimSpace(lines[i])
448 i++
449 if imp == ")" {
450 break
451 }
452 if imp != "" {
453 imports[imp] = true
454 }
455 }
456 continue
457 }
458 if hasPrefix(line, "import ") && !hasPrefix(line, "import (") {
459 imp := line[7:]
460 imports[imp] = true
461 i++
462 continue
463 }
464 body = append(body, lines[i])
465 i++
466 }
467 bodies = append(bodies, []byte(joinStrings(body, "\n")))
468 }
469 var out []byte
470 out = append(out, ("package " | pkgName | "\n")...)
471 if len(imports) > 0 {
472 var impKeys []string
473 for imp := range imports {
474 impKeys = append(impKeys, imp)
475 }
476 for i := int32(1); i < int32(len(impKeys)); i++ {
477 for j := i; j > 0 && impKeys[j] < impKeys[j-1]; j-- {
478 impKeys[j], impKeys[j-1] = impKeys[j-1], impKeys[j]
479 }
480 }
481 out = append(out, "import (\n"...)
482 for _, imp := range impKeys {
483 out = append(out, '\t')
484 out = append(out, imp...)
485 out = append(out, '\n')
486 }
487 out = append(out, ")\n"...)
488 }
489 for _, b := range bodies {
490 out = append(out, b...)
491 out = append(out, '\n')
492 }
493 return out
494 }
495
496 func fileContainsPart(base string, part string) bool {
497 plen := len(part)
498 for i := 0; i <= len(base)-plen; i++ {
499 if base[i:i+plen] == part {
500 before := i == 0 || base[i-1] == '_'
501 after := i+plen == len(base) || base[i+plen] == '_'
502 if before && after {
503 return true
504 }
505 }
506 }
507 return false
508 }
509
510 func shouldSkipFile(name string) bool {
511 if hasSuffix(name, "_test.go") || hasSuffix(name, "_test.mx") {
512 return true
513 }
514 base := name
515 if hasSuffix(base, ".go") {
516 base = base[:len(base)-3]
517 } else if hasSuffix(base, ".mx") {
518 base = base[:len(base)-3]
519 }
520 archSkip := []string{"arm64"}
521 for _, a := range archSkip {
522 if fileContainsPart(base, a) {
523 return true
524 }
525 }
526 suffixSkip := []string{"_wasm", "_asm"}
527 for _, s := range suffixSkip {
528 if hasSuffix(base, s) {
529 return true
530 }
531 }
532 exact := []string{"pipe", "multi", "scan", "reader", "replace", "search",
533 "ioutil"}
534 for _, s := range exact {
535 if base == s {
536 return true
537 }
538 }
539 return false
540 }
541
542 func hasBuildConstraint(data []byte, forbidden []string) bool {
543 for i := int32(0); i < int32(len(data)) && i < 512; i++ {
544 if data[i] == 'p' {
545 return false
546 }
547 if i+11 < int32(len(data)) && string(data[i:i+11]) == "//go:build " {
548 var buf []byte
549 for j := i + 11; j < int32(len(data)) && data[j] != '\n'; j++ {
550 buf = append(buf, data[j])
551 }
552 line := string(buf)
553 for _, f := range forbidden {
554 if line == f || hasPrefix(line, f | " ") || hasPrefix(line, f | "\t") {
555 return true
556 }
557 }
558 return false
559 }
560 if data[i] == '\n' {
561 continue
562 }
563 if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
564 for i < int32(len(data)) && data[i] != '\n' {
565 i++
566 }
567 continue
568 }
569 }
570 return false
571 }
572
573 func hasImport(data []byte, pkg string) bool {
574 target := "\"" | pkg | "\""
575 for i := int32(0); i <= int32(len(data))-int32(len(target)); i++ {
576 if string(data[i:i+int32(len(target))]) == target {
577 return true
578 }
579 }
580 return false
581 }
582
583 type pkgInfo struct {
584 path string
585 name string
586 dir string
587 files []string
588 cfiles []string
589 imports []string
590 }
591
592 func discoverPkg(pkgPath, root string) *pkgInfo {
593 var dir string
594 if hasPrefix(pkgPath, "/") || hasPrefix(pkgPath, "./") || hasPrefix(pkgPath, "../") {
595 dir = pkgPath
596 } else {
597 dir = joinPath(root, joinPath("src", pkgPath))
598 }
599 goFiles := listFiles(dir, ".go")
600 mxFiles := listFiles(dir, ".mx")
601 cFiles := listFiles(dir, ".c")
602 allMx := append(mxFiles, goFiles...)
603 forbidden := []string{"wasm", "js", "ignore", "compiler_bootstrap",
604 "scheduler.tasks", "scheduler.cores", "scheduler.asyncify",
605 "faketime", "baremetal", "nintendoswitch"}
606 var files []string
607 for _, f := range allMx {
608 if shouldSkipFile(f) {
609 continue
610 }
611 data, ok := readFile(joinPath(dir, f))
612 if !ok {
613 continue
614 }
615 bc := hasBuildConstraint(data, forbidden)
616 if bc {
617 writeStr(2, " skip-constraint: " | f | "\n")
618 continue
619 }
620 fileImps := extractImports(data)
621 skipIter := false
622 for _, imp := range fileImps {
623 if imp == "iter" {
624 skipIter = true
625 break
626 }
627 }
628 if skipIter {
629 writeStr(2, " skip-iter-import: " | f | "\n")
630 continue
631 }
632 writeStr(2, " include: " | f | "\n")
633 files = append(files, f)
634 }
635 if len(files) == 0 {
636 return nil
637 }
638 var allImports []string
639 pkgName := ""
640 for _, f := range files {
641 data, ok := readFile(joinPath(dir, f))
642 if !ok {
643 continue
644 }
645 if pkgName == "" {
646 pkgName = extractPkgName(data)
647 }
648 for _, imp := range extractImports(data) {
649 allImports = appendUniq(allImports, imp)
650 }
651 }
652 writeStr(2, " discover " | pkgPath | " [")
653 for fi, ff := range files {
654 if fi > 0 {
655 writeStr(2, ", ")
656 }
657 writeStr(2, ff)
658 }
659 writeStr(2, "]\n")
660 return &pkgInfo{
661 path: pkgPath,
662 name: pkgName,
663 dir: dir,
664 files: files,
665 cfiles: cFiles,
666 imports: allImports,
667 }
668 }
669
670 type resolver struct {
671 visited map[string]bool
672 order []*pkgInfo
673 root string
674 }
675
676 func (r *resolver) walk(path string) {
677 if r.visited[path] {
678 return
679 }
680 r.visited[path] = true
681 if isBuiltinOnly(path) {
682 return
683 }
684 pkg := discoverPkg(path, r.root)
685 if pkg == nil {
686 return
687 }
688 for _, imp := range pkg.imports {
689 r.walk(imp)
690 }
691 r.order = append(r.order, pkg)
692 }
693
694 func resolveAll(rootPkg string, root string) []*pkgInfo {
695 r := &resolver{
696 visited: map[string]bool{},
697 root: root,
698 }
699 r.walk(rootPkg)
700 return r.order
701 }
702
703 func registerBuiltins() {
704 initUniverse()
705 ensureImportRegistry()
706 importRegistry = map[string]*TCPackage{}
707
708 // unsafe - compiler builtin, no source
709 unsafePkg := NewTCPackage("unsafe", "unsafe")
710 unsafePkg.Scope().Insert(NewTypeName(unsafePkg, "Pointer", Typ[UnsafePointer]))
711 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Sizeof", parseSignatureDesc("interface{}->int32")))
712 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Offsetof", parseSignatureDesc("interface{}->int32")))
713 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Alignof", parseSignatureDesc("interface{}->int32")))
714 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Slice", parseSignatureDesc("ptr,int32->[]uint8")))
715 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "SliceData", parseSignatureDesc("[]uint8->ptr")))
716 importRegistry["unsafe"] = unsafePkg
717
718 // moxie - spawn/codec runtime, special handling
719 importRegistry["moxie"] = NewTCPackage("moxie", "moxie")
720
721 // runtime - assembly + C, pre-compiled in runtime.bc
722 rtPkg := NewTCPackage("runtime", "runtime")
723 rtPkg.Scope().Insert(NewTCFunc(rtPkg, "InitCShared", parseSignatureDesc("")))
724 importRegistry["runtime"] = rtPkg
725
726 // internal/task, runtime/interrupt - pre-compiled in runtime.bc
727 taskPkg := NewTCPackage("internal/task", "task")
728 taskPkg.Scope().Insert(NewTCFunc(taskPkg, "OnSystemStack", parseSignatureDesc("")))
729 taskPkg.Scope().Insert(NewTCFunc(taskPkg, "Current", parseSignatureDesc("")))
730 importRegistry["internal/task"] = taskPkg
731
732 intPkg := NewTCPackage("runtime/interrupt", "interrupt")
733 importRegistry["runtime/interrupt"] = intPkg
734 }
735
736 func addPtrMethod(pkg *TCPackage, named *Named, name, sigDesc string) {
737 var sig *Signature
738 if sigDesc == "" {
739 sig = NewSignature(nil, nil, nil, false)
740 } else {
741 sig = parseSignatureDesc(sigDesc)
742 }
743 fn := NewTCFunc(pkg, name, sig)
744 fn.hasPtrRecv = true
745 named.AddMethod(fn)
746 }
747
748 func compileSource(src []byte, pkgName, triple string) string {
749 ir := CompileToIR(src, pkgName, triple)
750 if len(ir) > 0 && ir[0] == ';' {
751 writeStr(2, ir)
752 fatal("compile error for " | pkgName)
753 }
754 if len(ir) == 0 {
755 fatal("empty IR for " | pkgName)
756 }
757 return ir
758 }
759
760 func removeAll(path string) {
761 entries := listFiles(path, "")
762 if entries == nil {
763 return
764 }
765 for _, e := range entries {
766 os.Remove(joinPath(path, e))
767 }
768 os.Remove(path)
769 }
770
771 func cacheClean() {
772 base := pkgCacheDir()
773 writeStr(2, "cleaning cache: " | base | "\n")
774 entries := listFiles(base, "")
775 if entries == nil {
776 writeStr(2, " (empty)\n")
777 return
778 }
779 for _, e := range entries {
780 removeAll(joinPath(base, e))
781 }
782 os.Remove(base)
783 writeStr(2, " done\n")
784 }
785
786 func main() {
787 builtinOnly = []string{"unsafe", "runtime", "moxie", "internal/task", "runtime/interrupt"}
788 args := getArgs()
789 if len(args) < 2 {
790 writeStr(2, "usage: mxc <build|version|cache> ...\n")
791 os.Exit(1)
792 }
793
794 cmd := args[1]
795 if cmd == "version" {
796 writeStr(1, "mxc 1.6.1\n")
797 return
798 }
799 if cmd == "cache" {
800 if len(args) >= 3 && args[2] == "clean" {
801 cacheClean()
802 return
803 }
804 writeStr(2, "usage: mxc cache clean\n")
805 os.Exit(1)
806 }
807 if cmd != "build" {
808 writeStr(2, "usage: mxc <build|version|cache> ...\n")
809 os.Exit(1)
810 }
811
812 outpath := ""
813 pkgPath := ""
814 printIR := false
815 forceRebuild := false
816 i := int32(2)
817 for i < int32(len(args)) {
818 if args[i] == "-o" && i+1 < int32(len(args)) {
819 outpath = args[i+1]
820 i += 2
821 } else if args[i] == "-print-ir" {
822 printIR = true
823 i++
824 } else if args[i] == "-a" {
825 forceRebuild = true
826 i++
827 } else {
828 pkgPath = args[i]
829 i++
830 }
831 }
832 if pkgPath == "" {
833 fatal("no package specified")
834 }
835 if outpath == "" {
836 outpath = pathBase(pkgPath)
837 }
838
839 root := getenv("MOXIEROOT")
840 if root == "" {
841 root = "."
842 }
843 triple := "x86_64-unknown-linux-musleabihf"
844 clang := "clang-21"
845
846 registerBuiltins()
847 pkgs := resolveAll(pkgPath, root)
848 if len(pkgs) == 0 {
849 fatal("no packages to compile")
850 }
851
852 tmpdir := "/tmp/mxc-build"
853 mkdirAll(tmpdir, 0755)
854
855 cacheBase := pkgCacheDir()
856 var allBcFiles []string
857
858 for _, pkg := range pkgs {
859 compilePath := pkg.path
860 if hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
861 compilePath = pkg.name
862 }
863
864 if isStdlib(pkg.path) {
865 hash := pkgCacheKey(pkg.dir, pkg.files)
866 cached := cachedBcPath(cacheBase, pkg.path, hash)
867 _, cacheOk := readFile(cached)
868 if cacheOk && !forceRebuild {
869 writeStr(2, " cached " | pkg.path | "\n")
870 src := concatSources(pkg.dir, pkg.files)
871 writeStr(2, " tc-start " | compilePath | " srcLen=" | simpleItoa(len(src)) | "\n")
872 TypeCheckOnly(src, compilePath)
873 writeStr(2, " tc-done " | compilePath | "\n")
874 src = nil
875 allBcFiles = append(allBcFiles, cached)
876 continue
877 }
878 writeStr(2, " compile " | pkg.path | "\n")
879 src := concatSources(pkg.dir, pkg.files)
880 ir := compileSource(src, compilePath, triple)
881 src = nil
882 llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll")
883 writeFile(llFile, []byte(ir), 0644)
884 ir = ""
885 bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc")
886 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
887 if rc != 0 {
888 fatal("clang failed for " | pkg.path)
889 }
890 cacheDir := joinPath(cacheBase, safeName(pkg.path))
891 mkdirAll(cacheDir, 0755)
892 data, rok := readFile(bcFile)
893 if rok {
894 writeFile(cached, data, 0644)
895 }
896 data = nil
897 allBcFiles = append(allBcFiles, bcFile)
898 } else {
899 writeStr(2, " compile " | pkg.path | "\n")
900 src := concatSources(pkg.dir, pkg.files)
901 ir := compileSource(src, compilePath, triple)
902 src = nil
903 if printIR {
904 writeStr(1, ir)
905 }
906 llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll")
907 writeFile(llFile, []byte(ir), 0644)
908 ir = ""
909 bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc")
910 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
911 if rc != 0 {
912 fatal("clang failed for " | pkg.path)
913 }
914 allBcFiles = append(allBcFiles, bcFile)
915 for _, cf := range pkg.cfiles {
916 cPath := joinPath(pkg.dir, cf)
917 cbcFile := joinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc")
918 rc = run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
919 if rc != 0 {
920 fatal("clang failed for " | cf)
921 }
922 allBcFiles = append(allBcFiles, cbcFile)
923 }
924 }
925 }
926
927 loadSysroot(joinPath(root, "_sysroot.env"))
928
929 sysroot := joinPath(root, "sysroot")
930 runtimeBc := getenv("RUNTIME_BC")
931 if runtimeBc == "" {
932 runtimeBc = joinPath(sysroot, "runtime.bc")
933 _, rok := readFile(runtimeBc)
934 if !rok {
935 runtimeBc = joinPath(root, "_runtime.bc")
936 }
937 }
938 muslDir := getenv("MUSL_DIR")
939 if muslDir == "" {
940 muslDir = joinPath(sysroot, "musl")
941 }
942 comprtLib := getenv("COMPRT_LIB")
943 if comprtLib == "" {
944 comprtLib = joinPath(joinPath(sysroot, "compiler-rt"), "lib.a")
945 }
946 bdwgcLib := getenv("BDWGC_LIB")
947 if bdwgcLib == "" {
948 bdwgcLib = joinPath(joinPath(sysroot, "bdwgc"), "lib.a")
949 }
950 muslLib := getenv("MUSL_LIB")
951 if muslLib == "" {
952 muslLib = joinPath(joinPath(sysroot, "musl"), "lib.a")
953 }
954
955 writeStr(2, " generate initAll\n")
956 var initCalls string
957 for _, pkg := range pkgs {
958 compilePath := pkg.path
959 if hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
960 compilePath = pkg.name
961 }
962 if compilePath == "main" {
963 continue
964 }
965 initSym := irGlobalSymbol(compilePath, "main")
966 initCalls = initCalls | " call void " | initSym | "(ptr %context)\n"
967 }
968 initAllIR := "\ndefine void @runtime.initAll(ptr %context) {\nentry:\n" | initCalls | " ret void\n}\n"
969 declared := map[string]bool{}
970 for _, pkg := range pkgs {
971 compilePath := pkg.path
972 if hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
973 compilePath = pkg.name
974 }
975 if compilePath == "main" {
976 continue
977 }
978 declSym := irGlobalSymbol(compilePath, "main")
979 if declared[declSym] {
980 continue
981 }
982 declared[declSym] = true
983 initAllIR = initAllIR | "declare void " | declSym | "(ptr)\n"
984 }
985 initAllFile := joinPath(tmpdir, "initall.ll")
986 writeFile(initAllFile, []byte(initAllIR), 0644)
987 initAllBc := joinPath(tmpdir, "initall.bc")
988 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", initAllFile, "-o", initAllBc})
989 if rc != 0 {
990 fatal("clang failed for initall")
991 }
992
993 writeStr(2, " merge bitcode\n")
994 llvmLink := "llvm-link-21"
995 mergedBc := joinPath(tmpdir, "merged.bc")
996 linkArgs := []string{llvmLink, "-o", mergedBc, runtimeBc}
997 shimsBc := joinPath(sysroot, "hashmap_shims.bc")
998 linkArgs = append(linkArgs, shimsBc)
999 runtimeGoBc := joinPath(sysroot, "runtime_go.bc")
1000 if _, gok := readFile(runtimeGoBc); gok {
1001 overrideArg := "--override=" | runtimeGoBc
1002 linkArgs = append(linkArgs, overrideArg)
1003 }
1004 linkArgs = append(linkArgs, allBcFiles...)
1005 linkArgs = append(linkArgs, initAllBc)
1006 rc = run(linkArgs)
1007 if rc != 0 {
1008 fatal("llvm-link failed")
1009 }
1010
1011 writeStr(2, " link\n")
1012 objFiles := getenv("OBJ_FILES")
1013 if objFiles == "" {
1014 objEntries := listFiles(joinPath(sysroot, "obj"), ".bc")
1015 if objEntries != nil {
1016 for _, e := range objEntries {
1017 objFiles = objFiles | joinPath(joinPath(sysroot, "obj"), e) | " "
1018 }
1019 }
1020 }
1021 linker := "ld.lld-21"
1022 ldArgs := []string{linker, "--gc-sections", "-o", outpath,
1023 joinPath(muslDir, "crt1.o"),
1024 mergedBc}
1025 for _, f := range splitBySpace(objFiles) {
1026 if f != "" {
1027 ldArgs = append(ldArgs, f)
1028 }
1029 }
1030 ldArgs = append(ldArgs, comprtLib, bdwgcLib, muslLib, "--lto-O0", "-z", "stack-size=67108864")
1031 rc = run(ldArgs)
1032 if rc != 0 {
1033 fatal("linker failed")
1034 }
1035 writeStr(2, " -> " | outpath | "\n")
1036 }
1037