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
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", 4302, 346, 0, 2260},
{"microbit", "examples/serial", 2829, 391, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8001, 1687, 132, 7488},

// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
Expand Down
17 changes: 17 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,23 @@ func TestBuild(t *testing.T) {
runTestWithConfig("print.go", t, opts, nil, nil)
})

t.Run("gc=none-runtime-panic", func(t *testing.T) {
t.Parallel()
opts := optionsFromTarget("cortex-m-qemu", sema)
opts.GC = "none"
opts.Scheduler = "none"
config, err := builder.NewConfig(&opts)
if err != nil {
t.Fatal(err)
}
err = Build("testdata/trivialpanic.go", t.TempDir()+"/trivialpanic", config)
if err != nil {
w := &bytes.Buffer{}
diagnostics.CreateDiagnostics(err).WriteTo(w, "")
t.Fatal(w.String())
}
})

t.Run("ldflags", func(t *testing.T) {
t.Parallel()
opts := optionsFromTarget("", sema)
Expand Down
12 changes: 6 additions & 6 deletions src/internal/task/task_none.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import "unsafe"
// There is only one goroutine so the task struct can be a global.
var mainTask Task

//go:linkname runtimePanic runtime.runtimePanic
func runtimePanic(str string)
//go:linkname runtimePanicSchedulerDisabled runtime.runtimePanicSchedulerDisabled
func runtimePanicSchedulerDisabled()

func Pause() {
runtimePanic("scheduler is disabled")
runtimePanicSchedulerDisabled()
}

func Current() *Task {
Expand All @@ -22,13 +22,13 @@ func Current() *Task {
//go:noinline
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
// The compiler will error if this is reachable.
runtimePanic("scheduler is disabled")
runtimePanicSchedulerDisabled()
}

type state struct{}

func (t *Task) Resume() {
runtimePanic("scheduler is disabled")
runtimePanicSchedulerDisabled()
}

// OnSystemStack returns whether the caller is running on the system stack.
Expand All @@ -39,7 +39,7 @@ func OnSystemStack() bool {

func SystemStack() uintptr {
// System stack is the current stack, so this shouldn't be called.
runtimePanic("scheduler is disabled")
runtimePanicSchedulerDisabled()
return 0 // unreachable
}

Expand Down
8 changes: 4 additions & 4 deletions src/runtime/chan.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ func (ch *channel) trySend(value unsafe.Pointer) (sent bool, wake *task.Task) {
// Note: we cannot currently recover from this panic.
// There's some state in the select statement especially that would be
// corrupted if we allowed recovering from this panic.
runtimePanic("send on closed channel")
runtimePanic(errSendOnClosedChannel)
}

// There is no value in the buffer and we have a receiver available. Copy
Expand Down Expand Up @@ -266,7 +266,7 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
// closed while sending).
if t.DataUint32() == chanOperationClosed {
// Oops, this channel was closed while sending!
runtimePanic("send on closed channel")
runtimePanic(errSendOnClosedChannel)
}
}

Expand Down Expand Up @@ -349,7 +349,7 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
func chanClose(ch *channel) {
if ch == nil {
// Not allowed by the language spec.
runtimePanic("close of nil channel")
runtimePanic(errCloseNilChannel)
}

mask := interrupt.Disable()
Expand All @@ -359,7 +359,7 @@ func chanClose(ch *channel) {
// Not allowed by the language spec.
ch.lock.Unlock()
interrupt.Restore(mask)
runtimePanic("close of closed channel")
runtimePanic(errCloseClosedChannel)
}

// Collect waiters and wake them after unlocking.
Expand Down
26 changes: 26 additions & 0 deletions src/runtime/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,29 @@ type plainError string

func (e plainError) Error() string { return string(e) }
func (e plainError) RuntimeError() {}

const (
errNilPointer = plainError("nil pointer dereference")
errNilMap = plainError("assignment to entry in nil map")
errIndexOutOfRange = plainError("index out of range")
errSliceOutOfRange = plainError("slice out of range")
errSliceToArray = plainError("slice smaller than array")
errUnsafeSliceLength = plainError("unsafe.Slice/String: len out of range")
errChannelTooBig = plainError("new channel is too big")
errNegativeShift = plainError("negative shift")
errDivideByZero = plainError("divide by zero")
errBlockingExported = plainError("trying to do blocking operation in exported function")
errTimersUnsupported = plainError("timers not supported without a scheduler")
errIntegerOverflow = plainError("integer overflow")
errUnsupportedSignal = plainError("unsupported signal number")
errUnsupportedExit = plainError("unsupported: syscall.Exit")
errSendOnClosedChannel = plainError("send on closed channel")
errCloseNilChannel = plainError("close of nil channel")
errCloseClosedChannel = plainError("close of closed channel")
errWasmBeforeInit = plainError("//go:wasmexport function called before runtime initialization")
errWasmAfterMain = plainError("//go:wasmexport function called after main.main returned")
errWasmDidNotFinish = plainError("//go:wasmexport function did not finish")
errUncomparable = plainError("comparing un-comparable type")
errTypeAssert = plainError("type assert failed")
errSchedulerDisabled = plainError("scheduler is disabled")
)
2 changes: 1 addition & 1 deletion src/runtime/hashmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,7 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
}
return hash
default:
runtimePanic("comparing un-comparable type")
runtimePanic(errUncomparable)
return 0 // unreachable
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func reflectValueEqual(x, y reflectlite.Value) bool {
case reflectlite.Interface:
return reflectValueEqual(x.Elem(), y.Elem())
default:
runtimePanic("comparing un-comparable type")
runtimePanic(errUncomparable)
return false // unreachable
}
}
Expand All @@ -86,7 +86,7 @@ func reflectValueEqual(x, y reflectlite.Value) bool {
// returns false.
func interfaceTypeAssert(ok bool) {
if !ok {
runtimePanic("type assert failed")
runtimePanic(errTypeAssert)
}
}

Expand Down
38 changes: 21 additions & 17 deletions src/runtime/panic.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ func panicOrGoexit(message interface{}, panicking panicState) {
abort()
}

// Cause a runtime panic, which is (currently) always a string.
func runtimePanic(msg string) {
// As long as this function is inined, llvm.returnaddress(0) will return
// Cause a recoverable runtime panic.
func runtimePanic(err Error) {
// As long as this function is inlined, llvm.returnaddress(0) will return
// something sensible.
runtimePanicAt(returnAddress(0), msg)
runtimePanicAt(returnAddress(0), err)
}

// runtimeFatal terminates for runtime failures that cannot safely be
Expand All @@ -103,7 +103,7 @@ func runtimeFatal(msg string) {
abort()
}

func runtimePanicAt(addr unsafe.Pointer, msg string) {
func runtimePanicAt(addr unsafe.Pointer, err Error) {
if panicStrategy() == tinygo.PanicStrategyTrap {
trap()
}
Expand All @@ -112,7 +112,7 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
if frame != nil {
// Use the normal panic mechanism so that this runtime error
// can be recovered with recover().
frame.PanicValue = plainError(msg)
frame.PanicValue = err
frame.Panicking = panicTrue | (frame.Panicking & panicGoexit)
tinygo_longjmp(frame)
// unreachable
Expand All @@ -128,11 +128,15 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) {
} else {
printstring("panic: runtime error: ")
}
printstring(msg)
printstring(err.Error())
printnl()
abort()
}

func runtimePanicSchedulerDisabled() {
runtimePanicAt(returnAddress(0), errSchedulerDisabled)
}

// Called at the start of a function that includes a deferred call.
// It gets passed in the stack-allocated defer frame and configures it.
// Note that the frame is not zeroed yet, so we need to initialize all values
Expand Down Expand Up @@ -220,54 +224,54 @@ func _recover(useParentFrame bool) interface{} {

// Panic when trying to dereference a nil pointer.
func nilPanic() {
runtimePanicAt(returnAddress(0), "nil pointer dereference")
runtimePanicAt(returnAddress(0), errNilPointer)
}

// Panic when trying to add an entry to a nil map
func nilMapPanic() {
runtimePanicAt(returnAddress(0), "assignment to entry in nil map")
runtimePanicAt(returnAddress(0), errNilMap)
}

// Panic when trying to access an array or slice out of bounds.
func lookupPanic() {
runtimePanicAt(returnAddress(0), "index out of range")
runtimePanicAt(returnAddress(0), errIndexOutOfRange)
}

// Panic when trying to slice a slice out of bounds.
func slicePanic() {
runtimePanicAt(returnAddress(0), "slice out of range")
runtimePanicAt(returnAddress(0), errSliceOutOfRange)
}

// Panic when trying to convert a slice to an array pointer (Go 1.17+) and the
// slice is shorter than the array.
func sliceToArrayPointerPanic() {
runtimePanicAt(returnAddress(0), "slice smaller than array")
runtimePanicAt(returnAddress(0), errSliceToArray)
}

// Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+)
// with a len that's too large (which includes if the ptr is nil and len is
// nonzero).
func unsafeSlicePanic() {
runtimePanicAt(returnAddress(0), "unsafe.Slice/String: len out of range")
runtimePanicAt(returnAddress(0), errUnsafeSliceLength)
}

// Panic when trying to create a new channel that is too big.
func chanMakePanic() {
runtimePanicAt(returnAddress(0), "new channel is too big")
runtimePanicAt(returnAddress(0), errChannelTooBig)
}

// Panic when a shift value is negative.
func negativeShiftPanic() {
runtimePanicAt(returnAddress(0), "negative shift")
runtimePanicAt(returnAddress(0), errNegativeShift)
}

// Panic when there is a divide by zero.
func divideByZeroPanic() {
runtimePanicAt(returnAddress(0), "divide by zero")
runtimePanicAt(returnAddress(0), errDivideByZero)
}

func blockingPanic() {
runtimePanicAt(returnAddress(0), "trying to do blocking operation in exported function")
runtimePanicAt(returnAddress(0), errBlockingExported)
}

//go:linkname fips_fatal crypto/internal/fips140.fatal
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/runtime_tinygowasm_unknown.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func syscall_Exit(code int) {
// Because this is the "unknown" target we can't call an exit function.
// But we also can't just return since the program will likely expect this
// function to never return. So we panic instead.
runtimePanic("unsupported: syscall.Exit")
runtimePanic(errUnsupportedExit)
}

// There is not yet any support for any form of parallelism on WebAssembly, so these
Expand Down
10 changes: 5 additions & 5 deletions src/runtime/runtime_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ func tinygo_sigpanic() {
sig := tinygo_caught_signal
switch sig {
case sig_SIGSEGV, sig_SIGBUS:
runtimePanic("nil pointer dereference")
runtimePanic(errNilPointer)
case sig_SIGFPE:
runtimePanic("divide by zero")
runtimePanic(errDivideByZero)
default:
runtimeFatal("signal")
}
Expand Down Expand Up @@ -382,7 +382,7 @@ func signal_enable(s uint32) {
if s >= 32 {
// TODO: to support higher signal numbers, we need to turn
// receivedSignals into a uint32 array.
runtimePanicAt(returnAddress(0), "unsupported signal number")
runtimePanicAt(returnAddress(0), errUnsupportedSignal)
}

// This is intentonally a non-atomic store. This is safe, since hasSignals
Expand All @@ -399,7 +399,7 @@ func signal_ignore(s uint32) {
if s >= 32 {
// TODO: to support higher signal numbers, we need to turn
// receivedSignals into a uint32 array.
runtimePanicAt(returnAddress(0), "unsupported signal number")
runtimePanicAt(returnAddress(0), errUnsupportedSignal)
}
tinygo_signal_ignore(s)
}
Expand All @@ -409,7 +409,7 @@ func signal_disable(s uint32) {
if s >= 32 {
// TODO: to support higher signal numbers, we need to turn
// receivedSignals into a uint32 array.
runtimePanicAt(returnAddress(0), "unsupported signal number")
runtimePanicAt(returnAddress(0), errUnsupportedSignal)
}
tinygo_signal_disable(s)
}
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/runtime_wasmentry.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ var initializeCalled bool
func wasmExportCheckRun() {
switch {
case !initializeCalled:
runtimePanic("//go:wasmexport function called before runtime initialization")
runtimePanic(errWasmBeforeInit)
case mainExited:
runtimePanic("//go:wasmexport function called after main.main returned")
runtimePanic(errWasmAfterMain)
}
}

Expand All @@ -82,7 +82,7 @@ func wasmExportCheckRun() {
func wasmExportRun(done *bool) {
scheduler(true)
if !*done {
runtimePanic("//go:wasmexport function did not finish")
runtimePanic(errWasmDidNotFinish)
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/runtime/runtime_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,11 @@ func tinygo_init_exception_handler()
func tinygo_sigpanic_windows(exceptionCode int32) {
switch uint32(exceptionCode) {
case _EXCEPTION_ACCESS_VIOLATION, _EXCEPTION_IN_PAGE_ERROR:
runtimePanic("nil pointer dereference")
runtimePanic(errNilPointer)
case _EXCEPTION_INT_DIVIDE_BY_ZERO:
runtimePanic("divide by zero")
runtimePanic(errDivideByZero)
case _EXCEPTION_INT_OVERFLOW:
runtimePanic("integer overflow")
runtimePanic(errIntegerOverflow)
default:
runtimeFatal("unknown exception")
}
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/scheduler_none.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ func NumCPU() int {
}

func addTimer(tim *timerNode) {
runtimePanic("timers not supported without a scheduler")
runtimePanic(errTimersUnsupported)
}

func reAddTimer(tn *timerNode) {
runtimePanic("timers not supported without a scheduler")
runtimePanic(errTimersUnsupported)
}

func removeTimer(tim *timer) *timerNode {
runtimePanic("timers not supported without a scheduler")
runtimePanic(errTimersUnsupported)
return nil
}

Expand Down
Loading
Loading