ffi.go raw
1 package compiler
2
3 import (
4 "moxie/loader"
5 "golang.org/x/tools/go/ssa"
6 "tinygo.org/x/go-llvm"
7 )
8
9 // isFFIFunction returns true if fn is an mxh FFI function backed by dlopen/dlsym.
10 func (c *compilerContext) isFFIFunction(fn *ssa.Function) bool {
11 if fn.Package() == nil || fn.Package().Pkg == nil {
12 return false
13 }
14 return loader.MXHLibrary(fn.Package().Pkg.Path()) != ""
15 }
16
17 // createFFIFunction generates a function body that resolves the symbol
18 // via dlopen/dlsym at first call, then dispatches through the function pointer.
19 func (c *compilerContext) createFFIFunction(fn *ssa.Function, llvmFn llvm.Value) {
20 pkgPath := fn.Package().Pkg.Path()
21 libPath := loader.MXHLibrary(pkgPath)
22 symName := loader.MXHSymbol(pkgPath, fn.Name())
23 if symName == "" {
24 symName = fn.Name()
25 }
26
27 // Create globals for the library handle and function pointer.
28 globalPrefix := "_mxh_" + fn.Name()
29 libHandleGlobal := c.getOrCreateFFILibHandle(pkgPath, libPath)
30 fptrGlobal := llvm.AddGlobal(c.mod, c.dataPtrType, globalPrefix+"_fptr")
31 fptrGlobal.SetInitializer(llvm.ConstNull(c.dataPtrType))
32 fptrGlobal.SetLinkage(llvm.InternalLinkage)
33
34 fnType := llvmFn.GlobalValueType()
35 llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
36 llvmFn.SetUnnamedAddr(true)
37
38 // Build the function body.
39 entry := c.ctx.AddBasicBlock(llvmFn, "entry")
40 resolveBlock := c.ctx.AddBasicBlock(llvmFn, "resolve")
41 callBlock := c.ctx.AddBasicBlock(llvmFn, "call")
42
43 irbuilder := c.ctx.NewBuilder()
44 defer irbuilder.Dispose()
45
46 // entry: check if function pointer is resolved
47 irbuilder.SetInsertPointAtEnd(entry)
48 fptr := irbuilder.CreateLoad(c.dataPtrType, fptrGlobal, "fptr")
49 isNull := irbuilder.CreateICmp(llvm.IntEQ, fptr, llvm.ConstNull(c.dataPtrType), "isnull")
50 irbuilder.CreateCondBr(isNull, resolveBlock, callBlock)
51
52 // resolve: call dlopen + dlsym, store function pointer
53 irbuilder.SetInsertPointAtEnd(resolveBlock)
54 libHandle := irbuilder.CreateLoad(c.dataPtrType, libHandleGlobal, "libh")
55 libIsNull := irbuilder.CreateICmp(llvm.IntEQ, libHandle, llvm.ConstNull(c.dataPtrType), "libnull")
56
57 resolveLibBlock := c.ctx.AddBasicBlock(llvmFn, "resolve.lib")
58 resolveSym := c.ctx.AddBasicBlock(llvmFn, "resolve.sym")
59 irbuilder.CreateCondBr(libIsNull, resolveLibBlock, resolveSym)
60
61 // resolve.lib: dlopen the library
62 irbuilder.SetInsertPointAtEnd(resolveLibBlock)
63 dlopenFn := c.getOrDeclareDlopen()
64 libPathStr := c.createCString(libPath)
65 rtldNow := llvm.ConstInt(c.ctx.Int32Type(), 2, false) // RTLD_NOW
66 newHandle := irbuilder.CreateCall(dlopenFn.GlobalValueType(), dlopenFn, []llvm.Value{libPathStr, rtldNow}, "newlib")
67 irbuilder.CreateStore(newHandle, libHandleGlobal)
68 irbuilder.CreateBr(resolveSym)
69
70 // resolve.sym: dlsym the function
71 irbuilder.SetInsertPointAtEnd(resolveSym)
72 libHandlePhi := irbuilder.CreatePHI(c.dataPtrType, "lib.phi")
73 libHandlePhi.AddIncoming([]llvm.Value{libHandle, newHandle}, []llvm.BasicBlock{resolveBlock, resolveLibBlock})
74
75 dlsymFn := c.getOrDeclareDlsym()
76 symStr := c.createCString(symName)
77 resolvedFptr := irbuilder.CreateCall(dlsymFn.GlobalValueType(), dlsymFn, []llvm.Value{libHandlePhi, symStr}, "sym")
78 irbuilder.CreateStore(resolvedFptr, fptrGlobal)
79 irbuilder.CreateBr(callBlock)
80
81 // call: invoke through function pointer
82 irbuilder.SetInsertPointAtEnd(callBlock)
83 fptrPhi := irbuilder.CreatePHI(c.dataPtrType, "fn.phi")
84 fptrPhi.AddIncoming([]llvm.Value{fptr, resolvedFptr}, []llvm.BasicBlock{entry, resolveSym})
85
86 // Collect parameters (skip the Go context param if present).
87 var params []llvm.Value
88 paramCount := llvmFn.ParamsCount()
89 for i := 0; i < paramCount; i++ {
90 params = append(params, llvmFn.Param(i))
91 }
92
93 // Build the C function type (no context param).
94 var cParamTypes []llvm.Type
95 for _, p := range params {
96 cParamTypes = append(cParamTypes, p.Type())
97 }
98 cFnType := llvm.FunctionType(fnType.ReturnType(), cParamTypes, false)
99
100 result := irbuilder.CreateCall(cFnType, fptrPhi, params, "")
101 if fnType.ReturnType().TypeKind() == llvm.VoidTypeKind {
102 irbuilder.CreateRetVoid()
103 } else {
104 irbuilder.CreateRet(result)
105 }
106 }
107
108 func (c *compilerContext) getOrCreateFFILibHandle(pkgPath, libPath string) llvm.Value {
109 name := "_mxh_lib_" + pkgPath
110 g := c.mod.NamedGlobal(name)
111 if !g.IsNil() {
112 return g
113 }
114 g = llvm.AddGlobal(c.mod, c.dataPtrType, name)
115 g.SetInitializer(llvm.ConstNull(c.dataPtrType))
116 g.SetLinkage(llvm.InternalLinkage)
117 return g
118 }
119
120 func (c *compilerContext) getOrDeclareDlopen() llvm.Value {
121 fn := c.mod.NamedFunction("dlopen")
122 if !fn.IsNil() {
123 return fn
124 }
125 fnType := llvm.FunctionType(c.dataPtrType, []llvm.Type{c.dataPtrType, c.ctx.Int32Type()}, false)
126 return llvm.AddFunction(c.mod, "dlopen", fnType)
127 }
128
129 func (c *compilerContext) getOrDeclareDlsym() llvm.Value {
130 fn := c.mod.NamedFunction("dlsym")
131 if !fn.IsNil() {
132 return fn
133 }
134 fnType := llvm.FunctionType(c.dataPtrType, []llvm.Type{c.dataPtrType, c.dataPtrType}, false)
135 return llvm.AddFunction(c.mod, "dlsym", fnType)
136 }
137
138 func (c *compilerContext) createCString(s string) llvm.Value {
139 data := s + "\x00"
140 globalType := llvm.ArrayType(c.ctx.Int8Type(), len(data))
141 global := llvm.AddGlobal(c.mod, globalType, ".ffi.str")
142 global.SetInitializer(c.ctx.ConstString(data, false))
143 global.SetLinkage(llvm.InternalLinkage)
144 global.SetGlobalConstant(true)
145 global.SetUnnamedAddr(true)
146 global.SetAlignment(1)
147 zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
148 return llvm.ConstInBoundsGEP(globalType, global, []llvm.Value{zero, zero})
149 }
150
151