diff --git a/GNUmakefile b/GNUmakefile index 627640a520..5aa40a9c61 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -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 \ @@ -365,6 +367,7 @@ TEST_PACKAGES_FAST = \ hash/crc64 \ hash/fnv \ html \ + image \ internal/itoa \ internal/profile \ math \ @@ -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 \ @@ -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 @@ -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 \ @@ -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) @@ -461,6 +448,7 @@ TEST_PACKAGES_NONWASM = \ embed/internal/embedtest \ expvar \ go/format \ + image \ os \ testing \ $(nil) @@ -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 @@ -510,7 +498,7 @@ 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. @@ -518,7 +506,6 @@ TEST_ADDITIONAL_FLAGS ?= .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 diff --git a/builder/build.go b/builder/build.go index 104e0f3866..da320ac17a 100644 --- a/builder/build.go +++ b/builder/build.go @@ -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 diff --git a/builder/config.go b/builder/config.go index 4dcc2786e8..8aa2363336 100644 --- a/builder/config.go +++ b/builder/config.go @@ -1,6 +1,7 @@ package builder import ( + "errors" "fmt" "runtime" @@ -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 } diff --git a/builder/sizes_test.go b/builder/sizes_test.go index 639c32b5c2..40e5b5f4e7 100644 --- a/builder/sizes_test.go +++ b/builder/sizes_test.go @@ -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 diff --git a/compileopts/config.go b/compileopts/config.go index 7786cd2178..f20672c476 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -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": @@ -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. diff --git a/compileopts/options.go b/compileopts/options.go index cb5f24d5c0..9b06611c55 100644 --- a/compileopts/options.go +++ b/compileopts/options.go @@ -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"} ) @@ -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 @@ -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, ", ")) diff --git a/compileopts/options_test.go b/compileopts/options_test.go index dd098e6c4a..094da03593 100644 --- a/compileopts/options_test.go +++ b/compileopts/options_test.go @@ -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 @@ -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 { diff --git a/compileopts/target.go b/compileopts/target.go index 92b011b830..f1e9504f87 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -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"` @@ -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") diff --git a/compileopts/target_test.go b/compileopts/target_test.go index 315ccf4e49..b6e2882f0c 100644 --- a/compileopts/target_test.go +++ b/compileopts/target_test.go @@ -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) + } + }) + } +} diff --git a/compiler/asserts.go b/compiler/asserts.go index 7e3a8b1504..4cacd13aff 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -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 } diff --git a/compiler/calls.go b/compiler/calls.go index 257973320e..fb3f8a8486 100644 --- a/compiler/calls.go +++ b/compiler/calls.go @@ -1,6 +1,7 @@ package compiler import ( + "go/token" "go/types" "strconv" @@ -43,20 +44,27 @@ const ( // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeInvoke instead. func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value { - member := b.program.ImportedPackage("runtime").Members[fnName] + fnType, llvmFn := b.getRuntimeFunction(fnName) + args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter + if isInvoke { + // chanSend is the only panic-capable runtime operation that can also + // suspend the task. + return b.createInvokeWithAnalysis(fnType, llvmFn, args, name, true, fnName == "chanSend") + } + return b.createCall(fnType, llvmFn, args, name) +} + +func (b *builder) getRuntimeFunction(name string) (llvm.Type, llvm.Value) { + member := b.program.ImportedPackage("runtime").Members[name] if member == nil { - panic("unknown runtime call: " + fnName) + panic("unknown runtime call: " + name) } fn := member.(*ssa.Function) fnType, llvmFn := b.getFunction(fn) if llvmFn.IsNil() { panic("trying to call non-existent function: " + fn.RelString(nil)) } - args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter - if isInvoke { - return b.createInvoke(fnType, llvmFn, args, name) - } - return b.createCall(fnType, llvmFn, args, name) + return fnType, llvmFn } // createRuntimeCall creates a new call to runtime. with the given @@ -77,11 +85,7 @@ func (b *builder) createRuntimeInvoke(fnName string, args []llvm.Value, name str // createCall creates a call to the given function with the arguments possibly // expanded. func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { - expanded := make([]llvm.Value, 0, len(args)) - for _, arg := range args { - fragments := b.expandFormalParam(arg) - expanded = append(expanded, fragments...) - } + expanded := b.expandFormalParams(args) call := b.CreateCall(fnType, fn, expanded, name) if !fn.IsAFunction().IsNil() { if cc := fn.FunctionCallConv(); cc != llvm.CCallConv { @@ -93,15 +97,447 @@ func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value, return call } -// createInvoke is like createCall but continues execution at the landing pad if -// the call resulted in a panic. -func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { +func (b *builder) expandFormalParams(args []llvm.Value) []llvm.Value { + expanded := make([]llvm.Value, 0, len(args)) + for _, arg := range args { + fragments := b.expandFormalParam(arg) + expanded = append(expanded, fragments...) + } + return expanded +} + +// createInvoke emits a Go call, adding unwind handling only when the static +// call graph indicates the call can unwind. +func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string, call *ssa.CallCommon) llvm.Value { + properties := b.callPropertiesFor(call) + return b.createInvokeWithAnalysis(fnType, fn, args, name, properties&callMayUnwind != 0, properties&callMaySuspend != 0) +} + +func (b *builder) createInvokeWithAnalysis(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string, mayUnwind, maySuspend bool) llvm.Value { + if !mayUnwind { + return b.createCall(fnType, fn, args, name) + } + if b.usesReturnUnwind() { + var result llvm.Value + if b.usesAsyncifyUnwind() && b.hasDeferFrame() { + if maySuspend { + result = b.createAsyncifySuspendInvoke(fnType, fn, args, name) + } else { + result = b.createAsyncifyNoSuspendInvoke(fnType, fn, args, name) + } + } else { + result = b.createCall(fnType, fn, args, name) + } + if b.needsPostCallUnwindCheck() { + b.createUnwindCheck(true) + } + + return result + } if b.hasDeferFrame() { b.createInvokeCheckpoint() } return b.createCall(fnType, fn, args, name) } +func (b *builder) createAsyncifyNoSuspendInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { + expanded := b.expandFormalParams(args) + paramTypes := append([]llvm.Type(nil), fnType.ParamTypes()...) + direct := !fn.IsAFunction().IsNil() + if !direct { + paramTypes = append([]llvm.Type{fn.Type()}, paramTypes...) + } + wrapperType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) + wrapperName := b.llvmFn.Name() + ".asyncifycatch." + strconv.Itoa(b.asyncifyCatchIndex) + b.asyncifyCatchIndex++ + wrapper := llvm.AddFunction(b.mod, wrapperName, wrapperType) + wrapper.SetLinkage(llvm.InternalLinkage) + wrapper.AddFunctionAttr(b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)) + + builder := b.ctx.NewBuilder() + entry := b.ctx.AddBasicBlock(wrapper, "entry") + builder.SetInsertPointAtEnd(entry) + target := fn + paramOffset := 0 + if !direct { + target = wrapper.Param(0) + paramOffset = 1 + } + callArgs := make([]llvm.Value, len(fnType.ParamTypes())) + for i := range callArgs { + callArgs[i] = wrapper.Param(i + paramOffset) + } + result := builder.CreateCall(fnType, target, callArgs, "") + unwindType, unwindLLVMFn := b.getRuntimeFunction("unwindPending") + unwinding := builder.CreateCall(unwindType, unwindLLVMFn, []llvm.Value{llvm.Undef(b.dataPtrType)}, "") + b.emitAsyncifyCatchReturn(builder, fnType, result, entry, unwinding) + builder.Dispose() + + wrapperArgs := expanded + if !direct { + wrapperArgs = append([]llvm.Value{fn}, expanded...) + } + return b.CreateCall(wrapperType, wrapper, wrapperArgs, name) +} + +func (b *builder) createAsyncifySuspendInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { + // Binaryen does not instrument a function containing stop_unwind, so a + // suspend-capable call needs two wrappers: + // + // defer caller (instrumented) + // | + // | volatile indirect call + // v + // panic catcher (not instrumented; contains stop_unwind) + // | + // v + // target trampoline (instrumented) + // | + // | indirect call + // v + // real target + // + // A scheduler unwind crosses both wrappers. A panic unwind stops in the + // catcher, then the caller branches to its defer landing pad. + expanded := b.expandFormalParams(args) + paramTypes := append([]llvm.Type{fn.Type()}, fnType.ParamTypes()...) + wrapperType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) + wrapperArgs := append([]llvm.Value{fn}, expanded...) + if catchGlobal, ok := b.asyncifyCatchers[wrapperType]; ok { + catchPointer := b.CreateLoad(catchGlobal.GlobalValueType(), catchGlobal, "") + catchPointer.SetVolatile(true) + return b.CreateCall(wrapperType, catchPointer, wrapperArgs, name) + } + wrapperName := b.llvmFn.Name() + ".asyncifysuspendcatch." + strconv.Itoa(b.asyncifyCatchIndex) + b.asyncifyCatchIndex++ + + targetType := wrapperType + var resultGlobal llvm.Value + if fnType.ReturnType().TypeKind() != llvm.VoidTypeKind { + targetType = llvm.FunctionType(b.ctx.VoidType(), paramTypes, false) + resultGlobal = llvm.AddGlobal(b.mod, b.dataPtrType, wrapperName+".result.ptr") + resultGlobal.SetLinkage(llvm.InternalLinkage) + resultGlobal.SetInitializer(llvm.ConstNull(b.dataPtrType)) + } + targetWrapper := llvm.AddFunction(b.mod, wrapperName+".target", targetType) + targetWrapper.SetLinkage(llvm.InternalLinkage) + targetWrapper.AddFunctionAttr(b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)) + builder := b.ctx.NewBuilder() + entry := b.ctx.AddBasicBlock(targetWrapper, "entry") + builder.SetInsertPointAtEnd(entry) + target := targetWrapper.Param(0) + callArgs := make([]llvm.Value, len(fnType.ParamTypes())) + for i := range callArgs { + callArgs[i] = targetWrapper.Param(i + 1) + } + result := builder.CreateCall(fnType, target, callArgs, "") + if fnType.ReturnType().TypeKind() == llvm.VoidTypeKind { + builder.CreateRetVoid() + } else { + resultPointer := builder.CreateLoad(b.dataPtrType, resultGlobal, "") + resultPointer.SetVolatile(true) + builder.CreateStore(result, resultPointer) + builder.CreateRetVoid() + } + builder.Dispose() + + catchWrapper := llvm.AddFunction(b.mod, wrapperName+".paniccatch", wrapperType) + catchWrapper.SetLinkage(llvm.InternalLinkage) + catchWrapper.AddFunctionAttr(b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)) + builder = b.ctx.NewBuilder() + entry = b.ctx.AddBasicBlock(catchWrapper, "entry") + builder.SetInsertPointAtEnd(entry) + catchArgs := make([]llvm.Value, len(paramTypes)) + for i := range catchArgs { + catchArgs[i] = catchWrapper.Param(i) + } + if fnType.ReturnType().TypeKind() == llvm.VoidTypeKind { + result = builder.CreateCall(targetType, targetWrapper, catchArgs, "") + } else { + // Asyncify can restore a stale hidden result pointer during rewind. Use + // a volatile slot to select the current catcher's storage, restoring its + // previous value before the unwind can reach the scheduler. This also + // makes recursive use safe. + resultStorage := builder.CreateAlloca(fnType.ReturnType(), "") + previousResultPointer := builder.CreateLoad(b.dataPtrType, resultGlobal, "") + previousResultPointer.SetVolatile(true) + storeResultPointer := builder.CreateStore(resultStorage, resultGlobal) + storeResultPointer.SetVolatile(true) + builder.CreateCall(targetType, targetWrapper, catchArgs, "") + restoreResultPointer := builder.CreateStore(previousResultPointer, resultGlobal) + restoreResultPointer.SetVolatile(true) + result = builder.CreateLoad(fnType.ReturnType(), resultStorage, "") + } + unwindType, unwindLLVMFn := b.getRuntimeFunction("unwindPending") + // This call must stay opaque to AddUnwindAssumptions. This catcher observes + // the signal set by its target, so assuming a clear signal on entry would + // let LLVM remove the check. + unwindGlobal := llvm.AddGlobal(b.mod, unwindLLVMFn.Type(), wrapperName+".unwind.ptr") + unwindGlobal.SetLinkage(llvm.InternalLinkage) + unwindGlobal.SetInitializer(unwindLLVMFn) + unwindPointer := builder.CreateLoad(unwindLLVMFn.Type(), unwindGlobal, "") + unwindPointer.SetVolatile(true) + unwinding := builder.CreateCall(unwindType, unwindPointer, []llvm.Value{llvm.Undef(b.dataPtrType)}, "") + b.emitAsyncifyCatchReturn(builder, fnType, result, entry, unwinding) + builder.Dispose() + + // Keep the caller-to-catcher edge opaque so Binaryen instruments the + // indirect call instead of treating it as a call into bottommost runtime. + catchGlobal := llvm.AddGlobal(b.mod, catchWrapper.Type(), wrapperName+".paniccatch.ptr") + catchGlobal.SetLinkage(llvm.InternalLinkage) + catchGlobal.SetInitializer(catchWrapper) + b.asyncifyCatchers[wrapperType] = catchGlobal + catchPointer := b.CreateLoad(catchWrapper.Type(), catchGlobal, "") + catchPointer.SetVolatile(true) + return b.CreateCall(wrapperType, catchPointer, wrapperArgs, name) +} + +func (b *builder) emitAsyncifyCatchReturn(builder llvm.Builder, fnType llvm.Type, result llvm.Value, entry llvm.BasicBlock, unwinding llvm.Value) { + wrapper := entry.Parent() + stopBlock := b.ctx.AddBasicBlock(wrapper, "unwind.stop") + returnBlock := b.ctx.AddBasicBlock(wrapper, "return") + builder.CreateCondBr(unwinding, stopBlock, returnBlock) + + builder.SetInsertPointAtEnd(stopBlock) + stopType, stopFn := b.getRuntimeFunction("asyncifyStopUnwindImport") + builder.CreateCall(stopType, stopFn, nil, "") + builder.CreateBr(returnBlock) + + builder.SetInsertPointAtEnd(returnBlock) + if fnType.ReturnType().TypeKind() == llvm.VoidTypeKind { + builder.CreateRetVoid() + return + } + phi := builder.CreatePHI(fnType.ReturnType(), "") + phi.AddIncoming([]llvm.Value{result, result}, []llvm.BasicBlock{entry, stopBlock}) + builder.CreateRet(phi) +} + +type callProperties uint8 + +const ( + callMaySuspend callProperties = 1 << iota + callMayUnwind +) + +type functionCallProperties struct { + properties callProperties + visiting bool + complete bool +} + +func (b *builder) functionCallProperties(fn *ssa.Function) callProperties { + analysis := b.callProperties[fn] + if analysis.complete { + return analysis.properties + } + if analysis.visiting || len(fn.Blocks) == 0 { + return callMaySuspend | callMayUnwind + } + + analysis.visiting = true + b.callProperties[fn] = analysis + + var properties callProperties + if fn.Pkg != nil && fn.Pkg.Pkg.Path() == "internal/task" && fn.Name() == "Pause" { + properties |= callMaySuspend + } + for _, block := range fn.Blocks { + if block == nil { + continue + } + for _, instruction := range block.Instrs { + properties |= instructionCallProperties(instruction) + call, ok := instruction.(ssa.CallInstruction) + if !ok { + continue + } + common := call.Common() + if builtin, ok := common.Value.(*ssa.Builtin); ok { + switch builtin.Name() { + case "close", "delete", "panic": + properties |= callMayUnwind + } + continue + } + callee := common.StaticCallee() + if callee == nil { + properties |= callMaySuspend | callMayUnwind + } else { + properties |= b.functionCallProperties(callee) + } + if properties == callMaySuspend|callMayUnwind { + break + } + } + } + + b.callProperties[fn] = functionCallProperties{ + properties: properties, + complete: true, + } + return properties +} + +func (b *builder) callPropertiesFor(call *ssa.CallCommon) callProperties { + callee := call.StaticCallee() + if callee == nil { + return callMaySuspend | callMayUnwind + } + return b.functionCallProperties(callee) +} + +func (b *builder) functionMaySuspend(fn *ssa.Function) bool { + return b.functionCallProperties(fn)&callMaySuspend != 0 +} + +func (b *builder) functionMayUnwind(fn *ssa.Function) bool { + return b.functionCallProperties(fn)&callMayUnwind != 0 +} + +func instructionCallProperties(instruction ssa.Instruction) callProperties { + switch instruction := instruction.(type) { + case *ssa.Alloc, *ssa.Call, *ssa.ChangeInterface, *ssa.ChangeType, + *ssa.Convert, *ssa.DebugRef, *ssa.Defer, *ssa.Extract, *ssa.Field, + *ssa.Go, *ssa.If, *ssa.Jump, *ssa.MakeClosure, *ssa.MakeInterface, + *ssa.MakeMap, *ssa.Phi, *ssa.Range, *ssa.Return, *ssa.RunDefers: + return 0 + case *ssa.Send: + return callMaySuspend | callMayUnwind + case *ssa.Next: + return callMaySuspend + case *ssa.Select: + return callMaySuspend | callMayUnwind + case *ssa.FieldAddr, *ssa.Index, *ssa.IndexAddr, *ssa.Lookup, + *ssa.MakeChan, *ssa.MakeSlice, *ssa.MapUpdate, *ssa.Panic, + *ssa.Slice, *ssa.SliceToArrayPointer, *ssa.Store, *ssa.TypeAssert: + return callMayUnwind + case *ssa.BinOp: + if binOpMayUnwind(instruction) { + return callMayUnwind + } + case *ssa.UnOp: + var properties callProperties + if instruction.Op == token.ARROW { + properties |= callMaySuspend + } + if instruction.Op == token.MUL { + properties |= callMayUnwind + } + return properties + } + // New SSA instructions must be treated conservatively until their lowering + // is audited for suspension and unwind paths. + return callMaySuspend | callMayUnwind +} + +func binOpMayUnwind(instruction *ssa.BinOp) bool { + switch instruction.Op { + case token.QUO, token.REM: + basic, ok := instruction.X.Type().Underlying().(*types.Basic) + return ok && basic.Info()&types.IsInteger != 0 + case token.SHL, token.SHR: + basic, ok := instruction.Y.Type().Underlying().(*types.Basic) + return ok && basic.Info()&types.IsUnsigned == 0 + case token.EQL, token.NEQ: + return typeMayPanicOnCompare(instruction.X.Type()) + default: + return false + } +} + +func typeMayPanicOnCompare(typ types.Type) bool { + switch typ := typ.Underlying().(type) { + case *types.Interface: + return true + case *types.Array: + return typeMayPanicOnCompare(typ.Elem()) + case *types.Struct: + for i := 0; i < typ.NumFields(); i++ { + if typeMayPanicOnCompare(typ.Field(i).Type()) { + return true + } + } + } + return false +} + +func (b *builder) inFunctionBody() bool { + block := b.GetInsertBlock() + return b.loweringBody && !b.llvmFn.IsNil() && !block.IsNil() && block.Parent() == b.llvmFn +} + +func (b *builder) needsPostCallUnwindCheck() bool { + if !b.inFunctionBody() || b.runningDefers || b.isUnwindRuntime() { + return false + } + // Explicit unwinding propagates through every caller. Asyncify already + // unwinds ordinary callers itself; only a defer frame stops it and needs a + // check that branches to the landing pad. + return !b.usesAsyncifyUnwind() || b.hasDeferFrame() +} + +func (b *builder) isUnwindRuntime() bool { + if !b.usesReturnUnwind() { + return false + } + if b.fn.Pkg == nil { + return false + } + if b.fn.Pkg.Pkg.Path() != "runtime" { + return false + } + switch b.fn.Name() { + case "startUnwind", "currentDeferFrame", "unwindPending", "clearUnwind", + "getUnwindSignal", "setUnwindSignal": + return true + default: + return false + } +} + +// When catch is true, route the unwind to this function's defer landing pad. +func (b *builder) createUnwindCheck(catch bool) { + unwind := b.createRuntimeCall("unwindPending", nil, "unwind") + continueBB := b.insertBasicBlock("unwind.continue") + if catch && b.hasDeferFrame() { + b.CreateCondBr(unwind, b.landingpad, continueBB) + } else { + b.CreateCondBr(unwind, b.unwindReturnBlock(), continueBB) + } + + b.SetInsertPointAtEnd(continueBB) + if !b.inFaultBlock { + b.currentBlockInfo.exit = continueBB + } +} + +func (b *builder) unwindReturnBlock() llvm.BasicBlock { + if !b.unwindReturn.IsNil() { + return b.unwindReturn + } + + savedBlock := b.GetInsertBlock() + b.unwindReturn = b.ctx.AddBasicBlock(b.llvmFn, "unwind.return") + b.SetInsertPointAtEnd(b.unwindReturn) + returnType := b.llvmFn.GlobalValueType().ReturnType() + if returnType.TypeKind() == llvm.VoidTypeKind { + b.CreateRetVoid() + } else { + b.CreateRet(llvm.Undef(returnType)) + } + b.SetInsertPointAtEnd(savedBlock) + return b.unwindReturn +} + +func (b *builder) createUnwindReturnOrUnreachable() { + if b.usesAsyncifyUnwind() { + b.CreateBr(b.unwindReturnBlock()) + } else { + b.CreateUnreachable() + } +} + // Expand an argument type to a list that can be used in a function call // parameter list. func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { diff --git a/compiler/compiler.go b/compiler/compiler.go index 19410d49ea..97b90535b2 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -60,6 +60,7 @@ type Config struct { Debug bool // Whether to emit debug information in the LLVM module. Nobounds bool // Whether to skip bounds checks PanicStrategy string + PanicUnwind string } // compilerContext contains function-independent data that should still be @@ -87,6 +88,8 @@ type compilerContext struct { program *ssa.Program diagnostics []error functionInfos map[*ssa.Function]functionInfo + callProperties map[*ssa.Function]functionCallProperties + asyncifyCatchers map[llvm.Type]llvm.Value astComments map[string]*ast.CommentGroup embedGlobals map[string][]*loader.EmbedFile pkg *types.Package @@ -100,14 +103,16 @@ type compilerContext struct { // importantly with a newly created LLVM context and module. func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *Config, dumpSSA bool) *compilerContext { c := &compilerContext{ - Config: config, - DumpSSA: dumpSSA, - difiles: make(map[string]llvm.Metadata), - ditypes: make(map[types.Type]llvm.Metadata), - machine: machine, - targetData: machine.CreateTargetData(), - functionInfos: map[*ssa.Function]functionInfo{}, - astComments: map[string]*ast.CommentGroup{}, + Config: config, + DumpSSA: dumpSSA, + difiles: make(map[string]llvm.Metadata), + ditypes: make(map[types.Type]llvm.Metadata), + machine: machine, + targetData: machine.CreateTargetData(), + functionInfos: map[*ssa.Function]functionInfo{}, + callProperties: map[*ssa.Function]functionCallProperties{}, + asyncifyCatchers: map[llvm.Type]llvm.Value{}, + astComments: map[string]*ast.CommentGroup{}, } c.ctx = llvm.NewContext() @@ -150,36 +155,41 @@ func (c *compilerContext) dispose() { type builder struct { *compilerContext llvm.Builder - fn *ssa.Function - llvmFnType llvm.Type - llvmFn llvm.Value - info functionInfo - locals map[ssa.Value]llvm.Value // local variables - indirectValues map[ssa.Value]llvm.Value - indirectReturn llvm.Value - blockInfo []blockInfo - currentBlock *ssa.BasicBlock - currentBlockInfo *blockInfo - tarjanStack []uint - tarjanIndex uint - phis []phiNode - deferPtr llvm.Value - deferFrame llvm.Value - stackChainAlloca llvm.Value - landingpad llvm.BasicBlock - difunc llvm.Metadata - dilocals map[*types.Var]llvm.Metadata - initInlinedAt llvm.Metadata // fake inlinedAt position - initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations - allDeferFuncs []any - deferFuncs map[*ssa.Function]int - deferInvokeFuncs map[string]int - deferClosureFuncs map[*ssa.Function]int - deferExprFuncs map[ssa.Value]int - selectRecvBuf map[*ssa.Select]llvm.Value - deferBuiltinFuncs map[ssa.Value]deferBuiltin - runDefersBlock []llvm.BasicBlock - afterDefersBlock []llvm.BasicBlock + fn *ssa.Function + llvmFnType llvm.Type + llvmFn llvm.Value + info functionInfo + locals map[ssa.Value]llvm.Value // local variables + indirectValues map[ssa.Value]llvm.Value + indirectReturn llvm.Value + blockInfo []blockInfo + currentBlock *ssa.BasicBlock + currentBlockInfo *blockInfo + tarjanStack []uint + tarjanIndex uint + phis []phiNode + deferPtr llvm.Value + deferFrame llvm.Value + stackChainAlloca llvm.Value + landingpad llvm.BasicBlock + unwindReturn llvm.BasicBlock + asyncifyCatchIndex int + difunc llvm.Metadata + dilocals map[*types.Var]llvm.Metadata + initInlinedAt llvm.Metadata // fake inlinedAt position + initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations + allDeferFuncs []any + deferFuncs map[*ssa.Function]int + deferInvokeFuncs map[string]int + deferClosureFuncs map[*ssa.Function]int + deferExprFuncs map[ssa.Value]int + selectRecvBuf map[*ssa.Select]llvm.Value + deferBuiltinFuncs map[ssa.Value]deferBuiltin + runningDefers bool + loweringBody bool + inFaultBlock bool + runDefersBlock []llvm.BasicBlock + afterDefersBlock []llvm.BasicBlock runtimeAssertBlocks map[string]llvm.BasicBlock interfaceAssertBlock llvm.BasicBlock @@ -1379,6 +1389,7 @@ func (b *builder) createFunction() { b.createFunctionStart(false) // Fill blocks with instructions. + b.loweringBody = true for _, block := range b.fn.DomPreorder() { if b.DumpSSA { fmt.Printf("%d: %s:\n", block.Index, block.Comment) @@ -1424,6 +1435,7 @@ func (b *builder) createFunction() { b.CreateRetVoid() } } + b.loweringBody = false // The rundefers instruction needs to be created after all defer // instructions have been created. Otherwise it won't handle all defer @@ -1572,10 +1584,13 @@ func (b *builder) createInstruction(instr ssa.Instruction) { case *ssa.Panic: value := b.getValue(instr.X, getPos(instr)) b.createRuntimeInvoke("_panic", []llvm.Value{value}, "") - b.CreateUnreachable() + b.createUnwindReturnOrUnreachable() case *ssa.Return: if b.hasDeferFrame() { b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "") + if b.usesReturnUnwind() { + b.createUnwindCheck(false) + } } b.createReturn(instr.Results, getPos(instr)) case *ssa.RunDefers: @@ -2337,7 +2352,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) result := b.createIndirectStorage(resultType, "call.result") params = append([]llvm.Value{result}, params...) params = append(params, context) - b.createInvoke(calleeType, callee, params, "") + b.createInvoke(calleeType, callee, params, "", instr) return result, nil } // This function takes a context parameter. @@ -2345,7 +2360,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) params = append(params, context) } - return b.createInvoke(calleeType, callee, params, ""), nil + return b.createInvoke(calleeType, callee, params, "", instr), nil } // getValue returns the LLVM value of a constant, function value, global, or @@ -3231,7 +3246,7 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va result = b.CreateICmp(llvm.IntEQ, typecodeX, typecodeY, "") } else { // Fall back to a full interface comparison. - result = b.createRuntimeCall("interfaceEqual", []llvm.Value{x, y}, "") + result = b.createRuntimeInvoke("interfaceEqual", []llvm.Value{x, y}, "") } if op == token.NEQ { result = b.CreateNot(result, "") diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 462a9bb8df..7feebbc857 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -392,6 +392,7 @@ func testCompilePackage(t *testing.T, options *compileopts.Options, file string) AutomaticStackSize: config.AutomaticStackSize(), DefaultStackSize: config.StackSize(), NeedsStackObjects: config.NeedsStackObjects(), + PanicUnwind: config.PanicUnwind(), } machine, err := NewTargetMachine(compilerConfig) if err != nil { diff --git a/compiler/defer.go b/compiler/defer.go index f8078f6b52..b7c0c0dbb1 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -23,30 +23,27 @@ import ( "tinygo.org/x/go-llvm" ) -// supportsRecover returns whether the compiler supports the recover() builtin -// for the current architecture. +// supportsRecover reports whether the selected unwind mode supports recover. func (b *builder) supportsRecover() bool { - switch b.archFamily() { - case "wasm32": - // Probably needs to be implemented using the exception handling - // proposal of WebAssembly: - // https://github.com/WebAssembly/exception-handling - return false - case "xtensa": - // TODO: add support for these architectures - return false - default: - return true - } + return b.PanicUnwind != "none" +} + +func (b *builder) usesExplicitUnwind() bool { + return b.PanicUnwind == "explicit" +} + +func (b *builder) usesAsyncifyUnwind() bool { + return b.PanicUnwind == "asyncify" +} + +func (b *builder) usesReturnUnwind() bool { + return b.usesExplicitUnwind() || b.usesAsyncifyUnwind() } // hasDeferFrame returns whether the current function needs to catch panics and // run defers. func (b *builder) hasDeferFrame() bool { - if b.fn.Recover == nil { - return false - } - return b.supportsRecover() + return b.fn.Recover != nil && b.supportsRecover() } // deferInitFunc sets up this function for future deferred calls. It must be @@ -94,6 +91,9 @@ func (b *builder) deferInitFunc() { // destroyDeferFrame. func (b *builder) createLandingPad() { b.SetInsertPointAtEnd(b.landingpad) + if b.usesReturnUnwind() { + b.createRuntimeCall("clearUnwind", nil, "") + } // Add debug info, if needed. // The location used is the closing bracket of the function. @@ -233,7 +233,6 @@ li a0, 0 } constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}" default: - // This case should have been handled by b.supportsRecover(). b.addError(b.fn.Pos(), "unknown architecture for defer: "+b.archFamily()) } asmType := llvm.FunctionType(resultType, []llvm.Type{b.dataPtrType}, false) @@ -260,6 +259,9 @@ func (b *builder) createInvokeCheckpoint() { // not update currentBlockInfo.exit because the fault block is a dead-end that // does not participate in phi node resolution. func (b *builder) createFaultCheckpoint() { + if b.usesReturnUnwind() { + return + } isZero := b.createCheckpoint(b.deferFrame) continueBB := b.insertBasicBlock("") b.CreateCondBr(isZero, continueBB, b.landingpad) @@ -553,6 +555,7 @@ func (b *builder) createDefer(instr *ssa.Defer) { // createRunDefers emits code to run all deferred functions. func (b *builder) createRunDefers() { deferType := b.getLLVMRuntimeType("_defer") + b.runningDefers = true // Add a loop like the following: // for stack != nil { @@ -658,7 +661,7 @@ func (b *builder) createRunDefers() { } forwardParams = b.prependIndirectResult(callback.Signature(), false, forwardParams, "defer.result") - b.createCall(fnType, fnPtr, forwardParams, "") + b.createInvokeWithAnalysis(fnType, fnPtr, forwardParams, "", true, true) case *ssa.Function: // Direct call. @@ -683,7 +686,7 @@ func (b *builder) createRunDefers() { // Call real function. fnType, fn := b.getFunction(callback) - b.createInvoke(fnType, fn, forwardParams, "") + b.createInvokeWithAnalysis(fnType, fn, forwardParams, "", b.functionMayUnwind(callback), b.functionMaySuspend(callback)) case *ssa.MakeClosure: // Get the real defer struct type and cast to it. @@ -699,7 +702,7 @@ func (b *builder) createRunDefers() { // Call deferred function. fnType, llvmFn := b.getFunction(fn) forwardParams = b.prependIndirectResult(fn.Signature, false, forwardParams, "defer.result") - b.createCall(fnType, llvmFn, forwardParams, "") + b.createInvokeWithAnalysis(fnType, llvmFn, forwardParams, "", b.functionMayUnwind(fn), b.functionMaySuspend(fn)) case *ssa.Builtin: db := b.deferBuiltinFuncs[callback] @@ -725,6 +728,13 @@ func (b *builder) createRunDefers() { panic("unknown deferred function type") } + if b.usesReturnUnwind() { + // A panic in a deferred call has reached its target frame. Keep + // running the remaining defers; destroyDeferFrame will propagate + // the panic afterwards if it was not recovered. + b.createRuntimeCall("clearUnwind", nil, "") + } + // Branch back to the start of the loop. b.CreateBr(loophead) } @@ -738,4 +748,5 @@ func (b *builder) createRunDefers() { // End of loop. b.SetInsertPointAtEnd(end) + b.runningDefers = false } diff --git a/compiler/interface.go b/compiler/interface.go index 84f91cc448..ff60877bdd 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -1163,11 +1163,13 @@ func (b *builder) getInterfaceAssertBlock() llvm.BasicBlock { block := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.throw") b.interfaceAssertBlock = block b.SetInsertPointAtEnd(block) + b.inFaultBlock = true if b.hasDeferFrame() { b.createFaultCheckpoint() } - b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "") - b.CreateUnreachable() + b.createRuntimeInvoke("interfaceTypeAssert", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "") + b.createUnwindReturnOrUnreachable() + b.inFaultBlock = false b.SetInsertPointAtEnd(savedBlock) return block } diff --git a/compiler/map.go b/compiler/map.go index 3481ae90ac..9011b8592b 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -106,7 +106,11 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, k if !hashmapIsBinaryKey(keyType) { fnName = "hashmapGenericGet" } - commaOkValue = b.createRuntimeCall(fnName, params, "") + if fnName == "hashmapGenericGet" { + commaOkValue = b.createRuntimeInvoke(fnName, params, "") + } else { + commaOkValue = b.createRuntimeCall(fnName, params, "") + } b.endValueStorage(mapKey) } @@ -155,7 +159,11 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok fnName = "hashmapGenericDelete" } params := []llvm.Value{m, keyAlloca} - b.createRuntimeCall(fnName, params, "") + if fnName == "hashmapGenericDelete" { + b.createRuntimeInvoke(fnName, params, "") + } else { + b.createRuntimeCall(fnName, params, "") + } b.emitLifetimeEnd(keyAlloca, keySize) return nil } diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll index 0eae6bbf29..d14e3860f6 100644 --- a/compiler/testdata/basic.ll +++ b/compiler/testdata/basic.ll @@ -48,7 +48,10 @@ divbyzero.next: ; preds = %entry divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %divbyzero.throw + ret i32 undef } declare void @runtime.divideByZeroPanic(ptr) #0 @@ -65,7 +68,10 @@ divbyzero.next: ; preds = %entry divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %divbyzero.throw + ret i32 undef } ; Function Attrs: nounwind @@ -84,7 +90,10 @@ divbyzero.next: ; preds = %entry divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %divbyzero.throw + ret i32 undef } ; Function Attrs: nounwind @@ -99,7 +108,10 @@ divbyzero.next: ; preds = %entry divbyzero.throw: ; preds = %entry call void @runtime.divideByZeroPanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %divbyzero.throw + ret i32 undef } ; Function Attrs: nounwind diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index 550f817f4e..02a6ed965a 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -24,8 +24,8 @@ entry: call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 %defer.next = load ptr, ptr %deferPtr, align 4 store i32 0, ptr %defer.alloca, align 4 - %defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 - store ptr %defer.next, ptr %defer.alloca.repack15, align 4 + %defer.alloca.repack11 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 + store ptr %defer.next, ptr %defer.alloca.repack11, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result = icmp eq i32 %setjmp, 0 @@ -42,7 +42,7 @@ rundefers.after: ; preds = %rundefers.end rundefers.block: ; preds = %1 br label %rundefers.loophead -rundefers.loophead: ; preds = %3, %rundefers.block +rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block %2 = load ptr, ptr %deferPtr, align 4 %stackIsNil = icmp eq ptr %2, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop @@ -57,11 +57,6 @@ rundefers.loop: ; preds = %rundefers.loophead ] rundefers.callback0: ; preds = %rundefers.loop - %setjmp1 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 - %setjmp.result2 = icmp eq i32 %setjmp1, 0 - br i1 %setjmp.result2, label %3, label %lpad - -3: ; preds = %rundefers.callback0 call void @"main.deferSimple$1"(ptr undef) br label %rundefers.loophead @@ -71,40 +66,35 @@ rundefers.default: ; preds = %rundefers.loop rundefers.end: ; preds = %rundefers.loophead br label %rundefers.after -recover: ; preds = %rundefers.end3 +recover: ; preds = %rundefers.end1 call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void -lpad: ; preds = %rundefers.callback012, %rundefers.callback0, %entry - br label %rundefers.loophead6 +lpad: ; preds = %entry + br label %rundefers.loophead4 -rundefers.loophead6: ; preds = %5, %lpad - %4 = load ptr, ptr %deferPtr, align 4 - %stackIsNil7 = icmp eq ptr %4, null - br i1 %stackIsNil7, label %rundefers.end3, label %rundefers.loop5 +rundefers.loophead4: ; preds = %rundefers.callback010, %lpad + %3 = load ptr, ptr %deferPtr, align 4 + %stackIsNil5 = icmp eq ptr %3, null + br i1 %stackIsNil5, label %rundefers.end1, label %rundefers.loop3 -rundefers.loop5: ; preds = %rundefers.loophead6 - %stack.next.gep8 = getelementptr inbounds nuw i8, ptr %4, i32 4 - %stack.next9 = load ptr, ptr %stack.next.gep8, align 4 - store ptr %stack.next9, ptr %deferPtr, align 4 - %callback11 = load i32, ptr %4, align 4 - switch i32 %callback11, label %rundefers.default4 [ - i32 0, label %rundefers.callback012 +rundefers.loop3: ; preds = %rundefers.loophead4 + %stack.next.gep6 = getelementptr inbounds nuw i8, ptr %3, i32 4 + %stack.next7 = load ptr, ptr %stack.next.gep6, align 4 + store ptr %stack.next7, ptr %deferPtr, align 4 + %callback9 = load i32, ptr %3, align 4 + switch i32 %callback9, label %rundefers.default2 [ + i32 0, label %rundefers.callback010 ] -rundefers.callback012: ; preds = %rundefers.loop5 - %setjmp13 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 - %setjmp.result14 = icmp eq i32 %setjmp13, 0 - br i1 %setjmp.result14, label %5, label %lpad - -5: ; preds = %rundefers.callback012 +rundefers.callback010: ; preds = %rundefers.loop3 call void @"main.deferSimple$1"(ptr undef) - br label %rundefers.loophead6 + br label %rundefers.loophead4 -rundefers.default4: ; preds = %rundefers.loop5 +rundefers.default2: ; preds = %rundefers.loop3 unreachable -rundefers.end3: ; preds = %rundefers.loophead6 +rundefers.end1: ; preds = %rundefers.loophead4 br label %recover } @@ -130,6 +120,95 @@ declare void @runtime.printint32(i32, ptr) #1 declare void @runtime.printunlock(ptr) #1 +; Function Attrs: nounwind +define hidden void @main.noPanicCall(ptr %context) unnamed_addr #0 { +entry: + call void @runtime.printlock(ptr undef) #4 + call void @runtime.printint32(i32 1, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 + ret void +} + +; Function Attrs: nounwind +define hidden void @main.deferNoPanicCall(ptr %context) unnamed_addr #0 { +entry: + %defer.alloca = alloca { i32, ptr }, align 4 + %deferframe.buf = alloca %runtime.deferFrame, align 4 + %deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24 + %0 = call ptr @llvm.stacksave.p0() + call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 + %defer.next = load ptr, ptr %deferPtr, align 4 + store i32 0, ptr %defer.alloca, align 4 + %defer.alloca.repack11 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 + store ptr %defer.next, ptr %defer.alloca.repack11, align 4 + store ptr %defer.alloca, ptr %deferPtr, align 4 + call void @main.noPanicCall(ptr undef) + br label %rundefers.block + +rundefers.after: ; preds = %rundefers.end + call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 + ret void + +rundefers.block: ; preds = %entry + br label %rundefers.loophead + +rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block + %1 = load ptr, ptr %deferPtr, align 4 + %stackIsNil = icmp eq ptr %1, null + br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop + +rundefers.loop: ; preds = %rundefers.loophead + %stack.next.gep = getelementptr inbounds nuw i8, ptr %1, i32 4 + %stack.next = load ptr, ptr %stack.next.gep, align 4 + store ptr %stack.next, ptr %deferPtr, align 4 + %callback = load i32, ptr %1, align 4 + switch i32 %callback, label %rundefers.default [ + i32 0, label %rundefers.callback0 + ] + +rundefers.callback0: ; preds = %rundefers.loop + call void @"main.deferNoPanicCall$1"(ptr undef) + br label %rundefers.loophead + +rundefers.default: ; preds = %rundefers.loop + unreachable + +rundefers.end: ; preds = %rundefers.loophead + br label %rundefers.after + +recover: ; preds = %rundefers.end1 + ret void + +lpad: ; No predecessors! + br label %rundefers.loophead4 + +rundefers.loophead4: ; preds = %rundefers.callback010, %lpad + br i1 poison, label %rundefers.end1, label %rundefers.loop3 + +rundefers.loop3: ; preds = %rundefers.loophead4 + switch i32 poison, label %rundefers.default2 [ + i32 0, label %rundefers.callback010 + ] + +rundefers.callback010: ; preds = %rundefers.loop3 + br label %rundefers.loophead4 + +rundefers.default2: ; preds = %rundefers.loop3 + unreachable + +rundefers.end1: ; preds = %rundefers.loophead4 + br label %recover +} + +; Function Attrs: nounwind +define internal void @"main.deferNoPanicCall$1"(ptr %context) unnamed_addr #0 { +entry: + call void @runtime.printlock(ptr undef) #4 + call void @runtime.printint32(i32 2, ptr undef) #4 + call void @runtime.printunlock(ptr undef) #4 + ret void +} + ; Function Attrs: nounwind define hidden void @main.deferMultiple(ptr %context) unnamed_addr #0 { entry: @@ -141,12 +220,12 @@ entry: call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 %defer.next = load ptr, ptr %deferPtr, align 4 store i32 0, ptr %defer.alloca, align 4 - %defer.alloca.repack22 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 - store ptr %defer.next, ptr %defer.alloca.repack22, align 4 + %defer.alloca.repack14 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 + store ptr %defer.next, ptr %defer.alloca.repack14, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 store i32 1, ptr %defer.alloca2, align 4 - %defer.alloca2.repack24 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4 - store ptr %defer.alloca, ptr %defer.alloca2.repack24, align 4 + %defer.alloca2.repack16 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4 + store ptr %defer.alloca, ptr %defer.alloca2.repack16, align 4 store ptr %defer.alloca2, ptr %deferPtr, align 4 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp.result = icmp eq i32 %setjmp, 0 @@ -163,7 +242,7 @@ rundefers.after: ; preds = %rundefers.end rundefers.block: ; preds = %1 br label %rundefers.loophead -rundefers.loophead: ; preds = %4, %3, %rundefers.block +rundefers.loophead: ; preds = %rundefers.callback1, %rundefers.callback0, %rundefers.block %2 = load ptr, ptr %deferPtr, align 4 %stackIsNil = icmp eq ptr %2, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop @@ -179,20 +258,10 @@ rundefers.loop: ; preds = %rundefers.loophead ] rundefers.callback0: ; preds = %rundefers.loop - %setjmp3 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 - %setjmp.result4 = icmp eq i32 %setjmp3, 0 - br i1 %setjmp.result4, label %3, label %lpad - -3: ; preds = %rundefers.callback0 call void @"main.deferMultiple$1"(ptr undef) br label %rundefers.loophead rundefers.callback1: ; preds = %rundefers.loop - %setjmp5 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 - %setjmp.result6 = icmp eq i32 %setjmp5, 0 - br i1 %setjmp.result6, label %4, label %lpad - -4: ; preds = %rundefers.callback1 call void @"main.deferMultiple$2"(ptr undef) br label %rundefers.loophead @@ -202,50 +271,40 @@ rundefers.default: ; preds = %rundefers.loop rundefers.end: ; preds = %rundefers.loophead br label %rundefers.after -recover: ; preds = %rundefers.end7 +recover: ; preds = %rundefers.end3 call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 ret void -lpad: ; preds = %rundefers.callback119, %rundefers.callback016, %rundefers.callback1, %rundefers.callback0, %entry - br label %rundefers.loophead10 - -rundefers.loophead10: ; preds = %7, %6, %lpad - %5 = load ptr, ptr %deferPtr, align 4 - %stackIsNil11 = icmp eq ptr %5, null - br i1 %stackIsNil11, label %rundefers.end7, label %rundefers.loop9 - -rundefers.loop9: ; preds = %rundefers.loophead10 - %stack.next.gep12 = getelementptr inbounds nuw i8, ptr %5, i32 4 - %stack.next13 = load ptr, ptr %stack.next.gep12, align 4 - store ptr %stack.next13, ptr %deferPtr, align 4 - %callback15 = load i32, ptr %5, align 4 - switch i32 %callback15, label %rundefers.default8 [ - i32 0, label %rundefers.callback016 - i32 1, label %rundefers.callback119 - ] +lpad: ; preds = %entry + br label %rundefers.loophead6 -rundefers.callback016: ; preds = %rundefers.loop9 - %setjmp17 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 - %setjmp.result18 = icmp eq i32 %setjmp17, 0 - br i1 %setjmp.result18, label %6, label %lpad +rundefers.loophead6: ; preds = %rundefers.callback113, %rundefers.callback012, %lpad + %3 = load ptr, ptr %deferPtr, align 4 + %stackIsNil7 = icmp eq ptr %3, null + br i1 %stackIsNil7, label %rundefers.end3, label %rundefers.loop5 -6: ; preds = %rundefers.callback016 - call void @"main.deferMultiple$1"(ptr undef) - br label %rundefers.loophead10 +rundefers.loop5: ; preds = %rundefers.loophead6 + %stack.next.gep8 = getelementptr inbounds nuw i8, ptr %3, i32 4 + %stack.next9 = load ptr, ptr %stack.next.gep8, align 4 + store ptr %stack.next9, ptr %deferPtr, align 4 + %callback11 = load i32, ptr %3, align 4 + switch i32 %callback11, label %rundefers.default4 [ + i32 0, label %rundefers.callback012 + i32 1, label %rundefers.callback113 + ] -rundefers.callback119: ; preds = %rundefers.loop9 - %setjmp20 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 - %setjmp.result21 = icmp eq i32 %setjmp20, 0 - br i1 %setjmp.result21, label %7, label %lpad +rundefers.callback012: ; preds = %rundefers.loop5 + call void @"main.deferMultiple$1"(ptr undef) + br label %rundefers.loophead6 -7: ; preds = %rundefers.callback119 +rundefers.callback113: ; preds = %rundefers.loop5 call void @"main.deferMultiple$2"(ptr undef) - br label %rundefers.loophead10 + br label %rundefers.loophead6 -rundefers.default8: ; preds = %rundefers.loop9 +rundefers.default4: ; preds = %rundefers.loop5 unreachable -rundefers.end7: ; preds = %rundefers.loophead10 +rundefers.end3: ; preds = %rundefers.loophead6 br label %recover } diff --git a/compiler/testdata/defer.go b/compiler/testdata/defer.go index b93d304991..de35c8ae3a 100644 --- a/compiler/testdata/defer.go +++ b/compiler/testdata/defer.go @@ -9,6 +9,17 @@ func deferSimple() { external() } +func noPanicCall() { + print(1) +} + +func deferNoPanicCall() { + defer func() { + print(2) + }() + noPanicCall() +} + func deferMultiple() { defer func() { print(3) diff --git a/compiler/testdata/func.ll b/compiler/testdata/func.ll index fd708789ed..5626e3155f 100644 --- a/compiler/testdata/func.ll +++ b/compiler/testdata/func.ll @@ -23,7 +23,10 @@ fpcall.next: ; preds = %entry fpcall.throw: ; preds = %entry call void @runtime.nilPanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %fpcall.throw + ret void } declare void @runtime.nilPanic(ptr) #0 diff --git a/compiler/testdata/generics.ll b/compiler/testdata/generics.ll index 5b9f4ee987..1c6c716f4c 100644 --- a/compiler/testdata/generics.ll +++ b/compiler/testdata/generics.ll @@ -105,7 +105,10 @@ store.next4: ; preds = %store.next ret %"main.Point[float32]" %10 deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry - unreachable + br label %unwind.return + +unwind.return: ; preds = %deref.throw + ret %"main.Point[float32]" undef } ; Function Attrs: allockind("alloc,zeroed") allocsize(0) @@ -168,7 +171,10 @@ store.next4: ; preds = %store.next ret %"main.Point[int]" %10 deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry - unreachable + br label %unwind.return + +unwind.return: ; preds = %deref.throw + ret %"main.Point[int]" undef } declare void @main.checkBool(i1, ptr) #0 diff --git a/compiler/testdata/go1.20.ll b/compiler/testdata/go1.20.ll index 3ef92b634a..edf6cdb4b5 100644 --- a/compiler/testdata/go1.20.ll +++ b/compiler/testdata/go1.20.ll @@ -41,7 +41,10 @@ unsafe.String.next: ; preds = %entry unsafe.String.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %unsafe.String.throw + ret %runtime._string undef } declare void @runtime.unsafeSlicePanic(ptr) #0 diff --git a/compiler/testdata/large.ll b/compiler/testdata/large.ll index 27da846060..dfb9502e2e 100644 --- a/compiler/testdata/large.ll +++ b/compiler/testdata/large.ll @@ -4,6 +4,8 @@ target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-n target triple = "wasm32-unknown-wasi" %runtime._string = type { ptr, i32 } +%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr } +%runtime._interface = type { ptr, ptr } %runtime.channelOp = type { ptr, ptr, i32, ptr } %runtime.chanSelectState = type { ptr, ptr } @@ -37,8 +39,8 @@ declare void @llvm.memcpy.p0.p0.i32(ptr noalias writeonly captures(none), ptr no define hidden i8 @"(main.largeReceiver).readLargeValue"(ptr readonly dereferenceable_or_null(1025) %receiver, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9 + %value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false) %0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024 %1 = load i8, ptr %0, align 1 @@ -52,12 +54,12 @@ declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3 define hidden void @main.makeLargeValue(ptr dereferenceable_or_null(1025) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9 + %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #12 %0 = getelementptr inbounds nuw i8, ptr %result, i32 1024 store i8 %value, ptr %0, align 1 - %1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9 + %1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %result, i32 1025, i1 false) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %1, i32 1025, i1 false) ret void @@ -74,8 +76,8 @@ entry: define hidden i8 @main.passZeroLargeValue(ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %"main.largeValue{}:main.largeValue" = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %"main.largeValue{}:main.largeValue", ptr nonnull %stackalloc, ptr undef) #9 + %"main.largeValue{}:main.largeValue" = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %"main.largeValue{}:main.largeValue", ptr nonnull %stackalloc, ptr undef) #12 store [1025 x i8] zeroinitializer, ptr %"main.largeValue{}:main.largeValue", align 1 %0 = call i8 @main.readLargeValue(ptr nonnull %"main.largeValue{}:main.largeValue", ptr undef) ret i8 %0 @@ -85,8 +87,8 @@ entry: define hidden i8 @main.readLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9 + %value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false) %0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024 %1 = load i8, ptr %0, align 1 @@ -97,8 +99,8 @@ entry: define hidden i8 @main.useLargeValue(ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9 + %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef) %0 = call i8 @main.readLargeValue(ptr nonnull %call.result, ptr undef) ret i8 %0 @@ -108,19 +110,22 @@ entry: define hidden i8 @main.useLargeFunctionValue(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9 + %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef) %0 = icmp eq ptr %fn.funcptr, null br i1 %0, label %fpcall.throw, label %fpcall.next fpcall.next: ; preds = %entry - %1 = call i8 %fn.funcptr(ptr nonnull %call.result, ptr %fn.context) #9 + %1 = call i8 %fn.funcptr(ptr nonnull %call.result, ptr %fn.context) #12 ret i8 %1 fpcall.throw: ; preds = %entry - call void @runtime.nilPanic(ptr undef) #9 - unreachable + call void @runtime.nilPanic(ptr undef) #12 + br label %unwind.return + +unwind.return: ; preds = %fpcall.throw + ret i8 undef } declare void @runtime.nilPanic(ptr) #0 @@ -129,10 +134,10 @@ declare void @runtime.nilPanic(ptr) #0 define hidden i8 @main.useLargeInterface(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9 - call void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr nonnull %call.result, ptr %value.value, ptr %value.typecode, ptr undef) #9 - %0 = call i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr %value.value, ptr nonnull %call.result, ptr %value.typecode, ptr undef) #9 + %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #12 + call void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr nonnull %call.result, ptr %value.value, ptr %value.typecode, ptr undef) #12 + %0 = call i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr %value.value, ptr nonnull %call.result, ptr %value.typecode, ptr undef) #12 ret i8 %0 } @@ -144,42 +149,51 @@ declare i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main. define hidden void @main.deferLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %defer.alloca = alloca { i32, ptr, ptr }, align 8 - %deferPtr = alloca ptr, align 4 - store ptr null, ptr %deferPtr, align 4 + %deferframe.buf = alloca %runtime.deferFrame, align 8 + %deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24 + %0 = call ptr @llvm.stacksave.p0() + call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #12 %stackalloc = alloca i8, align 1 - call void @runtime.trackPointer(ptr nonnull %defer.alloca, ptr nonnull %stackalloc, ptr undef) #9 + %defer.next = load ptr, ptr %deferPtr, align 4 + call void @runtime.trackPointer(ptr nonnull %defer.alloca, ptr nonnull %stackalloc, ptr undef) #12 store i32 0, ptr %defer.alloca, align 4 - %defer.alloca.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 - store ptr null, ptr %defer.alloca.repack1, align 4 - %defer.alloca.repack3 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8 - store ptr %value, ptr %defer.alloca.repack3, align 4 + %defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 + store ptr %defer.next, ptr %defer.alloca.repack15, align 4 + %defer.alloca.repack17 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8 + store ptr %value, ptr %defer.alloca.repack17, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4 br label %rundefers.block rundefers.after: ; preds = %rundefers.end + call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #12 + %unwind = call i1 @runtime.unwindPending(ptr undef) #12 + br i1 %unwind, label %unwind.return, label %unwind.continue + +unwind.continue: ; preds = %rundefers.after ret void rundefers.block: ; preds = %entry br label %rundefers.loophead rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block - %0 = load ptr, ptr %deferPtr, align 4 - %stackIsNil = icmp eq ptr %0, null + %1 = load ptr, ptr %deferPtr, align 4 + %stackIsNil = icmp eq ptr %1, null br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop rundefers.loop: ; preds = %rundefers.loophead - %stack.next.gep = getelementptr inbounds nuw i8, ptr %0, i32 4 + %stack.next.gep = getelementptr inbounds nuw i8, ptr %1, i32 4 %stack.next = load ptr, ptr %stack.next.gep, align 4 store ptr %stack.next, ptr %deferPtr, align 4 - %callback = load i32, ptr %0, align 4 + %callback = load i32, ptr %1, align 4 switch i32 %callback, label %rundefers.default [ i32 0, label %rundefers.callback0 ] rundefers.callback0: ; preds = %rundefers.loop - %gep = getelementptr inbounds nuw i8, ptr %0, i32 8 + %gep = getelementptr inbounds nuw i8, ptr %1, i32 8 %param = load ptr, ptr %gep, align 4 - %1 = call i8 @main.readLargeValue(ptr %param, ptr undef) + %2 = call i8 @main.deferLargeValue.asyncifycatch.0(ptr %param, ptr undef) #12 + call void @runtime.clearUnwind(ptr undef) #12 br label %rundefers.loophead rundefers.default: ; preds = %rundefers.loop @@ -188,28 +202,97 @@ rundefers.default: ; preds = %rundefers.loop rundefers.end: ; preds = %rundefers.loophead br label %rundefers.after -recover: ; No predecessors! +recover: ; preds = %rundefers.end3 + br i1 poison, label %unwind.return, label %unwind.continue2 + +unwind.continue2: ; preds = %recover + ret void + +lpad: ; No predecessors! + br label %rundefers.loophead6 + +rundefers.loophead6: ; preds = %rundefers.callback012, %lpad + br i1 poison, label %rundefers.end3, label %rundefers.loop5 + +rundefers.loop5: ; preds = %rundefers.loophead6 + switch i32 poison, label %rundefers.default4 [ + i32 0, label %rundefers.callback012 + ] + +rundefers.callback012: ; preds = %rundefers.loop5 + br label %rundefers.loophead6 + +rundefers.default4: ; preds = %rundefers.loop5 + unreachable + +rundefers.end3: ; preds = %rundefers.loophead6 + br label %recover + +unwind.return: ; preds = %recover, %rundefers.after ret void } +; Function Attrs: nocallback nofree nosync nounwind willreturn +declare ptr @llvm.stacksave.p0() #6 + +declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(28), ptr, ptr) #0 + +declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(28), ptr) #0 + +declare i1 @runtime.unwindPending(ptr) #0 + +; Function Attrs: noinline +define internal i8 @main.deferLargeValue.asyncifycatch.0(ptr %0, ptr %1) #7 { +entry: + %2 = call i8 @main.readLargeValue(ptr %0, ptr %1) + %3 = call i1 @runtime.unwindPending(ptr undef) + br i1 %3, label %unwind.stop, label %return + +unwind.stop: ; preds = %entry + call void @runtime.asyncifyStopUnwindImport() + br label %return + +return: ; preds = %unwind.stop, %entry + ret i8 %2 +} + +declare void @runtime.asyncifyStopUnwindImport() #8 + +declare void @runtime.clearUnwind(ptr) #0 + +; Function Attrs: noinline +define internal i8 @main.deferLargeValue.asyncifycatch.1(ptr %0, ptr %1) #7 { +entry: + %2 = call i8 @main.readLargeValue(ptr %0, ptr %1) + %3 = call i1 @runtime.unwindPending(ptr undef) + br i1 %3, label %unwind.stop, label %return + +unwind.stop: ; preds = %entry + call void @runtime.asyncifyStopUnwindImport() + br label %return + +return: ; preds = %unwind.stop, %entry + ret i8 %2 +} + ; Function Attrs: nounwind define hidden void @main.goLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %go.param = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %go.param, ptr nonnull %stackalloc, ptr undef) #9 + %go.param = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %go.param, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %go.param, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false) - call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %go.param, i32 65536, ptr undef) #9 + call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %go.param, i32 65536, ptr undef) #12 ret void } declare void @runtime.deadlock(ptr) #0 ; Function Attrs: nounwind -define linkonce_odr void @"main.readLargeValue$gowrapper"(ptr %0) unnamed_addr #6 { +define linkonce_odr void @"main.readLargeValue$gowrapper"(ptr %0) unnamed_addr #9 { entry: %1 = call i8 @main.readLargeValue(ptr %0, ptr undef) - call void @runtime.deadlock(ptr undef) #9 + call void @runtime.deadlock(ptr undef) #12 unreachable } @@ -219,8 +302,8 @@ declare void @"internal/task.start"(i32, ptr, i32, ptr) #0 define hidden void @main.makeLargeResults(ptr dereferenceable_or_null(1026) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9 + %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false) %0 = getelementptr inbounds nuw i8, ptr %return, i32 1025 @@ -232,12 +315,12 @@ entry: define hidden void @main.makeTwoLargeResults(ptr dereferenceable_or_null(2050) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9 + %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef) %0 = add i8 %value, 1 - %call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9 + %call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %0, ptr undef) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false) %1 = getelementptr inbounds nuw i8, ptr %return, i32 1025 @@ -249,13 +332,13 @@ entry: define hidden void @main.makeMixedLargeResults(ptr dereferenceable_or_null(2051) %return, i8 %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9 + %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef) %0 = add i8 %value, 1 %1 = add i8 %value, 2 - %call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9 + %call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %1, ptr undef) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false) %2 = getelementptr inbounds nuw i8, ptr %return, i32 1025 @@ -269,14 +352,14 @@ entry: define hidden void @main.chooseLargeValue(ptr dereferenceable_or_null(1025) %return, i1 %flag, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9 + %call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result, i8 1, ptr undef) br i1 %flag, label %if.then, label %if.done if.then: ; preds = %entry - %call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9 + %call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #12 call void @main.makeLargeValue(ptr nonnull %call.result1, i8 42, ptr undef) br label %if.done @@ -290,40 +373,43 @@ if.done: ; preds = %if.then, %entry define hidden void @main.makePointerLargeValue(ptr dereferenceable_or_null(1032) %return, ptr dereferenceable_or_null(1) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %complit = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #9 + %complit = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #12 br i1 false, label %store.throw, label %store.next store.next: ; preds = %entry %0 = getelementptr inbounds nuw i8, ptr %complit, i32 1028 store ptr %value, ptr %0, align 4 - %1 = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9 + %1 = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 4 dereferenceable(1032) %1, ptr noundef nonnull align 4 dereferenceable(1032) %complit, i32 1032, i1 false) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1032) %return, ptr noundef nonnull align 4 dereferenceable(1032) %1, i32 1032, i1 false) ret void store.throw: ; preds = %entry - unreachable + br label %unwind.return + +unwind.return: ; preds = %store.throw + ret void } ; Function Attrs: nounwind define hidden i8 @main.assertLargeValue(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %large = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %large, ptr nonnull %stackalloc, ptr undef) #9 - %typecode = call i1 @runtime.typeAssert(ptr %value.typecode, ptr nonnull @"reflect/types.typeid:named:main.largeValue", ptr undef) #9 - %typeassert.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %typeassert.result, ptr nonnull %stackalloc, ptr undef) #9 + %large = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %large, ptr nonnull %stackalloc, ptr undef) #12 + %typecode = call i1 @runtime.typeAssert(ptr %value.typecode, ptr nonnull @"reflect/types.typeid:named:main.largeValue", ptr undef) #12 + %typeassert.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %typeassert.result, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memset.p0.i32(ptr noundef nonnull align 1 dereferenceable(1026) %typeassert.result, i8 0, i32 1026, i1 false) br i1 %typecode, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry %0 = getelementptr inbounds nuw i8, ptr %typeassert.result, i32 1025 store i1 %typecode, ptr %0, align 1 - %t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9 + %t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, i32 1025, i1 false) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %large, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false) br i1 %typecode, label %if.done, label %if.then @@ -344,24 +430,24 @@ if.then: ; preds = %typeassert.next declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0 ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i32(ptr writeonly captures(none), i8, i32, i1 immarg) #7 +declare void @llvm.memset.p0.i32(ptr writeonly captures(none), i8, i32, i1 immarg) #10 ; Function Attrs: nounwind define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #9 - call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #9 - call void @runtime.hashmapBinarySet(ptr %0, ptr %key, ptr %value, ptr undef) #9 - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9 - %hashmap.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %hashmap.result, ptr nonnull %stackalloc, ptr undef) #9 - %1 = call i1 @runtime.hashmapBinaryGet(ptr %0, ptr %key, ptr nonnull %hashmap.result, i32 1025, ptr undef) #9 + %0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #12 + call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #12 + call void @runtime.hashmapBinarySet(ptr %0, ptr %key, ptr %value, ptr undef) #12 + %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #12 + %hashmap.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %hashmap.result, ptr nonnull %stackalloc, ptr undef) #12 + %1 = call i1 @runtime.hashmapBinaryGet(ptr %0, ptr %key, ptr nonnull %hashmap.result, i32 1025, ptr undef) #12 %2 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025 store i1 %1, ptr %2, align 1 - %t3 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %t3, ptr nonnull %stackalloc, ptr undef) #9 + %t3 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %t3, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t3, ptr noundef nonnull align 1 dereferenceable(1025) %hashmap.result, i32 1025, i1 false) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t3, i32 1025, i1 false) %3 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025 @@ -394,19 +480,19 @@ entry: %chan.op = alloca %runtime.channelOp, align 8 %stackalloc = alloca i8, align 1 call void @llvm.lifetime.start.p0(ptr nonnull %chan.op) - call void @runtime.chanSend(ptr %ch, ptr %value, ptr nonnull %chan.op, ptr undef) #9 + call void @runtime.chanSend(ptr %ch, ptr %value, ptr nonnull %chan.op, ptr undef) #12 call void @llvm.lifetime.end.p0(ptr nonnull %chan.op) - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9 - %chan.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %chan.result, ptr nonnull %stackalloc, ptr undef) #9 + %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #12 + %chan.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %chan.result, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.lifetime.start.p0(ptr nonnull %chan.op1) - %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.result, ptr nonnull %chan.op1, ptr undef) #9 + %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.result, ptr nonnull %chan.op1, ptr undef) #12 %1 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025 store i1 %0, ptr %1, align 1 call void @llvm.lifetime.end.p0(ptr nonnull %chan.op1) - %t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9 + %t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %chan.result, i32 1025, i1 false) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false) %2 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025 @@ -423,12 +509,12 @@ if.then: ; preds = %entry } ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) -declare void @llvm.lifetime.start.p0(ptr captures(none)) #8 +declare void @llvm.lifetime.start.p0(ptr captures(none)) #11 declare void @runtime.chanSend(ptr dereferenceable_or_null(36), 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 captures(none)) #8 +declare void @llvm.lifetime.end.p0(ptr captures(none)) #11 declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 @@ -449,10 +535,10 @@ entry: %.repack5 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12 store ptr null, ptr %.repack5, align 4 call void @llvm.lifetime.start.p0(ptr nonnull %select.block.alloca) - %select.result = call { i32, i1 } @runtime.chanSelect(ptr nonnull %select.recvbuf.alloca, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr nonnull %select.block.alloca, i32 2, i32 2, ptr undef) #9 + %select.result = call { i32, i1 } @runtime.chanSelect(ptr nonnull %select.recvbuf.alloca, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr nonnull %select.block.alloca, i32 2, i32 2, ptr undef) #12 call void @llvm.lifetime.end.p0(ptr nonnull %select.block.alloca) call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca) - call void @runtime.trackPointer(ptr nonnull %select.recvbuf.alloca, ptr nonnull %stackalloc, ptr undef) #9 + call void @runtime.trackPointer(ptr nonnull %select.recvbuf.alloca, ptr nonnull %stackalloc, ptr undef) #12 %1 = extractvalue { i32, i1 } %select.result, 0 %2 = icmp eq i32 %1, 0 br i1 %2, label %select.body, label %select.next @@ -465,10 +551,10 @@ select.next: ; preds = %entry br i1 %3, label %select.body1, label %select.next2 select.body1: ; preds = %select.next - %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9 - %select.received = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull %select.received, ptr nonnull %stackalloc, ptr undef) #9 + %result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #12 + %select.received = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull %select.received, ptr nonnull %stackalloc, ptr undef) #12 call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %select.received, ptr noundef nonnull align 1 dereferenceable(1025) %select.recvbuf.alloca, i32 1025, i1 false) call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %select.received, i32 1025, i1 false) %4 = getelementptr inbounds nuw i8, ptr %result, i32 1024 @@ -476,10 +562,13 @@ select.body1: ; preds = %select.next ret i8 %5 select.next2: ; preds = %select.next - call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #9 - call void @runtime.trackPointer(ptr nonnull @"main$pack", ptr nonnull %stackalloc, ptr undef) #9 - call void @runtime._panic(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull @"main$pack", ptr undef) #9 - unreachable + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #12 + call void @runtime.trackPointer(ptr nonnull @"main$pack", ptr nonnull %stackalloc, ptr undef) #12 + call void @runtime._panic(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull @"main$pack", ptr undef) #12 + br label %unwind.return + +unwind.return: ; preds = %select.next2 + ret i8 undef } declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #0 @@ -492,7 +581,10 @@ attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite attributes #3 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-indirect-result"="true" "tinygo-invoke"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" } attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" } -attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.readLargeValue" } -attributes #7 = { nocallback nofree nounwind willreturn memory(argmem: write) } -attributes #8 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } -attributes #9 = { nounwind } +attributes #6 = { nocallback nofree nosync nounwind willreturn } +attributes #7 = { noinline } +attributes #8 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="asyncify" "wasm-import-name"="stop_unwind" } +attributes #9 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.readLargeValue" } +attributes #10 = { nocallback nofree nounwind willreturn memory(argmem: write) } +attributes #11 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #12 = { nounwind } diff --git a/compiler/testdata/slice.ll b/compiler/testdata/slice.ll index a0756b54b6..9fdf794f09 100644 --- a/compiler/testdata/slice.ll +++ b/compiler/testdata/slice.ll @@ -36,7 +36,10 @@ lookup.next: ; preds = %entry lookup.throw: ; preds = %entry call void @runtime.lookupPanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %lookup.throw + ret i32 undef } declare void @runtime.lookupPanic(ptr) #0 @@ -115,7 +118,10 @@ slice.next: ; preds = %entry slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %slice.throw + ret { ptr, i32, i32 } undef } declare void @runtime.slicePanic(ptr) #0 @@ -138,7 +144,10 @@ slice.next: ; preds = %entry slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %slice.throw + ret { ptr, i32, i32 } undef } ; Function Attrs: nounwind @@ -159,7 +168,10 @@ slice.next: ; preds = %entry slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %slice.throw + ret { ptr, i32, i32 } undef } ; Function Attrs: nounwind @@ -180,7 +192,10 @@ slice.next: ; preds = %entry slice.throw: ; preds = %entry call void @runtime.slicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %slice.throw + ret { ptr, i32, i32 } undef } ; Function Attrs: nounwind @@ -213,7 +228,10 @@ slicetoarray.next: ; preds = %entry slicetoarray.throw: ; preds = %entry call void @runtime.sliceToArrayPointerPanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %slicetoarray.throw + ret ptr undef } declare void @runtime.sliceToArrayPointerPanic(ptr) #0 @@ -230,7 +248,10 @@ slicetoarray.next: ; preds = %entry ret ptr %makeslice slicetoarray.throw: ; preds = %entry - unreachable + br label %unwind.return + +unwind.return: ; preds = %slicetoarray.throw + ret ptr undef } ; Function Attrs: nounwind @@ -253,7 +274,10 @@ unsafe.Slice.next: ; preds = %entry unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %unsafe.Slice.throw + ret { ptr, i32, i32 } undef } declare void @runtime.unsafeSlicePanic(ptr) #0 @@ -277,7 +301,10 @@ unsafe.Slice.next: ; preds = %entry unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %unsafe.Slice.throw + ret { ptr, i32, i32 } undef } ; Function Attrs: nounwind @@ -301,7 +328,10 @@ unsafe.Slice.next: ; preds = %entry unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %unsafe.Slice.throw + ret { ptr, i32, i32 } undef } ; Function Attrs: nounwind @@ -325,7 +355,10 @@ unsafe.Slice.next: ; preds = %entry unsafe.Slice.throw: ; preds = %entry call void @runtime.unsafeSlicePanic(ptr undef) #5 - unreachable + br label %unwind.return + +unwind.return: ; preds = %unsafe.Slice.throw + ret { ptr, i32, i32 } undef } attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } diff --git a/compiler/testdata/string.ll b/compiler/testdata/string.ll index dd9986ed3d..53ee6f891d 100644 --- a/compiler/testdata/string.ll +++ b/compiler/testdata/string.ll @@ -46,7 +46,10 @@ lookup.next: ; preds = %entry lookup.throw: ; preds = %entry call void @runtime.lookupPanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %lookup.throw + ret i8 undef } declare void @runtime.lookupPanic(ptr) #0 @@ -91,7 +94,10 @@ lookup.next: ; preds = %entry lookup.throw: ; preds = %entry call void @runtime.lookupPanic(ptr undef) #2 - unreachable + br label %unwind.return + +unwind.return: ; preds = %lookup.throw + ret i8 undef } attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } diff --git a/main.go b/main.go index 6751b2669c..c627a2dde1 100644 --- a/main.go +++ b/main.go @@ -1762,6 +1762,7 @@ func main() { opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative, custom, precise, boehm)") panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") + panicUnwind := flag.String("panic-unwind", "", "panic unwind strategy (auto, explicit)") scheduler := flag.String("scheduler", "", "which scheduler to use (none, tasks, cores, threads, asyncify)") serial := flag.String("serial", "", "which serial output to use (none, uart, usb, rtt)") work := flag.Bool("work", false, "print the name of the temporary build directory and do not delete this directory on exit") @@ -1901,6 +1902,7 @@ func main() { Opt: *opt, GC: *gc, PanicStrategy: *panicStrategy, + PanicUnwind: *panicUnwind, Scheduler: *scheduler, Serial: *serial, Work: *work, diff --git a/main_test.go b/main_test.go index 2dfd1683f6..2a9e32d96e 100644 --- a/main_test.go +++ b/main_test.go @@ -408,11 +408,17 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { runTest("rand.go", options, t, nil, nil) }) } - if !isWebAssembly { - // The recover() builtin isn't supported yet on WebAssembly and Windows. - t.Run("recover.go", func(t *testing.T) { + t.Run("recover.go", func(t *testing.T) { + t.Parallel() + runTest("recover.go", options, t, nil, nil) + }) + if isWebAssembly { + t.Run("recover-explicit.go", func(t *testing.T) { t.Parallel() - runTest("recover.go", options, t, nil, nil) + options := compileopts.Options(options) + options.Scheduler = "none" + options.PanicUnwind = "explicit" + runTest("recover-explicit.go", options, t, nil, nil) }) } } @@ -598,7 +604,7 @@ func TestWebAssembly(t *testing.T) { for _, tc := range []testCase{ // Test whether there really are no imports when using -panic=trap. This // tests the bugfix for https://github.com/tinygo-org/tinygo/issues/4161. - {name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}}, + {name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.proc_exit", "wasi_snapshot_preview1.random_get"}}, {name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}}, } { t.Run(tc.name, func(t *testing.T) { @@ -1020,6 +1026,52 @@ func TestGoexitCrash(t *testing.T) { } }) } + + for _, tc := range []struct { + name string + arg string + panicStrategy string + want string + }{ + {"wasip1-deadlock", "deadlock", "", "deadlocked: no event source"}, + {"wasip1-goexit-panic-trap", "defer", "trap", "defer ran"}, + } { + t.Run(tc.name, func(t *testing.T) { + options := optionsFromTarget("wasip1", sema) + options.PanicStrategy = tc.panicStrategy + config, err := builder.NewConfig(&options) + if err != nil { + t.Fatal(err) + } + + result, err := builder.Build("testdata/goexit.go", ".wasm", t.TempDir(), config) + if err != nil { + t.Fatal("failed to build binary:", err) + } + data, err := os.ReadFile(result.Binary) + if err != nil { + t.Fatal("failed to read binary:", err) + } + + output := &bytes.Buffer{} + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) + defer r.Close(ctx) + wasi_snapshot_preview1.MustInstantiate(ctx, r) + moduleConfig := wazero.NewModuleConfig(). + WithArgs(result.Binary, tc.arg). + WithStdout(output). + WithStderr(output) + _, err = r.InstantiateWithConfig(ctx, data, moduleConfig) + if err == nil { + t.Fatal("program unexpectedly exited successfully") + } + if !strings.Contains(output.String(), tc.want) { + t.Fatalf("output does not contain %q:\n%s", tc.want, output.String()) + } + }) + } } func TestRuntimeFatal(t *testing.T) { diff --git a/src/internal/task/task_asyncify.go b/src/internal/task/task_asyncify.go index d7d9a6de42..a3d8fa999a 100644 --- a/src/internal/task/task_asyncify.go +++ b/src/internal/task/task_asyncify.go @@ -83,6 +83,10 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) { // currentTask is the current running task, or nil if currently in the scheduler. var currentTask *Task +// Panic unwinding is synchronous: it either reaches a defer frame or aborts +// without switching tasks. A single transient header is therefore sufficient. +var panicStackState stackState + // Current returns the current active task. func Current() *Task { return currentTask @@ -98,6 +102,14 @@ func Pause() { currentTask.state.unwind() } +// PanicUnwind starts an Asyncify unwind without pausing the task. A defer +// frame stops the unwind before it reaches the scheduler. +func PanicUnwind() { + panicStackState.asyncifysp = currentTask.state.asyncifysp + panicStackState.csp = currentTask.state.csp + panicStackState.unwind() +} + //export tinygo_unwind func (*stackState) unwind() diff --git a/src/runtime/panic.go b/src/runtime/panic.go index ff4bb8af25..3a837a7659 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -20,7 +20,7 @@ func trap() func tinygo_longjmp(frame *deferFrame) // Compiler intrinsic. -// Returns whether recover is supported on the current architecture. +// Reports whether recover and Goexit unwinding are supported. func supportsRecover() bool // Compile intrinsic. @@ -38,7 +38,7 @@ type deferFrame struct { JumpPC unsafe.Pointer // pc to return to ExtraRegs [deferExtraRegs]unsafe.Pointer // extra registers (depending on the architecture) Previous *deferFrame // previous recover buffer pointer - Panicking panicState // panic/Goexit state + PanicState panicState // panic/Goexit and unwind state PanicValue interface{} // panic value, might be nil for panic(nil) for example DeferPtr unsafe.Pointer // head of the stack-allocated defer list } @@ -48,6 +48,7 @@ type panicState uint8 const ( panicTrue panicState = 1 << iota panicGoexit + panicUnwinding ) // Builtin function panic(msg), used as a compiler intrinsic. @@ -55,23 +56,33 @@ func _panic(message interface{}) { panicOrGoexit(message, panicTrue) } +func startPanicUnwind(message interface{}, state panicState) bool { + // Note: recover is not supported inside interrupts. + // (This could be supported, like defer, but we currently don't). + if !supportsRecover() || interrupt.In() { + return false + } + frame := currentDeferFrame() + if frame == nil { + return false + } + if state&panicTrue != 0 { + // Preserve a suspended Goexit so it can resume if this panic is + // recovered. + state |= frame.PanicState & panicGoexit + } + frame.PanicValue = message + frame.PanicState = state + return startUnwind(frame) +} + func panicOrGoexit(message interface{}, panicking panicState) { + // Goexit must run deferred calls regardless of the panic strategy. if panicking != panicGoexit && panicStrategy() == tinygo.PanicStrategyTrap { trap() } - // Note: recover is not supported inside interrupts. - // (This could be supported, like defer, but we currently don't). - if supportsRecover() && !interrupt.In() { - frame := (*deferFrame)(task.Current().DeferFrame) - if frame != nil { - frame.PanicValue = message - if panicking&panicTrue != 0 { - panicking |= frame.Panicking & panicGoexit - } - frame.Panicking = panicking - tinygo_longjmp(frame) - // unreachable - } + if startPanicUnwind(message, panicking) { + return } if panicking == panicGoexit { // Call to Goexit() instead of a panic. @@ -107,16 +118,10 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) { if panicStrategy() == tinygo.PanicStrategyTrap { trap() } - if supportsRecover() && !interrupt.In() { - frame := (*deferFrame)(task.Current().DeferFrame) - if frame != nil { - // Use the normal panic mechanism so that this runtime error - // can be recovered with recover(). - frame.PanicValue = plainError(msg) - frame.Panicking = panicTrue | (frame.Panicking & panicGoexit) - tinygo_longjmp(frame) - // unreachable - } + // Use the normal panic mechanism so that this runtime error + // can be recovered with recover(). + if startPanicUnwind(plainError(msg), panicTrue) { + return } if hasReturnAddr { // Note: the string "panic: runtime error at " is also used in @@ -133,6 +138,16 @@ func runtimePanicAt(addr unsafe.Pointer, msg string) { abort() } +//go:inline +//go:nobounds +func currentDeferFrame() *deferFrame { + currentTask := task.Current() + if currentTask == nil { + return nil + } + return (*deferFrame)(currentTask.DeferFrame) +} + // 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 @@ -150,7 +165,7 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { currentTask := task.Current() frame.Previous = (*deferFrame)(currentTask.DeferFrame) frame.JumpSP = jumpSP - frame.Panicking = 0 + frame.PanicState = 0 frame.DeferPtr = nil currentTask.DeferFrame = unsafe.Pointer(frame) } @@ -163,12 +178,12 @@ func setupDeferFrame(frame *deferFrame, jumpSP unsafe.Pointer) { //go:nobounds func destroyDeferFrame(frame *deferFrame) { task.Current().DeferFrame = unsafe.Pointer(frame.Previous) - if frame.Panicking&panicTrue != 0 { + if frame.PanicState&panicTrue != 0 { // We're still panicking! // Re-raise the panic now. panicOrGoexit(frame.PanicValue, panicTrue) } - if frame.Panicking&panicGoexit != 0 { + if frame.PanicState&panicGoexit != 0 { // A deferred function panicked during Goexit, and that panic was // recovered. Continue the original Goexit instead of returning. panicOrGoexit(nil, panicGoexit) @@ -193,8 +208,7 @@ func _recover(useParentFrame bool) interface{} { if !supportsRecover() || interrupt.In() { // Either we're compiling without stack unwinding support, or we're // inside an interrupt where panic/recover is not supported. Either way, - // make this a no-op since panic() won't do any long jumps to a deferred - // function. + // panic() won't unwind to a deferred function. return nil } frame := (*deferFrame)(task.Current().DeferFrame) @@ -203,15 +217,15 @@ func _recover(useParentFrame bool) interface{} { // already), but instead from the previous frame. frame = frame.Previous } - if frame != nil && frame.Panicking != 0 { - if frame.Panicking&panicTrue == 0 { + if frame != nil && frame.PanicState&(panicTrue|panicGoexit) != 0 { + if frame.PanicState&panicTrue == 0 { // Special value that indicates we're exiting the goroutine using // Goexit(). Therefore, make this recover call a no-op. return nil } // Only the first call to recover returns the panic value. It also stops // the panicking sequence, hence setting panicking to false. - frame.Panicking &^= panicTrue + frame.PanicState &^= panicTrue return frame.PanicValue } // Not panicking, so return a nil interface. diff --git a/src/runtime/panic_unwind_asyncify.go b/src/runtime/panic_unwind_asyncify.go new file mode 100644 index 0000000000..0be2d6cb2a --- /dev/null +++ b/src/runtime/panic_unwind_asyncify.go @@ -0,0 +1,15 @@ +//go:build tinygo.unwind.asyncify + +package runtime + +import "internal/task" + +//go:wasmimport asyncify stop_unwind +func asyncifyStopUnwindImport() + +func startUnwind(frame *deferFrame) bool { + frame.PanicState |= panicUnwinding + setUnwindSignal(true) + task.PanicUnwind() + return true +} diff --git a/src/runtime/panic_unwind_explicit.go b/src/runtime/panic_unwind_explicit.go new file mode 100644 index 0000000000..231d4ae9c6 --- /dev/null +++ b/src/runtime/panic_unwind_explicit.go @@ -0,0 +1,9 @@ +//go:build tinygo.unwind.explicit + +package runtime + +func startUnwind(frame *deferFrame) bool { + frame.PanicState |= panicUnwinding + setUnwindSignal(true) + return true +} diff --git a/src/runtime/panic_unwind_none.go b/src/runtime/panic_unwind_none.go new file mode 100644 index 0000000000..3ccb5f5833 --- /dev/null +++ b/src/runtime/panic_unwind_none.go @@ -0,0 +1,7 @@ +//go:build tinygo.unwind.none + +package runtime + +func startUnwind(frame *deferFrame) bool { + return false +} diff --git a/src/runtime/panic_unwind_return.go b/src/runtime/panic_unwind_return.go new file mode 100644 index 0000000000..66d5cc8067 --- /dev/null +++ b/src/runtime/panic_unwind_return.go @@ -0,0 +1,19 @@ +//go:build tinygo.unwind.explicit || tinygo.unwind.asyncify + +package runtime + +//go:inline +//go:nobounds +func unwindPending() bool { + return getUnwindSignal() +} + +//go:inline +//go:nobounds +func clearUnwind() { + setUnwindSignal(false) + frame := currentDeferFrame() + if frame != nil { + frame.PanicState &^= panicUnwinding + } +} diff --git a/src/runtime/panic_unwind_setjmp.go b/src/runtime/panic_unwind_setjmp.go new file mode 100644 index 0000000000..f91aa4ccf3 --- /dev/null +++ b/src/runtime/panic_unwind_setjmp.go @@ -0,0 +1,8 @@ +//go:build tinygo.unwind.setjmp + +package runtime + +func startUnwind(frame *deferFrame) bool { + tinygo_longjmp(frame) + return false +} diff --git a/src/runtime/panic_unwind_signal_cores.go b/src/runtime/panic_unwind_signal_cores.go new file mode 100644 index 0000000000..d6435d65d5 --- /dev/null +++ b/src/runtime/panic_unwind_signal_cores.go @@ -0,0 +1,15 @@ +//go:build tinygo.unwind.explicit && scheduler.cores + +package runtime + +var unwindPendingSignal [numCPU]bool + +//go:inline +func getUnwindSignal() bool { + return unwindPendingSignal[currentCPU()] +} + +//go:inline +func setUnwindSignal(unwinding bool) { + unwindPendingSignal[currentCPU()] = unwinding +} diff --git a/src/runtime/panic_unwind_signal_unicore.go b/src/runtime/panic_unwind_signal_unicore.go new file mode 100644 index 0000000000..c688f30bde --- /dev/null +++ b/src/runtime/panic_unwind_signal_unicore.go @@ -0,0 +1,17 @@ +//go:build (tinygo.unwind.explicit || tinygo.unwind.asyncify) && !scheduler.cores && !scheduler.threads + +package runtime + +// The signal is only set while returning synchronously to the defer frame +// recorded in PanicState, and is cleared before deferred calls can schedule. +var unwindPendingSignal bool + +//go:inline +func getUnwindSignal() bool { + return unwindPendingSignal +} + +//go:inline +func setUnwindSignal(unwinding bool) { + unwindPendingSignal = unwinding +} diff --git a/testdata/goexit.go b/testdata/goexit.go index fd6c0d2cd6..09c7417a6c 100644 --- a/testdata/goexit.go +++ b/testdata/goexit.go @@ -10,6 +10,9 @@ func main() { switch os.Args[1] { case "main": runtime.Goexit() + case "defer": + defer println("defer ran") + runtime.Goexit() case "deadlock": f := func() { for i := 0; i < 10; i++ { diff --git a/testdata/recover-explicit.go b/testdata/recover-explicit.go new file mode 100644 index 0000000000..9473fdd397 --- /dev/null +++ b/testdata/recover-explicit.go @@ -0,0 +1,164 @@ +package main + +type pair struct { + first int + second int +} + +var panicMap = map[any]int{} + +func main() { + println("# direct panic") + direct() + + println("\n# results") + println("scalar result:", scalarResult()) + result := aggregateResult() + println("aggregate result:", result.first, result.second) + + println("\n# nested panics") + nestedDefer() + nestedPanic() + panicReplace() + deferPanic() + repanic() + + println("\n# runtime panics") + mustRecover("index", func() { + values := []int{1} + println(values[2]) + }) + mustRecover("index from helper", func() { + println(readOutOfBounds([]byte{1})) + }) + mustRecover("slice", func() { + values := []int{1} + _ = values[:2] + }) + mustRecover("type assertion", func() { + var value any = "string" + println(value.(int)) + }) + mustRecover("interface comparison", func() { + var value any = []int{} + println(value == value) + }) + mustRecover("map assignment", func() { + panicMap[[]int{}] = 1 + }) + mustRecover("map lookup", func() { + _ = panicMap[[]int{}] + }) + mustRecover("map delete", func() { + delete(panicMap, []int{}) + }) + mustRecover("nil map", func() { + var values map[string]int + values["key"] = 1 + }) + mustRecover("divide by zero", func() { + var divisor int + println(1 / divisor) + }) + mustRecover("nil pointer", func() { + var pointer *int + println(*pointer) + }) +} + +func direct() { + defer func() { + println("recovered direct:", recover() == "direct panic") + }() + panicHelper("direct panic") + println("unreachable after direct panic") +} + +//go:noinline +func panicHelper(value any) { + panic(value) +} + +func scalarResult() (result int) { + defer func() { + recover() + }() + result = 3 + panicHelper("scalar result panic") + return +} + +func aggregateResult() (result pair) { + defer func() { + recover() + }() + result = pair{1, 2} + panicHelper("aggregate result panic") + return +} + +func nestedDefer() { + defer func() { + println("recovered nested:", recover() == "nested panic") + }() + func() { + defer println("nested defer ran") + panicHelper("nested panic") + }() +} + +func nestedPanic() { + defer func() { + println("recovered outer:", recover() == "outer panic") + }() + defer func() { + println("recovered inner:", recover() == "inner panic") + panicHelper("outer panic") + }() + panicHelper("inner panic") +} + +func panicReplace() { + defer func() { + println("recovered replacement:", recover() == "replacement panic") + }() + defer func() { + panicHelper("replacement panic") + }() + panicHelper("original panic") +} + +func deferPanic() { + defer func() { + println("recovered deferred:", recover() == "deferred panic") + }() + defer panicHelper("deferred panic") +} + +func repanic() { + defer func() { + println("recovered repanic:", recover() == "repanic") + }() + defer func() { + value := recover() + panicHelper(value) + }() + panicHelper("repanic") +} + +func mustRecover(name string, fn func()) { + defer func() { + if recover() == nil { + println("failed to recover:", name) + } else { + println("recovered:", name) + } + }() + fn() + println("unreachable after:", name) +} + +//go:noinline +func readOutOfBounds(values []byte) byte { + return values[2] +} diff --git a/testdata/recover-explicit.txt b/testdata/recover-explicit.txt new file mode 100644 index 0000000000..34377f0e21 --- /dev/null +++ b/testdata/recover-explicit.txt @@ -0,0 +1,28 @@ +# direct panic +recovered direct: true + +# results +scalar result: 3 +aggregate result: 1 2 + +# nested panics +nested defer ran +recovered nested: true +recovered inner: true +recovered outer: true +recovered replacement: true +recovered deferred: true +recovered repanic: true + +# runtime panics +recovered: index +recovered: index from helper +recovered: slice +recovered: type assertion +recovered: interface comparison +recovered: map assignment +recovered: map lookup +recovered: map delete +recovered: nil map +recovered: divide by zero +recovered: nil pointer diff --git a/testdata/recover.go b/testdata/recover.go index bb90f0da62..f5ff7ffdec 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -6,6 +6,7 @@ import ( ) var wg sync.WaitGroup +var panicMap = map[interface{}]int{} func main() { println("# simple recover") @@ -274,6 +275,21 @@ func recoverRuntimeError() { _ = x.(string) }) recoverEmptyInterfaceTypeAssert() + recoverMustPanic("interface compare", func() { + var x interface{} = []int{} + _ = x == x + }) + recoverMustPanic("map key", func() { + panicMap[[]int{}] = 1 + }) + recoverMustPanic("map lookup key", func() { + m := map[interface{}]int{} + _ = m[[]int{}] + }) + recoverMustPanic("map delete key", func() { + m := map[interface{}]int{} + delete(m, []int{}) + }) } //go:noinline diff --git a/testdata/recover.txt b/testdata/recover.txt index e61cf25fe6..3769df4991 100644 --- a/testdata/recover.txt +++ b/testdata/recover.txt @@ -49,6 +49,10 @@ outer recovered: repanic value recovered: slice recovered: type assert recovered: empty interface type assert + recovered: interface compare + recovered: map key + recovered: map lookup key + recovered: map delete key # recover from nil map and closed channel recovered: nil map diff --git a/testdata/testing-wasm.txt b/testdata/testing-wasm.txt index 6a8ee05918..1d61154dc0 100644 --- a/testdata/testing-wasm.txt +++ b/testdata/testing-wasm.txt @@ -11,6 +11,8 @@ c failed after failed + --- FAIL: TestBar/Bar4 (0.00s) + fatal log Bar end --- FAIL: TestAllLowercase (0.00s) --- FAIL: TestAllLowercase/BETA (0.00s) diff --git a/testdata/testing.go b/testdata/testing.go index 2e56d8f577..f8447fa56d 100644 --- a/testdata/testing.go +++ b/testdata/testing.go @@ -6,7 +6,6 @@ import ( "errors" "flag" "io" - "runtime" "strings" "testing" ) @@ -26,16 +25,14 @@ func TestBar(t *testing.T) { t.Log("after failed") }) t.Run("Bar3", func(t *testing.T) {}) - if runtime.GOARCH != "wasm" { - t.Run("Bar4", func(t *testing.T) { - t.Fatal("fatal") - t.Log("after fatal") - }) - t.Run("Bar5", func(t *testing.T) { - t.SkipNow() - t.Error("after skip") - }) - } + t.Run("Bar4", func(t *testing.T) { + t.Fatal("fatal") + t.Log("after fatal") + }) + t.Run("Bar5", func(t *testing.T) { + t.SkipNow() + t.Error("after skip") + }) t.Log("log Bar end") } diff --git a/tests/testing/pass/pass_test.go b/tests/testing/pass/pass_test.go index 3dd229385d..4d545bed7f 100644 --- a/tests/testing/pass/pass_test.go +++ b/tests/testing/pass/pass_test.go @@ -5,3 +5,66 @@ import "testing" func TestPass(t *testing.T) { // This test passes. } + +func TestDeferredSuspend(t *testing.T) { + ready := make(chan struct{}) + release := make(chan struct{}) + finished := make(chan struct{}) + go func() { + defer func() { + close(ready) + <-release + close(finished) + }() + }() + <-ready + close(release) + <-finished +} + +func TestBlockingSendWithDefer(t *testing.T) { + ready := make(chan struct{}) + values := make(chan int) + finished := make(chan struct{}) + go func() { + defer close(finished) + close(ready) + values <- 1 + }() + <-ready + if value := <-values; value != 1 { + t.Fatalf("unexpected value: %d", value) + } + <-finished +} + +func TestConcurrentAggregateSuspend(t *testing.T) { + ready := make(chan int) + release := make(chan int) + results := make(chan [2]int) + for id := 1; id <= 2; id++ { + go func() { + defer func() {}() + first, second := suspendedPair(id, ready, release) + results <- [2]int{first, second} + }() + } + <-ready + <-ready + release <- 20 + release <- 10 + for range 2 { + result := <-results + if result[0] != 1 && result[0] != 2 { + t.Fatalf("unexpected first result: %d", result[0]) + } + if result[1] != 10 && result[1] != 20 { + t.Fatalf("unexpected second result: %d", result[1]) + } + } +} + +func suspendedPair(id int, ready chan<- int, release <-chan int) (int, int) { + ready <- id + return id, <-release +} diff --git a/transform/optimizer.go b/transform/optimizer.go index 150a9a77cb..e1d7f8071b 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -137,6 +137,10 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { } } + if speedLevel > 0 && config.PanicUnwind() == "asyncify" { + AddUnwindAssumptions(mod) + } + if config.VerifyIR() { if errs := ircheck.Module(mod); errs != nil { return errs diff --git a/transform/testdata/unwind.ll b/transform/testdata/unwind.ll new file mode 100644 index 0000000000..6ea94741ff --- /dev/null +++ b/transform/testdata/unwind.ll @@ -0,0 +1,53 @@ +target datalayout = "e-p:32:32" +target triple = "wasm32-unknown-unknown" + +@runtime.unwindPendingSignal = internal global i1 false +@value = global i32 0 + +define internal void @safe() { +entry: + store i32 1, ptr @value + ret void +} + +define internal void @panics() { +entry: + store i1 true, ptr @runtime.unwindPendingSignal + ret void +} + +declare void @external() + +define i1 @checkSafe() { +entry: + call void @safe() + %unwind = call i1 @runtime.unwindPending() + ret i1 %unwind +} + +define i1 @checkPanic() { +entry: + call void @panics() + %unwind = call i1 @runtime.unwindPending() + ret i1 %unwind +} + +define i1 @checkExternal() { +entry: + call void @external() + %unwind = call i1 @runtime.unwindPending() + ret i1 %unwind +} + +define i1 @checkIndirect(ptr %fn) { +entry: + call void %fn() + %unwind = call i1 @runtime.unwindPending() + ret i1 %unwind +} + +define i1 @runtime.unwindPending() { +entry: + %unwind = load i1, ptr @runtime.unwindPendingSignal + ret i1 %unwind +} diff --git a/transform/testdata/unwind.out.ll b/transform/testdata/unwind.out.ll new file mode 100644 index 0000000000..8462c65073 --- /dev/null +++ b/transform/testdata/unwind.out.ll @@ -0,0 +1,57 @@ +target datalayout = "e-p:32:32" +target triple = "wasm32-unknown-unknown" + +@runtime.unwindPendingSignal = internal unnamed_addr global i1 false +@value = local_unnamed_addr global i32 0 + +declare void @external() local_unnamed_addr + +define noundef i1 @checkSafe() local_unnamed_addr #0 { +entry: + %unwind.entry = load i1, ptr @runtime.unwindPendingSignal, align 1 + %0 = xor i1 %unwind.entry, true + tail call void @llvm.assume(i1 %0) + store i32 1, ptr @value, align 4 + ret i1 false +} + +define noundef i1 @checkPanic() local_unnamed_addr #0 { +entry: + %unwind.entry = load i1, ptr @runtime.unwindPendingSignal, align 1 + %0 = xor i1 %unwind.entry, true + tail call void @llvm.assume(i1 %0) + store i1 true, ptr @runtime.unwindPendingSignal, align 1 + ret i1 true +} + +define i1 @checkExternal() local_unnamed_addr { +entry: + %unwind.entry = load i1, ptr @runtime.unwindPendingSignal, align 1 + %0 = xor i1 %unwind.entry, true + tail call void @llvm.assume(i1 %0) + tail call void @external() + %unwind.i = load i1, ptr @runtime.unwindPendingSignal, align 1 + ret i1 %unwind.i +} + +define i1 @checkIndirect(ptr nocapture readonly %fn) local_unnamed_addr { +entry: + %unwind.entry = load i1, ptr @runtime.unwindPendingSignal, align 1 + %0 = xor i1 %unwind.entry, true + tail call void @llvm.assume(i1 %0) + tail call void %fn() + %unwind.i = load i1, ptr @runtime.unwindPendingSignal, align 1 + ret i1 %unwind.i +} + +define i1 @runtime.unwindPending() local_unnamed_addr #1 { +entry: + %unwind = load i1, ptr @runtime.unwindPendingSignal, align 1 + ret i1 %unwind +} + +declare void @llvm.assume(i1 noundef) #2 + +attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(readwrite, argmem: none, inaccessiblemem: write, target_mem0: none, target_mem1: none) } +attributes #1 = { mustprogress nofree norecurse nosync nounwind willreturn memory(read, argmem: none, inaccessiblemem: none, target_mem0: none, target_mem1: none) } +attributes #2 = { mustprogress nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write) } diff --git a/transform/transform_test.go b/transform/transform_test.go index b79e177639..65caebefae 100644 --- a/transform/transform_test.go +++ b/transform/transform_test.go @@ -199,6 +199,7 @@ func compileGoFileForTesting(t *testing.T, filename string) llvm.Module { AutomaticStackSize: config.AutomaticStackSize(), Debug: true, PanicStrategy: config.PanicStrategy(), + PanicUnwind: config.PanicUnwind(), } machine, err := compiler.NewTargetMachine(compilerConfig) if err != nil { diff --git a/transform/unwind.go b/transform/unwind.go new file mode 100644 index 0000000000..ebcafddce5 --- /dev/null +++ b/transform/unwind.go @@ -0,0 +1,53 @@ +package transform + +import "tinygo.org/x/go-llvm" + +// AddUnwindAssumptions records that normal Go calls only begin while the +// transient Asyncify unwind signal is clear. Asyncify panic unwinding is +// synchronous and cannot be interrupted by another Go entry. Checks are not +// emitted while deferred calls run, and landing pads clear the signal before +// invoking deferred code. LLVM's alias analysis can then remove checks around +// calls that do not modify the signal. +func AddUnwindAssumptions(mod llvm.Module) bool { + signal := mod.NamedGlobal("runtime.unwindPendingSignal") + if signal.IsNil() || + signal.GlobalValueType().TypeKind() != llvm.IntegerTypeKind || + signal.GlobalValueType().IntTypeWidth() != 1 { + return false + } + unwind := mod.NamedFunction("runtime.unwindPending") + if unwind.IsNil() { + return false + } + + functions := make(map[llvm.Value]struct{}) + // Suspension-safe catchers call unwindPending indirectly and are + // intentionally absent from this set. + for _, call := range getUses(unwind) { + if call.IsACallInst().IsNil() || call.CalledValue() != unwind { + continue + } + functions[call.InstructionParent().Parent()] = struct{}{} + } + if len(functions) == 0 { + return false + } + + ctx := mod.Context() + assumeType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int1Type()}, false) + assume := mod.NamedFunction("llvm.assume") + if assume.IsNil() { + assume = llvm.AddFunction(mod, "llvm.assume", assumeType) + } + + builder := ctx.NewBuilder() + defer builder.Dispose() + for fn := range functions { + first := fn.EntryBasicBlock().FirstInstruction() + builder.SetInsertPointBefore(first) + unwinding := builder.CreateLoad(ctx.Int1Type(), signal, "unwind.entry") + notUnwinding := builder.CreateNot(unwinding, "") + builder.CreateCall(assumeType, assume, []llvm.Value{notUnwinding}, "") + } + return true +} diff --git a/transform/unwind_test.go b/transform/unwind_test.go new file mode 100644 index 0000000000..13099057cd --- /dev/null +++ b/transform/unwind_test.go @@ -0,0 +1,20 @@ +package transform_test + +import ( + "testing" + + "github.com/tinygo-org/tinygo/transform" + "tinygo.org/x/go-llvm" +) + +func TestUnwindAssumptions(t *testing.T) { + t.Parallel() + testTransform(t, "testdata/unwind", func(mod llvm.Module) { + transform.AddUnwindAssumptions(mod) + po := llvm.NewPassBuilderOptions() + defer po.Dispose() + if err := mod.RunPasses("thinlto-pre-link", llvm.TargetMachine{}, po); err != nil { + t.Fatal(err) + } + }) +}