[llgo] llgoi: separate evaluation from printing

Summary:
Separate the evaluation of expressions from printing
of results. This is in preparation for splitting the
core of the interpreter out for use in alternative
interpreter frontends.

At the same time, the output is made less noisy in
response to comments on the golang-nuts announcement.
We would ideally print out values using Go syntax,
but this is impractical until we have libgo based on
Go 1.5. When that happens, fmt's %#v will handle
reflect.Value better, and so we can fix/filter type
names to remove automatically generated package names.

Reviewers: pcc

Subscribers: llvm-commits, axw

Differential Revision: http://reviews.llvm.org/D13761

llvm-svn: 267374
This commit is contained in:
Andrew Wilkins 2016-04-25 01:18:20 +00:00
parent 61a14911b2
commit bfb1679603
7 changed files with 124 additions and 94 deletions

View File

@ -83,8 +83,9 @@ type interp struct {
imports []*types.Package imports []*types.Package
scope map[string]types.Object scope map[string]types.Object
pkgmap map[string]*types.Package modules map[string]llvm.Module
pkgnum int pkgmap map[string]*types.Package
pkgnum int
} }
func (in *interp) makeCompilerOptions() error { func (in *interp) makeCompilerOptions() error {
@ -119,6 +120,7 @@ func (in *interp) init() error {
in.liner = liner.NewLiner() in.liner = liner.NewLiner()
in.scope = make(map[string]types.Object) in.scope = make(map[string]types.Object)
in.pkgmap = make(map[string]*types.Package) in.pkgmap = make(map[string]*types.Package)
in.modules = make(map[string]llvm.Module)
err := in.makeCompilerOptions() err := in.makeCompilerOptions()
if err != nil { if err != nil {
@ -139,23 +141,21 @@ func (in *interp) loadSourcePackageFromCode(pkgcode, pkgpath string, copts irgen
if err != nil { if err != nil {
return nil, err return nil, err
} }
files := []*ast.File{file} files := []*ast.File{file}
return in.loadSourcePackage(fset, files, pkgpath, copts) return in.loadSourcePackage(fset, files, pkgpath, copts)
} }
func (in *interp) loadSourcePackage(fset *token.FileSet, files []*ast.File, pkgpath string, copts irgen.CompilerOptions) (pkg *types.Package, err error) { func (in *interp) loadSourcePackage(fset *token.FileSet, files []*ast.File, pkgpath string, copts irgen.CompilerOptions) (_ *types.Package, resultErr error) {
compiler, err := irgen.NewCompiler(copts) compiler, err := irgen.NewCompiler(copts)
if err != nil { if err != nil {
return return nil, err
} }
module, err := compiler.Compile(fset, files, pkgpath) module, err := compiler.Compile(fset, files, pkgpath)
if err != nil { if err != nil {
return return nil, err
} }
pkg = module.Package in.modules[pkgpath] = module.Module
if in.engine.C != nil { if in.engine.C != nil {
in.engine.AddModule(module.Module) in.engine.AddModule(module.Module)
@ -163,25 +163,33 @@ func (in *interp) loadSourcePackage(fset *token.FileSet, files []*ast.File, pkgp
options := llvm.NewMCJITCompilerOptions() options := llvm.NewMCJITCompilerOptions()
in.engine, err = llvm.NewMCJITCompiler(module.Module, options) in.engine, err = llvm.NewMCJITCompiler(module.Module, options)
if err != nil { if err != nil {
return return nil, err
} }
} }
importname := irgen.ManglePackagePath(pkgpath) + "..import$descriptor" var importFunc func()
importglobal := module.Module.NamedGlobal(importname) importAddress := in.getPackageSymbol(pkgpath, ".import$descriptor")
*(*unsafe.Pointer)(unsafe.Pointer(&importFunc)) = importAddress
var importfunc func()
*(*unsafe.Pointer)(unsafe.Pointer(&importfunc)) = in.engine.PointerToGlobal(importglobal)
defer func() { defer func() {
p := recover() p := recover()
if p != nil { if p != nil {
err = fmt.Errorf("panic: %v\n%v", p, string(debug.Stack())) resultErr = fmt.Errorf("panic: %v\n%v", p, string(debug.Stack()))
} }
}() }()
importfunc() importFunc()
in.pkgmap[pkgpath] = pkg in.pkgmap[pkgpath] = module.Package
return
return module.Package, nil
}
func (in *interp) getPackageSymbol(pkgpath, name string) unsafe.Pointer {
symbolName := irgen.ManglePackagePath(pkgpath) + "." + name
global := in.modules[pkgpath].NamedGlobal(symbolName)
if global.IsNil() {
return nil
}
return in.engine.PointerToGlobal(global)
} }
func (in *interp) augmentPackageScope(pkg *types.Package) { func (in *interp) augmentPackageScope(pkg *types.Package) {
@ -246,19 +254,17 @@ func (l *line) ready() bool {
return l.parens <= 0 && l.bracks <= 0 && l.braces <= 0 return l.parens <= 0 && l.bracks <= 0 && l.braces <= 0
} }
func (in *interp) readExprLine(str string, assigns []string) error { func (in *interp) readExprLine(str string, assigns []string) ([]interface{}, error) {
in.pendingLine.append(str, assigns) in.pendingLine.append(str, assigns)
if !in.pendingLine.ready() {
if in.pendingLine.ready() { return nil, nil
err := in.interpretLine(in.pendingLine)
in.pendingLine = line{}
return err
} else {
return nil
} }
results, err := in.interpretLine(in.pendingLine)
in.pendingLine = line{}
return results, err
} }
func (in *interp) interpretLine(l line) error { func (in *interp) interpretLine(l line) ([]interface{}, error) {
pkgname := fmt.Sprintf("input%05d", in.pkgnum) pkgname := fmt.Sprintf("input%05d", in.pkgnum)
in.pkgnum++ in.pkgnum++
@ -277,14 +283,12 @@ func (in *interp) interpretLine(l line) error {
var err error var err error
tv, err = types.Eval(l.line, pkg, scope) tv, err = types.Eval(l.line, pkg, scope)
if err != nil { if err != nil {
return err return nil, err
} }
} }
var code bytes.Buffer var code bytes.Buffer
fmt.Fprintf(&code, "package %s", pkgname) fmt.Fprintf(&code, "package %s\n", pkgname)
code.WriteString("\n\nimport __fmt__ \"fmt\"\n")
code.WriteString("import __os__ \"os\"\n")
for _, pkg := range in.imports { for _, pkg := range in.imports {
fmt.Fprintf(&code, "import %q\n", pkg.Path()) fmt.Fprintf(&code, "import %q\n", pkg.Path())
@ -306,7 +310,7 @@ func (in *interp) interpretLine(l line) error {
typs = append(typs, types.Typ[types.Bool]) typs = append(typs, types.Typ[types.Bool])
} }
if len(l.assigns) != 0 && len(l.assigns) != len(typs) { if len(l.assigns) != 0 && len(l.assigns) != len(typs) {
return errors.New("return value mismatch") return nil, errors.New("return value mismatch")
} }
code.WriteString("var ") code.WriteString("var ")
@ -324,31 +328,34 @@ func (in *interp) interpretLine(l line) error {
fmt.Fprintf(&code, "__llgoiV%d", i) fmt.Fprintf(&code, "__llgoiV%d", i)
} }
} }
fmt.Fprintf(&code, " = %s\n\n", l.line) fmt.Fprintf(&code, " = %s\n", l.line)
code.WriteString("func init() {\n\t") code.WriteString("func init() {\n")
for i, t := range typs { varnames := make([]string, len(typs))
var varname, prefix string for i := range typs {
var varname string
if len(l.assigns) != 0 && l.assigns[i] != "" { if len(l.assigns) != 0 && l.assigns[i] != "" {
if _, ok := in.scope[l.assigns[i]]; ok { if _, ok := in.scope[l.assigns[i]]; ok {
fmt.Fprintf(&code, "\t%s = __llgoiV%d\n", l.assigns[i], i) fmt.Fprintf(&code, "\t%s = __llgoiV%d\n", l.assigns[i], i)
} }
varname = l.assigns[i] varname = l.assigns[i]
prefix = l.assigns[i]
} else { } else {
varname = fmt.Sprintf("__llgoiV%d", i) varname = fmt.Sprintf("__llgoiV%d", i)
prefix = fmt.Sprintf("#%d", i)
}
if _, ok := t.Underlying().(*types.Interface); ok {
fmt.Fprintf(&code, "\t__fmt__.Printf(\"%s %s (%%T) = %%+v\\n\", %s, %s)\n", prefix, t.String(), varname, varname)
} else {
fmt.Fprintf(&code, "\t__fmt__.Printf(\"%s %s = %%+v\\n\", %s)\n", prefix, t.String(), varname)
} }
varnames[i] = varname
} }
code.WriteString("}") code.WriteString("}\n\n")
code.WriteString("func __llgoiResults() []interface{} {\n")
code.WriteString("\treturn []interface{}{\n")
for _, varname := range varnames {
fmt.Fprintf(&code, "\t\t%s,\n", varname)
}
code.WriteString("\t}\n")
code.WriteString("}\n")
} else { } else {
if len(l.assigns) != 0 { if len(l.assigns) != 0 {
return errors.New("return value mismatch") return nil, errors.New("return value mismatch")
} }
fmt.Fprintf(&code, "func init() {\n\t%s}", l.line) fmt.Fprintf(&code, "func init() {\n\t%s}", l.line)
@ -359,11 +366,18 @@ func (in *interp) interpretLine(l line) error {
copts.DisableUnusedImportCheck = true copts.DisableUnusedImportCheck = true
pkg, err := in.loadSourcePackageFromCode(code.String(), pkgname, copts) pkg, err := in.loadSourcePackageFromCode(code.String(), pkgname, copts)
if err != nil { if err != nil {
return err return nil, err
} }
in.imports = append(in.imports, pkg) in.imports = append(in.imports, pkg)
var results []interface{}
llgoiResultsAddress := in.getPackageSymbol(pkgname, "__llgoiResults$descriptor")
if llgoiResultsAddress != nil {
var resultsFunc func() []interface{}
*(*unsafe.Pointer)(unsafe.Pointer(&resultsFunc)) = llgoiResultsAddress
results = resultsFunc()
}
for _, assign := range l.assigns { for _, assign := range l.assigns {
if assign != "" { if assign != "" {
if _, ok := in.scope[assign]; !ok { if _, ok := in.scope[assign]; !ok {
@ -376,7 +390,7 @@ func (in *interp) interpretLine(l line) error {
in.scope[l.declName] = pkg.Scope().Lookup(l.declName) in.scope[l.declName] = pkg.Scope().Lookup(l.declName)
} }
return nil return results, nil
} }
func (in *interp) maybeReadAssignment(line string, s *scanner.Scanner, initial string, base int) (bool, error) { func (in *interp) maybeReadAssignment(line string, s *scanner.Scanner, initial string, base int) (bool, error) {
@ -404,7 +418,9 @@ func (in *interp) maybeReadAssignment(line string, s *scanner.Scanner, initial s
return false, nil return false, nil
} }
return true, in.readExprLine(line[int(pos)-base+2:], assigns) // It's an assignment statement, there are no results.
_, err := in.readExprLine(line[int(pos)-base+2:], assigns)
return true, err
} }
func (in *interp) loadPackage(pkgpath string) (*types.Package, error) { func (in *interp) loadPackage(pkgpath string) (*types.Package, error) {
@ -428,8 +444,6 @@ func (in *interp) loadPackage(pkgpath string) (*types.Package, error) {
} }
} }
fmt.Printf("# %s\n", pkgpath)
inputs := make([]string, len(buildpkg.GoFiles)) inputs := make([]string, len(buildpkg.GoFiles))
for i, file := range buildpkg.GoFiles { for i, file := range buildpkg.GoFiles {
inputs[i] = filepath.Join(buildpkg.Dir, file) inputs[i] = filepath.Join(buildpkg.Dir, file)
@ -446,7 +460,7 @@ func (in *interp) loadPackage(pkgpath string) (*types.Package, error) {
// readLine accumulates lines of input, including trailing newlines, // readLine accumulates lines of input, including trailing newlines,
// executing statements as they are completed. // executing statements as they are completed.
func (in *interp) readLine(line string) error { func (in *interp) readLine(line string) ([]interface{}, error) {
if !in.pendingLine.ready() { if !in.pendingLine.ready() {
return in.readExprLine(line, nil) return in.readExprLine(line, nil)
} }
@ -459,33 +473,32 @@ func (in *interp) readLine(line string) error {
_, tok, lit := s.Scan() _, tok, lit := s.Scan()
switch tok { switch tok {
case token.EOF: case token.EOF:
return nil return nil, nil
case token.IMPORT: case token.IMPORT:
_, tok, lit = s.Scan() _, tok, lit = s.Scan()
if tok != token.STRING { if tok != token.STRING {
return errors.New("expected string literal") return nil, errors.New("expected string literal")
} }
pkgpath, err := strconv.Unquote(lit) pkgpath, err := strconv.Unquote(lit)
if err != nil { if err != nil {
return err return nil, err
} }
pkg, err := in.loadPackage(pkgpath) pkg, err := in.loadPackage(pkgpath)
if err != nil { if err != nil {
return err return nil, err
} }
in.imports = append(in.imports, pkg) in.imports = append(in.imports, pkg)
return nil return nil, nil
case token.IDENT: case token.IDENT:
ok, err := in.maybeReadAssignment(line, &s, lit, file.Base()) ok, err := in.maybeReadAssignment(line, &s, lit, file.Base())
if err != nil { if err != nil {
return err return nil, err
} }
if ok { if ok {
return nil return nil, nil
} }
fallthrough fallthrough
default: default:
@ -493,6 +506,14 @@ func (in *interp) readLine(line string) error {
} }
} }
// printResult prints a value that was the result of an expression evaluated
// by the interpreter.
func printResult(w io.Writer, v interface{}) {
// TODO the result should be formatted in Go syntax, without
// package qualifiers for types defined within the interpreter.
fmt.Fprintf(w, "%+v", v)
}
// formatHistory reformats the provided Go source by collapsing all lines // formatHistory reformats the provided Go source by collapsing all lines
// and adding semicolons where required, suitable for adding to line history. // and adding semicolons where required, suitable for adding to line history.
func formatHistory(input []byte) string { func formatHistory(input []byte) string {
@ -560,10 +581,14 @@ func main() {
continue continue
} }
buf.WriteString(line + "\n") buf.WriteString(line + "\n")
err = in.readLine(line + "\n") results, err := in.readLine(line + "\n")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
} }
for _, result := range results {
printResult(os.Stdout, result)
fmt.Println()
}
} }
if liner.TerminalSupported() { if liner.TerminalSupported() {

View File

@ -1,4 +1,4 @@
// RUN: llgoi < %s | FileCheck %s // RUN: llgoi < %s | FileCheck %s
1 + 1 1 + 1
// CHECK: #0 untyped int = 2 // CHECK: 2

View File

@ -4,17 +4,15 @@
Answer := 1 Answer := 1
import "foo" import "foo"
// CHECK: # bar
// CHECK: # foo
// Test that importing binary after source works. // Test that importing binary after source works.
import "strconv" import "strconv"
foo.Answer() foo.Answer()
// CHECK: #0 int = 42 // CHECK: 42
strconv.FormatBool(true) strconv.FormatBool(true)
// CHECK: #0 string = true // CHECK: true
var v1 strconv.NumError var v1 strconv.NumError
var v2 strconv.NumError var v2 strconv.NumError

View File

@ -4,11 +4,9 @@
import "strconv" import "strconv"
import "foo" import "foo"
// CHECK: # bar
// CHECK: # foo
foo.Answer() foo.Answer()
// CHECK: #0 int = 42 // CHECK: 42
strconv.FormatBool(true) strconv.FormatBool(true)
// CHECK: #0 string = true // CHECK: true

View File

@ -2,18 +2,21 @@
import "errors" import "errors"
err := errors.New("foo") err := errors.New("foo")
// CHECK: err error {{.*}} = foo err
// CHECK: foo
err.(interface{Foo()}) err.(interface{Foo()})
// CHECK: panic: interface conversion // CHECK: panic: interface conversion
_, _ := err.(interface{Foo()}) _, ok := err.(interface{Foo()})
// CHECK: #0 interface{Foo()} (<nil>) = <nil> ok
// CHECK: #1 bool = false // CHECK: false
err.(interface{Error() string}) err.(interface{Error() string})
// CHECK: #0 interface{Error() string} {{.*}} = foo // CHECK: foo
_, _ := err.(interface{Error() string}) iface, ok := err.(interface{Error() string})
// CHECK: #0 interface{Error() string} {{.*}} = foo iface
// CHECK: #1 bool = true // CHECK: foo
ok
// CHECK: true

View File

@ -1,22 +1,27 @@
// RUN: llgoi < %s | FileCheck %s // RUN: llgoi < %s | FileCheck %s
m := make(map[int]int) m := make(map[int]int)
// CHECK: m map[int]int = map[] m
// CHECK: map[]
m[0] m[0]
// CHECK: #0 int = 0 // CHECK: 0
_, _ := m[0] m0, ok := m[0]
// CHECK: #0 int = 0 m0
// CHECK: #1 bool = false // CHECK: 0
ok
// CHECK: false
func() { func() {
m[0] = 1 m[0] = 1
}() }()
m[0] m[0]
// CHECK: #0 int = 1 // CHECK: 1
_, _ := m[0] m0, ok = m[0]
// CHECK: #0 int = 1 m0
// CHECK: #1 bool = true // CHECK: 1
ok
// CHECK: true

View File

@ -1,17 +1,18 @@
// RUN: llgoi < %s 2>&1 | FileCheck %s // RUN: llgoi < %s 2>&1 | FileCheck %s
x := 3 x := 3
// CHECK: x untyped int = 3 x
// CHECK: 3
x + x x + x
// CHECK: #0 int = 6 // CHECK: 6
x * x x * x
// CHECK: #0 int = 9 // CHECK: 9
x = 4 x = 4
x + x x + x
// CHECK: #0 int = 8 // CHECK: 8
x := true x := true
// CHECK: cannot assign {{.*}} to x (variable of type int) // CHECK: cannot assign {{.*}} to x (variable of type int)
@ -19,11 +20,11 @@ x := true
x, y := func() (int, int) { x, y := func() (int, int) {
return 1, 2 return 1, 2
}() }()
// CHECK: x int = 1 // CHECK: 1
// CHECK: y int = 2 // CHECK: 2
x, _ = func() (int, int) { x, _ = func() (int, int) {
return 3, 4 return 3, 4
}() }()
x x
// CHECK: #0 int = 3 // CHECK: 3