diff --git a/c/c.go b/c/c.go index 34a1c171..18f9dc1f 100644 --- a/c/c.go +++ b/c/c.go @@ -40,11 +40,11 @@ type integer interface { //go:linkname Str llgo.cstr func Str(string) *Char -//go:linkname Advance llgo.advance -func Advance(ptr Pointer, offset int) Pointer +// llgo:link Advance llgo.advance +func Advance[PtrT any](ptr PtrT, offset int) PtrT { return ptr } // llgo:link Index llgo.index -// func Index[T any, I integer](ptr *T, offset I) T { return *ptr } +func Index[T any, I integer](ptr *T, offset I) T { return *ptr } //go:linkname Alloca llgo.alloca func Alloca(size uintptr) Pointer diff --git a/chore/gentests/gentests.go b/chore/gentests/gentests.go index f839dfc8..b57138a1 100644 --- a/chore/gentests/gentests.go +++ b/chore/gentests/gentests.go @@ -31,6 +31,7 @@ func main() { llgen.Verbose = false + llgenDir(dir + "/cl/_testlibc") llgenDir(dir + "/cl/_testrt") llgenDir(dir+"/cl/_testdata", "") } diff --git a/cl/_testdata/uint/out.ll b/cl/_testdata/uint/out.ll index ffcd184e..ceaaae3a 100644 --- a/cl/_testdata/uint/out.ll +++ b/cl/_testdata/uint/out.ll @@ -1,8 +1,6 @@ ; ModuleID = 'main' source_filename = "main" -%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 } - @"main.init$guard" = global ptr null @__llgo_argc = global ptr null @__llgo_argv = global ptr null @@ -33,17 +31,11 @@ _llgo_0: store ptr %1, ptr @__llgo_argv, align 8 call void @"github.com/goplus/llgo/internal/runtime.init"() call void @main.init() - %2 = call %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.NewString"(ptr @0, i64 10) - %3 = call ptr @"github.com/goplus/llgo/c.Str"(%"github.com/goplus/llgo/internal/runtime.String" %2) - %4 = call i32 @main.f(i32 100) - %5 = call i32 (ptr, ...) @printf(ptr %3, i32 %4) + %2 = call i32 @main.f(i32 100) + %3 = call i32 (ptr, ...) @printf(ptr @0, i32 %2) ret void } declare void @"github.com/goplus/llgo/internal/runtime.init"() -declare ptr @"github.com/goplus/llgo/c.Str"(%"github.com/goplus/llgo/internal/runtime.String") - -declare %"github.com/goplus/llgo/internal/runtime.String" @"github.com/goplus/llgo/internal/runtime.NewString"(ptr, i64) - declare i32 @printf(ptr, ...) diff --git a/cl/_testlibc/argv/in.go b/cl/_testlibc/argv/in.go new file mode 100644 index 00000000..b4055521 --- /dev/null +++ b/cl/_testlibc/argv/in.go @@ -0,0 +1,11 @@ +package main + +import ( + "github.com/goplus/llgo/c" +) + +func main() { + for i := c.Int(0); i < c.Argc; i++ { + c.Printf(c.Str("%s\n"), c.Index(c.Argv, i)) + } +} diff --git a/cl/_testlibc/argv/out.ll b/cl/_testlibc/argv/out.ll new file mode 100644 index 00000000..9253ebe4 --- /dev/null +++ b/cl/_testlibc/argv/out.ll @@ -0,0 +1,50 @@ +; ModuleID = 'main' +source_filename = "main" + +@"main.init$guard" = global ptr null +@__llgo_argc = global ptr null +@__llgo_argv = global ptr null +@0 = private unnamed_addr constant [4 x i8] c"%s\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(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + br label %_llgo_3 + +_llgo_1: ; preds = %_llgo_3 + %2 = load ptr, ptr @__llgo_argv, align 8 + %3 = getelementptr ptr, ptr %2, i32 %7 + %4 = load ptr, ptr %3, align 8 + %5 = call i32 (ptr, ...) @printf(ptr @0, ptr %4) + %6 = add i32 %7, 1 + br label %_llgo_3 + +_llgo_2: ; preds = %_llgo_3 + ret void + +_llgo_3: ; preds = %_llgo_1, %_llgo_0 + %7 = phi i32 [ 0, %_llgo_0 ], [ %6, %_llgo_1 ] + %8 = load i32, ptr @__llgo_argc, align 4 + %9 = icmp slt i32 %7, %8 + br i1 %9, label %_llgo_1, label %_llgo_2 +} + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +declare i32 @printf(ptr, ...) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index 460166e7..d41d1272 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -159,13 +159,18 @@ func TestErrImport(t *testing.T) { } func TestErrInitLinkname(t *testing.T) { + var ctx context + ctx.initLinkname("foo", "//llgo:link abc", func(name string) (bool, bool) { + return false, false + }) defer func() { if r := recover(); r == nil { t.Fatal("initLinkname: no error?") } }() - var ctx context - ctx.initLinkname("foo", "//go:linkname Printf printf", false) + ctx.initLinkname("foo", "//go:linkname Printf printf", func(name string) (isVar bool, ok bool) { + return false, name == "Printf" + }) } func TestErrVarOf(t *testing.T) { diff --git a/cl/compile.go b/cl/compile.go index 4170a4c4..24179d6d 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -170,7 +170,7 @@ func (p *context) compileMethods(pkg llssa.Package, typ types.Type) { for i, n := 0, mthds.Len(); i < n; i++ { mthd := mthds.At(i) if ssaMthd := prog.MethodValue(mthd); ssaMthd != nil { - p.compileFuncDecl(pkg, mthd.Obj().Pkg(), ssaMthd) + p.compileFuncDecl(pkg, ssaMthd) } } } @@ -205,8 +205,8 @@ var ( argvTy = types.NewPointer(types.NewPointer(types.Typ[types.Int8])) ) -func (p *context) compileFuncDecl(pkg llssa.Package, pkgTypes *types.Package, f *ssa.Function) llssa.Function { - name, ftype := p.funcName(pkgTypes, f, true) +func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) llssa.Function { + pkgTypes, name, ftype := p.funcName(f, true) if ftype != goFunc { return nil } @@ -271,15 +271,15 @@ func (p *context) compileFuncDecl(pkg llssa.Package, pkgTypes *types.Package, f // 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, ftype := p.funcName(pkgTypes, fn, false) + _, name, ftype := p.funcName(fn, false) if ftype == llgoInstr { switch name { case "cstr": ftype = llgoCstr case "advance": ftype = llgoAdvance + case "index": + ftype = llgoIndex case "alloca": ftype = llgoAlloca case "allocaCStr": @@ -289,9 +289,12 @@ func (p *context) funcOf(fn *ssa.Function) (ret llssa.Function, ftype int) { default: panic("unknown llgo instruction: " + name) } - } else if ret = pkg.FuncOf(name); ret == nil && len(fn.FreeVars) == 0 { - sig := fn.Signature - ret = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false) + } else { + pkg := p.pkg + if ret = pkg.FuncOf(name); ret == nil && len(fn.FreeVars) == 0 { + sig := fn.Signature + ret = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false) + } } return } @@ -376,6 +379,12 @@ func cstr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { panic("cstr(): invalid arguments") } +// 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)) +} + +// func advance(ptr *T, offset int) *T func (p *context) advance(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { if len(args) == 2 { ptr := p.compileValue(b, args[0]) @@ -488,6 +497,8 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue ret = cstr(b, call.Args) case llgoAdvance: ret = p.advance(b, call.Args) + case llgoIndex: + ret = p.index(b, call.Args) case llgoAlloca: ret = p.alloca(b, call.Args) case llgoAllocaCStr: @@ -667,7 +678,7 @@ func (p *context) compileFunction(v *ssa.Function) (llssa.Function, int) { // v.Pkg == nil: means auto generated function? if v.Pkg == p.goPkg || v.Pkg == nil { // function in this package - if fn := p.compileFuncDecl(p.pkg, p.goTyps, v); fn != nil { + if fn := p.compileFuncDecl(p.pkg, v); fn != nil { return fn, goFunc } } @@ -776,11 +787,12 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret ll member := m.val switch member := member.(type) { case *ssa.Function: - if member.TypeParams() != nil { + if member.TypeParams() != nil || member.TypeArgs() != nil { + // TODO(xsw): don't compile generic functions // Do not try to build generic (non-instantiated) functions. continue } - ctx.compileFuncDecl(ret, member.Pkg.Pkg, member) + ctx.compileFuncDecl(ret, member) case *ssa.Type: ctx.compileType(ret, member) case *ssa.Global: diff --git a/cl/compile_test.go b/cl/compile_test.go index b9c3443f..2d8861cc 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -28,6 +28,10 @@ func testCompile(t *testing.T, src, expected string) { cltest.TestCompileEx(t, src, "foo.go", expected) } +func TestFromTestlibc(t *testing.T) { + cltest.FromDir(t, "", "./_testlibc", false) +} + func TestFromTestrt(t *testing.T) { cltest.FromDir(t, "", "./_testrt", true) } diff --git a/cl/import.go b/cl/import.go index 8f5f149f..4951e793 100644 --- a/cl/import.go +++ b/cl/import.go @@ -29,19 +29,49 @@ import ( "golang.org/x/tools/go/ssa" ) -type contentLines = [][]byte -type contentMap = map[string]contentLines +type symInfo struct { + file string + isVar bool +} -func contentOf(m contentMap, file string) (lines contentLines, err error) { - if v, ok := m[file]; ok { - return v, nil +type pkgSymInfo struct { + files map[string][]byte // file => content + syms map[string]symInfo // name => isVar +} + +func newPkgSymInfo() *pkgSymInfo { + return &pkgSymInfo{ + files: make(map[string][]byte), + syms: make(map[string]symInfo), } - b, err := os.ReadFile(file) - if err == nil { - lines = bytes.Split(b, []byte{'\n'}) - m[file] = lines +} + +func (p *pkgSymInfo) addSym(fset *token.FileSet, pos token.Pos, name string, isVar bool) { + f := fset.File(pos) + if fp := f.Position(pos); fp.Line > 2 { + file := fp.Filename + if _, ok := p.files[file]; !ok { + b, err := os.ReadFile(file) + if err == nil { + p.files[file] = b + } + } + p.syms[name] = symInfo{file, isVar} + } +} + +func (p *pkgSymInfo) initLinknames(ctx *context, pkgPath string) { + for file, b := range p.files { + lines := bytes.Split(b, []byte{'\n'}) + for _, line := range lines { + ctx.initLinkname(pkgPath, string(line), func(name string) (isVar bool, ok bool) { + if sym, ok := p.syms[name]; ok && file == sym.file { + return sym.isVar, true + } + return + }) + } } - return } // PkgKindOf returns the kind of a package. @@ -87,23 +117,23 @@ func (p *context) importPkg(pkg *types.Package, i *pkgInfo) { i.kind = kind fset := p.fset names := scope.Names() - contents := make(contentMap) - pkgPath := llssa.PathOf(pkg) + syms := newPkgSymInfo() for _, name := range names { if token.IsExported(name) { obj := scope.Lookup(name) switch obj := obj.(type) { case *types.Func: if pos := obj.Pos(); pos != token.NoPos { - p.initLinknameByPos(fset, pos, pkgPath, contents, false) + syms.addSym(fset, pos, name, false) } case *types.Var: if pos := obj.Pos(); pos != token.NoPos { - p.initLinknameByPos(fset, pos, pkgPath, contents, true) + syms.addSym(fset, pos, name, true) } } } } + syms.initLinknames(p, llssa.PathOf(pkg)) } func (p *context) initFiles(pkgPath string, files []*ast.File) { @@ -112,50 +142,53 @@ func (p *context) initFiles(pkgPath string, files []*ast.File) { switch decl := decl.(type) { case *ast.FuncDecl: if decl.Recv == nil { - p.initLinknameByDoc(decl.Doc, pkgPath, false) + p.initLinknameByDoc(decl.Doc, pkgPath, decl.Name.Name, false) } case *ast.GenDecl: if decl.Tok == token.VAR && len(decl.Specs) == 1 { - p.initLinknameByDoc(decl.Doc, pkgPath, true) + if names := decl.Specs[0].(*ast.ValueSpec).Names; len(names) == 1 { + p.initLinknameByDoc(decl.Doc, pkgPath, names[0].Name, true) + } } } } } } -func (p *context) initLinknameByDoc(doc *ast.CommentGroup, pkgPath string, isVar bool) { +func (p *context) initLinknameByDoc(doc *ast.CommentGroup, pkgPath, expName string, isVar bool) { if doc != nil { if n := len(doc.List); n > 0 { line := doc.List[n-1].Text - p.initLinkname(pkgPath, line, isVar) + p.initLinkname(pkgPath, line, func(name string) (bool, bool) { + return isVar, name == expName + }) } } } -func (p *context) initLinknameByPos(fset *token.FileSet, pos token.Pos, pkgPath string, contents contentMap, isVar bool) { - f := fset.File(pos) - if fp := f.Position(pos); fp.Line > 2 { - lines, err := contentOf(contents, fp.Filename) - if err != nil { - panic(err) - } - if i := fp.Line - 2; i < len(lines) { - line := string(lines[i]) - p.initLinkname(pkgPath, line, isVar) - } - } -} - -func (p *context) initLinkname(pkgPath, line string, isVar bool) { +func (p *context) initLinkname(pkgPath, line string, f func(name string) (isVar bool, ok bool)) { const ( - linkname = "//go:linkname " + linkname = "//go:linkname " + llgolink = "//llgo:link " + llgolink2 = "// llgo:link " ) if strings.HasPrefix(line, linkname) { - text := strings.TrimSpace(line[len(linkname):]) - if idx := strings.IndexByte(text, ' '); idx > 0 { + p.initLink(pkgPath, line, len(linkname), f) + } else if strings.HasPrefix(line, llgolink2) { + p.initLink(pkgPath, line, len(llgolink2), f) + } else if strings.HasPrefix(line, llgolink) { + p.initLink(pkgPath, line, len(llgolink), f) + } +} + +func (p *context) initLink(pkgPath string, line string, prefix int, f func(name string) (isVar bool, ok bool)) { + text := strings.TrimSpace(line[prefix:]) + if idx := strings.IndexByte(text, ' '); idx > 0 { + name := text[:idx] + if isVar, ok := f(name); ok { link := strings.TrimLeft(text[idx+1:], " ") if isVar || strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr - name := pkgPath + "." + text[:idx] + name := pkgPath + "." + name p.link[name] = link } else { panic(line + ": no specified call convention. eg. //go:linkname Printf C.printf") @@ -203,23 +236,38 @@ const ( llgoAlloca = llgoInstrBase + 2 llgoAllocaCStr = llgoInstrBase + 3 llgoAdvance = llgoInstrBase + 4 + llgoIndex = llgoInstrBase + 5 ) -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, ignoredFunc +func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, string, int) { + var pkg *types.Package + var orgName string + if origin := fn.Origin(); origin != nil { + pkg = origin.Pkg.Pkg + p.ensureLoaded(pkg) + orgName = funcName(pkg, origin) + } else { + if fnPkg := fn.Pkg; fnPkg != nil { + pkg = fnPkg.Pkg + } else { + pkg = p.goTyps + } + p.ensureLoaded(pkg) + orgName = funcName(pkg, fn) + if ignore && ignoreName(orgName) || checkCgo(fn.Name()) { + return nil, orgName, ignoredFunc + } } - if v, ok := p.link[name]; ok { + if v, ok := p.link[orgName]; ok { if strings.HasPrefix(v, "C.") { - return v[2:], cFunc + return nil, v[2:], cFunc } if strings.HasPrefix(v, "llgo.") { - return v[5:], llgoInstr + return nil, v[5:], llgoInstr } - return v, goFunc + return pkg, v, goFunc } - return name, goFunc + return pkg, funcName(pkg, fn), goFunc } const ( diff --git a/ssa/expr.go b/ssa/expr.go index a8e4a0a4..4dfa3b62 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -496,12 +496,20 @@ func (b Builder) Phi(t Type) Phi { // ----------------------------------------------------------------------------- -// Advance returns the pointer ptr advanced by offset bytes. +// Advance returns the pointer ptr advanced by offset. func (b Builder) Advance(ptr Expr, offset Expr) Expr { if debugInstr { log.Printf("Advance %v, %v\n", ptr.impl, offset.impl) } - ret := llvm.CreateGEP(b.impl, b.Prog.tyInt8(), ptr.impl, []llvm.Value{offset.impl}) + var elem llvm.Type + var prog = b.Prog + switch t := ptr.raw.Type.(type) { + case *types.Basic: // void + elem = prog.tyInt8() + default: + elem = prog.rawType(t.(*types.Pointer).Elem()).ll + } + ret := llvm.CreateGEP(b.impl, elem, ptr.impl, []llvm.Value{offset.impl}) return Expr{ret, ptr.Type} } diff --git a/ssa/type_cvt.go b/ssa/type_cvt.go index 76f9aae0..d6691f1e 100644 --- a/ssa/type_cvt.go +++ b/ssa/type_cvt.go @@ -17,6 +17,7 @@ package ssa import ( + "fmt" "go/token" "go/types" "unsafe" @@ -100,7 +101,7 @@ func (p goTypes) cvtType(typ types.Type) (raw types.Type, cvt bool) { return types.NewChan(t.Dir(), elem), true } default: - panic("unreachable") + panic(fmt.Sprintf("cvtType: unexpected type - %T", typ)) } return typ, false }