Skip to content
Draft
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
4 changes: 2 additions & 2 deletions builder/testdata/binary-size.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
target package code rodata data bss
hifive1b examples/echo 4321 323 0 2268
microbit examples/serial 2842 382 8 2264
wioterminal examples/pininterrupt 8039 1665 132 7496
microbit examples/serial 2846 382 8 2264
wioterminal examples/pininterrupt 8075 1665 132 7504
14 changes: 7 additions & 7 deletions compiler/testdata/channel.ll
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ entry:
}

; Function Attrs: nounwind
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(40) %ch, ptr %context) unnamed_addr #1 {
entry:
%chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4
Expand All @@ -31,13 +31,13 @@ entry:
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(ptr nocapture) #2

declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
declare void @runtime.chanSend(ptr dereferenceable_or_null(40), ptr, ptr dereferenceable_or_null(16), ptr) #0

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(ptr nocapture) #2

; Function Attrs: nounwind
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(40) %ch, ptr %context) unnamed_addr #1 {
entry:
%chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4
Expand All @@ -49,10 +49,10 @@ entry:
ret void
}

declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(40), ptr, ptr dereferenceable_or_null(16), ptr) #0

; Function Attrs: nounwind
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(40) %ch, ptr %context) unnamed_addr #1 {
entry:
%chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
Expand All @@ -62,7 +62,7 @@ entry:
}

; Function Attrs: nounwind
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(40) %ch, ptr %context) unnamed_addr #1 {
entry:
%chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
Expand All @@ -72,7 +72,7 @@ entry:
}

; Function Attrs: nounwind
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(36) %ch1, ptr dereferenceable_or_null(36) %ch2, ptr %context) unnamed_addr #1 {
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(40) %ch1, ptr dereferenceable_or_null(40) %ch2, ptr %context) unnamed_addr #1 {
entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4
Expand Down
4 changes: 2 additions & 2 deletions compiler/testdata/goroutine-cortex-m-qemu-tasks.ll
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,13 @@ declare i32 @llvm.umin.i32(i32, i32) #7
declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #8

; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #0 {
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(40) %ch, ptr %context) unnamed_addr #0 {
entry:
call void @runtime.chanClose(ptr %ch, ptr undef) #11
ret void
}

declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
declare void @runtime.chanClose(ptr dereferenceable_or_null(40), ptr) #1

; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #0 {
Expand Down
4 changes: 2 additions & 2 deletions compiler/testdata/goroutine-wasm-asyncify.ll
Original file line number Diff line number Diff line change
Expand Up @@ -155,13 +155,13 @@ declare i32 @llvm.umin.i32(i32, i32) #7
declare void @llvm.memmove.p0.p0.i32(ptr nocapture writeonly, ptr nocapture readonly, i32, i1 immarg) #8

; Function Attrs: nounwind
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(40) %ch, ptr %context) unnamed_addr #1 {
entry:
call void @runtime.chanClose(ptr %ch, ptr undef) #11
ret void
}

declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #0
declare void @runtime.chanClose(ptr dereferenceable_or_null(40), ptr) #0

; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
Expand Down
8 changes: 4 additions & 4 deletions compiler/testdata/large.ll
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(48), ptr, ptr
declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(48), ptr, ptr, i32, ptr) #0

; Function Attrs: nounwind
define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(40) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%chan.op1 = alloca %runtime.channelOp, align 8
%chan.op = alloca %runtime.channelOp, align 8
Expand Down Expand Up @@ -425,15 +425,15 @@ if.then: ; preds = %entry
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(ptr nocapture) #8

declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
declare void @runtime.chanSend(ptr dereferenceable_or_null(40), ptr, ptr dereferenceable_or_null(16), ptr) #0

; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(ptr nocapture) #8

declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(40), ptr, ptr dereferenceable_or_null(16), ptr) #0

; Function Attrs: nounwind
define hidden i8 @main.selectLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
define hidden i8 @main.selectLargeChannel(ptr dereferenceable_or_null(40) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%select.block.alloca = alloca [2 x %runtime.channelOp], align 8
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
Expand Down
4 changes: 2 additions & 2 deletions compiler/testdata/pragma.ll
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ entry:

declare void @main.undefinedFunctionNotInSection(ptr) #0

declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(36), ptr nocapture, ptr) #0
declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(40), ptr nocapture, ptr) #0

