diff --git a/_demo/cgobasic/cgobasic.go b/_demo/cgobasic/cgobasic.go new file mode 100644 index 00000000..576668a2 --- /dev/null +++ b/_demo/cgobasic/cgobasic.go @@ -0,0 +1,55 @@ +package main + +/* +#include +#include +#include +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +func main() { + // C.CString example + cstr := C.CString("Hello, World!") + C.puts(cstr) + + // C.CBytes example + bytes := []byte{65, 66, 67, 68} // ABCD + cbytes := C.CBytes(bytes) + + // C.GoString example + gostr := C.GoString(cstr) + println("Converted back to Go string: ", gostr) + + // C.GoStringN example (with length limit) + gostringN := C.GoStringN(cstr, 5) // only take first 5 characters + println("Length-limited string: ", gostringN) + + // C.GoBytes example + gobytes := C.GoBytes(cbytes, 4) // 4 is the length + println("Converted back to Go byte slice: ", gobytes) + + // C math library examples + x := 2.0 + // Calculate square root + sqrtResult := C.sqrt(C.double(x)) + fmt.Printf("sqrt(%v) = %v\n", x, float64(sqrtResult)) + + // Calculate sine + sinResult := C.sin(C.double(x)) + fmt.Printf("sin(%v) = %v\n", x, float64(sinResult)) + + // Calculate cosine + cosResult := C.cos(C.double(x)) + fmt.Printf("cos(%v) = %v\n", x, float64(cosResult)) + + // Calculate natural logarithm + logResult := C.log(C.double(x)) + fmt.Printf("log(%v) = %v\n", x, float64(logResult)) + + C.free(unsafe.Pointer(cstr)) + C.free(cbytes) +} diff --git a/_demo/cgocfiles/cgocfiles.go b/_demo/cgocfiles/cgocfiles.go new file mode 100644 index 00000000..aaef07da --- /dev/null +++ b/_demo/cgocfiles/cgocfiles.go @@ -0,0 +1,15 @@ +package main + +/* +#include "in.h" +*/ +import "C" +import "fmt" + +func main() { + r := C.test_structs(&C.s4{a: 1}, &C.s8{a: 1, b: 2}, &C.s12{a: 1, b: 2, c: 3}, &C.s16{a: 1, b: 2, c: 3, d: 4}, &C.s20{a: 1, b: 2, c: 3, d: 4, e: 5}) + fmt.Println(r) + if r != 35 { + panic("test_structs failed") + } +} diff --git a/_demo/cgocfiles/in.c b/_demo/cgocfiles/in.c new file mode 100644 index 00000000..9cb621df --- /dev/null +++ b/_demo/cgocfiles/in.c @@ -0,0 +1,12 @@ +#include +#include "in.h" + +int test_structs(s4* s4, s8* s8, s12* s12, s16* s16, s20* s20) { + printf("s4.a: %d\n", s4->a); + printf("s8.a: %d, s8.b: %d\n", s8->a, s8->b); + printf("s12.a: %d, s12.b: %d, s12.c: %d\n", s12->a, s12->b, s12->c); + printf("s16.a: %d, s16.b: %d, s16.c: %d, s16.d: %d\n", s16->a, s16->b, s16->c, s16->d); + printf("s20.a: %d, s20.b: %d, s20.c: %d, s20.d: %d, s20.e: %d\n", s20->a, s20->b, s20->c, s20->d, s20->e); + + return s4->a + s8->a + s8->b + s12->a + s12->b + s12->c + s16->a + s16->b + s16->c + s16->d + s20->a + s20->b + s20->c + s20->d + s20->e; +} diff --git a/_demo/cgocfiles/in.h b/_demo/cgocfiles/in.h new file mode 100644 index 00000000..87daf421 --- /dev/null +++ b/_demo/cgocfiles/in.h @@ -0,0 +1,33 @@ +#pragma once + +typedef struct { + int a; +} s4; + +typedef struct { + int a; + int b; +} s8; + +typedef struct { + int a; + int b; + int c; +} s12; + +typedef struct { + int a; + int b; + int c; + int d; +} s16; + +typedef struct { + int a; + int b; + int c; + int d; + int e; +} s20; + +extern int test_structs(s4* s4, s8* s8, s12* s12, s16* s16, s20* s20); diff --git a/_demo/cgofull/bar.go b/_demo/cgofull/bar.go new file mode 100644 index 00000000..08e6830f --- /dev/null +++ b/_demo/cgofull/bar.go @@ -0,0 +1,16 @@ +package main + +/* +#cgo CFLAGS: -DBAR +#include +#include "foo.h" +static void foo(Foo* f) { + printf("foo in bar: %d\n", f->a); +} +*/ +import "C" + +func Bar(f *C.Foo) { + C.print_foo(f) + C.foo(f) +} diff --git a/_demo/cgofull/cgofull.go b/_demo/cgofull/cgofull.go new file mode 100644 index 00000000..82c5e0f6 --- /dev/null +++ b/_demo/cgofull/cgofull.go @@ -0,0 +1,95 @@ +package main + +/* +#cgo windows,!amd64 CFLAGS: -D_WIN32 +#cgo !windows CFLAGS: -D_POSIX +#cgo windows,amd64 CFLAGS: -D_WIN64 +#cgo linux,amd64 CFLAGS: -D_LINUX64 +#cgo !windows,amd64 CFLAGS: -D_UNIX64 +#include +#include "foo.h" +typedef struct { + int a; +} s4; + +typedef struct { + int a; + int b; +} s8; + +typedef struct { + int a; + int b; + int c; +} s12; + +typedef struct { + int a; + int b; + int c; + int d; +} s16; + +typedef struct { + int a; + int b; + int c; + int d; + int e; +} s20; + +static int test_structs(s4* s4, s8* s8, s12* s12, s16* s16, s20* s20) { + printf("s4.a: %d\n", s4->a); + printf("s8.a: %d, s8.b: %d\n", s8->a, s8->b); + printf("s12.a: %d, s12.b: %d, s12.c: %d\n", s12->a, s12->b, s12->c); + printf("s16.a: %d, s16.b: %d, s16.c: %d, s16.d: %d\n", s16->a, s16->b, s16->c, s16->d); + printf("s20.a: %d, s20.b: %d, s20.c: %d, s20.d: %d, s20.e: %d\n", s20->a, s20->b, s20->c, s20->d, s20->e); + + return s4->a + s8->a + s8->b + s12->a + s12->b + s12->c + s16->a + s16->b + s16->c + s16->d + s20->a + s20->b + s20->c + s20->d + s20->e; +} + +static void test_macros() { +#ifdef FOO + printf("FOO is defined\n"); +#endif +#ifdef BAR + printf("BAR is defined\n"); +#endif +#ifdef _WIN32 + printf("WIN32 is defined\n"); +#endif +#ifdef _POSIX + printf("POSIX is defined\n"); +#endif +#ifdef _WIN64 + printf("WIN64 is defined\n"); +#endif +#ifdef _LINUX64 + printf("LINUX64 is defined\n"); +#endif +#ifdef _UNIX64 + printf("UNIX64 is defined\n"); +#endif +} +*/ +import "C" +import "fmt" + +func main() { + runPy() + f := &C.Foo{a: 1} + Foo(f) + Bar(f) + C.test_macros() + r := C.test_structs(&C.s4{a: 1}, &C.s8{a: 1, b: 2}, &C.s12{a: 1, b: 2, c: 3}, &C.s16{a: 1, b: 2, c: 3, d: 4}, &C.s20{a: 1, b: 2, c: 3, d: 4, e: 5}) + fmt.Println(r) + if r != 35 { + panic("test_structs failed") + } +} + +func runPy() { + Initialize() + defer Finalize() + Run("print('Hello, Python!')") +} diff --git a/_demo/cgofull/foo.c b/_demo/cgofull/foo.c new file mode 100644 index 00000000..b3f07e04 --- /dev/null +++ b/_demo/cgofull/foo.c @@ -0,0 +1,6 @@ +#include +#include "foo.h" + +void print_foo(Foo* f) { + printf("print_foo: %d\n", f->a); +} diff --git a/_demo/cgofull/foo.go b/_demo/cgofull/foo.go new file mode 100644 index 00000000..1d6aa3b4 --- /dev/null +++ b/_demo/cgofull/foo.go @@ -0,0 +1,16 @@ +package main + +/* +#cgo CFLAGS: -DFOO +#include +#include "foo.h" +static void foo(Foo* f) { + printf("foo in bar: %d\n", f->a); +} +*/ +import "C" + +func Foo(f *C.Foo) { + C.print_foo(f) + C.foo(f) +} diff --git a/_demo/cgofull/foo.h b/_demo/cgofull/foo.h new file mode 100644 index 00000000..f714fec6 --- /dev/null +++ b/_demo/cgofull/foo.h @@ -0,0 +1,7 @@ +#pragma once + +typedef struct { + int a; +} Foo; + +extern void print_foo(Foo* f); diff --git a/_demo/cgofull/py.go b/_demo/cgofull/py.go new file mode 100644 index 00000000..509d88ca --- /dev/null +++ b/_demo/cgofull/py.go @@ -0,0 +1,24 @@ +package main + +/* +#cgo pkg-config: python3-embed +#include +*/ +import "C" +import "fmt" + +func Initialize() { + C.Py_Initialize() +} + +func Finalize() { + C.Py_Finalize() +} + +func Run(code string) error { + if C.PyRun_SimpleString(C.CString(code)) != 0 { + C.PyErr_Print() + return fmt.Errorf("failed to run code") + } + return nil +} diff --git a/_demo/cgopython/cgopython.go b/_demo/cgopython/cgopython.go new file mode 100644 index 00000000..be57c5ec --- /dev/null +++ b/_demo/cgopython/cgopython.go @@ -0,0 +1,13 @@ +package main + +/* +#cgo pkg-config: python3-embed +#include +*/ +import "C" + +func main() { + C.Py_Initialize() + defer C.Py_Finalize() + C.PyRun_SimpleString(C.CString("print('Hello, Python!')")) +} diff --git a/cl/_testrt/strlen/out.ll b/cl/_testrt/strlen/out.ll index 917e6bd9..bd201f26 100644 --- a/cl/_testrt/strlen/out.ll +++ b/cl/_testrt/strlen/out.ll @@ -1,6 +1,7 @@ ; ModuleID = 'main' source_filename = "main" +@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1 @main.format = global [10 x i8] zeroinitializer, align 1 @"main.init$guard" = global i1 false, align 1 @__llgo_argc = global i32 0, align 4 diff --git a/cl/_testrt/struct/out.ll b/cl/_testrt/struct/out.ll index b1cb9d64..bdf1e248 100644 --- a/cl/_testrt/struct/out.ll +++ b/cl/_testrt/struct/out.ll @@ -3,6 +3,7 @@ source_filename = "main" %main.Foo = type { i32, i1 } +@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1 @main.format = global [10 x i8] zeroinitializer, align 1 @"main.init$guard" = global i1 false, align 1 @__llgo_argc = global i32 0, align 4 diff --git a/cl/_testrt/typalias/out.ll b/cl/_testrt/typalias/out.ll index 778f876d..beb826c5 100644 --- a/cl/_testrt/typalias/out.ll +++ b/cl/_testrt/typalias/out.ll @@ -1,6 +1,7 @@ ; ModuleID = 'main' source_filename = "main" +@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1 @main.format = global [10 x i8] zeroinitializer, align 1 @"main.init$guard" = global i1 false, align 1 @__llgo_argc = global i32 0, align 4 diff --git a/cl/compile.go b/cl/compile.go index 7e25497c..28a92470 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -114,6 +114,11 @@ type context struct { state pkgState inCFunc bool skipall bool + + cgoCalled bool + cgoArgs []llssa.Expr + cgoRet llssa.Expr + cgoFuncs map[string][]string } type pkgState byte @@ -163,7 +168,7 @@ func (p *context) compileMethods(pkg llssa.Package, typ types.Type) { func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { typ := globalType(gbl) name, vtype, define := p.varName(gbl.Pkg.Pkg, gbl) - if vtype == pyVar || ignoreName(name) || checkCgo(gbl.Name()) { + if vtype == pyVar || ignoreName(name) { return } if debugInstr { @@ -189,6 +194,10 @@ var ( argvTy = types.NewPointer(types.NewPointer(types.Typ[types.Int8])) ) +func isCgoCfunc(f *ssa.Function) bool { + return strings.HasPrefix(f.Name(), "_Cfunc_") +} + func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyObjRef, int) { pkgTypes, name, ftype := p.funcName(f, true) if ftype != goFunc { @@ -242,9 +251,14 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun fn.Inline(llssa.NoInline) } } - if nblk := len(f.Blocks); nblk > 0 { - fn.MakeBlocks(nblk) // to set fn.HasBody() = true + p.cgoCalled = false + p.cgoArgs = nil + if isCgoCfunc(f) { + fn.MakeBlocks(1) + } else { + fn.MakeBlocks(nblk) // to set fn.HasBody() = true + } if f.Recover != nil { // set recover block fn.SetRecover(fn.Block(f.Recover.Index)) } @@ -269,8 +283,15 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) - for i, block := range f.Blocks { - off[i] = p.compilePhis(b, block) + if isCgoCfunc(f) { + p.cgoArgs = make([]llssa.Expr, len(f.Params)) + for i, param := range f.Params { + p.cgoArgs[i] = p.compileValue(b, param) + } + } else { + for i, block := range f.Blocks { + off[i] = p.compilePhis(b, block) + } } p.blkInfos = blocks.Infos(f.Blocks) i := 0 @@ -279,6 +300,10 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun doMainInit := (i == 0 && name == "main") doModInit := (i == 1 && isInit) p.compileBlock(b, block, off[i], doMainInit, doModInit) + if isCgoCfunc(f) { + // just process first block for performance + break + } if i = p.blkInfos[i].Next; i < 0 { break } @@ -381,14 +406,69 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do callRuntimeInit(b, pkg) b.Call(pkg.FuncOf("main.init").Expr) } + fname := p.goProg.Fset.Position(block.Parent().Pos()).Filename + if p.cgoFuncs == nil { + p.cgoFuncs = make(map[string][]string) + } + var cgoFuncs []string + if funcs, ok := p.cgoFuncs[fname]; ok { + cgoFuncs = funcs + } + cgoReturned := false for i, instr := range instrs { if i == 1 && doModInit && p.state == pkgInPatch { // in patch package but no pkgFNoOldInit initFnNameOld := initFnNameOfHasPatch(p.fn.Name()) fnOld := pkg.NewFunc(initFnNameOld, llssa.NoArgsNoRet, llssa.InC) b.Call(fnOld.Expr) } - p.compileInstr(b, instr) + if isCgoCfunc(block.Parent()) { + switch instr := instr.(type) { + case *ssa.Alloc: + // return value allocation + p.compileInstr(b, instr) + case *ssa.UnOp: + // load cgo function pointer + if instr.Op == token.MUL && strings.HasPrefix(instr.X.Name(), "_cgo_") { + cgoFuncs = append(cgoFuncs, instr.X.Name()) + p.cgoFuncs[fname] = cgoFuncs + p.compileInstr(b, instr) + } + case *ssa.Call: + // call c function + p.compileInstr(b, instr) + p.cgoCalled = true + case *ssa.Return: + // return cgo function result + if len(instr.Results) > 0 { + b.Return(p.cgoRet) + } else { + b.Return(llssa.Nil) + } + cgoReturned = true + } + } else { + p.compileInstr(b, instr) + } } + // is cgo cfunc but not return yet, some funcs has multiple blocks + if isCgoCfunc(block.Parent()) && !cgoReturned { + if !p.cgoCalled { + panic("cgo cfunc not called") + } + for _, block := range block.Parent().Blocks { + for _, instr := range block.Instrs { + if instr, ok := instr.(*ssa.Return); ok { + if len(instr.Results) > 0 { + b.Return(p.cgoRet) + } else { + b.Return(llssa.Nil) + } + goto end + } + } + } + } +end: if pyModInit { jump := block.Instrs[n+last].(*ssa.Jump) jumpTo := p.jumpTo(jump) @@ -892,11 +972,12 @@ type Patches = map[string]Patch // NewPackage compiles a Go package to LLVM IR package. func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) { - return NewPackageEx(prog, nil, pkg, files) + ret, _, err = NewPackageEx(prog, nil, pkg, files) + return } // NewPackageEx compiles a Go package to LLVM IR package. -func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) { +func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs map[string][]string, err error) { pkgProg := pkg.Prog pkgTypes := pkg.Pkg oldTypes := pkgTypes @@ -960,6 +1041,7 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [ ctx.initAfter = nil fn() } + externs = ctx.cgoFuncs return } diff --git a/cl/import.go b/cl/import.go index 58ae08a8..fd241849 100644 --- a/cl/import.go +++ b/cl/import.go @@ -412,6 +412,17 @@ const ( llgoAtomicUMax = llgoAtomicOpBase + llssa.OpUMax llgoAtomicUMin = llgoAtomicOpBase + llssa.OpUMin + llgoCgoBase = llgoInstrBase + 0x30 + llgoCgoCString = llgoCgoBase + 0x0 + llgoCgoCBytes = llgoCgoBase + 0x1 + llgoCgoGoString = llgoCgoBase + 0x2 + llgoCgoGoStringN = llgoCgoBase + 0x3 + llgoCgoGoBytes = llgoCgoBase + 0x4 + llgoCgoCMalloc = llgoCgoBase + 0x5 + llgoCgoCheckPointer = llgoCgoBase + 0x6 + llgoCgoCgocall = llgoCgoBase + 0x7 + llgoCgoUse = llgoCgoBase + 0x8 + llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin) ) @@ -423,6 +434,15 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin p.ensureLoaded(pkg) orgName = funcName(pkg, origin, true) } else { + fname := fn.Name() + if checkCgo(fname) { + return nil, fname, llgoInstr + } + if isCgoCfunc(fn) { + if _, ok := llgoInstrs[fname]; ok { + return nil, fname, llgoInstr + } + } if fnPkg := fn.Pkg; fnPkg != nil { pkg = fnPkg.Pkg } else { diff --git a/cl/instr.go b/cl/instr.go index ff1c30c1..6ae03ea7 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -66,6 +66,77 @@ func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { panic("cstr(): invalid arguments") } +// ----------------------------------------------------------------------------- + +// func _Cfunc_CString(s string) *int8 +func (p *context) cgoCString(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 1 { + return b.CString(p.compileValue(b, args[0])) + } + panic("cgoCString(string): invalid arguments") +} + +// func _Cfunc_CBytes(bytes []byte) *int8 +func (p *context) cgoCBytes(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 1 { + return b.CBytes(p.compileValue(b, args[0])) + } + panic("cgoCBytes([]byte): invalid arguments") +} + +// func _Cfunc_GoString(s *int8) string +func (p *context) cgoGoString(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 1 { + return b.GoString(p.compileValue(b, args[0])) + } + panic("cgoGoString(): invalid arguments") +} + +// func _Cfunc_GoStringN(s *int8, n int) string +func (p *context) cgoGoStringN(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 2 { + return b.GoStringN(p.compileValue(b, args[0]), p.compileValue(b, args[1])) + } + panic("cgoGoStringN(, n int): invalid arguments") +} + +// func _Cfunc_GoBytes(s *int8, n int) []byte +func (p *context) cgoGoBytes(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 2 { + return b.GoBytes(p.compileValue(b, args[0]), p.compileValue(b, args[1])) + } + panic("cgoGoBytes(, n int): invalid arguments") +} + +// func _Cfunc__CMalloc(n int) unsafe.Pointer +func (p *context) cgoCMalloc(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 1 { + return b.CMalloc(p.compileValue(b, args[0])) + } + panic("cgoCMalloc(n int): invalid arguments") +} + +// func _cgoCheckPointer(ptr any, arg any) +func (p *context) cgoCheckPointer(b llssa.Builder, args []ssa.Value) { + // don't need to do anything +} + +// func _cgo_runtime_cgocall(fn unsafe.Pointer, arg unsafe.Pointer) int +func (p *context) cgoCgocall(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + pfn := p.compileValue(b, args[0]) + pfn.Type = p.prog.Pointer(p.fn.Type) + fn := b.Load(pfn) + p.cgoRet = b.Call(fn, p.cgoArgs...) + return p.cgoRet +} + +// func _Cgo_use(v any) +func (p *context) cgoUse(b llssa.Builder, args []ssa.Value) { + // don't need to do anything +} + +// ----------------------------------------------------------------------------- + // func index(arr *T, idx int) T func (p *context) index(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { return b.Load(p.advance(b, args)) @@ -244,6 +315,16 @@ var llgoInstrs = map[string]int{ "atomicMin": int(llgoAtomicMin), "atomicUMax": int(llgoAtomicUMax), "atomicUMin": int(llgoAtomicUMin), + + "_Cfunc_CString": llgoCgoCString, + "_Cfunc_CBytes": llgoCgoCBytes, + "_Cfunc_GoString": llgoCgoGoString, + "_Cfunc_GoStringN": llgoCgoGoStringN, + "_Cfunc_GoBytes": llgoCgoGoBytes, + "_Cfunc__CMalloc": llgoCgoCMalloc, + "_cgoCheckPointer": llgoCgoCheckPointer, + "_cgo_runtime_cgocall": llgoCgoCgocall, + "_Cgo_use": llgoCgoUse, } // funcOf returns a function by name and set ftype = goFunc, cFunc, etc. @@ -371,6 +452,24 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon ret = pystr(b, args) case llgoCstr: ret = cstr(b, args) + case llgoCgoCString: + ret = p.cgoCString(b, args) + case llgoCgoCBytes: + ret = p.cgoCBytes(b, args) + case llgoCgoGoString: + ret = p.cgoGoString(b, args) + case llgoCgoGoStringN: + ret = p.cgoGoStringN(b, args) + case llgoCgoGoBytes: + ret = p.cgoGoBytes(b, args) + case llgoCgoCMalloc: + ret = p.cgoCMalloc(b, args) + case llgoCgoCheckPointer: + p.cgoCheckPointer(b, args) + case llgoCgoCgocall: + p.cgoCgocall(b, args) + case llgoCgoUse: + p.cgoUse(b, args) case llgoAdvance: ret = p.advance(b, args) case llgoIndex: @@ -407,7 +506,7 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon if ftype >= llgoAtomicOpBase && ftype <= llgoAtomicOpLast { ret = p.atomic(b, llssa.AtomicOp(ftype-llgoAtomicOpBase), args) } else { - log.Panicln("unknown ftype:", ftype) + log.Panicf("unknown ftype: %d for %s", ftype, cv.Name()) } } default: diff --git a/internal/build/build.go b/internal/build/build.go index 8a81727f..ca7cd220 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -201,7 +201,7 @@ func Do(args []string, conf *Config) { env := llvm.New("") os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows - ctx := &context{env, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0} + ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0} pkgs := buildAllPkgs(ctx, initial, verbose) var llFiles []string @@ -249,6 +249,7 @@ const ( type context struct { env *llvm.Env + conf *packages.Config progSSA *ssa.Program prog llssa.Program dedup packages.Deduper @@ -286,8 +287,14 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs pkg.ExportFile = "" case cl.PkgLinkIR, cl.PkgLinkExtern, cl.PkgPyModule: if len(pkg.GoFiles) > 0 { - buildPkg(ctx, aPkg, verbose) - pkg.ExportFile = " " + concatPkgLinkFiles(ctx, pkg, verbose) + " " + pkg.ExportFile + cgoParts, err := buildPkg(ctx, aPkg, verbose) + if err != nil { + panic(err) + } + linkParts := concatPkgLinkFiles(ctx, pkg, verbose) + allParts := append(linkParts, cgoParts...) + allParts = append(allParts, pkg.ExportFile) + pkg.ExportFile = " " + strings.Join(allParts, " ") } else { // panic("todo") // TODO(xsw): support packages out of llgo @@ -337,7 +344,12 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs } } default: - buildPkg(ctx, aPkg, verbose) + cgoParts, err := buildPkg(ctx, aPkg, verbose) + if err != nil { + panic(err) + } + allParts := append(cgoParts, pkg.ExportFile) + pkg.ExportFile = " " + strings.Join(allParts, " ") setNeedRuntimeOrPyInit(pkg, prog.NeedRuntime, prog.NeedPyInit) } } @@ -483,7 +495,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, llFiles return } -func buildPkg(ctx *context, aPkg *aPackage, verbose bool) { +func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoParts []string, err error) { pkg := aPkg.Package pkgPath := pkg.PkgPath if debugBuild || verbose { @@ -503,12 +515,13 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) { cl.SetDebug(cl.DbgFlagAll) } - ret, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax) + ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax) if showDetail { llssa.SetDebug(0) cl.SetDebug(0) } check(err) + cgoParts, err = buildCgo(ctx, aPkg, aPkg.Package.Syntax, externs, verbose) if needLLFile(ctx.mode) { pkg.ExportFile += ".ll" os.WriteFile(pkg.ExportFile, []byte(ret.String()), 0644) @@ -517,6 +530,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) { } } aPkg.LPkg = ret + return } const ( @@ -690,25 +704,11 @@ func isSingleLinkFile(ret string) bool { return len(ret) > 0 && ret[0] != ' ' } -func concatPkgLinkFiles(ctx *context, pkg *packages.Package, verbose bool) string { - var b strings.Builder - var ret string - var n int +func concatPkgLinkFiles(ctx *context, pkg *packages.Package, verbose bool) (parts []string) { llgoPkgLinkFiles(ctx, pkg, func(linkFile string) { - if n == 0 { - ret = linkFile - } else { - b.WriteByte(' ') - b.WriteString(linkFile) - } - n++ + parts = append(parts, linkFile) }, verbose) - if n > 1 { - b.WriteByte(' ') - b.WriteString(ret) - return b.String() - } - return ret + return } // const LLGoFiles = "file1; file2; ..." diff --git a/internal/build/cgo.go b/internal/build/cgo.go new file mode 100644 index 00000000..02d72ed6 --- /dev/null +++ b/internal/build/cgo.go @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package build + +import ( + "encoding/json" + "fmt" + "go/ast" + "go/token" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + + "github.com/goplus/llgo/internal/buildtags" +) + +type cgoDecl struct { + tag string + cflags string + ldflags string +} + +type cgoPreamble struct { + goFile string + src string +} + +const ( + cgoHeader = ` +#include +static void* _Cmalloc(size_t size) { + return malloc(size); +} +` +) + +func buildCgo(ctx *context, pkg *aPackage, files []*ast.File, externs map[string][]string, verbose bool) (cgoParts []string, err error) { + cfiles, preambles, cdecls, err := parseCgo_(pkg, files) + if err != nil { + return + } + tagUsed := make(map[string]bool) + for _, cdecl := range cdecls { + if cdecl.tag != "" { + tagUsed[cdecl.tag] = false + } + } + buildtags.CheckTags(ctx.conf.BuildFlags, tagUsed) + cflags := []string{} + ldflags := []string{} + for _, cdecl := range cdecls { + if cdecl.tag == "" || tagUsed[cdecl.tag] { + if cdecl.cflags != "" { + cflags = append(cflags, cdecl.cflags) + } + if cdecl.ldflags != "" { + ldflags = append(ldflags, cdecl.ldflags) + } + } + } + incDirs := make(map[string]none) + for _, preamble := range preambles { + dir, _ := filepath.Split(preamble.goFile) + if _, ok := incDirs[dir]; !ok { + incDirs[dir] = none{} + cflags = append(cflags, "-I"+dir) + } + } + for _, cfile := range cfiles { + clFile(ctx, cflags, cfile, pkg.ExportFile, func(linkFile string) { + cgoParts = append(cgoParts, linkFile) + }, verbose) + } + re := regexp.MustCompile(`^(_cgo_[^_]+_Cfunc_)(.*)$`) + cgoFuncs := make(map[string]string) + mallocFix := false + for _, funcs := range externs { + for _, funcName := range funcs { + if m := re.FindStringSubmatch(funcName); len(m) > 0 { + cgoFuncs[funcName] = m[2] + // fix missing _cgo_9113e32b6599_Cfunc__Cmalloc + if !mallocFix { + pkgPrefix := m[1] + mallocName := pkgPrefix + "_Cmalloc" + cgoFuncs[mallocName] = "_Cmalloc" + mallocFix = true + } + } + } + } + for _, preamble := range preambles { + tmpFile, err := os.CreateTemp("", "-cgo-*.c") + if err != nil { + return nil, fmt.Errorf("failed to create temp file: %v", err) + } + tmpName := tmpFile.Name() + defer os.Remove(tmpName) + code := cgoHeader + "\n\n" + preamble.src + externDecls := genExternDeclsByClang(code, cflags, cgoFuncs) + if err = os.WriteFile(tmpName, []byte(code+"\n\n"+externDecls), 0644); err != nil { + return nil, err + } + clFile(ctx, cflags, tmpName, pkg.ExportFile, func(linkFile string) { + cgoParts = append(cgoParts, linkFile) + }, verbose) + } + cgoParts = append(cgoParts, ldflags...) + return +} + +// clangASTNode represents a node in clang's AST +type clangASTNode struct { + Kind string `json:"kind"` + Name string `json:"name,omitempty"` + Inner []clangASTNode `json:"inner,omitempty"` +} + +func genExternDeclsByClang(src string, cflags []string, cgoFuncs map[string]string) string { + tmpSrc, err := os.CreateTemp("", "cgo-src-*.c") + if err != nil { + return "" + } + defer os.Remove(tmpSrc.Name()) + if err := os.WriteFile(tmpSrc.Name(), []byte(src), 0644); err != nil { + return "" + } + args := append([]string{"-Xclang", "-ast-dump=json", "-fsyntax-only"}, cflags...) + args = append(args, tmpSrc.Name()) + cmd := exec.Command("clang", args...) + output, err := cmd.Output() + if err != nil { + return "" + } + var astRoot clangASTNode + if err := json.Unmarshal(output, &astRoot); err != nil { + return "" + } + + funcNames := make(map[string]bool) + extractFuncNames(&astRoot, funcNames) + + b := strings.Builder{} + var toRemove []string + for cgoFunc, funcName := range cgoFuncs { + if funcNames[funcName] { + b.WriteString(fmt.Sprintf("void* %s = (void*)%s;\n", cgoFunc, funcName)) + toRemove = append(toRemove, cgoFunc) + } + } + for _, funcName := range toRemove { + delete(cgoFuncs, funcName) + } + return b.String() +} + +func extractFuncNames(node *clangASTNode, funcNames map[string]bool) { + for _, inner := range node.Inner { + if inner.Kind == "FunctionDecl" && inner.Name != "" { + funcNames[inner.Name] = true + } + } +} + +func parseCgo_(pkg *aPackage, files []*ast.File) (cfiles []string, preambles []cgoPreamble, cdecls []cgoDecl, err error) { + dirs := make(map[string]none) + for _, file := range files { + pos := pkg.Fset.Position(file.Name.NamePos) + dir, _ := filepath.Split(pos.Filename) + dirs[dir] = none{} + } + for dir := range dirs { + files, err := filepath.Glob(filepath.Join(dir, "*.c")) + if err != nil { + continue + } + cfiles = append(cfiles, files...) + } + + for _, file := range files { + for _, decl := range file.Decls { + switch decl := decl.(type) { + case *ast.GenDecl: + if decl.Tok == token.IMPORT { + if doc := decl.Doc; doc != nil && len(decl.Specs) == 1 { + spec := decl.Specs[0].(*ast.ImportSpec) + if spec.Path.Value == "\"unsafe\"" { + pos := pkg.Fset.Position(doc.Pos()) + preamble, flags, err := parseCgoPreamble(pos, doc.Text()) + if err != nil { + panic(err) + } + preambles = append(preambles, preamble) + cdecls = append(cdecls, flags...) + } + } + } + } + } + } + return +} + +func parseCgoPreamble(pos token.Position, text string) (preamble cgoPreamble, decls []cgoDecl, err error) { + b := strings.Builder{} + fline := pos.Line + fname := pos.Filename + b.WriteString(fmt.Sprintf("#line %d %q\n", fline, fname)) + + for _, line := range strings.Split(text, "\n") { + fline++ + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "#cgo ") { + var cgoDecls []cgoDecl + cgoDecls, err = parseCgoDecl(line) + if err != nil { + return + } + decls = append(decls, cgoDecls...) + b.WriteString(fmt.Sprintf("#line %d %q\n", fline, fname)) + } else { + b.WriteString(line) + b.WriteString("\n") + } + } + preamble = cgoPreamble{ + goFile: pos.Filename, + src: b.String(), + } + return +} + +// Parse cgo directive like: +// #cgo pkg-config: python3 +// #cgo windows CFLAGS: -IC:/Python312/include +// #cgo windows LDFLAGS: -LC:/Python312/libs -lpython312 +// #cgo CFLAGS: -I/usr/include/python3.12 +// #cgo LDFLAGS: -L/usr/lib/python3.12/config-3.12-x86_64-linux-gnu -lpython3.12 +func parseCgoDecl(line string) (cgoDecls []cgoDecl, err error) { + idx := strings.Index(line, ":") + if idx == -1 { + err = fmt.Errorf("invalid cgo format: %v", line) + return + } + + decl := strings.TrimSpace(line[:idx]) + arg := strings.TrimSpace(line[idx+1:]) + + // Split on first space to remove #cgo + parts := strings.SplitN(decl, " ", 2) + if len(parts) < 2 { + err = fmt.Errorf("invalid cgo directive: %v", line) + return + } + + // Process remaining part + remaining := strings.TrimSpace(parts[1]) + var tag, flag string + + // Split on last space to get flag + if lastSpace := strings.LastIndex(remaining, " "); lastSpace != -1 { + tag = strings.TrimSpace(remaining[:lastSpace]) + flag = strings.TrimSpace(remaining[lastSpace+1:]) + } else { + flag = remaining + } + + switch flag { + case "pkg-config": + ldflags, e := exec.Command("pkg-config", "--libs", arg).Output() + if e != nil { + err = fmt.Errorf("pkg-config: %v", e) + return + } + cflags, e := exec.Command("pkg-config", "--cflags", arg).Output() + if e != nil { + err = fmt.Errorf("pkg-config: %v", e) + return + } + cgoDecls = append(cgoDecls, cgoDecl{ + tag: tag, + cflags: strings.TrimSpace(string(cflags)), + ldflags: strings.TrimSpace(string(ldflags)), + }) + case "CFLAGS": + cgoDecls = append(cgoDecls, cgoDecl{ + tag: tag, + cflags: arg, + }) + case "LDFLAGS": + cgoDecls = append(cgoDecls, cgoDecl{ + tag: tag, + ldflags: arg, + }) + } + return +} diff --git a/internal/buildtags/buildtags.go b/internal/buildtags/buildtags.go new file mode 100644 index 00000000..8688e572 --- /dev/null +++ b/internal/buildtags/buildtags.go @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package buildtags + +import ( + "fmt" + "go/build" + "io" + "io/fs" + "strings" +) + +// checkTags checks which build tags are valid by creating virtual test files +// and using build.Context.MatchFile to verify them +func CheckTags(buildFlags []string, testTags map[string]bool) { + buildCtx := build.Default + buildCtx.BuildTags = parseBuildTags(buildFlags) + + // Create virtual filesystem + vfs := &virtualFS{ + files: make(map[string]virtualFile), + } + + // Generate virtual files for each test tag + i := 0 + fileToTag := make(map[string]string) // Map to track which file corresponds to which tag + for tag := range testTags { + fileName := fmt.Sprintf("a%02d.go", i) + content := fmt.Sprintf("// +build %s\n\npackage check\n", tag) + vfs.files[fileName] = virtualFile{ + name: fileName, + content: content, + dir: ".", + } + fileToTag[fileName] = tag + i++ + } + + // Override OpenFile to return our virtual file contents + buildCtx.OpenFile = func(name string) (io.ReadCloser, error) { + if file, ok := vfs.files[name]; ok { + return io.NopCloser(strings.NewReader(file.content)), nil + } + return nil, fs.ErrNotExist + } + + // Check each file against build context + for fileName, tag := range fileToTag { + match, err := buildCtx.MatchFile(".", fileName) + if err == nil && match { + testTags[tag] = true + } + } +} + +// virtualFile represents a virtual build tag check file +type virtualFile struct { + name string + content string + dir string +} + +// virtualFS implements a virtual filesystem for build tag checking +type virtualFS struct { + files map[string]virtualFile +} + +func parseBuildTags(buildFlags []string) []string { + buildTags := make([]string, 0) + // Extract tags from buildFlags + for i := 0; i < len(buildFlags); i++ { + flag := buildFlags[i] + if flag == "-tags" && i+1 < len(buildFlags) { + // Handle "-tags xxx" format + tags := strings.FieldsFunc(buildFlags[i+1], func(r rune) bool { + return r == ',' || r == ' ' + }) + buildTags = append(buildTags, tags...) + i++ // Skip the next item since we've processed it + } else if strings.HasPrefix(flag, "-tags=") { + // Handle "-tags=xxx" format + value := strings.TrimPrefix(flag, "-tags=") + tags := strings.FieldsFunc(value, func(r rune) bool { + return r == ',' || r == ' ' + }) + buildTags = append(buildTags, tags...) + } + } + + // Remove duplicates from tags + seen := make(map[string]bool) + uniqueBuildTags := make([]string, 0, len(buildTags)) + for _, tag := range buildTags { + if !seen[tag] { + seen[tag] = true + uniqueBuildTags = append(uniqueBuildTags, tag) + } + } + return uniqueBuildTags +} diff --git a/internal/buildtags/buildtags_test.go b/internal/buildtags/buildtags_test.go new file mode 100644 index 00000000..70d7c37d --- /dev/null +++ b/internal/buildtags/buildtags_test.go @@ -0,0 +1,153 @@ +package buildtags + +import ( + "reflect" + "runtime" + "testing" +) + +func TestCheckTags(t *testing.T) { + tests := []struct { + name string + buildFlags []string + testTags map[string]bool + want map[string]bool + }{ + { + name: "mywindows tags", + buildFlags: []string{"-tags", "mywindows"}, + testTags: map[string]bool{ + "mywindows": false, + "!mywindows": false, + "mywindows,myamd64": false, + }, + want: map[string]bool{ + "mywindows": true, + "!mywindows": false, + "mywindows,myamd64": runtime.GOARCH == "myamd64", + }, + }, + { + name: "non-mywindows tags", + buildFlags: []string{"-tags", "mylinux"}, + testTags: map[string]bool{ + "mywindows": false, + "!mywindows": false, + "mylinux,myamd64": false, + "!mywindows,myamd64": false, + }, + want: map[string]bool{ + "mywindows": false, + "!mywindows": true, + "mylinux,myamd64": runtime.GOARCH == "myamd64", + "!mywindows,myamd64": runtime.GOARCH == "myamd64", + }, + }, + { + name: "multiple tags", + buildFlags: []string{"-tags", "mywindows,myamd64"}, + testTags: map[string]bool{ + "mywindows": false, + "myamd64": false, + "mywindows,myamd64": false, + "mylinux,myamd64": false, + }, + want: map[string]bool{ + "mywindows": true, + "myamd64": true, + "mywindows,myamd64": true, + "mylinux,myamd64": false, + }, + }, + { + name: "tags with equals format", + buildFlags: []string{"-tags=mywindows,myamd64"}, + testTags: map[string]bool{ + "mywindows": false, + "myamd64": false, + "mywindows,myamd64": false, + }, + want: map[string]bool{ + "mywindows": true, + "myamd64": true, + "mywindows,myamd64": true, + }, + }, + { + name: "complex tag combinations", + buildFlags: []string{"-tags", "mylinux,myamd64"}, + testTags: map[string]bool{ + "mywindows": false, + "!mywindows": false, + "mylinux": false, + "mylinux,myamd64": false, + "!mywindows,myamd64": false, + }, + want: map[string]bool{ + "mywindows": false, + "!mywindows": true, + "mylinux": true, + "mylinux,myamd64": true, + "!mywindows,myamd64": true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testTags := make(map[string]bool) + for k := range tt.testTags { + testTags[k] = false + } + + CheckTags(tt.buildFlags, testTags) + + if !reflect.DeepEqual(testTags, tt.want) { + t.Errorf("CheckTags() = %v, want %v", testTags, tt.want) + } + }) + } +} + +func TestParseBuildTags(t *testing.T) { + tests := []struct { + name string + buildFlags []string + want []string + }{ + { + name: "space separated tags", + buildFlags: []string{"-tags", "mywindows myamd64"}, + want: []string{"mywindows", "myamd64"}, + }, + { + name: "equals format", + buildFlags: []string{"-tags=mywindows,myamd64"}, + want: []string{"mywindows", "myamd64"}, + }, + { + name: "multiple -tags flags", + buildFlags: []string{"-tags", "mywindows", "-tags", "myamd64"}, + want: []string{"mywindows", "myamd64"}, + }, + { + name: "duplicate tags", + buildFlags: []string{"-tags", "mywindows myamd64", "-tags=mywindows"}, + want: []string{"mywindows", "myamd64"}, + }, + { + name: "empty tags", + buildFlags: []string{}, + want: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parseBuildTags(tt.buildFlags) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("name: %v, parseBuildTags() = %v, want %v", tt.name, got, tt.want) + } + }) + } +} diff --git a/internal/runtime/z_cgo.go b/internal/runtime/z_cgo.go new file mode 100644 index 00000000..81440a93 --- /dev/null +++ b/internal/runtime/z_cgo.go @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +func CString(s string) *int8 { + p := c.Malloc(uintptr(len(s)) + 1) + return CStrCopy(p, *(*String)(unsafe.Pointer(&s))) +} + +func CBytes(b []byte) *int8 { + p := c.Malloc(uintptr(len(b))) + c.Memcpy(p, unsafe.Pointer(&b[0]), uintptr(len(b))) + return (*int8)(p) +} + +func GoString(p *int8) string { + return GoStringN(p, int(c.Strlen(p))) +} + +func GoStringN(p *int8, n int) string { + return string((*[1 << 30]byte)(unsafe.Pointer(p))[:n:n]) +} + +func GoBytes(p *int8, n int) []byte { + return (*[1 << 30]byte)(unsafe.Pointer(p))[:n:n] +} diff --git a/ssa/expr.go b/ssa/expr.go index 9dda62d2..01778fdd 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -254,6 +254,41 @@ func (b Builder) CStr(v string) Expr { return Expr{llvm.CreateGlobalStringPtr(b.impl, v), b.Prog.CStr()} } +// CString returns a c-style string +func (b Builder) CString(v Expr) Expr { + fn := b.Pkg.rtFunc("CString") + return b.Call(fn, v) +} + +// CBytes returns a c-style bytes +func (b Builder) CBytes(v Expr) Expr { + fn := b.Pkg.rtFunc("CBytes") + return b.Call(fn, v) +} + +// GoString returns a Go string +func (b Builder) GoString(v Expr) Expr { + fn := b.Pkg.rtFunc("GoString") + return b.Call(fn, v) +} + +// GoStringN returns a Go string +func (b Builder) GoStringN(v Expr, n Expr) Expr { + fn := b.Pkg.rtFunc("GoStringN") + return b.Call(fn, v, n) +} + +// GoBytes returns a Go bytes +func (b Builder) GoBytes(v Expr, n Expr) (ret Expr) { + fn := b.Pkg.rtFunc("GoBytes") + return b.Call(fn, v, n) +} + +// CMalloc returns a c-style pointer +func (b Builder) CMalloc(n Expr) Expr { + return b.malloc(n) +} + // Str returns a Go string constant expression. func (b Builder) Str(v string) Expr { prog := b.Prog @@ -982,7 +1017,7 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { bi := raw.(*builtinTy) return b.BuiltinCall(bi.name, args...) default: - log.Panicf("unreachable: %d(%T)\n", kind, raw) + log.Panicf("unreachable: %d(%T), %v\n", kind, raw, fn.RawType()) } ret.Type = b.Prog.retType(sig) ret.impl = llvm.CreateCall(b.impl, ll, fn.impl, llvmParamsEx(data, args, sig.Params(), b))