compiler: make map and channel panics recoverable

Change hashmap set operations and channel send/close from
createRuntimeCall to createRuntimeInvoke. This places a setjmp
checkpoint before each call, allowing runtimePanicAt to safely
longjmp when these operations panic.
This commit is contained in:
Jake Bailey
2026-05-10 15:55:36 -07:00
committed by Ron Evans
parent eed4afda63
commit 039f48f3d2
4 changed files with 29 additions and 4 deletions
+2 -2
View File
@@ -48,7 +48,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the send. // Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
// End the lifetime of the allocas. // End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8: // This also works around a bug in CoroSplit, at least in LLVM 8:
@@ -101,7 +101,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// createChanClose closes the given channel. // createChanClose closes the given channel.
func (b *builder) createChanClose(ch llvm.Value) { func (b *builder) createChanClose(ch llvm.Value) {
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "") b.createRuntimeInvoke("chanClose", []llvm.Value{ch}, "")
} }
// createSelect emits all IR necessary for a select statements. That's a // createSelect emits all IR necessary for a select statements. That's a
+2 -2
View File
@@ -133,7 +133,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, valueAlloca} params := []llvm.Value{m, key, valueAlloca}
b.createRuntimeCall("hashmapStringSet", params, "") b.createRuntimeInvoke("hashmapStringSet", params, "")
} else { } else {
// Key stored at actual type. // Key stored at actual type.
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
@@ -143,7 +143,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
fnName = "hashmapGenericSet" fnName = "hashmapGenericSet"
} }
params := []llvm.Value{m, keyAlloca, valueAlloca} params := []llvm.Value{m, keyAlloca, valueAlloca}
b.createRuntimeCall(fnName, params, "") b.createRuntimeInvoke(fnName, params, "")
b.emitLifetimeEnd(keyAlloca, keySize) b.emitLifetimeEnd(keyAlloca, keySize)
} }
b.emitLifetimeEnd(valueAlloca, valueSize) b.emitLifetimeEnd(valueAlloca, valueSize)
+20
View File
@@ -41,6 +41,9 @@ func main() {
println("\n# recover runtime errors") println("\n# recover runtime errors")
recoverRuntimeError() recoverRuntimeError()
println("\n# recover from nil map and closed channel")
recoverNilMapAndChan()
} }
func recoverSimple() { func recoverSimple() {
@@ -241,3 +244,20 @@ func recoverMustPanic(name string, f func()) {
}() }()
f() f()
} }
// Test recovering from nil map assignment and closed channel send.
func recoverNilMapAndChan() {
recoverMustPanic("nil map", func() {
var m map[string]int
m["x"] = 1
})
recoverMustPanic("closed chan", func() {
ch := make(chan int)
close(ch)
ch <- 1
})
recoverMustPanic("close nil chan", func() {
var ch chan int
close(ch)
})
}
+5
View File
@@ -44,3 +44,8 @@ outer recovered: repanic value
recovered: slice recovered: slice
recovered: type assert recovered: type assert
recovered: empty interface type assert recovered: empty interface type assert
# recover from nil map and closed channel
recovered: nil map
recovered: closed chan
recovered: close nil chan