; Function Attrs: nounwind
define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(36) %c, ptr %d, ptr %context) unnamed_addr #1 {
define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(40) %c, ptr %d, ptr %context) unnamed_addr #1 {
entry:
ret void
}
Expand Down
3 changes: 3 additions & 0 deletions make/test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ tinygo-test:
@# TestUnmarshalNestingLimit{Slice,Struct}: encoding/asn1 nesting limit added in
@# https://github.com/golang/go/commit/6a6d115f9a7422b2fa081ba6f567eefb4a099462
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(TEST_PACKAGES_HOST)) $(TEST_PACKAGES_SLOW)
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) -skip='^(TestReflectFuncOf|TestChannelMovedOutOfBubble|TestTimerFromInsideBubble|TestWaitGroupMovedIntoBubble|TestWaitGroupMovedOutOfBubble|TestWaitGroupMovedBetweenBubblesWithNonZeroCount)$$' internal/synctest
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) -skip='^(TestFatal|TestError|TestVerboseError|TestSkip|TestVerboseSkip|TestHelper|TestHTTPTransport100Continue)$$' testing/synctest
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) -run='^TestSynctestMarshal$$' encoding/json
ifeq ($(TEST_ENCODING_XML),true)
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) -stack-size=16MB encoding/xml
endif
Expand Down
37 changes: 37 additions & 0 deletions src/internal/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ type Task struct {
// since it falls into the padding of the FipsIndicator bit above.
RunState uint8

// SynctestBlocked is set while this task is durably blocked.
SynctestBlocked bool

// DeferFrame stores a pointer to the (stack allocated) defer frame of the
// goroutine that is used for the recover builtin.
DeferFrame unsafe.Pointer

// SynctestBubble identifies the synctest bubble this task belongs to.
SynctestBubble unsafe.Pointer
}

