Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,16 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
// exactly.
errs = nil
switch config.Options.Opt {
case "none:", "0":
errs = c.Optimize(0, 0, 0) // -O0
case "none", "0":
errs = transform.Optimize(c.Module(), config, 0, 0, 0) // -O0
case "1":
errs = c.Optimize(1, 0, 0) // -O1
errs = transform.Optimize(c.Module(), config, 1, 0, 0) // -O1
case "2":
errs = c.Optimize(2, 0, 225) // -O2
errs = transform.Optimize(c.Module(), config, 2, 0, 225) // -O2
case "s":
errs = c.Optimize(2, 1, 225) // -Os
errs = transform.Optimize(c.Module(), config, 2, 1, 225) // -Os
case "z":
errs = c.Optimize(2, 2, 5) // -Oz, default
errs = transform.Optimize(c.Module(), config, 2, 2, 5) // -Oz, default
default:
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
}
Expand Down
38 changes: 38 additions & 0 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,29 @@ type Config struct {
TestConfig TestConfig
}

// FuncValueImplementation is an enum for the particular implementations of Go
// func values.
type FuncValueImplementation int

// These constants describe the various possible implementations of Go func
// values.
const (
FuncValueNone FuncValueImplementation = iota

// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
FuncValueDoubleword

// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
FuncValueSwitch
)

// Triple returns the LLVM target triple, like armv6m-none-eabi.
func (c *Config) Triple() string {
return c.Target.Triple
Expand Down Expand Up @@ -111,6 +134,21 @@ func (c *Config) Scheduler() string {
return "coroutines"
}

// FuncImplementation picks an appropriate func value implementation for the
// target.
func (c *Config) FuncImplementation() FuncValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "none", "coroutines":
return FuncValueSwitch
case "tasks":
return FuncValueDoubleword
default:
panic("unknown scheduler type")
}
}

