Skip to content
Open
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
37 changes: 12 additions & 25 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,10 @@ TEST_PACKAGES_FAST = \
container/heap \
container/list \
container/ring \
crypto/des \
crypto/ecdsa \
crypto/elliptic \
crypto/hmac \
crypto/md5 \
crypto/sha1 \
crypto/sha256 \
Expand All @@ -365,6 +367,7 @@ TEST_PACKAGES_FAST = \
hash/crc64 \
hash/fnv \
html \
image \
internal/itoa \
internal/profile \
math \
Expand All @@ -375,10 +378,14 @@ TEST_PACKAGES_FAST = \
os \
path \
reflect \
regexp/syntax \
strconv \
sync \
testing \
testing/iotest \
text/scanner \
text/tabwriter \
text/template/parse \
unicode \
unicode/utf16 \
unicode/utf8 \
Expand All @@ -389,21 +396,14 @@ TEST_PACKAGES_FAST = \
# bytes requires mmap
# compress/flate appears to hang on wasi
# crypto/aes needs reflect.Type.Method(), not yet implemented
# crypto/des fails on wasi, needs panic()/recover()
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
# encoding/xml takes a minute on linux and gives a stack overflow on wasi
# image fails on wasi, needs panic()/recover()
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
# mime: fails on wasi, needs panic()/recover()
# mime fails on wasi: bufio.Scanner reports an impossible read count
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
# mime/quotedprintable requires syscall.Faccessat
# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK
# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK
# regexp/syntax: fails on wasip1, needs panic()/recover()
# strconv: fails on wasi, needs panic()/recover()
# text/tabwriter: fails on wasi, needs panic()/recover()
# text/template/parse: fails on wasi, needs panic()/recover()
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi

# Additional standard library packages that pass tests on individual platforms
Expand All @@ -412,13 +412,10 @@ TEST_PACKAGES_LINUX := \
compress/flate \
context \
crypto/aes \
crypto/des \
crypto/ecdh \
crypto/hmac \
debug/dwarf \
debug/plan9obj \
encoding/xml \
image \
io/ioutil \
mime \
mime/multipart \
Expand All @@ -427,25 +424,15 @@ TEST_PACKAGES_LINUX := \
net/mail \
net/textproto \
os/user \
regexp/syntax \
strconv \
testing/fstest \
text/tabwriter \
text/template/parse
$(nil)

TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)

# os/user requires t.Skip() support
TEST_PACKAGES_WINDOWS := \
compress/flate \
crypto/des \
crypto/hmac \
image \
mime \
regexp/syntax \
strconv \
text/tabwriter \
text/template/parse \
$(nil)


Expand All @@ -461,6 +448,7 @@ TEST_PACKAGES_NONWASM = \
embed/internal/embedtest \
expvar \
go/format \
image \
os \
testing \
$(nil)
Expand All @@ -477,11 +465,11 @@ TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PAC
TEST_PACKAGES_NONBAREMETAL = \
$(TEST_PACKAGES_NONWASM) \
math \
regexp/syntax \
$(nil)

TEST_PACKAGES_FAST_WASI = $(filter-out $(TEST_PACKAGES_NOWASI), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NOWASI = \
crypto/ecdsa \
$(nil)

# Report platforms on which each standard library package is known to pass tests
Expand Down Expand Up @@ -510,15 +498,14 @@ TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
TEST_IOFS := false
endif

TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic|TestAsValidation'
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestAsValidation'
TEST_ADDITIONAL_FLAGS ?=

# Test known-working standard library packages.
# TODO: parallelize, and only show failing tests (no implied -v flag).
.PHONY: tinygo-test
tinygo-test:
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
@# TestParseAndBytesRoundTrip/P256/Generic: needs Goexit to run defers on wasm.
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(TEST_PACKAGES_HOST)) $(TEST_PACKAGES_SLOW)
ifeq ($(TEST_ENCODING_XML),true)
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) -stack-size=16MB encoding/xml
Expand Down
1 change: 1 addition & 0 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed
Nobounds: config.Options.Nobounds,
PanicStrategy: config.PanicStrategy(),
PanicUnwind: config.PanicUnwind(),
}

