From 2cbcc53c540ee1d8f7373c6e5644d1ca1c8b49c0 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 24 Apr 2024 07:55:51 +0800 Subject: [PATCH 1/4] llgo build/install --- build_install_run.go | 45 ---------- cmd/internal/build/build.go | 76 +---------------- load.go => cmd/internal/install/install.go | 24 +++--- cmd/llgo/llgo.go | 2 + gen.go | 98 ---------------------- internal/build/build_install.go | 95 +++++++++++++++++++++ 6 files changed, 115 insertions(+), 225 deletions(-) delete mode 100644 build_install_run.go rename load.go => cmd/internal/install/install.go (54%) delete mode 100644 gen.go create mode 100644 internal/build/build_install.go diff --git a/build_install_run.go b/build_install_run.go deleted file mode 100644 index 70c5dbbd..00000000 --- a/build_install_run.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 llgo - -import ( - "github.com/goplus/llgo/x/gocmd" - "github.com/goplus/mod/gopmod" -) - -// ----------------------------------------------------------------------------- - -// NotFound returns if cause err is ErrNotFound or not -func NotFound(err error) bool { - return gopmod.IsNotFound(err) -} - -// ----------------------------------------------------------------------------- - -func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) { - panic("todo") -} - -func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) { - panic("todo") -} - -func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) { - panic("todo") -} - -// ----------------------------------------------------------------------------- diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go index fddf7ce2..ebe5e4e2 100644 --- a/cmd/internal/build/build.go +++ b/cmd/internal/build/build.go @@ -18,88 +18,20 @@ package build import ( - "fmt" - "log" - "os" - "path/filepath" - "reflect" - - "github.com/goplus/llgo" "github.com/goplus/llgo/cmd/internal/base" - "github.com/goplus/llgo/internal/projs" - "github.com/goplus/llgo/x/gocmd" + "github.com/goplus/llgo/internal/build" ) // llgo build var Cmd = &base.Command{ - UsageLine: "llgo build [flags] [packages]", - Short: "Build Go files", + UsageLine: "llgo build [-o output] [build flags] [packages]", + Short: "Compile packages and dependencies", } -var ( - flagOutput = flag.String("o", "", "build output file") - _ = flag.Bool("v", false, "print verbose information") - flag = &Cmd.Flag -) - func init() { Cmd.Run = runCmd } func runCmd(cmd *base.Command, args []string) { - err := flag.Parse(args) - if err != nil { - log.Panicln("parse input arguments failed:", err) - } - - args = flag.Args() - if len(args) == 0 { - args = []string{"."} - } - - proj, args, err := projs.ParseOne(args...) - if err != nil { - log.Panicln(err) - } - if len(args) != 0 { - log.Panicln("too many arguments:", args) - } - - conf := &llgo.Config{} - confCmd := &gocmd.BuildConfig{} - if *flagOutput != "" { - output, err := filepath.Abs(*flagOutput) - if err != nil { - log.Panicln(err) - } - confCmd.Output = output - } - build(proj, conf, confCmd) + build.Do(args, build.ModeBuild) } - -func build(proj projs.Proj, conf *llgo.Config, build *gocmd.BuildConfig) { - var obj string - var err error - switch v := proj.(type) { - case *projs.DirProj: - obj = v.Dir - err = llgo.BuildDir(obj, conf, build) - case *projs.PkgPathProj: - obj = v.Path - err = llgo.BuildPkgPath("", obj, conf, build) - case *projs.FilesProj: - err = llgo.BuildFiles(v.Files, conf, build) - default: - log.Panicln("`llgo build` doesn't support", reflect.TypeOf(v)) - } - if llgo.NotFound(err) { - fmt.Fprintf(os.Stderr, "llgo build %v: not found\n", obj) - } else if err != nil { - fmt.Fprintln(os.Stderr, err) - } else { - return - } - os.Exit(1) -} - -// ----------------------------------------------------------------------------- diff --git a/load.go b/cmd/internal/install/install.go similarity index 54% rename from load.go rename to cmd/internal/install/install.go index 7966059e..1ae49dbf 100644 --- a/load.go +++ b/cmd/internal/install/install.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved. + * 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. @@ -14,20 +14,24 @@ * limitations under the License. */ -package llgo +// Package install implements the “llgo install command. +package install import ( - "github.com/goplus/llgo/ssa" + "github.com/goplus/llgo/cmd/internal/base" + "github.com/goplus/llgo/internal/build" ) -// ----------------------------------------------------------------------------- - -type Config struct { +// llgo install +var Cmd = &base.Command{ + UsageLine: "llgo install [build flags] [packages]", + Short: "Compile and install packages and dependencies", } -// LoadDir loads Go packages from a specified directory. -func LoadDir(dir string, conf *Config, genTestPkg, promptGen bool) (out, test *ssa.Package, err error) { - panic("todo") +func init() { + Cmd.Run = runCmd } -// ----------------------------------------------------------------------------- +func runCmd(cmd *base.Command, args []string) { + build.Do(args, build.ModeInstall) +} diff --git a/cmd/llgo/llgo.go b/cmd/llgo/llgo.go index 7e22a168..9f30a120 100644 --- a/cmd/llgo/llgo.go +++ b/cmd/llgo/llgo.go @@ -27,6 +27,7 @@ import ( "github.com/goplus/llgo/cmd/internal/base" "github.com/goplus/llgo/cmd/internal/build" "github.com/goplus/llgo/cmd/internal/help" + "github.com/goplus/llgo/cmd/internal/install" ) func mainUsage() { @@ -38,6 +39,7 @@ func init() { flag.Usage = mainUsage base.Llgo.Commands = []*base.Command{ build.Cmd, + install.Cmd, } } diff --git a/gen.go b/gen.go deleted file mode 100644 index 262f1298..00000000 --- a/gen.go +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 llgo - -/* -import ( - "fmt" - "io/fs" - "os" - "path/filepath" - "strings" - - "github.com/qiniu/x/errors" -) - -type GenFlags int - -const ( - GenFlagCheckOnly GenFlags = 1 << iota - GenFlagPrintError - GenFlagPrompt -) - -// Gen generates llgo_autogen.ll for a Go package directory. -func Gen(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string, bool, error) { - recursively := strings.HasSuffix(dir, "/...") - if recursively { - dir = dir[:len(dir)-4] - } - return dir, recursively, genDir(dir, conf, genTestPkg, recursively, flags) -} - -func genDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenFlags) (err error) { - if conf == nil { - conf = new(Config) - } - if recursively { - var ( - list errors.List - ) - fn := func(path string, d fs.DirEntry, err error) error { - if err == nil && d.IsDir() { - if strings.HasPrefix(d.Name(), "_") || (path != dir && hasMod(path)) { // skip _ - return filepath.SkipDir - } - if e := genGoIn(path, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) { - if flags&GenFlagPrintError != 0 { - fmt.Fprintln(os.Stderr, e) - } - list.Add(e) - } - } - return err - } - err = filepath.WalkDir(dir, fn) - if err != nil { - return errors.NewWith(err, `filepath.WalkDir(dir, fn)`, -2, "filepath.WalkDir", dir, fn) - } - return list.ToError() - } - if e := genGoIn(dir, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) { - if (flags & GenFlagPrintError) != 0 { - fmt.Fprintln(os.Stderr, e) - } - err = e - } - return -} - -func hasMod(dir string) bool { - _, err := os.Lstat(dir + "/go.mod") - return err == nil -} - -// GenPkgPath generates llgo_autogen.ll for a Go package. -func GenPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool, flags GenFlags) (localDir string, recursively bool, err error) { - panic("todo") -} - -// GenFiles generates llgo_autogen.ll for specified Go files. -func GenFiles(autogen string, files []string, conf *Config) (outFiles []string, err error) { - panic("todo") -} -*/ diff --git a/internal/build/build_install.go b/internal/build/build_install.go new file mode 100644 index 00000000..252539ed --- /dev/null +++ b/internal/build/build_install.go @@ -0,0 +1,95 @@ +/* + * 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 ( + "log" + "os" + "strings" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + + "github.com/goplus/llgo/cl" + llssa "github.com/goplus/llgo/ssa" +) + +type Mode int + +const ( + ModeBuild Mode = iota + ModeInstall +) + +// ----------------------------------------------------------------------------- + +const ( + loadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles + loadImports = loadFiles | packages.NeedImports + loadTypes = loadImports | packages.NeedTypes | packages.NeedTypesSizes + loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo +) + +func Do(args []string, mode Mode) { + flags, patterns := parseArgs(args) + cfg := &packages.Config{ + Mode: loadSyntax | packages.NeedExportFile, + BuildFlags: flags, + } + + if patterns == nil { + patterns = []string{"."} + } + initial, err := packages.Load(cfg, patterns...) + check(err) + + // Create SSA-form program representation. + _, ssaPkgs := ssautil.AllPackages(initial, ssa.SanityCheckFunctions) + + llssa.Initialize(llssa.InitAll) + prog := llssa.NewProgram(nil) + for i, ssaPkg := range ssaPkgs { + pkg := initial[i] + if ssaPkg == nil { // TODO(xsw): error handling + log.Panicf("cannot build SSA for package %s", pkg) + } + ssaPkg.Build() + ret, err := cl.NewPackage(prog, ssaPkg, pkg.Syntax) + check(err) + if mode == ModeInstall { + os.WriteFile(pkg.ExportFile+".ll", []byte(ret.String()), 0644) + } + } +} + +func parseArgs(args []string) (flags, patterns []string) { + for i, arg := range args { + if !strings.HasPrefix(arg, "-") { + return args[:i], args[i:] + } + } + return args, nil +} + +func check(err error) { + if err != nil { + panic(err) + } +} + +// ----------------------------------------------------------------------------- From b1342d8d976540aa272d49897c9245981f57aa47 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 24 Apr 2024 11:13:17 +0800 Subject: [PATCH 2/4] build.Do --- cl/compile.go | 3 ++ internal/build/build_install.go | 61 ++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index 0a448401..c9ebfb86 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -116,6 +116,9 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) { name := p.funcName(f.Pkg.Pkg, f) + if name == "unsafe.init" { + return + } if debugInstr { log.Println("==> NewFunc", name) } diff --git a/internal/build/build_install.go b/internal/build/build_install.go index 252539ed..9622abc8 100644 --- a/internal/build/build_install.go +++ b/internal/build/build_install.go @@ -17,13 +17,14 @@ package build import ( + "fmt" + "go/token" "log" "os" "strings" "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" "github.com/goplus/llgo/cl" llssa "github.com/goplus/llgo/ssa" @@ -59,24 +60,58 @@ func Do(args []string, mode Mode) { check(err) // Create SSA-form program representation. - _, ssaPkgs := ssautil.AllPackages(initial, ssa.SanityCheckFunctions) + ssaProg, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions) + ssaProg.Build() + for _, errPkg := range errPkgs { + log.Println("cannot build SSA for package", errPkg) + } llssa.Initialize(llssa.InitAll) + llssa.SetDebug(llssa.DbgFlagAll) + cl.SetDebug(cl.DbgFlagAll) + prog := llssa.NewProgram(nil) - for i, ssaPkg := range ssaPkgs { - pkg := initial[i] - if ssaPkg == nil { // TODO(xsw): error handling - log.Panicf("cannot build SSA for package %s", pkg) - } - ssaPkg.Build() - ret, err := cl.NewPackage(prog, ssaPkg, pkg.Syntax) - check(err) - if mode == ModeInstall { - os.WriteFile(pkg.ExportFile+".ll", []byte(ret.String()), 0644) - } + for _, pkg := range pkgs { + buildPkg(prog, pkg, mode) } } +func buildPkg(prog llssa.Program, pkg aPackage, mode Mode) { + pkgPath := pkg.PkgPath + fmt.Fprintln(os.Stderr, pkgPath) + if pkgPath == "unsafe" { // TODO(xsw): remove this special case + return + } + ret, err := cl.NewPackage(prog, pkg.SSA, pkg.Syntax) + check(err) + if mode == ModeInstall { + os.WriteFile(pkg.ExportFile+".ll", []byte(ret.String()), 0644) + } +} + +type aPackage struct { + *packages.Package + SSA *ssa.Package +} + +func allPkgs(initial []*packages.Package, mode ssa.BuilderMode) (prog *ssa.Program, all []aPackage, errs []*packages.Package) { + var fset *token.FileSet + if len(initial) > 0 { + fset = initial[0].Fset + } + + prog = ssa.NewProgram(fset, mode) + packages.Visit(initial, nil, func(p *packages.Package) { + if p.Types != nil && !p.IllTyped { + ssaPkg := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true) + all = append(all, aPackage{p, ssaPkg}) + } else { + errs = append(errs, p) + } + }) + return +} + func parseArgs(args []string) (flags, patterns []string) { for i, arg := range args { if !strings.HasPrefix(arg, "-") { From 5b734805405e270f2f08e77d9b3a0be8d190b479 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 24 Apr 2024 11:20:31 +0800 Subject: [PATCH 3/4] disable debug info --- internal/build/build_install.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/build/build_install.go b/internal/build/build_install.go index 9622abc8..9c968d67 100644 --- a/internal/build/build_install.go +++ b/internal/build/build_install.go @@ -67,8 +67,8 @@ func Do(args []string, mode Mode) { } llssa.Initialize(llssa.InitAll) - llssa.SetDebug(llssa.DbgFlagAll) - cl.SetDebug(cl.DbgFlagAll) + // llssa.SetDebug(llssa.DbgFlagAll) + // cl.SetDebug(cl.DbgFlagAll) prog := llssa.NewProgram(nil) for _, pkg := range pkgs { From 15679891428d23a73d26a42164fbaeab942d4849 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 24 Apr 2024 11:49:43 +0800 Subject: [PATCH 4/4] llgo install: use clang to link --- internal/build/build_install.go | 22 +++++++++++----- x/clang/clang.go | 46 +++++++++++++++++++++++++++++++++ x/llexportdata/llexportdata.go | 40 ---------------------------- 3 files changed, 62 insertions(+), 46 deletions(-) create mode 100644 x/clang/clang.go delete mode 100644 x/llexportdata/llexportdata.go diff --git a/internal/build/build_install.go b/internal/build/build_install.go index 9c968d67..166fcaf8 100644 --- a/internal/build/build_install.go +++ b/internal/build/build_install.go @@ -28,6 +28,7 @@ import ( "github.com/goplus/llgo/cl" llssa "github.com/goplus/llgo/ssa" + "github.com/goplus/llgo/x/clang" ) type Mode int @@ -60,8 +61,7 @@ func Do(args []string, mode Mode) { check(err) // Create SSA-form program representation. - ssaProg, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions) - ssaProg.Build() + _, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions) for _, errPkg := range errPkgs { log.Println("cannot build SSA for package", errPkg) } @@ -71,22 +71,32 @@ func Do(args []string, mode Mode) { // cl.SetDebug(cl.DbgFlagAll) prog := llssa.NewProgram(nil) + llFiles := make([]string, 0, len(pkgs)) for _, pkg := range pkgs { - buildPkg(prog, pkg, mode) + pkg.SSA.Build() + llFiles = buildPkg(llFiles, prog, pkg, mode) + } + if mode == ModeInstall { + fmt.Fprintln(os.Stderr, "clang", llFiles) + err = clang.New("").Exec(llFiles...) + check(err) } } -func buildPkg(prog llssa.Program, pkg aPackage, mode Mode) { +func buildPkg(llFiles []string, prog llssa.Program, pkg aPackage, mode Mode) []string { pkgPath := pkg.PkgPath fmt.Fprintln(os.Stderr, pkgPath) if pkgPath == "unsafe" { // TODO(xsw): remove this special case - return + return llFiles } ret, err := cl.NewPackage(prog, pkg.SSA, pkg.Syntax) check(err) if mode == ModeInstall { - os.WriteFile(pkg.ExportFile+".ll", []byte(ret.String()), 0644) + file := pkg.ExportFile + ".ll" + os.WriteFile(file, []byte(ret.String()), 0644) + llFiles = append(llFiles, file) } + return llFiles } type aPackage struct { diff --git a/x/clang/clang.go b/x/clang/clang.go new file mode 100644 index 00000000..eeda4758 --- /dev/null +++ b/x/clang/clang.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 clang + +import ( + "os" + "os/exec" +) + +// ----------------------------------------------------------------------------- + +// Cmd represents a nm command. +type Cmd struct { + app string +} + +// New creates a new nm command. +func New(app string) *Cmd { + if app == "" { + app = "clang" + } + return &Cmd{app} +} + +func (p *Cmd) Exec(args ...string) error { + cmd := exec.Command(p.app, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() +} + +// ----------------------------------------------------------------------------- diff --git a/x/llexportdata/llexportdata.go b/x/llexportdata/llexportdata.go deleted file mode 100644 index 59bc05bf..00000000 --- a/x/llexportdata/llexportdata.go +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 llexportdata - -import ( - "go/token" - "go/types" - "io" -) - -// Read reads export data from in, decodes it, and returns type information for the package. -// -// The package path (effectively its linker symbol prefix) is specified by path, since unlike -// the package name, this information may not be recorded in the export data. -// -// File position information is added to fset. -// -// Read may inspect and add to the imports map to ensure that references within the export data -// to other packages are consistent. The caller must ensure that imports[path] does not exist, -// or exists but is incomplete (see types.Package.Complete), and Read inserts the resulting package -// into this map entry. -// -// On return, the state of the reader is undefined. -func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) { - panic("todo") -}