From edb7a4e1a56754347c0dbf80c90dbde6fa7453eb Mon Sep 17 00:00:00 2001 From: xushiwei Date: Tue, 23 Apr 2024 18:57:46 +0800 Subject: [PATCH] ssadump --- build/build.go | 57 ----------- build_install_run.go | 2 +- chore/ssadump/ssadump.go | 201 +++++++++++++++++++++++++++++++++++++++ cmd/internal/gen/gen.go | 95 ------------------ cmd/llgo/llgo.go | 2 - gen.go | 66 ++++++++++++- 6 files changed, 267 insertions(+), 156 deletions(-) delete mode 100644 build/build.go create mode 100644 chore/ssadump/ssadump.go delete mode 100644 cmd/internal/gen/gen.go diff --git a/build/build.go b/build/build.go deleted file mode 100644 index 13cba697..00000000 --- a/build/build.go +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2023 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 ( - "go/build" -) - -// An ImportMode controls the behavior of the Import method. -type ImportMode = build.ImportMode - -// A Package describes the Go package found in a directory. -type Package struct { - *build.Package -} - -// A Context specifies the supporting context for a build. -type Context struct { - *build.Context -} - -// Import returns details about the Go package named by the import path, -// interpreting local import paths relative to the srcDir directory. -// If the path is a local import path naming a package that can be imported -// using a standard import path, the returned package will set p.ImportPath -// to that path. -// -// If an error occurs, Import returns a non-nil error and a non-nil -// *Package containing partial information. -func (ctxt Context) Import(path string, srcDir string, mode ImportMode) (ret Package, err error) { - pkg, err := build.Import(path, srcDir, mode) - if err != nil { - return - } - ret = Package{pkg} - return -} - -// ImportDir is like Import but processes the Go package found in -// the named directory. -func (ctxt *Context) ImportDir(dir string, mode ImportMode) (Package, error) { - return ctxt.Import(".", dir, mode) -} diff --git a/build_install_run.go b/build_install_run.go index 3d9a92ab..70c5dbbd 100644 --- a/build_install_run.go +++ b/build_install_run.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. diff --git a/chore/ssadump/ssadump.go b/chore/ssadump/ssadump.go new file mode 100644 index 00000000..03fb623a --- /dev/null +++ b/chore/ssadump/ssadump.go @@ -0,0 +1,201 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// ssadump: a tool for displaying and interpreting the SSA form of Go programs. +package main // import "golang.org/x/tools/cmd/ssadump" + +import ( + "flag" + "fmt" + "go/build" + "go/types" + "os" + "runtime" + "runtime/pprof" + + "golang.org/x/tools/go/buildutil" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/interp" + "golang.org/x/tools/go/ssa/ssautil" +) + +const ( + loadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles + loadImports = loadFiles | packages.NeedImports + loadTypes = loadImports | packages.NeedTypes | packages.NeedTypesSizes + loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo +) + +// flags +var ( + mode = ssa.BuilderMode(0) + + testFlag = flag.Bool("test", false, "include implicit test packages and executables") + + runFlag = flag.Bool("run", false, "interpret the SSA program") + + interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter. +The value is a sequence of zero or more more of these letters: +R disable [R]ecover() from panic; show interpreter crash instead. +T [T]race execution of the program. Best for single-threaded programs! +`) + + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + + args stringListValue +) + +func init() { + flag.Var(&mode, "build", ssa.BuilderModeDoc) + flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc) + flag.Var(&args, "arg", "add argument to interpreted program") +} + +const usage = `SSA builder and interpreter. +Usage: ssadump [-build=[DBCSNFLG]] [-test] [-run] [-interp=[TR]] [-arg=...] package... +Use -help flag to display options. + +Examples: +% ssadump -build=F hello.go # dump SSA form of a single package +% ssadump -build=F -test fmt # dump SSA form of a package and its tests +% ssadump -run -interp=T hello.go # interpret a program, with tracing + +The -run flag causes ssadump to build the code in a runnable form and run the first +package named main. + +Interpretation of the standard "testing" package is no longer supported. +` + +func main() { + if err := doMain(); err != nil { + fmt.Fprintf(os.Stderr, "ssadump: %s\n", err) + os.Exit(1) + } +} + +func doMain() error { + flag.Parse() + if len(flag.Args()) == 0 { + fmt.Fprint(os.Stderr, usage) + os.Exit(1) + } + + cfg := &packages.Config{ + Mode: loadSyntax, + Tests: *testFlag, + } + + // Choose types.Sizes from conf.Build. + // TODO(adonovan): remove this when go/packages provides a better way. + var wordSize int64 = 8 + switch build.Default.GOARCH { + case "386", "arm": + wordSize = 4 + } + sizes := &types.StdSizes{ + MaxAlign: 8, + WordSize: wordSize, + } + + var interpMode interp.Mode + for _, c := range *interpFlag { + switch c { + case 'T': + interpMode |= interp.EnableTracing + case 'R': + interpMode |= interp.DisableRecover + default: + return fmt.Errorf("unknown -interp option: '%c'", c) + } + } + + // Profiling support. + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + // Load, parse and type-check the initial packages, + // and, if -run, their dependencies. + if *runFlag { + cfg.Mode = loadSyntax | packages.NeedDeps + } + initial, err := packages.Load(cfg, flag.Args()...) + if err != nil { + return err + } + if len(initial) == 0 { + return fmt.Errorf("no packages") + } + if packages.PrintErrors(initial) > 0 { + return fmt.Errorf("packages contain errors") + } + + // Turn on instantiating generics during build if the program will be run. + if *runFlag { + mode |= ssa.InstantiateGenerics + } + + // Create SSA-form program representation. + prog, pkgs := ssautil.AllPackages(initial, mode) + + for i, p := range pkgs { + if p == nil { + return fmt.Errorf("cannot build SSA for package %s", initial[i]) + } + } + + if !*runFlag { + // Build and display only the initial packages + // (and synthetic wrappers). + for _, p := range pkgs { + p.Build() + } + + } else { + // Run the interpreter. + // Build SSA for all packages. + prog.Build() + + // Earlier versions of the interpreter needed the runtime + // package; however, interp cannot handle unsafe constructs + // used during runtime's package initialization at the moment. + // The key construct blocking support is: + // *((*T)(unsafe.Pointer(p))) + // Unfortunately, this means only trivial programs can be + // interpreted by ssadump. + if prog.ImportedPackage("runtime") != nil { + return fmt.Errorf("-run: program depends on runtime package (interpreter can run only trivial programs)") + } + + if runtime.GOARCH != build.Default.GOARCH { + return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)", + build.Default.GOARCH, runtime.GOARCH) + } + + // Run first main package. + for _, main := range ssautil.MainPackages(pkgs) { + fmt.Fprintf(os.Stderr, "Running: %s\n", main.Pkg.Path()) + os.Exit(interp.Interpret(main, interpMode, sizes, main.Pkg.Path(), args)) + } + return fmt.Errorf("no main package") + } + return nil +} + +// stringListValue is a flag.Value that accumulates strings. +// e.g. --flag=one --flag=two would produce []string{"one", "two"}. +type stringListValue []string + +func (ss *stringListValue) Get() interface{} { return []string(*ss) } + +func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) } + +func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil } diff --git a/cmd/internal/gen/gen.go b/cmd/internal/gen/gen.go deleted file mode 100644 index a5c63883..00000000 --- a/cmd/internal/gen/gen.go +++ /dev/null @@ -1,95 +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 build implements the “llgo gen command. -package gen - -import ( - "fmt" - "log" - "os" - "reflect" - - "github.com/goplus/llgo" - "github.com/goplus/llgo/cl" - "github.com/goplus/llgo/cmd/internal/base" - "github.com/goplus/llgo/internal/projs" - "github.com/goplus/llgo/ssa" - "github.com/qiniu/x/errors" -) - -// llgo gen -var Cmd = &base.Command{ - UsageLine: "llgo gen [-v] [packages|files]", - Short: "Convert Go code into LLVM ir (*.ll) code", -} - -var ( - flag = &Cmd.Flag - flagVerbose = flag.Bool("v", false, "print verbose information") -) - -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) - } - pattern := flag.Args() - if len(pattern) == 0 { - pattern = []string{"."} - } - - projects, err := projs.ParseAll(pattern...) - if err != nil { - log.Panicln("gopprojs.ParseAll:", err) - } - - ssa.Initialize(ssa.InitAll) - if *flagVerbose { - ssa.SetDebug(ssa.DbgFlagAll) - cl.SetDebug(cl.DbgFlagAll) - } - - for _, proj := range projects { - switch v := proj.(type) { - case *projs.DirProj: - _, _, err = llgo.Gen(v.Dir, nil, true, 0) - case *projs.PkgPathProj: - _, _, err = llgo.GenPkgPath("", v.Path, nil, true, 0) - case *projs.FilesProj: - _, err = llgo.GenFiles("", v.Files, nil) - default: - log.Panicln("`llgo gen` doesn't support", reflect.TypeOf(v)) - } - if err != nil { - fmt.Fprintf(os.Stderr, "llgo gen failed: %d errors.\n", errorNum(err)) - os.Exit(1) - } - } -} - -func errorNum(err error) int { - if e, ok := err.(errors.List); ok { - return len(e) - } - return 1 -} - -// ----------------------------------------------------------------------------- diff --git a/cmd/llgo/llgo.go b/cmd/llgo/llgo.go index 14049b6d..7e22a168 100644 --- a/cmd/llgo/llgo.go +++ b/cmd/llgo/llgo.go @@ -26,7 +26,6 @@ import ( "github.com/goplus/llgo/cmd/internal/base" "github.com/goplus/llgo/cmd/internal/build" - "github.com/goplus/llgo/cmd/internal/gen" "github.com/goplus/llgo/cmd/internal/help" ) @@ -39,7 +38,6 @@ func init() { flag.Usage = mainUsage base.Llgo.Commands = []*base.Command{ build.Cmd, - gen.Cmd, } } diff --git a/gen.go b/gen.go index b033c9d2..262f1298 100644 --- a/gen.go +++ b/gen.go @@ -16,11 +16,74 @@ 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) { - panic("todo") + 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. @@ -32,3 +95,4 @@ func GenPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool, flags G func GenFiles(autogen string, files []string, conf *Config) (outFiles []string, err error) { panic("todo") } +*/