const (
Expand Down Expand Up @@ -74,3 +80,34 @@ func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer

//go:linkname scheduleTask runtime.scheduleTask
func scheduleTask(*Task)

func inheritSynctest(t *Task) {
if !synctestIsEnabled() {
return
}
parent := Current()
if parent == nil || parent.SynctestBubble == nil {
return
}
t.SynctestBubble = parent.SynctestBubble
synctestTaskCreated(t)
}

func exitSynctest(t *Task) {
if !synctestIsEnabled() {
return
}
if t.SynctestBubble != nil {
synctestTaskExited(t)
t.SynctestBubble = nil
}
}

//go:linkname synctestTaskCreated runtime.synctestTaskCreated
func synctestTaskCreated(*Task)

//go:linkname synctestTaskExited runtime.synctestTaskExited
func synctestTaskExited(*Task)

//go:linkname synctestIsEnabled runtime.synctestIsEnabled
func synctestIsEnabled() bool
1 change: 1 addition & 0 deletions src/internal/task/task_asyncify.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type stackState struct {
// The new goroutine is immediately started.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
inheritSynctest(t)
addLiveTask(t)
t.state.initialize(fn, args, stackSize)
scheduleTask(t)
Expand Down
1 change: 1 addition & 0 deletions src/internal/task/task_exit.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func Exit() {

func exit(goexit bool) {
t := Current()
exitSynctest(t)
remaining := atomic.AddUint32(&liveTasks, ^uint32(0))
if t == mainTask {
if goexit {
Expand Down
1 change: 1 addition & 0 deletions src/internal/task/task_stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var startTask [0]uint8
// The new goroutine is scheduled to run later.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
inheritSynctest(t)
addLiveTask(t)
t.state.initialize(fn, args, stackSize)
scheduleTask(t)
Expand Down
3 changes: 3 additions & 0 deletions src/internal/task/task_threads.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (t *Task) Resume() {
// Start a new OS thread.
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
t := &Task{}
inheritSynctest(t)
t.state.id = atomic.AddUintptr(&goroutineID, 1)
if verbose {
println("*** start: ", t.state.id, "from", Current().state.id)
Expand Down Expand Up @@ -132,6 +133,8 @@ func taskExited(t *Task) {
}

func exit(t *Task) bool {
exitSynctest(t)

// Remove from the queue.
// TODO: this can be made more efficient by using a doubly linked list.
activeTaskLock.Lock()
Expand Down
49 changes: 49 additions & 0 deletions src/runtime/chan.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type channel struct {
receivers chanQueue
lock task.PMutex
buf unsafe.Pointer
synctest unsafe.Pointer
}

const (
Expand Down Expand Up @@ -142,6 +143,23 @@ func chanMake(elementSize uintptr, bufSize uintptr) *channel {
elementSize: elementSize,
bufCap: bufSize,
buf: alloc(elementSize*bufSize, nil),
synctest: currentTaskSynctestBubble(),
}
}

func currentTaskSynctestBubble() unsafe.Pointer {
if !synctestEnabled {
return nil
}
if current := task.Current(); current != nil {
return current.SynctestBubble
}
return nil
}

func (ch *channel) checkSynctest(op string) {
if synctestEnabled && ch.synctest != nil && currentTaskSynctestBubble() != ch.synctest {
runtimeFatal(op + " synctest channel from outside bubble")
}
}

Expand Down Expand Up @@ -231,8 +249,10 @@ func (ch *channel) trySend(value unsafe.Pointer) (sent bool, wake *task.Task) {
func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
if ch == nil {
// A nil channel blocks forever. Do not schedule this goroutine again.
synctestTaskBlock(task.Current())
deadlock()
}
ch.checkSynctest("send on")

mask := interrupt.Disable()
ch.lock.Lock()
Expand All @@ -254,6 +274,9 @@ func chanSend(ch *channel, value unsafe.Pointer, op *channelOp) {
op.index = 0
op.value = value
ch.senders.push(op)
if synctestEnabled && ch.synctest != nil {
synctestTaskBlock(t)
}
ch.lock.Unlock()
interrupt.Restore(mask)

Expand Down Expand Up @@ -311,8 +334,10 @@ func (ch *channel) tryRecv(value unsafe.Pointer) (received, ok bool, wake *task.
func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
if ch == nil {
// A nil channel blocks forever. Do not schedule this goroutine again.
synctestTaskBlock(task.Current())
deadlock()
}
ch.checkSynctest("receive on")

mask := interrupt.Disable()
ch.lock.Lock()
Expand All @@ -334,6 +359,9 @@ func chanRecv(ch *channel, value unsafe.Pointer, op *channelOp) bool {
op.task = t
op.index = 0
ch.receivers.push(op)
if synctestEnabled && ch.synctest != nil {
synctestTaskBlock(t)
}
ch.lock.Unlock()
interrupt.Restore(mask)

Expand All @@ -351,6 +379,7 @@ func chanClose(ch *channel) {
// Not allowed by the language spec.
runtimePanic("close of nil channel")
}
ch.checkSynctest("close of")

mask := interrupt.Disable()
ch.lock.Lock()
Expand Down Expand Up @@ -457,6 +486,12 @@ func unlockAllStates(states []chanSelectState) {
// The 'ops' slice must be set if (and only if) this is a blocking select.
func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelOp) (uint32, bool) {
mask := interrupt.Disable()
var currentBubble unsafe.Pointer
var synctestDurable bool
if synctestEnabled {
currentBubble = currentTaskSynctestBubble()
synctestDurable = currentBubble != nil
}

// Lock everything.
chanSelectLock.Lock()
Expand All @@ -475,6 +510,17 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
// operation.
continue
}
if synctestEnabled {
if state.ch.synctest != nil && state.ch.synctest != currentBubble {
unlockAllStates(states)
chanSelectLock.Unlock()
interrupt.Restore(mask)
runtimeFatal("select on synctest channel from outside bubble")
}
if state.ch.synctest == nil {
synctestDurable = false
}
}

if state.value == nil { // chan receive
if received, ok, sender := state.ch.tryRecv(recvbuf); received {
Expand Down Expand Up @@ -528,6 +574,9 @@ func chanSelect(recvbuf unsafe.Pointer, states []chanSelectState, ops []channelO
state.ch.senders.push(op)
}
}
if synctestDurable {
synctestTaskBlock(t)
}

// Now we wait until one of the send/receive operations can proceed.
unlockAllStates(states)
Expand Down
8 changes: 7 additions & 1 deletion src/runtime/scheduler_cooperative.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ var finalizerIdleGC func() bool
func deadlock() {
// Keep permanently blocked tasks reachable so their suspended stacks remain
// GC roots, but never put them back on the runnable queue.
deadlockedTasks.Push(task.Current())
current := task.Current()
synctestTaskBlock(current)
deadlockedTasks.Push(current)
task.Pause()
runtimeFatal("unreachable")
}
Expand Down Expand Up @@ -94,6 +96,7 @@ func goexit() {

// Add this task to the end of the run queue.
func scheduleTask(t *task.Task) {
synctestTaskWake(t)
runqueue.Push(t)
}

Expand Down Expand Up @@ -297,6 +300,9 @@ func sleep(duration int64) {
if duration <= 0 {
return
}
if synctestSleep(duration) {
return
}
addSleepTask(task.Current(), nanosecondsToTicks(duration))
task.Pause()
}
Expand Down
Loading
Loading