Introduce llgoi, a REPL for Go

llgoi is a Go REPL based on llgo irgen and the LLVM JIT. It supports
expressions, statements, most declarations and imports, including binary
imports from the standard library and source imports from $GOPATH.

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

llvm-svn: 226097
GitOrigin-RevId: 6725a83e84a5e6e6ebc8db6fe9199a431e045d79
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b4ec074..9a93a4b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,12 +39,6 @@
   ssaopt/esc.go
 )
 
-install(FILES ${CMAKE_BINARY_DIR}/bin/llgo${CMAKE_EXECUTABLE_SUFFIX}
-        DESTINATION bin
-        PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
-                    GROUP_READ GROUP_EXECUTE
-                    WORLD_READ WORLD_EXECUTE)
-
 llvm_add_go_executable(llgo-stage2 llvm.org/llgo/cmd/gllgo
   DEPENDS libgo ${CMAKE_BINARY_DIR}/bin/llgo${CMAKE_EXECUTABLE_SUFFIX}
   GOFLAGS "cc=${CMAKE_BINARY_DIR}/bin/clang"
@@ -63,6 +57,22 @@
   cmd/cc-wrapper/main.go
 )
 
+llvm_add_go_executable(llgoi llvm.org/llgo/cmd/llgoi
+  DEPENDS libgo ${CMAKE_BINARY_DIR}/bin/llgo${CMAKE_EXECUTABLE_SUFFIX}
+          cmd/llgoi/isatty_posix.go
+          cmd/llgoi/llgoi.go
+  GOFLAGS "cc=${CMAKE_BINARY_DIR}/bin/clang"
+          "cxx=${CMAKE_BINARY_DIR}/bin/clang++"
+          "llgo=${CMAKE_BINARY_DIR}/bin/llgo${CMAKE_EXECUTABLE_SUFFIX}"
+)
+
+install(FILES ${CMAKE_BINARY_DIR}/bin/llgo${CMAKE_EXECUTABLE_SUFFIX}
+              ${CMAKE_BINARY_DIR}/bin/llgoi${CMAKE_EXECUTABLE_SUFFIX}
+        DESTINATION bin
+        PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
+                    GROUP_READ GROUP_EXECUTE
+                    WORLD_READ WORLD_EXECUTE)
+
 function(add_clobber_steps name)
   ExternalProject_Add_Step(${name} force-reconfigure
     DEPENDERS configure
diff --git a/cmd/llgoi/isatty_posix.go b/cmd/llgoi/isatty_posix.go
new file mode 100644
index 0000000..69a4b5c
--- /dev/null
+++ b/cmd/llgoi/isatty_posix.go
@@ -0,0 +1,29 @@
+//===- isatty_posix.go - isatty implementation for POSIX ------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// Implements isatty for POSIX systems.
+//
+//===----------------------------------------------------------------------===//
+
+package main
+
+// +build !windows
+
+/*
+#include <unistd.h>
+*/
+import "C"
+
+import (
+	"os"
+)
+
+func isatty(file *os.File) bool {
+	return C.isatty(C.int(file.Fd())) != 0
+}
diff --git a/cmd/llgoi/llgoi.go b/cmd/llgoi/llgoi.go
new file mode 100644
index 0000000..e8968f8
--- /dev/null
+++ b/cmd/llgoi/llgoi.go
@@ -0,0 +1,524 @@
+//===- 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 (
+	"bufio"
+	"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/llvm/bindings/go/llvm"
+)
+
+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
+
+	pendingLine line
+
+	copts irgen.CompilerOptions
+
+	imports []*types.Package
+	scope   map[string]types.Object
+
+	pkgmap, inputPkgmap 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,
+	}
+	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 := in.inputPkgmap[pkgpath]; ok {
+			return pkg, nil
+		}
+		return origImporter(pkgmap, pkgpath)
+	}
+	return nil
+}
+
+func (in *interp) init() error {
+	in.scope = make(map[string]types.Object)
+	in.pkgmap = make(map[string]*types.Package)
+	in.inputPkgmap = make(map[string]*types.Package)
+
+	err := in.makeCompilerOptions()
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (in *interp) dispose() {
+	in.engine.Dispose()
+}
+
+func (in *interp) loadSourcePackageFromCode(pkgcode, pkgpath string, copts irgen.CompilerOptions) (*types.Package, error) {
+	fset := token.NewFileSet()
+	file, err := parser.ParseFile(fset, "<input>", 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) (pkg *types.Package, err error) {
+	compiler, err := irgen.NewCompiler(copts)
+	if err != nil {
+		return
+	}
+
+	module, err := compiler.Compile(fset, files, pkgpath)
+	if err != nil {
+		return
+	}
+	pkg = module.Package
+
+	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
+		}
+	}
+
+	importname := irgen.ManglePackagePath(pkgpath) + "..import$descriptor"
+	importglobal := module.Module.NamedGlobal(importname)
+
+	var importfunc func()
+	*(*unsafe.Pointer)(unsafe.Pointer(&importfunc)) = in.engine.PointerToGlobal(importglobal)
+
+	defer func() {
+		p := recover()
+		if p != nil {
+			err = fmt.Errorf("panic: %v\n%v", p, string(debug.Stack()))
+		}
+	}()
+	importfunc()
+	in.inputPkgmap[pkgpath] = pkg
+	return
+}
+
+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) error {
+	in.pendingLine.append(str, assigns)
+
+	if in.pendingLine.ready() {
+		err := in.interpretLine(in.pendingLine)
+		in.pendingLine = line{}
+		return err
+	} else {
+		return nil
+	}
+}
+
+func (in *interp) interpretLine(l line) 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 err
+		}
+	}
+
+	var code bytes.Buffer
+	fmt.Fprintf(&code, "package %s", pkgname)
+	code.WriteString("\n\nimport __fmt__ \"fmt\"\n")
+	code.WriteString("import __os__ \"os\"\n")
+
+	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 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\n", l.line)
+
+		code.WriteString("func init() {\n\t")
+		for i, t := range typs {
+			var varname, prefix 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]
+				prefix = l.assigns[i]
+			} else {
+				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)
+			}
+		}
+		code.WriteString("}")
+	} else {
+		if len(l.assigns) != 0 {
+			return 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 err
+	}
+
+	in.imports = append(in.imports, pkg)
+
+	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 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
+	}
+
+	return true, in.readExprLine(line[int(pos)-base+2:], assigns)
+}
+
+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
+		}
+	}
+
+	fmt.Printf("# %s\n", pkgpath)
+
+	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)
+}
+
+func (in *interp) readLine(line string) 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
+
+	case token.IMPORT:
+		_, tok, lit = s.Scan()
+		if tok != token.STRING {
+			return errors.New("expected string literal")
+		}
+		pkgpath, err := strconv.Unquote(lit)
+		if err != nil {
+			return err
+		}
+		pkg, err := in.loadPackage(pkgpath)
+		if err != nil {
+			return err
+		}
+		in.imports = append(in.imports, pkg)
+		return nil
+
+	case token.IDENT:
+		ok, err := in.maybeReadAssignment(line, &s, lit, file.Base())
+		if err != nil {
+			return err
+		}
+		if ok {
+			return nil
+		}
+
+		fallthrough
+
+	default:
+		return in.readExprLine(line, nil)
+	}
+}
+
+func main() {
+	llvm.LinkInMCJIT()
+	llvm.InitializeNativeTarget()
+	llvm.InitializeNativeAsmPrinter()
+
+	var in interp
+	err := in.init()
+	if err != nil {
+		panic(err)
+	}
+	defer in.dispose()
+
+	tty := isatty(os.Stdin)
+
+	r := bufio.NewReader(os.Stdin)
+	for {
+		if tty {
+			if in.pendingLine.ready() {
+				os.Stdout.WriteString("(llgo) ")
+			} else {
+				os.Stdout.WriteString("       ")
+			}
+		}
+		line, err := r.ReadString('\n')
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			panic(err)
+		}
+
+		err = in.readLine(line)
+		if err != nil {
+			fmt.Println(err)
+		}
+	}
+
+	if tty {
+		os.Stdout.WriteString("\n")
+	}
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 641ade8..c8a346f 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -9,6 +9,7 @@
     FileCheck
     count
     llgo
