diff --git a/interp/compiler.go b/interp/compiler.go index 141071be5b..a38db5971b 100644 --- a/interp/compiler.go +++ b/interp/compiler.go @@ -15,11 +15,13 @@ import ( // Functions are in SSA form, just like the LLVM version if it. The first block // (blocks[0]) is the entry block. type function struct { - llvmFn llvm.Value - name string // precalculated llvmFn.Name() - params []llvm.Value // precalculated llvmFn.Params() - blocks []*basicBlock - locals map[llvm.Value]int + llvmFn llvm.Value + name string // precalculated llvmFn.Name() + params []llvm.Value // precalculated llvmFn.Params() + blocks []*basicBlock + locals map[llvm.Value]int + deferFrame llvm.Value + deferStack llvm.Value } // basicBlock represents a LLVM basic block and contains a slice of @@ -77,6 +79,22 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function { return fn } + for inst := llvmFn.EntryBasicBlock().FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { + if inst.IsACallInst().IsNil() || inst.CalledValue().Name() != "runtime.setupDeferFrame" { + continue + } + frame := inst.Operand(0) + stack := inst.Operand(1) + if frame.IsAAllocaInst().IsNil() || stack.IsACallInst().IsNil() || !strings.HasPrefix(stack.CalledValue().Name(), "llvm.stacksave") { + continue + } + use := stack.FirstUse() + if !use.IsNil() && use.User() == inst && use.NextUse().IsNil() { + fn.deferFrame = frame + fn.deferStack = stack + } + } + for i, param := range fn.params { fn.locals[param] = i } diff --git a/interp/errors.go b/interp/errors.go index 36aaa0cfb3..a15e51d12d 100644 --- a/interp/errors.go +++ b/interp/errors.go @@ -17,6 +17,7 @@ var ( errIntegerAsPointer = errors.New("interp: trying to use an integer as a pointer (memory-mapped I/O?)") errUnsupportedInst = errors.New("interp: unsupported instruction") errUnsupportedRuntimeInst = errors.New("interp: unsupported instruction (to be emitted at runtime)") + errDeferUnsupported = errors.New("interp: cannot partially evaluate a function with defer") errMapAlreadyCreated = errors.New("interp: map already created") errLoopUnrolled = errors.New("interp: loop unrolled") errLoopTooLong = errors.New("interp: loop ran too many iterations") @@ -29,7 +30,7 @@ var errInvalidPtrToIntSize = errors.New("interp: ptrtoint integer size does not func isRecoverableError(err error) bool { return err == errIntegerAsPointer || err == errUnsupportedInst || - err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated || + err == errUnsupportedRuntimeInst || err == errDeferUnsupported || err == errMapAlreadyCreated || err == errLoopUnrolled || err == errLoopTooLong || err == errInvalidPtrToIntSize } diff --git a/interp/interpreter.go b/interp/interpreter.go index 1f6bc060bc..ba025ac5e9 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -122,6 +122,25 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } inst := bb.instructions[instIndex] + // compiler/defer.go emits these checkpoints for panic recovery. + // A fully evaluated call cannot panic or need a runtime defer frame. + if !fn.deferFrame.IsNil() && inst.opcode == llvm.Call { + if inst.llvmInst == fn.deferStack { + locals[inst.localIndex] = literalValue{uint64(0)} + continue + } + called := inst.llvmInst.CalledValue() + if inst.llvmInst.OperandsCount() >= 2 && inst.llvmInst.Operand(0) == fn.deferFrame { + name := called.Name() + if name == "runtime.setupDeferFrame" || name == "runtime.destroyDeferFrame" { + continue + } + if attr := inst.llvmInst.GetCallSiteEnumAttribute(-1, llvm.AttributeKindID("returns_twice")); !attr.IsNil() { + locals[inst.localIndex] = makeLiteralInt(0, inst.llvmInst.Type().IntTypeWidth()) + continue + } + } + } operands = operands[:0] isRuntimeInst := false if inst.opcode != llvm.PHI { @@ -201,6 +220,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } switch inst.opcode { case llvm.Ret: + if !fn.deferFrame.IsNil() && len(mem.instructions) != 0 { + return nil, mem, r.errorAt(inst, errDeferUnsupported) + } if time.Since(r.start) > r.timeout { // Running for more than the allowed timeout; This shouldn't happen, but it does. // See github.com/tinygo-org/tinygo/issues/2124 @@ -272,6 +294,53 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } callFn := r.getFunction(fnPtr.llvmValue(&mem)) switch { + case callFn.name == "sync/atomic.SwapInt32" || callFn.name == "sync/atomic.SwapInt64" || + callFn.name == "sync/atomic.SwapUint32" || callFn.name == "sync/atomic.SwapUint64" || + callFn.name == "sync/atomic.SwapUintptr" || callFn.name == "sync/atomic.SwapPointer" || + callFn.name == "sync/atomic.CompareAndSwapInt32" || callFn.name == "sync/atomic.CompareAndSwapInt64" || + callFn.name == "sync/atomic.CompareAndSwapUint32" || callFn.name == "sync/atomic.CompareAndSwapUint64" || + callFn.name == "sync/atomic.CompareAndSwapUintptr" || callFn.name == "sync/atomic.CompareAndSwapPointer": + ptr, err := operands[1].asPointer(r) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + if mem.hasExternalLoadOrStore(ptr) { + if err := r.runAtRuntime(fn, inst, locals, &mem, indent); err != nil { + return nil, mem, err + } + continue + } + old := mem.load(ptr, operands[2].len(r)) + if old == nil { + if err := r.runAtRuntime(fn, inst, locals, &mem, indent); err != nil { + return nil, mem, err + } + continue + } + newValue := operands[2] + compare := strings.HasPrefix(callFn.name, "sync/atomic.CompareAndSwap") + if compare { + equal, err := r.interpretICmp(old, operands[2], llvm.IntEQ) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + if !equal { + locals[inst.localIndex] = literalValue{uint8(0)} + continue + } + newValue = operands[3] + } + if !mem.store(newValue, ptr) { + if err := r.runAtRuntime(fn, inst, locals, &mem, indent); err != nil { + return nil, mem, err + } + continue + } + if compare { + locals[inst.localIndex] = literalValue{uint8(1)} + } else { + locals[inst.localIndex] = old + } case callFn.name == "runtime.trackPointer": // Allocas and such are created as globals, so don't need a // runtime.trackPointer. @@ -279,16 +348,13 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // which case this call won't even get to this point but will // already be emitted in initAll. continue - case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "runtime.hashmapInterfaceHash" || + case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current" || callFn.name == "time.startTimer" || callFn.name == "time.stopTimer" || callFn.name == "time.resetTimer": // These functions should be run at runtime. Specifically: // * Print and panic functions are best emitted directly without // interpreting them, otherwise we get a ton of putchar (etc.) // calls. - // * runtime.hashmapGet tries to access the map value directly. - // This is not possible as the map value is treated as a special - // kind of object in this package. // * os.runtime_args reads globals that are initialized outside // the view of the interp package so it always needs to be run // at runtime. @@ -409,6 +475,44 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent result = max(lhs, rhs) } locals[inst.localIndex] = makeLiteralInt(uint64(result), inst.llvmInst.Type().IntTypeWidth()) + case strings.HasPrefix(callFn.name, "llvm.memset.p0"): + nBytes, err := operands[3].Uint(r) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + volatile, err := operands[4].Uint(r) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + if volatile != 0 { + if err := r.runAtRuntime(fn, inst, locals, &mem, indent); err != nil { + return nil, mem, err + } + continue + } + if nBytes == 0 { + continue + } + dst, err := operands[1].asPointer(r) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + obj := mem.get(dst.index()) + if mem.hasExternalLoadOrStore(dst) || obj.buffer == nil || uint64(dst.offset())+nBytes > uint64(obj.size) { + if err := r.runAtRuntime(fn, inst, locals, &mem, indent); err != nil { + return nil, mem, err + } + continue + } + fill, err := operands[2].Uint(r) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + buf := newRawValue(uint32(nBytes)) + for i := range buf.buf { + buf.buf[i] = fill + } + mem.store(buf, dst) case strings.HasPrefix(callFn.name, "llvm.memcpy.p0") || strings.HasPrefix(callFn.name, "llvm.memmove.p0"): // Copy a block of memory from one pointer to another. if operands[4].(literalValue).value.(uint8) != 0 { @@ -609,7 +713,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent return nil, mem, r.errorAt(inst, err) } size := operands[1].(literalValue).value.(uint64) - if inst.llvmInst.IsVolatile() || inst.llvmInst.Ordering() != llvm.AtomicOrderingNotAtomic || mem.hasExternalStore(ptr) { + if inst.llvmInst.IsVolatile() || mem.hasExternalStore(ptr) { // If there could be an external store (for example, because a // pointer to the object was passed to a function that could not // be interpreted at compile time) then the load must be done at @@ -639,7 +743,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } - if inst.llvmInst.IsVolatile() || inst.llvmInst.Ordering() != llvm.AtomicOrderingNotAtomic || mem.hasExternalLoadOrStore(ptr) { + if inst.llvmInst.IsVolatile() || mem.hasExternalLoadOrStore(ptr) { err := r.runAtRuntime(fn, inst, locals, &mem, indent) if err != nil { return nil, mem, err @@ -1072,6 +1176,9 @@ func intOperands(r *runner, lhs, rhs value) (int64, int64, error) { } func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error { + if !fn.deferFrame.IsNil() { + return r.errorAt(inst, errDeferUnsupported) + } numOperands := inst.llvmInst.OperandsCount() operands := make([]llvm.Value, numOperands) for i := range numOperands { diff --git a/interp/testdata/copy.ll b/interp/testdata/copy.ll index f389be340a..f3c1887ba8 100644 --- a/interp/testdata/copy.ll +++ b/interp/testdata/copy.ll @@ -13,6 +13,9 @@ target triple = "x86_64--linux" @volatileSrc = global [2 x i8] c"xy" @volatileDst = global [2 x i8] zeroinitializer +@fillDst = global [4 x i8] c"ABCD" +@fillEscapedDst = global [4 x i8] c"ABCD" +@fillVolatileDst = global [4 x i8] c"ABCD" declare void @use(ptr) @@ -27,6 +30,9 @@ define internal void @main.init() { call void @testMoveExternal() call void @testMoveEscaped() call void @testVolatileCopy() + call void @testFill() + call void @testFillEscaped() + call void @testVolatileFill() ret void } @@ -66,3 +72,25 @@ define internal void @testVolatileCopy() { declare void @llvm.memmove.p0.p0.i64(ptr, ptr, i64, i1) declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1) + +define internal void @testFill() { + %dst = getelementptr [4 x i8], ptr @fillDst, i64 0, i64 1 + call void @llvm.memset.p0.i64(ptr %dst, i8 127, i64 2, i1 false) + call void @llvm.memset.p0.i64(ptr null, i8 0, i64 0, i1 false) + ret void +} + +define internal void @testFillEscaped() { + call void @use(ptr @fillEscapedDst) + %dst = getelementptr [4 x i8], ptr @fillEscapedDst, i64 0, i64 1 + call void @llvm.memset.p0.i64(ptr %dst, i8 127, i64 2, i1 false) + ret void +} + +define internal void @testVolatileFill() { + %dst = getelementptr [4 x i8], ptr @fillVolatileDst, i64 0, i64 1 + call void @llvm.memset.p0.i64(ptr %dst, i8 127, i64 2, i1 true) + ret void +} + +declare void @llvm.memset.p0.i64(ptr, i8, i64, i1) diff --git a/interp/testdata/copy.out.ll b/interp/testdata/copy.out.ll index dcabd7b72d..81d1cfda35 100644 --- a/interp/testdata/copy.out.ll +++ b/interp/testdata/copy.out.ll @@ -9,6 +9,9 @@ target triple = "x86_64--linux" @moveEscapedDst = local_unnamed_addr global [4 x i8] zeroinitializer @volatileSrc = global [2 x i8] c"xy" @volatileDst = global [2 x i8] zeroinitializer +@fillDst = local_unnamed_addr global [4 x i8] c"A\7F\7FD" +@fillEscapedDst = global [4 x i8] c"ABCD" +@fillVolatileDst = global [4 x i8] c"ABCD" declare void @use(ptr) local_unnamed_addr @@ -17,6 +20,9 @@ define void @runtime.initAll() local_unnamed_addr { call void @use(ptr @moveEscapedSrc) call void @llvm.memmove.p0.p0.i64(ptr @moveEscapedDst, ptr @moveEscapedSrc, i64 4, i1 false) call void @llvm.memcpy.p0.p0.i64(ptr @volatileDst, ptr @volatileSrc, i64 2, i1 true) + call void @use(ptr @fillEscapedDst) + call void @llvm.memset.p0.i64(ptr getelementptr inbounds (i8, ptr @fillEscapedDst, i32 1), i8 127, i64 2, i1 false) + call void @llvm.memset.p0.i64(ptr getelementptr inbounds (i8, ptr @fillVolatileDst, i32 1), i8 127, i64 2, i1 true) ret void } @@ -24,4 +30,7 @@ declare void @llvm.memmove.p0.p0.i64(ptr nocapture writeonly, ptr nocapture read declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #0 +declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg) #1 + attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } +attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: write) } diff --git a/interp/testdata/defer.ll b/interp/testdata/defer.ll new file mode 100644 index 0000000000..79d0fb8e92 --- /dev/null +++ b/interp/testdata/defer.ll @@ -0,0 +1,102 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +%frame = type { ptr, ptr, i32, i32, ptr, ptr, ptr } +%record = type { ptr, ptr } + +@constant.counter = global i32 0 +@constant.result = global i32 0 +@external.counter = global i32 0 +@external.result = global i32 0 +@nested.counter = global i32 0 +@nested.result = global i32 0 +@panic.counter = global i32 0 +@panic.result = global i32 0 + +declare ptr @llvm.stacksave.p0() +declare void @runtime.setupDeferFrame(ptr, ptr, ptr) +declare void @runtime.destroyDeferFrame(ptr, ptr) +declare void @runtime._panic(ptr, ptr, ptr) +declare void @external() + +define void @runtime.initAll() { + call void @main.init() + ret void +} + +define internal void @main.init() { + %constant = call i32 @deferred(ptr @constant.counter, ptr @constantBody) + store i32 %constant, ptr @constant.result + %external = call i32 @deferred(ptr @external.counter, ptr @externalBody) + store i32 %external, ptr @external.result + %nested = call i32 @deferred(ptr @nested.counter, ptr @nestedBody) + store i32 %nested, ptr @nested.result + %panic = call i32 @deferred(ptr @panic.counter, ptr @panicBody) + store i32 %panic, ptr @panic.result + ret void +} + +define internal void @constantBody() { + ret void +} + +define internal void @externalBody() { + call void @external() + ret void +} + +define internal void @nestedBody() { + call void @externalBody() + ret void +} + +define internal void @panicBody() { + call void @runtime._panic(ptr undef, ptr undef, ptr undef) + unreachable +} + +define internal void @callback(ptr %counter) { + %old = load i32, ptr %counter + %next = add i32 %old, 1 + store i32 %next, ptr %counter + ret void +} + +define internal i32 @deferred(ptr %counter, ptr %bodyFn) { +entry: + %frame = alloca %frame + %head = getelementptr %frame, ptr %frame, i32 0, i32 6 + %sp = call ptr @llvm.stacksave.p0() + call void @runtime.setupDeferFrame(ptr %frame, ptr %sp, ptr undef) + %record = alloca %record + %prev = load ptr, ptr %head + store ptr %prev, ptr %record + %argument = getelementptr %record, ptr %record, i32 0, i32 1 + store ptr %counter, ptr %argument + store ptr %record, ptr %head + %checkpoint = call i32 asm "", "=r,r"(ptr %frame) returns_twice + %normal = icmp eq i32 %checkpoint, 0 + br i1 %normal, label %body, label %defers + +body: + store i32 40, ptr %counter + call void %bodyFn() + br label %defers + +defers: + %current = load ptr, ptr %head + %done = icmp eq ptr %current, null + br i1 %done, label %return, label %callback + +callback: + %next = load ptr, ptr %current + store ptr %next, ptr %head + %argptr = getelementptr %record, ptr %current, i32 0, i32 1 + %arg = load ptr, ptr %argptr + call void @callback(ptr %arg) + br label %defers + +return: + call void @runtime.destroyDeferFrame(ptr %frame, ptr undef) + ret i32 42 +} diff --git a/interp/testdata/defer.out.ll b/interp/testdata/defer.out.ll new file mode 100644 index 0000000000..27916cd516 --- /dev/null +++ b/interp/testdata/defer.out.ll @@ -0,0 +1,98 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64--linux" + +%frame = type { ptr, ptr, i32, i32, ptr, ptr, ptr } +%record = type { ptr, ptr } + +@constant.counter = local_unnamed_addr global i32 41 +@constant.result = local_unnamed_addr global i32 42 +@external.counter = global i32 0 +@external.result = local_unnamed_addr global i32 0 +@nested.counter = global i32 0 +@nested.result = local_unnamed_addr global i32 0 +@panic.counter = global i32 0 +@panic.result = local_unnamed_addr global i32 0 + +declare ptr @llvm.stacksave.p0() #0 + +declare void @runtime.setupDeferFrame(ptr, ptr, ptr) local_unnamed_addr + +declare void @runtime.destroyDeferFrame(ptr, ptr) local_unnamed_addr + +declare void @runtime._panic(ptr, ptr, ptr) local_unnamed_addr + +declare void @external() local_unnamed_addr + +define void @runtime.initAll() local_unnamed_addr { + %external = call fastcc i32 @deferred(ptr @external.counter, ptr @externalBody) + store i32 %external, ptr @external.result, align 4 + %nested = call fastcc i32 @deferred(ptr @nested.counter, ptr @nestedBody) + store i32 %nested, ptr @nested.result, align 4 + %panic = call fastcc i32 @deferred(ptr @panic.counter, ptr @panicBody) + store i32 %panic, ptr @panic.result, align 4 + ret void +} + +define internal void @externalBody() { + call void @external() + ret void +} + +define internal void @nestedBody() { + call void @externalBody() + ret void +} + +define internal void @panicBody() { + call void @runtime._panic(ptr undef, ptr undef, ptr undef) + unreachable +} + +define internal fastcc void @callback(ptr %counter) unnamed_addr { + %old = load i32, ptr %counter, align 4 + %next = add i32 %old, 1 + store i32 %next, ptr %counter, align 4 + ret void +} + +define internal fastcc i32 @deferred(ptr %counter, ptr %bodyFn) unnamed_addr { +entry: + %frame = alloca %frame, align 8 + %head = getelementptr %frame, ptr %frame, i32 0, i32 6 + %sp = call ptr @llvm.stacksave.p0() + call void @runtime.setupDeferFrame(ptr %frame, ptr %sp, ptr undef) + %record = alloca %record, align 8 + %prev = load ptr, ptr %head, align 8 + store ptr %prev, ptr %record, align 8 + %argument = getelementptr %record, ptr %record, i32 0, i32 1 + store ptr %counter, ptr %argument, align 8 + store ptr %record, ptr %head, align 8 + %checkpoint = call i32 asm "", "=r,r"(ptr %frame) #1 + %normal = icmp eq i32 %checkpoint, 0 + br i1 %normal, label %body, label %defers + +body: ; preds = %entry + store i32 40, ptr %counter, align 4 + call void %bodyFn() + br label %defers + +defers: ; preds = %callback, %body, %entry + %current = load ptr, ptr %head, align 8 + %done = icmp eq ptr %current, null + br i1 %done, label %return, label %callback + +callback: ; preds = %defers + %next = load ptr, ptr %current, align 8 + store ptr %next, ptr %head, align 8 + %argptr = getelementptr %record, ptr %current, i32 0, i32 1 + %arg = load ptr, ptr %argptr, align 8 + call fastcc void @callback(ptr %arg) + br label %defers + +return: ; preds = %defers + call void @runtime.destroyDeferFrame(ptr %frame, ptr undef) + ret i32 42 +} + +attributes #0 = { nocallback nofree nosync nounwind willreturn } +attributes #1 = { returns_twice } diff --git a/interp/testdata/revert.ll b/interp/testdata/revert.ll index e82c0fb78b..9a757e4912 100644 --- a/interp/testdata/revert.ll +++ b/interp/testdata/revert.ll @@ -5,6 +5,10 @@ declare void @externalCall(i64) declare i64 @ptrHash(ptr nocapture) +declare void @modifyAtomic(ptr) +declare i32 @"sync/atomic.SwapUint32"(ptr, i32, ptr) +declare i1 @"sync/atomic.CompareAndSwapUint32"(ptr, i32, i32, ptr) + @foo.knownAtRuntime = global i64 0 @bar.knownAtRuntime = global i64 0 @baz.someGlobal = external global [3 x {i64, i32}] @@ -14,6 +18,14 @@ declare i64 @ptrHash(ptr nocapture) @y.ready = global i32 0 @z.bloom = global i64 0 @z.arr = global [32 x i8] zeroinitializer +@atomic.value = global i32 0 +@atomic.old = global i32 0 +@atomic.success = global i1 false +@atomic.failure = global i1 false +@atomic.seen = global i32 0 +@atomic.runtimeOld = global i32 0 +@atomic.runtimeSuccess = global i1 false +@atomic.runtimeSeen = global i32 0 define void @runtime.initAll() unnamed_addr { entry: @@ -22,6 +34,7 @@ entry: call void @bar.init(ptr undef) call void @main.init(ptr undef) call void @x.init(ptr undef) + call void @atomic.init(ptr undef) call void @y.init(ptr undef) call void @z.init(ptr undef) ret void @@ -78,6 +91,27 @@ end: ret void } +define internal void @atomic.init(ptr %context) unnamed_addr { + store atomic i32 7, ptr @atomic.value seq_cst, align 4 + %old = call i32 @"sync/atomic.SwapUint32"(ptr @atomic.value, i32 9, ptr undef) + store i32 %old, ptr @atomic.old + %success = call i1 @"sync/atomic.CompareAndSwapUint32"(ptr @atomic.value, i32 9, i32 11, ptr undef) + store i1 %success, ptr @atomic.success + %failure = call i1 @"sync/atomic.CompareAndSwapUint32"(ptr @atomic.value, i32 9, i32 13, ptr undef) + store i1 %failure, ptr @atomic.failure + %seen = load atomic i32, ptr @atomic.value seq_cst, align 4 + store i32 %seen, ptr @atomic.seen + call void @modifyAtomic(ptr @atomic.value) + %runtimeOld = call i32 @"sync/atomic.SwapUint32"(ptr @atomic.value, i32 15, ptr undef) + store i32 %runtimeOld, ptr @atomic.runtimeOld + %runtimeSuccess = call i1 @"sync/atomic.CompareAndSwapUint32"(ptr @atomic.value, i32 15, i32 17, ptr undef) + store i1 %runtimeSuccess, ptr @atomic.runtimeSuccess + %runtimeSeen = load atomic i32, ptr @atomic.value seq_cst, align 4 + store i32 %runtimeSeen, ptr @atomic.runtimeSeen + store atomic i32 19, ptr @atomic.value seq_cst, align 4 + ret void +} + define internal void @z.init(ptr %context) unnamed_addr { ; This can be safely expanded. call void @z.setArr(ptr @z.bloom, i64 1, ptr @z.bloom) diff --git a/interp/testdata/revert.out.ll b/interp/testdata/revert.out.ll index db226c2635..a97249699c 100644 --- a/interp/testdata/revert.out.ll +++ b/interp/testdata/revert.out.ll @@ -5,16 +5,30 @@ target triple = "x86_64--linux" @bar.knownAtRuntime = local_unnamed_addr global i64 0 @baz.someGlobal = external local_unnamed_addr global [3 x { i64, i32 }] @baz.someInt = local_unnamed_addr global i32 0 -@x.atomicNum = local_unnamed_addr global i32 0 +@x.atomicNum = local_unnamed_addr global i32 1 @x.volatileNum = global i32 0 @y.ready = local_unnamed_addr global i32 0 @z.bloom = global i64 0 @z.arr = global [32 x i8] zeroinitializer +@atomic.value = global i32 11 +@atomic.old = local_unnamed_addr global i32 7 +@atomic.success = local_unnamed_addr global i1 true +@atomic.failure = local_unnamed_addr global i1 false +@atomic.seen = local_unnamed_addr global i32 11 +@atomic.runtimeOld = local_unnamed_addr global i32 0 +@atomic.runtimeSuccess = local_unnamed_addr global i1 false +@atomic.runtimeSeen = local_unnamed_addr global i32 0 declare void @externalCall(i64) local_unnamed_addr declare i64 @ptrHash(ptr nocapture) local_unnamed_addr +declare void @modifyAtomic(ptr) local_unnamed_addr + +declare i32 @"sync/atomic.SwapUint32"(ptr, i32, ptr) local_unnamed_addr + +declare i1 @"sync/atomic.CompareAndSwapUint32"(ptr, i32, i32, ptr) local_unnamed_addr + define void @runtime.initAll() unnamed_addr { entry: call fastcc void @baz.init(ptr undef) @@ -22,11 +36,16 @@ entry: %val = load i64, ptr @foo.knownAtRuntime, align 8 store i64 %val, ptr @bar.knownAtRuntime, align 8 call void @externalCall(i64 3) - store atomic i32 1, ptr @x.atomicNum seq_cst, align 4 - %x = load atomic i32, ptr @x.atomicNum seq_cst, align 4 - store i32 %x, ptr @x.atomicNum, align 4 %y = load volatile i32, ptr @x.volatileNum, align 4 store volatile i32 %y, ptr @x.volatileNum, align 4 + call void @modifyAtomic(ptr @atomic.value) + %runtimeOld = call i32 @"sync/atomic.SwapUint32"(ptr @atomic.value, i32 15, ptr undef) + store i32 %runtimeOld, ptr @atomic.runtimeOld, align 4 + %runtimeSuccess = call i1 @"sync/atomic.CompareAndSwapUint32"(ptr @atomic.value, i32 15, i32 17, ptr undef) + store i1 %runtimeSuccess, ptr @atomic.runtimeSuccess, align 1 + %runtimeSeen = load atomic i32, ptr @atomic.value seq_cst, align 4 + store i32 %runtimeSeen, ptr @atomic.runtimeSeen, align 4 + store atomic i32 19, ptr @atomic.value seq_cst, align 4 call fastcc void @y.init(ptr undef) call fastcc void @z.set(ptr @z.bloom, ptr @z.bloom) call fastcc void @z.setArr(ptr @z.arr, i64 32, ptr @z.bloom) diff --git a/testdata/init.go b/testdata/init.go index 4201dff3fe..c80b3b3d78 100644 --- a/testdata/init.go +++ b/testdata/init.go @@ -1,5 +1,7 @@ package main +import "sync" + func init() { println("init") } @@ -15,6 +17,9 @@ func main() { println("v7:", cap(v7), string(v7)) println("v8:", v8) println("v9:", len(v9), v9[0], v9[1], v9[2]) + println("deferred init:", deferredInitValue) + initOnce.Do(func() { initOnceCalls++ }) + println("once init:", onceInitValue, initOnceCalls) println(uint8SliceSrc[0]) println(uint8SliceDst[0]) @@ -24,6 +29,23 @@ func main() { println("shifted:", string(shifted[:]), shiftDone) } +var deferredInitValue = deferredInit() +var initOnce sync.Once +var initOnceCalls int +var onceInitValue = onceInit() + +func deferredInit() (result int) { + result = 20 + defer func() { result++ }() + defer func() { result *= 2 }() + return +} + +func onceInit() int { + initOnce.Do(func() { initOnceCalls++ }) + return initOnceCalls +} + type ( t2 struct { x int diff --git a/testdata/init.txt b/testdata/init.txt index ceeacac24c..c3da32ea94 100644 --- a/testdata/init.txt +++ b/testdata/init.txt @@ -10,6 +10,8 @@ v6: false v7: 3 foo v8: barba v9: 3 2 3 4 +deferred init: 41 +once init: 1 1 3 3 5 diff --git a/testdata/recover.go b/testdata/recover.go index 48f01f3f30..29d2243c1b 100644 --- a/testdata/recover.go +++ b/testdata/recover.go @@ -8,8 +8,10 @@ import ( var wg sync.WaitGroup var panicMap = map[interface{}]int{} +var recoveredInitValue = recoveredInit() func main() { + println("recovered init:", recoveredInitValue) println("# simple recover") recoverSimple() @@ -54,6 +56,16 @@ func main() { recoverSignals() } +func recoveredInit() (result int) { + defer func() { + if recover() == "init panic" { + result += 2 + } + }() + result = 40 + panic("init panic") +} + func recoverSimple() { defer func() { println("recovering...") diff --git a/testdata/recover.txt b/testdata/recover.txt index 620d182951..c335a9b9d9 100644 --- a/testdata/recover.txt +++ b/testdata/recover.txt @@ -1,3 +1,4 @@ +recovered init: 42 # simple recover running panic... recovering...