package main import ( "os" "unsafe" ) //export mxc_run func cRun(argv unsafe.Pointer, argc int32) int32 //export mxc_getenv func cGetenv(name unsafe.Pointer, nameLen int32, buf unsafe.Pointer, bufCap int32) int32 //export mxc_listdir func cListdir(dir unsafe.Pointer, dirLen int32, buf unsafe.Pointer, bufCap int32) int32 //export write func cWrite(fd int32, buf unsafe.Pointer, count uint32) int32 //export mxc_writefile func cWritefile(path unsafe.Pointer, pathLen int32, data unsafe.Pointer, dataLen int32, mode uint32) int32 //export mxc_mkdir func cMkdir(path unsafe.Pointer, pathLen int32, mode uint32) int32 //export mxc_readfile func cReadfile(path unsafe.Pointer, pathLen int32, buf unsafe.Pointer, bufCap int32) int32 //export mxc_filesize func cFilesize(path unsafe.Pointer, pathLen int32) int32 func writeStr(fd int32, s string) { if len(s) > 0 { cWrite(fd, unsafe.Pointer(unsafe.SliceData(s)), uint32(len(s))) } } func writeFile(path string, data []byte, mode uint32) { if len(path) == 0 { return } cWritefile(unsafe.Pointer(unsafe.SliceData(path)), int32(len(path)), unsafe.Pointer(unsafe.SliceData(data)), int32(len(data)), mode) } func mkdirAll(path string, mode uint32) { if len(path) == 0 { return } cMkdir(unsafe.Pointer(unsafe.SliceData(path)), int32(len(path)), mode) } func readFile(path string) ([]byte, bool) { if len(path) == 0 { return nil, false } pp := unsafe.Pointer(unsafe.SliceData(path)) pl := int32(len(path)) sz := cFilesize(pp, pl) if sz < 0 { return nil, false } if sz == 0 { sz = 65536 } buf := []byte{:sz} n := cReadfile(pp, pl, unsafe.Pointer(unsafe.SliceData(buf)), sz) if n < 0 { return nil, false } return buf[:n], true } func fatal(msg string) { writeStr(2, "mxc: " | msg | "\n") os.Exit(1) } func getenv(name string) string { buf := []byte{:4096} n := cGetenv(unsafe.Pointer(unsafe.SliceData(name)), int32(len(name)), unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf))) if n <= 0 { return "" } return string(buf[:n]) } func getArgs() []string { data, ok := readFile("/proc/self/cmdline") if !ok { return nil } var args []string start := int32(0) for i := int32(0); i < int32(len(data)); i++ { if data[i] == 0 { if i > start { args = append(args, string(data[start:i])) } start = i + 1 } } if start < int32(len(data)) { args = append(args, string(data[start:])) } return args } func listFiles(dir, ext string) []string { buf := []byte{:65536} n := cListdir(unsafe.Pointer(unsafe.SliceData(dir)), int32(len(dir)), unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf))) if n <= 0 { return nil } var result []string start := int32(0) for i := int32(0); i < n; i++ { if buf[i] == 0 { name := string(buf[start:i]) if hasSuffix(name, ext) { result = append(result, name) } start = i + 1 } } return result } func run(args []string) int32 { argv := []unsafe.Pointer{:len(args) + 1} cstrs := [][]byte{:len(args)} for i, a := range args { b := []byte{:len(a) + 1} copy(b, a) b[len(a)] = 0 cstrs[i] = b argv[i] = unsafe.Pointer(unsafe.SliceData(b)) } return cRun(unsafe.Pointer(unsafe.SliceData(argv)), int32(len(args))) } func splitLines(s string) []string { var result []string start := int32(0) for i := int32(0); i < int32(len(s)); i++ { if s[i] == '\n' { if i > start { result = append(result, s[start:i]) } start = i + 1 } } if start < int32(len(s)) { result = append(result, s[start:]) } return result } func hasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } func hasSuffix(s, suffix string) bool { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix } func irNeedsQuote(s string) bool { for i := 0; i < len(s); i++ { c := s[i] if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '$' { continue } return true } return false } func irGlobalSymbol(pkg, name string) string { sym := pkg | "." | name if irNeedsQuote(sym) { return "@\"" | sym | "\"" } return "@" | sym } func trimSpace(s string) string { i := int32(0) for i < int32(len(s)) && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r') { i++ } j := int32(len(s)) for j > i && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n' || s[j-1] == '\r') { j-- } return s[i:j] } func joinPath(a, b string) string { if len(a) == 0 { return b } if a[len(a)-1] == '/' { return a | b } return a | "/" | b } func pathDir(path string) string { for i := int32(len(path)) - 1; i >= 0; i-- { if path[i] == '/' { if i == 0 { return "/" } return path[:i] } } return "." } func cleanPath(path string) string { parts := splitSlash(path) var out []string for _, p := range parts { if p == "." || p == "" { continue } if p == ".." && len(out) > 0 && out[len(out)-1] != ".." { out = out[:len(out)-1] } else { out = append(out, p) } } if len(out) == 0 { return "." } result := out[0] for i := 1; i < len(out); i++ { result = result | "/" | out[i] } if len(path) > 0 && path[0] == '/' { result = "/" | result } return result } func splitSlash(s string) []string { var parts []string start := int32(0) for i := int32(0); i < int32(len(s)); i++ { if s[i] == '/' { parts = append(parts, s[start:i]) start = i + 1 } } parts = append(parts, s[start:]) return parts } func pathBase(path string) string { for i := int32(len(path)) - 1; i >= 0; i-- { if path[i] == '/' { return path[i+1:] } } return path } func joinStrings(ss []string, sep string) string { if len(ss) == 0 { return "" } n := int32(0) for _, s := range ss { n += int32(len(s)) } n += int32(len(sep)) * (int32(len(ss)) - 1) buf := []byte{:0:n} for i, s := range ss { if i > 0 { buf = append(buf, sep...) } buf = append(buf, s...) } return string(buf) } func appendUniq(ss []string, s string) []string { for _, x := range ss { if x == s { return ss } } return append(ss, s) } func fnvHash(data []byte) string { h := uint32(2166136261) for i := int32(0); i < int32(len(data)); i++ { h ^= uint32(data[i]) h *= uint32(16777619) } hex := "0123456789abcdef" buf := []byte{:8} for i := int32(7); i >= 0; i-- { buf[i] = hex[h&0xf] h >>= 4 } return string(buf) } func pkgCacheDir() string { return joinPath(joinPath(getMoxiePath(), "cache"), "mxc-1.9.2/pkg") } func getMoxiePath() string { p := getenv("MOXIEPATH") if p != "" { return p } home := getenv("HOME") if home == "" { return "/tmp/moxie" } return joinPath(home, "moxie") } func pkgCacheKey(dir string, files []string) string { var sorted []string for _, f := range files { sorted = append(sorted, f) } for i := int32(1); i < int32(len(sorted)); i++ { for j := i; j > 0 && sorted[j] < sorted[j-1]; j-- { sorted[j], sorted[j-1] = sorted[j-1], sorted[j] } } var all []byte for _, f := range sorted { p := joinPath(dir, f) data, ok := readFile(p) if !ok { continue } all = append(all, data...) all = append(all, 0) } return fnvHash(all) } func cachedBcPath(cacheBase, pkgPath, hash string) string { return joinPath(joinPath(cacheBase, safeName(pkgPath)), hash | ".bc") } func isStdlib(path string) bool { return path != "." && !hasPrefix(path, "/") && !hasPrefix(path, "./") && !hasPrefix(path, "../") } var builtinOnly []string func isBuiltinOnly(path string) bool { for _, b := range builtinOnly { if path == b { return true } } return false } func isInRuntime(path string) bool { return path == "internal/task" || path == "runtime" } func loadSysroot(path string) { data, ok := readFile(path) if !ok { return } for _, line := range splitLines(string(data)) { line = trimSpace(line) if len(line) == 0 || line[0] == '#' { continue } eq := int32(-1) for i := int32(0); i < int32(len(line)); i++ { if line[i] == '=' { eq = i break } } if eq < 0 { continue } key := line[:eq] val := line[eq+1:] os.Setenv(key, val) } } func splitBySpace(s string) []string { var result []string start := int32(0) inWord := false for i := int32(0); i < int32(len(s)); i++ { if s[i] == ' ' || s[i] == '\t' { if inWord { result = append(result, s[start:i]) inWord = false } } else { if !inWord { start = i inWord = true } } } if inWord { result = append(result, s[start:]) } return result } func safeName(pkg string) string { b := []byte{:len(pkg)} for i := int32(0); i < int32(len(pkg)); i++ { if pkg[i] == '/' { b[i] = '_' } else { b[i] = pkg[i] } } return string(b) } func extractPkgName(data []byte) string { for i := int32(0); i < int32(len(data)); i++ { if i+8 < int32(len(data)) && string(data[i:i+8]) == "package " { j := i + 8 for j < int32(len(data)) && data[j] != '\n' && data[j] != '\r' && data[j] != ' ' { j++ } return string(data[i+8 : j]) } if data[i] == '\n' { continue } if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' { for i < int32(len(data)) && data[i] != '\n' { i++ } continue } break } return "main" } func extractImports(data []byte) []string { var imports []string lines := splitLines(string(data)) inBlock := false for _, line := range lines { line = trimSpace(line) if line == "import (" { inBlock = true continue } if inBlock && line == ")" { inBlock = false continue } if inBlock { imp := extractQuoted(line) if imp != "" { imports = appendUniq(imports, imp) } } if hasPrefix(line, "import \"") { imp := extractQuoted(line[7:]) if imp != "" { imports = appendUniq(imports, imp) } } } return imports } func extractQuoted(s string) string { start := int32(-1) for i := int32(0); i < int32(len(s)); i++ { if s[i] == '"' { if start < 0 { start = i + 1 } else { return s[start:i] } } } return "" } func concatSources(dir string, files []string) []byte { imports := map[string]bool{} var bodies [][]byte pkgName := "" for _, f := range files { data, ok := readFile(joinPath(dir, f)) if !ok { continue } if pkgName == "" { pkgName = extractPkgName(data) } lines := splitLines(string(data)) var body []string i := int32(0) for i < int32(len(lines)) { line := trimSpace(lines[i]) if hasPrefix(line, "package ") { i++ continue } if line == "import (" { i++ for i < int32(len(lines)) { imp := trimSpace(lines[i]) i++ if imp == ")" { break } if imp != "" { imports[imp] = true } } continue } if hasPrefix(line, "import ") && !hasPrefix(line, "import (") { imp := line[7:] imports[imp] = true i++ continue } body = append(body, lines[i]) i++ } bodies = append(bodies, []byte(joinStrings(body, "\n"))) } var out []byte out = append(out, ("package " | pkgName | "\n")...) if len(imports) > 0 { var impKeys []string for imp := range imports { impKeys = append(impKeys, imp) } for i := int32(1); i < int32(len(impKeys)); i++ { for j := i; j > 0 && impKeys[j] < impKeys[j-1]; j-- { impKeys[j], impKeys[j-1] = impKeys[j-1], impKeys[j] } } out = append(out, "import (\n"...) for _, imp := range impKeys { out = append(out, '\t') out = append(out, imp...) out = append(out, '\n') } out = append(out, ")\n"...) } for _, b := range bodies { out = append(out, b...) out = append(out, '\n') } return out } func fileContainsPart(base string, part string) bool { plen := len(part) for i := 0; i <= len(base)-plen; i++ { if base[i:i+plen] == part { before := i == 0 || base[i-1] == '_' after := i+plen == len(base) || base[i+plen] == '_' if before && after { return true } } } return false } func shouldSkipFile(name string) bool { if hasSuffix(name, "_test.mx") { return true } base := name if hasSuffix(base, ".mx") { base = base[:len(base)-3] } if globalTargetArch == "wasm" { for _, a := range []string{"amd64", "arm64", "386"} { if fileContainsPart(base, a) { return true } } for _, s := range []string{"_linux", "_unix", "_darwin", "_posix"} { if hasSuffix(base, s) { return true } } } else { for _, a := range []string{"arm64"} { if fileContainsPart(base, a) { return true } } if hasSuffix(base, "_wasm") { return true } } exact := []string{"pipe", "multi", "replace", "search", "ioutil", "quic", "gc_stack_portable", "gc_precise", "wait_other", "os_other", "time_go122"} for _, s := range exact { if base == s { return true } } return false } func constraintRequiresForbidden(line string, forbidden []string) bool { if len(line) == 0 { return false } if line[0] == '!' { return false } topOR := false depth := int32(0) for i := int32(0); i < int32(len(line)); i++ { if line[i] == '(' { depth++ } else if line[i] == ')' { depth-- } else if depth == 0 && i+2 <= int32(len(line)) && string(line[i:i+2]) == "||" { topOR = true break } } if topOR { return false } for _, f := range forbidden { if constraintContainsPositive(line, f) { return true } } return false } func constraintContainsNegated(line string, word string) bool { wl := int32(len(word)) for i := int32(1); i <= int32(len(line))-wl; i++ { if string(line[i:i+wl]) == word && line[i-1] == '!' { after := i + wl if after < int32(len(line)) { c := line[after] if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' || c == '.' { continue } } return true } } return false } func constraintContainsPositive(line string, word string) bool { wl := int32(len(word)) for i := int32(0); i <= int32(len(line))-wl; i++ { if string(line[i:i+wl]) == word { if i > 0 && line[i-1] == '!' { continue } after := i + wl if after < int32(len(line)) { c := line[after] if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' { continue } } if i > 0 { c := line[i-1] if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' { continue } } return true } } return false } func hasBuildConstraint(data []byte, forbidden []string) bool { for i := int32(0); i < int32(len(data)) && i < 512; i++ { if data[i] == 'p' { return false } if i+9 < int32(len(data)) && string(data[i:i+9]) == "//:build " { var buf []byte for j := i + 9; j < int32(len(data)) && data[j] != '\n'; j++ { buf = append(buf, data[j]) } line := string(buf) if constraintRequiresForbidden(line, forbidden) { return true } if globalTargetArch == "wasm" { if constraintContainsPositive(line, "moxie.wasm") || constraintContainsPositive(line, "wasm") || constraintContainsPositive(line, "js") { return false } if constraintContainsNegated(line, "js") || constraintContainsNegated(line, "wasm") || constraintContainsNegated(line, "moxie.wasm") { return true } } else { if constraintContainsPositive(line, "linux") || constraintContainsPositive(line, "unix") || constraintContainsPositive(line, globalTargetArch) { return false } if constraintContainsNegated(line, "linux") || constraintContainsNegated(line, "unix") || constraintContainsNegated(line, globalTargetArch) { return true } } for _, f := range forbidden { if line == f || hasPrefix(line, f | " ") || hasPrefix(line, f | "\t") || hasPrefix(line, f | "(") { return true } } if constraintContainsPositive(line, "purego") { return true } return false } if data[i] == '\n' { continue } if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' { for i < int32(len(data)) && data[i] != '\n' { i++ } continue } } return false } func hasImport(data []byte, pkg string) bool { target := "\"" | pkg | "\"" for i := int32(0); i <= int32(len(data))-int32(len(target)); i++ { if string(data[i:i+int32(len(target))]) == target { return true } } return false } type pkgInfo struct { path string name string dir string files []string cfiles []string sfiles []string imports []string } var globalModPrefix string var globalModDir string var globalTargetOS string var globalTargetArch string var globalReplaces map[string]string func parseReplaceLine(line string, baseDir string) (string, string, bool) { arrow := int32(-1) for k := int32(0); k < int32(len(line))-1; k++ { if line[k] == '=' && line[k+1] == '>' { arrow = k break } } if arrow < 0 { return "", "", false } from := trimSpace(line[:arrow]) to := trimSpace(line[arrow+2:]) for fi := int32(0); fi < int32(len(from)); fi++ { if from[fi] == ' ' { from = from[:fi] break } } if hasPrefix(to, "./") || hasPrefix(to, "../") || hasPrefix(to, "/") { to = cleanPath(joinPath(baseDir, to)) } return from, to, true } func findModuleRoot(startDir string) (string, string) { dir := startDir for { data, ok := readFile(joinPath(dir, "moxie.mod")) if ok { modName := "" globalReplaces = map[string]string{} lines := splitLines(string(data)) inReplace := false for _, line := range lines { line = trimSpace(line) if hasPrefix(line, "module ") { modName = trimSpace(line[7:]) continue } if line == "replace (" { inReplace = true continue } if inReplace { if line == ")" { inReplace = false continue } from, to, ok2 := parseReplaceLine(line, dir) if ok2 { globalReplaces[from] = to } continue } if hasPrefix(line, "replace ") { from, to, ok2 := parseReplaceLine(line[8:], dir) if ok2 { globalReplaces[from] = to } } } if modName != "" { return modName, dir } } parent := pathDir(dir) if parent == dir || parent == "" || parent == "." { break } dir = parent } return "", "" } func mergeModReplaces(dir string) { data, ok := readFile(joinPath(dir, "moxie.mod")) if !ok { return } if globalReplaces == nil { globalReplaces = map[string]string{} } inReplace := false for _, line := range splitLines(string(data)) { line = trimSpace(line) if line == "replace (" { inReplace = true continue } if inReplace { if line == ")" { inReplace = false continue } from, to, ok2 := parseReplaceLine(line, dir) if ok2 { if _, exists := globalReplaces[from]; !exists { globalReplaces[from] = to } } continue } if hasPrefix(line, "replace ") { from, to, ok2 := parseReplaceLine(line[8:], dir) if ok2 { if _, exists := globalReplaces[from]; !exists { globalReplaces[from] = to } } } } } func discoverPkg(pkgPath, root string) *pkgInfo { var dir string if pkgPath == "." || pkgPath == ".." || hasPrefix(pkgPath, "/") || hasPrefix(pkgPath, "./") || hasPrefix(pkgPath, "../") { dir = pkgPath } else if globalReplaces != nil { if rdir, ok := globalReplaces[pkgPath]; ok { dir = rdir } if dir == "" { for from, to := range globalReplaces { if hasPrefix(pkgPath, from|"/") { rel := pkgPath[len(from)+1:] dir = joinPath(to, rel) break } } } } if dir == "" && globalModPrefix != "" && pkgPath == globalModPrefix { dir = globalModDir } if dir == "" && globalModPrefix != "" && hasPrefix(pkgPath, globalModPrefix|"/") { rel := pkgPath[len(globalModPrefix)+1:] dir = joinPath(globalModDir, rel) } if dir == "" { candidate := joinPath(getMoxiePath(), pkgPath) mxF := listFiles(candidate, ".mx") if len(mxF) > 0 { dir = candidate } } if dir != "" { mergeModReplaces(dir) } if dir == "" { dir = joinPath(root, joinPath("src", pkgPath)) mxFiles := listFiles(dir, ".mx") if len(mxFiles) == 0 { dir = joinPath(root, joinPath("src/vendor", pkgPath)) } } mxFiles := listFiles(dir, ".mx") var cFiles []string for _, cf := range listFiles(dir, ".c") { if hasSuffix(cf, "_test.c") { continue } cdata, cok := readFile(joinPath(dir, cf)) if cok && hasBuildConstraint(cdata, []string{"ignore"}) { continue } cFiles = append(cFiles, cf) } allSFiles := listFiles(dir, ".s") var sFiles []string for _, sf := range allSFiles { if !shouldSkipFile(sf[:len(sf)-2] | ".mx") { sFiles = append(sFiles, sf) } } allMx := mxFiles var forbidden []string if globalTargetArch == "wasm" { forbidden = []string{"linux", "unix", "darwin", "amd64", "arm64", "ignore", "compiler_bootstrap", "scheduler.tasks", "scheduler.cores", "scheduler.asyncify", "faketime", "baremetal", "nintendoswitch", "boringcrypto", "cgo"} } else { forbidden = []string{"wasm", "js", "ignore", "compiler_bootstrap", "scheduler.tasks", "scheduler.cores", "scheduler.asyncify", "faketime", "baremetal", "nintendoswitch", "wasm_unknown", "boringcrypto", "cgo", "gc.leaking", "gc.none", "gc.dealloc", "gc.conservative", "gc.precise", "runtime_asserts", "runtime_memhash_tsip", "runtime_memhash_leveldb", "avr", "moxie.wasm"} } var files []string for _, f := range allMx { if shouldSkipFile(f) { continue } data, ok := readFile(joinPath(dir, f)) if !ok { continue } bc := hasBuildConstraint(data, forbidden) if bc { writeStr(2, " skip-constraint: " | f | "\n") continue } fileImps := extractImports(data) skipIter := false for _, imp := range fileImps { if imp == "iter" { skipIter = true break } } if skipIter { writeStr(2, " skip-iter-import: " | f | "\n") continue } writeStr(2, " include: " | f | "\n") files = append(files, f) } if len(files) == 0 { return nil } var allImports []string pkgName := "" for _, f := range files { data, ok := readFile(joinPath(dir, f)) if !ok { continue } if pkgName == "" { pkgName = extractPkgName(data) } for _, imp := range extractImports(data) { allImports = appendUniq(allImports, imp) } } writeStr(2, " discover " | pkgPath | " [") for fi, ff := range files { if fi > 0 { writeStr(2, ", ") } writeStr(2, ff) } writeStr(2, "]\n") return &pkgInfo{ path: pkgPath, name: pkgName, dir: dir, files: files, cfiles: cFiles, sfiles: sFiles, imports: allImports, } } type resolver struct { visited map[string]bool order []*pkgInfo root string } func (r *resolver) walk(path string) { if r.visited[path] { return } r.visited[path] = true if isBuiltinOnly(path) { return } pkg := discoverPkg(path, r.root) if pkg == nil { return } for _, imp := range pkg.imports { r.walk(imp) } r.order = append(r.order, pkg) } func resolveAll(rootPkg string, root string) []*pkgInfo { r := &resolver{ visited: map[string]bool{}, root: root, } r.walk(rootPkg) return r.order } func registerBuiltins() { initUniverse() ensureImportRegistry() importRegistry = map[string]*TCPackage{} // unsafe - compiler builtin, no source unsafePkg := NewTCPackage("unsafe", "unsafe") unsafePkg.Scope().Insert(NewTypeName(unsafePkg, "Pointer", Typ[UnsafePointer])) unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Sizeof", parseSignatureDesc("interface{}->int32"))) unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Offsetof", parseSignatureDesc("interface{}->int32"))) unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Alignof", parseSignatureDesc("interface{}->int32"))) unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Slice", parseSignatureDesc("ptr,int32->[]uint8"))) unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "SliceData", parseSignatureDesc("[]uint8->ptr"))) importRegistry["unsafe"] = unsafePkg // moxie - spawn/codec runtime, special handling importRegistry["moxie"] = NewTCPackage("moxie", "moxie") // runtime - assembly + C, pre-compiled in runtime.bc rtPkg := NewTCPackage("runtime", "runtime") rtPkg.Scope().Insert(NewTCFunc(rtPkg, "InitCShared", parseSignatureDesc(""))) rtPkg.Scope().Insert(NewTCFunc(rtPkg, "LastSpawnedParentFd", parseSignatureDesc("->int32"))) rtPkg.Scope().Insert(NewTCFunc(rtPkg, "PipeClosed", parseSignatureDesc("int32->bool"))) rtPkg.Scope().Insert(NewTCFunc(rtPkg, "PipeFDCanSend", parseSignatureDesc("int32->bool"))) rtPkg.Scope().Insert(NewTCFunc(rtPkg, "Caller", parseSignatureDesc("int32->ptr,string,int32,bool"))) rtPkg.Scope().Insert(NewTCVar(rtPkg, "ChildPipeFd", Typ[Int32])) goarchConst := NewTCConst(rtPkg, "GOARCH", Typ[TCString], constStr{globalTargetArch}) rtPkg.Scope().Insert(goarchConst) goosConst := NewTCConst(rtPkg, "GOOS", Typ[TCString], constStr{globalTargetOS}) rtPkg.Scope().Insert(goosConst) importRegistry["runtime"] = rtPkg } func addPtrMethod(pkg *TCPackage, named *Named, name, sigDesc string) { var sig *Signature if sigDesc == "" { sig = NewSignature(nil, nil, nil, false) } else { sig = parseSignatureDesc(sigDesc) } fn := NewTCFunc(pkg, name, sig) fn.hasPtrRecv = true named.AddMethod(fn) } func compileSource(src []byte, pkgName, triple string) string { ir := CompileToIR(src, pkgName, triple) if len(ir) > 0 && ir[0] == ';' { writeStr(2, ir) fatal("compile error for " | pkgName) } if len(ir) == 0 { fatal("empty IR for " | pkgName) } return ir } func removeAll(path string) { entries := listFiles(path, "") if entries == nil { return } for _, e := range entries { os.Remove(joinPath(path, e)) } os.Remove(path) } type modRequire struct { path string version string } func parseModRequires(dir string) (string, []modRequire, map[string]string) { data, ok := readFile(joinPath(dir, "moxie.mod")) if !ok { return "", nil, nil } modName := "" var reqs []modRequire repls := map[string]string{} inRequire := false inReplace := false for _, line := range splitLines(string(data)) { line = trimSpace(line) if hasPrefix(line, "module ") { modName = trimSpace(line[7:]) continue } if line == "require (" { inRequire = true continue } if inRequire { if line == ")" { inRequire = false continue } parts := splitBySpace(line) if len(parts) >= 2 { reqs = append(reqs, modRequire{path: parts[0], version: parts[1]}) } else if len(parts) == 1 { reqs = append(reqs, modRequire{path: parts[0], version: ""}) } continue } if hasPrefix(line, "require ") { parts := splitBySpace(line[8:]) if len(parts) >= 2 { reqs = append(reqs, modRequire{path: parts[0], version: parts[1]}) } else if len(parts) == 1 { reqs = append(reqs, modRequire{path: parts[0], version: ""}) } continue } if line == "replace (" { inReplace = true continue } if inReplace { if line == ")" { inReplace = false continue } from, to, ok2 := parseReplaceLine(line, dir) if ok2 { repls[from] = to } continue } if hasPrefix(line, "replace ") { from, to, ok2 := parseReplaceLine(line[8:], dir) if ok2 { repls[from] = to } } } return modName, reqs, repls } func cmdFetch(url string) { mpath := getMoxiePath() mkdirAll(mpath, 0755) lockPath := joinPath(mpath, ".lock") _, locked := readFile(lockPath) if locked { fatal("fetch already running (" | lockPath | ")") } writeFile(lockPath, []byte("1"), 0644) rootDir := "." if url != "" { tmpdir := "/tmp/mxc-fetch" removeAll(tmpdir) writeStr(2, "clone " | url | "\n") if run([]string{"git", "clone", url, tmpdir}) != 0 { os.Remove(lockPath) fatal("clone failed: " | url) } modName, _, _ := parseModRequires(tmpdir) if modName == "" { removeAll(tmpdir) os.Remove(lockPath) fatal("cloned repo has no moxie.mod") } dest := joinPath(mpath, modName) _, exists := readFile(joinPath(dest, ".git/HEAD")) if exists { removeAll(tmpdir) writeStr(2, " already exists: " | modName | ", updating remote and pulling\n") run([]string{"git", "-C", dest, "remote", "set-url", "origin", url}) run([]string{"git", "-C", dest, "fetch", "origin"}) run([]string{"git", "-C", dest, "reset", "--hard", "origin/HEAD"}) } else { mkdirAll(pathDir(dest), 0755) if run([]string{"mv", tmpdir, dest}) != 0 { removeAll(tmpdir) os.Remove(lockPath) fatal("move failed: " | tmpdir | " -> " | dest) } } writeStr(2, "module: " | modName | "\n") rootDir = dest } modName, reqs, repls := parseModRequires(rootDir) if modName == "" { os.Remove(lockPath) fatal("no moxie.mod in " | rootDir) } if url == "" { writeStr(2, "module: " | modName | "\n") } var queue []modRequire for _, r := range reqs { if _, ok := repls[r.path]; ok { writeStr(2, " skip (replaced): " | r.path | "\n") continue } queue = append(queue, r) } seen := map[string]bool{} for len(queue) > 0 { req := queue[0] queue = queue[1:] if seen[req.path] { continue } seen[req.path] = true dest := joinPath(mpath, req.path) gitHead := joinPath(dest, ".git/HEAD") _, exists := readFile(gitHead) if !exists { mkdirAll(pathDir(dest), 0755) cloneURL := "https://" | req.path writeStr(2, " clone " | req.path | "\n") if run([]string{"git", "clone", cloneURL, dest}) != 0 { writeStr(2, " FAIL: clone " | req.path | "\n") continue } } if req.version != "" && req.version != "v0.0.0" { if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 { writeStr(2, " tag " | req.version | " not found, pulling\n") run([]string{"git", "-C", dest, "pull"}) if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 { writeStr(2, " WARN: " | req.version | " unavailable for " | req.path | "\n") } } else { writeStr(2, " " | req.path | " @ " | req.version | "\n") } } else if exists { writeStr(2, " pull " | req.path | "\n") run([]string{"git", "-C", dest, "pull"}) } else { writeStr(2, " fetched " | req.path | "\n") } _, subReqs, _ := parseModRequires(dest) for _, sr := range subReqs { if !seen[sr.path] { queue = append(queue, sr) } } } os.Remove(lockPath) writeStr(2, "fetch done\n") } func cacheClean() { base := pkgCacheDir() writeStr(2, "cleaning cache: " | base | "\n") entries := listFiles(base, "") if entries == nil { writeStr(2, " (empty)\n") return } for _, e := range entries { removeAll(joinPath(base, e)) } os.Remove(base) writeStr(2, " done\n") } func main() { builtinOnly = []string{"unsafe", "moxie", "runtime", "runtime/interrupt", "internal/task", "internal/gclayout"} args := getArgs() if len(args) < 2 { writeStr(2, "usage: mxc ...\n") os.Exit(1) } cmd := args[1] if cmd == "version" { writeStr(1, "mxc 1.9.2\n") return } if cmd == "cache" { if len(args) >= 3 && args[2] == "clean" { cacheClean() return } writeStr(2, "usage: mxc cache clean\n") os.Exit(1) } if cmd == "fetch" { fetchURL := "" if len(args) >= 3 { fetchURL = args[2] } cmdFetch(fetchURL) return } if cmd != "build" { writeStr(2, "usage: mxc ...\n") os.Exit(1) } outpath := "" pkgPath := "" printIR := false forceRebuild := false targetFlag := "" i := int32(2) for i < int32(len(args)) { if args[i] == "-o" && i+1 < int32(len(args)) { outpath = args[i+1] i += 2 } else if args[i] == "-target" && i+1 < int32(len(args)) { targetFlag = args[i+1] i += 2 } else if args[i] == "-print-ir" { printIR = true i++ } else if args[i] == "-a" { forceRebuild = true i++ } else if args[i] == "-x" || args[i] == "-work" { i++ } else { pkgPath = args[i] i++ } } if pkgPath == "" { fatal("no package specified") } if outpath == "" { outpath = pathBase(pkgPath) } root := getenv("MOXIEROOT") if root == "" { root = "." } globalTargetOS = "linux" globalTargetArch = "amd64" triple := "x86_64-unknown-linux-musleabihf" if targetFlag == "js/wasm" || targetFlag == "wasm" { globalTargetOS = "js" globalTargetArch = "wasm" triple = "wasm32-unknown-js" } isWasm := globalTargetArch == "wasm" clang := "clang-21" searchDir := "." if hasPrefix(pkgPath, "/") { searchDir = pkgPath } else if hasPrefix(pkgPath, "./") || hasPrefix(pkgPath, "../") { searchDir = pkgPath } globalModPrefix, globalModDir = findModuleRoot(searchDir) if globalModPrefix == "" { globalModPrefix, globalModDir = findModuleRoot(".") } registerBuiltins() pkgs := resolveAll(pkgPath, root) if len(pkgs) == 0 { fatal("no packages to compile") } tmpdir := "/tmp/mxc-build" os.RemoveAll(tmpdir) mkdirAll(tmpdir, 0755) cacheBase := pkgCacheDir() var allBcFiles []string var overrideBcFiles []string for _, pkg := range pkgs { compilePath := pkg.path if compilePath == "." || hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") { compilePath = pkg.name } if isStdlib(pkg.path) { hash := pkgCacheKey(pkg.dir, pkg.files) cached := cachedBcPath(cacheBase, pkg.path, hash) _, cacheOk := readFile(cached) if cacheOk && !forceRebuild { writeStr(2, " cached " | pkg.path | "\n") src := concatSources(pkg.dir, pkg.files) writeStr(2, " tc-start " | compilePath | " srcLen=" | simpleItoa(len(src)) | "\n") TypeCheckOnly(src, compilePath) writeStr(2, " tc-done " | compilePath | "\n") src = nil if isInRuntime(pkg.path) { overrideBcFiles = append(overrideBcFiles, cached) } else { allBcFiles = append(allBcFiles, cached) } continue } writeStr(2, " compile " | pkg.path | "\n") src := concatSources(pkg.dir, pkg.files) ir := compileSource(src, compilePath, triple) src = nil llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll") writeFile(llFile, []byte(ir), 0644) ir = "" bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc") rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile}) if rc != 0 { fatal("clang failed for " | pkg.path) } cacheDir := joinPath(cacheBase, safeName(pkg.path)) mkdirAll(cacheDir, 0755) data, rok := readFile(bcFile) if rok { writeFile(cached, data, 0644) } data = nil if isInRuntime(pkg.path) { overrideBcFiles = append(overrideBcFiles, bcFile) } else { allBcFiles = append(allBcFiles, bcFile) } } else { writeStr(2, " compile " | pkg.path | "\n") src := concatSources(pkg.dir, pkg.files) ir := compileSource(src, compilePath, triple) src = nil if printIR { writeStr(1, ir) } llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll") writeFile(llFile, []byte(ir), 0644) ir = "" bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc") rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile}) if rc != 0 { fatal("clang failed for " | pkg.path) } allBcFiles = append(allBcFiles, bcFile) for _, cf := range pkg.cfiles { cPath := joinPath(pkg.dir, cf) cbcFile := joinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc") rc = run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile}) if rc != 0 { fatal("clang failed for " | cf) } allBcFiles = append(allBcFiles, cbcFile) } } } loadSysroot(joinPath(root, "_sysroot.env")) sysroot := joinPath(root, "sysroot") runtimeBc := "" muslDir := "" comprtLib := "" bdwgcLib := "" muslLib := "" if isWasm { runtimeBc = joinPath(joinPath(sysroot, "wasm"), "runtime.bc") } else { runtimeBc = getenv("RUNTIME_BC") if runtimeBc == "" { runtimeBc = joinPath(sysroot, "runtime.bc") _, rok := readFile(runtimeBc) if !rok { runtimeBc = joinPath(root, "_runtime.bc") } } muslDir = getenv("MUSL_DIR") if muslDir == "" { muslDir = joinPath(sysroot, "musl") } comprtLib = getenv("COMPRT_LIB") if comprtLib == "" { comprtLib = joinPath(joinPath(sysroot, "compiler-rt"), "lib.a") } bdwgcLib = getenv("BDWGC_LIB") if bdwgcLib == "" { bdwgcLib = joinPath(joinPath(sysroot, "bdwgc"), "lib.a") } muslLib = getenv("MUSL_LIB") if muslLib == "" { muslLib = joinPath(joinPath(sysroot, "musl"), "lib.a") } } writeStr(2, " generate initAll\n") ipt := "i64" sty := "{ptr, i64, i64}" if isWasm { ipt = "i32" sty = "{ptr, i32, i32}" } var initCalls string for _, pkg := range pkgs { compilePath := pkg.path if compilePath == "." || hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") { compilePath = pkg.name } if compilePath == "main" { continue } initSym := irGlobalSymbol(compilePath, "main") initCalls = initCalls | " call void " | initSym | "(ptr %context)\n" } initAllIR := "@\"os.Args\" = external global " | sty | "\n" initAllIR = initAllIR | "\ndefine hidden void @runtime.initAll(ptr %context) {\nentry:\n" | initCalls if !isWasm { initAllIR = initAllIR | " %_osargs = call " | sty | " @\"os.runtime_args\"(ptr null)\n" initAllIR = initAllIR | " store " | sty | " %_osargs, ptr @\"os.Args\"\n" } initAllIR = initAllIR | " ret void\n}\n" declared := map[string]bool{} for _, pkg := range pkgs { compilePath := pkg.path if compilePath == "." || hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") { compilePath = pkg.name } if compilePath == "main" { continue } declSym := irGlobalSymbol(compilePath, "main") if declared[declSym] { continue } declared[declSym] = true initAllIR = initAllIR | "declare void " | declSym | "(ptr)\n" } initAllIR = initAllIR | "\n; runtime linkname shims\n" initAllIR = initAllIR | "declare hidden void @runtime.sleepTicks(i64, ptr)\n" initAllIR = initAllIR | "define hidden void @\"time.Sleep\"(i64 %d, ptr %ctx) {\n" initAllIR = initAllIR | " %1 = icmp sle i64 %d, 0\n" initAllIR = initAllIR | " br i1 %1, label %ret, label %dosleep\n" initAllIR = initAllIR | "dosleep:\n" initAllIR = initAllIR | " call void @runtime.sleepTicks(i64 %d, ptr %ctx)\n" initAllIR = initAllIR | " br label %ret\n" initAllIR = initAllIR | "ret:\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden i32 @\"internal/poll.runtime_pollWait\"(i64 %ctx, i32 %mode, ptr %context) {\n" initAllIR = initAllIR | " ret i32 0\n}\n" initAllIR = initAllIR | "define hidden i32 @\"internal/poll.runtime_pollReset\"(i64 %ctx, i32 %mode, ptr %context) {\n" initAllIR = initAllIR | " ret i32 0\n}\n" initAllIR = initAllIR | "define hidden void @slices.Sort(" | sty | " %x, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden void @slices.SortFunc(" | sty | " %x, {ptr, ptr} %cmp, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden void @slices.SortStableFunc(" | sty | " %x, {ptr, ptr} %cmp, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "\n; time linkname shims\n" initAllIR = initAllIR | "declare hidden i64 @runtime.nanotime(ptr)\n" initAllIR = initAllIR | "define hidden i64 @\"time.runtimeNano\"(ptr %ctx) {\n" initAllIR = initAllIR | " %1 = call i64 @runtime.nanotime(ptr %ctx)\n" initAllIR = initAllIR | " ret i64 %1\n}\n" initAllIR = initAllIR | "define hidden i1 @\"time.resetTimer\"(ptr %t, i64 %when, i64 %period, ptr %ctx) {\n" initAllIR = initAllIR | " ret i1 false\n}\n" initAllIR = initAllIR | "\n; math/rand linkname shim\n" initAllIR = initAllIR | "declare hidden {i64, i1} @runtime.hardwareRand(ptr)\n" initAllIR = initAllIR | "define hidden i64 @\"math/rand.runtime_rand\"(ptr %ctx) {\n" initAllIR = initAllIR | " %1 = call {i64, i1} @runtime.hardwareRand(ptr %ctx)\n" initAllIR = initAllIR | " %2 = extractvalue {i64, i1} %1, 0\n" initAllIR = initAllIR | " ret i64 %2\n}\n" initAllIR = initAllIR | "\n; internal/poll linkname shims\n" initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollClose\"(i64 %ctx, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_Semacquire\"(ptr %sema, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_Semrelease\"(ptr %sema, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollSetDeadline\"(i64 %ctx, i64 %d, i32 %mode, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollUnblock\"(i64 %ctx, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "\n; time linkname shims (timer)\n" initAllIR = initAllIR | "define hidden ptr @\"time.newTimer\"(i64 %when, i64 %period, {ptr, ptr} %f, {ptr, ptr} %arg, ptr %cp, ptr %ctx) {\n" initAllIR = initAllIR | " %1 = call ptr @runtime.alloc(" | ipt | " 64, ptr null, ptr null)\n" initAllIR = initAllIR | " ret ptr %1\n}\n" initAllIR = initAllIR | "declare hidden ptr @runtime.alloc(" | ipt | ", ptr, ptr)\n" initAllIR = initAllIR | "define hidden i1 @\"time.stopTimer\"(ptr %t, ptr %ctx) {\n" initAllIR = initAllIR | " ret i1 false\n}\n" initAllIR = initAllIR | "define hidden i1 @\"time.runtimeIsBubbled\"(ptr %ctx) {\n" initAllIR = initAllIR | " ret i1 false\n}\n" initAllIR = initAllIR | "\n; generic monomorphization stubs\n" initAllIR = initAllIR | "define hidden void @\"smesh.lol/pkg/nostr/varint.Encode\"({ptr, ptr} %w, i64 %v, ptr %ctx) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "\n; net linkname shim\n" initAllIR = initAllIR | "define hidden i64 @\"net.runtime_rand\"(ptr %ctx) {\n" initAllIR = initAllIR | " %1 = call {i64, i1} @runtime.hardwareRand(ptr %ctx)\n" initAllIR = initAllIR | " %2 = extractvalue {i64, i1} %1, 0\n" initAllIR = initAllIR | " ret i64 %2\n}\n" initAllIR = initAllIR | "\n; poll init/open shims\n" initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollServerInit\"(ptr %ctx) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden i64 @\"internal/poll.runtime_pollOpen\"(i64 %fd, ptr %ctx) {\n" initAllIR = initAllIR | " ret i64 0\n}\n" initAllIR = initAllIR | "\n; syscall shims\n" initAllIR = initAllIR | "define hidden i32 @syscall.Getpagesize(ptr %ctx) {\n" initAllIR = initAllIR | " ret i32 4096\n}\n" initAllIR = initAllIR | "\n; unsafe builtins (compiler should resolve these at compile time)\n" initAllIR = initAllIR | "define hidden i32 @unsafe.Sizeof({ptr, ptr} %x, ptr %ctx) {\n" initAllIR = initAllIR | " ret i32 8\n}\n" initAllIR = initAllIR | "define hidden i32 @unsafe.Offsetof({ptr, ptr} %x, ptr %ctx) {\n" initAllIR = initAllIR | " ret i32 0\n}\n" if !isWasm { initAllIR = initAllIR | "\n; os.runtime_args - builds os.Args from main_argc/main_argv\n" initAllIR = initAllIR | "@runtime.main_argc = external global i32\n" initAllIR = initAllIR | "@runtime.main_argv = external global ptr\n" initAllIR = initAllIR | "declare i64 @strlen(ptr)\n" initAllIR = initAllIR | "define hidden {ptr, i64, i64} @\"os.runtime_args\"(ptr %ctx) {\n" initAllIR = initAllIR | "entry:\n" initAllIR = initAllIR | " %argc = load i32, ptr @runtime.main_argc\n" initAllIR = initAllIR | " %n = sext i32 %argc to i64\n" initAllIR = initAllIR | " %cmp0 = icmp sle i64 %n, 0\n" initAllIR = initAllIR | " br i1 %cmp0, label %ret0, label %alloc\n" initAllIR = initAllIR | "ret0:\n" initAllIR = initAllIR | " ret {ptr, i64, i64} zeroinitializer\n" initAllIR = initAllIR | "alloc:\n" initAllIR = initAllIR | " %strSz = mul i64 %n, 24\n" initAllIR = initAllIR | " %backing = call ptr @runtime.alloc(i64 %strSz, ptr null, ptr null)\n" initAllIR = initAllIR | " %argv = load ptr, ptr @runtime.main_argv\n" initAllIR = initAllIR | " br label %loop\n" initAllIR = initAllIR | "loop:\n" initAllIR = initAllIR | " %i = phi i64 [0, %alloc], [%i1, %next]\n" initAllIR = initAllIR | " %ap = getelementptr ptr, ptr %argv, i64 %i\n" initAllIR = initAllIR | " %cstr = load ptr, ptr %ap\n" initAllIR = initAllIR | " %slen = call i64 @strlen(ptr %cstr)\n" initAllIR = initAllIR | " %off = mul i64 %i, 24\n" initAllIR = initAllIR | " %sp = getelementptr i8, ptr %backing, i64 %off\n" initAllIR = initAllIR | " store ptr %cstr, ptr %sp\n" initAllIR = initAllIR | " %sp1 = getelementptr i8, ptr %sp, i64 8\n" initAllIR = initAllIR | " store i64 %slen, ptr %sp1\n" initAllIR = initAllIR | " %sp2 = getelementptr i8, ptr %sp, i64 16\n" initAllIR = initAllIR | " store i64 %slen, ptr %sp2\n" initAllIR = initAllIR | " %i1 = add i64 %i, 1\n" initAllIR = initAllIR | " %done = icmp sge i64 %i1, %n\n" initAllIR = initAllIR | " br i1 %done, label %ret, label %next\n" initAllIR = initAllIR | "next:\n" initAllIR = initAllIR | " br label %loop\n" initAllIR = initAllIR | "ret:\n" initAllIR = initAllIR | " %r0 = insertvalue {ptr, i64, i64} undef, ptr %backing, 0\n" initAllIR = initAllIR | " %r1 = insertvalue {ptr, i64, i64} %r0, i64 %n, 1\n" initAllIR = initAllIR | " %r2 = insertvalue {ptr, i64, i64} %r1, i64 %n, 2\n" initAllIR = initAllIR | " ret {ptr, i64, i64} %r2\n" initAllIR = initAllIR | "}\n" } initAllIR = initAllIR | "\n; functions with uncompiled bodies\n" initAllIR = initAllIR | "define hidden void @\"crypto/tls.signedMessage\"(" | sty | " %ctx, {ptr, ptr} %transcript, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllIR = initAllIR | "define hidden void @\"math/big.Float.Text\"(ptr %x, i8 %format, i32 %prec, ptr %context) {\n" initAllIR = initAllIR | " ret void\n}\n" initAllFile := joinPath(tmpdir, "initall.ll") writeFile(initAllFile, []byte(initAllIR), 0644) initAllBc := joinPath(tmpdir, "initall.bc") rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", initAllFile, "-o", initAllBc}) if rc != 0 { fatal("clang failed for initall") } writeStr(2, " merge bitcode\n") llvmLink := "llvm-link-21" mergedBc := joinPath(tmpdir, "merged.bc") linkArgs := []string{llvmLink, "-o", mergedBc, runtimeBc} stdlibBc := joinPath(sysroot, "stdlib.bc") if _, slok := readFile(stdlibBc); slok { linkArgs = append(linkArgs, stdlibBc) } if !isWasm { shimsBc := joinPath(sysroot, "hashmap_shims.bc") linkArgs = append(linkArgs, shimsBc) } for _, bc := range overrideBcFiles { linkArgs = append(linkArgs, "--override=" | bc) } for _, bc := range allBcFiles { if isWasm { linkArgs = append(linkArgs, "--override=" | bc) } else { linkArgs = append(linkArgs, bc) } } linkArgs = append(linkArgs, initAllBc) if !isWasm { runtimeGoBc := joinPath(sysroot, "runtime_go.bc") if _, gok := readFile(runtimeGoBc); gok { linkArgs = append(linkArgs, "--override=" | runtimeGoBc) } stubsBc := joinPath(sysroot, "stubs_moxie.bc") if _, sok := readFile(stubsBc); sok { linkArgs = append(linkArgs, "--override=" | stubsBc) } deallocBc := joinPath(sysroot, "dealloc_freelist.bc") if _, dok := readFile(deallocBc); dok { linkArgs = append(linkArgs, "--override=" | deallocBc) } } rc = run(linkArgs) if rc != 0 { fatal("llvm-link failed") } writeStr(2, " link\n") var ldArgs []string if isWasm { mergedObj := joinPath(tmpdir, "merged.o") rc = run([]string{"llc-21", "-filetype=obj", "-mtriple=wasm32-unknown-unknown", "-o", mergedObj, mergedBc}) if rc != 0 { fatal("llc failed for wasm") } ldArgs = []string{"wasm-ld", "--export-dynamic", "--allow-undefined", "--gc-sections", "-o", outpath, mergedObj, "-z", "stack-size=67108864"} } else { objFiles := getenv("OBJ_FILES") if objFiles == "" { objEntries := listFiles(joinPath(sysroot, "obj"), ".bc") if objEntries != nil { for _, e := range objEntries { objFiles = objFiles | joinPath(joinPath(sysroot, "obj"), e) | " " } } } ldArgs = []string{"ld.lld-21", "--gc-sections", "-o", outpath, joinPath(muslDir, "crt1.o"), mergedBc} for _, f := range splitBySpace(objFiles) { if f != "" { ldArgs = append(ldArgs, f) } } ldArgs = append(ldArgs, comprtLib, muslLib, "--lto-O0", "-z", "stack-size=67108864") } rc = run(ldArgs) if rc != 0 { fatal("linker failed") } writeStr(2, " -> " | outpath | "\n") }