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