spawnbinary.go raw
1 package compiler
2
3 // Cross-binary spawn: when a spawn target's package was loaded from a .mxh
4 // cache file (loader.MXHPackages), the compiler routes to SpawnBinary instead
5 // of spawnDomain. The binary path and .mxh hash are embedded as string
6 // constants resolved from the mxinstall cache at compile time.
7
8 import (
9 "strings"
10
11 "moxie/cache"
12 "moxie/loader"
13 "golang.org/x/tools/go/ssa"
14 "tinygo.org/x/go-llvm"
15 )
16
17 // isExternalMXHSpawn returns true if the spawn target function's package was
18 // loaded from a .mxh cache file and should use the cross-binary spawn path.
19 func (b *builder) isExternalMXHSpawn(fn *ssa.Function) bool {
20 if fn.Package() == nil {
21 return false
22 }
23 return b.MXHPackages[fn.Package().Pkg.Path()]
24 }
25
26 // createCrossBinarySpawn handles spawn() calls whose target is in a .mxh
27 // package. It emits a call to runtime.SpawnBinary with the cached binary path
28 // and the .mxh content hash for the protocol types crossing the boundary.
29 //
30 // The child binary is a standalone program that reads MOXIE_IPC_FD from its
31 // environment to find the IPC channel established by SpawnBinary. The .mxh
32 // hash is exchanged during the handshake to verify protocol compatibility.
33 func (b *builder) createCrossBinarySpawn(targetFn *ssa.Function, instr *ssa.CallCommon) (llvm.Value, error) {
34 importPath := targetFn.Package().Pkg.Path()
35
36 // Resolve binary path from cache.
37 binPath := cache.BinPath(b.Triple, importPath, binaryBaseName(importPath))
38
39 // Look up the .mxh hash for this package.
40 hash := loader.MXHHashForImport(importPath)
41 if hash == "" {
42 b.addError(instr.Pos(), "spawn: no .mxh found for "+importPath+"; run `moxie install "+importPath+"`")
43 return llvm.Value{}, nil
44 }
45
46 // Build LLVM string constants for binPath and hash.
47 binPathVal := b.makeStringConst(binPath)
48 hashVal := b.makeStringConst(hash)
49
50 // Call runtime.SpawnBinary(binPath, hash).
51 runtimePkg := b.program.ImportedPackage("runtime")
52 spawnBinaryMember, ok := runtimePkg.Members["SpawnBinary"]
53 if !ok {
54 b.addError(instr.Pos(), "spawn: runtime.SpawnBinary not found — rebuild runtime")
55 return llvm.Value{}, nil
56 }
57 fnType, fnVal := b.getFunction(spawnBinaryMember.(*ssa.Function))
58 b.createCall(fnType, fnVal, []llvm.Value{binPathVal, hashVal, llvm.Undef(b.dataPtrType)}, "ipc.fd")
59
60 // Cross-binary spawn doesn't return a lifecycle channel yet.
61 // TODO: extend SpawnBinary to register domain and return lifecycle chan.
62 return llvm.Undef(b.dataPtrType), nil
63 }
64
65 // binaryBaseName extracts the binary name from an import path.
66 // "git.smesh.lol/iskradb" → "iskradb"
67 func binaryBaseName(importPath string) string {
68 if i := strings.LastIndexByte(importPath, '/'); i >= 0 {
69 return importPath[i+1:]
70 }
71 return importPath
72 }
73
74 // makeStringConst creates an LLVM string constant (_string struct) for s.
75 // Follows the same pattern as constant string emission in compiler.go:3155.
76 func (b *builder) makeStringConst(s string) llvm.Value {
77 strLen := llvm.ConstInt(b.uintptrType, uint64(len(s)), false)
78 var strPtr llvm.Value
79 if s != "" {
80 globalType := llvm.ArrayType(b.ctx.Int8Type(), len(s))
81 global := llvm.AddGlobal(b.mod, globalType, ".spawn.str")
82 global.SetInitializer(b.ctx.ConstString(s, false))
83 global.SetLinkage(llvm.InternalLinkage)
84 global.SetGlobalConstant(true)
85 global.SetUnnamedAddr(true)
86 global.SetAlignment(1)
87 zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
88 strPtr = llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero})
89 } else {
90 strPtr = llvm.ConstNull(b.dataPtrType)
91 }
92 return llvm.ConstNamedStruct(b.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen, strLen})
93 }
94