Skip to content
Closed
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
13 changes: 9 additions & 4 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ func (c *Config) GOARCH() string {

// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
gc := c.GC()
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + gc, "scheduler." + c.Scheduler()}...)
switch gc {
case "list", "blocks", "extalloc":
tags = append(tags, "gc.conservative")
}
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
Expand All @@ -96,7 +101,7 @@ func (c *Config) CgoEnabled() bool {
}

// GC returns the garbage collection strategy in use on this platform. Valid
// values are "none", "leaking", "extalloc", and "conservative".
// values are "none", "leaking", "list", "extalloc", and "blocks".
func (c *Config) GC() string {
if c.Options.GC != "" {
return c.Options.GC
Expand All @@ -106,7 +111,7 @@ func (c *Config) GC() string {
}
for _, tag := range c.Target.BuildTags {
if tag == "baremetal" || tag == "wasm" {
return "conservative"
return "blocks"
}
}
return "extalloc"
Expand All @@ -116,7 +121,7 @@ func (c *Config) GC() string {
// that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "extalloc":
case "list", "blocks", "extalloc":
for _, tag := range c.BuildTags() {
if tag == "wasm" {
return true
Expand Down
2 changes: 1 addition & 1 deletion compileopts/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
)

var (
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
validGCOptions = []string{"none", "leaking", "list", "extalloc", "blocks"}
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"}
Expand Down
12 changes: 9 additions & 3 deletions compileopts/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (

func TestVerifyOptions(t *testing.T) {

expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`)
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, list, extalloc, blocks`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
Expand Down Expand Up @@ -42,16 +42,22 @@ func TestVerifyOptions(t *testing.T) {
GC: "leaking",
},
},
{
name: "GCOptionList",
opts: compileopts.Options{
GC: "list",
},
},
{
name: "GCOptionExtalloc",
opts: compileopts.Options{
GC: "extalloc",
},
},
{
name: "GCOptionConservative",
name: "GCOptionBlocks",
opts: compileopts.Options{
GC: "conservative",
GC: "blocks",
},
},
{
Expand Down
43 changes: 40 additions & 3 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/gctype"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"github.com/tinygo-org/tinygo/loader"
Expand Down Expand Up @@ -51,6 +52,7 @@ type compilerContext struct {
ir *ir.Program
diagnostics []error
astComments map[string]*ast.CommentGroup
typer *gctype.Typer
}

// builder contains all information relevant to build a single function.
Expand Down Expand Up @@ -249,6 +251,13 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con
c.createFunctionDeclaration(f)
}

allocTyped := c.mod.NamedFunction("runtime.allocTyped")
if !allocTyped.IsNil() {
// The runtime has precise types available.
// Initialize garbage collector type system.
c.typer = gctype.NewTyper(c.ctx, c.mod, c.targetData)
}

// Add definitions to declarations.
var initFuncs []llvm.Value
irbuilder := c.ctx.NewBuilder()
Expand Down Expand Up @@ -314,8 +323,14 @@ func Compile(pkgName string, machine llvm.TargetMachine, config *compileopts.Con

// Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value.
// Do the same for the typed allocator.
alloc := c.mod.NamedFunction("runtime.alloc")
for _, attrName := range []string{"noalias", "nonnull"} {
c.mod.NamedFunction("runtime.alloc").AddAttributeAtIndex(0, getAttr(attrName))
attr := getAttr(attrName)
alloc.AddAttributeAtIndex(0, attr)
if !allocTyped.IsNil() {
allocTyped.AddAttributeAtIndex(0, attr)
}
}

// On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
Expand Down Expand Up @@ -1462,7 +1477,18 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, b.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
}
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
var buf llvm.Value
if b.typer != nil {
// Allocate a typed value.
t, err := b.typer.Create(typ)
if err != nil {
return llvm.Value{}, b.makeError(expr.Pos(), err.Error())
}
buf = b.createRuntimeCall("allocTyped", []llvm.Value{sizeValue, t}, expr.Comment)
} else {
// Allocate an untyped value.
buf = b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
}
buf = b.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
return buf, nil
} else {
Expand Down Expand Up @@ -1675,7 +1701,18 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, err
}
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf")
var slicePtr llvm.Value
if b.typer != nil {
// Allocate a typed value.
t, err := b.typer.Create(llvmElemType)
if err != nil {
return llvm.Value{}, b.makeError(expr.Pos(), err.Error())
}
slicePtr = b.createRuntimeCall("allocTyped", []llvm.Value{sliceSize, t}, "makeslice.buf")
} else {
// Allocate an untyped value.
slicePtr = b.createRuntimeCall("alloc", []llvm.Value{sliceSize}, "makeslice.buf")
}
slicePtr = b.CreateBitCast(slicePtr, llvm.PointerType(llvmElemType, 0), "makeslice.array")

// Extend or truncate if necessary. This is safe as we've already done
Expand Down
161 changes: 161 additions & 0 deletions compiler/gctype/gctype.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package gctype

import (
"errors"
"fmt"
"math/big"

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

// getPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bigint at the word offset that contains a pointer. This scan is recursive.
func getPointerBitmap(targetData llvm.TargetData, typ llvm.Type, name string) *big.Int {
alignment := targetData.PrefTypeAlignment(llvm.PointerType(typ.Context().Int8Type(), 0))
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := getPointerBitmap(targetData, subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := getPointerBitmap(targetData, subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind: " + name)
}
}

// NewTyper creates a Typer.
func NewTyper(ctx llvm.Context, mod llvm.Module, td llvm.TargetData) *Typer {
ptr := llvm.PointerType(ctx.Int8Type(), 0)
return &Typer{
tcache: make(map[llvm.Type]llvm.Value),
td: td,
ctx: ctx,
mod: mod,
uintptr: ctx.IntType(int(td.TypeSizeInBits(ptr))),
i8: ctx.Int8Type(),
ptrSize: td.TypeAllocSize(ptr),
ptrAlign: uint64(td.ABITypeAlignment(ptr)),
}
}

// Typer creates GC types.
type Typer struct {
// tcache is a cache of GC types by LLVM type.
tcache map[llvm.Type]llvm.Value

// td is the target platform data.
td llvm.TargetData

ctx llvm.Context

mod llvm.Module

uintptr, i8 llvm.Type

ptrSize, ptrAlign uint64
}

// Create a GC type for the given LLVM type.
func (t *Typer) Create(typ llvm.Type) (llvm.Value, error) {
// Check the cache before attempting to create the global.
if g, ok := t.tcache[typ]; ok {
return g, nil
}

// Find the type size.
size := t.td.TypeAllocSize(typ)

// Compute a pointer bitmap.
// TODO: clean this up and maybe use error handling?
b := getPointerBitmap(t.td, typ, "")
if b.Cmp(big.NewInt(0)) == 0 {
// The type has no pointers.
return llvm.ConstNull(llvm.PointerType(t.uintptr, 0)), nil
}

// Use some limited sanity-checking.
align := uint64(t.td.ABITypeAlignment(typ))
switch {
case size < t.ptrSize:
return llvm.Value{}, errors.New("type has pointers but is smaller than a pointer")
case align%t.ptrAlign != 0:
return llvm.Value{}, errors.New("alignment of pointery type is not a multiple of pointer alignment")
case size%align != 0:
return llvm.Value{}, errors.New("type violates the array alignment invariant")
}

// Convert size into increments of pointer-align.
size /= t.ptrAlign

// Create a global for the type.
g := t.createGlobal(size, b.Bytes())

// Save the global to the cache.
t.tcache[typ] = g

return g, nil
}

func (t *Typer) createGlobal(size uint64, layout []byte) llvm.Value {
// TODO: compression?

// Generate the name of the global.
name := fmt.Sprintf("tinygo.gc.type.%d.%x", size, layout)

// Create the global if it does not exist.
g := t.mod.NamedGlobal(name)
if g.IsNil() {
// Convert the encoded layout to a byte array.
bitmapValues := make([]llvm.Value, len(layout))
for i, b := range layout {
bitmapValues[len(layout)-i-1] = llvm.ConstInt(t.i8, uint64(b), false)
}
bitmapArray := llvm.ConstArray(t.i8, bitmapValues)

// Construct a tuple of the size + the array.
tuple := t.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(t.uintptr, size, false),
bitmapArray,
}, false)

// Create a global constant initialized with the tuple.
g = llvm.AddGlobal(t.mod, tuple.Type(), name)
g.SetInitializer(tuple)
g.SetGlobalConstant(true)
g.SetUnnamedAddr(true)
g.SetLinkage(llvm.InternalLinkage)
}

// Get a pointer to the size component of the global.
// This is used because different globals will end up with different sizes.
g = llvm.ConstBitCast(g, llvm.PointerType(t.uintptr, 0))

return g
}
2 changes: 1 addition & 1 deletion interp/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// means that monotonic time in the time package is counted from
// time.Time{}.Sub(1), which should be fine.
locals[inst.localIndex] = literalValue{uint64(0)}
case callFn.name == "runtime.alloc":
case callFn.name == "runtime.alloc" || callFn.name == "runtime.allocTyped":
// Allocate heap memory. At compile time, this is instead done
// by creating a global variable.

Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ func main() {
command := os.Args[1]

opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, conservative)")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, extalloc, blocks, list)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (none, coroutines, tasks)")
printIR := flag.Bool("printir", false, "print LLVM IR")
Expand Down
4 changes: 2 additions & 2 deletions src/internal/task/task_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ func (t *Task) Resume() {
// initialize the state and prepare to call the specified function with the specified argument bundle.
func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
// Create a stack.
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0)))
stack := make([]unsafe.Pointer, stackSize/unsafe.Sizeof(unsafe.Pointer(nil)))

// Set up the stack canary, a random number that should be checked when
// switching from the task back to the scheduler. The stack canary pointer
// points to the first word of the stack. If it has changed between now and
// the next stack switch, there was a stack overflow.
s.canaryPtr = &stack[0]
s.canaryPtr = (*uintptr)(unsafe.Pointer(&stack[0]))
*s.canaryPtr = stackCanary

// Get a pointer to the top of the stack, where the initial register values
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build gc.conservative
// +build gc.blocks

package runtime

Expand Down
2 changes: 1 addition & 1 deletion src/runtime/gc_globals_conservative.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build gc.conservative gc.extalloc
// +build gc.conservative
// +build baremetal

package runtime
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/gc_globals_precise.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// +build gc.conservative gc.extalloc
// +build gc.conservative
// +build !baremetal

package runtime
Expand Down
Loading