Этот коммит содержится в:
Ayke van Laethem 2018-04-11 16:17:14 +02:00
коммит bc28975c8c
4 изменённых файлов: 257 добавлений и 0 удалений

1
.gitignore предоставленный Обычный файл
Просмотреть файл

@ -0,0 +1 @@
build

22
Makefile Обычный файл
Просмотреть файл

@ -0,0 +1,22 @@
all: tgo
tgo: build/tgo
test: build/hello.o
.PHONY: all tgo test test-run clean
build/tgo: *.go
@mkdir -p build
@go build -o build/tgo -i .
build/hello.o: build/tgo hello/hello.go
@./build/tgo -printir -target x86_64-pc-linux-gnu -o build/hello.o hello/hello.go
build/hello: build/hello.o
@clang -o build/hello build/hello.o
test-run: build/hello
@./build/hello
clean:
@rm -rf build

6
hello/hello.go Обычный файл
Просмотреть файл

@ -0,0 +1,6 @@
package main
func main() {
println("Hello world from Go!")
}

228
tgo.go Обычный файл
Просмотреть файл

@ -0,0 +1,228 @@
package main
import (
"errors"
"flag"
"fmt"
"go/ast"
"go/constant"
"go/parser"
"go/token"
"os"
"llvm.org/llvm/bindings/go/llvm"
)
func init() {
llvm.InitializeAllTargets()
llvm.InitializeAllTargetMCs()
llvm.InitializeAllTargetInfos()
llvm.InitializeAllAsmParsers()
llvm.InitializeAllAsmPrinters()
}
type Compiler struct {
mod llvm.Module
ctx llvm.Context
builder llvm.Builder
machine llvm.TargetMachine
putsFunc llvm.Value
}
func NewCompiler(path, triplet string) (*Compiler, error) {
c := &Compiler{}
target, err := llvm.GetTargetFromTriple(triplet)
if err != nil {
return nil, err
}
c.machine = target.CreateTargetMachine(triplet, "", "", llvm.CodeGenLevelDefault, llvm.RelocDefault, llvm.CodeModelDefault)
c.mod = llvm.NewModule(path)
c.ctx = c.mod.Context()
c.builder = c.ctx.NewBuilder()
putsType := llvm.FunctionType(llvm.Int32Type(), []llvm.Type{llvm.PointerType(llvm.Int8Type(), 0)}, false)
c.putsFunc = llvm.AddFunction(c.mod, "puts", putsType)
mainType := llvm.FunctionType(llvm.Int32Type(), nil, false)
mainFunc := llvm.AddFunction(c.mod, "main", mainType)
start := c.ctx.AddBasicBlock(mainFunc, "start")
c.builder.SetInsertPointAtEnd(start)
return c, nil
}
func (c *Compiler) Parse(path string) error {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, path, nil, 0)
if err != nil {
return err
}
fmt.Println("package:", file.Name.Name)
for _, decl := range file.Decls {
switch v := decl.(type) {
case *ast.FuncDecl:
err := c.parseFunc(v)
if err != nil {
return err
}
default:
return errors.New("unknown declaration")
}
}
c.builder.CreateRet(llvm.ConstInt(llvm.Int32Type(), 0, false))
return nil
}
func (c *Compiler) IR() string {
return c.mod.String()
}
func (c *Compiler) parseFunc(f *ast.FuncDecl) error {
fmt.Println("func:", f.Name)
// TODO: external functions
for _, stmt := range f.Body.List {
err := c.parseStmt(stmt)
if err != nil {
return err
}
}
return nil
}
func (c *Compiler) parseStmt(stmt ast.Node) error {
switch v := stmt.(type) {
case *ast.ExprStmt:
err := c.parseExpr(v.X)
if err != nil {
return err
}
default:
return errors.New("unknown stmt")
}
return nil
}
func (c *Compiler) parseExpr(expr ast.Expr) error {
switch v := expr.(type) {
case *ast.CallExpr:
values := []llvm.Value{}
zero := llvm.ConstInt(llvm.Int32Type(), 0, false)
name := v.Fun.(*ast.Ident).Name
fmt.Printf(" call: %s\n", name)
if name != "println" {
return errors.New("implement anything other than println()")
}
for _, arg := range v.Args {
switch arg := arg.(type) {
case *ast.BasicLit:
fmt.Printf(" arg: %s\n", arg.Value)
val := constant.MakeFromLiteral(arg.Value, arg.Kind, 0)
switch arg.Kind {
case token.STRING:
msg := c.builder.CreateGlobalString(constant.StringVal(val) + "\x00", "")
msgCast := c.builder.CreateGEP(msg, []llvm.Value{zero, zero}, "")
values = append(values, msgCast)
default:
return errors.New("todo: print anything other than strings")
}
default:
return errors.New("unknown arg type")
}
}
c.builder.CreateCall(c.putsFunc, values, "")
default:
return errors.New("unknown expr")
}
return nil
}
func (c *Compiler) Verify() error {
return llvm.VerifyModule(c.mod, llvm.PrintMessageAction)
}
func (c *Compiler) Optimize(optLevel int) {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.UseInlinerWithThreshold(200) // TODO depend on opt level, and -Os
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
builder.PopulateFunc(funcPasses)
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(c.mod)
}
func (c *Compiler) EmitObject(path string) error {
buf, err := c.machine.EmitToMemoryBuffer(c.mod, llvm.ObjectFile)
if err != nil {
return err
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
f.Write(buf.Bytes())
f.Close()
return nil
}
func Compile(inpath, outpath, target string, printIR bool) error {
c, err := NewCompiler(inpath, target)
if err != nil {
return err
}
err = c.Parse(inpath)
if err != nil {
return err
}
c.Verify()
c.Optimize(2)
c.Verify()
if printIR {
fmt.Println(c.IR())
}
err = c.EmitObject(outpath)
if err != nil {
return err
}
return nil
}
func main() {
outpath := flag.String("o", "", "output filename")
target := flag.String("target", "x86_64-pc-linux-gnu", "LLVM target")
printIR := flag.Bool("printir", false, "print LLVM IR after optimizing")
flag.Parse()
if *outpath == "" || flag.NArg() != 1 {
fmt.Fprintf(os.Stderr, "usage: %s [-printir] [-target=<target>] -o <output> <input>", os.Args[0])
flag.PrintDefaults()
}
err := Compile(flag.Args()[0], *outpath, *target, *printIR)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
}
}