diff --git a/GNUmakefile b/GNUmakefile index 28031ffec5..80d6a6e896 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -362,6 +362,7 @@ TEST_PACKAGES_FAST = \ # debug/plan9obj requires os.ReadAt, which is not yet supported on windows # image requires recover(), which is not yet supported on wasi # io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi +# math/bits: needs panic()/recover() # mime: fail on wasi; neds panic()/recover() # mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK # mime/quotedprintable requires syscall.Faccessat @@ -382,8 +383,11 @@ TEST_PACKAGES_LINUX := \ crypto/hmac \ debug/dwarf \ debug/plan9obj \ + encoding/binary \ + go/constant \ image \ io/ioutil \ + math/bits \ mime \ mime/multipart \ mime/quotedprintable \ @@ -403,6 +407,9 @@ TEST_PACKAGES_WINDOWS := \ compress/flate \ crypto/des \ crypto/hmac \ + encoding/binary \ + go/constant \ + math/bits \ strconv \ text/template/parse \ $(nil) diff --git a/compiler/asserts.go b/compiler/asserts.go index f07b73bc26..2de9a81283 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -31,7 +31,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) { // Now do the bounds check: index >= arrayLen outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "") - b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic") + b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic", true) } // createSliceBoundsCheck emits a bounds check before a slicing operation to make @@ -74,7 +74,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap") outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax") outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap") - b.createRuntimeAssert(outOfBounds, "slice", "slicePanic") + b.createRuntimeAssert(outOfBounds, "slice", "slicePanic", false) } // createSliceToArrayPointerCheck adds a check for slice-to-array pointer @@ -86,7 +86,7 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i // > run-time panic occurs. arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false) isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "") - b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic") + b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic", false) } // createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice @@ -118,7 +118,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "") assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "") assert = b.CreateOr(assert, lenOutOfBounds, "") - b.createRuntimeAssert(assert, name, "unsafeSlicePanic") + b.createRuntimeAssert(assert, name, "unsafeSlicePanic", false) } // createChanBoundsCheck creates a bounds check before creating a new channel to @@ -155,7 +155,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, // Do the check for a too large (or negative) buffer size. bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "") - b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic") + b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic", false) } // createNilCheck checks whether the given pointer is nil, and panics if it is. @@ -199,7 +199,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "") // Emit the nil check in IR. - b.createRuntimeAssert(isnil, blockPrefix, "nilPanic") + b.createRuntimeAssert(isnil, blockPrefix, "nilPanic", false) } // createNegativeShiftCheck creates an assertion that panics if the given shift value is negative. @@ -212,7 +212,7 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) { // isNegative = shift < 0 isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "") - b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic") + b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic", false) } // createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic @@ -225,12 +225,12 @@ func (b *builder) createDivideByZeroCheck(y llvm.Value) { // isZero = y == 0 isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "") - b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic") + b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic", true) } // createRuntimeAssert is a common function to create a new branch on an assert // bool, calling an assert func if the assert value is true (1). -func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) { +func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string, isInvoke bool) { // Check whether we can resolve this check at compile time. if !assert.IsAConstantInt().IsNil() { val := assert.ZExtValue() @@ -245,17 +245,17 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc // current insert position. faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw") nextBlock := b.insertBasicBlock(blockPrefix + ".next") - b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes // Now branch to the out-of-bounds or the regular block. b.CreateCondBr(assert, faultBlock, nextBlock) // Fail: the assert triggered so panic. b.SetInsertPointAtEnd(faultBlock) - b.createRuntimeCall(assertFunc, nil, "") + b.createRuntimeCallCommon(assertFunc, nil, "", isInvoke) b.CreateUnreachable() // Ok: assert didn't trigger so continue normally. + b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes b.SetInsertPointAtEnd(nextBlock) } diff --git a/compiler/symbol.go b/compiler/symbol.go index 1de3c6f39d..8360bfc661 100644 --- a/compiler/symbol.go +++ b/compiler/symbol.go @@ -688,20 +688,25 @@ func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo { // Check for //go: pragmas, which may change the link name (among others). doc := c.astComments[info.linkName] if doc != nil { - info.parsePragmas(doc) + info.parsePragmas(doc, g) } return info } // Parse //go: pragma comments from the source. In particular, it parses the // //go:extern pragma on globals. -func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) { +func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, g *ssa.Global) { for _, comment := range doc.List { if !strings.HasPrefix(comment.Text, "//go:") { continue } parts := strings.Fields(comment.Text) switch parts[0] { + case "//go:linkname": + if len(parts) == 3 && g.Name() == parts[1] { + info.linkName = parts[2] + info.extern = true + } case "//go:extern": info.extern = true if len(parts) == 2 { diff --git a/compiler/testdata/pragma.go b/compiler/testdata/pragma.go index 1e6e967f53..c44f26457a 100644 --- a/compiler/testdata/pragma.go +++ b/compiler/testdata/pragma.go @@ -2,6 +2,12 @@ package main import _ "unsafe" +// Use the go:linkname mechanism to link this global to a different package. +// This is used in math/bits. +// +//go:linkname linknamedGlobal runtime.testLinknamedGlobal +var linknamedGlobal int + // Creates an external global with name extern_global. // //go:extern extern_global diff --git a/compiler/testdata/pragma.ll b/compiler/testdata/pragma.ll index a3cbb72c1d..38d99c3b3a 100644 --- a/compiler/testdata/pragma.ll +++ b/compiler/testdata/pragma.ll @@ -3,6 +3,7 @@ source_filename = "pragma.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" +@runtime.testLinknamedGlobal = external global i32, align 4 @extern_global = external global [0 x i8], align 1 @main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32 @main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16 diff --git a/src/runtime/error.go b/src/runtime/error.go index 3ae5ea3aae..20c8faa426 100644 --- a/src/runtime/error.go +++ b/src/runtime/error.go @@ -4,5 +4,23 @@ package runtime type Error interface { error + // Method to indicate this is indeed a runtime error. RuntimeError() } + +type runtimeError struct { + msg string +} + +func (r runtimeError) Error() string { + return r.msg +} + +// Purely here to satisfy the Error interface. +func (r runtimeError) RuntimeError() {} + +var ( + divideError error = runtimeError{"runtime error: integer divide by zero"} + lookupError error = runtimeError{"runtime error: index out of range"} + overflowError error = runtimeError{"runtime error: integer overflow"} +) diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 9ae1f982b9..fb5cbceab4 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -187,7 +187,7 @@ func nilMapPanic() { // Panic when trying to access an array or slice out of bounds. func lookupPanic() { - runtimePanicAt(returnAddress(0), "index out of range") + _panic(lookupError) } // Panic when trying to slice a slice out of bounds. @@ -220,7 +220,7 @@ func negativeShiftPanic() { // Panic when there is a divide by zero. func divideByZeroPanic() { - runtimePanicAt(returnAddress(0), "divide by zero") + _panic(divideError) } func blockingPanic() { diff --git a/testdata/recover.go b/testdata/recover.go index 6fdf282e7b..7d335fbbfc 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -30,6 +30,10 @@ func main() { println("\n# defer panic") deferPanic() + println("\n# runtime panics") + runtimePanicDivByZero(1, 0) + runtimePanicLookup([]int{1, 2, 3}, 10) + println("\n# runtime.Goexit") runtimeGoexit() } @@ -114,6 +118,26 @@ func deferPanic() { println("defer panic") } +func runtimePanicDivByZero(a, b int) int { + defer func() { + if err := recover(); err != nil { + println("recovered:", err) + } + }() + + return a / b +} + +func runtimePanicLookup(slice []int, index int) int { + defer func() { + if err := recover(); err != nil { + println("recovered:", err) + } + }() + + return slice[index] +} + func runtimeGoexit() { wg.Add(1) go func() {