//===- llgoi.go - llgo-based Go REPL --------------------------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This is llgoi, a Go REPL based on llgo and the LLVM JIT. // //===----------------------------------------------------------------------===// package main import ( "bytes" "errors" "fmt" "go/ast" "go/build" "go/parser" "go/scanner" "go/token" "io" "os" "os/exec" "path/filepath" "runtime/debug" "strconv" "strings" "unsafe" "llvm.org/llgo/driver" "llvm.org/llgo/irgen" "llvm.org/llgo/third_party/gotools/go/types" "llvm.org/llgo/third_party/liner" "llvm.org/llvm/bindings/go/llvm" ) // /* Force exporting __morestack if it's available, so that it is // available to the engine when linking with libLLVM.so. */ // // void *__morestack __attribute__((weak)); import "C" func getInstPrefix() (string, error) { path, err := exec.LookPath(os.Args[0]) if err != nil { return "", err } path, err = filepath.EvalSymlinks(path) if err != nil { return "", err } prefix := filepath.Join(path, "..", "..") return prefix, nil } func llvmVersion() string { return strings.Replace(llvm.Version, "svn", "", 1) } type line struct { line string isStmt bool declName string assigns []string parens, bracks, braces int } type interp struct { engine llvm.ExecutionEngine liner *liner.State pendingLine line copts irgen.CompilerOptions imports []*types.Package scope map[string]types.Object modules map[string]llvm.Module pkgmap map[string]*types.Package pkgnum int } func (in *interp) makeCompilerOptions() error { prefix, err := getInstPrefix() if err != nil { return err } importPaths := []string{filepath.Join(prefix, "lib", "go", "llgo-"+llvmVersion())} in.copts = irgen.CompilerOptions{ TargetTriple: llvm.DefaultTargetTriple(), ImportPaths: importPaths, GenerateDebug: true, Packages: in.pkgmap, } err = in.copts.MakeImporter() if err != nil { return err } origImporter := in.copts.Importer in.copts.Importer = func(pkgmap map[string]*types.Package, pkgpath string) (*types.Package, error) { if pkg, ok := pkgmap[pkgpath]; ok && pkg.Complete() { return pkg, nil } return origImporter(pkgmap, pkgpath) } return nil } func (in *interp) init() error { in.liner = liner.NewLiner() in.scope = make(map[string]types.Object) in.pkgmap = make(map[string]*types.Package) in.modules = make(map[string]llvm.Module) err := in.makeCompilerOptions() if err != nil { return err } return nil } func (in *interp) dispose() { in.liner.Close() in.engine.Dispose() } func (in *interp) loadSourcePackageFromCode(pkgcode, pkgpath string, copts irgen.CompilerOptions) (*types.Package, error) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "", pkgcode, parser.DeclarationErrors|parser.ParseComments) if err != nil { return nil, err } files := []*ast.File{file} return in.loadSourcePackage(fset, files, pkgpath, copts) } func (in *interp) loadSourcePackage(fset *token.FileSet, files []*ast.File, pkgpath string, copts irgen.CompilerOptions) (_ *types.Package, resultErr error) { compiler, err := irgen.NewCompiler(copts) if err != nil { return nil, err } module, err := compiler.Compile(fset, files, pkgpath) if err != nil { return nil, err } in.modules[pkgpath] = module.Module if in.engine.C != nil { in.engine.AddModule(module.Module) } else { options := llvm.NewMCJITCompilerOptions() in.engine, err = llvm.NewMCJITCompiler(module.Module, options) if err != nil { return nil, err } } var importFunc func() importAddress := in.getPackageSymbol(pkgpath, ".import$descriptor") *(*unsafe.Pointer)(unsafe.Pointer(&importFunc)) = importAddress defer func() { p := recover() if p != nil { resultErr = fmt.Errorf("panic: %v\n%v", p, string(debug.Stack())) } }() importFunc() in.pkgmap[pkgpath] = module.Package 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) { for _, obj := range in.scope { pkg.Scope().Insert(obj) } } func (l *line) append(str string, assigns []string) { var s scanner.Scanner fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(str)) s.Init(file, []byte(str), nil, 0) _, tok, _ := s.Scan() if l.line == "" { switch tok { case token.FOR, token.GO, token.IF, token.LBRACE, token.SELECT, token.SWITCH: l.isStmt = true case token.CONST, token.FUNC, token.TYPE, token.VAR: var lit string _, tok, lit = s.Scan() if tok == token.IDENT { l.declName = lit } } } for tok != token.EOF { switch tok { case token.LPAREN: l.parens++ case token.RPAREN: l.parens-- case token.LBRACE: l.braces++ case token.RBRACE: l.braces-- case token.LBRACK: l.bracks++ case token.RBRACK: l.bracks-- case token.DEC, token.INC, token.ASSIGN, token.ADD_ASSIGN, token.SUB_ASSIGN, token.MUL_ASSIGN, token.QUO_ASSIGN, token.REM_ASSIGN, token.AND_ASSIGN, token.OR_ASSIGN, token.XOR_ASSIGN, token.SHL_ASSIGN, token.SHR_ASSIGN, token.AND_NOT_ASSIGN: if l.parens == 0 && l.bracks == 0 && l.braces == 0 { l.isStmt = true } } _, tok, _ = s.Scan() } if l.line == "" { l.assigns = assigns } l.line += str } func (l *line) ready() bool { return l.parens <= 0 && l.bracks <= 0 && l.braces <= 0 } func (in *interp) readExprLine(str string, assigns []string) ([]interface{}, error) { in.pendingLine.append(str, assigns) if !in.pendingLine.ready() { return nil, nil } results, err := in.interpretLine(in.pendingLine) in.pendingLine = line{} return results, err } func (in *interp) interpretLine(l line) ([]interface{}, error) { pkgname := fmt.Sprintf("input%05d", in.pkgnum) in.pkgnum++ pkg := types.NewPackage(pkgname, pkgname) scope := pkg.Scope() for _, imppkg := range in.imports { obj := types.NewPkgName(token.NoPos, pkg, imppkg.Name(), imppkg) scope.Insert(obj) } in.augmentPackageScope(pkg) var tv types.TypeAndValue if l.declName == "" && !l.isStmt { var err error tv, err = types.Eval(l.line, pkg, scope) if err != nil { return nil, err } } var code bytes.Buffer fmt.Fprintf(&code, "package %s\n", pkgname) for _, pkg := range in.imports { fmt.Fprintf(&code, "import %q\n", pkg.Path()) } if l.declName != "" { code.WriteString(l.line) } else if !l.isStmt && tv.IsValue() { var typs []types.Type if tuple, ok := tv.Type.(*types.Tuple); ok { typs = make([]types.Type, tuple.Len()) for i := range typs { typs[i] = tuple.At(i).Type() } } else { typs = []types.Type{tv.Type} } if len(l.assigns) == 2 && tv.HasOk() { typs = append(typs, types.Typ[types.Bool]) } if len(l.assigns) != 0 && len(l.assigns) != len(typs) { return nil, errors.New("return value mismatch") } code.WriteString("var ") for i := range typs { if i != 0 { code.WriteString(", ") } if len(l.assigns) != 0 && l.assigns[i] != "" { if _, ok := in.scope[l.assigns[i]]; ok { fmt.Fprintf(&code, "__llgoiV%d", i) } else { code.WriteString(l.assigns[i]) } } else { fmt.Fprintf(&code, "__llgoiV%d", i) } } fmt.Fprintf(&code, " = %s\n", l.line) code.WriteString("func init() {\n") varnames := make([]string, len(typs)) for i := range typs { var varname string if len(l.assigns) != 0 && l.assigns[i] != "" { if _, ok := in.scope[l.assigns[i]]; ok { fmt.Fprintf(&code, "\t%s = __llgoiV%d\n", l.assigns[i], i) } varname = l.assigns[i] } else { varname = fmt.Sprintf("__llgoiV%d", i) } varnames[i] = varname } 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 { if len(l.assigns) != 0 { return nil, errors.New("return value mismatch") } fmt.Fprintf(&code, "func init() {\n\t%s}", l.line) } copts := in.copts copts.PackageCreated = in.augmentPackageScope copts.DisableUnusedImportCheck = true pkg, err := in.loadSourcePackageFromCode(code.String(), pkgname, copts) if err != nil { return nil, err } 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 { if assign != "" { if _, ok := in.scope[assign]; !ok { in.scope[assign] = pkg.Scope().Lookup(assign) } } } if l.declName != "" { in.scope[l.declName] = pkg.Scope().Lookup(l.declName) } return results, nil } func (in *interp) maybeReadAssignment(line string, s *scanner.Scanner, initial string, base int) (bool, error) { if initial == "_" { initial = "" } assigns := []string{initial} pos, tok, lit := s.Scan() for tok == token.COMMA { pos, tok, lit = s.Scan() if tok != token.IDENT { return false, nil } if lit == "_" { lit = "" } assigns = append(assigns, lit) pos, tok, lit = s.Scan() } if tok != token.DEFINE { return false, nil } // 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) { pkg, err := in.copts.Importer(in.pkgmap, pkgpath) if err == nil { return pkg, nil } buildpkg, err := build.Import(pkgpath, ".", 0) if err != nil { return nil, err } if len(buildpkg.CgoFiles) != 0 { return nil, fmt.Errorf("%s: cannot load cgo package", pkgpath) } for _, imp := range buildpkg.Imports { _, err := in.loadPackage(imp) if err != nil { return nil, err } } inputs := make([]string, len(buildpkg.GoFiles)) for i, file := range buildpkg.GoFiles { inputs[i] = filepath.Join(buildpkg.Dir, file) } fset := token.NewFileSet() files, err := driver.ParseFiles(fset, inputs) if err != nil { return nil, err } return in.loadSourcePackage(fset, files, pkgpath, in.copts) } // readLine accumulates lines of input, including trailing newlines, // executing statements as they are completed. func (in *interp) readLine(line string) ([]interface{}, error) { if !in.pendingLine.ready() { return in.readExprLine(line, nil) } var s scanner.Scanner fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(line)) s.Init(file, []byte(line), nil, 0) _, tok, lit := s.Scan() switch tok { case token.EOF: return nil, nil case token.IMPORT: _, tok, lit = s.Scan() if tok != token.STRING { return nil, errors.New("expected string literal") } pkgpath, err := strconv.Unquote(lit) if err != nil { return nil, err } pkg, err := in.loadPackage(pkgpath) if err != nil { return nil, err } in.imports = append(in.imports, pkg) return nil, nil case token.IDENT: ok, err := in.maybeReadAssignment(line, &s, lit, file.Base()) if err != nil { return nil, err } if ok { return nil, nil } fallthrough default: return in.readExprLine(line, nil) } } // 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 // and adding semicolons where required, suitable for adding to line history. func formatHistory(input []byte) string { var buf bytes.Buffer var s scanner.Scanner fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(input)) s.Init(file, input, nil, 0) pos, tok, lit := s.Scan() for tok != token.EOF { if int(pos)-1 > buf.Len() { n := int(pos) - 1 - buf.Len() buf.WriteString(strings.Repeat(" ", n)) } var semicolon bool if tok == token.SEMICOLON { semicolon = true } else if lit != "" { buf.WriteString(lit) } else { buf.WriteString(tok.String()) } pos, tok, lit = s.Scan() if semicolon { switch tok { case token.RBRACE, token.RPAREN, token.EOF: default: buf.WriteRune(';') } } } return buf.String() } func main() { llvm.LinkInMCJIT() llvm.InitializeNativeTarget() llvm.InitializeNativeAsmPrinter() var in interp err := in.init() if err != nil { panic(err) } defer in.dispose() var buf bytes.Buffer for { if in.pendingLine.ready() && buf.Len() > 0 { history := formatHistory(buf.Bytes()) in.liner.AppendHistory(history) buf.Reset() } prompt := "(llgo) " if !in.pendingLine.ready() { prompt = strings.Repeat(" ", len(prompt)) } line, err := in.liner.Prompt(prompt) if err == io.EOF { break } else if err != nil { panic(err) } if line == "" { continue } buf.WriteString(line + "\n") results, err := in.readLine(line + "\n") if err != nil { fmt.Println(err) } for _, result := range results { printResult(os.Stdout, result) fmt.Println() } } if liner.TerminalSupported() { fmt.Println() } }