Fixes #1358 The compiler was attempting to compile methods for non-instantiated generic types (like unique.Handle[T]), which caused the SSA builder to panic with a nil pointer dereference. This fix adds two checks: 1. In compileType: Skip generic type definitions (types with type parameters but no type arguments) 2. In compileMethods: Skip types containing type parameters and skip methods from external packages The unique.Make demo now compiles without panicking, though it still requires runtime support for the unique package to link successfully. Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: cpunion <cpunion@users.noreply.github.com>
1219 lines
30 KiB
Go
1219 lines
30 KiB
Go
/*
|
|
* 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 cl
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/constant"
|
|
"go/token"
|
|
"go/types"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/goplus/llgo/cl/blocks"
|
|
"github.com/goplus/llgo/internal/typepatch"
|
|
"golang.org/x/tools/go/ssa"
|
|
|
|
llssa "github.com/goplus/llgo/ssa"
|
|
)
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
type dbgFlags = int
|
|
|
|
const (
|
|
DbgFlagInstruction dbgFlags = 1 << iota
|
|
DbgFlagGoSSA
|
|
|
|
DbgFlagAll = DbgFlagInstruction | DbgFlagGoSSA
|
|
)
|
|
|
|
var (
|
|
debugInstr bool
|
|
debugGoSSA bool
|
|
|
|
enableCallTracing bool
|
|
enableDbg bool
|
|
enableDbgSyms bool
|
|
disableInline bool
|
|
)
|
|
|
|
// SetDebug sets debug flags.
|
|
func SetDebug(dbgFlags dbgFlags) {
|
|
debugInstr = (dbgFlags & DbgFlagInstruction) != 0
|
|
debugGoSSA = (dbgFlags & DbgFlagGoSSA) != 0
|
|
}
|
|
|
|
func EnableDebug(b bool) {
|
|
enableDbg = b
|
|
}
|
|
|
|
func EnableDbgSyms(b bool) {
|
|
enableDbgSyms = b
|
|
}
|
|
|
|
func EnableTrace(b bool) {
|
|
enableCallTracing = b
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
type instrOrValue interface {
|
|
ssa.Instruction
|
|
ssa.Value
|
|
}
|
|
|
|
const (
|
|
PkgNormal = iota
|
|
PkgLLGo
|
|
PkgPyModule // py.<module>
|
|
PkgNoInit // noinit: a package that don't need to be initialized
|
|
PkgDeclOnly // decl: a package that only have declarations
|
|
PkgLinkIR // link llvm ir (.ll)
|
|
PkgLinkExtern // link external object (.a/.so/.dll/.dylib/etc.)
|
|
// PkgLinkBitCode // link bitcode (.bc)
|
|
)
|
|
|
|
type pkgInfo struct {
|
|
kind int
|
|
}
|
|
|
|
type none = struct{}
|
|
|
|
type context struct {
|
|
prog llssa.Program
|
|
pkg llssa.Package
|
|
fn llssa.Function
|
|
fset *token.FileSet
|
|
goProg *ssa.Program
|
|
goTyps *types.Package
|
|
goPkg *ssa.Package
|
|
pyMod string
|
|
skips map[string]none
|
|
loaded map[*types.Package]*pkgInfo // loaded packages
|
|
bvals map[ssa.Value]llssa.Expr // block values
|
|
vargs map[*ssa.Alloc][]llssa.Expr // varargs
|
|
|
|
patches Patches
|
|
blkInfos []blocks.Info
|
|
|
|
inits []func()
|
|
phis []func()
|
|
initAfter func()
|
|
|
|
state pkgState
|
|
inCFunc bool
|
|
skipall bool
|
|
|
|
cgoCalled bool
|
|
cgoArgs []llssa.Expr
|
|
cgoRet llssa.Expr
|
|
cgoSymbols []string
|
|
}
|
|
|
|
type pkgState byte
|
|
|
|
const (
|
|
pkgNormal pkgState = iota
|
|
pkgHasPatch
|
|
pkgInPatch
|
|
|
|
pkgFNoOldInit = 0x80 // flag if no initFnNameOld
|
|
)
|
|
|
|
func (p *context) compileType(pkg llssa.Package, t *ssa.Type) {
|
|
tn := t.Object().(*types.TypeName)
|
|
if tn.IsAlias() { // don't need to compile alias type
|
|
return
|
|
}
|
|
tnName := tn.Name()
|
|
typ := tn.Type()
|
|
name := llssa.FullName(tn.Pkg(), tnName)
|
|
if debugInstr {
|
|
log.Println("==> NewType", name, typ)
|
|
}
|
|
if named, ok := typ.(*types.Named); ok {
|
|
if tp := named.TypeParams(); tp != nil && tp.Len() > 0 {
|
|
if ta := named.TypeArgs(); ta == nil || ta.Len() == 0 {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
p.compileMethods(pkg, typ)
|
|
p.compileMethods(pkg, types.NewPointer(typ))
|
|
}
|
|
|
|
func hasGenericTypeParam(typ types.Type) bool {
|
|
switch t := typ.(type) {
|
|
case *types.TypeParam:
|
|
return true
|
|
case *types.Named:
|
|
if tp := t.TypeParams(); tp != nil && tp.Len() > 0 {
|
|
if ta := t.TypeArgs(); ta == nil || ta.Len() == 0 {
|
|
return true
|
|
}
|
|
}
|
|
if ta := t.TypeArgs(); ta != nil {
|
|
for i := 0; i < ta.Len(); i++ {
|
|
if hasGenericTypeParam(ta.At(i)) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return hasGenericTypeParam(t.Underlying())
|
|
case *types.Pointer:
|
|
return hasGenericTypeParam(t.Elem())
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *context) compileMethods(pkg llssa.Package, typ types.Type) {
|
|
if hasGenericTypeParam(typ) {
|
|
return
|
|
}
|
|
var typPkg *types.Package
|
|
if named, ok := typ.(*types.Named); ok {
|
|
typPkg = named.Obj().Pkg()
|
|
} else if ptr, ok := typ.(*types.Pointer); ok {
|
|
if named, ok := ptr.Elem().(*types.Named); ok {
|
|
typPkg = named.Obj().Pkg()
|
|
}
|
|
}
|
|
if typPkg != nil && typPkg != p.goTyps {
|
|
return
|
|
}
|
|
prog := p.goProg
|
|
mthds := prog.MethodSets.MethodSet(typ)
|
|
for i, n := 0, mthds.Len(); i < n; i++ {
|
|
mthd := mthds.At(i)
|
|
if ssaMthd := prog.MethodValue(mthd); ssaMthd != nil {
|
|
if ssaMthd.TypeParams() != nil || ssaMthd.TypeArgs() != nil {
|
|
continue
|
|
}
|
|
p.compileFuncDecl(pkg, ssaMthd)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global variable.
|
|
func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
|
|
typ := p.patchType(gbl.Type())
|
|
name, vtype, define := p.varName(gbl.Pkg.Pkg, gbl)
|
|
if vtype == pyVar {
|
|
return
|
|
}
|
|
if debugInstr {
|
|
log.Println("==> NewVar", name, typ)
|
|
}
|
|
g := pkg.NewVar(name, typ, llssa.Background(vtype))
|
|
if define {
|
|
g.InitNil()
|
|
}
|
|
}
|
|
|
|
func makeClosureCtx(pkg *types.Package, vars []*ssa.FreeVar) *types.Var {
|
|
n := len(vars)
|
|
flds := make([]*types.Var, n)
|
|
for i, v := range vars {
|
|
flds[i] = types.NewField(token.NoPos, pkg, v.Name(), v.Type(), false)
|
|
}
|
|
t := types.NewPointer(types.NewStruct(flds, nil))
|
|
return types.NewParam(token.NoPos, pkg, "__llgo_ctx", t)
|
|
}
|
|
|
|
func isCgoExternSymbol(f *ssa.Function) bool {
|
|
name := f.Name()
|
|
return isCgoCfunc(name) || isCgoCmacro(name)
|
|
}
|
|
|
|
func isCgoCfpvar(name string) bool {
|
|
return strings.HasPrefix(name, "_Cfpvar_")
|
|
}
|
|
|
|
func isCgoCfunc(name string) bool {
|
|
return strings.HasPrefix(name, "_Cfunc_")
|
|
}
|
|
|
|
func isCgoCmacro(name string) bool {
|
|
return strings.HasPrefix(name, "_Cmacro_")
|
|
}
|
|
|
|
func isCgoVar(name string) bool {
|
|
return strings.HasPrefix(name, "__cgo_")
|
|
}
|
|
|
|
func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyObjRef, int) {
|
|
pkgTypes, name, ftype := p.funcName(f)
|
|
if ftype != goFunc {
|
|
return nil, nil, ignoredFunc
|
|
}
|
|
sig := p.patchType(f.Signature).(*types.Signature)
|
|
state := p.state
|
|
isInit := (f.Name() == "init" && sig.Recv() == nil)
|
|
if isInit && state == pkgHasPatch {
|
|
name = initFnNameOfHasPatch(name)
|
|
// TODO(xsw): pkg.init$guard has been set, change ssa.If to ssa.Jump
|
|
block := f.Blocks[0].Instrs[1].(*ssa.If).Block()
|
|
block.Succs[0], block.Succs[1] = block.Succs[1], block.Succs[0]
|
|
}
|
|
|
|
fn := pkg.FuncOf(name)
|
|
if fn != nil && fn.HasBody() {
|
|
return fn, nil, goFunc
|
|
}
|
|
|
|
var hasCtx = len(f.FreeVars) > 0
|
|
if hasCtx {
|
|
if debugInstr {
|
|
log.Println("==> NewClosure", name, "type:", sig)
|
|
}
|
|
ctx := makeClosureCtx(pkgTypes, f.FreeVars)
|
|
sig = llssa.FuncAddCtx(ctx, sig)
|
|
} else {
|
|
if debugInstr {
|
|
log.Println("==> NewFunc", name, "type:", sig.Recv(), sig, "ftype:", ftype)
|
|
}
|
|
}
|
|
if fn == nil {
|
|
fn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), hasCtx, f.Origin() != nil)
|
|
if disableInline {
|
|
fn.Inline(llssa.NoInline)
|
|
}
|
|
}
|
|
// set compiled to check generic function global instantiation
|
|
pkg.Prog.SetFuncCompiled(name)
|
|
isCgo := isCgoExternSymbol(f)
|
|
if nblk := len(f.Blocks); nblk > 0 {
|
|
p.cgoCalled = false
|
|
p.cgoArgs = nil
|
|
if isCgo {
|
|
fn.MakeBlocks(1)
|
|
} else {
|
|
fn.MakeBlocks(nblk) // to set fn.HasBody() = true
|
|
}
|
|
if f.Recover != nil { // set recover block
|
|
fn.SetRecover(fn.Block(f.Recover.Index))
|
|
}
|
|
p.inits = append(p.inits, func() {
|
|
p.fn = fn
|
|
p.state = state // restore pkgState when compiling funcBody
|
|
defer func() {
|
|
p.fn = nil
|
|
}()
|
|
p.phis = nil
|
|
if debugGoSSA {
|
|
f.WriteTo(os.Stderr)
|
|
}
|
|
if debugInstr {
|
|
log.Println("==> FuncBody", name)
|
|
}
|
|
b := fn.NewBuilder()
|
|
if enableDbg {
|
|
pos := p.goProg.Fset.Position(f.Pos())
|
|
bodyPos := p.getFuncBodyPos(f)
|
|
b.DebugFunction(fn, pos, bodyPos)
|
|
}
|
|
p.bvals = make(map[ssa.Value]llssa.Expr)
|
|
off := make([]int, len(f.Blocks))
|
|
if isCgo {
|
|
p.cgoArgs = make([]llssa.Expr, len(f.Params))
|
|
for i, param := range f.Params {
|
|
p.cgoArgs[i] = p.compileValue(b, param)
|
|
}
|
|
} else {
|
|
for i, block := range f.Blocks {
|
|
off[i] = p.compilePhis(b, block)
|
|
}
|
|
}
|
|
p.blkInfos = blocks.Infos(f.Blocks)
|
|
i := 0
|
|
for {
|
|
block := f.Blocks[i]
|
|
doModInit := (i == 1 && isInit)
|
|
p.compileBlock(b, block, off[i], doModInit)
|
|
if isCgo {
|
|
// just process first block for performance
|
|
break
|
|
}
|
|
if i = p.blkInfos[i].Next; i < 0 {
|
|
break
|
|
}
|
|
}
|
|
for _, phi := range p.phis {
|
|
phi()
|
|
}
|
|
b.EndBuild()
|
|
})
|
|
for _, af := range f.AnonFuncs {
|
|
p.compileFuncDecl(pkg, af)
|
|
}
|
|
}
|
|
return fn, nil, goFunc
|
|
}
|
|
|
|
func (p *context) getFuncBodyPos(f *ssa.Function) token.Position {
|
|
if f.Object() != nil {
|
|
if fn, ok := f.Object().(*types.Func); ok && fn.Scope() != nil {
|
|
return p.goProg.Fset.Position(fn.Scope().Pos())
|
|
}
|
|
}
|
|
return p.goProg.Fset.Position(f.Pos())
|
|
}
|
|
|
|
func isGlobal(v *types.Var) bool {
|
|
// TODO(lijie): better implementation
|
|
return strings.HasPrefix(v.Parent().String(), "package ")
|
|
}
|
|
|
|
func (p *context) debugRef(b llssa.Builder, v *ssa.DebugRef) {
|
|
object := v.Object()
|
|
variable, ok := object.(*types.Var)
|
|
if !ok {
|
|
// Not a local variable.
|
|
return
|
|
}
|
|
if variable.IsField() {
|
|
// skip *ssa.FieldAddr
|
|
return
|
|
}
|
|
if isGlobal(variable) {
|
|
// avoid generate local variable debug info of global variable in function
|
|
return
|
|
}
|
|
pos := p.goProg.Fset.Position(v.Pos())
|
|
value := p.compileValue(b, v.X)
|
|
fn := v.Parent()
|
|
dbgVar := p.getLocalVariable(b, fn, variable)
|
|
scope := variable.Parent()
|
|
diScope := b.DIScope(p.fn, scope)
|
|
if v.IsAddr {
|
|
// *ssa.Alloc
|
|
b.DIDeclare(variable, value, dbgVar, diScope, pos, b.Func.Block(v.Block().Index))
|
|
} else {
|
|
b.DIValue(variable, value, dbgVar, diScope, pos, b.Func.Block(v.Block().Index))
|
|
}
|
|
}
|
|
|
|
func (p *context) debugParams(b llssa.Builder, f *ssa.Function) {
|
|
for i, param := range f.Params {
|
|
variable := param.Object().(*types.Var)
|
|
pos := p.goProg.Fset.Position(param.Pos())
|
|
v := p.compileValue(b, param)
|
|
ty := param.Type()
|
|
argNo := i + 1
|
|
div := b.DIVarParam(p.fn, pos, param.Name(), p.type_(ty, llssa.InGo), argNo)
|
|
b.DIParam(variable, v, div, p.fn, pos, p.fn.Block(0))
|
|
}
|
|
}
|
|
|
|
func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, doModInit bool) llssa.BasicBlock {
|
|
var last int
|
|
var pyModInit bool
|
|
var prog = p.prog
|
|
var pkg = p.pkg
|
|
var fn = p.fn
|
|
var instrs = block.Instrs[n:]
|
|
var ret = fn.Block(block.Index)
|
|
b.SetBlock(ret)
|
|
if block.Index == 0 && enableCallTracing && !strings.HasPrefix(fn.Name(), "github.com/goplus/llgo/runtime/internal/runtime.Print") {
|
|
b.Printf("call " + fn.Name() + "\n\x00")
|
|
}
|
|
// place here to avoid wrong current-block
|
|
if enableDbgSyms && block.Index == 0 {
|
|
p.debugParams(b, block.Parent())
|
|
}
|
|
if doModInit {
|
|
if pyModInit = p.pyMod != ""; pyModInit {
|
|
last = len(instrs) - 1
|
|
instrs = instrs[:last]
|
|
} else if p.state != pkgHasPatch {
|
|
// TODO(xsw): confirm pyMod don't need to call AfterInit
|
|
p.initAfter = func() {
|
|
pkg.AfterInit(b, ret)
|
|
}
|
|
}
|
|
}
|
|
fnName := block.Parent().Name()
|
|
cgoReturned := false
|
|
isCgoCfunc := isCgoCfunc(fnName)
|
|
isCgoCmacro := isCgoCmacro(fnName)
|
|
for i, instr := range instrs {
|
|
if i == 1 && doModInit && p.state == pkgInPatch { // in patch package but no pkgFNoOldInit
|
|
initFnNameOld := initFnNameOfHasPatch(p.fn.Name())
|
|
fnOld := pkg.NewFunc(initFnNameOld, llssa.NoArgsNoRet, llssa.InC)
|
|
b.Call(fnOld.Expr)
|
|
}
|
|
if isCgoCfunc || isCgoCmacro {
|
|
switch instr := instr.(type) {
|
|
case *ssa.Alloc:
|
|
// return value allocation
|
|
p.compileInstr(b, instr)
|
|
case *ssa.UnOp:
|
|
// load cgo function pointer
|
|
varName := instr.X.Name()
|
|
if instr.Op == token.MUL && strings.HasPrefix(varName, "_cgo_") {
|
|
p.cgoSymbols = append(p.cgoSymbols, varName)
|
|
p.compileInstr(b, instr)
|
|
}
|
|
case *ssa.Call:
|
|
if isCgoCmacro {
|
|
p.cgoRet = p.compileValue(b, instr.Call.Args[0])
|
|
p.cgoCalled = true
|
|
} else {
|
|
// call c function
|
|
p.compileInstr(b, instr)
|
|
p.cgoCalled = true
|
|
}
|
|
case *ssa.Return:
|
|
// return cgo function result
|
|
if isCgoCmacro {
|
|
ty := p.type_(instr.Results[0].Type(), llssa.InGo)
|
|
p.cgoRet.Type = p.prog.Pointer(ty)
|
|
p.cgoRet = b.Load(p.cgoRet)
|
|
}
|
|
b.Return(p.cgoRet)
|
|
cgoReturned = true
|
|
}
|
|
} else {
|
|
p.compileInstr(b, instr)
|
|
}
|
|
}
|
|
// is cgo cfunc but not return yet, some funcs has multiple blocks
|
|
if (isCgoCfunc || isCgoCmacro) && !cgoReturned {
|
|
if !p.cgoCalled {
|
|
panic("cgo cfunc not called")
|
|
}
|
|
for _, block := range block.Parent().Blocks {
|
|
for _, instr := range block.Instrs {
|
|
if _, ok := instr.(*ssa.Return); ok {
|
|
b.Return(p.cgoRet)
|
|
goto end
|
|
}
|
|
}
|
|
}
|
|
}
|
|
end:
|
|
if pyModInit {
|
|
jump := block.Instrs[n+last].(*ssa.Jump)
|
|
jumpTo := p.jumpTo(jump)
|
|
modPath := p.pyMod
|
|
modName := pysymPrefix + modPath
|
|
modPtr := pkg.PyNewModVar(modName, true).Expr
|
|
mod := b.Load(modPtr)
|
|
cond := b.BinOp(token.NEQ, mod, prog.Nil(mod.Type))
|
|
newBlk := fn.MakeBlock()
|
|
b.If(cond, jumpTo, newBlk)
|
|
b.SetBlockEx(newBlk, llssa.AtEnd, false)
|
|
b.Store(modPtr, b.PyImportMod(modPath))
|
|
b.Jump(jumpTo)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
const (
|
|
RuntimeInit = llssa.PkgRuntime + ".init"
|
|
)
|
|
|
|
func isAny(t types.Type) bool {
|
|
if t, ok := t.Underlying().(*types.Interface); ok {
|
|
return t.Empty()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func intVal(v ssa.Value) int64 {
|
|
if c, ok := v.(*ssa.Const); ok {
|
|
if iv, exact := constant.Int64Val(c.Value); exact {
|
|
return iv
|
|
}
|
|
}
|
|
panic("intVal: ssa.Value is not a const int")
|
|
}
|
|
|
|
func (p *context) isVArgs(v ssa.Value) (ret []llssa.Expr, ok bool) {
|
|
switch v := v.(type) {
|
|
case *ssa.Alloc:
|
|
ret, ok = p.vargs[v] // varargs: this is a varargs index
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *context) checkVArgs(v *ssa.Alloc, t *types.Pointer) bool {
|
|
if v.Comment == "varargs" { // this maybe a varargs allocation
|
|
if arr, ok := t.Elem().(*types.Array); ok {
|
|
if isAny(arr.Elem()) && isAllocVargs(p, v) {
|
|
p.vargs[v] = make([]llssa.Expr, arr.Len())
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isAllocVargs(ctx *context, v *ssa.Alloc) bool {
|
|
refs := *v.Referrers()
|
|
n := len(refs)
|
|
lastref := refs[n-1]
|
|
if i, ok := lastref.(*ssa.Slice); ok {
|
|
if refs = *i.Referrers(); len(refs) == 1 {
|
|
var call *ssa.CallCommon
|
|
switch ref := refs[0].(type) {
|
|
case *ssa.Call:
|
|
call = &ref.Call
|
|
case *ssa.Defer:
|
|
call = &ref.Call
|
|
case *ssa.Go:
|
|
call = &ref.Call
|
|
default:
|
|
return false
|
|
}
|
|
if call.IsInvoke() {
|
|
return llssa.HasNameValist(call.Signature())
|
|
}
|
|
return ctx.funcKind(call.Value) == fnHasVArg
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isPhi(i ssa.Instruction) bool {
|
|
_, ok := i.(*ssa.Phi)
|
|
return ok
|
|
}
|
|
|
|
func (p *context) compilePhis(b llssa.Builder, block *ssa.BasicBlock) int {
|
|
fn := p.fn
|
|
ret := fn.Block(block.Index)
|
|
b.SetBlockEx(ret, llssa.AtEnd, false)
|
|
if ninstr := len(block.Instrs); ninstr > 0 {
|
|
if isPhi(block.Instrs[0]) {
|
|
n := 1
|
|
for n < ninstr && isPhi(block.Instrs[n]) {
|
|
n++
|
|
}
|
|
rets := make([]llssa.Expr, n) // TODO(xsw): check to remove this
|
|
for i := 0; i < n; i++ {
|
|
iv := block.Instrs[i].(*ssa.Phi)
|
|
rets[i] = p.compilePhi(b, iv)
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
iv := block.Instrs[i].(*ssa.Phi)
|
|
p.bvals[iv] = rets[i]
|
|
}
|
|
return n
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (p *context) compilePhi(b llssa.Builder, v *ssa.Phi) (ret llssa.Expr) {
|
|
phi := b.Phi(p.type_(v.Type(), llssa.InGo))
|
|
ret = phi.Expr
|
|
p.phis = append(p.phis, func() {
|
|
preds := v.Block().Preds
|
|
bblks := make([]llssa.BasicBlock, len(preds))
|
|
for i, pred := range preds {
|
|
bblks[i] = p.fn.Block(pred.Index)
|
|
}
|
|
edges := v.Edges
|
|
phi.AddIncoming(b, bblks, func(i int, blk llssa.BasicBlock) llssa.Expr {
|
|
b.SetBlockEx(blk, llssa.BeforeLast, false)
|
|
return p.compileValue(b, edges[i])
|
|
})
|
|
})
|
|
return
|
|
}
|
|
|
|
func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue bool) (ret llssa.Expr) {
|
|
if asValue {
|
|
if v, ok := p.bvals[iv]; ok {
|
|
return v
|
|
}
|
|
log.Panicln("unreachable:", iv)
|
|
}
|
|
switch v := iv.(type) {
|
|
case *ssa.Call:
|
|
ret = p.call(b, llssa.Call, &v.Call)
|
|
case *ssa.BinOp:
|
|
x := p.compileValue(b, v.X)
|
|
y := p.compileValue(b, v.Y)
|
|
ret = b.BinOp(v.Op, x, y)
|
|
case *ssa.UnOp:
|
|
x := p.compileValue(b, v.X)
|
|
if v.Op == token.ARROW {
|
|
ret = b.Recv(x, v.CommaOk)
|
|
} else {
|
|
ret = b.UnOp(v.Op, x)
|
|
}
|
|
case *ssa.ChangeType:
|
|
t := v.Type()
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.ChangeType(p.type_(t, llssa.InGo), x)
|
|
case *ssa.Convert:
|
|
t := v.Type()
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.Convert(p.type_(t, llssa.InGo), x)
|
|
case *ssa.FieldAddr:
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.FieldAddr(x, v.Field)
|
|
case *ssa.Alloc:
|
|
t := v.Type().(*types.Pointer)
|
|
if p.checkVArgs(v, t) { // varargs: this maybe a varargs allocation
|
|
return
|
|
}
|
|
elem := p.type_(t.Elem(), llssa.InGo)
|
|
ret = b.Alloc(elem, v.Heap)
|
|
case *ssa.IndexAddr:
|
|
vx := v.X
|
|
if _, ok := p.isVArgs(vx); ok { // varargs: this is a varargs index
|
|
return
|
|
}
|
|
x := p.compileValue(b, vx)
|
|
idx := p.compileValue(b, v.Index)
|
|
ret = b.IndexAddr(x, idx)
|
|
case *ssa.Index:
|
|
x := p.compileValue(b, v.X)
|
|
idx := p.compileValue(b, v.Index)
|
|
ret = b.Index(x, idx, func() (addr llssa.Expr, zero bool) {
|
|
switch n := v.X.(type) {
|
|
case *ssa.Const:
|
|
zero = true
|
|
case *ssa.UnOp:
|
|
addr = p.compileValue(b, n.X)
|
|
}
|
|
return
|
|
})
|
|
case *ssa.Lookup:
|
|
x := p.compileValue(b, v.X)
|
|
idx := p.compileValue(b, v.Index)
|
|
ret = b.Lookup(x, idx, v.CommaOk)
|
|
case *ssa.Slice:
|
|
vx := v.X
|
|
if _, ok := p.isVArgs(vx); ok { // varargs: this is a varargs slice
|
|
return
|
|
}
|
|
var low, high, max llssa.Expr
|
|
x := p.compileValue(b, vx)
|
|
if v.Low != nil {
|
|
low = p.compileValue(b, v.Low)
|
|
}
|
|
if v.High != nil {
|
|
high = p.compileValue(b, v.High)
|
|
}
|
|
if v.Max != nil {
|
|
max = p.compileValue(b, v.Max)
|
|
}
|
|
ret = b.Slice(x, low, high, max)
|
|
ret.Type = b.Prog.Type(v.Type(), llssa.InGo)
|
|
case *ssa.MakeInterface:
|
|
if refs := *v.Referrers(); len(refs) == 1 {
|
|
switch ref := refs[0].(type) {
|
|
case *ssa.Store:
|
|
if va, ok := ref.Addr.(*ssa.IndexAddr); ok {
|
|
if _, ok = p.isVArgs(va.X); ok { // varargs: this is a varargs store
|
|
return
|
|
}
|
|
}
|
|
case *ssa.Call:
|
|
if fn, ok := ref.Call.Value.(*ssa.Function); ok {
|
|
if _, _, ftype := p.funcOf(fn); ftype == llgoFuncAddr { // llgo.funcAddr
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
t := p.type_(v.Type(), llssa.InGo)
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.MakeInterface(t, x)
|
|
case *ssa.MakeSlice:
|
|
t := p.type_(v.Type(), llssa.InGo)
|
|
nLen := p.compileValue(b, v.Len)
|
|
nCap := p.compileValue(b, v.Cap)
|
|
ret = b.MakeSlice(t, nLen, nCap)
|
|
case *ssa.MakeMap:
|
|
var nReserve llssa.Expr
|
|
t := p.type_(v.Type(), llssa.InGo)
|
|
if v.Reserve != nil {
|
|
nReserve = p.compileValue(b, v.Reserve)
|
|
}
|
|
ret = b.MakeMap(t, nReserve)
|
|
case *ssa.MakeClosure:
|
|
fn := p.compileValue(b, v.Fn)
|
|
bindings := p.compileValues(b, v.Bindings, 0)
|
|
ret = b.MakeClosure(fn, bindings)
|
|
case *ssa.TypeAssert:
|
|
x := p.compileValue(b, v.X)
|
|
t := p.type_(v.AssertedType, llssa.InGo)
|
|
ret = b.TypeAssert(x, t, v.CommaOk)
|
|
case *ssa.Extract:
|
|
x := p.compileValue(b, v.Tuple)
|
|
ret = b.Extract(x, v.Index)
|
|
case *ssa.Range:
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.Range(x)
|
|
case *ssa.Next:
|
|
var typ llssa.Type
|
|
if !v.IsString {
|
|
typ = p.type_(v.Iter.(*ssa.Range).X.Type(), llssa.InGo)
|
|
}
|
|
iter := p.compileValue(b, v.Iter)
|
|
ret = b.Next(typ, iter, v.IsString)
|
|
case *ssa.ChangeInterface:
|
|
t := v.Type()
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.ChangeInterface(p.type_(t, llssa.InGo), x)
|
|
case *ssa.Field:
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.Field(x, v.Field)
|
|
case *ssa.MakeChan:
|
|
t := v.Type()
|
|
size := p.compileValue(b, v.Size)
|
|
ret = b.MakeChan(p.type_(t, llssa.InGo), size)
|
|
case *ssa.Select:
|
|
states := make([]*llssa.SelectState, len(v.States))
|
|
for i, s := range v.States {
|
|
states[i] = &llssa.SelectState{
|
|
Chan: p.compileValue(b, s.Chan),
|
|
Send: s.Dir == types.SendOnly,
|
|
}
|
|
if s.Send != nil {
|
|
states[i].Value = p.compileValue(b, s.Send)
|
|
}
|
|
}
|
|
ret = b.Select(states, v.Blocking)
|
|
case *ssa.SliceToArrayPointer:
|
|
t := p.type_(v.Type(), llssa.InGo)
|
|
x := p.compileValue(b, v.X)
|
|
ret = b.SliceToArrayPointer(x, t)
|
|
default:
|
|
panic(fmt.Sprintf("compileInstrAndValue: unknown instr - %T\n", iv))
|
|
}
|
|
p.bvals[iv] = ret
|
|
return ret
|
|
}
|
|
|
|
func (p *context) jumpTo(v *ssa.Jump) llssa.BasicBlock {
|
|
fn := p.fn
|
|
succs := v.Block().Succs
|
|
return fn.Block(succs[0].Index)
|
|
}
|
|
|
|
func (p *context) getDebugLocScope(v *ssa.Function, pos token.Pos) *types.Scope {
|
|
if v.Object() == nil {
|
|
return nil
|
|
}
|
|
funcScope := v.Object().(*types.Func).Scope()
|
|
if funcScope == nil {
|
|
return nil
|
|
}
|
|
return funcScope.Innermost(pos)
|
|
}
|
|
|
|
func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
|
|
if iv, ok := instr.(instrOrValue); ok {
|
|
p.compileInstrOrValue(b, iv, false)
|
|
return
|
|
}
|
|
if enableDbg {
|
|
scope := p.getDebugLocScope(instr.Parent(), instr.Pos())
|
|
if scope != nil {
|
|
diScope := b.DIScope(p.fn, scope)
|
|
pos := p.fset.Position(instr.Pos())
|
|
b.DISetCurrentDebugLocation(diScope, pos)
|
|
}
|
|
}
|
|
switch v := instr.(type) {
|
|
case *ssa.Store:
|
|
va := v.Addr
|
|
if va, ok := va.(*ssa.IndexAddr); ok {
|
|
if args, ok := p.isVArgs(va.X); ok { // varargs: this is a varargs store
|
|
idx := intVal(va.Index)
|
|
val := v.Val
|
|
if vi, ok := val.(*ssa.MakeInterface); ok {
|
|
val = vi.X
|
|
}
|
|
args[idx] = p.compileValue(b, val)
|
|
return
|
|
}
|
|
}
|
|
ptr := p.compileValue(b, va)
|
|
val := p.compileValue(b, v.Val)
|
|
b.Store(ptr, val)
|
|
case *ssa.Jump:
|
|
jmpb := p.jumpTo(v)
|
|
b.Jump(jmpb)
|
|
case *ssa.Return:
|
|
var results []llssa.Expr
|
|
if n := len(v.Results); n > 0 {
|
|
results = make([]llssa.Expr, n)
|
|
for i, r := range v.Results {
|
|
results[i] = p.compileValue(b, r)
|
|
}
|
|
}
|
|
b.Return(results...)
|
|
case *ssa.If:
|
|
fn := p.fn
|
|
cond := p.compileValue(b, v.Cond)
|
|
succs := v.Block().Succs
|
|
thenb := fn.Block(succs[0].Index)
|
|
elseb := fn.Block(succs[1].Index)
|
|
b.If(cond, thenb, elseb)
|
|
case *ssa.MapUpdate:
|
|
m := p.compileValue(b, v.Map)
|
|
key := p.compileValue(b, v.Key)
|
|
val := p.compileValue(b, v.Value)
|
|
b.MapUpdate(m, key, val)
|
|
case *ssa.Defer:
|
|
p.call(b, p.blkInfos[v.Block().Index].Kind, &v.Call)
|
|
case *ssa.Go:
|
|
p.call(b, llssa.Go, &v.Call)
|
|
case *ssa.RunDefers:
|
|
b.RunDefers()
|
|
case *ssa.Panic:
|
|
arg := p.compileValue(b, v.X)
|
|
b.Panic(arg)
|
|
case *ssa.Send:
|
|
ch := p.compileValue(b, v.Chan)
|
|
x := p.compileValue(b, v.X)
|
|
b.Send(ch, x)
|
|
case *ssa.DebugRef:
|
|
if enableDbgSyms {
|
|
p.debugRef(b, v)
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("compileInstr: unknown instr - %T\n", instr))
|
|
}
|
|
}
|
|
|
|
func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.Var) llssa.DIVar {
|
|
pos := p.fset.Position(v.Pos())
|
|
t := p.type_(v.Type(), llssa.InGo)
|
|
for i, param := range fn.Params {
|
|
if param.Object().(*types.Var) == v {
|
|
argNo := i + 1
|
|
return b.DIVarParam(p.fn, pos, v.Name(), t, argNo)
|
|
}
|
|
}
|
|
scope := b.DIScope(p.fn, v.Parent())
|
|
return b.DIVarAuto(scope, pos, v.Name(), t)
|
|
}
|
|
|
|
func (p *context) compileFunction(v *ssa.Function) (goFn llssa.Function, pyFn llssa.PyObjRef, kind int) {
|
|
// TODO(xsw) v.Pkg == nil: means auto generated function?
|
|
if v.Pkg == p.goPkg || v.Pkg == nil {
|
|
// function in this package
|
|
goFn, pyFn, kind = p.compileFuncDecl(p.pkg, v)
|
|
if kind != ignoredFunc {
|
|
return
|
|
}
|
|
}
|
|
return p.funcOf(v)
|
|
}
|
|
|
|
func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr {
|
|
if iv, ok := v.(instrOrValue); ok {
|
|
return p.compileInstrOrValue(b, iv, true)
|
|
}
|
|
switch v := v.(type) {
|
|
case *ssa.Parameter:
|
|
fn := v.Parent()
|
|
for idx, param := range fn.Params {
|
|
if param == v {
|
|
return b.Param(idx)
|
|
}
|
|
}
|
|
case *ssa.Function:
|
|
aFn, pyFn, _ := p.compileFunction(v)
|
|
if aFn != nil {
|
|
return aFn.Expr
|
|
}
|
|
return pyFn.Expr
|
|
case *ssa.Global:
|
|
varName := v.Name()
|
|
val := p.varOf(b, v)
|
|
if isCgoVar(varName) {
|
|
p.cgoSymbols = append(p.cgoSymbols, val.Name())
|
|
}
|
|
if enableDbgSyms {
|
|
pos := p.fset.Position(v.Pos())
|
|
b.DIGlobal(val, v.Name(), pos)
|
|
}
|
|
return val
|
|
case *ssa.Const:
|
|
t := types.Default(v.Type())
|
|
bg := llssa.InGo
|
|
if p.inCFunc {
|
|
bg = llssa.InC
|
|
}
|
|
return b.Const(v.Value, p.type_(t, bg))
|
|
case *ssa.FreeVar:
|
|
fn := v.Parent()
|
|
for idx, freeVar := range fn.FreeVars {
|
|
if freeVar == v {
|
|
return p.fn.FreeVar(b, idx)
|
|
}
|
|
}
|
|
}
|
|
panic(fmt.Sprintf("compileValue: unknown value - %T\n", v))
|
|
}
|
|
|
|
func (p *context) compileVArg(ret []llssa.Expr, b llssa.Builder, v ssa.Value) []llssa.Expr {
|
|
_ = b
|
|
switch v := v.(type) {
|
|
case *ssa.Slice: // varargs: this is a varargs slice
|
|
if args, ok := p.isVArgs(v.X); ok {
|
|
return append(ret, args...)
|
|
}
|
|
case *ssa.Const:
|
|
if v.Value == nil {
|
|
return ret
|
|
}
|
|
case *ssa.Parameter:
|
|
if llssa.HasNameValist(v.Parent().Signature) {
|
|
return ret
|
|
}
|
|
}
|
|
panic(fmt.Sprintf("compileVArg: unknown value - %T\n", v))
|
|
}
|
|
|
|
func (p *context) compileValues(b llssa.Builder, vals []ssa.Value, hasVArg int) []llssa.Expr {
|
|
n := len(vals) - hasVArg
|
|
ret := make([]llssa.Expr, n)
|
|
for i := 0; i < n; i++ {
|
|
ret[i] = p.compileValue(b, vals[i])
|
|
}
|
|
if hasVArg > 0 {
|
|
ret = p.compileVArg(ret, b, vals[n])
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// Patch is a patch of some package.
|
|
type Patch struct {
|
|
Alt *ssa.Package
|
|
Types *types.Package
|
|
}
|
|
|
|
// Patches is patches of some packages.
|
|
type Patches = map[string]Patch
|
|
|
|
// NewPackage compiles a Go package to LLVM IR package.
|
|
func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) {
|
|
ret, _, err = NewPackageEx(prog, nil, pkg, files)
|
|
return
|
|
}
|
|
|
|
// NewPackageEx compiles a Go package to LLVM IR package.
|
|
func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) {
|
|
pkgProg := pkg.Prog
|
|
pkgTypes := pkg.Pkg
|
|
oldTypes := pkgTypes
|
|
pkgName, pkgPath := pkgTypes.Name(), llssa.PathOf(pkgTypes)
|
|
patch, hasPatch := patches[pkgPath]
|
|
if hasPatch {
|
|
pkgTypes = patch.Types
|
|
pkg.Pkg = pkgTypes
|
|
patch.Alt.Pkg = pkgTypes
|
|
}
|
|
if pkgPath == llssa.PkgRuntime {
|
|
prog.SetRuntime(pkgTypes)
|
|
}
|
|
ret = prog.NewPackage(pkgName, pkgPath)
|
|
if enableDbg {
|
|
ret.InitDebug(pkgName, pkgPath, pkgProg.Fset)
|
|
}
|
|
|
|
ctx := &context{
|
|
prog: prog,
|
|
pkg: ret,
|
|
fset: pkgProg.Fset,
|
|
goProg: pkgProg,
|
|
goTyps: pkgTypes,
|
|
goPkg: pkg,
|
|
patches: patches,
|
|
skips: make(map[string]none),
|
|
vargs: make(map[*ssa.Alloc][]llssa.Expr),
|
|
loaded: map[*types.Package]*pkgInfo{
|
|
types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly?
|
|
},
|
|
cgoSymbols: make([]string, 0, 128),
|
|
}
|
|
ctx.initPyModule()
|
|
ctx.initFiles(pkgPath, files, pkgName == "C")
|
|
ctx.prog.SetPatch(ctx.patchType)
|
|
ret.SetPatch(ctx.patchType)
|
|
ret.SetResolveLinkname(ctx.resolveLinkname)
|
|
|
|
if hasPatch {
|
|
skips := ctx.skips
|
|
typepatch.Merge(pkgTypes, oldTypes, skips, ctx.skipall)
|
|
ctx.skips = nil
|
|
ctx.state = pkgInPatch
|
|
if _, ok := skips["init"]; ok || ctx.skipall {
|
|
ctx.state |= pkgFNoOldInit
|
|
}
|
|
processPkg(ctx, ret, patch.Alt)
|
|
ctx.state = pkgHasPatch
|
|
ctx.skips = skips
|
|
}
|
|
if !ctx.skipall {
|
|
processPkg(ctx, ret, pkg)
|
|
}
|
|
for len(ctx.inits) > 0 {
|
|
inits := ctx.inits
|
|
ctx.inits = nil
|
|
for _, ini := range inits {
|
|
ini()
|
|
}
|
|
}
|
|
if fn := ctx.initAfter; fn != nil {
|
|
ctx.initAfter = nil
|
|
fn()
|
|
}
|
|
externs = ctx.cgoSymbols
|
|
return
|
|
}
|
|
|
|
func initFnNameOfHasPatch(name string) string {
|
|
return name + "$hasPatch"
|
|
}
|
|
|
|
func processPkg(ctx *context, ret llssa.Package, pkg *ssa.Package) {
|
|
type namedMember struct {
|
|
name string
|
|
val ssa.Member
|
|
}
|
|
|
|
members := make([]*namedMember, 0, len(pkg.Members))
|
|
skips := ctx.skips
|
|
for name, v := range pkg.Members {
|
|
if _, ok := skips[name]; !ok {
|
|
members = append(members, &namedMember{name, v})
|
|
}
|
|
}
|
|
sort.Slice(members, func(i, j int) bool {
|
|
return members[i].name < members[j].name
|
|
})
|
|
|
|
for _, m := range members {
|
|
member := m.val
|
|
switch member := member.(type) {
|
|
case *ssa.Function:
|
|
if member.TypeParams() != nil || member.TypeArgs() != nil {
|
|
// TODO(xsw): don't compile generic functions
|
|
// Do not try to build generic (non-instantiated) functions.
|
|
continue
|
|
}
|
|
ctx.compileFuncDecl(ret, member)
|
|
case *ssa.Type:
|
|
ctx.compileType(ret, member)
|
|
case *ssa.Global:
|
|
if !isCgoVar(member.Name()) {
|
|
ctx.compileGlobal(ret, member)
|
|
}
|
|
}
|
|
}
|
|
|
|
// check instantiate named in RuntimeTypes
|
|
var typs []*types.Named
|
|
for _, T := range pkg.Prog.RuntimeTypes() {
|
|
if typ, ok := T.(*types.Named); ok && typ.TypeArgs() != nil && typ.Obj().Pkg() == pkg.Pkg {
|
|
typs = append(typs, typ)
|
|
}
|
|
}
|
|
sort.Slice(typs, func(i, j int) bool {
|
|
return typs[i].String() < typs[j].String()
|
|
})
|
|
for _, typ := range typs {
|
|
ctx.compileMethods(ret, typ)
|
|
ctx.compileMethods(ret, types.NewPointer(typ))
|
|
}
|
|
}
|
|
|
|
func (p *context) type_(typ types.Type, bg llssa.Background) llssa.Type {
|
|
return p.prog.Type(p.patchType(typ), bg)
|
|
}
|
|
|
|
func (p *context) patchType(typ types.Type) (r types.Type) {
|
|
r, _ = p._patchType(typ)
|
|
return
|
|
}
|
|
|
|
func (p *context) _patchType(typ types.Type) (types.Type, bool) {
|
|
switch typ := typ.(type) {
|
|
case *types.Pointer:
|
|
if t, ok := p._patchType(typ.Elem()); ok {
|
|
return types.NewPointer(t), true
|
|
}
|
|
case *types.Named:
|
|
o := typ.Obj()
|
|
if pkg := o.Pkg(); typepatch.IsPatched(pkg) {
|
|
if patch, ok := p.patches[pkg.Path()]; ok {
|
|
if obj := patch.Types.Scope().Lookup(o.Name()); obj != nil {
|
|
raw := p.prog.Type(instantiate(obj.Type(), typ), llssa.InGo).RawType()
|
|
return raw, typ != raw
|
|
}
|
|
}
|
|
}
|
|
case *types.Tuple:
|
|
var patched bool
|
|
vars := make([]*types.Var, typ.Len())
|
|
for i := 0; i < typ.Len(); i++ {
|
|
v := typ.At(i)
|
|
if t, ok := p._patchType(v.Type()); ok {
|
|
vars[i] = types.NewVar(v.Pos(), v.Pkg(), v.Name(), t)
|
|
patched = true
|
|
} else {
|
|
vars[i] = v
|
|
}
|
|
}
|
|
if patched {
|
|
return types.NewTuple(vars...), true
|
|
}
|
|
case *types.Signature:
|
|
params, ok1 := p._patchType(typ.Params())
|
|
results, ok2 := p._patchType(typ.Results())
|
|
if ok1 || ok2 {
|
|
return types.NewSignature(typ.Recv(), params.(*types.Tuple), results.(*types.Tuple), typ.Variadic()), true
|
|
}
|
|
}
|
|
return typ, false
|
|
}
|
|
|
|
func instantiate(orig types.Type, t *types.Named) (typ types.Type) {
|
|
typ, _ = llssa.Instantiate(orig, t)
|
|
return
|
|
}
|
|
|
|
func (p *context) resolveLinkname(name string) string {
|
|
if link, ok := p.prog.Linkname(name); ok {
|
|
prefix, ltarget, _ := strings.Cut(link, ".")
|
|
if prefix != "C" {
|
|
panic("resolveLinkname: invalid link: " + link)
|
|
}
|
|
return ltarget
|
|
}
|
|
return name
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|