diff --git a/cl/_testgo/defer/in.go b/cl/_testgo/defer/in.go new file mode 100644 index 00000000..344527b3 --- /dev/null +++ b/cl/_testgo/defer/in.go @@ -0,0 +1,25 @@ +package main + +func f(s string) bool { + return len(s) > 5 +} + +func fail() { + panic("error") +} + +func main() { + defer func() { + println("hi") + if e := recover(); e != nil { + println(e.(string)) + } + }() + if s := "hello"; f(s) { + defer println(s) + } else { + defer println("world") + fail() + } + defer println("bye") +} diff --git a/cl/_testgo/defer/out.ll b/cl/_testgo/defer/out.ll new file mode 100644 index 00000000..e69de29b diff --git a/cl/compile.go b/cl/compile.go index 19b7e265..4b3ed739 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -796,6 +796,8 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { key := p.compileValue(b, v.Key) val := p.compileValue(b, v.Value) b.MapUpdate(m, key, val) + case *ssa.Defer: + p.call(b, llssa.Defer, &v.Call) case *ssa.Go: p.call(b, llssa.Go, &v.Call) case *ssa.Panic: diff --git a/internal/runtime/z_c.go b/internal/runtime/z_c.go index 2689b8cc..e7a1bef4 100644 --- a/internal/runtime/z_c.go +++ b/internal/runtime/z_c.go @@ -35,7 +35,7 @@ func AllocZ(size uintptr) unsafe.Pointer { } // Zeroinit initializes memory to zero. -func Zeroinit(p c.Pointer, size uintptr) c.Pointer { +func Zeroinit(p unsafe.Pointer, size uintptr) unsafe.Pointer { return c.Memset(p, 0, size) } diff --git a/internal/runtime/z_defer.go b/internal/runtime/z_defer.go new file mode 100644 index 00000000..7f59abb4 --- /dev/null +++ b/internal/runtime/z_defer.go @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +// Defer presents defer statements in a function. +type Defer struct { + proc func(uintptr) + bits uintptr + link *Defer +} + +// DeferProc calls deferred statements. +func DeferProc(d *Defer) { + for d != nil { + d.proc(d.bits) + d = d.link + } +} diff --git a/ssa/decl.go b/ssa/decl.go index 255bc1fc..d91d4d1f 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -85,15 +85,13 @@ func (p Package) NewVar(name string, typ types.Type, bg Background) Global { return p.doNewVar(name, t) } -/* -// NewVarFrom creates a new global variable. -func (p Package) NewVarFrom(name string, t Type) Global { +// NewVarEx creates a new global variable. +func (p Package) NewVarEx(name string, t Type) Global { if v, ok := p.vars[name]; ok { return v } return p.doNewVar(name, t) } -*/ func (p Package) doNewVar(name string, t Type) Global { var gbl llvm.Value @@ -185,7 +183,11 @@ type aFunction struct { params []Type freeVars Expr base int // base = 1 if hasFreeVars; base = 0 otherwise - hasVArg bool + + deferNextBit int // next defer bit + deferData Expr + + hasVArg bool } // Function represents a function or method. @@ -222,7 +224,14 @@ func newFunction(fn llvm.Value, t Type, pkg Package, prog Program, hasFreeVars b if hasFreeVars { base = 1 } - return &aFunction{Expr{fn, t}, pkg, prog, nil, params, Expr{}, base, hasVArg} + return &aFunction{ + Expr: Expr{fn, t}, + Pkg: pkg, + Prog: prog, + params: params, + base: base, + hasVArg: hasVArg, + } } func newParams(fn Type, prog Program) (params []Type, hasVArg bool) { @@ -240,12 +249,15 @@ func newParams(fn Type, prog Program) (params []Type, hasVArg bool) { return } -/* // Name returns the function's name. func (p Function) Name() string { return p.impl.Name() } -*/ + +// DeferFuncName returns the name of the defer procedure. +func (p Function) DeferFuncName() string { + return p.Name() + "._llgo_defer" +} // Params returns the function's ith parameter. func (p Function) Param(i int) Expr { diff --git a/ssa/expr.go b/ssa/expr.go index c829d3aa..fa04059a 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -493,7 +493,6 @@ func (b Builder) ChangeType(t Type, x Expr) (ret Expr) { typ := t.raw.Type switch typ.(type) { default: - // TODO(xsw): remove instr name ret.impl = llvm.CreateBitCast(b.impl, x.impl, t.ll) ret.Type = b.Prog.rawType(typ) return @@ -748,7 +747,6 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { var ll llvm.Type var data Expr var sig *types.Signature - var prog = b.Prog var raw = fn.raw.Type switch kind { case vkClosure: @@ -758,7 +756,7 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { fallthrough case vkFuncPtr: sig = raw.(*types.Signature) - ll = prog.FuncDecl(sig, InC).ll + ll = b.Prog.FuncDecl(sig, InC).ll case vkFuncDecl: sig = raw.(*types.Signature) ll = fn.ll @@ -768,7 +766,7 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { default: log.Panicf("unreachable: %d(%T)\n", kind, raw) } - ret.Type = prog.retType(sig) + ret.Type = b.Prog.retType(sig) ret.impl = llvm.CreateCall(b.impl, ll, fn.impl, llvmParamsEx(data, args, sig.Params(), b)) return } @@ -795,8 +793,8 @@ type DoAction int const ( Call DoAction = iota - Go Defer + Go ) // Do call a function with an action. @@ -804,6 +802,8 @@ func (b Builder) Do(da DoAction, fn Expr, args ...Expr) (ret Expr) { switch da { case Call: return b.Call(fn, args...) + case Defer: + b.Defer(fn, args...) case Go: b.Go(fn, args...) } diff --git a/ssa/goroutine.go b/ssa/goroutine.go index e050632a..389ae8df 100644 --- a/ssa/goroutine.go +++ b/ssa/goroutine.go @@ -50,7 +50,7 @@ func (p Program) tyPthreadCreate() *types.Signature { return p.createThdTy } -func (b Builder) createThread(pp, attr, routine, arg Expr) Expr { +func (b Builder) pthreadCreate(pp, attr, routine, arg Expr) Expr { fn := b.Pkg.cFunc("pthread_create", b.Prog.tyPthreadCreate()) return b.Call(fn, pp, attr, routine, arg) } @@ -86,7 +86,7 @@ func (b Builder) Go(fn Expr, args ...Expr) { data := Expr{b.aggregateMalloc(t, flds...), voidPtr} size := prog.SizeOf(voidPtr) pthd := b.Alloca(prog.IntVal(uint64(size), prog.Uintptr())) - b.createThread(pthd, prog.Null(voidPtr), pkg.routine(t, len(args)), data) + b.pthreadCreate(pthd, prog.Null(voidPtr), pkg.routine(t, len(args)), data) } func (p Package) routineName() string { @@ -112,3 +112,71 @@ func (p Package) routine(t Type, n int) Expr { } // ----------------------------------------------------------------------------- + +// func(c.Pointer) +func (p Program) tyDestruct() *types.Signature { + if p.destructTy == nil { + paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(paramPtr) + p.destructTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.destructTy +} + +// func(*c.Int, func(c.Pointer)) c.Int +func (p Program) tyPthreadKeyCreate() *types.Signature { + if p.createKeyTy == nil { + cint := p.CInt() + cintPtr := p.Pointer(cint) + paramCintPtr := types.NewParam(token.NoPos, nil, "", cintPtr.raw.Type) + paramDestruct := types.NewParam(token.NoPos, nil, "", p.tyDestruct()) + paramCInt := types.NewParam(token.NoPos, nil, "", cint.raw.Type) + params := types.NewTuple(paramCintPtr, paramDestruct) + results := types.NewTuple(paramCInt) + p.createKeyTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.createKeyTy +} + +func (b Builder) pthreadKeyCreate(key, destruct Expr) Expr { + fn := b.Pkg.cFunc("pthread_key_create", b.Prog.tyPthreadKeyCreate()) + return b.Call(fn, key, destruct) +} + +// ----------------------------------------------------------------------------- + +// func(c.Int) c.Pointer +func (p Program) tyPthreadGetspecific() *types.Signature { + if p.getSpecTy == nil { + paramCInt := types.NewParam(token.NoPos, nil, "", p.CInt().raw.Type) + paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(paramCInt) + results := types.NewTuple(paramPtr) + p.getSpecTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.getSpecTy +} + +// func(c.Int, c.Pointer) c.Int +func (p Program) tyPthreadSetspecific() *types.Signature { + if p.setSpecTy == nil { + paramCInt := types.NewParam(token.NoPos, nil, "", p.CInt().raw.Type) + paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type) + params := types.NewTuple(paramCInt, paramPtr) + results := types.NewTuple(paramCInt) + p.setSpecTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.setSpecTy +} + +func (b Builder) pthreadGetspecific(key Expr) Expr { + fn := b.Pkg.cFunc("pthread_getspecific", b.Prog.tyPthreadGetspecific()) + return b.Call(fn, key) +} + +func (b Builder) pthreadSetspecific(key, val Expr) Expr { + fn := b.Pkg.cFunc("pthread_setspecific", b.Prog.tyPthreadSetspecific()) + return b.Call(fn, key, val) +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/memory.go b/ssa/memory.go index df7b4908..94cfc7f3 100644 --- a/ssa/memory.go +++ b/ssa/memory.go @@ -74,6 +74,14 @@ func (b Builder) aggregateAllocU(t Type, flds ...llvm.Value) llvm.Value { return ptr } +func (b Builder) aggregateAlloca(t Type, flds ...llvm.Value) llvm.Value { + prog := b.Prog + size := prog.SizeOf(t) + ptr := b.Alloca(prog.IntVal(size, prog.Uintptr())).impl + aggregateInit(b.impl, ptr, t.ll, flds...) + return ptr +} + func (b Builder) aggregateMalloc(t Type, flds ...llvm.Value) llvm.Value { prog := b.Prog size := prog.SizeOf(t) diff --git a/ssa/package.go b/ssa/package.go index a81deef7..94a5a18e 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -137,6 +137,7 @@ type aProgram struct { boolTy Type cstrTy Type cintTy Type + cintPtr Type stringTy Type uintptrTy Type intTy Type @@ -148,26 +149,35 @@ type aProgram struct { u32Ty Type i64Ty Type u64Ty Type + pyObjPtr Type pyObjPPtr Type - abiTyptr Type - abiTypptr Type - pyImpTy *types.Signature - pyNewList *types.Signature - pyListSetI *types.Signature - callArgs *types.Signature - callNoArgs *types.Signature - callOneArg *types.Signature - callFOArgs *types.Signature - loadPyModS *types.Signature - getAttrStr *types.Signature + abiTyPtr Type + abiTyPPtr Type + deferTy Type + deferPtr Type + + pyImpTy *types.Signature + pyNewList *types.Signature + pyListSetI *types.Signature + floatFromDbl *types.Signature + callNoArgs *types.Signature + callOneArg *types.Signature + callFOArgs *types.Signature + loadPyModS *types.Signature + getAttrStr *types.Signature mallocTy *types.Signature freeTy *types.Signature + createKeyTy *types.Signature createThdTy *types.Signature + getSpecTy *types.Signature + setSpecTy *types.Signature routineTy *types.Signature + destructTy *types.Signature + deferFnTy *types.Signature paramObjPtr_ *types.Var @@ -333,20 +343,36 @@ func (p Program) Eface() Type { } */ +// DeferPtr returns *runtime.Defer. +func (p Program) DeferPtr() Type { + if p.deferPtr == nil { + p.deferPtr = p.Pointer(p.Defer()) + } + return p.deferPtr +} + +// Defer returns runtime.Defer type. +func (p Program) Defer() Type { + if p.deferTy == nil { + p.deferTy = p.rtType("Defer") + } + return p.deferTy +} + // AbiTypePtr returns *abi.Type. func (p Program) AbiTypePtr() Type { - if p.abiTyptr == nil { - p.abiTyptr = p.rawType(types.NewPointer(p.rtNamed("Type"))) + if p.abiTyPtr == nil { + p.abiTyPtr = p.rawType(types.NewPointer(p.rtNamed("Type"))) } - return p.abiTyptr + return p.abiTyPtr } // AbiTypePtrPtr returns **abi.Type. func (p Program) AbiTypePtrPtr() Type { - if p.abiTypptr == nil { - p.abiTypptr = p.Pointer(p.AbiTypePtr()) + if p.abiTyPPtr == nil { + p.abiTyPPtr = p.Pointer(p.AbiTypePtr()) } - return p.abiTypptr + return p.abiTyPPtr } // PyObjectPtrPtr returns the **py.Object type. @@ -418,6 +444,15 @@ func (p Program) Any() Type { return p.anyTy } +// CIntPtr returns *c.Int type. +func (p Program) CIntPtr() Type { + if p.cintPtr == nil { + p.cintPtr = p.Pointer(p.CInt()) + } + return p.cintPtr +} + +// CInt returns c.Int type. func (p Program) CInt() Type { if p.cintTy == nil { // C.int p.cintTy = p.rawType(types.Typ[types.Int32]) // TODO(xsw): support 64-bit @@ -528,6 +563,8 @@ type aPackage struct { Prog Program iRoutine int + + deferMgr } type Package = *aPackage @@ -595,10 +632,14 @@ func (p Package) String() string { // AfterInit is called after the package is initialized (init all packages that depends on). func (p Package) AfterInit(b Builder, ret BasicBlock) { + doDeferInit := p.hasDefer() doAbiInit := p.hasAbiInit() doPyLoadModSyms := p.pyHasModSyms() - if doAbiInit || doPyLoadModSyms { + if doDeferInit || doAbiInit || doPyLoadModSyms { b.SetBlockEx(ret, afterInit, false) + if doDeferInit { + p.deferInit(b) + } if doAbiInit { p.abiInit(b) } @@ -739,14 +780,14 @@ func (p Program) tyNewList() *types.Signature { // func(float64) *Object func (p Program) tyFloatFromDouble() *types.Signature { - if p.callArgs == nil { + if p.floatFromDbl == nil { paramObjPtr := p.paramObjPtr() paramFloat := types.NewParam(token.NoPos, nil, "", p.Float64().raw.Type) params := types.NewTuple(paramFloat) results := types.NewTuple(paramObjPtr) - p.callArgs = types.NewSignatureType(nil, nil, nil, params, results, false) + p.floatFromDbl = types.NewSignatureType(nil, nil, nil, params, results, false) } - return p.callArgs + return p.floatFromDbl } // func(*Object, ...) diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index 92fcd37f..43523753 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -19,9 +19,11 @@ package ssa import ( "bytes" "fmt" + "go/token" "go/types" "log" "strings" + "unsafe" "github.com/goplus/llvm" ) @@ -128,6 +130,96 @@ func notInit(instr llvm.Value) bool { return true } +// ----------------------------------------------------------------------------- + +const ( + deferKey = "__llgo_defer" +) + +type deferMgr struct { + deferb unsafe.Pointer + deferparam Expr +} + +// func(uintptr) +func (p Program) tyDeferFunc() *types.Signature { + if p.deferFnTy == nil { + paramUintptr := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type) + params := types.NewTuple(paramUintptr) + p.deferFnTy = types.NewSignatureType(nil, nil, nil, params, nil, false) + } + return p.deferFnTy +} + +func (p Package) hasDefer() bool { + return p.deferb != nil +} + +func (p Package) deferInit(b Builder) { + prog := p.Prog + keyVar := p.newDeferKey() + keyNil := prog.Null(prog.CIntPtr()) + keyVar.Init(keyNil) + keyVar.impl.SetLinkage(llvm.LinkOnceAnyLinkage) + + eq := b.BinOp(token.EQL, b.Load(keyVar.Expr), keyNil) + b.IfThen(eq, func() { + b.pthreadKeyCreate(keyVar.Expr, prog.Null(prog.VoidPtr())) + }) + + b = Builder(p.deferb) + b.Return() +} + +func (p Package) newDeferKey() Global { + return p.NewVarEx(deferKey, p.Prog.CIntPtr()) +} + +func (b Builder) deferKey() Expr { + return b.Load(b.Pkg.newDeferKey().Expr) +} + +// Defer emits a defer instruction. +func (b Builder) Defer(fn Expr, args ...Expr) { + if debugInstr { + logCall("Defer", fn, args) + } + prog := b.Prog + pkg := b.Pkg + self := b.Func + next := self.deferNextBit + self.deferNextBit++ + zero := prog.Val(uintptr(0)) + key := b.deferKey() + if next == 0 { + name := self.DeferFuncName() + deferfn := pkg.NewFunc(name, b.Prog.tyDeferFunc(), InC) + deferb := deferfn.MakeBody(1) + pkg.deferb = unsafe.Pointer(deferb) + pkg.deferparam = deferfn.Param(0) + + // TODO(xsw): move to funtion start + // proc func(uintptr) + // bits uintptr + // link *Defer + link := b.pthreadGetspecific(key) + ptr := b.aggregateAlloca(prog.Defer(), deferfn.impl, zero.impl, link.impl) + self.deferData = Expr{ptr, prog.DeferPtr()} + b.pthreadSetspecific(key, self.deferData) + } + bitsPtr := b.FieldAddr(self.deferData, 1) + nextbit := prog.Val(uintptr(1 << next)) + b.Store(bitsPtr, b.BinOp(token.OR, b.Load(bitsPtr), nextbit)) + + b = Builder(pkg.deferb) + has := b.BinOp(token.NEQ, b.BinOp(token.AND, pkg.deferparam, nextbit), zero) + b.IfThen(has, func() { + b.Call(fn, args...) + }) +} + +// ----------------------------------------------------------------------------- + // Panic emits a panic instruction. func (b Builder) Panic(v Expr) { if debugInstr { @@ -206,6 +298,17 @@ func (b Builder) If(cond Expr, thenb, elseb BasicBlock) { b.impl.CreateCondBr(cond.impl, thenb.first, elseb.first) } +// IfThen emits an if-then instruction. +func (b Builder) IfThen(cond Expr, then func()) { + blks := b.Func.MakeBlocks(2) + b.If(cond, blks[0], blks[1]) + b.SetBlockEx(blks[0], AtEnd, false) + then() + b.Jump(blks[1]) + b.SetBlockEx(blks[1], AtEnd, false) + b.blk.last = blks[1].last +} + // ----------------------------------------------------------------------------- // Phi represents a phi node.