+    llgoi
     libgo
     not
   )
diff --git a/test/lit.cfg b/test/lit.cfg
index e4639ab..a94e33e 100644
--- a/test/lit.cfg
+++ b/test/lit.cfg
@@ -3,13 +3,14 @@
 import sys
 
 config.name = 'llgo'
-config.suffixes = ['.go']
+config.suffixes = ['.go', '.test']
 config.test_format = lit.formats.ShTest()
 config.test_source_root = config.llvm_src_root + '/tools/llgo/test'
 config.test_exec_root = config.llvm_obj_root + '/tools/llgo/test'
 config.excludes = ['Inputs']
 
 config.substitutions.append((r"\bllgo\b", config.llvm_obj_root + '/bin/llgo -static-libgo'))
+config.substitutions.append((r"\bllgoi\b", config.llvm_obj_root + '/bin/llgoi'))
 config.substitutions.append((r"\bFileCheck\b", config.llvm_obj_root + '/bin/FileCheck'))
 config.substitutions.append((r"\bcount\b", config.llvm_obj_root + '/bin/count'))
 config.substitutions.append((r"\bnot\b", config.llvm_obj_root + '/bin/not'))
diff --git a/test/llgoi/Inputs/src/bar/answer.go b/test/llgoi/Inputs/src/bar/answer.go
new file mode 100644
index 0000000..7f1bd02
--- /dev/null
+++ b/test/llgoi/Inputs/src/bar/answer.go
@@ -0,0 +1,5 @@
+package bar
+
+func Answer() int {
+	return 42
+}
diff --git a/test/llgoi/Inputs/src/foo/answer.go b/test/llgoi/Inputs/src/foo/answer.go
new file mode 100644
index 0000000..1593e36
--- /dev/null
+++ b/test/llgoi/Inputs/src/foo/answer.go
@@ -0,0 +1,7 @@
+package foo
+
+import "bar"
+
+func Answer() int {
+	return bar.Answer()
+}
diff --git a/test/llgoi/Inputs/src/foo_cgo/answer.go b/test/llgoi/Inputs/src/foo_cgo/answer.go
new file mode 100644
index 0000000..5fe8a31
--- /dev/null
+++ b/test/llgoi/Inputs/src/foo_cgo/answer.go
@@ -0,0 +1,8 @@
+package foo_cgo
+
+// #include <stdint.h>
+import "C"
+
+func Answer() C.uint64_t {
+	return 42
+}
diff --git a/test/llgoi/arith.test b/test/llgoi/arith.test
new file mode 100644
index 0000000..83b30d9
--- /dev/null
+++ b/test/llgoi/arith.test
@@ -0,0 +1,4 @@
+// RUN: llgoi < %s | FileCheck %s
+
+1 + 1
+// CHECK: #0 untyped int = 2
diff --git a/test/llgoi/import-binary.test b/test/llgoi/import-binary.test
new file mode 100644
index 0000000..542054e
--- /dev/null
+++ b/test/llgoi/import-binary.test
@@ -0,0 +1,5 @@
+// RUN: llgoi < %s | FileCheck %s
+
+import "fmt"
+fmt.Println(1, "two", 3)
+// CHECK: 1 two 3
diff --git a/test/llgoi/import-source.test b/test/llgoi/import-source.test
new file mode 100644
index 0000000..22cf574
--- /dev/null
+++ b/test/llgoi/import-source.test
@@ -0,0 +1,14 @@
+// RUN: env GOPATH=%S/Inputs llgoi < %s | FileCheck %s
+
+// make sure user symbols do not conflict with imported source package
+Answer := 1
+
+import "foo"
+// CHECK: # bar
+// CHECK: # foo
+
+foo.Answer()
+// CHECK: #0 int = 42
+
+import "foo_cgo"
+// CHECK: foo_cgo: cannot load cgo package
diff --git a/test/llgoi/interfaces.test b/test/llgoi/interfaces.test
new file mode 100644
index 0000000..bc12b09
--- /dev/null
+++ b/test/llgoi/interfaces.test
@@ -0,0 +1,19 @@
+// RUN: llgoi < %s | FileCheck %s
+
+import "errors"
+err := errors.New("foo")
+// CHECK: err error {{.*}} = foo
+
+err.(interface{Foo()})
+// CHECK: panic: interface conversion
+
+_, _ := err.(interface{Foo()})
+// CHECK: #0 interface{Foo()} (<nil>) = <nil>
+// CHECK: #1 bool = false
+
+err.(interface{Error() string})
+// CHECK: #0 interface{Error() string} {{.*}} = foo
+
+_, _ := err.(interface{Error() string})
+// CHECK: #0 interface{Error() string} {{.*}} = foo
+// CHECK: #1 bool = true
diff --git a/test/llgoi/maps.test b/test/llgoi/maps.test
new file mode 100644
index 0000000..8678104
--- /dev/null
+++ b/test/llgoi/maps.test
@@ -0,0 +1,22 @@
+// RUN: llgoi < %s | FileCheck %s
+
+m := make(map[int]int)
+// CHECK: m map[int]int = map[]
+
+m[0]
+// CHECK: #0 int = 0
+
+_, _ := m[0]
+// CHECK: #0 int = 0
+// CHECK: #1 bool = false
+
+func() {
+	m[0] = 1
+}()
+
+m[0]
+// CHECK: #0 int = 1
+
+_, _ := m[0]
+// CHECK: #0 int = 1
+// CHECK: #1 bool = true
diff --git a/test/llgoi/panic.test b/test/llgoi/panic.test
new file mode 100644
index 0000000..65ecd7b
--- /dev/null
+++ b/test/llgoi/panic.test
@@ -0,0 +1,16 @@
+// RUN: llgoi < %s | FileCheck %s
+
+panic("msg")
+// CHECK: panic: msg
+
+import "fmt"
+func() {
+	defer func() {
+		r := recover()
+		fmt.Println("recovered", r)
+	}()
+	panic("msg")
+}()
+// CHECK-NOT: {{^}}panic:
+// CHECK: recovered msg
+// CHECK-NOT: {{^}}panic:
diff --git a/test/llgoi/vars.test b/test/llgoi/vars.test
new file mode 100644
index 0000000..1e1f063
--- /dev/null
+++ b/test/llgoi/vars.test
@@ -0,0 +1,29 @@
+// RUN: llgoi < %s 2>&1 | FileCheck %s
+
+x := 3
+// CHECK: x untyped int = 3
+
+x + x
+// CHECK: #0 int = 6
+
+x * x
+// CHECK: #0 int = 9
+
+x = 4
+x + x
+// CHECK: #0 int = 8
+
+x := true
+// CHECK: cannot assign {{.*}} to x (variable of type int)
+
+x, y := func() (int, int) {
+	return 1, 2
+}()
+// CHECK: x int = 1
+// CHECK: y int = 2
+
+x, _ = func() (int, int) {
+	return 3, 4
+}()
+x
+// CHECK: #0 int = 3