diff --git a/cl/_testcgo/alloca/in.go b/cl/_testcgo/alloca/in.go new file mode 100644 index 00000000..83452977 --- /dev/null +++ b/cl/_testcgo/alloca/in.go @@ -0,0 +1,14 @@ +package main + +import ( + "unsafe" + + "github.com/goplus/llgo/internal/runtime/c" +) + +func main() { + s := c.String("Hi\n") + s2 := c.Alloca(4) + c.Memcpy(s2, unsafe.Pointer(s), 4) + c.Printf(c.String("%s"), s) +} diff --git a/cl/_testcgo/alloca/out.ll b/cl/_testcgo/alloca/out.ll new file mode 100644 index 00000000..44cd672d --- /dev/null +++ b/cl/_testcgo/alloca/out.ll @@ -0,0 +1,36 @@ +; ModuleID = 'main' +source_filename = "main" + +%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } + +@"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() + %0 = call ptr @"github.com/goplus/llgo/internal/runtime/c.String"([4 x i8] c"Hi\0A\00") + %1 = alloca i8, i64 4, align 1 + %2 = call ptr @memcpy(ptr %1, ptr %0, i64 4) + %3 = call ptr @"github.com/goplus/llgo/internal/runtime/c.String"([3 x i8] c"%s\00") + %4 = call i32 (ptr, ...) @printf(ptr %3, ptr %0) + ret void +} + +declare ptr @"github.com/goplus/llgo/internal/runtime/c.String"(%"github.com/goplus/llgo/internal/runtime.String") + +declare ptr @memcpy(ptr, ptr, i64) + +declare i32 @printf(ptr, ...) diff --git a/cl/_testcgo/cstr/in.go b/cl/_testcgo/cstr/in.go new file mode 100644 index 00000000..a8285b4e --- /dev/null +++ b/cl/_testcgo/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/_testcgo/cstr/out.ll b/cl/_testcgo/cstr/out.ll new file mode 100644 index 00000000..f7b5d255 --- /dev/null +++ b/cl/_testcgo/cstr/out.ll @@ -0,0 +1,27 @@ +; ModuleID = 'main' +source_filename = "main" + +@"main.init$guard" = global ptr null +@0 = private unnamed_addr constant [14 x i8] c"Hello, world\0A\00", align 1 + +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(ptr @0) + ret void +} + +declare void @printf(ptr, ...) diff --git a/cl/_testcgo/unreachable/in.go b/cl/_testcgo/unreachable/in.go new file mode 100644 index 00000000..c0c0a46b --- /dev/null +++ b/cl/_testcgo/unreachable/in.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/goplus/llgo/internal/runtime/c" +) + +func foo() { + c.Unreachable() +} + +func main() { + foo() + c.Printf(c.String("Hello\n")) +} diff --git a/cl/_testcgo/unreachable/out.ll b/cl/_testcgo/unreachable/out.ll new file mode 100644 index 00000000..52ff58db --- /dev/null +++ b/cl/_testcgo/unreachable/out.ll @@ -0,0 +1,38 @@ +; ModuleID = 'main' +source_filename = "main" + +%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } + +@"main.init$guard" = global ptr null + +define void @main.foo() { +_llgo_0: + unreachable + ret void +} + +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 @main.foo() + %0 = call ptr @"github.com/goplus/llgo/internal/runtime/c.String"([7 x i8] c"Hello\0A\00") + %1 = call i32 (ptr, ...) @printf(ptr %0) + ret void +} + +declare ptr @"github.com/goplus/llgo/internal/runtime/c.String"(%"github.com/goplus/llgo/internal/runtime.String") + +declare i32 @printf(ptr, ...) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index c947bdd5..a3fa722a 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -25,6 +25,34 @@ import ( "golang.org/x/tools/go/ssa" ) +func TestErrAlloca(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Fatal("alloca: no error?") + } + }() + var ctz context + ctz.alloca(nil, nil) +} + +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..8421a2ad 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,26 @@ 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.CString(sv) + } + } + } + panic("cstr(): invalid arguments") +} + +func (p *context) alloca(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { + if len(args) == 1 { + n := p.compileValue(b, args[0]) + return b.Alloca(n) + } + panic("alloca(size uintptr): 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 +308,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,10 +319,28 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue args := p.compileValues(b, call.Args, kind) ret = b.BuiltinCall(fn, args...) } - } else { - fn := p.compileValue(b, cv) - args := p.compileValues(b, call.Args, kind) - ret = b.Call(fn, args...) + 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) + case llgoAlloca: + ret = p.alloca(b, call.Args) + case llgoUnreachable: + b.Unreachable() + default: + panic("todo") + } + default: + panic("todo") + /* + fn := p.compileValue(b, cv) + args := p.compileValues(b, call.Args, kind) + ret = b.Call(fn, args...) + */ } case *ssa.BinOp: x := p.compileValue(b, v.X) @@ -421,8 +461,14 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { } } case *ssa.Function: - fn := p.funcOf(v) - return fn.Expr + panic("unreachable") + /* + 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) return g.Expr 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/go.mod b/go.mod index be8b2c10..4c601893 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/aykevl/go-wasm v0.0.1 github.com/goplus/gogen v1.15.2 - github.com/goplus/llvm v0.7.2 + github.com/goplus/llvm v0.7.3-0.20240429101120-83f0ad1fd7b3 github.com/goplus/mod v0.13.10 github.com/qiniu/x v1.13.10 golang.org/x/tools v0.20.0 diff --git a/go.sum b/go.sum index 2db946f6..0de84975 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/goplus/llvm v0.7.1 h1:B12Fr/wc3pAsq5PLuac9u9IuKpLRuCufdVAeGDP/MRw= github.com/goplus/llvm v0.7.1/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/llvm v0.7.2 h1:NL3LlwAmYVCGA6yV40AjOvMDKl2dbCqoYPtugmLQK+E= github.com/goplus/llvm v0.7.2/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= +github.com/goplus/llvm v0.7.3-0.20240429063826-4d6268bd1670 h1:M5fGMLl9vj1W4lSKrOkhzVpoyyMOXtfAU5MsukbhUnY= +github.com/goplus/llvm v0.7.3-0.20240429063826-4d6268bd1670/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= +github.com/goplus/llvm v0.7.3-0.20240429101120-83f0ad1fd7b3 h1:FWne6LdbQ0p+lGBpkdmCYzcJDDTH85ygkkWKy2Pd3EQ= +github.com/goplus/llvm v0.7.3-0.20240429101120-83f0ad1fd7b3/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE= github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c= github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE= diff --git a/internal/runtime/c/c.go b/internal/runtime/c/c.go index 451d80b0..7c11ea9a 100644 --- a/internal/runtime/c/c.go +++ b/internal/runtime/c/c.go @@ -16,19 +16,20 @@ package c +import "C" import "unsafe" 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 @@ -38,4 +39,4 @@ func Malloc(size uintptr) unsafe.Pointer func Memcpy(dst, src unsafe.Pointer, n uintptr) unsafe.Pointer //go:linkname Printf C.printf -func Printf(format *int8, __llgo_va_list ...any) +func Printf(format *int8, __llgo_va_list ...any) C.int diff --git a/ssa/expr.go b/ssa/expr.go index 4ea32c11..822e7172 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -82,12 +82,6 @@ func (p Program) Null(t Type) Expr { return Expr{llvm.ConstNull(t.ll), t} } -// CStringVal returns a c-style string constant expression. -func (p Program) CStringVal(v string) Expr { - t := p.CString() - return Expr{llvm.ConstString(v, true), t} -} - // StringVal returns string constant expression. func (p Program) StringVal(v string) Expr { t := p.String() @@ -132,7 +126,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) } @@ -153,6 +147,11 @@ func (b Builder) Const(v constant.Value, typ Type) Expr { panic("todo") } +// CString returns a c-style string constant expression. +func (b Builder) CString(v string) Expr { + return Expr{llvm.CreateGlobalStringPtr(b.impl, v), b.Prog.CString()} +} + // ----------------------------------------------------------------------------- const ( @@ -277,7 +276,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 +317,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 +353,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 +376,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 +406,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 @@ -421,6 +420,30 @@ func (b Builder) Alloc(t *types.Pointer, heap bool) (ret Expr) { return } +// Alloca allocates space for n bytes. +func (b Builder) Alloca(n Expr) (ret Expr) { + if debugInstr { + log.Printf("Alloca %v\n", n.impl) + } + prog := b.Prog + telem := prog.tyInt8() + ret.impl = llvm.CreateArrayAlloca(b.impl, telem, n.impl) + ret.Type = &aType{prog.tyVoidPtr(), types.Typ[types.UnsafePointer], vkPtr} + return +} + +/* +// ArrayAlloca reserves space for an array of n elements of type telem. +func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) { + if debugInstr { + log.Printf("ArrayAlloca %v, %v\n", telem.t, n.impl) + } + ret.impl = llvm.CreateArrayAlloca(b.impl, telem.ll, n.impl) + ret.Type = b.Prog.Pointer(telem) + return +} +*/ + // The ChangeType instruction applies to X a value-preserving type // change to Type(). // @@ -452,7 +475,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 +511,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 +647,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 +682,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..73d2eb09 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. @@ -76,6 +76,11 @@ func (b Builder) Panic(v Expr) { b.impl.CreateUnreachable() // TODO(xsw): pass v } +// Unreachable emits an unreachable instruction. +func (b Builder) Unreachable() { + b.impl.CreateUnreachable() +} + // Return emits a return instruction. func (b Builder) Return(results ...Expr) { if debugInstr {