// PanicStrategy returns the panic strategy selected for this target. Valid
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
// instruction).
Expand Down
49 changes: 2 additions & 47 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,6 @@ func init() {
// The TinyGo import path.
const tinygoPath = "github.com/tinygo-org/tinygo"

// functionsUsedInTransform is a list of function symbols that may be used
// during TinyGo optimization passes so they have to be marked as external
// linkage until all TinyGo passes have finished.
var functionsUsedInTransforms = []string{
"runtime.alloc",
"runtime.free",
"runtime.nilPanic",
}

var taskFunctionsUsedInTransforms = []string{}

var coroFunctionsUsedInTransforms = []string{
"internal/task.start",
"internal/task.Pause",
"internal/task.fake",
"internal/task.Current",
"internal/task.createTask",
"(*internal/task.Task).setState",
"(*internal/task.Task).returnTo",
"(*internal/task.Task).returnCurrent",
"(*internal/task.Task).setReturnPtr",
"(*internal/task.Task).getReturnPtr",
}

type Compiler struct {
*compileopts.Config
mod llvm.Module
Expand Down Expand Up @@ -156,21 +132,6 @@ func (c *Compiler) Module() llvm.Module {
return c.mod
}

// getFunctionsUsedInTransforms gets a list of all special functions that should be preserved during transforms and optimization.
func (c *Compiler) getFunctionsUsedInTransforms() []string {
fnused := functionsUsedInTransforms
switch c.Scheduler() {
case "none":
case "coroutines":
fnused = append(append([]string{}, fnused...), coroFunctionsUsedInTransforms...)
case "tasks":
fnused = append(append([]string{}, fnused...), taskFunctionsUsedInTransforms...)
default:
panic(fmt.Errorf("invalid scheduler %q", c.Scheduler()))
}
return fnused
}

// Compile the given package path or .go file path. Return an error when this
// fails (in any stage).
func (c *Compiler) Compile(mainPath string) []error {
Expand Down Expand Up @@ -338,14 +299,8 @@ func (c *Compiler) Compile(mainPath string) []error {
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering

// Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range c.getFunctionsUsedInTransforms() {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
panic(fmt.Errorf("missing core function %q", name))
}
fn.SetLinkage(llvm.ExternalLinkage)
}
// Replace callMain placeholder with actual main function.
c.mod.NamedFunction("runtime.callMain").ReplaceAllUsesWith(realMain)

// Load some attributes
getAttr := func(attrName string) llvm.Attribute {
Expand Down
53 changes: 10 additions & 43 deletions compiler/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,20 @@ package compiler
import (
"go/types"

"github.com/tinygo-org/tinygo/compileopts"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)

type funcValueImplementation int

const (
funcValueNone funcValueImplementation = iota

// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
funcValueDoubleword

// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
funcValueSwitch
)

// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "none", "coroutines":
return funcValueSwitch
case "tasks":
return funcValueDoubleword
default:
panic("unknown scheduler type")
}
}

// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value
switch c.funcImplementation() {
case funcValueDoubleword:
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case funcValueSwitch:
case compileopts.FuncValueSwitch:
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
Expand Down Expand Up @@ -94,10 +61,10 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
// value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
case compileopts.FuncValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
Expand All @@ -110,11 +77,11 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (

// getFuncType returns the type of a func value given a signature.
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
switch c.funcImplementation() {
case funcValueDoubleword:
switch c.FuncImplementation() {
case compileopts.FuncValueDoubleword:
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
case compileopts.FuncValueSwitch:
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
Expand Down
42 changes: 23 additions & 19 deletions compiler/check.go → compiler/ircheck/check.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package compiler

// This file implements a set of sanity checks for the IR that is generated.
// It can catch some mistakes that LLVM's verifier cannot.
// Package ircheck implements a checker for LLVM IR, that goes a bit further
// than the regular LLVM IR verifier. Note that it checks different things, so
// this is not a replacement for the LLVM verifier but does catch things that
// the LLVM verifier doesn't catch.
package ircheck

import (
"errors"
Expand All @@ -10,7 +11,11 @@ import (
"tinygo.org/x/go-llvm"
)

func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
type checker struct {
ctx llvm.Context
}

func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return nil
Expand Down Expand Up @@ -81,7 +86,7 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia
return nil
}

func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check type
if err := c.checkType(v.Type(), types, specials); err != nil {
return fmt.Errorf("failed to verify type of value: %s", err.Error())
Expand All @@ -95,7 +100,7 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia
return nil
}

func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check value properties
if err := c.checkValue(inst, types, specials); err != nil {
return errorAt(inst, err.Error())
Expand Down Expand Up @@ -129,7 +134,7 @@ func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{
return nil
}

func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check basic block value and type
var errs []error
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
Expand All @@ -146,7 +151,7 @@ func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struc
return errs
}

func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check function value and type
var errs []error
if err := c.checkValue(fn, types, specials); err != nil {
Expand All @@ -161,26 +166,25 @@ func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, sp
return errs
}

func (c *Compiler) checkModule() []error {
// Module checks the given module and returns a slice of error, if there are
// any.
func Module(mod llvm.Module) []error {
// check for any context mismatches
var errs []error
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
c := checker{
ctx: mod.Context(),
}
if c.ctx == llvm.GlobalContext() {
// somewhere we accidentally used the global context instead of a real context
errs = append(errs, errors.New("module uses global context"))
default:
// we used some other context by accident
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}

types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
errs = append(errs, c.checkFunction(fn, types, specials)...)
}
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
if err := c.checkValue(g, types, specials); err != nil {
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
}
Expand Down
48 changes: 48 additions & 0 deletions compiler/ircheck/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ircheck

import (
"go/scanner"
"go/token"
"path/filepath"

"tinygo.org/x/go-llvm"
)

// errorAt returns an error value at the location of the instruction.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(inst llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(inst),
Msg: msg,
}
}

// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
if !val.IsAInstruction().IsNil() {
loc := val.InstructionDebugLoc()
if loc.IsNil() {
return token.Position{}
}
file := loc.LocationScope().ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.LocationLine()),
Column: int(loc.LocationColumn()),
}
} else if !val.IsAFunction().IsNil() {
loc := val.Subprogram()
if loc.IsNil() {
return token.Position{}
}
file := loc.ScopeFile()
return token.Position{
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
Line: int(loc.SubprogramLine()),
}
} else {
return token.Position{}
}
}
Loading