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/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/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/cmd/internal/install/install.go b/cmd/internal/install/install.go new file mode 100644 index 00000000..1ae49dbf --- /dev/null +++ b/cmd/internal/install/install.go @@ -0,0 +1,37 @@ +/* + * 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 install implements the “llgo install command. +package install + +import ( + "github.com/goplus/llgo/cmd/internal/base" + "github.com/goplus/llgo/internal/build" +) + +// llgo install +var Cmd = &base.Command{ + UsageLine: "llgo install [build flags] [packages]", + Short: "Compile and install packages and dependencies", +} + +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..166fcaf8 --- /dev/null +++ b/internal/build/build_install.go @@ -0,0 +1,140 @@ +/* + * 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 ( + "fmt" + "go/token" + "log" + "os" + "strings" + + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/ssa" + + "github.com/goplus/llgo/cl" + llssa "github.com/goplus/llgo/ssa" + "github.com/goplus/llgo/x/clang" +) + +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. + _, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions) + 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) + llFiles := make([]string, 0, len(pkgs)) + for _, pkg := range pkgs { + 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(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 llFiles + } + ret, err := cl.NewPackage(prog, pkg.SSA, pkg.Syntax) + check(err) + if mode == ModeInstall { + file := pkg.ExportFile + ".ll" + os.WriteFile(file, []byte(ret.String()), 0644) + llFiles = append(llFiles, file) + } + return llFiles +} + +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, "-") { + return args[:i], args[i:] + } + } + return args, nil +} + +func check(err error) { + if err != nil { + panic(err) + } +} + +// ----------------------------------------------------------------------------- diff --git a/load.go b/x/clang/clang.go similarity index 64% rename from load.go rename to x/clang/clang.go index 7966059e..eeda4758 100644 --- a/load.go +++ b/x/clang/clang.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,33 @@ * limitations under the License. */ -package llgo +package clang import ( - "github.com/goplus/llgo/ssa" + "os" + "os/exec" ) // ----------------------------------------------------------------------------- -type Config struct { +// Cmd represents a nm command. +type Cmd struct { + app string } -// 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") +// 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") -}