diff --git a/cl/_testdata/cstr/in.go b/cl/_testdata/cstr/in.go new file mode 100644 index 00000000..a8285b4e --- /dev/null +++ b/cl/_testdata/cstr/in.go @@ -0,0 +1,13 @@ +package main + +import _ "unsafe" + +//go:linkname cstr llgo.cstr +func cstr(string) *int8 + +//go:linkname printf C.printf +func printf(format *int8, __llgo_va_list ...any) + +func main() { + printf(cstr("Hello, world\n")) +} diff --git a/cl/_testdata/cstr/out.ll b/cl/_testdata/cstr/out.ll new file mode 100644 index 00000000..d5bfe25a --- /dev/null +++ b/cl/_testdata/cstr/out.ll @@ -0,0 +1,26 @@ +; ModuleID = 'main' +source_filename = "main" + +@"main.init$guard" = global ptr null + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define void @main() { +_llgo_0: + call void @main.init() + call void (ptr, ...) @printf([14 x i8] c"Hello, world\0A\00") + ret void +} + +declare void @printf(ptr, ...) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index c947bdd5..bf83a119 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -25,6 +25,24 @@ import ( "golang.org/x/tools/go/ssa" ) +func TestCStrNoArgs(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("cstr: no error?") + } + }() + cstr(nil, nil) +} + +func TestCStrNonconst(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("cstr: no error?") + } + }() + cstr(nil, []ssa.Value{&ssa.Parameter{}}) +} + func TestPkgNoInit(t *testing.T) { pkg := types.NewPackage("foo", "foo") ctx := &context{ diff --git a/cl/compile.go b/cl/compile.go index ed182b14..20ba4cdd 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -189,8 +189,9 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { func (p *context) compileFunc(pkg llssa.Package, pkgTypes *types.Package, f *ssa.Function) { sig := f.Signature - name, ok := p.funcName(pkgTypes, f, true) - if !ok { // ignored + name, ftype := p.funcName(pkgTypes, f, true) + switch ftype { + case ignoredFunc, llgoInstr: // llgo extended instructions return } if debugInstr { @@ -269,6 +270,18 @@ func (p *context) checkVArgs(v *ssa.Alloc, t *types.Pointer) bool { return false } +func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 1 { + if c, ok := args[0].(*ssa.Const); ok { + if v := c.Value; v.Kind() == constant.String { + sv := constant.StringVal(v) + return b.Prog.CStringVal(sv) + } + } + } + panic("cstr(): invalid arguments") +} + func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue bool) (ret llssa.Expr) { if asValue { if v, ok := p.bvals[iv]; ok { @@ -287,8 +300,9 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue if debugGoSSA { log.Println(">>> Call", cv, call.Args) } - if builtin, ok := cv.(*ssa.Builtin); ok { - fn := builtin.Name() + switch cv := cv.(type) { + case *ssa.Builtin: + fn := cv.Name() if fn == "ssa:wrapnilchk" { // TODO(xsw): check nil ptr arg := call.Args[0] ret = p.compileValue(b, arg) @@ -297,7 +311,18 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue args := p.compileValues(b, call.Args, kind) ret = b.BuiltinCall(fn, args...) } - } else { + case *ssa.Function: + fn, ftype := p.funcOf(cv) + switch ftype { + case goFunc, cFunc: + args := p.compileValues(b, call.Args, kind) + ret = b.Call(fn.Expr, args...) + case llgoCstr: + ret = cstr(b, call.Args) + default: + panic("todo") + } + default: fn := p.compileValue(b, cv) args := p.compileValues(b, call.Args, kind) ret = b.Call(fn, args...) @@ -421,7 +446,10 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { } } case *ssa.Function: - fn := p.funcOf(v) + fn, ftype := p.funcOf(v) + if ftype >= llgoInstrBase { + panic("can't use llgo instruction as a value") + } return fn.Expr case *ssa.Global: g := p.varOf(v) diff --git a/cl/import.go b/cl/import.go index 249402a6..7cf32f7e 100644 --- a/cl/import.go +++ b/cl/import.go @@ -134,9 +134,9 @@ func (p *context) initLinkname(pkgPath, line string) { text := strings.TrimSpace(line[len(linkname):]) if idx := strings.IndexByte(text, ' '); idx > 0 { link := strings.TrimLeft(text[idx+1:], " ") - if strings.Contains(link, ".") { // eg. C.printf, C.strlen + if strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr name := pkgPath + "." + text[:idx] - p.link[name] = link[2:] + p.link[name] = link } else { panic(line + ": no specified call convention. eg. //go:linkname Printf C.printf") } @@ -171,25 +171,56 @@ func checkCgo(fnName string) bool { (fnName[4] == '_' || strings.HasPrefix(fnName[4:], "Check")) } -func (p *context) funcName(pkg *types.Package, fn *ssa.Function, ignore bool) (string, bool) { +const ( + ignoredFunc = iota + goFunc + cFunc + llgoInstr = -1 + + llgoInstrBase = 0x80 + llgoCstr = llgoInstrBase + 1 + llgoAlloca = llgoInstrBase + 2 + llgoUnreachable = llgoInstrBase + 3 +) + +func (p *context) funcName(pkg *types.Package, fn *ssa.Function, ignore bool) (string, int) { name := funcName(pkg, fn) if ignore && ignoreName(name) || checkCgo(fn.Name()) { - return name, false + return name, ignoredFunc } if v, ok := p.link[name]; ok { - return v, true + if strings.HasPrefix(v, "C.") { + return v[2:], cFunc + } + if strings.HasPrefix(v, "llgo.") { + return v[5:], llgoInstr + } + return v, goFunc } - return name, true + return name, goFunc } -func (p *context) funcOf(fn *ssa.Function) llssa.Function { +// funcOf returns a function by name and set ftype = goFunc, cFunc, etc. +// or returns nil and set ftype = llgoCstr, llgoAlloca, llgoUnreachable, etc. +func (p *context) funcOf(fn *ssa.Function) (ret llssa.Function, ftype int) { pkgTypes := p.ensureLoaded(fn.Pkg.Pkg) pkg := p.pkg - name, _ := p.funcName(pkgTypes, fn, false) - if ret := pkg.FuncOf(name); ret != nil { - return ret + name, ftype := p.funcName(pkgTypes, fn, false) + if ftype == llgoInstr { + switch name { + case "cstr": + ftype = llgoCstr + case "alloca": + ftype = llgoAlloca + case "unreachable": + ftype = llgoUnreachable + default: + panic("unknown llgo instruction: " + name) + } + } else if ret = pkg.FuncOf(name); ret == nil { + ret = pkg.NewFunc(name, fn.Signature) } - return pkg.NewFunc(name, fn.Signature) + return } func (p *context) varOf(v *ssa.Global) llssa.Global { diff --git a/internal/runtime/c/c.go b/internal/runtime/c/c.go index 451d80b0..d3914551 100644 --- a/internal/runtime/c/c.go +++ b/internal/runtime/c/c.go @@ -22,13 +22,13 @@ const ( LLGoPackage = "decl" ) -//go:linkname String llgo.CString +//go:linkname String llgo.cstr func String(string) *int8 -//go:linkname Alloca llgo.Alloca +//go:linkname Alloca llgo.alloca func Alloca(size uintptr) unsafe.Pointer -//go:linkname Unreachable llgo.Unreachable +//go:linkname Unreachable llgo.unreachable func Unreachable() //go:linkname Malloc C.malloc diff --git a/ssa/expr.go b/ssa/expr.go index 4ea32c11..777f0da8 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -132,7 +132,7 @@ func (p Program) Val(v interface{}) Expr { // Const returns a constant expression. func (b Builder) Const(v constant.Value, typ Type) Expr { - prog := b.prog + prog := b.Prog if v == nil { return prog.Null(typ) } @@ -277,7 +277,7 @@ func (b Builder) BinOp(op token.Token, x, y Expr) Expr { } return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type} case isPredOp(op): // op: == != < <= < >= - tret := b.prog.Bool() + tret := b.Prog.Bool() kind := x.kind switch kind { case vkSigned: @@ -318,7 +318,7 @@ func (b Builder) Load(ptr Expr) Expr { if debugInstr { log.Printf("Load %v\n", ptr.impl) } - telem := b.prog.Elem(ptr.Type) + telem := b.Prog.Elem(ptr.Type) return Expr{llvm.CreateLoad(b.impl, telem.ll, ptr.impl), telem} } @@ -354,7 +354,7 @@ func (b Builder) FieldAddr(x Expr, idx int) Expr { if debugInstr { log.Printf("FieldAddr %v, %d\n", x.impl, idx) } - prog := b.prog + prog := b.Prog tstruc := prog.Elem(x.Type) telem := prog.Field(tstruc, idx) pt := prog.Pointer(telem) @@ -377,7 +377,7 @@ func (b Builder) IndexAddr(x, idx Expr) Expr { if debugInstr { log.Printf("IndexAddr %v, %v\n", x.impl, idx.impl) } - prog := b.prog + prog := b.Prog telem := prog.Index(x.Type) pt := prog.Pointer(telem) indices := []llvm.Value{idx.impl} @@ -407,7 +407,7 @@ func (b Builder) Alloc(t *types.Pointer, heap bool) (ret Expr) { if debugInstr { log.Printf("Alloc %v, %v\n", t, heap) } - prog := b.prog + prog := b.Prog telem := t.Elem() if heap { pkg := b.fn.pkg @@ -452,7 +452,7 @@ func (b Builder) ChangeType(t Type, x Expr) (ret Expr) { switch typ.(type) { default: ret.impl = b.impl.CreateBitCast(x.impl, t.ll, "bitCast") - ret.Type = b.prog.Type(typ) + ret.Type = b.Prog.Type(typ) return } } @@ -488,7 +488,7 @@ func (b Builder) ChangeType(t Type, x Expr) (ret Expr) { // t1 = convert []byte <- string (t0) func (b Builder) Convert(t Type, x Expr) (ret Expr) { typ := t.t - ret.Type = b.prog.Type(typ) + ret.Type = b.Prog.Type(typ) switch und := typ.Underlying().(type) { case *types.Basic: kind := und.Kind() @@ -624,7 +624,7 @@ func (b Builder) TypeAssert(x Expr, assertedTyp Type, commaOk bool) (ret Expr) { default: panic("todo") } - typ := b.InlineCall(pkg.rtFunc("Basic"), b.prog.Val(int(kind))) + typ := b.InlineCall(pkg.rtFunc("Basic"), b.Prog.Val(int(kind))) return b.InlineCall(fn, x, typ) } panic("todo") @@ -659,7 +659,7 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { } switch t := fn.t.(type) { case *types.Signature: - ret.Type = b.prog.retType(t) + ret.Type = b.Prog.retType(t) default: panic("todo") } diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index a8af71d4..bed0d537 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -50,7 +50,7 @@ func (p BasicBlock) Index() int { type aBuilder struct { impl llvm.Builder fn Function - prog Program + Prog Program } // Builder represents a builder for creating instructions in a function.