// Load the target machine, which is the LLVM object that contains all
Expand Down
19 changes: 17 additions & 2 deletions builder/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package builder

import (
"errors"
"fmt"
"runtime"

Expand Down Expand Up @@ -54,10 +55,24 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
return nil, fmt.Errorf("cannot compile with Go toolchain version go%d.%d (TinyGo was built using toolchain version %s)", gorootMajor, gorootMinor, runtime.Version())
}

return &compileopts.Config{
config := &compileopts.Config{
Options: options,
Target: spec,
GoMinorVersion: gorootMinor,
TestConfig: options.TestConfig,
}, nil
}
requestedPanicUnwind := options.PanicUnwind
if requestedPanicUnwind == "" {
requestedPanicUnwind = spec.PanicUnwind
}
if requestedPanicUnwind == "explicit" && config.Scheduler() == "asyncify" {
return nil, errors.New("explicit panic unwinding cannot be used with the asyncify scheduler")
}
if config.PanicUnwind() == "explicit" && !config.SupportsExplicitUnwind() {
return nil, fmt.Errorf("explicit panic unwinding is not supported on %s", config.Triple())
}
if config.PanicUnwind() == "explicit" && config.Scheduler() == "threads" {
return nil, errors.New("explicit panic unwinding is not supported with the threads scheduler")
}
return config, nil
}
6 changes: 3 additions & 3 deletions builder/sizes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4313, 323, 0, 2260},
{"microbit", "examples/serial", 2838, 382, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8027, 1665, 132, 7488},
{"hifive1b", "examples/echo", 4301, 323, 0, 2260},
{"microbit", "examples/serial", 2834, 382, 8, 2256},
{"wioterminal", "examples/pininterrupt", 7991, 1665, 132, 7488},

// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
Expand Down
37 changes: 37 additions & 0 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func (c *Config) BuildTags() []string {
"osusergo", // to get os/user to work
"math_big_pure_go", // to get math/big to work
"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
"tinygo.unwind." + c.PanicUnwind(),
"serial." + c.Serial()}...) // used inside the machine package
switch c.Scheduler() {
case "threads", "cores":
Expand Down Expand Up @@ -202,6 +203,42 @@ func (c *Config) PanicStrategy() string {
return c.Options.PanicStrategy
}

// PanicUnwind returns the mechanism used to unwind panics and Goexit. Asyncify
// provides unwinding whenever that scheduler is selected. Explicit
// return-based unwinding must be requested by the command line or target
// specification.
func (c *Config) PanicUnwind() string {
if c.Scheduler() == "asyncify" {
return "asyncify"
}
requested := c.Target.PanicUnwind
if c.Options.PanicUnwind != "" {
requested = c.Options.PanicUnwind
}
if requested == "explicit" {
return "explicit"
}
arch, _, _ := strings.Cut(c.Triple(), "-")
switch arch {
case "wasm32", "xtensa":
return "none"
default:
return "setjmp"
}
}

// SupportsExplicitUnwind reports whether the target can use return-based
// panic unwinding.
func (c *Config) SupportsExplicitUnwind() bool {
arch, _, _ := strings.Cut(c.Triple(), "-")
switch arch {
case "wasm32", "riscv64", "xtensa":
return true
default:
return false
}
}

// AutomaticStackSize returns whether goroutine stack sizes should be determined
// automatically at compile time, if possible. If it is false, no attempt is
// made.
Expand Down
11 changes: 11 additions & 0 deletions compileopts/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
validPrintSizeOptions = []string{"none", "short", "full", "html"}
validPanicStrategyOptions = []string{"print", "trap"}
validPanicUnwindOptions = []string{"auto", "explicit"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
)

Expand All @@ -32,6 +33,7 @@ type Options struct {
Opt string
GC string
PanicStrategy string
PanicUnwind string
Scheduler string
StackSize uint64 // goroutine stack size (if none could be automatically determined)
Serial string
Expand Down Expand Up @@ -120,6 +122,15 @@ func (o *Options) Verify() error {
}
}

if o.PanicUnwind != "" {
valid := slices.Contains(validPanicUnwindOptions, o.PanicUnwind)
if !valid {
return fmt.Errorf(`invalid panic-unwind option '%s': valid values are %s`,
o.PanicUnwind,
strings.Join(validPanicUnwindOptions, ", "))
}
}

if o.Opt != "" {
if !slices.Contains(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
Expand Down
20 changes: 20 additions & 0 deletions compileopts/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ func TestVerifyOptions(t *testing.T) {
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify, threads, cores`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`)
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
expectedPanicUnwindError := errors.New(`invalid panic-unwind option 'incorrect': valid values are auto, explicit`)

testCases := []struct {
name string
Expand Down Expand Up @@ -117,6 +118,25 @@ func TestVerifyOptions(t *testing.T) {
PanicStrategy: "trap",
},
},
{
name: "InvalidPanicUnwindOption",
opts: compileopts.Options{
PanicUnwind: "incorrect",
},
expectedError: expectedPanicUnwindError,
},
{
name: "PanicUnwindOptionAuto",
opts: compileopts.Options{
PanicUnwind: "auto",
},
},
{
name: "PanicUnwindOptionExplicit",
opts: compileopts.Options{
PanicUnwind: "explicit",
},
},
}

for _, tc := range testCases {
Expand Down
6 changes: 6 additions & 0 deletions compileopts/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type TargetSpec struct {
BuildTags []string `json:"build-tags,omitempty"`
BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified)
GC string `json:"gc,omitempty"`
PanicUnwind string `json:"panic-unwind,omitempty"`
Scheduler string `json:"scheduler,omitempty"`
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
Linker string `json:"linker,omitempty"`
Expand Down Expand Up @@ -224,6 +225,11 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
if err != nil {
return nil, fmt.Errorf("%s : %w", options.Target, err)
}
switch spec.PanicUnwind {
case "", "auto", "explicit":
default:
return nil, fmt.Errorf("%s: invalid panic-unwind option %q", options.Target, spec.PanicUnwind)
}

if spec.Scheduler == "asyncify" {
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_asyncify_wasm.S")
Expand Down
46 changes: 46 additions & 0 deletions compileopts/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,49 @@ func TestConfigLinkerFlavor(t *testing.T) {
})
}
}

func TestConfigPanicUnwind(t *testing.T) {
tests := []struct {
name string
options Options
target TargetSpec
want string
}{
{
name: "native defaults to setjmp",
target: TargetSpec{Triple: "x86_64-unknown-linux"},
want: "setjmp",
},
{
name: "riscv64 defaults to setjmp",
target: TargetSpec{Triple: "riscv64-unknown-unknown"},
want: "setjmp",
},
{
name: "explicit command line opt in",
options: Options{PanicUnwind: "explicit"},
target: TargetSpec{Triple: "riscv64-unknown-unknown"},
want: "explicit",
},
{
name: "auto overrides target opt in",
options: Options{PanicUnwind: "auto"},
target: TargetSpec{Triple: "riscv64-unknown-unknown", PanicUnwind: "explicit"},
want: "setjmp",
},
{
name: "asyncify enables unwinding",
options: Options{Scheduler: "asyncify"},
target: TargetSpec{Triple: "wasm32-unknown-unknown", PanicUnwind: "auto"},
want: "asyncify",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
config := Config{Options: &tc.options, Target: &tc.target}
if got := config.PanicUnwind(); got != tc.want {
t.Fatalf("got %q, want %q", got, tc.want)
}
})
}
}
6 changes: 4 additions & 2 deletions compiler/asserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,13 @@ func (b *builder) getRuntimeAssertBlock(blockPrefix, assertFunc string) llvm.Bas
block := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
b.runtimeAssertBlocks[assertFunc] = block
b.SetInsertPointAtEnd(block)
b.inFaultBlock = true
if b.hasDeferFrame() {
b.createFaultCheckpoint()
}
b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable()
b.createRuntimeInvoke(assertFunc, nil, "")
b.createUnwindReturnOrUnreachable()
b.inFaultBlock = false
b.SetInsertPointAtEnd(savedBlock)
return block
}
Expand Down
Loading
Loading