From a848d720db6e533ea8b014e9bcc76e5c9c73f9b5 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 13 Jan 2021 17:22:13 +0100 Subject: [PATCH] compiler: refactor and add tests This commit finally introduces unit tests for the compiler, to check whether input Go code is converted to the expected output IR. To make this necessary, a few refactors were needed. Hopefully these refactors (to compile a program package by package instead of all at once) will eventually become standard, so that packages can all be compiled separate from each other and be cached between compiles. --- .circleci/config.yml | 2 +- Makefile | 2 +- builder/build.go | 45 +++++-- compiler/compiler.go | 219 +++++++++++++++++++++-------------- compiler/compiler_test.go | 161 +++++++++++++++++++++++++ compiler/testdata/basic.go | 57 +++++++++ compiler/testdata/basic.ll | 98 ++++++++++++++++ compiler/testdata/pointer.go | 41 +++++++ compiler/testdata/pointer.ll | 49 ++++++++ compiler/testdata/slice.go | 9 ++ compiler/testdata/slice.ll | 19 +++ loader/ssa.go | 8 ++ 12 files changed, 610 insertions(+), 100 deletions(-) create mode 100644 compiler/compiler_test.go create mode 100644 compiler/testdata/basic.go create mode 100644 compiler/testdata/basic.ll create mode 100644 compiler/testdata/pointer.go create mode 100644 compiler/testdata/pointer.ll create mode 100644 compiler/testdata/slice.go create mode 100644 compiler/testdata/slice.ll diff --git a/.circleci/config.yml b/.circleci/config.yml index defaa8a1..e8d36e1c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -114,7 +114,7 @@ commands: key: wasi-libc-sysroot-systemclang-v2 paths: - lib/wasi-libc/sysroot - - run: go test -v -tags=llvm<> ./cgo ./compileopts ./interp ./transform . + - run: go test -v -tags=llvm<> ./cgo ./compileopts ./compiler ./interp ./transform . - run: make gen-device -j4 - run: make smoketest XTENSA=0 - run: make tinygo-test diff --git a/Makefile b/Makefile index 601735a4..56521716 100644 --- a/Makefile +++ b/Makefile @@ -163,7 +163,7 @@ tinygo: CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" . test: wasi-libc - CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./interp ./transform . + CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform . # Test known-working standard library packages. # TODO: do this in one command, parallelize, and only show failing tests (no diff --git a/builder/build.go b/builder/build.go index 3e7ce62a..d3e0c223 100644 --- a/builder/build.go +++ b/builder/build.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "errors" "fmt" + "go/types" "io/ioutil" "os" "path/filepath" @@ -19,6 +20,7 @@ import ( "github.com/tinygo-org/tinygo/compiler" "github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/interp" + "github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/stacksize" "github.com/tinygo-org/tinygo/transform" "tinygo.org/x/go-llvm" @@ -43,16 +45,31 @@ type BuildResult struct { // The error value may be of type *MultiError. Callers will likely want to check // for this case and print such errors individually. func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error { - // Compile Go code to IR. + // Load the target machine, which is the LLVM object that contains all + // details of a target (alignment restrictions, pointer size, default + // address spaces, etc). machine, err := compiler.NewTargetMachine(config) if err != nil { return err } - buildOutput, errs := compiler.Compile(pkgName, machine, config) + + // Load entire program AST into memory. + lprogram, err := loader.Load(config, []string{pkgName}, config.ClangHeaders, types.Config{ + Sizes: compiler.Sizes(machine), + }) + if err != nil { + return err + } + err = lprogram.Parse() + if err != nil { + return err + } + + // Compile AST to IR. + mod, errs := compiler.CompileProgram(pkgName, lprogram, machine, config) if errs != nil { return newMultiError(errs) } - mod := buildOutput.Mod if config.Options.PrintIR { fmt.Println("; Generated LLVM IR:") @@ -208,17 +225,21 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil } // Compile C files in packages. - for i, file := range buildOutput.ExtraFiles { - outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o") - err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...) - if err != nil { - return &commandError{"failed to build", file, err} + // Gather the list of (C) file paths that should be included in the build. + for i, pkg := range lprogram.Sorted() { + for j, filename := range pkg.CFiles { + file := filepath.Join(pkg.Dir, filename) + outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"."+strconv.Itoa(j)+"-"+filepath.Base(file)+".o") + err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...) + if err != nil { + return &commandError{"failed to build", file, err} + } + ldflags = append(ldflags, outpath) } - ldflags = append(ldflags, outpath) } - if len(buildOutput.ExtraLDFlags) > 0 { - ldflags = append(ldflags, buildOutput.ExtraLDFlags...) + if len(lprogram.LDFlags) > 0 { + ldflags = append(ldflags, lprogram.LDFlags...) } // Link the object files together. @@ -304,7 +325,7 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(Buil } return action(BuildResult{ Binary: tmppath, - MainDir: buildOutput.MainDir, + MainDir: lprogram.MainPkg().Dir, }) } } diff --git a/compiler/compiler.go b/compiler/compiler.go index 832f232c..d6c5736c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -9,6 +9,7 @@ import ( "go/token" "go/types" "path/filepath" + "sort" "strconv" "strings" @@ -53,6 +54,45 @@ type compilerContext struct { astComments map[string]*ast.CommentGroup } +// newCompilerContext returns a new compiler context ready for use, most +// importantly with a newly created LLVM context and module. +func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *compileopts.Config) *compilerContext { + c := &compilerContext{ + Config: config, + difiles: make(map[string]llvm.Metadata), + ditypes: make(map[types.Type]llvm.Metadata), + machine: machine, + targetData: machine.CreateTargetData(), + } + + c.ctx = llvm.NewContext() + c.mod = c.ctx.NewModule(moduleName) + c.mod.SetTarget(config.Triple()) + c.mod.SetDataLayout(c.targetData.String()) + if c.Debug() { + c.dibuilder = llvm.NewDIBuilder(c.mod) + } + + c.uintptrType = c.ctx.IntType(c.targetData.PointerSize() * 8) + if c.targetData.PointerSize() <= 4 { + // 8, 16, 32 bits targets + c.intType = c.ctx.Int32Type() + } else if c.targetData.PointerSize() == 8 { + // 64 bits target + c.intType = c.ctx.Int64Type() + } else { + panic("unknown pointer size") + } + c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0) + + dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false) + dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType) + c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() + dummyFunc.EraseFromParentAsFunction() + + return c +} + // builder contains all information relevant to build a single function. type builder struct { *compilerContext @@ -76,6 +116,18 @@ type builder struct { deferBuiltinFuncs map[ssa.Value]deferBuiltin } +func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ir.Function) *builder { + return &builder{ + compilerContext: c, + Builder: irbuilder, + fn: f, + locals: make(map[ssa.Value]llvm.Value), + dilocals: make(map[*types.Var]llvm.Metadata), + blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), + blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), + } +} + type deferBuiltin struct { funcName string callback int @@ -127,94 +179,47 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) { return machine, nil } -// CompilerOutput is returned from the Compile() call. It contains the compile -// output and information necessary to continue to compile and link the program. -type CompilerOutput struct { - // The LLVM module that contains the compiled but not optimized LLVM module - // for all the Go code in the program. - Mod llvm.Module +// Sizes returns a types.Sizes appropriate for the given target machine. It +// includes the correct int size and aligment as is necessary for the Go +// typechecker. +func Sizes(machine llvm.TargetMachine) types.Sizes { + targetData := machine.CreateTargetData() + defer targetData.Dispose() - // ExtraFiles is a list of C source files included in packages that should - // be built and linked together with the main executable to form one - // program. They can be used from CGo, for example. - ExtraFiles []string - - // ExtraLDFlags are linker flags obtained during CGo processing. These flags - // must be passed to the linker which links the entire executable. - ExtraLDFlags []string - - // MainDir is the absolute directory path to the directory of the main - // package. This is useful for testing: tests must be run in the package - // directory that is being tested. - MainDir string -} - -// Compile the given package path or .go file path. Return an error when this -// fails (in any stage). If successful it returns the LLVM module and a list of -// extra C files to be compiled. If not, one or more errors will be returned. -// -// The fact that it returns a list of filenames to compile is a layering -// violation. Eventually, this Compile function should only compile a single -// package and not the whole program, and loading of the program (including CGo -// processing) should be moved outside the compiler package. -func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Config) (output CompilerOutput, errors []error) { - c := &compilerContext{ - Config: config, - difiles: make(map[string]llvm.Metadata), - ditypes: make(map[types.Type]llvm.Metadata), - machine: machine, - targetData: machine.CreateTargetData(), + intPtrType := targetData.IntPtrType() + if intPtrType.IntTypeWidth()/8 <= 32 { } - c.ctx = llvm.NewContext() - c.mod = c.ctx.NewModule(pkgName) - c.mod.SetTarget(config.Triple()) - c.mod.SetDataLayout(c.targetData.String()) - if c.Debug() { - c.dibuilder = llvm.NewDIBuilder(c.mod) - } - output.Mod = c.mod - - c.uintptrType = c.ctx.IntType(c.targetData.PointerSize() * 8) - if c.targetData.PointerSize() <= 4 { + var intWidth int + if targetData.PointerSize() <= 4 { // 8, 16, 32 bits targets - c.intType = c.ctx.Int32Type() - } else if c.targetData.PointerSize() == 8 { + intWidth = 32 + } else if targetData.PointerSize() == 8 { // 64 bits target - c.intType = c.ctx.Int64Type() + intWidth = 64 } else { panic("unknown pointer size") } - c.i8ptrType = llvm.PointerType(c.ctx.Int8Type(), 0) - dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false) - dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType) - c.funcPtrAddrSpace = dummyFunc.Type().PointerAddressSpace() - dummyFunc.EraseFromParentAsFunction() - - lprogram, err := loader.Load(c.Config, []string{pkgName}, c.ClangHeaders, types.Config{ - Sizes: &stdSizes{ - IntSize: int64(c.targetData.TypeAllocSize(c.intType)), - PtrSize: int64(c.targetData.PointerSize()), - MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)), - }}) - if err != nil { - return output, []error{err} + return &stdSizes{ + IntSize: int64(intWidth / 8), + PtrSize: int64(targetData.PointerSize()), + MaxAlign: int64(targetData.PrefTypeAlignment(intPtrType)), } +} - err = lprogram.Parse() - if err != nil { - return output, []error{err} - } - output.ExtraLDFlags = lprogram.LDFlags - output.MainDir = lprogram.MainPkg().Dir +// CompileProgram compiles the given package path or .go file path. Return an +// error when this fails (in any stage). If successful it returns the LLVM +// module. If not, one or more errors will be returned. +func CompileProgram(pkgName string, lprogram *loader.Program, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []error) { + c := newCompilerContext(pkgName, machine, config) c.ir = ir.NewProgram(lprogram) // Run a simple dead code elimination pass. - err = c.ir.SimpleDCE() + err := c.ir.SimpleDCE() if err != nil { - return output, []error{err} + return llvm.Module{}, []error{err} } // Initialize debug information. @@ -265,15 +270,7 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con } // Create the function definition. - b := builder{ - compilerContext: c, - Builder: irbuilder, - fn: f, - locals: make(map[ssa.Value]llvm.Value), - dilocals: make(map[*types.Var]llvm.Metadata), - blockEntries: make(map[*ssa.BasicBlock]llvm.BasicBlock), - blockExits: make(map[*ssa.BasicBlock]llvm.BasicBlock), - } + b := newBuilder(c, irbuilder, f) b.createFunctionDefinition() } @@ -352,14 +349,64 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con c.dibuilder.Finalize() } - // Gather the list of (C) file paths that should be included in the build. - for _, pkg := range c.ir.LoaderProgram.Sorted() { - for _, filename := range pkg.CFiles { - output.ExtraFiles = append(output.ExtraFiles, filepath.Join(pkg.Dir, filename)) + return c.mod, c.diagnostics +} + +// CompilePackage compiles a single package to a LLVM module. +func CompilePackage(moduleName string, pkg *loader.Package, machine llvm.TargetMachine, config *compileopts.Config) (llvm.Module, []error) { + c := newCompilerContext(moduleName, machine, config) + + // Build SSA from AST. + ssaPkg := pkg.LoadSSA() + ssaPkg.Build() + + // Sort by position, so that the order of the functions in the IR matches + // the order of functions in the source file. This is useful for testing, + // for example. + var members []string + for name := range ssaPkg.Members { + members = append(members, name) + } + sort.Slice(members, func(i, j int) bool { + iPos := ssaPkg.Members[members[i]].Pos() + jPos := ssaPkg.Members[members[j]].Pos() + if i == j { + // Cannot sort by pos, so do it by name. + return members[i] < members[j] + } + return iPos < jPos + }) + + // Create *ir.Functions objects. + var functions []*ir.Function + for _, name := range members { + member := ssaPkg.Members[name] + switch member := member.(type) { + case *ssa.Function: + functions = append(functions, &ir.Function{ + Function: member, + }) } } - return output, c.diagnostics + // Declare all functions. + for _, fn := range functions { + c.createFunctionDeclaration(fn) + } + + // Add definitions to declarations. + irbuilder := c.ctx.NewBuilder() + defer irbuilder.Dispose() + for _, f := range functions { + if f.Blocks == nil { + continue // external function + } + // Create the function definition. + b := newBuilder(c, irbuilder, f) + b.createFunctionDefinition() + } + + return c.mod, nil } // getLLVMRuntimeType obtains a named type from the runtime package and returns diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go new file mode 100644 index 00000000..2ae11229 --- /dev/null +++ b/compiler/compiler_test.go @@ -0,0 +1,161 @@ +package compiler + +import ( + "flag" + "go/types" + "io/ioutil" + "regexp" + "strconv" + "strings" + "testing" + + "github.com/tinygo-org/tinygo/compileopts" + "github.com/tinygo-org/tinygo/loader" + "tinygo.org/x/go-llvm" +) + +// Pass -update to go test to update the output of the test files. +var flagUpdate = flag.Bool("update", false, "update tests based on test output") + +// Basic tests for the compiler. Build some Go files and compare the output with +// the expected LLVM IR for regression testing. +func TestCompiler(t *testing.T) { + target, err := compileopts.LoadTarget("i686--linux") + if err != nil { + t.Fatal("failed to load target:", err) + } + config := &compileopts.Config{ + Options: &compileopts.Options{}, + Target: target, + } + machine, err := NewTargetMachine(config) + if err != nil { + t.Fatal("failed to create target machine:", err) + } + + tests := []string{ + "basic.go", + "pointer.go", + "slice.go", + } + + for _, testCase := range tests { + t.Run(testCase, func(t *testing.T) { + // Load entire program AST into memory. + lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{ + Sizes: Sizes(machine), + }) + if err != nil { + t.Fatal("failed to create target machine:", err) + } + err = lprogram.Parse() + if err != nil { + t.Fatalf("could not parse test case %s: %s", testCase, err) + } + + // Compile AST to IR. + pkg := lprogram.MainPkg() + mod, errs := CompilePackage(testCase, pkg, machine, config) + if errs != nil { + for _, err := range errs { + t.Log("error:", err) + } + return + } + + // Optimize IR a little. + funcPasses := llvm.NewFunctionPassManagerForModule(mod) + defer funcPasses.Dispose() + funcPasses.AddInstructionCombiningPass() + funcPasses.InitializeFunc() + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + funcPasses.RunFunc(fn) + } + funcPasses.FinalizeFunc() + + outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll" + + // Update test if needed. Do not check the result. + if *flagUpdate { + err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666) + if err != nil { + t.Error("failed to write updated output file:", err) + } + return + } + + expected, err := ioutil.ReadFile(outfile) + if err != nil { + t.Fatal("failed to read golden file:", err) + } + + if !fuzzyEqualIR(mod.String(), string(expected)) { + t.Errorf("output does not match expected output:\n%s", mod.String()) + } + }) + } +} + +var alignRegexp = regexp.MustCompile(", align [0-9]+$") + +// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly +// equal. That means, only relevant lines are compared (excluding comments +// etc.). +func fuzzyEqualIR(s1, s2 string) bool { + lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) + lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) + if len(lines1) != len(lines2) { + return false + } + for i, line1 := range lines1 { + line2 := lines2[i] + match1 := alignRegexp.MatchString(line1) + match2 := alignRegexp.MatchString(line2) + if match1 != match2 { + // Only one of the lines has the align keyword. Remove it. + // This is a change to make the test work in both LLVM 10 and LLVM + // 11 (LLVM 11 appears to automatically add alignment everywhere). + line1 = alignRegexp.ReplaceAllString(line1, "") + line2 = alignRegexp.ReplaceAllString(line2, "") + } + if line1 != line2 { + return false + } + } + + return true +} + +// filterIrrelevantIRLines removes lines from the input slice of strings that +// are not relevant in comparing IR. For example, empty lines and comments are +// stripped out. +func filterIrrelevantIRLines(lines []string) []string { + var out []string + llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0]) + if err != nil { + // Note: this should never happen and if it does, it will always happen + // for a particular build because llvm.Version is a constant. + panic(err) + } + for _, line := range lines { + line = strings.Split(line, ";")[0] // strip out comments/info + line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments + if line == "" { + continue + } + if strings.HasPrefix(line, "source_filename = ") { + continue + } + if llvmVersion < 10 && strings.HasPrefix(line, "attributes ") { + // Ignore attribute groups. These may change between LLVM versions. + // Right now test outputs are for LLVM 10. + continue + } + if llvmVersion < 10 && strings.HasPrefix(line, "target datalayout ") { + // Ignore the target layout. This may change between LLVM versions. + continue + } + out = append(out, line) + } + return out +} diff --git a/compiler/testdata/basic.go b/compiler/testdata/basic.go new file mode 100644 index 00000000..3a636704 --- /dev/null +++ b/compiler/testdata/basic.go @@ -0,0 +1,57 @@ +package main + +// Basic tests that don't need to be split into a separate file. + +func addInt(x, y int) int { + return x + y +} + +func equalInt(x, y int) bool { + return x == y +} + +func floatEQ(x, y float32) bool { + return x == y +} + +func floatNE(x, y float32) bool { + return x != y +} + +func floatLower(x, y float32) bool { + return x < y +} + +func floatLowerEqual(x, y float32) bool { + return x <= y +} + +func floatGreater(x, y float32) bool { + return x > y +} + +func floatGreaterEqual(x, y float32) bool { + return x >= y +} + +func complexReal(x complex64) float32 { + return real(x) +} + +func complexImag(x complex64) float32 { + return imag(x) +} + +func complexAdd(x, y complex64) complex64 { + return x + y +} + +func complexSub(x, y complex64) complex64 { + return x - y +} + +func complexMul(x, y complex64) complex64 { + return x * y +} + +// TODO: complexDiv (requires runtime call) diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll new file mode 100644 index 00000000..dd5c9fa0 --- /dev/null +++ b/compiler/testdata/basic.ll @@ -0,0 +1,98 @@ +; ModuleID = 'basic.go' +source_filename = "basic.go" +target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" +target triple = "i686--linux" + +define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +define internal i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = add i32 %x, %y + ret i32 %0 +} + +define internal i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = icmp eq i32 %x, %y + ret i1 %0 +} + +define internal i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fcmp oeq float %x, %y + ret i1 %0 +} + +define internal i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fcmp une float %x, %y + ret i1 %0 +} + +define internal i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fcmp olt float %x, %y + ret i1 %0 +} + +define internal i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fcmp ole float %x, %y + ret i1 %0 +} + +define internal i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fcmp ogt float %x, %y + ret i1 %0 +} + +define internal i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fcmp oge float %x, %y + ret i1 %0 +} + +define internal float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret float %x.r +} + +define internal float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret float %x.i +} + +define internal { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fadd float %x.r, %y.r + %1 = fadd float %x.i, %y.i + %2 = insertvalue { float, float } undef, float %0, 0 + %3 = insertvalue { float, float } %2, float %1, 1 + ret { float, float } %3 +} + +define internal { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fsub float %x.r, %y.r + %1 = fsub float %x.i, %y.i + %2 = insertvalue { float, float } undef, float %0, 0 + %3 = insertvalue { float, float } %2, float %1, 1 + ret { float, float } %3 +} + +define internal { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = fmul float %x.r, %y.r + %1 = fmul float %x.i, %y.i + %2 = fsub float %0, %1 + %3 = fmul float %x.r, %y.i + %4 = fmul float %x.i, %y.r + %5 = fadd float %3, %4 + %6 = insertvalue { float, float } undef, float %2, 0 + %7 = insertvalue { float, float } %6, float %5, 1 + ret { float, float } %7 +} diff --git a/compiler/testdata/pointer.go b/compiler/testdata/pointer.go new file mode 100644 index 00000000..84a983c3 --- /dev/null +++ b/compiler/testdata/pointer.go @@ -0,0 +1,41 @@ +package main + +// This file tests various operations on pointers, such as pointer arithmetic +// and dereferencing pointers. + +import "unsafe" + +// Dereference pointers. + +func pointerDerefZero(x *[0]int) [0]int { + return *x // This is a no-op, there is nothing to load. +} + +// Unsafe pointer casts, they are sometimes a no-op. + +func pointerCastFromUnsafe(x unsafe.Pointer) *int { + return (*int)(x) +} + +func pointerCastToUnsafe(x *int) unsafe.Pointer { + return unsafe.Pointer(x) +} + +func pointerCastToUnsafeNoop(x *byte) unsafe.Pointer { + return unsafe.Pointer(x) +} + +// The compiler has support for a few special cast+add patterns that are +// transformed into a single GEP. + +func pointerUnsafeGEPFixedOffset(ptr *byte) *byte { + return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + 10)) +} + +func pointerUnsafeGEPByteOffset(ptr *byte, offset uintptr) *byte { + return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset)) +} + +func pointerUnsafeGEPIntOffset(ptr *int32, offset uintptr) *int32 { + return (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset*4)) +} diff --git a/compiler/testdata/pointer.ll b/compiler/testdata/pointer.ll new file mode 100644 index 00000000..a5f4c889 --- /dev/null +++ b/compiler/testdata/pointer.ll @@ -0,0 +1,49 @@ +; ModuleID = 'pointer.go' +source_filename = "pointer.go" +target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" +target triple = "i686--linux" + +define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +define internal [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret [0 x i32] zeroinitializer +} + +define internal i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = bitcast i8* %x to i32* + ret i32* %0 +} + +define internal i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = bitcast i32* %x to i8* + ret i8* %0 +} + +define internal i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret i8* %x +} + +define internal i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = getelementptr inbounds i8, i8* %ptr, i32 10 + ret i8* %0 +} + +define internal i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = getelementptr inbounds i8, i8* %ptr, i32 %offset + ret i8* %0 +} + +define internal i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + %0 = getelementptr i32, i32* %ptr, i32 %offset + ret i32* %0 +} diff --git a/compiler/testdata/slice.go b/compiler/testdata/slice.go new file mode 100644 index 00000000..216fad4d --- /dev/null +++ b/compiler/testdata/slice.go @@ -0,0 +1,9 @@ +package main + +func sliceLen(ints []int) int { + return len(ints) +} + +func sliceCap(ints []int) int { + return cap(ints) +} diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll new file mode 100644 index 00000000..72301eef --- /dev/null +++ b/compiler/testdata/slice.ll @@ -0,0 +1,19 @@ +; ModuleID = 'slice.go' +source_filename = "slice.go" +target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" +target triple = "i686--linux" + +define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret void +} + +define internal i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret i32 %ints.len +} + +define internal i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr { +entry: + ret i32 %ints.cap +} diff --git a/loader/ssa.go b/loader/ssa.go index c3b87c49..f97511ee 100644 --- a/loader/ssa.go +++ b/loader/ssa.go @@ -16,3 +16,11 @@ func (p *Program) LoadSSA() *ssa.Program { return prog } + +// LoadSSA constructs the SSA form of this package. +// +// The program must already be parsed and type-checked with the .Parse() method. +func (p *Package) LoadSSA() *ssa.Package { + prog := ssa.NewProgram(p.program.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug) + return prog.CreatePackage(p.Pkg, p.Files, &p.info, true) +}