move out c/cpp/py

This commit is contained in:
Li Jie
2025-04-03 15:52:18 +08:00
parent 0a8a4eb6a6
commit ed366568b4
777 changed files with 4608 additions and 139122 deletions

333
ssa/abi/abi.go Normal file
View File

@@ -0,0 +1,333 @@
/*
* 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 abi
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"go/types"
"hash"
"log"
"strings"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/runtime/abi"
)
// -----------------------------------------------------------------------------
func BasicKind(t *types.Basic) abi.Kind {
kind := t.Kind()
switch kind {
case types.String:
return abi.String
case types.UnsafePointer:
return abi.UnsafePointer
}
return abi.Kind(kind)
}
func UnderlyingKind(t types.Type) abi.Kind {
switch t := t.(type) {
case *types.Basic:
return BasicKind(t)
case *types.Pointer:
return abi.Pointer
case *types.Slice:
return abi.Slice
case *types.Signature:
return abi.Func
case *types.Interface:
return abi.Interface
case *types.Struct:
return abi.Struct
case *types.Map:
return abi.Map
case *types.Array:
return abi.Array
case *types.Chan:
return abi.Chan
}
panic("todo")
}
func ChanDir(dir types.ChanDir) (abi.ChanDir, string) {
switch dir {
case types.SendRecv:
return abi.BothDir, "chan"
case types.SendOnly:
return abi.SendDir, "chan->"
case types.RecvOnly:
return abi.RecvDir, "<-chan"
}
panic("invlid chan dir")
}
// -----------------------------------------------------------------------------
type DataKind int
const (
Invalid DataKind = iota
Indirect // allocate memory for the value
Pointer // store a pointer value directly in the interface value
Integer // store a integer value directly in the interface value
BitCast // store other value (need bitcast) directly in the interface value
)
func DataKindOf(raw types.Type, lvl int, is32Bits bool) (DataKind, types.Type, int) {
switch t := raw.Underlying().(type) {
case *types.Basic:
kind := t.Kind()
switch {
case types.Bool <= kind && kind <= types.Uintptr:
if is32Bits && (kind == types.Int64 || kind == types.Uint64) {
return Indirect, raw, lvl
}
return Integer, raw, lvl
case kind == types.Float32:
return BitCast, raw, lvl
case kind == types.Float64:
if is32Bits {
return Indirect, raw, lvl
}
return BitCast, raw, lvl
case kind == types.UnsafePointer:
return Pointer, raw, lvl
}
case *types.Pointer, *types.Signature, *types.Map, *types.Chan:
return Pointer, raw, lvl
case *types.Struct:
if t.NumFields() == 1 {
return DataKindOf(t.Field(0).Type(), lvl+1, is32Bits)
}
case *types.Interface, *types.Slice:
case *types.Array:
if t.Len() == 1 {
return DataKindOf(t.Elem(), lvl+1, is32Bits)
}
default:
panic("unkown type")
}
return Indirect, raw, lvl
}
// -----------------------------------------------------------------------------
// Builder is a helper for constructing ABI types.
type Builder struct {
buf []byte
Pkg string
}
// New creates a new ABI type Builder.
func New(pkg string) *Builder {
ret := new(Builder)
ret.Init(pkg)
return ret
}
func (b *Builder) Init(pkg string) {
b.Pkg = pkg
b.buf = make([]byte, sha256.Size)
}
// TypeName returns the ABI type name for the specified type.
func (b *Builder) TypeName(t types.Type) (ret string, pub bool) {
switch t := t.(type) {
case *types.Basic:
return BasicName(t), true
case *types.Pointer:
ret, pub = b.TypeName(t.Elem())
return "*" + ret, pub
case *types.Struct:
return b.StructName(t)
case *types.Signature:
return b.FuncName(t), true
case *types.Slice:
ret, pub = b.TypeName(t.Elem())
return "[]" + ret, pub
case *types.Array:
ret, pub = b.TypeName(t.Elem())
return fmt.Sprintf("[%v]%s", t.Len(), ret), pub
case *types.Named:
o := t.Obj()
pkg := o.Pkg()
return "_llgo_" + FullName(pkg, NamedName(t)), (pkg == nil || o.Exported())
case *types.Interface:
if t.Empty() {
return "_llgo_any", true
}
return b.InterfaceName(t)
case *types.Map:
key, pub1 := b.TypeName(t.Key())
elem, pub2 := b.TypeName(t.Elem())
return fmt.Sprintf("map[%s]%s", key, elem), pub1 && pub2
case *types.Chan:
elem, pub := b.TypeName(t.Elem())
var s string
switch t.Dir() {
case types.SendRecv:
s = "chan"
case types.SendOnly:
s = "chan<-"
case types.RecvOnly:
s = "<-chan"
}
return fmt.Sprintf("%s %s", s, elem), pub
case *types.Alias:
return b.TypeName(types.Unalias(t))
}
log.Panicf("todo: %T\n", t)
return
}
func NamedName(t *types.Named) string {
if targs := t.TypeArgs(); targs != nil {
n := targs.Len()
infos := make([]string, n)
for i := 0; i < n; i++ {
infos[i] = types.TypeString(targs.At(i), PathOf)
}
return t.Obj().Name() + "[" + strings.Join(infos, ",") + "]"
}
return t.Obj().Name()
}
func TypeArgs(typeArgs []types.Type) string {
targs := make([]string, len(typeArgs))
for i, t := range typeArgs {
targs[i] = types.TypeString(t, PathOf)
}
return "[" + strings.Join(targs, ",") + "]"
}
const (
PatchPathPrefix = env.LLGoRuntimePkg + "/internal/lib/"
)
// PathOf returns the package path of the specified package.
func PathOf(pkg *types.Package) string {
if pkg == nil {
return ""
}
return strings.TrimPrefix(pkg.Path(), PatchPathPrefix)
}
// FullName returns the full name of a package member.
func FullName(pkg *types.Package, name string) string {
if pkg == nil {
return name
}
return PathOf(pkg) + "." + name
}
// BasicName returns the ABI type name for the specified basic type.
func BasicName(t *types.Basic) string {
name := t.Name()
if name == "byte" {
name = "uint8"
}
return "_llgo_" + name
}
// FuncName returns the ABI type name for the specified function type.
func (b *Builder) FuncName(t *types.Signature) string {
hash := b.funcHash(t)
hashStr := base64.RawURLEncoding.EncodeToString(hash)
return "_llgo_func$" + hashStr
}
func (b *Builder) funcHash(t *types.Signature) []byte {
h := sha256.New()
params, results := t.Params(), t.Results()
fmt.Fprintln(h, "func", params.Len(), results.Len(), t.Variadic())
b.tuple(h, params)
b.tuple(h, results)
return h.Sum(b.buf[:0])
}
func (b *Builder) tuple(h hash.Hash, t *types.Tuple) {
n := t.Len()
for i := 0; i < n; i++ {
v := t.At(i)
ft, _ := b.TypeName(v.Type())
fmt.Fprintln(h, ft)
}
}
// InterfaceName returns the ABI type name for the specified interface type.
func (b *Builder) InterfaceName(t *types.Interface) (ret string, pub bool) {
hash, private := b.interfaceHash(t)
hashStr := base64.RawURLEncoding.EncodeToString(hash)
if private {
return b.Pkg + ".iface$" + hashStr, false
}
return "_llgo_iface$" + hashStr, true
}
func (b *Builder) interfaceHash(t *types.Interface) (ret []byte, private bool) {
h := sha256.New()
n := t.NumMethods()
fmt.Fprintln(h, "interface", n)
for i := 0; i < n; i++ {
m := t.Method(i)
if !m.Exported() {
private = true
}
ft := b.FuncName(m.Type().(*types.Signature))
fmt.Fprintln(h, m.Name(), ft)
}
ret = h.Sum(b.buf[:0])
return
}
// StructName returns the ABI type name for the specified struct type.
func (b *Builder) StructName(t *types.Struct) (ret string, pub bool) {
hash, private := b.structHash(t)
hashStr := base64.RawURLEncoding.EncodeToString(hash)
if private {
return b.Pkg + ".struct$" + hashStr, false
}
return "_llgo_struct$" + hashStr, false
}
func (b *Builder) structHash(t *types.Struct) (ret []byte, private bool) {
h := sha256.New()
n := t.NumFields()
fmt.Fprintln(h, "struct", n)
for i := 0; i < n; i++ {
f := t.Field(i)
if !f.Exported() {
private = true
}
name := f.Name()
if f.Embedded() {
name = "-"
}
ft, pub := b.TypeName(f.Type())
fmt.Fprintln(h, name, ft)
if !pub {
private = true
}
}
ret = h.Sum(b.buf[:0])
return
}
// -----------------------------------------------------------------------------

547
ssa/abi/map.go Normal file
View File

@@ -0,0 +1,547 @@
package abi
import (
"go/token"
"go/types"
"log"
"github.com/goplus/llgo/runtime/abi"
)
// Builds a type representing a Bucket structure for
// the given map type. This type is not visible to users -
// we include only enough information to generate a correct GC
// program for it.
// Make sure this stays in sync with runtime/map.go.
//
// A "bucket" is a "struct" {
// tophash [BUCKETSIZE]uint8
// keys [BUCKETSIZE]keyType
// elems [BUCKETSIZE]elemType
// overflow *bucket
// }
const (
BUCKETSIZE = abi.MapBucketCount
MAXKEYSIZE = abi.MapMaxKeyBytes
MAXELEMSIZE = abi.MapMaxElemBytes
)
func makefield(name string, t types.Type) *types.Var {
return types.NewField(token.NoPos, nil, name, t, false)
}
// MapBucketType makes the map bucket type given the type of the map.
func MapBucketType(t *types.Map, sizes types.Sizes) types.Type {
keytype := t.Key()
elemtype := t.Elem()
if sizes.Sizeof(keytype) > MAXKEYSIZE {
keytype = types.NewPointer(keytype)
}
if sizes.Sizeof(elemtype) > MAXELEMSIZE {
elemtype = types.NewPointer(elemtype)
}
field := make([]*types.Var, 0, 5)
// The first field is: uint8 topbits[BUCKETSIZE].
arr := types.NewArray(types.Typ[types.Uint8], BUCKETSIZE)
field = append(field, makefield("topbits", arr))
arr = types.NewArray(keytype, BUCKETSIZE)
//arr.SetNoalg(true)
keys := makefield("keys", arr)
field = append(field, keys)
arr = types.NewArray(elemtype, BUCKETSIZE)
//arr.SetNoalg(true)
elems := makefield("elems", arr)
field = append(field, elems)
// If keys and elems have no pointers, the map implementation
// can keep a list of overflow pointers on the side so that
// buckets can be marked as having no pointers.
// Arrange for the bucket to have no pointers by changing
// the type of the overflow field to uintptr in this case.
// See comment on hmap.overflow in runtime/map.go.
otyp := types.Typ[types.UnsafePointer]
if !HasPtrData(elemtype) && !HasPtrData(keytype) {
otyp = types.Typ[types.Uintptr]
}
overflow := makefield("overflow", otyp)
field = append(field, overflow)
// link up fields
bucket := types.NewStruct(field[:], nil)
// Check invariants that map code depends on.
if !types.Comparable(t.Key()) {
log.Fatalf("unsupported map key type for %v", t)
}
if BUCKETSIZE < 8 {
log.Fatalf("bucket size %d too small for proper alignment %d", BUCKETSIZE, 8)
}
if uint8(sizes.Alignof(keytype)) > BUCKETSIZE {
log.Fatalf("key align too big for %v", t)
}
if uint8(sizes.Alignof(elemtype)) > BUCKETSIZE {
log.Fatalf("elem align %d too big for %v, BUCKETSIZE=%d", sizes.Alignof(elemtype), t, BUCKETSIZE)
}
if sizes.Alignof(keytype) > MAXKEYSIZE {
log.Fatalf("key size too large for %v", t)
}
if sizes.Alignof(elemtype) > MAXELEMSIZE {
log.Fatalf("elem size too large for %v", t)
}
if sizes.Alignof(t.Key()) > MAXKEYSIZE && !isPointer(keytype) {
log.Fatalf("key indirect incorrect for %v", t)
}
if sizes.Alignof(t.Elem()) > MAXELEMSIZE && !isPointer(elemtype) {
log.Fatalf("elem indirect incorrect for %v", t)
}
if sizes.Sizeof(keytype)%sizes.Alignof(keytype) != 0 {
log.Fatalf("key size not a multiple of key align for %v", t)
}
if sizes.Sizeof(elemtype)%sizes.Alignof(elemtype) != 0 {
log.Fatalf("elem size not a multiple of elem align for %v", t)
}
if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(keytype)) != 0 {
log.Fatalf("bucket align not multiple of key align %v", t)
}
if uint8(sizes.Alignof(bucket))%uint8(sizes.Alignof(elemtype)) != 0 {
log.Fatalf("bucket align not multiple of elem align %v", t)
}
offs := sizes.Offsetsof(field)
if offs[1]%sizes.Alignof(keytype) != 0 {
log.Fatalf("bad alignment of keys in bmap for %v", t)
}
if offs[2]%sizes.Alignof(elemtype) != 0 {
log.Fatalf("bad alignment of elems in bmap for %v", t)
}
// // Double-check that overflow field is final memory in struct,
// // with no padding at end.
// if overflow.Offset != bucket.Size()-int64(types.PtrSize) {
// log.Fatalf("bad offset of overflow in bmap for %v, overflow.Offset=%d, bucket.Size()-int64(types.PtrSize)=%d",
// t, overflow.Offset, bucket.Size()-int64(types.PtrSize))
// }
return bucket
}
func isPointer(t types.Type) (ok bool) {
_, ok = t.Underlying().(*types.Pointer)
return
}
func MapTypeFlags(t *types.Map, sizes types.Sizes) (flags int) {
if sizes.Sizeof(t.Key()) > MAXKEYSIZE {
flags |= 1 // indirect key
}
if sizes.Sizeof(t.Elem()) > MAXELEMSIZE {
flags |= 2 // indirect value
}
if IsReflexive(t.Key()) {
flags |= 4 // reflexive key
}
if needkeyupdate(t.Key()) {
flags |= 8 // need key update
}
if hashMightPanic(t.Key()) {
flags |= 16 // hash might panic
}
return
}
// $GOROOT/src/cmd/compile/internal/reflectdata/reflect.go
// func MapBucketType(t *types.Type) *types.Type {
// if t.MapType().Bucket != nil {
// return t.MapType().Bucket
// }
// keytype := t.Key()
// elemtype := t.Elem()
// types.CalcSize(keytype)
// types.CalcSize(elemtype)
// if keytype.Size() > MAXKEYSIZE {
// keytype = types.NewPtr(keytype)
// }
// if elemtype.Size() > MAXELEMSIZE {
// elemtype = types.NewPtr(elemtype)
// }
// field := make([]*types.Field, 0, 5)
// // The first field is: uint8 topbits[BUCKETSIZE].
// arr := types.NewArray(types.Types[types.TUINT8], BUCKETSIZE)
// field = append(field, makefield("topbits", arr))
// arr = types.NewArray(keytype, BUCKETSIZE)
// arr.SetNoalg(true)
// keys := makefield("keys", arr)
// field = append(field, keys)
// arr = types.NewArray(elemtype, BUCKETSIZE)
// arr.SetNoalg(true)
// elems := makefield("elems", arr)
// field = append(field, elems)
// // If keys and elems have no pointers, the map implementation
// // can keep a list of overflow pointers on the side so that
// // buckets can be marked as having no pointers.
// // Arrange for the bucket to have no pointers by changing
// // the type of the overflow field to uintptr in this case.
// // See comment on hmap.overflow in runtime/map.go.
// otyp := types.Types[types.TUNSAFEPTR]
// if !elemtype.HasPointers() && !keytype.HasPointers() {
// otyp = types.Types[types.TUINTPTR]
// }
// overflow := makefield("overflow", otyp)
// field = append(field, overflow)
// // link up fields
// bucket := types.NewStruct(field[:])
// bucket.SetNoalg(true)
// types.CalcSize(bucket)
// // Check invariants that map code depends on.
// if !types.IsComparable(t.Key()) {
// base.Fatalf("unsupported map key type for %v", t)
// }
// if BUCKETSIZE < 8 {
// base.Fatalf("bucket size %d too small for proper alignment %d", BUCKETSIZE, 8)
// }
// if uint8(keytype.Alignment()) > BUCKETSIZE {
// base.Fatalf("key align too big for %v", t)
// }
// if uint8(elemtype.Alignment()) > BUCKETSIZE {
// base.Fatalf("elem align %d too big for %v, BUCKETSIZE=%d", elemtype.Alignment(), t, BUCKETSIZE)
// }
// if keytype.Size() > MAXKEYSIZE {
// base.Fatalf("key size too large for %v", t)
// }
// if elemtype.Size() > MAXELEMSIZE {
// base.Fatalf("elem size too large for %v", t)
// }
// if t.Key().Size() > MAXKEYSIZE && !keytype.IsPtr() {
// base.Fatalf("key indirect incorrect for %v", t)
// }
// if t.Elem().Size() > MAXELEMSIZE && !elemtype.IsPtr() {
// base.Fatalf("elem indirect incorrect for %v", t)
// }
// if keytype.Size()%keytype.Alignment() != 0 {
// base.Fatalf("key size not a multiple of key align for %v", t)
// }
// if elemtype.Size()%elemtype.Alignment() != 0 {
// base.Fatalf("elem size not a multiple of elem align for %v", t)
// }
// if uint8(bucket.Alignment())%uint8(keytype.Alignment()) != 0 {
// base.Fatalf("bucket align not multiple of key align %v", t)
// }
// if uint8(bucket.Alignment())%uint8(elemtype.Alignment()) != 0 {
// base.Fatalf("bucket align not multiple of elem align %v", t)
// }
// if keys.Offset%keytype.Alignment() != 0 {
// base.Fatalf("bad alignment of keys in bmap for %v", t)
// }
// if elems.Offset%elemtype.Alignment() != 0 {
// base.Fatalf("bad alignment of elems in bmap for %v", t)
// }
// // Double-check that overflow field is final memory in struct,
// // with no padding at end.
// if overflow.Offset != bucket.Size()-int64(types.PtrSize) {
// base.Fatalf("bad offset of overflow in bmap for %v, overflow.Offset=%d, bucket.Size()-int64(types.PtrSize)=%d",
// t, overflow.Offset, bucket.Size()-int64(types.PtrSize))
// }
// t.MapType().Bucket = bucket
// bucket.StructType().Map = t
// return bucket
// }
// PtrDataSize returns the length in bytes of the prefix of t
// containing pointer data. Anything after this offset is scalar data.
//
// PtrDataSize is only defined for actual Go types. It's an error to
// use it on compiler-internal types (e.g., TSSA, TRESULTS).
func HasPtrData(t types.Type) bool {
switch t := t.Underlying().(type) {
case *types.Basic:
switch t.Kind() {
case types.String:
return true
case types.UnsafePointer:
return true
default:
return false
}
case *types.Pointer:
return true
case *types.Signature, *types.Chan, *types.Map:
return true
case *types.Interface:
return true
case *types.Slice:
return true
case *types.Array:
if t.Len() == 0 {
return false
}
return HasPtrData(t.Elem())
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
if HasPtrData(t.Field(i).Type()) {
return true
}
}
return false
default:
log.Fatalf("PtrDataSize: unexpected type, %v", t)
return false
}
}
// $GOROOT/src/cmd/compile/internal/types/type.go
// func PtrDataSize(t *Type) int64 {
// switch t.Kind() {
// case TBOOL, TINT8, TUINT8, TINT16, TUINT16, TINT32,
// TUINT32, TINT64, TUINT64, TINT, TUINT,
// TUINTPTR, TCOMPLEX64, TCOMPLEX128, TFLOAT32, TFLOAT64:
// return 0
// case TPTR:
// if t.Elem().NotInHeap() {
// return 0
// }
// return int64(PtrSize)
// case TUNSAFEPTR, TFUNC, TCHAN, TMAP:
// return int64(PtrSize)
// case TSTRING:
// // struct { byte *str; intgo len; }
// return int64(PtrSize)
// case TINTER:
// // struct { Itab *tab; void *data; } or
// // struct { Type *type; void *data; }
// // Note: see comment in typebits.Set
// return 2 * int64(PtrSize)
// case TSLICE:
// if t.Elem().NotInHeap() {
// return 0
// }
// // struct { byte *array; uintgo len; uintgo cap; }
// return int64(PtrSize)
// case TARRAY:
// if t.NumElem() == 0 {
// return 0
// }
// // t.NumElem() > 0
// size := PtrDataSize(t.Elem())
// if size == 0 {
// return 0
// }
// return (t.NumElem()-1)*t.Elem().Size() + size
// case TSTRUCT:
// // Find the last field that has pointers, if any.
// fs := t.Fields().Slice()
// for i := len(fs) - 1; i >= 0; i-- {
// if size := PtrDataSize(fs[i].Type); size > 0 {
// return fs[i].Offset + size
// }
// }
// return 0
// case TSSA:
// if t != TypeInt128 {
// base.Fatalf("PtrDataSize: unexpected ssa type %v", t)
// }
// return 0
// default:
// base.Fatalf("PtrDataSize: unexpected type, %v", t)
// return 0
// }
// }
// IsReflexive reports whether t has a reflexive equality operator.
// That is, if x==x for all x of type t.
func IsReflexive(t types.Type) bool {
switch t := t.Underlying().(type) {
case *types.Basic:
switch t.Kind() {
case types.Float32, types.Float64, types.Complex64, types.Complex128:
return false
default:
return true
}
case *types.Pointer, *types.Chan:
return true
case *types.Interface:
return false
case *types.Array:
return IsReflexive(t.Elem())
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
if !IsReflexive(t.Field(i).Type()) {
return false
}
}
return true
default:
log.Fatalf("bad type for map key: %v", t)
return false
}
}
// $GOROOT/src/cmd/compile/internal/types/type.go
// func IsReflexive(t *Type) bool {
// switch t.Kind() {
// case TBOOL,
// TINT,
// TUINT,
// TINT8,
// TUINT8,
// TINT16,
// TUINT16,
// TINT32,
// TUINT32,
// TINT64,
// TUINT64,
// TUINTPTR,
// TPTR,
// TUNSAFEPTR,
// TSTRING,
// TCHAN:
// return true
// case TFLOAT32,
// TFLOAT64,
// TCOMPLEX64,
// TCOMPLEX128,
// TINTER:
// return false
// case TARRAY:
// return IsReflexive(t.Elem())
// case TSTRUCT:
// for _, t1 := range t.Fields().Slice() {
// if !IsReflexive(t1.Type) {
// return false
// }
// }
// return true
// default:
// base.Fatalf("bad type for map key: %v", t)
// return false
// }
// }
// $GOROOT/src/cmd/compile/internal/types/type.go
// needkeyupdate reports whether map updates with t as a key
// need the key to be updated.
func needkeyupdate(t types.Type) bool {
switch t := t.Underlying().(type) {
case *types.Basic:
switch t.Kind() {
case types.Float32, types.Float64, types.Complex64, types.Complex128:
return true // floats and complex can be +0/-0
case types.String:
return true // strings might have smaller backing stores
default:
return false
}
case *types.Interface:
return true
case *types.Pointer, *types.Chan:
return false
case *types.Array:
return needkeyupdate(t.Elem())
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
if needkeyupdate(t.Field(i).Type()) {
return true
}
}
return false
default:
log.Fatalf("bad type for map key: %v", t)
return true
}
}
// $GOROOT/src/cmd/compile/internal/reflectdata/reflect.go
// func needkeyupdate(t *types.Type) bool {
// switch t.Kind() {
// case types.TBOOL, types.TINT, types.TUINT, types.TINT8, types.TUINT8, types.TINT16, types.TUINT16, types.TINT32, types.TUINT32,
// types.TINT64, types.TUINT64, types.TUINTPTR, types.TPTR, types.TUNSAFEPTR, types.TCHAN:
// return false
// case types.TFLOAT32, types.TFLOAT64, types.TCOMPLEX64, types.TCOMPLEX128, // floats and complex can be +0/-0
// types.TINTER,
// types.TSTRING: // strings might have smaller backing stores
// return true
// case types.TARRAY:
// return needkeyupdate(t.Elem())
// case types.TSTRUCT:
// for _, t1 := range t.Fields().Slice() {
// if needkeyupdate(t1.Type) {
// return true
// }
// }
// return false
// default:
// base.Fatalf("bad type for map key: %v", t)
// return true
// }
// }
// hashMightPanic reports whether the hash of a map key of type t might panic.
func hashMightPanic(t types.Type) bool {
switch t := t.Underlying().(type) {
case *types.Interface:
return true
case *types.Array:
return hashMightPanic(t.Elem())
case *types.Struct:
for i, n := 0, t.NumFields(); i < n; i++ {
if hashMightPanic(t.Field(i).Type()) {
return true
}
}
return false
default:
return false
}
}
// $GOROOT/src/cmd/compile/internal/reflectdata/reflect.go
// func hashMightPanic(t *types.Type) bool {
// switch t.Kind() {
// case types.TINTER:
// return true
// case types.TARRAY:
// return hashMightPanic(t.Elem())
// case types.TSTRUCT:
// for _, t1 := range t.Fields().Slice() {
// if hashMightPanic(t1.Type) {
// return true
// }
// }
// return false
// default:
// return false
// }
// }

505
ssa/abitype.go Normal file
View File

@@ -0,0 +1,505 @@
/*
* 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 ssa
import (
"go/token"
"go/types"
"unsafe"
"github.com/goplus/llgo/ssa/abi"
"github.com/goplus/llvm"
"golang.org/x/tools/go/types/typeutil"
)
// -----------------------------------------------------------------------------
// abiBasic returns the abi type of the specified basic kind.
func (b Builder) abiBasic(t *types.Basic) func() Expr {
/*
TODO(xsw):
return b.abiExtern(abi.BasicName(t))
*/
return func() Expr {
kind := int(abi.BasicKind(t))
dk, _, _ := abi.DataKindOf(t, 0, b.Prog.is32Bits)
switch dk {
case abi.Integer, abi.BitCast, abi.Pointer:
const kindDirectIface = 1 << 5
kind |= kindDirectIface
}
return b.InlineCall(b.Pkg.rtFunc("Basic"), b.Prog.Val(kind))
}
}
/*
func (b Builder) abiExtern(name string) Expr {
g := b.Pkg.NewVarFrom(name, b.Prog.AbiTypePtrPtr())
return b.Load(g.Expr)
}
*/
func (b Builder) abiTypeOf(t types.Type) func() Expr {
switch t := t.(type) {
case *types.Basic:
return b.abiBasic(t)
case *types.Pointer:
return b.abiPointerOf(t)
case *types.Struct:
return b.abiStructOf(t)
case *types.Named:
if _, ok := t.Underlying().(*types.Interface); ok {
return b.abiNamedInterfaceOf(t)
}
return b.abiNamedOf(t)
case *types.Interface:
return b.abiInterfaceOf(t)
case *types.Signature:
return b.abiFuncOf(t)
case *types.Slice:
return b.abiSliceOf(t)
case *types.Array:
return b.abiArrayOf(t)
case *types.Chan:
return b.abiChanOf(t)
case *types.Map:
return b.abiMapOf(t)
case *types.Alias:
return b.abiTypeOf(types.Unalias(t))
}
panic("todo")
}
func (b Builder) abiTupleOf(t *types.Tuple) func() Expr {
n := t.Len()
tuple := make([]Expr, n)
for i := 0; i < n; i++ {
tuple[i] = b.abiType(t.At(i).Type())
}
return func() Expr {
prog := b.Prog
tSlice := prog.Slice(prog.AbiTypePtr())
return b.SliceLit(tSlice, tuple...)
}
}
// func Func(in, out []*Type, variadic bool)
func (b Builder) abiFuncOf(sig *types.Signature) func() Expr {
params := b.abiTupleOf(sig.Params())
results := b.abiTupleOf(sig.Results())
return func() Expr {
prog := b.Prog
pkg := b.Pkg
fn := pkg.rtFunc("Func")
variadic := prog.Val(sig.Variadic())
return b.Call(fn, params(), results(), variadic)
}
}
// Imethod{name string, typ *FuncType}
func (b Builder) abiImethodOf(mName string, typ Expr) Expr {
prog := b.Prog
name := b.Str(mName)
return b.aggregateValue(prog.rtType("Imethod"), name.impl, typ.impl)
}
func (b Builder) abiMethods(t *types.Named) (ret, pret int) {
methods := typeutil.IntuitiveMethodSet(t, nil)
pret = len(methods)
for _, m := range methods {
if _, ok := m.Recv().(*types.Pointer); ok {
continue
}
ret++
}
return
}
// Method{name string, typ *FuncType, ifn, tfn abi.Text}
func (b Builder) abiMethodOf(mPkg *types.Package, mName string, mSig *types.Signature /*, bg Background = InGo */) (mthd, ptrMthd Expr) {
prog := b.Prog
name := b.Str(mName).impl
if !token.IsExported(mName) {
name = b.Str(abi.FullName(mPkg, mName)).impl
}
abiSigGo := types.NewSignatureType(nil, nil, nil, mSig.Params(), mSig.Results(), mSig.Variadic())
abiSig := prog.FuncDecl(abiSigGo, InGo).raw.Type
abiTyp := b.abiType(abiSig)
abiTypImpl := abiTyp.impl
recv := mSig.Recv()
recvType := recv.Type()
if _, ok := recvType.(*types.Pointer); ok {
ptrMthd, _ = b.abiMthd(mPkg, mName, mSig, name, abiTypImpl, llvm.Value{})
return
}
ptrRecv := types.NewVar(0, nil, "", types.NewPointer(recvType))
ptrSig := types.NewSignatureType(ptrRecv, nil, nil, mSig.Params(), mSig.Results(), mSig.Variadic())
ptrMthd, ifn := b.abiMthd(mPkg, mName, ptrSig, name, abiTypImpl, llvm.Value{})
mthd, _ = b.abiMthd(mPkg, mName, mSig, name, abiTypImpl, ifn)
return
}
func (b Builder) abiMthd(mPkg *types.Package, mName string, mSig *types.Signature, name, abiTyp, ifn llvm.Value) (ret Expr, tfn llvm.Value) {
fullName := FuncName(mPkg, mName, mSig.Recv(), false)
if mSig.TypeParams().Len() > 0 || mSig.RecvTypeParams().Len() > 0 {
if !b.Pkg.Prog.FuncCompiled(fullName) {
return
}
}
if b.Pkg.fnlink != nil {
fullName = b.Pkg.fnlink(fullName)
}
tfn = b.Pkg.NewFunc(fullName, mSig, InGo).impl // TODO(xsw): use rawType to speed up
if ifn.IsNil() {
ifn = tfn
}
ret = b.aggregateValue(b.Prog.rtType("Method"), name, abiTyp, ifn, tfn)
return
}
// func Interface(pkgPath, name string, methods []abi.Imethod)
func (b Builder) abiInterfaceOf(t *types.Interface) func() Expr {
n := t.NumMethods()
typs := make([]Expr, n)
for i := 0; i < n; i++ {
m := t.Method(i)
typs[i] = b.abiType(m.Type())
}
return func() Expr {
prog := b.Prog
methods := make([]Expr, n)
for i := 0; i < n; i++ {
m := t.Method(i)
mName := m.Name()
if !token.IsExported(mName) {
mName = abi.FullName(m.Pkg(), mName)
}
methods[i] = b.abiImethodOf(mName, typs[i])
}
pkg := b.Pkg
fn := pkg.rtFunc("Interface")
tSlice := lastParamType(prog, fn)
methodSlice := b.SliceLit(tSlice, methods...)
return b.Call(fn, b.Str(pkg.Path()), methodSlice)
}
}
func (b Builder) abiInitNamedInterface(ret Expr, t *types.Interface) func() Expr {
n := t.NumMethods()
typs := make([]Expr, n)
for i := 0; i < n; i++ {
m := t.Method(i)
typs[i] = b.abiType(m.Type())
}
return func() Expr {
prog := b.Prog
methods := make([]Expr, n)
for i := 0; i < n; i++ {
m := t.Method(i)
mName := m.Name()
if !token.IsExported(mName) {
mName = abi.FullName(m.Pkg(), mName)
}
methods[i] = b.abiImethodOf(mName, typs[i])
}
fn := b.Pkg.rtFunc("InitNamedInterface")
tSlice := lastParamType(prog, fn)
methodSlice := b.SliceLit(tSlice, methods...)
return b.Call(fn, ret, methodSlice)
}
}
// func NewNamed(kind abi.Kind, methods, ptrMethods int)
func (b Builder) abiNamedOf(t *types.Named) func() Expr {
expr := b.abiNamed(t)
return func() Expr {
return expr
}
}
func (b Builder) abiNamed(t *types.Named) Expr {
pkg := b.Pkg
tunder := t.Underlying()
kind := int(abi.UnderlyingKind(tunder))
size := b.sizeof(tunder)
numMethods, numPtrMethods := b.abiMethods(t)
newNamed := pkg.rtFunc("NewNamed")
obj := t.Obj()
expr := b.Call(newNamed, b.Str(abi.PathOf(obj.Pkg())), b.Str(abi.NamedName(t)), b.Prog.Val(kind), b.Prog.IntVal(uint64(size), b.Prog.Uintptr()), b.Prog.Val(numMethods), b.Prog.Val(numPtrMethods))
return expr
}
func (b Builder) abiNamedInterfaceOf(t *types.Named) func() Expr {
obj := t.Obj()
fn := b.Pkg.rtFunc("NewNamedInterface")
expr := b.Call(fn, b.Str(abi.PathOf(obj.Pkg())), b.Str(obj.Name()))
return func() Expr {
return expr
}
}
func (b Builder) sizeof(t types.Type) int64 {
sizes := (*goProgram)(unsafe.Pointer(b.Prog))
return sizes.Sizeof(t)
}
// func InitNamed(ret *Type, pkgPath, name string, underlying *Type, methods, ptrMethods []Method)
func (b Builder) abiInitNamed(ret Expr, t *types.Named) func() Expr {
under := b.abiType(t.Underlying())
return func() Expr {
pkg := b.Pkg
prog := b.Prog
var initNamed = pkg.rtFunc("InitNamed")
var tSlice = lastParamType(prog, initNamed)
mset := typeutil.IntuitiveMethodSet(t, nil)
n := len(mset)
var methods, ptrMethods Expr
if n == 0 {
methods = prog.Zero(tSlice)
ptrMethods = methods
} else {
var mthds []Expr
var ptrMthds = make([]Expr, 0, n)
for i := 0; i < n; i++ {
var mPkg *types.Package
var mSig *types.Signature
m := mset[i]
obj := m.Obj()
mName := obj.Name()
if token.IsExported(mName) {
mPkg = t.Obj().Pkg()
mSig = m.Type().(*types.Signature)
} else {
mPkg = obj.Pkg()
mSig = obj.Type().(*types.Signature)
}
mthd, ptrMthd := b.abiMethodOf(mPkg, mName, mSig)
if !mthd.IsNil() {
mthds = append(mthds, mthd)
}
if !ptrMthd.IsNil() {
ptrMthds = append(ptrMthds, ptrMthd)
}
}
if len(mthds) > 0 {
methods = b.SliceLit(tSlice, mthds...)
} else {
methods = prog.Zero(tSlice)
}
if len(ptrMthds) > 0 {
ptrMethods = b.SliceLit(tSlice, ptrMthds...)
} else {
ptrMethods = prog.Zero(tSlice)
}
}
return b.Call(initNamed, ret, under, methods, ptrMethods)
}
}
func (b Builder) abiPointerOf(t *types.Pointer) func() Expr {
elem := b.abiTypeOf(t.Elem())
return func() Expr {
return b.Call(b.Pkg.rtFunc("PointerTo"), elem())
}
}
func (b Builder) abiSliceOf(t *types.Slice) func() Expr {
elem := b.abiTypeOf(t.Elem())
return func() Expr {
return b.Call(b.Pkg.rtFunc("SliceOf"), elem())
}
}
func (b Builder) abiArrayOf(t *types.Array) func() Expr {
elem := b.abiTypeOf(t.Elem())
return func() Expr {
n := b.Prog.IntVal(uint64(t.Len()), b.Prog.Uintptr())
return b.Call(b.Pkg.rtFunc("ArrayOf"), n, elem())
}
}
func (b Builder) abiChanOf(t *types.Chan) func() Expr {
elem := b.abiTypeOf(t.Elem())
return func() Expr {
dir, s := abi.ChanDir(t.Dir())
return b.Call(b.Pkg.rtFunc("ChanOf"), b.Prog.IntVal(uint64(dir), b.Prog.Int()), b.Str(s), elem())
}
}
func (b Builder) abiMapOf(t *types.Map) func() Expr {
key := b.abiTypeOf(t.Key())
elem := b.abiTypeOf(t.Elem())
sizes := (*goProgram)(unsafe.Pointer(b.Prog))
bucket := b.abiTypeOf(abi.MapBucketType(t, sizes))
flags := abi.MapTypeFlags(t, sizes)
return func() Expr {
return b.Call(b.Pkg.rtFunc("MapOf"), key(), elem(), bucket(), b.Prog.Val(flags))
}
}
// func StructField(name string, typ *abi.Type, off uintptr, tag string, embedded bool)
// func Struct(pkgPath string, size uintptr, fields []abi.StructField)
func (b Builder) abiStructOf(t *types.Struct) func() Expr {
n := t.NumFields()
typs := make([]func() Expr, n)
for i := 0; i < n; i++ {
f := t.Field(i)
typs[i] = b.abiTypeOf(f.Type())
}
return func() Expr {
pkg := b.Pkg
prog := b.Prog
flds := make([]Expr, n)
strucAbi := pkg.rtFunc("Struct")
sfAbi := pkg.rtFunc("StructField")
tStruc := prog.rawType(t)
for i := 0; i < n; i++ {
f := t.Field(i)
off := uintptr(prog.OffsetOf(tStruc, i))
name := b.Str(f.Name())
tag := b.Str(t.Tag(i))
embedded := prog.Val(f.Embedded())
flds[i] = b.Call(sfAbi, name, typs[i](), prog.Val(off), tag, embedded)
}
pkgPath := b.Str(pkg.Path())
tSlice := lastParamType(prog, strucAbi)
fldSlice := b.SliceLit(tSlice, flds...)
size := prog.IntVal(prog.SizeOf(tStruc), prog.Uintptr())
return b.Call(strucAbi, pkgPath, size, fldSlice)
}
}
func lastParamType(prog Program, fn Expr) Type {
params := fn.raw.Type.(*types.Signature).Params()
return prog.rawType(params.At(params.Len() - 1).Type())
}
// -----------------------------------------------------------------------------
func (p Package) patchType(t types.Type) types.Type {
switch t := t.(type) {
case *types.Pointer:
return types.NewPointer(p.patchType(t.Elem()))
}
return p.patch(t)
}
func (p Package) abiTypeInit(g Global, t types.Type, pub bool) {
b := p.afterBuilder()
if p.patch != nil {
t = p.patchType(t)
}
tabi := b.abiTypeOf(t)
expr := g.Expr
var eq Expr
var blks []BasicBlock
if pub {
eq = b.BinOp(token.EQL, b.Load(expr), b.Prog.Nil(expr.Type))
blks = b.Func.MakeBlocks(2)
b.If(eq, blks[0], blks[1])
b.SetBlockEx(blks[0], AtEnd, false)
}
vexpr := tabi()
prog := p.Prog
if kind, _, _ := abi.DataKindOf(t, 0, prog.is32Bits); kind == abi.Pointer {
b.InlineCall(b.Pkg.rtFunc("SetDirectIface"), vexpr)
}
b.Store(expr, vexpr)
if pub {
b.Jump(blks[1])
b.SetBlockEx(blks[1], AtEnd, false)
b.blk.last = blks[1].last
}
if t, ok := t.(*types.Named); ok {
if iface, ok := t.Underlying().(*types.Interface); ok {
tabi = b.abiInitNamedInterface(vexpr, iface)
} else {
tabi = b.abiInitNamed(vexpr, t)
}
if pub {
blks = b.Func.MakeBlocks(2)
b.If(eq, blks[0], blks[1])
b.SetBlockEx(blks[0], AtEnd, false)
}
tabi()
if pub {
b.Jump(blks[1])
b.SetBlockEx(blks[1], AtEnd, false)
b.blk.last = blks[1].last
}
}
}
// abiType returns the abi type of the specified type.
func (b Builder) abiType(t types.Type) Expr {
switch t := t.(type) {
case *types.Pointer:
b.checkAbi(t.Elem())
case *types.Array:
b.checkAbi(t.Elem())
case *types.Map:
b.checkAbi(t.Key())
b.checkAbi(t.Elem())
case *types.Slice:
b.checkAbi(t.Elem())
case *types.Chan:
b.checkAbi(t.Elem())
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
b.checkAbi(t.Field(i).Type())
}
case *types.Interface:
for i := 0; i < t.NumMethods(); i++ {
b.checkAbi(t.Method(i).Type())
}
case *types.Signature:
for i := 0; i < t.Params().Len(); i++ {
b.checkAbi(t.Params().At(i).Type())
}
for i := 0; i < t.Results().Len(); i++ {
b.checkAbi(t.Results().At(i).Type())
}
}
g := b.loadType(t)
return b.Load(g.Expr)
}
func (b Builder) checkAbi(t types.Type) {
if b.Pkg.chkabi[t] {
return
}
b.abiType(t)
}
func (b Builder) loadType(t types.Type) Global {
b.Pkg.chkabi[t] = true
pkg := b.Pkg
name, pub := pkg.abi.TypeName(t)
g := pkg.VarOf(name)
if g == nil {
prog := b.Prog
g = pkg.doNewVar(name, prog.AbiTypePtrPtr())
g.InitNil()
g.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
pkg.abiTypeInit(g, t, pub)
}
return g
}
// -----------------------------------------------------------------------------

119
ssa/cl_test.go Normal file
View File

@@ -0,0 +1,119 @@
//go:build !llgo
// +build !llgo
/*
* 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 ssa_test
import (
"flag"
"go/types"
"io"
"log"
"testing"
"github.com/goplus/llgo/cl/cltest"
"github.com/goplus/llgo/ssa"
"github.com/goplus/llgo/ssa/ssatest"
)
func TestMain(m *testing.M) {
flag.Parse()
ssa.SetDebug(ssa.DbgFlagAll)
if !testing.Verbose() {
log.SetOutput(io.Discard)
}
m.Run()
}
func TestFromTestlibgo(t *testing.T) {
cltest.FromDir(t, "", "../cl/_testlibgo")
}
func TestFromTestgo(t *testing.T) {
cltest.FromDir(t, "", "../cl/_testgo")
}
func TestFromTestpy(t *testing.T) {
cltest.FromDir(t, "", "../cl/_testpy")
}
func TestFromTestlibc(t *testing.T) {
cltest.FromDir(t, "", "../cl/_testlibc")
}
func TestFromTestrt(t *testing.T) {
cltest.FromDir(t, "", "../cl/_testrt")
}
func TestFromTestdata(t *testing.T) {
cltest.FromDir(t, "", "../cl/_testdata")
}
func TestMakeInterface(t *testing.T) {
prog := ssatest.NewProgram(t, &ssa.Target{GOARCH: "x86"})
pkg := prog.NewPackage("foo", "foo")
fn := pkg.NewFunc("main", types.NewSignatureType(nil, nil, nil, nil, nil, false), ssa.InC)
b := fn.MakeBody(1)
b.MakeInterface(prog.Any(), prog.IntVal(100, prog.Int64()))
b.MakeInterface(prog.Any(), prog.FloatVal(100, prog.Float64()))
b.DeferData()
b.Return()
}
/*
func TestCallback(t *testing.T) {
ctx := llvm.NewContext()
mod := ctx.NewModule("foo/bar")
tc := llvm.FunctionType(ctx.VoidType(), nil, false)
callback := llvm.PointerType(tc, 0)
params := []llvm.Type{callback}
tfn := llvm.FunctionType(ctx.VoidType(), params, false)
f := llvm.AddFunction(mod, "fn", tfn)
b := ctx.NewBuilder()
blk := llvm.AddBasicBlock(f, "")
b.SetInsertPointAtEnd(blk)
arg := f.Param(0)
// arg = b.CreateLoad(tc, arg, "")
b.CreateCall(tc, arg, nil, "")
b.CreateRetVoid()
expected := `; ModuleID = 'foo/bar'
`
if v := mod.String(); v != expected {
t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected)
}
}
/*
func TestMap(t *testing.T) {
var m typeutil.Map
sig := types.NewSignatureType(nil, nil, nil, nil, nil, false)
m.Set(sig, 1)
csig := (*ssa.CFuncPtr)(sig)
m.Set(csig, 2)
if v := m.At(sig); v.(int) != 1 {
t.Fatal("At(sig):", v)
}
if v := m.At(csig); v.(int) != 2 {
t.Fatal("At(csig):", v)
}
}
*/

767
ssa/datastruct.go Normal file
View File

@@ -0,0 +1,767 @@
/*
* 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 ssa
import (
"fmt"
"go/types"
"log"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
// The FieldAddr instruction yields the address of Field of *struct X.
//
// The field is identified by its index within the field list of the
// struct type of X.
//
// Dynamically, this instruction panics if X evaluates to a nil
// pointer.
//
// Type() returns a (possibly named) *types.Pointer.
//
// Example printed form:
//
// t1 = &t0.name [#1]
func (b Builder) FieldAddr(x Expr, idx int) Expr {
if debugInstr {
log.Printf("FieldAddr %v, %d\n", x.impl, idx)
}
prog := b.Prog
tstruc := prog.Elem(x.Type)
telem := prog.Field(tstruc, idx)
pt := prog.Pointer(telem)
return Expr{llvm.CreateStructGEP(b.impl, tstruc.ll, x.impl, idx), pt}
}
// The Field instruction yields the value of Field of struct X.
func (b Builder) Field(x Expr, idx int) Expr {
if debugInstr {
log.Printf("Field %v, %d\n", x.impl, idx)
}
return b.getField(x, idx)
}
func (b Builder) getField(x Expr, idx int) Expr {
tfld := b.Prog.Field(x.Type, idx)
fld := llvm.CreateExtractValue(b.impl, x.impl, idx)
return Expr{fld, tfld}
}
// -----------------------------------------------------------------------------
func (b Builder) Complex(r, i Expr) Expr {
if debugInstr {
log.Printf("Complex %v, %v\n", r.impl, i.impl)
}
prog := b.Prog
var t Type
switch kind := r.raw.Type.Underlying().(*types.Basic).Kind(); kind {
case types.Float64:
t = prog.Complex128()
case types.Float32:
t = prog.Complex64()
}
return b.aggregateValue(t, r.impl, i.impl)
}
// MakeString creates a new string from a C string pointer and length.
func (b Builder) MakeString(cstr Expr, n ...Expr) (ret Expr) {
if debugInstr {
log.Printf("MakeString %v\n", cstr.impl)
}
pkg := b.Pkg
prog := b.Prog
ret.Type = prog.String()
if len(n) == 0 {
ret.impl = b.Call(pkg.rtFunc("StringFromCStr"), cstr).impl
} else {
// TODO(xsw): remove Convert
ret.impl = b.Call(pkg.rtFunc("StringFrom"), cstr, b.Convert(prog.Int(), n[0])).impl
}
return
}
// StringData returns the data pointer of a string.
func (b Builder) StringData(x Expr) Expr {
if debugInstr {
log.Printf("StringData %v\n", x.impl)
}
ptr := llvm.CreateExtractValue(b.impl, x.impl, 0)
return Expr{ptr, b.Prog.CStr()}
}
// StringLen returns the length of a string.
func (b Builder) StringLen(x Expr) Expr {
if debugInstr {
log.Printf("StringLen %v\n", x.impl)
}
ptr := llvm.CreateExtractValue(b.impl, x.impl, 1)
return Expr{ptr, b.Prog.Int()}
}
// -----------------------------------------------------------------------------
// SliceData returns the data pointer of a slice.
func (b Builder) SliceData(x Expr) Expr {
if debugInstr {
log.Printf("SliceData %v\n", x.impl)
}
ptr := llvm.CreateExtractValue(b.impl, x.impl, 0)
ty := x.Type.RawType()
tySlice := ty.Underlying().(*types.Slice)
return Expr{ptr, b.Prog.Pointer(b.Prog.rawType(tySlice.Elem()))}
}
// SliceLen returns the length of a slice.
func (b Builder) SliceLen(x Expr) Expr {
if debugInstr {
log.Printf("SliceLen %v\n", x.impl)
}
ptr := llvm.CreateExtractValue(b.impl, x.impl, 1)
return Expr{ptr, b.Prog.Int()}
}
// SliceCap returns the length of a slice cap.
func (b Builder) SliceCap(x Expr) Expr {
if debugInstr {
log.Printf("SliceCap %v\n", x.impl)
}
ptr := llvm.CreateExtractValue(b.impl, x.impl, 2)
return Expr{ptr, b.Prog.Int()}
}
// -----------------------------------------------------------------------------
// The IndexAddr instruction yields the address of the element at
// index `idx` of collection `x`. `idx` is an integer expression.
//
// The elements of maps and strings are not addressable; use Lookup (map),
// Index (string), or MapUpdate instead.
//
// Dynamically, this instruction panics if `x` evaluates to a nil *array
// pointer.
//
// Example printed form:
//
// t2 = &t0[t1]
func (b Builder) IndexAddr(x, idx Expr) Expr {
if debugInstr {
log.Printf("IndexAddr %v, %v\n", x.impl, idx.impl)
}
prog := b.Prog
telem := prog.Index(x.Type)
pt := prog.Pointer(telem)
switch t := x.raw.Type.Underlying().(type) {
case *types.Slice:
ptr := b.SliceData(x)
max := b.SliceLen(x)
idx = b.checkIndex(idx, max)
indices := []llvm.Value{idx.impl}
return Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, ptr.impl, indices), pt}
case *types.Pointer:
ar := t.Elem().Underlying().(*types.Array)
max := prog.IntVal(uint64(ar.Len()), prog.Int())
idx = b.checkIndex(idx, max)
}
indices := []llvm.Value{idx.impl}
return Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, x.impl, indices), pt}
}
func isConstantInt(x Expr) (v int64, ok bool) {
if rv := x.impl.IsAConstantInt(); !rv.IsNil() {
v = rv.SExtValue()
ok = true
}
return
}
func isConstantUint(x Expr) (v uint64, ok bool) {
if rv := x.impl.IsAConstantInt(); !rv.IsNil() {
v = rv.ZExtValue()
ok = true
}
return
}
func checkRange(idx Expr, max Expr) (checkMin, checkMax bool) {
if idx.kind == vkSigned {
if v, ok := isConstantInt(idx); ok {
if v < 0 {
checkMin = true
}
if m, ok := isConstantInt(max); ok {
if v >= m {
checkMax = true
}
} else {
checkMax = true
}
} else {
checkMin = true
checkMax = true
}
} else {
if v, ok := isConstantUint(idx); ok {
if m, ok := isConstantUint(max); ok {
if v >= m {
checkMax = true
}
} else {
checkMax = true
}
} else {
checkMax = true
}
}
return
}
// check index >= 0 && index < max and size to uint
func (b Builder) checkIndex(idx Expr, max Expr) Expr {
prog := b.Prog
// check range
checkMin, checkMax := checkRange(idx, max)
// fit size
var typ Type
if idx.kind == vkSigned {
typ = prog.Int()
} else {
typ = prog.Uint()
}
if prog.SizeOf(idx.Type) < prog.SizeOf(typ) {
idx.Type = typ
idx.impl = castUintptr(b, idx.impl, typ)
}
// check range expr
var check Expr
if checkMin {
zero := llvm.ConstInt(idx.ll, 0, false)
check = Expr{llvm.CreateICmp(b.impl, llvm.IntSLT, idx.impl, zero), prog.Bool()}
}
if checkMax {
r := Expr{llvm.CreateICmp(b.impl, llvm.IntSGE, idx.impl, max.impl), prog.Bool()}
if check.IsNil() {
check = r
} else {
check = Expr{b.impl.CreateOr(r.impl, check.impl, ""), prog.Bool()}
}
}
if !check.IsNil() {
b.InlineCall(b.Pkg.rtFunc("AssertIndexRange"), check)
}
return idx
}
// The Index instruction yields element Index of collection X, an array,
// string or type parameter containing an array, a string, a pointer to an,
// array or a slice.
//
// Example printed form:
//
// t2 = t0[t1]
func (b Builder) Index(x, idx Expr, takeAddr func() (addr Expr, zero bool)) Expr {
if debugInstr {
log.Printf("Index %v, %v\n", x.impl, idx.impl)
}
prog := b.Prog
var telem Type
var ptr Expr
var max Expr
var zero bool
switch t := x.raw.Type.Underlying().(type) {
case *types.Basic:
if t.Kind() != types.String {
panic(fmt.Errorf("invalid operation: cannot index %v", t))
}
telem = prog.rawType(types.Typ[types.Byte])
ptr = b.StringData(x)
max = b.StringLen(x)
case *types.Array:
telem = prog.Index(x.Type)
ptr, zero = takeAddr()
max = prog.IntVal(uint64(t.Len()), prog.Int())
}
idx = b.checkIndex(idx, max)
if zero {
return prog.Zero(telem)
}
if ptr.IsNil() {
if x.impl.IsConstant() {
return Expr{llvm.ConstExtractElement(x.impl, idx.impl), telem}
}
ptr = b.Alloc(x.Type, false)
b.impl.CreateStore(x.impl, ptr.impl)
}
pt := prog.Pointer(telem)
indices := []llvm.Value{idx.impl}
buf := Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, ptr.impl, indices), pt}
return b.Load(buf)
}
// -----------------------------------------------------------------------------
// The Slice instruction yields a slice of an existing string, slice
// or *array X between optional integer bounds Low and High.
//
// Dynamically, this instruction panics if X evaluates to a nil *array
// pointer.
//
// Type() returns string if the type of X was string, otherwise a
// *types.Slice with the same element type as X.
//
// Example printed form:
//
// t1 = slice t0[1:]
func (b Builder) Slice(x, low, high, max Expr) (ret Expr) {
if debugInstr {
log.Printf("Slice %v, %v, %v\n", x.impl, low.impl, high.impl)
}
prog := b.Prog
var nCap Expr
var nEltSize Expr
var base Expr
var lowIsNil = low.IsNil()
if lowIsNil {
low = prog.IntVal(0, prog.Int())
}
switch t := x.raw.Type.Underlying().(type) {
case *types.Basic:
if t.Kind() != types.String {
panic(fmt.Errorf("invalid operation: cannot slice %v", t))
}
if high.IsNil() {
high = b.StringLen(x)
}
ret.Type = x.Type
ret.impl = b.InlineCall(b.Pkg.rtFunc("StringSlice"), x, low, high).impl
return
case *types.Slice:
nEltSize = SizeOf(prog, prog.Index(x.Type))
nCap = b.SliceCap(x)
if high.IsNil() {
high = b.SliceLen(x)
}
ret.Type = x.Type
base = b.SliceData(x)
case *types.Pointer:
telem := t.Elem()
switch te := telem.Underlying().(type) {
case *types.Array:
elem := prog.rawType(te.Elem())
ret.Type = prog.Slice(elem)
nEltSize = SizeOf(prog, elem)
nCap = prog.IntVal(uint64(te.Len()), prog.Int())
if high.IsNil() {
if lowIsNil && max.IsNil() {
ret.impl = b.unsafeSlice(x, nCap.impl, nCap.impl).impl
return
}
high = nCap
}
base = x
}
}
if max.IsNil() {
max = nCap
}
ret.impl = b.InlineCall(b.Pkg.rtFunc("NewSlice3"), base, nEltSize, nCap, low, high, max).impl
return
}
// SliceLit creates a new slice with the specified elements.
func (b Builder) SliceLit(t Type, elts ...Expr) Expr {
prog := b.Prog
telem := prog.Index(t)
ptr := b.AllocU(telem, int64(len(elts)))
for i, elt := range elts {
b.Store(b.Advance(ptr, prog.Val(i)), elt)
}
size := llvm.ConstInt(prog.tyInt(), uint64(len(elts)), false)
return b.unsafeSlice(ptr, size, size)
}
// The MakeSlice instruction yields a slice of length Len backed by a
// newly allocated array of length Cap.
//
// Both Len and Cap must be non-nil Values of integer type.
//
// (Alloc(types.Array) followed by Slice will not suffice because
// Alloc can only create arrays of constant length.)
//
// Type() returns a (possibly named) *types.Slice.
//
// Example printed form:
//
// t1 = make []string 1:int t0
// t1 = make StringSlice 1:int t0
func (b Builder) MakeSlice(t Type, len, cap Expr) (ret Expr) {
if debugInstr {
log.Printf("MakeSlice %v, %v, %v\n", t.RawType(), len.impl, cap.impl)
}
prog := b.Prog
len = b.fitIntSize(len)
cap = b.fitIntSize(cap)
telem := prog.Index(t)
ret = b.InlineCall(b.Pkg.rtFunc("MakeSlice"), len, cap, prog.IntVal(prog.SizeOf(telem), prog.Int()))
ret.Type = t
return
}
// fit size to int
func (b Builder) fitIntSize(n Expr) Expr {
prog := b.Prog
typ := prog.Int()
if prog.SizeOf(n.Type) != prog.SizeOf(typ) {
n.Type = typ
n.impl = castInt(b, n.impl, typ)
}
return n
}
// -----------------------------------------------------------------------------
// The MakeMap instruction creates a new hash-table-based map object
// and yields a value of kind map.
//
// t is a (possibly named) *types.Map.
//
// Example printed form:
//
// t1 = make map[string]int t0
// t1 = make StringIntMap t0
func (b Builder) MakeMap(t Type, nReserve Expr) (ret Expr) {
if debugInstr {
log.Printf("MakeMap %v, %v\n", t.RawType(), nReserve.impl)
}
if nReserve.IsNil() {
nReserve = b.Prog.Val(0)
}
typ := b.abiType(t.raw.Type)
ret = b.InlineCall(b.Pkg.rtFunc("MakeMap"), typ, nReserve)
ret.Type = t
return
}
// The Lookup instruction yields element Index of collection map X.
// Index is the appropriate key type.
//
// If CommaOk, the result is a 2-tuple of the value above and a
// boolean indicating the result of a map membership test for the key.
// The components of the tuple are accessed using Extract.
//
// Example printed form:
//
// t2 = t0[t1]
// t5 = t3[t4],ok
func (b Builder) Lookup(x, key Expr, commaOk bool) (ret Expr) {
if debugInstr {
log.Printf("Lookup %v, %v, %v\n", x.impl, key.impl, commaOk)
}
prog := b.Prog
typ := b.abiType(x.raw.Type)
vtyp := prog.Elem(x.Type)
ptr := b.mapKeyPtr(key)
if commaOk {
vals := b.Call(b.Pkg.rtFunc("MapAccess2"), typ, x, ptr)
val := b.Load(Expr{b.impl.CreateExtractValue(vals.impl, 0, ""), prog.Pointer(vtyp)})
ok := b.impl.CreateExtractValue(vals.impl, 1, "")
t := prog.Struct(vtyp, prog.Bool())
return b.aggregateValue(t, val.impl, ok)
} else {
val := b.Call(b.Pkg.rtFunc("MapAccess1"), typ, x, ptr)
val.Type = prog.Pointer(vtyp)
ret = b.Load(val)
}
return
}
// The MapUpdate instruction updates the association of Map[Key] to
// Value.
//
// Pos() returns the ast.KeyValueExpr.Colon or ast.IndexExpr.Lbrack,
// if explicit in the source.
//
// Example printed form:
//
// t0[t1] = t2
func (b Builder) MapUpdate(m, k, v Expr) {
if debugInstr {
log.Printf("MapUpdate %v[%v] = %v\n", m.impl, k.impl, v.impl)
}
typ := b.abiType(m.raw.Type)
ptr := b.mapKeyPtr(k)
ret := b.Call(b.Pkg.rtFunc("MapAssign"), typ, m, ptr)
ret.Type = b.Prog.Pointer(v.Type)
b.Store(ret, v)
}
// key => unsafe.Pointer
func (b Builder) mapKeyPtr(x Expr) Expr {
typ := x.Type
vtyp := b.Prog.VoidPtr()
vptr := b.AllocU(typ)
b.Store(vptr, x)
return Expr{vptr.impl, vtyp}
}
// -----------------------------------------------------------------------------
// The Range instruction yields an iterator over the domain and range
// of X, which must be a string or map.
//
// Elements are accessed via Next.
//
// Type() returns an opaque and degenerate "rangeIter" type.
//
// Pos() returns the ast.RangeStmt.For.
//
// Example printed form:
//
// t0 = range "hello":string
func (b Builder) Range(x Expr) Expr {
switch x.kind {
case vkString:
return b.InlineCall(b.Pkg.rtFunc("NewStringIter"), x)
case vkMap:
typ := b.abiType(x.raw.Type)
return b.InlineCall(b.Pkg.rtFunc("NewMapIter"), typ, x)
}
panic("unsupport range for " + x.raw.Type.String())
}
// The Next instruction reads and advances the (map or string)
// iterator Iter and returns a 3-tuple value (ok, k, v). If the
// iterator is not exhausted, ok is true and k and v are the next
// elements of the domain and range, respectively. Otherwise ok is
// false and k and v are undefined.
//
// Components of the tuple are accessed using Extract.
//
// The IsString field distinguishes iterators over strings from those
// over maps, as the Type() alone is insufficient: consider
// map[int]rune.
//
// Type() returns a *types.Tuple for the triple (ok, k, v).
// The types of k and/or v may be types.Invalid.
//
// Example printed form:
//
// t1 = next t0
func (b Builder) Next(typ Type, iter Expr, isString bool) Expr {
if isString {
return b.InlineCall(b.Pkg.rtFunc("StringIterNext"), iter)
}
prog := b.Prog
ktyp := prog.Type(typ.raw.Type.Underlying().(*types.Map).Key(), InGo)
vtyp := prog.Type(typ.raw.Type.Underlying().(*types.Map).Elem(), InGo)
rets := b.InlineCall(b.Pkg.rtFunc("MapIterNext"), iter)
ok := b.impl.CreateExtractValue(rets.impl, 0, "")
t := prog.Struct(prog.Bool(), ktyp, vtyp)
blks := b.Func.MakeBlocks(3)
b.If(Expr{ok, prog.Bool()}, blks[0], blks[1])
b.SetBlockEx(blks[2], AtEnd, false)
phi := b.Phi(t)
phi.AddIncoming(b, blks[:2], func(i int, blk BasicBlock) Expr {
b.SetBlockEx(blk, AtEnd, false)
if i == 0 {
k := b.impl.CreateExtractValue(rets.impl, 1, "")
v := b.impl.CreateExtractValue(rets.impl, 2, "")
valTrue := aggregateValue(b.impl, t.ll, prog.BoolVal(true).impl,
llvm.CreateLoad(b.impl, ktyp.ll, k),
llvm.CreateLoad(b.impl, vtyp.ll, v))
b.Jump(blks[2])
return Expr{valTrue, t}
}
valFalse := aggregateValue(b.impl, t.ll, prog.BoolVal(false).impl,
llvm.ConstNull(ktyp.ll),
llvm.ConstNull(vtyp.ll))
b.Jump(blks[2])
return Expr{valFalse, t}
})
b.SetBlockEx(blks[2], AtEnd, false)
b.blk.last = blks[2].last
return phi.Expr
}
// The MakeChan instruction creates a new channel object and yields a
// value of kind chan.
//
// Type() returns a (possibly named) *types.Chan.
//
// Pos() returns the ast.CallExpr.Lparen for the make(chan) that
// created it.
//
// Example printed form:
//
// t0 = make chan int 0
// t0 = make IntChan 0
//
// type MakeChan struct {
// register
// Size Value // int; size of buffer; zero => synchronous.
// }
func (b Builder) MakeChan(t Type, size Expr) (ret Expr) {
if debugInstr {
log.Printf("MakeChan %v, %v\n", t.RawType(), size.impl)
}
prog := b.Prog
eltSize := prog.IntVal(prog.SizeOf(prog.Elem(t)), prog.Int())
ret.Type = t
ret.impl = b.InlineCall(b.Pkg.rtFunc("NewChan"), eltSize, size).impl
return
}
// The Send instruction sends X on channel Chan.
//
// Pos() returns the ast.SendStmt.Arrow, if explicit in the source.
//
// Example printed form:
//
// send t0 <- t1
func (b Builder) Send(ch Expr, x Expr) (ret Expr) {
if debugInstr {
log.Printf("Send %v, %v\n", ch.impl, x.impl)
}
prog := b.Prog
eltSize := prog.IntVal(prog.SizeOf(prog.Elem(ch.Type)), prog.Int())
ret = b.InlineCall(b.Pkg.rtFunc("ChanSend"), ch, b.toPtr(x), eltSize)
return
}
func (b Builder) toPtr(x Expr) Expr {
typ := x.Type
vtyp := b.Prog.VoidPtr()
vptr := b.Alloc(typ, false)
b.Store(vptr, x)
return Expr{vptr.impl, vtyp}
}
func (b Builder) Recv(ch Expr, commaOk bool) (ret Expr) {
if debugInstr {
log.Printf("Recv %v, %v\n", ch.impl, commaOk)
}
prog := b.Prog
eltSize := prog.IntVal(prog.SizeOf(prog.Elem(ch.Type)), prog.Int())
etyp := prog.Elem(ch.Type)
ptr := b.Alloc(etyp, false)
ok := b.InlineCall(b.Pkg.rtFunc("ChanRecv"), ch, ptr, eltSize)
if commaOk {
val := b.Load(ptr)
t := prog.Struct(etyp, prog.Bool())
return b.aggregateValue(t, val.impl, ok.impl)
} else {
return b.Load(ptr)
}
}
type SelectState struct {
Chan Expr // channel to use (for send or receive)
Value Expr // value to send (for send)
Send bool // direction of case (SendOnly or RecvOnly)
}
// The Select instruction tests whether (or blocks until) one
// of the specified sent or received states is entered.
//
// Let n be the number of States for which Dir==RECV and T_i (0<=i<n)
// be the element type of each such state's Chan.
// Select returns an n+2-tuple
//
// (index int, recvOk bool, r_0 T_0, ... r_n-1 T_n-1)
//
// The tuple's components, described below, must be accessed via the
// Extract instruction.
//
// If Blocking, select waits until exactly one state holds, i.e. a
// channel becomes ready for the designated operation of sending or
// receiving; select chooses one among the ready states
// pseudorandomly, performs the send or receive operation, and sets
// 'index' to the index of the chosen channel.
//
// If !Blocking, select doesn't block if no states hold; instead it
// returns immediately with index equal to -1.
//
// If the chosen channel was used for a receive, the r_i component is
// set to the received value, where i is the index of that state among
// all n receive states; otherwise r_i has the zero value of type T_i.
// Note that the receive index i is not the same as the state
// index index.
//
// The second component of the triple, recvOk, is a boolean whose value
// is true iff the selected operation was a receive and the receive
// successfully yielded a value.
//
// Pos() returns the ast.SelectStmt.Select.
//
// Example printed form:
//
// t3 = select nonblocking [<-t0, t1<-t2]
// t4 = select blocking []
func (b Builder) Select(states []*SelectState, blocking bool) (ret Expr) {
ops := make([]Expr, len(states))
for i, s := range states {
ops[i] = b.chanOp(s)
}
var fn Expr
if blocking {
fn = b.Pkg.rtFunc("Select")
} else {
fn = b.Pkg.rtFunc("TrySelect")
}
prog := b.Prog
tSlice := lastParamType(prog, fn)
slice := b.SliceLit(tSlice, ops...)
ret = b.Call(fn, slice)
chosen := b.impl.CreateExtractValue(ret.impl, 0, "")
recvOK := b.impl.CreateExtractValue(ret.impl, 1, "")
if !blocking {
chosen = llvm.CreateSelect(b.impl, recvOK, chosen, prog.Val(-1).impl)
}
results := []llvm.Value{chosen, recvOK}
typs := []Type{prog.Int(), prog.Bool()}
for i, s := range states {
if !s.Send {
etyp := b.Prog.Elem(s.Chan.Type)
typs = append(typs, etyp)
r := b.Load(Expr{b.impl.CreateExtractValue(ops[i].impl, 1, ""), prog.Pointer(etyp)})
results = append(results, r.impl)
}
}
return b.aggregateValue(b.Prog.Struct(typs...), results...)
}
func (b Builder) chanOp(s *SelectState) Expr {
prog := b.Prog
var val Expr
var size Expr
if s.Send {
val = b.toPtr(s.Value)
size = prog.IntVal(prog.SizeOf(s.Value.Type), prog.Int32())
} else {
etyp := prog.Elem(s.Chan.Type)
val = b.Alloc(etyp, false)
size = prog.IntVal(prog.SizeOf(etyp), prog.Int32())
}
send := prog.BoolVal(s.Send)
typ := b.Prog.rtType("ChanOp")
return b.aggregateValue(typ, s.Chan.impl, val.impl, size.impl, send.impl)
}
// -----------------------------------------------------------------------------

370
ssa/decl.go Normal file
View File

@@ -0,0 +1,370 @@
/*
* 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 ssa
import (
"go/types"
"log"
"strconv"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
const (
NameValist = "__llgo_va_list"
)
func VArg() *types.Var {
return types.NewParam(0, nil, NameValist, types.NewSlice(tyAny))
}
func hasNameValist(sig *types.Signature) bool {
if sig.Variadic() {
if params := sig.Params(); params.At(params.Len()-1).Name() == NameValist {
return true
}
}
return false
}
// -----------------------------------------------------------------------------
type aNamedConst struct {
}
// A NamedConst is a Member of a Package representing a package-level
// named constant.
//
// Pos() returns the position of the declaring ast.ValueSpec.Names[*]
// identifier.
//
// NB: a NamedConst is not a Value; it contains a constant Value, which
// it augments with the name and position of its 'const' declaration.
type NamedConst = *aNamedConst
/*
// NewConst creates a new named constant.
func (p Package) NewConst(name string, val constant.Value) NamedConst {
return &aNamedConst{}
}
*/
// -----------------------------------------------------------------------------
type aGlobal struct {
Expr
//array bool
}
// A Global is a named Value holding the address of a package-level
// variable.
type Global = *aGlobal
// NewVar creates a new global variable.
func (p Package) NewVar(name string, typ types.Type, bg Background) Global {
if v, ok := p.vars[name]; ok {
return v
}
t := p.Prog.Type(typ, bg)
return p.doNewVar(name, t)
}
// 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 {
typ := p.Prog.Elem(t).ll
gbl := llvm.AddGlobal(p.mod, typ, name)
alignment := p.Prog.td.ABITypeAlignment(typ)
gbl.SetAlignment(alignment)
ret := &aGlobal{Expr{gbl, t}}
p.vars[name] = ret
return ret
}
// VarOf returns a global variable by name.
func (p Package) VarOf(name string) Global {
return p.vars[name]
}
// Init initializes the global variable with the given value.
func (g Global) Init(v Expr) {
g.impl.SetInitializer(v.impl)
}
func (g Global) InitNil() {
g.impl.SetInitializer(llvm.ConstNull(g.impl.GlobalValueType()))
}
func (g Global) ReplaceAllUsesWith(v Expr) {
g.impl.ReplaceAllUsesWith(v.impl)
}
// -----------------------------------------------------------------------------
// Function represents the parameters, results, and code of a function
// or method.
//
// If Blocks is nil, this indicates an external function for which no
// Go source code is available. In this case, FreeVars, Locals, and
// Params are nil too. Clients performing whole-program analysis must
// handle external functions specially.
//
// Blocks contains the function's control-flow graph (CFG).
// Blocks[0] is the function entry point; block order is not otherwise
// semantically significant, though it may affect the readability of
// the disassembly.
// To iterate over the blocks in dominance order, use DomPreorder().
//
// Recover is an optional second entry point to which control resumes
// after a recovered panic. The Recover block may contain only a return
// statement, preceded by a load of the function's named return
// parameters, if any.
//
// A nested function (Parent()!=nil) that refers to one or more
// lexically enclosing local variables ("free variables") has FreeVars.
// Such functions cannot be called directly but require a
// value created by MakeClosure which, via its Bindings, supplies
// values for these parameters.
//
// If the function is a method (Signature.Recv() != nil) then the first
// element of Params is the receiver parameter.
//
// A Go package may declare many functions called "init".
// For each one, Object().Name() returns "init" but Name() returns
// "init#1", etc, in declaration order.
//
// Pos() returns the declaring ast.FuncLit.Type.Func or the position
// of the ast.FuncDecl.Name, if the function was explicit in the
// source. Synthetic wrappers, for which Synthetic != "", may share
// the same position as the function they wrap.
// Syntax.Pos() always returns the position of the declaring "func" token.
//
// Type() returns the function's Signature.
//
// A generic function is a function or method that has uninstantiated type
// parameters (TypeParams() != nil). Consider a hypothetical generic
// method, (*Map[K,V]).Get. It may be instantiated with all ground
// (non-parameterized) types as (*Map[string,int]).Get or with
// parameterized types as (*Map[string,U]).Get, where U is a type parameter.
// In both instantiations, Origin() refers to the instantiated generic
// method, (*Map[K,V]).Get, TypeParams() refers to the parameters [K,V] of
// the generic method. TypeArgs() refers to [string,U] or [string,int],
// respectively, and is nil in the generic method.
type aFunction struct {
Expr
Pkg Package
Prog Program
blks []BasicBlock
defer_ *aDefer
recov BasicBlock
params []Type
freeVars Expr
base int // base = 1 if hasFreeVars; base = 0 otherwise
hasVArg bool
diFunc DIFunction
}
// Function represents a function or method.
type Function = *aFunction
// NewFunc creates a new function.
func (p Package) NewFunc(name string, sig *types.Signature, bg Background) Function {
return p.NewFuncEx(name, sig, bg, false, false)
}
// NewFuncEx creates a new function.
func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, hasFreeVars bool, instantiated bool) Function {
if v, ok := p.fns[name]; ok {
return v
}
t := p.Prog.FuncDecl(sig, bg)
if debugInstr {
log.Println("NewFunc", name, t.raw.Type, "hasFreeVars:", hasFreeVars)
}
fn := llvm.AddFunction(p.mod, name, t.ll)
if instantiated {
fn.SetLinkage(llvm.LinkOnceAnyLinkage)
}
ret := newFunction(fn, t, p, p.Prog, hasFreeVars)
p.fns[name] = ret
return ret
}
// FuncOf returns a function by name.
func (p Package) FuncOf(name string) Function {
return p.fns[name]
}
func newFunction(fn llvm.Value, t Type, pkg Package, prog Program, hasFreeVars bool) Function {
params, hasVArg := newParams(t, prog)
base := 0
if hasFreeVars {
base = 1
}
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) {
sig := fn.raw.Type.(*types.Signature)
in := sig.Params()
if n := in.Len(); n > 0 {
if hasVArg = hasNameValist(sig); hasVArg {
n--
}
params = make([]Type, n)
for i := 0; i < n; i++ {
params[i] = prog.rawType(in.At(i).Type())
}
}
return
}
// Name returns the function's name.
func (p Function) Name() string {
return p.impl.Name()
}
// Params returns the function's ith parameter.
func (p Function) Param(i int) Expr {
i += p.base // skip if hasFreeVars
return Expr{p.impl.Param(i), p.params[i]}
}
func (p Function) closureCtx(b Builder) Expr {
if p.freeVars.IsNil() {
if p.base == 0 {
panic("ssa: function has no free variables")
}
ptr := Expr{p.impl.Param(0), p.params[0]}
if b.blk.Index() != 0 {
blk := b.impl.GetInsertBlock()
b.SetBlockEx(p.blks[0], AtStart, false)
p.freeVars = b.Load(ptr)
b.impl.SetInsertPointAtEnd(blk)
} else {
p.freeVars = b.Load(ptr)
}
}
return p.freeVars
}
// FreeVar returns the function's ith free variable.
func (p Function) FreeVar(b Builder, i int) Expr {
ctx := p.closureCtx(b)
return b.getField(ctx, i)
}
// NewBuilder creates a new Builder for the function.
func (p Function) NewBuilder() Builder {
prog := p.Prog
b := prog.ctx.NewBuilder()
// TODO(xsw): Finalize may cause panic, so comment it.
// b.Finalize()
return &aBuilder{b, nil, p, p.Pkg, prog,
make(map[Expr]dbgExpr), make(map[*types.Scope]DIScope)}
}
// HasBody reports whether the function has a body.
func (p Function) HasBody() bool {
return len(p.blks) > 0
}
// MakeBody creates nblk basic blocks for the function, and creates
// a new Builder associated to #0 block.
func (p Function) MakeBody(nblk int) Builder {
p.MakeBlocks(nblk)
b := p.NewBuilder()
b.blk = p.blks[0]
b.impl.SetInsertPointAtEnd(b.blk.last)
return b
}
// MakeBlocks creates nblk basic blocks for the function.
func (p Function) MakeBlocks(nblk int) []BasicBlock {
n := len(p.blks)
if n == 0 {
p.blks = make([]BasicBlock, 0, nblk)
}
for i := 0; i < nblk; i++ {
p.addBlock(n + i)
}
return p.blks[n:]
}
func (p Function) addBlock(idx int) BasicBlock {
label := "_llgo_" + strconv.Itoa(idx)
blk := llvm.AddBasicBlock(p.impl, label)
ret := &aBasicBlock{blk, blk, p, idx}
p.blks = append(p.blks, ret)
return ret
}
// MakeBlock creates a new basic block for the function.
func (p Function) MakeBlock() BasicBlock {
return p.addBlock(len(p.blks))
}
// Block returns the ith basic block of the function.
func (p Function) Block(idx int) BasicBlock {
return p.blks[idx]
}
// SetRecover sets the recover block for the function.
func (p Function) SetRecover(blk BasicBlock) {
p.recov = blk
}
// -----------------------------------------------------------------------------
type inlineAttr int
const (
NoInline inlineAttr = iota
AlwaysInline
InlineHint
)
func (p Function) Inline(inline inlineAttr) {
inlineAttrName := map[inlineAttr]string{
NoInline: "noinline",
AlwaysInline: "alwaysinline",
InlineHint: "inlinehint",
}[inline]
inlineAttr := p.Pkg.mod.Context().CreateEnumAttribute(llvm.AttributeKindID(inlineAttrName), 0)
p.impl.AddFunctionAttr(inlineAttr)
}
// -----------------------------------------------------------------------------

791
ssa/di.go Normal file
View File

@@ -0,0 +1,791 @@
package ssa
import (
"debug/dwarf"
"fmt"
"go/token"
"go/types"
"path/filepath"
"unsafe"
"github.com/goplus/llvm"
)
type Positioner interface {
Position(pos token.Pos) token.Position
}
type aDIBuilder struct {
di *llvm.DIBuilder
prog Program
types map[Type]DIType
positioner Positioner
m llvm.Module // Add this field
}
type diBuilder = *aDIBuilder
func newDIBuilder(prog Program, pkg Package, positioner Positioner) diBuilder {
m := pkg.mod
ctx := m.Context()
b := &aDIBuilder{
di: llvm.NewDIBuilder(m),
prog: prog,
types: make(map[*aType]DIType),
positioner: positioner,
m: m, // Initialize the m field
}
b.addNamedMetadataOperand("llvm.module.flags", 2, "Debug Info Version", 3)
b.addNamedMetadataOperand("llvm.module.flags", 7, "Dwarf Version", 4)
b.addNamedMetadataOperand("llvm.module.flags", 1, "wchar_size", 4)
b.addNamedMetadataOperand("llvm.module.flags", 8, "PIC Level", 2)
b.addNamedMetadataOperand("llvm.module.flags", 7, "uwtable", 1)
b.addNamedMetadataOperand("llvm.module.flags", 7, "frame-pointer", 1)
// Add llvm.ident metadata
identNode := ctx.MDNode([]llvm.Metadata{
ctx.MDString("LLGo Compiler"),
})
m.AddNamedMetadataOperand("llvm.ident", identNode)
return b
}
// New method to add named metadata operand
func (b diBuilder) addNamedMetadataOperand(name string, intValue int, stringValue string, intValue2 int) {
ctx := b.m.Context()
b.m.AddNamedMetadataOperand(name,
ctx.MDNode([]llvm.Metadata{
llvm.ConstInt(ctx.Int32Type(), uint64(intValue), false).ConstantAsMetadata(),
ctx.MDString(stringValue),
llvm.ConstInt(ctx.Int32Type(), uint64(intValue2), false).ConstantAsMetadata(),
}),
)
}
// ----------------------------------------------------------------------------
type aCompilationUnit struct {
ll llvm.Metadata
}
type CompilationUnit = *aCompilationUnit
func (c CompilationUnit) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
return &aDIScopeMeta{c.ll}
}
var DWARF_LANG_C llvm.DwarfLang = 0x2
var DWARF_LANG_GO llvm.DwarfLang = 0x16
func (b diBuilder) createCompileUnit(filename, dir string) CompilationUnit {
return &aCompilationUnit{ll: b.di.CreateCompileUnit(llvm.DICompileUnit{
// TODO(lijie): use C language for now, change after Go plugin of LLDB is ready
Language: DWARF_LANG_C - 1,
File: filename,
Dir: dir,
Producer: "LLGo",
Optimized: true,
RuntimeVersion: 1,
})}
}
// ----------------------------------------------------------------------------
type aDIScopeMeta struct {
ll llvm.Metadata
}
type DIScopeMeta = *aDIScopeMeta
type DIScope interface {
scopeMeta(b diBuilder, pos token.Position) DIScopeMeta
}
// ----------------------------------------------------------------------------
type aDIFile struct {
ll llvm.Metadata
}
type DIFile = *aDIFile
func (b diBuilder) createFile(filename string) DIFile {
dir, file := filepath.Split(filename)
return &aDIFile{ll: b.di.CreateFile(file, dir)}
}
func (f DIFile) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
return &aDIScopeMeta{b.file(pos.Filename).ll}
}
// ----------------------------------------------------------------------------
type aDIType struct {
ll llvm.Metadata
}
type DIType = *aDIType
func (b diBuilder) createType(name string, ty Type, pos token.Position) DIType {
var typ llvm.Metadata
switch t := ty.RawType().(type) {
case *types.Basic:
if t.Kind() == types.UnsafePointer {
typ = b.di.CreatePointerType(llvm.DIPointerType{
Name: name,
SizeInBits: b.prog.SizeOf(b.prog.rawType(t)) * 8,
AlignInBits: uint32(b.prog.sizes.Alignof(t) * 8),
})
return &aDIType{typ}
}
var encoding llvm.DwarfTypeEncoding
if t.Info()&types.IsBoolean != 0 {
encoding = llvm.DW_ATE_boolean
} else if t.Info()&types.IsUnsigned != 0 {
encoding = llvm.DW_ATE_unsigned
} else if t.Info()&types.IsInteger != 0 {
encoding = llvm.DW_ATE_signed
} else if t.Info()&types.IsFloat != 0 {
encoding = llvm.DW_ATE_float
} else if t.Info()&types.IsComplex != 0 {
return b.createComplexType(ty)
} else if t.Info()&types.IsString != 0 {
return b.createStringType()
} else {
panic(fmt.Errorf("can't create debug info of basic type: %v, %T", ty.RawType(), ty.RawType()))
}
typ = b.di.CreateBasicType(llvm.DIBasicType{
Name: name,
SizeInBits: b.prog.SizeOf(b.prog.rawType(t)) * 8,
Encoding: encoding,
})
case *types.Pointer:
return b.createPointerType(name, b.prog.rawType(t.Elem()), pos)
case *types.Named:
// Create typedef type for named types
return b.createTypedefType(name, ty, pos)
case *types.Interface:
ty := b.prog.rtType("Iface")
return b.createInterfaceType(name, ty)
case *types.Slice:
ty := b.prog.rtType("Slice")
tyElem := b.prog.rawType(t.Elem())
return b.createSliceType(name, ty, tyElem)
case *types.Struct:
return b.createStructType(name, ty, pos)
case *types.Signature:
tyFn := b.prog.Closure(t)
return b.createFuncPtrType(name, tyFn, pos)
case *types.Array:
return b.createArrayType(ty, t.Len())
case *types.Chan:
return b.createChanType(name, ty, pos)
case *types.Map:
ty := b.prog.rtType("Map")
return b.createMapType(name, ty, pos)
case *types.Tuple:
return b.createTupleType(name, ty, pos)
default:
panic(fmt.Errorf("can't create debug info of type: %v, %T", ty.RawType(), ty.RawType()))
}
return &aDIType{typ}
}
// ----------------------------------------------------------------------------
type aDIFunction struct {
ll llvm.Metadata
}
type DIFunction = *aDIFunction
func (p Function) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
return &aDIScopeMeta{p.diFunc.ll}
}
// ----------------------------------------------------------------------------
type aDIGlobalVariableExpression struct {
ll llvm.Metadata
}
type DIGlobalVariableExpression = *aDIGlobalVariableExpression
func (b diBuilder) createGlobalVariableExpression(scope DIScope, pos token.Position, name, linkageName string, ty Type, isLocalToUnit bool) DIGlobalVariableExpression {
return &aDIGlobalVariableExpression{
ll: b.di.CreateGlobalVariableExpression(
scope.scopeMeta(b, pos).ll,
llvm.DIGlobalVariableExpression{
Name: name,
LinkageName: linkageName,
File: b.file(pos.Filename).ll,
Line: pos.Line,
Type: b.diType(ty, pos).ll,
LocalToUnit: isLocalToUnit,
AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8),
},
),
}
}
// ----------------------------------------------------------------------------
type aDILexicalBlock struct {
ll llvm.Metadata
}
type DILexicalBlock = *aDILexicalBlock
func (l *aDILexicalBlock) scopeMeta(b diBuilder, pos token.Position) DIScopeMeta {
return &aDIScopeMeta{l.ll}
}
// ----------------------------------------------------------------------------
type aDIVar struct {
ll llvm.Metadata
}
type DIVar = *aDIVar
func (b diBuilder) createParameterVariable(scope DIScope, pos token.Position, name string, argNo int, ty DIType) DIVar {
return &aDIVar{
ll: b.di.CreateParameterVariable(
scope.scopeMeta(b, pos).ll,
llvm.DIParameterVariable{
Name: name,
File: b.file(pos.Filename).ll,
Line: pos.Line,
ArgNo: argNo,
Type: ty.ll,
AlwaysPreserve: true,
},
),
}
}
func (b diBuilder) createAutoVariable(scope DIScope, pos token.Position, name string, ty DIType) DIVar {
return &aDIVar{
ll: b.di.CreateAutoVariable(
scope.scopeMeta(b, pos).ll,
llvm.DIAutoVariable{
Name: name,
File: b.file(pos.Filename).ll,
Line: pos.Line,
Type: ty.ll,
AlwaysPreserve: true,
},
),
}
}
func (b diBuilder) createTypedefType(name string, ty Type, pos token.Position) DIType {
underlyingType := b.diType(b.prog.rawType(ty.RawType().(*types.Named).Underlying()), pos)
typ := b.di.CreateTypedef(llvm.DITypedef{
Name: name,
Type: underlyingType.ll,
File: b.file(pos.Filename).ll,
Line: pos.Line,
AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8),
})
return &aDIType{typ}
}
func (b diBuilder) createStringType() DIType {
ty := b.prog.rtType("String")
return b.doCreateStructType("string", ty, token.Position{}, func(ditStruct DIType) []llvm.Metadata {
return []llvm.Metadata{
b.createMemberType("data", ty, b.prog.CStr(), 0),
b.createMemberType("len", ty, b.prog.Uint(), 1),
}
})
}
func (b diBuilder) createArrayType(ty Type, l int64) DIType {
tyElem := b.prog.rawType(ty.RawType().(*types.Array).Elem())
return &aDIType{ll: b.di.CreateArrayType(llvm.DIArrayType{
SizeInBits: b.prog.SizeOf(ty) * 8,
AlignInBits: uint32(b.prog.sizes.Alignof(ty.RawType()) * 8),
ElementType: b.diType(tyElem, token.Position{}).ll,
Subscripts: []llvm.DISubrange{{
Count: l,
}},
})}
}
func (b diBuilder) createSliceType(name string, ty, tyElem Type) DIType {
pos := token.Position{}
diElemTyPtr := b.prog.Pointer(tyElem)
return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata {
return []llvm.Metadata{
b.createMemberTypeEx("data", ty, diElemTyPtr, 0, pos, 0),
b.createMemberTypeEx("len", ty, b.prog.Uint(), 1, pos, 0),
b.createMemberTypeEx("cap", ty, b.prog.Uint(), 2, pos, 0),
}
})
}
func (b diBuilder) createInterfaceType(name string, ty Type) DIType {
tyRaw := ty.RawType().Underlying()
tyIntr := b.prog.rawType(tyRaw)
tyType := b.prog.VoidPtr()
tyData := b.prog.VoidPtr()
return b.doCreateStructType(name, tyIntr, token.Position{}, func(ditStruct DIType) []llvm.Metadata {
return []llvm.Metadata{
b.createMemberType("type", ty, tyType, 0),
b.createMemberType("data", ty, tyData, 1),
}
})
}
func (b diBuilder) createMemberType(name string, tyStruct, tyField Type, idxField int) llvm.Metadata {
return b.createMemberTypeEx(name, tyStruct, tyField, idxField, token.Position{}, 0)
}
func (b diBuilder) createMemberTypeEx(name string, tyStruct, tyField Type, idxField int, pos token.Position, flags int) llvm.Metadata {
return b.di.CreateMemberType(
b.diType(tyStruct, pos).ll,
llvm.DIMemberType{
Name: name,
SizeInBits: b.prog.SizeOf(tyField) * 8,
AlignInBits: uint32(b.prog.sizes.Alignof(tyField.RawType()) * 8),
OffsetInBits: b.prog.OffsetOf(tyStruct, idxField) * 8,
Type: b.diType(tyField, pos).ll,
Flags: flags,
},
)
}
func (b diBuilder) createMapType(name string, tyMap Type, pos token.Position) DIType {
// ty := tyMap.RawType().(*types.Map)
// tk := b.prog.rawType(ty.Key())
// tv := b.prog.rawType(ty.Elem())
tyCount := b.prog.Int()
return b.doCreateStructType(name, tyMap, pos, func(ditStruct DIType) []llvm.Metadata {
return []llvm.Metadata{
b.createMemberType("count", tyMap, tyCount, 0),
}
})
}
func (b diBuilder) createChanType(name string, t Type, pos token.Position) DIType {
return b.doCreateStructType(name, t, pos, func(ditStruct DIType) []llvm.Metadata {
return []llvm.Metadata{}
})
}
func (b diBuilder) createComplexType(t Type) DIType {
var tfield Type
var tyName string
if t.RawType().(*types.Basic).Kind() == types.Complex128 {
tfield = b.prog.Float64()
tyName = "complex128"
} else {
tfield = b.prog.Float32()
tyName = "complex64"
}
return b.doCreateStructType(tyName, t, token.Position{}, func(ditStruct DIType) []llvm.Metadata {
return []llvm.Metadata{
b.createMemberType("real", t, tfield, 0),
b.createMemberType("imag", t, tfield, 1),
}
})
}
func (b diBuilder) createPointerType(name string, ty Type, pos token.Position) DIType {
ptrType := b.prog.VoidPtr()
return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{
Name: name,
Pointee: b.diType(ty, pos).ll,
SizeInBits: b.prog.SizeOf(ptrType) * 8,
AlignInBits: uint32(b.prog.sizes.Alignof(ptrType.RawType())) * 8,
})}
}
func (b diBuilder) doCreateStructType(name string, ty Type, pos token.Position, fn func(ty DIType) []llvm.Metadata) (ret DIType) {
structType := ty.RawType().Underlying()
scope := b.file(pos.Filename)
ret = &aDIType{b.di.CreateReplaceableCompositeType(
scope.ll,
llvm.DIReplaceableCompositeType{
Tag: dwarf.TagStructType,
Name: name,
File: b.file(pos.Filename).ll,
Line: pos.Line,
SizeInBits: b.prog.SizeOf(ty) * 8,
AlignInBits: uint32(b.prog.sizes.Alignof(structType) * 8),
},
)}
b.types[ty] = ret
fields := fn(ret)
st := b.di.CreateStructType(
scope.ll,
llvm.DIStructType{
Name: name,
File: b.file(pos.Filename).ll,
Line: pos.Line,
SizeInBits: b.prog.SizeOf(ty) * 8,
AlignInBits: uint32(b.prog.sizes.Alignof(structType) * 8),
Elements: fields,
},
)
ret.ll.ReplaceAllUsesWith(st)
ret.ll = st
return
}
func (b diBuilder) createStructType(name string, ty Type, pos token.Position) (ret DIType) {
structType := ty.RawType().(*types.Struct)
return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata {
fields := make([]llvm.Metadata, structType.NumFields())
for i := 0; i < structType.NumFields(); i++ {
field := structType.Field(i)
tyField := b.prog.rawType(field.Type())
flags := 0
pos := b.positioner.Position(field.Pos())
fields[i] = b.createMemberTypeEx(field.Name(), ty, tyField, i, pos, flags)
}
return fields
})
}
func (b diBuilder) createTupleType(name string, ty Type, pos token.Position) DIType {
tupleType := ty.RawType().(*types.Tuple)
if tupleType.Len() == 0 {
return &aDIType{}
}
if tupleType.Len() == 1 {
t := b.prog.rawType(tupleType.At(0).Type())
return b.diType(t, pos)
}
return b.doCreateStructType(name, ty, pos, func(ditStruct DIType) []llvm.Metadata {
fields := make([]llvm.Metadata, ty.RawType().(*types.Tuple).Len())
for i := 0; i < ty.RawType().(*types.Tuple).Len(); i++ {
field := ty.RawType().(*types.Tuple).At(i)
tyField := b.prog.rawType(field.Type())
fields[i] = b.createMemberTypeEx(field.Name(), ty, tyField, i, pos, 0)
}
return fields
})
}
func (b diBuilder) createFuncPtrType(name string, ty Type, pos token.Position) DIType {
ptr := b.prog.VoidPtr()
return &aDIType{ll: b.di.CreatePointerType(llvm.DIPointerType{
Name: name,
Pointee: b.diType(ptr, pos).ll,
SizeInBits: b.prog.SizeOf(ptr) * 8,
AlignInBits: uint32(b.prog.sizes.Alignof(ptr.RawType()) * 8),
})}
}
// ----------------------------------------------------------------------------
func (b diBuilder) dbgDeclare(v Expr, dv DIVar, scope DIScope, pos token.Position, expr DIExpression, blk BasicBlock) {
loc := llvm.DebugLoc{
Line: uint(pos.Line),
Col: uint(pos.Column),
Scope: scope.scopeMeta(b, pos).ll,
}
b.di.InsertDeclareAtEnd(
v.impl,
dv.ll,
expr.ll,
loc,
blk.last,
)
}
func (b diBuilder) dbgValue(v Expr, dv DIVar, scope DIScope, pos token.Position, expr DIExpression, blk BasicBlock) {
loc := llvm.DebugLoc{
Line: uint(pos.Line),
Col: uint(pos.Column),
Scope: scope.scopeMeta(b, pos).ll,
}
b.di.InsertValueAtEnd(
v.impl,
dv.ll,
expr.ll,
loc,
blk.last,
)
}
func (b diBuilder) diType(t Type, pos token.Position) DIType {
name := t.RawType().String()
return b.diTypeEx(name, t, pos)
}
func (b diBuilder) diTypeEx(name string, t Type, pos token.Position) DIType {
if ty, ok := b.types[t]; ok {
return ty
}
ty := b.createType(name, t, pos)
b.types[t] = ty
return ty
}
func (b diBuilder) varParam(scope DIScope, pos token.Position, varName string, vt DIType, argNo int) DIVar {
return b.createParameterVariable(
scope,
pos,
varName,
argNo,
vt,
)
}
func (b diBuilder) varAuto(scope DIScope, pos token.Position, varName string, vt DIType) DIVar {
return b.createAutoVariable(scope, pos, varName, vt)
}
func (b diBuilder) file(filename string) DIFile {
return b.createFile(filename)
}
// ----------------------------------------------------------------------------
type aDIExpression struct {
ll llvm.Metadata
}
type DIExpression = *aDIExpression
func (b diBuilder) createExpression(ops []uint64) DIExpression {
return &aDIExpression{b.di.CreateExpression(ops)}
}
// -----------------------------------------------------------------------------
// Copy to alloca'd memory to get declareable address.
func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, exists bool) {
if v, ok := b.dbgVars[v]; ok {
return v.ptr, v.val, true
}
t := v.Type.RawType().Underlying()
dbgPtr, dbgVal = b.doConstructDebugAddr(v, t)
dbgExpr := dbgExpr{dbgPtr, dbgVal}
b.dbgVars[v] = dbgExpr
b.dbgVars[dbgVal] = dbgExpr
return dbgPtr, dbgVal, false
}
func (b Builder) doConstructDebugAddr(v Expr, t types.Type) (dbgPtr Expr, dbgVal Expr) {
var ty Type
switch t := t.(type) {
case *types.Basic:
if t.Info()&types.IsComplex != 0 {
if t.Kind() == types.Complex128 {
ty = b.Prog.Complex128()
} else {
ty = b.Prog.Complex64()
}
} else if t.Info()&types.IsString != 0 {
ty = b.Prog.rtType("String")
} else {
ty = v.Type
}
case *types.Struct:
ty = v.Type
case *types.Slice:
ty = b.Prog.Type(b.Prog.rtType("Slice").RawType().Underlying(), InGo)
case *types.Signature:
ty = b.Prog.Closure(t)
case *types.Named:
ty = b.Prog.Type(t.Underlying(), InGo)
case *types.Map:
ty = b.Prog.Type(b.Prog.rtType("Map").RawType().Underlying(), InGo)
default:
ty = v.Type
}
dbgPtr = b.AllocaT(ty)
dbgPtr.Type = b.Prog.Pointer(v.Type)
b.Store(dbgPtr, v)
dbgVal = b.Load(dbgPtr)
return dbgPtr, dbgVal
}
func (b Builder) di() diBuilder {
return b.Pkg.di
}
func (b Builder) DIParam(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) {
b.DIValue(variable, v, dv, scope, pos, blk)
}
func (b Builder) DIDeclare(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) {
expr := b.di().createExpression(nil)
b.di().dbgDeclare(v, dv, scope, pos, expr, blk)
}
func (b Builder) DIValue(variable *types.Var, v Expr, dv DIVar, scope DIScope, pos token.Position, blk BasicBlock) {
ty := v.Type.RawType().Underlying()
if !needConstructAddr(ty) {
expr := b.di().createExpression(nil)
b.di().dbgValue(v, dv, scope, pos, expr, blk)
} else {
dbgPtr, _, _ := b.constructDebugAddr(v)
expr := b.di().createExpression([]uint64{opDeref})
b.di().dbgValue(dbgPtr, dv, scope, pos, expr, blk)
}
}
const (
opDeref = 0x06
)
func needConstructAddr(t types.Type) bool {
switch t := t.(type) {
case *types.Basic:
if t.Info()&types.IsComplex != 0 {
return true
} else if t.Info()&types.IsString != 0 {
return true
}
return false
case *types.Pointer:
return false
default:
return true
}
}
func (b Builder) DIVarParam(scope DIScope, pos token.Position, varName string, vt Type, argNo int) DIVar {
t := b.di().diType(vt, pos)
return b.di().varParam(scope, pos, varName, t, argNo)
}
func (b Builder) DIVarAuto(scope DIScope, pos token.Position, varName string, vt Type) DIVar {
t := b.di().diType(vt, pos)
return b.di().varAuto(scope, pos, varName, t)
}
// hack for types.Scope
type hackScope struct {
parent *types.Scope
children []*types.Scope
number int // parent.children[number-1] is this scope; 0 if there is no parent
elems map[string]types.Object // lazily allocated
pos, end token.Pos // scope extent; may be invalid
comment string // for debugging only
isFunc bool // set if this is a function scope (internal use only)
}
func isFunc(scope *types.Scope) bool {
hs := (*hackScope)(unsafe.Pointer(scope))
return hs.isFunc
}
func (b Builder) DIScope(f Function, scope *types.Scope) DIScope {
if cachedScope, ok := b.diScopeCache[scope]; ok {
return cachedScope
}
pos := b.di().positioner.Position(scope.Pos())
// skip package and universe scope
// if scope.Parent().Parent() == nil {
// return b.di().file(pos.Filename)
// }
var result DIScope
if isFunc(scope) {
// TODO(lijie): should check scope == function scope
result = f
} else {
parentScope := b.DIScope(f, scope.Parent())
result = &aDILexicalBlock{b.di().di.CreateLexicalBlock(parentScope.scopeMeta(b.di(), pos).ll, llvm.DILexicalBlock{
File: b.di().file(pos.Filename).ll,
Line: pos.Line,
Column: pos.Column,
})}
}
b.diScopeCache[scope] = result
return result
}
const (
MD_dbg = 0
)
func (b Builder) DIGlobal(v Expr, name string, pos token.Position) {
if _, ok := b.Pkg.glbDbgVars[v]; ok {
return
}
gv := b.di().createGlobalVariableExpression(
b.Pkg.cu,
pos,
name,
name,
v.Type,
false,
)
v.impl.AddMetadata(MD_dbg, gv.ll)
b.Pkg.glbDbgVars[v] = true
}
func (b Builder) DISetCurrentDebugLocation(diScope DIScope, pos token.Position) {
b.impl.SetCurrentDebugLocation(
uint(pos.Line),
uint(pos.Column),
diScope.scopeMeta(b.di(), pos).ll,
llvm.Metadata{},
)
}
func (b Builder) DebugFunction(f Function, pos token.Position, bodyPos token.Position) {
p := f
if p.diFunc == nil {
sig := p.Type.raw.Type.(*types.Signature)
rt := p.Prog.Type(sig.Results(), InGo)
paramTypes := make([]llvm.Metadata, len(p.params)+1)
paramTypes[0] = b.di().diType(rt, pos).ll
for i, t := range p.params {
paramTypes[i+1] = b.di().diType(t, pos).ll
}
diFuncType := b.di().di.CreateSubroutineType(llvm.DISubroutineType{
File: b.di().file(pos.Filename).ll,
Parameters: paramTypes,
})
dif := llvm.DIFunction{
Type: diFuncType,
Name: p.Name(),
LinkageName: p.Name(),
File: b.di().file(pos.Filename).ll,
Line: pos.Line,
ScopeLine: bodyPos.Line,
IsDefinition: true,
LocalToUnit: true,
Optimized: true,
}
p.diFunc = &aDIFunction{
b.di().di.CreateFunction(b.di().file(pos.Filename).ll, dif),
}
p.impl.SetSubprogram(p.diFunc.ll)
}
b.impl.SetCurrentDebugLocation(
uint(bodyPos.Line),
uint(bodyPos.Column),
p.diFunc.ll,
f.impl.InstructionDebugLoc(),
)
}
func (b Builder) Param(idx int) Expr {
p := b.Func.Param(idx)
if v, ok := b.dbgVars[p]; ok {
return v.val
}
return p
}
// -----------------------------------------------------------------------------

394
ssa/eh.go Normal file
View File

@@ -0,0 +1,394 @@
/*
* 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 ssa
// #include <setjmp.h>
import "C"
import (
"go/token"
"go/types"
"log"
"unsafe"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
type sigjmpbuf = C.sigjmp_buf
// func(env unsafe.Pointer, savemask c.Int) c.Int
func (p Program) tySigsetjmp() *types.Signature {
if p.sigsetjmpTy == nil {
paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)
paramCInt := types.NewParam(token.NoPos, nil, "", p.CInt().raw.Type)
params := types.NewTuple(paramPtr, paramCInt)
results := types.NewTuple(paramCInt)
p.sigsetjmpTy = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.sigsetjmpTy
}
// func(env unsafe.Pointer, retval c.Int)
func (p Program) tySiglongjmp() *types.Signature {
if p.sigljmpTy == nil {
paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)
paramCInt := types.NewParam(token.NoPos, nil, "", p.CInt().raw.Type)
params := types.NewTuple(paramPtr, paramCInt)
p.sigljmpTy = types.NewSignatureType(nil, nil, nil, params, nil, false)
}
return p.sigljmpTy
}
func (b Builder) AllocaSigjmpBuf() Expr {
prog := b.Prog
n := unsafe.Sizeof(sigjmpbuf{})
size := prog.IntVal(uint64(n), prog.Uintptr())
return b.Alloca(size)
}
func (b Builder) Sigsetjmp(jb, savemask Expr) Expr {
fname := "sigsetjmp"
if b.Prog.target.GOOS == "linux" {
fname = "__sigsetjmp"
}
fn := b.Pkg.cFunc(fname, b.Prog.tySigsetjmp())
return b.Call(fn, jb, savemask)
}
func (b Builder) Siglongjmp(jb, retval Expr) {
fn := b.Pkg.cFunc("siglongjmp", b.Prog.tySiglongjmp()) // TODO(xsw): mark as noreturn
b.Call(fn, jb, retval)
// b.Unreachable()
}
// -----------------------------------------------------------------------------
const (
deferKey = "__llgo_defer"
)
func (p Function) deferInitBuilder() (b Builder, next BasicBlock) {
b = p.NewBuilder()
next = b.setBlockMoveLast(p.blks[0])
p.blks[0].last = next.last
return
}
type aDefer struct {
nextBit int // next defer bit
key Expr // pthread TLS key
data Expr // pointer to runtime.Defer
bitsPtr Expr // pointer to defer bits
rethPtr Expr // next block of Rethrow
rundPtr Expr // next block of RunDefers
argsPtr Expr // func and args links
procBlk BasicBlock // deferProc block
panicBlk BasicBlock // panic block (runDefers and rethrow)
rundsNext []BasicBlock // next blocks of RunDefers
stmts []func(bits Expr)
}
func (p Package) keyInit(name string) {
keyVar := p.VarOf(name)
if keyVar == nil {
return
}
prog := p.Prog
keyVar.InitNil()
keyVar.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
b := p.afterBuilder()
eq := b.BinOp(token.EQL, b.Load(keyVar.Expr), prog.IntVal(0, prog.CInt()))
b.IfThen(eq, func() {
b.pthreadKeyCreate(keyVar.Expr, prog.Nil(prog.VoidPtr()))
})
}
func (p Package) newKey(name string) Global {
return p.NewVarEx(name, p.Prog.CIntPtr())
}
func (b Builder) deferKey() Expr {
return b.Load(b.Pkg.newKey(deferKey).Expr)
}
const (
// 0: addr sigjmpbuf
// 1: bits uintptr
// 2: link *Defer
// 3: reth voidptr: block address after Rethrow
// 4: rund voidptr: block address after RunDefers
// 5: func and args links
deferSigjmpbuf = iota
deferBits
deferLink
deferRethrow
deferRunDefers
deferArgs
)
func (b Builder) getDefer(kind DoAction) *aDefer {
if b.Func.recov == nil {
// b.Func.recov maybe nil in ssa.NaiveForm
return nil
}
self := b.Func
if self.defer_ == nil {
// TODO(xsw): check if in pkg.init
var next, panicBlk BasicBlock
if kind != DeferAlways {
b, next = self.deferInitBuilder()
}
prog := b.Prog
blks := self.MakeBlocks(2)
procBlk, rethrowBlk := blks[0], blks[1]
key := b.deferKey()
zero := prog.Val(uintptr(0))
link := Expr{b.pthreadGetspecific(key).impl, prog.DeferPtr()}
jb := b.AllocaSigjmpBuf()
ptr := b.aggregateAlloca(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl)
deferData := Expr{ptr, prog.DeferPtr()}
b.pthreadSetspecific(key, deferData)
bitsPtr := b.FieldAddr(deferData, deferBits)
rethPtr := b.FieldAddr(deferData, deferRethrow)
rundPtr := b.FieldAddr(deferData, deferRunDefers)
argsPtr := b.FieldAddr(deferData, deferArgs)
czero := prog.IntVal(0, prog.CInt())
retval := b.Sigsetjmp(jb, czero)
if kind != DeferAlways {
panicBlk = self.MakeBlock()
} else {
blks = self.MakeBlocks(2)
next, panicBlk = blks[0], blks[1]
}
b.If(b.BinOp(token.EQL, retval, czero), next, panicBlk)
self.defer_ = &aDefer{
key: key,
data: deferData,
bitsPtr: bitsPtr,
rethPtr: rethPtr,
rundPtr: rundPtr,
argsPtr: argsPtr,
procBlk: procBlk,
panicBlk: panicBlk,
rundsNext: []BasicBlock{rethrowBlk},
}
b.SetBlockEx(rethrowBlk, AtEnd, false) // rethrow
b.Call(b.Pkg.rtFunc("Rethrow"), link)
b.Jump(self.recov)
if kind == DeferAlways {
b.SetBlockEx(next, AtEnd, false)
b.blk.last = next.last
}
}
return self.defer_
}
// DeferData returns the defer data (*runtime.Defer).
func (b Builder) DeferData() Expr {
key := b.deferKey()
return Expr{b.pthreadGetspecific(key).impl, b.Prog.DeferPtr()}
}
// Defer emits a defer instruction.
func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) {
if debugInstr {
logCall("Defer", fn, args)
}
var prog Program
var nextbit Expr
var self = b.getDefer(kind)
switch kind {
case DeferInCond:
prog = b.Prog
next := self.nextBit
self.nextBit++
bits := b.Load(self.bitsPtr)
nextbit = prog.Val(uintptr(1 << next))
b.Store(self.bitsPtr, b.BinOp(token.OR, bits, nextbit))
case DeferAlways:
// nothing to do
default:
panic("todo: DeferInLoop is not supported - " + b.Func.Name())
}
typ := b.saveDeferArgs(self, fn, args)
self.stmts = append(self.stmts, func(bits Expr) {
switch kind {
case DeferInCond:
zero := prog.Val(uintptr(0))
has := b.BinOp(token.NEQ, b.BinOp(token.AND, bits, nextbit), zero)
b.IfThen(has, func() {
b.callDefer(self, typ, fn, args)
})
case DeferAlways:
b.callDefer(self, typ, fn, args)
}
})
}
/*
type node struct {
prev *node
fn func()
args ...
}
// push
defer.Args = &node{defer.Args,fn,args...}
// pop
node := defer.Args
defer.Args = node.prev
free(node)
*/
func (b Builder) saveDeferArgs(self *aDefer, fn Expr, args []Expr) Type {
if fn.kind != vkClosure && len(args) == 0 {
return nil
}
prog := b.Prog
offset := 1
if fn.kind == vkClosure {
offset++
}
typs := make([]Type, len(args)+offset)
flds := make([]llvm.Value, len(args)+offset)
typs[0] = prog.VoidPtr()
flds[0] = b.Load(self.argsPtr).impl
if offset == 2 {
typs[1] = fn.Type
flds[1] = fn.impl
}
for i, arg := range args {
typs[i+offset] = arg.Type
flds[i+offset] = arg.impl
}
typ := prog.Struct(typs...)
ptr := Expr{b.aggregateMalloc(typ, flds...), prog.VoidPtr()}
b.Store(self.argsPtr, ptr)
return typ
}
func (b Builder) callDefer(self *aDefer, typ Type, fn Expr, args []Expr) {
if typ == nil {
b.Call(fn, args...)
return
}
prog := b.Prog
ptr := b.Load(self.argsPtr)
data := b.Load(Expr{ptr.impl, prog.Pointer(typ)})
offset := 1
b.Store(self.argsPtr, Expr{b.getField(data, 0).impl, prog.VoidPtr()})
if fn.kind == vkClosure {
fn = b.getField(data, 1)
offset++
}
for i := 0; i < len(args); i++ {
args[i] = b.getField(data, i+offset)
}
b.Call(fn, args...)
b.free(ptr)
}
// RunDefers emits instructions to run deferred instructions.
func (b Builder) RunDefers() {
self := b.getDefer(DeferInCond)
if self == nil {
return
}
blk := b.Func.MakeBlock()
self.rundsNext = append(self.rundsNext, blk)
b.Store(self.rundPtr, blk.Addr())
b.Jump(self.procBlk)
b.SetBlockEx(blk, AtEnd, false)
b.blk.last = blk.last
}
func (p Function) endDefer(b Builder) {
self := p.defer_
if self == nil {
return
}
nexts := self.rundsNext
if len(nexts) == 0 {
return
}
rethrowBlk := nexts[0]
procBlk := self.procBlk
panicBlk := self.panicBlk
rethPtr := self.rethPtr
rundPtr := self.rundPtr
bitsPtr := self.bitsPtr
stmts := self.stmts
n := len(stmts)
rethsNext := make([]BasicBlock, n+1)
blks := p.MakeBlocks(n - 1)
copy(rethsNext[1:], blks)
rethsNext[0] = rethrowBlk
rethsNext[n] = procBlk
for i := n - 1; i >= 0; i-- {
rethNext := rethsNext[i]
b.SetBlockEx(rethsNext[i+1], AtEnd, true)
b.Store(rethPtr, rethNext.Addr())
stmts[i](b.Load(bitsPtr))
if i != 0 {
b.Jump(rethNext)
}
}
link := b.getField(b.Load(self.data), deferLink)
b.pthreadSetspecific(self.key, link)
b.IndirectJump(b.Load(rundPtr), nexts)
b.SetBlockEx(panicBlk, AtEnd, false) // panicBlk: exec runDefers and rethrow
b.Store(rundPtr, rethrowBlk.Addr())
b.IndirectJump(b.Load(rethPtr), rethsNext)
}
// -----------------------------------------------------------------------------
// Unreachable emits an unreachable instruction.
func (b Builder) Unreachable() {
b.impl.CreateUnreachable()
}
// Recover emits a recover instruction.
func (b Builder) Recover() Expr {
if debugInstr {
log.Println("Recover")
}
// TODO(xsw): recover can't be a function call in Go
return b.Call(b.Pkg.rtFunc("Recover"))
}
// Panic emits a panic instruction.
func (b Builder) Panic(v Expr) {
b.Call(b.Pkg.rtFunc("Panic"), v)
b.Unreachable() // TODO: func supports noreturn attribute
}
// -----------------------------------------------------------------------------

1401
ssa/expr.go Normal file

File diff suppressed because it is too large Load Diff

178
ssa/goroutine.go Normal file
View File

@@ -0,0 +1,178 @@
/*
* 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 ssa
import (
"go/token"
"go/types"
"strconv"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
// func(c.Pointer) c.Pointer
func (p Program) tyRoutine() *types.Signature {
if p.routineTy == nil {
paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)
params := types.NewTuple(paramPtr)
p.routineTy = types.NewSignatureType(nil, nil, nil, params, params, false)
}
return p.routineTy
}
func (b Builder) pthreadCreate(pp, attr, routine, arg Expr) Expr {
fn := b.Pkg.rtFunc("CreateThread")
return b.Call(fn, pp, attr, routine, arg)
}
// -----------------------------------------------------------------------------
// The Go instruction creates a new goroutine and calls the specified
// function within it.
//
// Example printed form:
//
// go println(t0, t1)
// go t3()
// go invoke t5.Println(...t6)
func (b Builder) Go(fn Expr, args ...Expr) {
if debugInstr {
logCall("Go", fn, args)
}
prog := b.Prog
pkg := b.Pkg
var offset int
if fn.kind != vkBuiltin {
offset = 1
}
typs := make([]Type, len(args)+offset)
flds := make([]llvm.Value, len(args)+offset)
if offset == 1 {
typs[0] = fn.Type
flds[0] = fn.impl
}
for i, arg := range args {
typs[i+offset] = arg.Type
flds[i+offset] = arg.impl
}
t := prog.Struct(typs...)
voidPtr := prog.VoidPtr()
data := Expr{b.aggregateMalloc(t, flds...), voidPtr}
size := prog.SizeOf(voidPtr)
pthd := b.Alloca(prog.IntVal(uint64(size), prog.Uintptr()))
b.pthreadCreate(pthd, prog.Nil(voidPtr), pkg.routine(t, fn, len(args)), data)
}
func (p Package) routineName() string {
p.iRoutine++
return p.Path() + "._llgo_routine$" + strconv.Itoa(p.iRoutine)
}
func (p Package) routine(t Type, fn Expr, n int) Expr {
prog := p.Prog
routine := p.NewFunc(p.routineName(), prog.tyRoutine(), InC)
b := routine.MakeBody(1)
param := routine.Param(0)
data := Expr{llvm.CreateLoad(b.impl, t.ll, param.impl), t}
args := make([]Expr, n)
var offset int
if fn.kind != vkBuiltin {
fn = b.getField(data, 0)
offset = 1
}
for i := 0; i < n; i++ {
args[i] = b.getField(data, i+offset)
}
b.Call(fn, args...)
b.free(param)
b.Return(prog.Nil(prog.VoidPtr()))
return routine.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)
}
// -----------------------------------------------------------------------------

340
ssa/interface.go Normal file
View File

@@ -0,0 +1,340 @@
/*
* 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 ssa
import (
"go/constant"
"go/token"
"go/types"
"log"
"github.com/goplus/llgo/ssa/abi"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
// unsafeEface(t *abi.Type, data unsafe.Pointer) Eface
func (b Builder) unsafeEface(t, data llvm.Value) llvm.Value {
return aggregateValue(b.impl, b.Prog.rtEface(), t, data)
}
// unsafeIface(itab *runtime.Itab, data unsafe.Pointer) Eface
func (b Builder) unsafeIface(itab, data llvm.Value) llvm.Value {
return aggregateValue(b.impl, b.Prog.rtIface(), itab, data)
}
// func NewItab(tintf *InterfaceType, typ *Type) *runtime.Itab
func (b Builder) newItab(tintf, typ Expr) Expr {
return b.Call(b.Pkg.rtFunc("NewItab"), tintf, typ)
}
func (b Builder) unsafeInterface(rawIntf *types.Interface, t Expr, data llvm.Value) llvm.Value {
if rawIntf.Empty() {
return b.unsafeEface(t.impl, data)
}
tintf := b.abiType(rawIntf)
itab := b.newItab(tintf, t)
return b.unsafeIface(itab.impl, data)
}
func iMethodOf(rawIntf *types.Interface, name string) int {
n := rawIntf.NumMethods()
for i := 0; i < n; i++ {
m := rawIntf.Method(i)
if m.Name() == name {
// TODO(xsw): check signature
return i
}
}
return -1
}
// Imethod returns closure of an interface method.
func (b Builder) Imethod(intf Expr, method *types.Func) Expr {
prog := b.Prog
rawIntf := intf.raw.Type.Underlying().(*types.Interface)
tclosure := prog.Type(method.Type(), InGo)
i := iMethodOf(rawIntf, method.Name())
data := b.InlineCall(b.Pkg.rtFunc("IfacePtrData"), intf)
impl := intf.impl
itab := Expr{b.faceItab(impl), prog.VoidPtrPtr()}
pfn := b.Advance(itab, prog.IntVal(uint64(i+3), prog.Int()))
fn := b.Load(pfn)
ret := b.aggregateValue(tclosure, fn.impl, data.impl)
return ret
}
// -----------------------------------------------------------------------------
// MakeInterface constructs an instance of an interface type from a
// value of a concrete type.
//
// Use Program.MethodSets.MethodSet(X.Type()) to find the method-set
// of X, and Program.MethodValue(m) to find the implementation of a method.
//
// To construct the zero value of an interface type T, use:
//
// NewConst(constant.MakeNil(), T, pos)
//
// Example printed form:
//
// t1 = make interface{} <- int (42:int)
// t2 = make Stringer <- t0
func (b Builder) MakeInterface(tinter Type, x Expr) (ret Expr) {
rawIntf := tinter.raw.Type.Underlying().(*types.Interface)
if debugInstr {
log.Printf("MakeInterface %v, %v\n", rawIntf, x.impl)
}
if x.kind == vkFuncDecl {
typ := b.Prog.Type(x.raw.Type, InGo)
x = checkExpr(x, typ.raw.Type, b)
}
prog := b.Prog
typ := x.Type
tabi := b.abiType(typ.raw.Type)
kind, _, lvl := abi.DataKindOf(typ.raw.Type, 0, prog.is32Bits)
switch kind {
case abi.Indirect:
vptr := b.AllocU(typ)
b.Store(vptr, x)
return Expr{b.unsafeInterface(rawIntf, tabi, vptr.impl), tinter}
}
ximpl := x.impl
if lvl > 0 {
ximpl = extractVal(b.impl, ximpl, lvl)
}
var u llvm.Value
switch kind {
case abi.Pointer:
return Expr{b.unsafeInterface(rawIntf, tabi, ximpl), tinter}
case abi.Integer:
tu := prog.Uintptr()
u = llvm.CreateIntCast(b.impl, ximpl, tu.ll)
case abi.BitCast:
tu := prog.Uintptr()
if b.Prog.td.TypeAllocSize(typ.ll) < b.Prog.td.TypeAllocSize(tu.ll) {
u = llvm.CreateBitCast(b.impl, ximpl, prog.Uint32().ll)
} else {
u = llvm.CreateBitCast(b.impl, ximpl, tu.ll)
}
default:
panic("todo")
}
data := llvm.CreateIntToPtr(b.impl, u, prog.tyVoidPtr())
return Expr{b.unsafeInterface(rawIntf, tabi, data), tinter}
}
func (b Builder) valFromData(typ Type, data llvm.Value) Expr {
prog := b.Prog
kind, real, lvl := abi.DataKindOf(typ.raw.Type, 0, prog.is32Bits)
switch kind {
case abi.Indirect:
impl := b.impl
tll := typ.ll
tptr := llvm.PointerType(tll, 0)
ptr := llvm.CreatePointerCast(impl, data, tptr)
return Expr{llvm.CreateLoad(impl, tll, ptr), typ}
}
t := typ
if lvl > 0 {
t = prog.rawType(real)
}
switch kind {
case abi.Pointer:
return b.buildVal(typ, data, lvl)
case abi.Integer:
x := castUintptr(b, data, prog.Uintptr())
return b.buildVal(typ, castInt(b, x, t), lvl)
case abi.BitCast:
x := castUintptr(b, data, prog.Uintptr())
if int(prog.SizeOf(t)) != prog.PointerSize() {
x = castInt(b, x, prog.Int32())
}
return b.buildVal(typ, llvm.CreateBitCast(b.impl, x, t.ll), lvl)
}
panic("todo")
}
func extractVal(b llvm.Builder, val llvm.Value, lvl int) llvm.Value {
for lvl > 0 {
// TODO(xsw): check array support
val = llvm.CreateExtractValue(b, val, 0)
lvl--
}
return val
}
func (b Builder) buildVal(typ Type, val llvm.Value, lvl int) Expr {
if lvl == 0 {
return Expr{val, typ}
}
switch t := typ.raw.Type.Underlying().(type) {
case *types.Struct:
telem := b.Prog.rawType(t.Field(0).Type())
elem := b.buildVal(telem, val, lvl-1)
return Expr{aggregateValue(b.impl, typ.ll, elem.impl), typ}
case *types.Array:
telem := b.Prog.rawType(t.Elem())
elem := b.buildVal(telem, val, lvl-1)
return Expr{llvm.ConstArray(typ.ll, []llvm.Value{elem.impl}), typ}
}
panic("todo")
}
// The TypeAssert instruction tests whether interface value X has type
// AssertedType.
//
// If !CommaOk, on success it returns v, the result of the conversion
// (defined below); on failure it panics.
//
// If CommaOk: on success it returns a pair (v, true) where v is the
// result of the conversion; on failure it returns (z, false) where z
// is AssertedType's zero value. The components of the pair must be
// accessed using the Extract instruction.
//
// If Underlying: tests whether interface value X has the underlying
// type AssertedType.
//
// If AssertedType is a concrete type, TypeAssert checks whether the
// dynamic type in interface X is equal to it, and if so, the result
// of the conversion is a copy of the value in the interface.
//
// If AssertedType is an interface, TypeAssert checks whether the
// dynamic type of the interface is assignable to it, and if so, the
// result of the conversion is a copy of the interface value X.
// If AssertedType is a superinterface of X.Type(), the operation will
// fail iff the operand is nil. (Contrast with ChangeInterface, which
// performs no nil-check.)
//
// Type() reflects the actual type of the result, possibly a
// 2-types.Tuple; AssertedType is the asserted type.
//
// Depending on the TypeAssert's purpose, Pos may return:
// - the ast.CallExpr.Lparen of an explicit T(e) conversion;
// - the ast.TypeAssertExpr.Lparen of an explicit e.(T) operation;
// - the ast.CaseClause.Case of a case of a type-switch statement;
// - the Ident(m).NamePos of an interface method value i.m
// (for which TypeAssert may be used to effect the nil check).
//
// Example printed form:
//
// t1 = typeassert t0.(int)
// t3 = typeassert,ok t2.(T)
func (b Builder) TypeAssert(x Expr, assertedTyp Type, commaOk bool) Expr {
if debugInstr {
log.Printf("TypeAssert %v, %v, %v\n", x.impl, assertedTyp.raw.Type, commaOk)
}
tx := b.faceAbiType(x)
tabi := b.abiType(assertedTyp.raw.Type)
var eq Expr
var val func() Expr
if x.RawType() == assertedTyp.RawType() {
eq = b.Const(constant.MakeBool(true), b.Prog.Bool())
val = func() Expr { return x }
} else {
if rawIntf, ok := assertedTyp.raw.Type.Underlying().(*types.Interface); ok {
eq = b.InlineCall(b.Pkg.rtFunc("Implements"), tabi, tx)
val = func() Expr { return Expr{b.unsafeInterface(rawIntf, tx, b.faceData(x.impl)), assertedTyp} }
} else {
eq = b.BinOp(token.EQL, tx, tabi)
val = func() Expr { return b.valFromData(assertedTyp, b.faceData(x.impl)) }
}
}
if commaOk {
prog := b.Prog
t := prog.Struct(assertedTyp, prog.Bool())
blks := b.Func.MakeBlocks(3)
b.If(eq, blks[0], blks[1])
b.SetBlockEx(blks[2], AtEnd, false)
phi := b.Phi(t)
phi.AddIncoming(b, blks[:2], func(i int, blk BasicBlock) Expr {
b.SetBlockEx(blk, AtEnd, false)
if i == 0 {
valTrue := aggregateValue(b.impl, t.ll, val().impl, prog.BoolVal(true).impl)
b.Jump(blks[2])
return Expr{valTrue, t}
}
zero := prog.Zero(assertedTyp)
valFalse := aggregateValue(b.impl, t.ll, zero.impl, prog.BoolVal(false).impl)
b.Jump(blks[2])
return Expr{valFalse, t}
})
b.SetBlockEx(blks[2], AtEnd, false)
b.blk.last = blks[2].last
return phi.Expr
}
blks := b.Func.MakeBlocks(2)
b.If(eq, blks[0], blks[1])
b.SetBlockEx(blks[1], AtEnd, false)
b.Panic(b.MakeInterface(b.Prog.Any(), b.Str("type assertion "+x.RawType().String()+" -> "+assertedTyp.RawType().String()+" failed")))
b.SetBlockEx(blks[0], AtEnd, false)
b.blk.last = blks[0].last
return val()
}
// ChangeInterface constructs a value of one interface type from a
// value of another interface type known to be assignable to it.
// This operation cannot fail.
//
// Pos() returns the ast.CallExpr.Lparen if the instruction arose from
// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the
// instruction arose from an explicit e.(T) operation; or token.NoPos
// otherwise.
//
// Example printed form:
//
// t1 = change interface interface{} <- I (t0)
func (b Builder) ChangeInterface(typ Type, x Expr) (ret Expr) {
rawIntf := typ.raw.Type.Underlying().(*types.Interface)
tabi := b.faceAbiType(x)
data := b.faceData(x.impl)
return Expr{b.unsafeInterface(rawIntf, tabi, data), typ}
}
// -----------------------------------------------------------------------------
/*
// InterfaceData returns the data pointer of an interface.
func (b Builder) InterfaceData(x Expr) Expr {
if debugInstr {
log.Printf("InterfaceData %v\n", x.impl)
}
return Expr{b.faceData(x.impl), b.Prog.VoidPtr()}
}
*/
func (b Builder) faceData(x llvm.Value) llvm.Value {
return llvm.CreateExtractValue(b.impl, x, 1)
}
func (b Builder) faceItab(x llvm.Value) llvm.Value {
return llvm.CreateExtractValue(b.impl, x, 0)
}
func (b Builder) faceAbiType(x Expr) Expr {
if x.kind == vkIface {
return b.InlineCall(b.Pkg.rtFunc("IfaceType"), x)
}
typ := llvm.CreateExtractValue(b.impl, x.impl, 0)
return Expr{typ, b.Prog.AbiTypePtr()}
}
// -----------------------------------------------------------------------------

367
ssa/memory.go Normal file
View File

@@ -0,0 +1,367 @@
/*
* 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 ssa
import (
"go/token"
"go/types"
"log"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
func (b Builder) aggregateAllocU(t Type, flds ...llvm.Value) llvm.Value {
prog := b.Prog
size := prog.SizeOf(t)
ptr := b.allocUninited(prog.IntVal(size, prog.Uintptr())).impl
aggregateInit(b.impl, ptr, t.ll, flds...)
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)
ptr := b.malloc(prog.IntVal(size, prog.Uintptr())).impl
aggregateInit(b.impl, ptr, t.ll, flds...)
return ptr
}
// aggregateValue yields the value of the aggregate X with the fields
func (b Builder) aggregateValue(t Type, flds ...llvm.Value) Expr {
return Expr{aggregateValue(b.impl, t.ll, flds...), t}
}
func aggregateValue(b llvm.Builder, tll llvm.Type, flds ...llvm.Value) llvm.Value {
agg := llvm.Undef(tll)
for i, fld := range flds {
agg = b.CreateInsertValue(agg, fld, i, "")
}
return agg
}
func aggregateInit(b llvm.Builder, ptr llvm.Value, tll llvm.Type, flds ...llvm.Value) {
for i, fld := range flds {
b.CreateStore(fld, llvm.CreateStructGEP(b, tll, ptr, i))
}
}
/*
func (b Builder) dupMalloc(v Expr) Expr {
prog := b.Prog
n := prog.SizeOf(v.Type)
tptr := prog.Pointer(v.Type)
ptr := b.malloc(prog.Val(uintptr(n))).impl
b.Store(Expr{ptr, tptr}, v)
return Expr{ptr, tptr}
}
*/
// -----------------------------------------------------------------------------
// The Alloc instruction reserves space for a variable of the given type,
// zero-initializes it, and yields its address.
//
// If heap is false, Alloc zero-initializes the same local variable in
// the call frame and returns its address; in this case the Alloc must
// be present in Function.Locals. We call this a "local" alloc.
//
// If heap is true, Alloc allocates a new zero-initialized variable
// each time the instruction is executed. We call this a "new" alloc.
//
// When Alloc is applied to a channel, map or slice type, it returns
// the address of an uninitialized (nil) reference of that kind; store
// the result of MakeSlice, MakeMap or MakeChan in that location to
// instantiate these types.
//
// Example printed form:
//
// t0 = local int
// t1 = new int
func (b Builder) Alloc(elem Type, heap bool) (ret Expr) {
if debugInstr {
log.Printf("Alloc %v, %v\n", elem.RawType(), heap)
}
prog := b.Prog
pkg := b.Pkg
size := SizeOf(prog, elem)
if heap {
ret = b.InlineCall(pkg.rtFunc("AllocZ"), size)
} else {
ret = Expr{llvm.CreateAlloca(b.impl, elem.ll), prog.VoidPtr()}
ret.impl = b.zeroinit(ret, size).impl
}
ret.Type = prog.Pointer(elem)
return
}
// AllocU allocates uninitialized space for n*sizeof(elem) bytes.
func (b Builder) AllocU(elem Type, n ...int64) (ret Expr) {
prog := b.Prog
size := SizeOf(prog, elem, n...)
return Expr{b.allocUninited(size).impl, prog.Pointer(elem)}
}
func (b Builder) allocUninited(size Expr) (ret Expr) {
return b.InlineCall(b.Pkg.rtFunc("AllocU"), size)
}
// AllocZ allocates zero initialized space for n bytes.
func (b Builder) AllocZ(n Expr) (ret Expr) {
return b.InlineCall(b.Pkg.rtFunc("AllocZ"), n)
}
// Alloca allocates uninitialized space for n bytes.
func (b Builder) Alloca(n Expr) (ret Expr) {
if debugInstr {
log.Printf("Alloca %v\n", n.impl)
}
prog := b.Prog
telem := prog.tyInt8()
ret.impl = llvm.CreateArrayAlloca(b.impl, telem, n.impl)
ret.Type = prog.VoidPtr()
return
}
func (b Builder) AllocaT(t Type) (ret Expr) {
if debugInstr {
log.Printf("AllocaT %v\n", t.RawType())
}
prog := b.Prog
ret.impl = llvm.CreateAlloca(b.impl, t.ll)
ret.Type = prog.Pointer(t)
return
}
/* TODO(xsw):
// AllocaU allocates uninitialized space for n*sizeof(elem) bytes.
func (b Builder) AllocaU(elem Type, n ...int64) (ret Expr) {
prog := b.Prog
size := SizeOf(prog, elem, n...)
return Expr{b.Alloca(size).impl, prog.Pointer(elem)}
}
*/
// AllocaCStr allocates space for copy it from a Go string.
func (b Builder) AllocaCStr(gostr Expr) (ret Expr) {
if debugInstr {
log.Printf("AllocaCStr %v\n", gostr.impl)
}
n := b.StringLen(gostr)
n1 := b.BinOp(token.ADD, n, b.Prog.Val(1))
cstr := b.Alloca(n1)
return b.InlineCall(b.Pkg.rtFunc("CStrCopy"), cstr, gostr)
}
// func allocaCStrs(strs []string, endWithNil bool) **int8
func (b Builder) AllocaCStrs(strs Expr, endWithNil bool) (cstrs Expr) {
if debugInstr {
log.Printf("AllocaCStrs %v, %v\n", strs.impl, endWithNil)
}
prog := b.Prog
n := b.SliceLen(strs)
n1 := n
if endWithNil {
n1 = b.BinOp(token.ADD, n, prog.Val(1))
}
tcstr := prog.CStr()
cstrs = b.ArrayAlloca(tcstr, n1)
b.Times(n, func(i Expr) {
pstr := b.IndexAddr(strs, i)
s := b.Load(pstr)
b.Store(b.Advance(cstrs, i), b.AllocaCStr(s))
})
if endWithNil {
b.Store(b.Advance(cstrs, n), prog.Nil(tcstr))
}
return
}
// -----------------------------------------------------------------------------
func (p Program) tyMalloc() *types.Signature {
if p.mallocTy == nil {
paramSize := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type)
paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)
params := types.NewTuple(paramSize)
results := types.NewTuple(paramPtr)
p.mallocTy = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.mallocTy
}
func (p Program) tyFree() *types.Signature {
if p.freeTy == nil {
paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)
params := types.NewTuple(paramPtr)
p.freeTy = types.NewSignatureType(nil, nil, nil, params, nil, false)
}
return p.freeTy
}
func (p Program) tyMemsetInline() *types.Signature {
if p.memsetInlineTy == nil {
paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)
paramInt := types.NewParam(token.NoPos, nil, "", p.Byte().raw.Type)
paramSize := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type)
paramBool := types.NewParam(token.NoPos, nil, "", p.Bool().raw.Type)
params := types.NewTuple(paramPtr, paramInt, paramSize, paramBool)
p.memsetInlineTy = types.NewSignatureType(nil, nil, nil, params, nil, false)
}
return p.memsetInlineTy
}
func (b Builder) malloc(size Expr) Expr {
fn := b.Pkg.cFunc("malloc", b.Prog.tyMalloc())
return b.Call(fn, size)
}
func (b Builder) free(ptr Expr) Expr {
fn := b.Pkg.cFunc("free", b.Prog.tyFree())
return b.Call(fn, ptr)
}
// declare void @llvm.memset.inline.p0.p0i8.i32(ptr <dest>, i8 <val>, i32 <len>, i1 <isvolatile>)
// declare void @llvm.memset.inline.p0.p0.i64(ptr <dest>, i8 <val>, i64 <len>, i1 <isvolatile>)
func (b Builder) memset(ptr, val, len, isvolatile Expr) Expr {
fn := b.Pkg.cFunc("llvm.memset", b.Prog.tyMemsetInline())
b.Call(fn, ptr, val, len, isvolatile)
return ptr
}
func (b Builder) zeroinit(ptr, size Expr) Expr {
return b.memset(ptr, b.Prog.IntVal(0, b.Prog.Byte()), size, b.Prog.Val(false))
}
// -----------------------------------------------------------------------------
// ArrayAlloca reserves space for an array of n elements of type telem.
func (b Builder) ArrayAlloca(telem Type, n Expr) (ret Expr) {
if debugInstr {
log.Printf("ArrayAlloca %v, %v\n", telem.raw.Type, n.impl)
}
ret.impl = llvm.CreateArrayAlloca(b.impl, telem.ll, n.impl)
ret.Type = b.Prog.Pointer(telem)
return
}
/* TODO(xsw):
// ArrayAlloc allocates zero initialized space for an array of n elements of type telem.
func (b Builder) ArrayAlloc(telem Type, n Expr) (ret Expr) {
prog := b.Prog
elemSize := SizeOf(prog, telem)
size := b.BinOp(token.MUL, n, elemSize)
ret.impl = b.AllocZ(size).impl
ret.Type = prog.Pointer(telem)
return
}
*/
// -----------------------------------------------------------------------------
// AtomicOp is an atomic operation.
type AtomicOp = llvm.AtomicRMWBinOp
const (
OpXchg = llvm.AtomicRMWBinOpXchg
OpAdd = llvm.AtomicRMWBinOpAdd
OpSub = llvm.AtomicRMWBinOpSub
OpAnd = llvm.AtomicRMWBinOpAnd
OpNand = llvm.AtomicRMWBinOpNand
OpOr = llvm.AtomicRMWBinOpOr
OpXor = llvm.AtomicRMWBinOpXor
OpMax = llvm.AtomicRMWBinOpMax
OpMin = llvm.AtomicRMWBinOpMin
OpUMax = llvm.AtomicRMWBinOpUMax
OpUMin = llvm.AtomicRMWBinOpUMin
)
// Atomic performs an atomic operation on the memory location pointed to by ptr.
func (b Builder) Atomic(op AtomicOp, ptr, val Expr) Expr {
if debugInstr {
log.Printf("Atomic %v, %v, %v\n", op, ptr.impl, val.impl)
}
t := b.Prog.Elem(ptr.Type)
val = b.ChangeType(t, val)
ret := b.impl.CreateAtomicRMW(op, ptr.impl, val.impl, llvm.AtomicOrderingSequentiallyConsistent, false)
return Expr{ret, t}
}
// AtomicCmpXchg performs an atomic compare-and-swap operation on the memory location pointed to by ptr.
func (b Builder) AtomicCmpXchg(ptr, old, new Expr) Expr {
if debugInstr {
log.Printf("AtomicCmpXchg %v, %v, %v\n", ptr.impl, old.impl, new.impl)
}
prog := b.Prog
t := prog.Elem(ptr.Type)
old = b.ChangeType(t, old)
new = b.ChangeType(t, new)
ret := b.impl.CreateAtomicCmpXchg(
ptr.impl, old.impl, new.impl,
llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, false)
return Expr{ret, prog.Struct(t, prog.Bool())}
}
// Load returns the value at the pointer ptr.
func (b Builder) Load(ptr Expr) Expr {
if debugInstr {
log.Printf("Load %v\n", ptr.impl)
}
if ptr.kind == vkPyVarRef {
return b.pyLoad(ptr)
}
telem := b.Prog.Elem(ptr.Type)
return Expr{llvm.CreateLoad(b.impl, telem.ll, ptr.impl), telem}
}
// Store stores val at the pointer ptr.
func (b Builder) Store(ptr, val Expr) Expr {
raw := ptr.raw.Type
if debugInstr {
log.Printf("Store %v, %v, %v\n", raw, ptr.impl, val.impl)
}
val = checkExpr(val, raw.(*types.Pointer).Elem(), b)
return Expr{b.impl.CreateStore(val.impl, ptr.impl), b.Prog.Void()}
}
// Advance returns the pointer ptr advanced by offset.
func (b Builder) Advance(ptr Expr, offset Expr) Expr {
if debugInstr {
log.Printf("Advance %v, %v\n", ptr.impl, offset.impl)
}
var elem llvm.Type
var prog = b.Prog
switch t := ptr.raw.Type.(type) {
case *types.Basic: // void
elem = prog.tyInt8()
default:
elem = prog.rawType(t.(*types.Pointer).Elem()).ll
}
ret := llvm.CreateGEP(b.impl, elem, ptr.impl, []llvm.Value{offset.impl})
return Expr{ret, ptr.Type}
}
// -----------------------------------------------------------------------------

809
ssa/package.go Normal file
View File

@@ -0,0 +1,809 @@
/*
* 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 ssa
import (
"fmt"
"go/token"
"go/types"
"runtime"
"strconv"
"unsafe"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/ssa/abi"
"github.com/goplus/llvm"
"golang.org/x/tools/go/types/typeutil"
)
const (
PkgPython = "github.com/goplus/lib/py"
PkgRuntime = env.LLGoRuntimePkg + "/internal/runtime"
)
// -----------------------------------------------------------------------------
type dbgFlags = int
const (
DbgFlagInstruction dbgFlags = 1 << iota
DbgFlagAll = DbgFlagInstruction
)
var (
debugInstr bool
)
// SetDebug sets debug flags.
func SetDebug(dbgFlags dbgFlags) {
debugInstr = (dbgFlags & DbgFlagInstruction) != 0
}
// -----------------------------------------------------------------------------
// InitFlags is a set of flags for initializing the LLVM library.
type InitFlags int
const (
InitNativeTarget InitFlags = 1 << iota
InitAllTargets
InitAllTargetInfos
InitAllTargetMCs
InitNativeAsmPrinter
InitAllAsmPrinters
InitAllAsmParsers
InitNative = InitNativeTarget | InitNativeAsmPrinter
InitAll = InitAllTargets | InitAllAsmParsers | InitAllAsmPrinters | InitAllTargetInfos | InitAllTargetMCs
)
// Initialize initializes the LLVM library.
func Initialize(flags InitFlags) {
if flags&InitAllTargetInfos != 0 {
llvm.InitializeAllTargetInfos()
}
if flags&InitAllTargets != 0 {
llvm.InitializeAllTargets()
}
if flags&InitAllTargetMCs != 0 {
llvm.InitializeAllTargetMCs()
}
if flags&InitAllAsmParsers != 0 {
llvm.InitializeAllAsmParsers()
}
if flags&InitAllAsmPrinters != 0 {
llvm.InitializeAllAsmPrinters()
}
if flags&InitNativeTarget != 0 {
llvm.InitializeNativeTarget()
}
if flags&InitNativeAsmPrinter != 0 {
llvm.InitializeNativeAsmPrinter()
}
}
// -----------------------------------------------------------------------------
type aProgram struct {
ctx llvm.Context
typs typeutil.Map // rawType -> Type
sizes types.Sizes // provided by Go compiler
gocvt goTypes
patchType func(types.Type) types.Type
fnsCompiled map[string]bool
rt *types.Package
rtget func() *types.Package
py *types.Package
pyget func() *types.Package
target *Target
td llvm.TargetData
// tm llvm.TargetMachine
named map[string]llvm.Type
fnnamed map[string]int
intType llvm.Type
int1Type llvm.Type
int8Type llvm.Type
int16Type llvm.Type
int32Type llvm.Type
int64Type llvm.Type
voidType llvm.Type
voidPtrTy llvm.Type
c64Type llvm.Type
c128Type llvm.Type
rtStringTy llvm.Type
rtEfaceTy llvm.Type
rtIfaceTy llvm.Type
rtSliceTy llvm.Type
rtMapTy llvm.Type
rtChanTy llvm.Type
anyTy Type
voidTy Type
voidPtr Type
voidPPtr Type
boolTy Type
cstrTy Type
cintTy Type
cintPtr Type
stringTy Type
uintptrTy Type
intTy Type
uintTy Type
f64Ty Type
f32Ty Type
c128Ty Type
c64Ty Type
byteTy Type
i32Ty Type
u32Ty Type
i64Ty Type
u64Ty Type
pyObjPtr Type
pyObjPPtr Type
abiTyPtr Type
abiTyPPtr Type
deferTy Type
deferPtr Type
pyImpTy *types.Signature
pyNewList *types.Signature
pyListSetI *types.Signature
pyNewTuple *types.Signature
pyTupleSetI *types.Signature
floatFromDbl *types.Signature
callNoArgs *types.Signature
callOneArg *types.Signature
callFOArgs *types.Signature
loadPyModS *types.Signature
getAttrStr *types.Signature
pyUniStr *types.Signature
mallocTy *types.Signature
freeTy *types.Signature
memsetInlineTy *types.Signature
createKeyTy *types.Signature
getSpecTy *types.Signature
setSpecTy *types.Signature
routineTy *types.Signature
destructTy *types.Signature
sigsetjmpTy *types.Signature
sigljmpTy *types.Signature
printfTy *types.Signature
paramObjPtr_ *types.Var
linkname map[string]string // pkgPath.nameInPkg => linkname
ptrSize int
is32Bits bool
}
// A Program presents a program.
type Program = *aProgram
// NewProgram creates a new program.
func NewProgram(target *Target) Program {
if target == nil {
target = &Target{
GOOS: runtime.GOOS,
GOARCH: runtime.GOARCH,
}
}
ctx := llvm.NewContext()
td := target.targetData() // TODO(xsw): target config
fnsCompiled := make(map[string]bool)
/*
arch := target.GOARCH
if arch == "" {
arch = runtime.GOARCH
}
sizes := types.SizesFor("gc", arch)
// TODO(xsw): Finalize may cause panic, so comment it.
ctx.Finalize()
*/
is32Bits := (td.PointerSize() == 4 || target.GOARCH == "x86") // TODO(xsw): remove temp code
return &aProgram{
ctx: ctx, gocvt: newGoTypes(), fnsCompiled: fnsCompiled,
target: target, td: td, is32Bits: is32Bits,
ptrSize: td.PointerSize(), named: make(map[string]llvm.Type), fnnamed: make(map[string]int),
linkname: make(map[string]string),
}
}
func (p Program) SetPatch(patchType func(types.Type) types.Type) {
p.patchType = patchType
}
func (p Program) patch(typ types.Type) types.Type {
if p.patchType != nil {
return p.patchType(typ)
}
return typ
}
// SetRuntime sets the runtime.
// Its type can be *types.Package or func() *types.Package.
func (p Program) SetRuntime(runtime any) {
switch v := runtime.(type) {
case *types.Package:
p.rt = v
case func() *types.Package:
p.rtget = v
}
}
func (p Program) SetTypeBackground(fullName string, bg Background) {
p.gocvt.typbg.Store(fullName, bg)
}
func (p Program) SetLinkname(name, link string) {
p.linkname[name] = link
}
func (p Program) Linkname(name string) (link string, ok bool) {
link, ok = p.linkname[name]
return
}
func (p Program) runtime() *types.Package {
if p.rt == nil {
p.rt = p.rtget()
}
return p.rt
}
// check generic function instantiation
func (p Program) FuncCompiled(name string) bool {
_, ok := p.fnsCompiled[name]
return ok
}
func (p Program) SetFuncCompiled(name string) {
p.fnsCompiled[name] = true
}
func (p Program) rtNamed(name string) *types.Named {
if rt := p.runtime(); rt != nil {
if rtScope := rt.Scope(); rtScope != nil {
if obj := rtScope.Lookup(name); obj != nil {
t := obj.Type()
for {
if alias, ok := t.(*types.Alias); ok {
t = types.Unalias(alias)
} else {
break
}
}
t, _ = p.gocvt.cvtNamed(t.(*types.Named))
return t.(*types.Named)
}
}
}
panic(fmt.Errorf("runtime type (%s) not found, install from pre-built package or set LLGO_ROOT", name))
}
func (p Program) rtType(name string) Type {
return p.rawType(p.rtNamed(name))
}
func (p Program) rtEface() llvm.Type {
if p.rtEfaceTy.IsNil() {
p.rtEfaceTy = p.rtType("Eface").ll
}
return p.rtEfaceTy
}
func (p Program) rtIface() llvm.Type {
if p.rtIfaceTy.IsNil() {
p.rtIfaceTy = p.rtType("Iface").ll
}
return p.rtIfaceTy
}
func (p Program) rtMap() llvm.Type {
if p.rtMapTy.IsNil() {
p.rtMapTy = p.rtType("Map").ll
}
return p.rtMapTy
}
func (p Program) rtSlice() llvm.Type {
if p.rtSliceTy.IsNil() {
p.rtSliceTy = p.rtType("Slice").ll
}
return p.rtSliceTy
}
func (p Program) rtString() llvm.Type {
if p.rtStringTy.IsNil() {
p.rtStringTy = p.rtType("String").ll
}
return p.rtStringTy
}
func (p Program) rtChan() llvm.Type {
if p.rtChanTy.IsNil() {
p.rtChanTy = p.rtType("Chan").ll
}
return p.rtChanTy
}
func (p Program) tyComplex64() llvm.Type {
if p.c64Type.IsNil() {
ctx := p.ctx
f32 := ctx.FloatType()
p.c64Type = ctx.StructType([]llvm.Type{f32, f32}, false)
}
return p.c64Type
}
func (p Program) tyComplex128() llvm.Type {
if p.c128Type.IsNil() {
ctx := p.ctx
f64 := ctx.DoubleType()
p.c128Type = ctx.StructType([]llvm.Type{f64, f64}, false)
}
return p.c128Type
}
// NewPackage creates a new package.
func (p Program) NewPackage(name, pkgPath string) Package {
mod := p.ctx.NewModule(pkgPath)
// TODO(xsw): Finalize may cause panic, so comment it.
// mod.Finalize()
gbls := make(map[string]Global)
fns := make(map[string]Function)
stubs := make(map[string]Function)
pyobjs := make(map[string]PyObjRef)
pymods := make(map[string]Global)
strs := make(map[string]llvm.Value)
chkabi := make(map[types.Type]bool)
glbDbgVars := make(map[Expr]bool)
// Don't need reset p.needPyInit here
// p.needPyInit = false
ret := &aPackage{
mod: mod, vars: gbls, fns: fns, stubs: stubs,
pyobjs: pyobjs, pymods: pymods, strs: strs,
chkabi: chkabi, Prog: p,
di: nil, cu: nil, glbDbgVars: glbDbgVars,
}
ret.abi.Init(pkgPath)
return ret
}
// Struct returns a struct type.
func (p Program) Struct(typs ...Type) Type {
els := make([]*types.Var, len(typs))
for i, t := range typs {
els[i] = types.NewParam(token.NoPos, nil, "_llgo_f"+strconv.Itoa(i), t.raw.Type)
}
return p.rawType(types.NewStruct(els, nil))
}
// Defer returns runtime.Defer type.
func (p Program) Defer() Type {
if p.deferTy == nil {
p.deferTy = p.rtType("Defer")
}
return p.deferTy
}
// DeferPtr returns *runtime.Defer type.
func (p Program) DeferPtr() Type {
if p.deferPtr == nil {
p.deferPtr = p.Pointer(p.Defer())
}
return p.deferPtr
}
// AbiTypePtr returns *abi.Type type.
func (p Program) AbiTypePtr() Type {
if p.abiTyPtr == nil {
p.abiTyPtr = p.rawType(types.NewPointer(p.rtNamed("Type")))
}
return p.abiTyPtr
}
// AbiTypePtrPtr returns **abi.Type type.
func (p Program) AbiTypePtrPtr() Type {
if p.abiTyPPtr == nil {
p.abiTyPPtr = p.Pointer(p.AbiTypePtr())
}
return p.abiTyPPtr
}
// Void returns void type.
func (p Program) Void() Type {
if p.voidTy == nil {
p.voidTy = &aType{p.tyVoid(), rawType{types.Typ[types.Invalid]}, vkInvalid}
}
return p.voidTy
}
// VoidPtr returns *void type.
func (p Program) VoidPtr() Type {
if p.voidPtr == nil {
p.voidPtr = p.rawType(types.Typ[types.UnsafePointer])
}
return p.voidPtr
}
// VoidPtrPtr returns **void type.
func (p Program) VoidPtrPtr() Type {
if p.voidPPtr == nil {
p.voidPPtr = p.rawType(types.NewPointer(types.Typ[types.UnsafePointer]))
}
return p.voidPPtr
}
// Bool returns bool type.
func (p Program) Bool() Type {
if p.boolTy == nil {
p.boolTy = p.rawType(types.Typ[types.Bool])
}
return p.boolTy
}
// CStr returns *int8 type.
func (p Program) CStr() Type {
if p.cstrTy == nil { // *int8
p.cstrTy = p.rawType(types.NewPointer(types.Typ[types.Int8]))
}
return p.cstrTy
}
// String returns string type.
func (p Program) String() Type {
if p.stringTy == nil {
p.stringTy = p.rawType(types.Typ[types.String])
}
return p.stringTy
}
// Any returns the any (empty interface) type.
func (p Program) Any() Type {
if p.anyTy == nil {
p.anyTy = p.rawType(tyAny)
}
return p.anyTy
}
/*
// Eface returns the empty interface type.
// It is equivalent to Any.
func (p Program) Eface() Type {
return p.Any()
}
*/
// 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
}
return p.cintTy
}
// Int returns int type.
func (p Program) Int() Type {
if p.intTy == nil {
p.intTy = p.rawType(types.Typ[types.Int])
}
return p.intTy
}
// Uint returns uint type.
func (p Program) Uint() Type {
if p.uintTy == nil {
p.uintTy = p.rawType(types.Typ[types.Uint])
}
return p.uintTy
}
// Uintptr returns uintptr type.
func (p Program) Uintptr() Type {
if p.uintptrTy == nil {
p.uintptrTy = p.rawType(types.Typ[types.Uintptr])
}
return p.uintptrTy
}
// Float64 returns float64 type.
func (p Program) Float64() Type {
if p.f64Ty == nil {
p.f64Ty = p.rawType(types.Typ[types.Float64])
}
return p.f64Ty
}
// Float32 returns float32 type.
func (p Program) Float32() Type {
if p.f32Ty == nil {
p.f32Ty = p.rawType(types.Typ[types.Float32])
}
return p.f32Ty
}
// Complex128 returns complex128 type.
func (p Program) Complex128() Type {
if p.c128Ty == nil {
p.c128Ty = p.rawType(types.Typ[types.Complex128])
}
return p.c128Ty
}
// Complex64 returns complex64 type.
func (p Program) Complex64() Type {
if p.c64Ty == nil {
p.c64Ty = p.rawType(types.Typ[types.Complex64])
}
return p.c64Ty
}
// Byte returns byte type.
func (p Program) Byte() Type {
if p.byteTy == nil {
p.byteTy = p.rawType(types.Typ[types.Byte])
}
return p.byteTy
}
// Int32 returns int32 type.
func (p Program) Int32() Type {
if p.i32Ty == nil {
p.i32Ty = p.rawType(types.Typ[types.Int32])
}
return p.i32Ty
}
// Uint32 returns uint32 type.
func (p Program) Uint32() Type {
if p.u32Ty == nil {
p.u32Ty = p.rawType(types.Typ[types.Uint32])
}
return p.u32Ty
}
// Int64 returns int64 type.
func (p Program) Int64() Type {
if p.i64Ty == nil {
p.i64Ty = p.rawType(types.Typ[types.Int64])
}
return p.i64Ty
}
// Uint64 returns uint64 type.
func (p Program) Uint64() Type {
if p.u64Ty == nil {
p.u64Ty = p.rawType(types.Typ[types.Uint64])
}
return p.u64Ty
}
// -----------------------------------------------------------------------------
// A Package is a single analyzed Go package containing Members for
// all package-level functions, variables, constants and types it
// declares. These may be accessed directly via Members, or via the
// type-specific accessor methods Func, Type, Var and Const.
//
// Members also contains entries for "init" (the synthetic package
// initializer) and "init#%d", the nth declared init function,
// and unspecified other things too.
type aPackage struct {
mod llvm.Module
abi abi.Builder
Prog Program
di diBuilder
cu CompilationUnit
glbDbgVars map[Expr]bool
vars map[string]Global
fns map[string]Function
stubs map[string]Function
pyobjs map[string]PyObjRef
pymods map[string]Global
strs map[string]llvm.Value
goStrs map[string]llvm.Value
chkabi map[types.Type]bool
afterb unsafe.Pointer
patch func(types.Type) types.Type
fnlink func(string) string
iRoutine int
NeedRuntime bool
NeedPyInit bool
}
type Package = *aPackage
func (p Package) rtFunc(fnName string) Expr {
p.NeedRuntime = true
fn := p.Prog.runtime().Scope().Lookup(fnName).(*types.Func)
name := FullName(fn.Pkg(), fnName)
sig := fn.Type().(*types.Signature)
return p.NewFunc(name, sig, InGo).Expr
}
func (p Package) cFunc(fullName string, sig *types.Signature) Expr {
return p.NewFunc(fullName, sig, InC).Expr
}
const (
closureCtx = "__llgo_ctx"
closureStub = "__llgo_stub."
)
func (p Package) closureStub(b Builder, t types.Type, v Expr) Expr {
name := v.impl.Name()
prog := b.Prog
nilVal := prog.Nil(prog.VoidPtr()).impl
if fn, ok := p.stubs[name]; ok {
v = fn.Expr
} else {
sig := v.raw.Type.(*types.Signature)
n := sig.Params().Len()
nret := sig.Results().Len()
ctx := types.NewParam(token.NoPos, nil, closureCtx, types.Typ[types.UnsafePointer])
sig = FuncAddCtx(ctx, sig)
fn := p.NewFunc(closureStub+name, sig, InC)
fn.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
args := make([]Expr, n)
for i := 0; i < n; i++ {
args[i] = fn.Param(i + 1)
}
b := fn.MakeBody(1)
call := b.Call(v, args...)
call.impl.SetTailCall(true)
switch nret {
case 0:
b.impl.CreateRetVoid()
default: // TODO(xsw): support multiple return values
b.impl.CreateRet(call.impl)
}
p.stubs[name] = fn
v = fn.Expr
}
return b.aggregateValue(prog.rawType(t), v.impl, nilVal)
}
// -----------------------------------------------------------------------------
// Path returns the package path.
func (p Package) Path() string {
return p.abi.Pkg
}
// String returns a string representation of the package.
func (p Package) String() string {
return p.mod.String()
}
// SetPatch sets a patch function.
func (p Package) SetPatch(fn func(types.Type) types.Type) {
p.patch = fn
}
// SetResolveLinkname sets a function to resolve linkname.
func (p Package) SetResolveLinkname(fn func(string) string) {
p.fnlink = fn
}
// -----------------------------------------------------------------------------
func (p Package) afterBuilder() Builder {
if p.afterb == nil {
fn := p.NewFunc(p.Path()+".init$after", NoArgsNoRet, InC)
fnb := fn.MakeBody(1)
p.afterb = unsafe.Pointer(fnb)
}
return Builder(p.afterb)
}
// AfterInit is called after the package is initialized (init all packages that depends on).
func (p Package) AfterInit(b Builder, ret BasicBlock) {
p.keyInit(deferKey)
doAfterb := p.afterb != nil
doPyLoadModSyms := p.pyHasModSyms()
if doAfterb || doPyLoadModSyms {
b.SetBlockEx(ret, afterInit, false)
if doAfterb {
afterb := Builder(p.afterb)
afterb.Return()
b.Call(afterb.Func.Expr)
}
if doPyLoadModSyms {
p.pyLoadModSyms(b)
}
}
}
func (p Package) InitDebug(name, pkgPath string, positioner Positioner) {
p.di = newDIBuilder(p.Prog, p, positioner)
p.cu = p.di.createCompileUnit(name, pkgPath)
}
// -----------------------------------------------------------------------------
/*
type CodeGenFileType = llvm.CodeGenFileType
const (
AssemblyFile = llvm.AssemblyFile
ObjectFile = llvm.ObjectFile
)
func (p *Package) CodeGen(ft CodeGenFileType) (ret []byte, err error) {
buf, err := p.prog.targetMachine().EmitToMemoryBuffer(p.mod, ft)
if err != nil {
return
}
ret = buf.Bytes()
buf.Dispose()
return
}
func (p *Package) Bitcode() []byte {
buf := llvm.WriteBitcodeToMemoryBuffer(p.mod)
ret := buf.Bytes()
buf.Dispose()
return ret
}
func (p *Package) WriteTo(w io.Writer) (int64, error) {
n, err := w.Write(p.Bitcode())
return int64(n), err
}
func (p *Package) WriteFile(file string) (err error) {
f, err := os.Create(file)
if err != nil {
return
}
defer f.Close()
return llvm.WriteBitcodeToFile(p.mod, f)
}
*/
// -----------------------------------------------------------------------------

477
ssa/python.go Normal file
View File

@@ -0,0 +1,477 @@
/*
* 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 ssa
import (
"go/token"
"go/types"
"sort"
"strings"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
// PyObjectPtrPtr returns the **py.Object type.
func (p Program) PyObjectPtrPtr() Type {
if p.pyObjPPtr == nil {
p.pyObjPPtr = p.Pointer(p.PyObjectPtr())
}
return p.pyObjPPtr
}
// PyObjectPtr returns the *py.Object type.
func (p Program) PyObjectPtr() Type {
if p.pyObjPtr == nil {
objPtr := types.NewPointer(p.pyNamed("Object"))
p.pyObjPtr = p.rawType(objPtr)
}
return p.pyObjPtr
}
func (p Program) pyNamed(name string) *types.Named {
// TODO(xsw): does python type need to convert?
t := p.python().Scope().Lookup(name).Type().(*types.Named)
return t
}
func (p Program) python() *types.Package {
if p.py == nil {
p.py = p.pyget()
}
return p.py
}
// SetPython sets the Python package.
// Its type can be *types.Package or func() *types.Package.
func (p Program) SetPython(py any) {
switch v := py.(type) {
case *types.Package:
p.py = v
case func() *types.Package:
p.pyget = v
}
}
// -----------------------------------------------------------------------------
func (p Package) pyFunc(fullName string, sig *types.Signature) Expr {
p.NeedPyInit = true
return p.NewFunc(fullName, sig, InC).Expr
}
func (p Program) paramObjPtr() *types.Var {
if p.paramObjPtr_ == nil {
objPtr := p.PyObjectPtr().raw.Type
p.paramObjPtr_ = types.NewParam(token.NoPos, nil, "", objPtr)
}
return p.paramObjPtr_
}
// func(*char) *Object
func (p Program) tyImportPyModule() *types.Signature {
if p.pyImpTy == nil {
charPtr := types.NewPointer(types.Typ[types.Int8])
params := types.NewTuple(types.NewParam(token.NoPos, nil, "", charPtr))
results := types.NewTuple(p.paramObjPtr())
p.pyImpTy = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.pyImpTy
}
// func(*Object) *Object
func (p Program) tyCallNoArgs() *types.Signature {
if p.callNoArgs == nil {
params := types.NewTuple(p.paramObjPtr())
p.callNoArgs = types.NewSignatureType(nil, nil, nil, params, params, false)
}
return p.callNoArgs
}
// func(*Object, *Object) *Object
func (p Program) tyCallOneArg() *types.Signature {
if p.callOneArg == nil {
paramObjPtr := p.paramObjPtr()
params := types.NewTuple(paramObjPtr, paramObjPtr)
results := types.NewTuple(paramObjPtr)
p.callOneArg = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.callOneArg
}
// func(*Object, ...) *Object
func (p Program) tyCallFunctionObjArgs() *types.Signature {
if p.callFOArgs == nil {
paramObjPtr := p.paramObjPtr()
params := types.NewTuple(paramObjPtr, VArg())
results := types.NewTuple(paramObjPtr)
p.callFOArgs = types.NewSignatureType(nil, nil, nil, params, results, true)
}
return p.callFOArgs
}
/*
// func(*Object, *Object, *Object) *Object
func (p Program) tyCall() *types.Signature {
if p.callArgs == nil {
paramObjPtr := p.paramObjPtr()
params := types.NewTuple(paramObjPtr, paramObjPtr, paramObjPtr)
results := types.NewTuple(paramObjPtr)
p.callArgs = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.callArgs
}
*/
// func(*Object, uintptr, *Object) cint
func (p Program) tyListSetItem() *types.Signature {
if p.pyListSetI == nil {
paramUintptr := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type)
paramCInt := types.NewParam(token.NoPos, nil, "", p.CInt().raw.Type)
paramObjPtr := p.paramObjPtr()
params := types.NewTuple(paramObjPtr, paramUintptr, paramObjPtr)
results := types.NewTuple(paramCInt)
p.pyListSetI = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.pyListSetI
}
// func(uintptr) *Object
func (p Program) tyNewList() *types.Signature {
if p.pyNewList == nil {
paramUintptr := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type)
params := types.NewTuple(paramUintptr)
results := types.NewTuple(p.paramObjPtr())
p.pyNewList = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.pyNewList
}
// func(*Object, uintptr, *Object) cint
func (p Program) tyTupleSetItem() *types.Signature {
if p.pyTupleSetI == nil {
paramUintptr := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type)
paramCInt := types.NewParam(token.NoPos, nil, "", p.CInt().raw.Type)
paramObjPtr := p.paramObjPtr()
params := types.NewTuple(paramObjPtr, paramUintptr, paramObjPtr)
results := types.NewTuple(paramCInt)
p.pyTupleSetI = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.pyTupleSetI
}
// func(uintptr) *Object
func (p Program) tyNewTuple() *types.Signature {
if p.pyNewTuple == nil {
paramUintptr := types.NewParam(token.NoPos, nil, "", p.Uintptr().raw.Type)
params := types.NewTuple(paramUintptr)
results := types.NewTuple(p.paramObjPtr())
p.pyNewTuple = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.pyNewTuple
}
// func(float64) *Object
func (p Program) tyFloatFromDouble() *types.Signature {
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.floatFromDbl = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.floatFromDbl
}
// func(*Object, ...)
func (p Program) tyLoadPyModSyms() *types.Signature {
if p.loadPyModS == nil {
paramObjPtr := p.paramObjPtr()
params := types.NewTuple(paramObjPtr, VArg())
p.loadPyModS = types.NewSignatureType(nil, nil, nil, params, nil, true)
}
return p.loadPyModS
}
// func(*char) *Object
func (p Program) tyPyUnicodeFromString() *types.Signature {
if p.pyUniStr == nil {
charPtr := types.NewPointer(types.Typ[types.Int8])
params := types.NewTuple(types.NewParam(token.NoPos, nil, "", charPtr))
results := types.NewTuple(p.paramObjPtr())
p.pyUniStr = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.pyUniStr
}
// func(*Objecg, *char) *Object
func (p Program) tyGetAttrString() *types.Signature {
if p.getAttrStr == nil {
charPtr := types.NewPointer(types.Typ[types.Int8])
paramObjPtr := p.paramObjPtr()
params := types.NewTuple(paramObjPtr, types.NewParam(token.NoPos, nil, "", charPtr))
results := types.NewTuple(paramObjPtr)
p.getAttrStr = types.NewSignatureType(nil, nil, nil, params, results, false)
}
return p.getAttrStr
}
// PyNewModVar creates a new global variable for a Python module.
func (p Package) PyNewModVar(name string, doInit bool) Global {
if v, ok := p.pymods[name]; ok {
return v
}
prog := p.Prog
objPtr := prog.PyObjectPtrPtr().raw.Type
g := p.NewVar(name, objPtr, InC)
if doInit {
g.InitNil()
g.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
}
p.pymods[name] = g
return g
}
// PyImportMod imports a Python module.
func (b Builder) PyImportMod(path string) Expr {
fnImp := b.Pkg.pyFunc("PyImport_ImportModule", b.Prog.tyImportPyModule())
return b.Call(fnImp, b.CStr(path))
}
// PyLoadModSyms loads python objects from specified module.
func (b Builder) PyLoadModSyms(modName string, objs ...PyObjRef) Expr {
pkg := b.Pkg
fnLoad := pkg.pyFunc("llgoLoadPyModSyms", b.Prog.tyLoadPyModSyms())
modPtr := pkg.PyNewModVar(modName, false).Expr
mod := b.Load(modPtr)
args := make([]Expr, 1, len(objs)*2+2)
args[0] = mod
nbase := len(modName) + 1
for _, o := range objs {
fullName := o.impl.Name()
name := fullName[nbase:]
args = append(args, b.CStr(name))
args = append(args, o.Expr)
}
prog := b.Prog
args = append(args, prog.Nil(prog.CStr()))
return b.Call(fnLoad, args...)
}
func (b Builder) pyCall(fn Expr, args []Expr) (ret Expr) {
prog := b.Prog
pkg := b.Pkg
fn = b.Load(fn)
sig := fn.raw.Type.(*types.Signature)
params := sig.Params()
n := params.Len()
switch n {
case 0:
call := pkg.pyFunc("PyObject_CallNoArgs", prog.tyCallNoArgs())
ret = b.Call(call, fn)
case 1:
if !sig.Variadic() {
call := pkg.pyFunc("PyObject_CallOneArg", prog.tyCallOneArg())
return b.Call(call, fn, args[0])
}
fallthrough
default:
call := pkg.pyFunc("PyObject_CallFunctionObjArgs", prog.tyCallFunctionObjArgs())
n = len(args)
callargs := make([]Expr, n+2)
callargs[0] = fn
copy(callargs[1:], args)
callargs[n+1] = prog.Nil(prog.PyObjectPtr())
ret = b.Call(call, callargs...)
}
return
}
// PyNewList(n uintptr) *Object
func (b Builder) PyNewList(n Expr) (ret Expr) {
prog := b.Prog
fn := b.Pkg.pyFunc("PyList_New", prog.tyNewList())
return b.Call(fn, n)
}
// PyListSetItem(list *Object, index uintptr, item *Object) c.Int
func (b Builder) PyListSetItem(list, index, item Expr) (ret Expr) {
prog := b.Prog
fn := b.Pkg.pyFunc("PyList_SetItem", prog.tyListSetItem())
return b.Call(fn, list, index, item)
}
// PyList(args ...Expr) *Object
func (b Builder) PyList(args ...Expr) (ret Expr) {
prog := b.Prog
n := len(args)
uintPtr := prog.Uintptr()
list := b.PyNewList(prog.IntVal(uint64(n), uintPtr))
for i, arg := range args {
b.PyListSetItem(list, prog.IntVal(uint64(i), uintPtr), b.PyVal(arg))
}
return list
}
// PyNewTuple(n int) *Object
func (b Builder) PyNewTuple(n Expr) (ret Expr) {
prog := b.Prog
fn := b.Pkg.pyFunc("PyTuple_New", prog.tyNewTuple())
return b.Call(fn, n)
}
// PyTupleSetItem(list *Object, index uintptr, item *Object) c.Int
func (b Builder) PyTupleSetItem(list, index, item Expr) (ret Expr) {
prog := b.Prog
fn := b.Pkg.pyFunc("PyTuple_SetItem", prog.tyTupleSetItem())
return b.Call(fn, list, index, item)
}
// PyTuple(args ...Expr) *Object
func (b Builder) PyTuple(args ...Expr) (ret Expr) {
prog := b.Prog
n := len(args)
uintPtr := prog.Uintptr()
list := b.PyNewTuple(prog.IntVal(uint64(n), uintPtr))
for i, arg := range args {
b.PyTupleSetItem(list, prog.IntVal(uint64(i), uintPtr), b.PyVal(arg))
}
return list
}
// PyVal(v any) *Object
func (b Builder) PyVal(v Expr) (ret Expr) {
switch t := v.raw.Type.(type) {
case *types.Basic:
switch t.Kind() {
case types.Float64:
return b.PyFloat(v)
default:
panic("PyVal: todo")
}
default:
return v
}
}
// PyFloat(fltVal float64) *Object
func (b Builder) PyFloat(fltVal Expr) (ret Expr) {
fn := b.Pkg.pyFunc("PyFloat_FromDouble", b.Prog.tyFloatFromDouble())
return b.Call(fn, fltVal)
}
// PyStr returns a py-style string constant expression.
func (b Builder) PyStr(v string) Expr {
fn := b.Pkg.pyFunc("PyUnicode_FromString", b.Prog.tyPyUnicodeFromString())
return b.Call(fn, b.CStr(v))
}
// -----------------------------------------------------------------------------
type aPyGlobal struct {
Expr
}
type PyGlobal = *aPyGlobal
// PyNewVar creates a Python variable.
func (b Builder) PyNewVar(modName, name string) PyGlobal {
modPtr := b.Pkg.PyNewModVar(modName, false).Expr
mod := b.Load(modPtr)
return &aPyGlobal{pyVarExpr(mod, name)}
}
func (b Builder) pyLoad(ptr Expr) Expr {
t := ptr.raw.Type.(*pyVarTy)
fn := b.Pkg.pyFunc("PyObject_GetAttrString", b.Prog.tyGetAttrString())
return b.Call(fn, t.mod, b.CStr(t.name))
}
// -----------------------------------------------------------------------------
type aPyObjRef struct {
Expr
Obj Global
}
// PyObjRef represents a python object reference.
type PyObjRef = *aPyObjRef
// PyNewFunc creates a new python function.
func (p Package) PyNewFunc(name string, sig *types.Signature, doInit bool) PyObjRef {
if v, ok := p.pyobjs[name]; ok {
return v
}
prog := p.Prog
obj := p.NewVar(name, prog.PyObjectPtrPtr().RawType(), InC)
if doInit {
p.NeedPyInit = true
obj.InitNil()
obj.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
}
ty := &aType{obj.ll, rawType{types.NewPointer(sig)}, vkPyFuncRef}
expr := Expr{obj.impl, ty}
ret := &aPyObjRef{expr, obj}
p.pyobjs[name] = ret
return ret
}
// PyObjOf returns a python object by name.
func (p Package) PyObjOf(name string) PyObjRef {
return p.pyobjs[name]
}
func (p Package) pyHasModSyms() bool {
return len(p.pyobjs) > 0
}
// pyLoadModSyms loads module symbols used in this package.
func (p Package) pyLoadModSyms(b Builder) {
objs := p.pyobjs
names := make([]string, 0, len(objs))
for name := range objs {
names = append(names, name)
}
sort.Strings(names)
mods := make(map[string][]PyObjRef)
modNames := make([]string, 0, 8)
lastMod := ""
for _, name := range names {
modName := modOf(name)
mods[modName] = append(mods[modName], objs[name])
if modName != lastMod {
modNames = append(modNames, modName)
lastMod = modName
}
}
for _, modName := range modNames {
objs := mods[modName]
b.PyLoadModSyms(modName, objs...)
}
}
func modOf(name string) string {
if pos := strings.LastIndexByte(name, '.'); pos > 0 {
return name[:pos]
}
panic("unreachable")
}
// -----------------------------------------------------------------------------

552
ssa/ssa_test.go Normal file
View File

@@ -0,0 +1,552 @@
//go:build !llgo
// +build !llgo
/*
* 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 ssa
import (
"go/constant"
"go/token"
"go/types"
"testing"
"unsafe"
"github.com/goplus/gogen/packages"
"github.com/goplus/llvm"
)
func TestEndDefer(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("foo", "foo")
fn := pkg.NewFunc("main", NoArgsNoRet, InC)
b := fn.MakeBody(1)
fn.defer_ = &aDefer{}
fn.endDefer(b)
}
func TestUnsafeString(t *testing.T) {
prog := NewProgram(nil)
prog.SetRuntime(func() *types.Package {
fset := token.NewFileSet()
imp := packages.NewImporter(fset)
pkg, _ := imp.Import(PkgRuntime)
return pkg
})
pkg := prog.NewPackage("foo", "foo")
b := pkg.NewFunc("main", NoArgsNoRet, InC).MakeBody(1)
b.Println(b.BuiltinCall("String", b.CStr("hello"), prog.Val(5)))
b.Return()
}
func TestPointerSize(t *testing.T) {
expected := unsafe.Sizeof(uintptr(0))
if size := NewProgram(nil).PointerSize(); size != int(expected) {
t.Fatal("bad PointerSize:", size)
}
}
func TestSetBlock(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Log("SetBlock: no error?")
}
}()
fn := &aFunction{}
b := &aBuilder{Func: fn}
b.SetBlock(&aBasicBlock{})
}
func TestSetBlockEx(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Log("SetBlockEx: no error?")
}
}()
fn := &aFunction{}
b := &aBuilder{Func: fn}
b.SetBlockEx(&aBasicBlock{fn: fn}, -1, false)
}
func TestSetPython(t *testing.T) {
prog := NewProgram(nil)
typ := types.NewPackage("foo", "foo")
prog.SetPython(typ)
}
func TestClosureCtx(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Log("closureCtx: no error?")
}
}()
var f aFunction
f.closureCtx(nil)
}
func TestTypes(t *testing.T) {
ctx := llvm.NewContext()
llvmIntType(ctx, 4)
intT := types.NewVar(0, nil, "", types.Typ[types.Int])
ret := types.NewTuple(intT, intT)
sig := types.NewSignatureType(nil, nil, nil, nil, ret, false)
prog := NewProgram(nil)
prog.retType(sig)
}
func TestIndexType(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Log("indexType: no error?")
}
}()
indexType(types.Typ[types.Int])
}
func TestCvtType(t *testing.T) {
gt := newGoTypes()
params := types.NewTuple(types.NewParam(0, nil, "", NoArgsNoRet))
sig := types.NewSignatureType(nil, nil, nil, params, nil, false)
ret1 := gt.cvtFunc(sig, nil)
if ret1 == sig {
t.Fatal("cvtFunc failed")
}
defer func() {
if r := recover(); r == nil {
t.Log("cvtType: no error?")
}
}()
gt.cvtType(nil)
}
func TestUserdefExpr(t *testing.T) {
c := &pyVarTy{}
b := &builtinTy{}
_ = c.String()
_ = b.String()
test := func(a types.Type) {
defer func() {
if r := recover(); r == nil {
t.Log("TestUserdefExpr: no error?")
}
}()
a.Underlying()
}
test(c)
test(b)
}
func TestAny(t *testing.T) {
prog := NewProgram(nil)
prog.SetRuntime(func() *types.Package {
ret := types.NewPackage("runtime", "runtime")
scope := ret.Scope()
name := types.NewTypeName(0, ret, "Eface", nil)
types.NewNamed(name, types.NewStruct(nil, nil), nil)
scope.Insert(name)
return ret
})
prog.Any()
}
func assertPkg(t *testing.T, p Package, expected string) {
t.Helper()
if v := p.String(); v != expected {
t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected)
}
}
func TestPyFunc(t *testing.T) {
prog := NewProgram(nil)
py := types.NewPackage("foo", "foo")
o := types.NewTypeName(0, py, "Object", nil)
types.NewNamed(o, types.Typ[types.Int], nil)
py.Scope().Insert(o)
prog.SetPython(py)
pkg := prog.NewPackage("bar", "foo/bar")
a := pkg.PyNewFunc("a", NoArgsNoRet, false)
if pkg.PyNewFunc("a", NoArgsNoRet, false) != a {
t.Fatal("NewPyFunc(a) failed")
}
foo := pkg.PyNewModVar("foo", false)
if pkg.PyNewModVar("foo", false) != foo {
t.Fatal("NewPyModVar(foo) failed")
}
}
func TestVar(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
typ := types.NewPointer(types.Typ[types.Int])
a := pkg.NewVar("a", typ, InGo)
if pkg.NewVar("a", typ, InGo) != a {
t.Fatal("NewVar(a) failed")
}
pkg.NewVarEx("a", prog.Type(typ, InGo))
a.Init(prog.Val(100))
b := pkg.NewVar("b", typ, InGo)
b.Init(a.Expr)
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
@a = global i64 100, align 8
@b = global i64 @a, align 8
`)
}
func TestConst(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Bool]))
sig := types.NewSignatureType(nil, nil, nil, nil, rets, false)
b := pkg.NewFunc("fn", sig, InGo).MakeBody(1)
b.Return(b.Const(constant.MakeBool(true), prog.Bool()))
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i1 @fn() {
_llgo_0:
ret i1 true
}
`)
}
func TestStruct(t *testing.T) {
empty := types.NewStruct(nil, nil)
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
pkg.NewVar("a", types.NewPointer(empty), InGo)
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
@a = external global {}, align 1
`)
if pkg.NeedRuntime {
t.Fatal("NeedRuntime?")
}
}
func TestNamedStruct(t *testing.T) {
src := types.NewPackage("bar", "foo/bar")
empty := types.NewNamed(types.NewTypeName(0, src, "Empty", nil), types.NewStruct(nil, nil), nil)
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
pkg.NewVar("a", types.NewPointer(empty), InGo)
if pkg.VarOf("a") == nil {
t.Fatal("VarOf failed")
}
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
%bar.Empty = type {}
@a = external global %bar.Empty, align 1
`)
}
func TestDeclFunc(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(types.NewVar(0, nil, "a", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, nil, false)
pkg.NewFunc("fn", sig, InGo)
if pkg.FuncOf("fn") == nil {
t.Fatal("FuncOf failed")
}
if prog.retType(sig) != prog.Void() {
t.Fatal("retType failed")
}
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
declare void @fn(i64)
`)
}
func TestBasicFunc(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(
types.NewVar(0, nil, "a", types.Typ[types.Int]),
types.NewVar(0, nil, "b", types.Typ[types.Float64]))
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
pkg.NewFunc("fn", sig, InGo).MakeBody(1).
Return(prog.Val(1))
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i64 @fn(i64 %0, double %1) {
_llgo_0:
ret i64 1
}
`)
}
func TestFuncParam(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(
types.NewVar(0, nil, "a", types.Typ[types.Int]),
types.NewVar(0, nil, "b", types.Typ[types.Float64]))
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
fn := pkg.NewFunc("fn", sig, InGo)
fn.MakeBody(1).Return(fn.Param(0))
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i64 @fn(i64 %0, double %1) {
_llgo_0:
ret i64 %0
}
`)
}
func TestFuncCall(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(
types.NewVar(0, nil, "a", types.Typ[types.Int]),
types.NewVar(0, nil, "b", types.Typ[types.Float64]))
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
fn := pkg.NewFunc("fn", sig, InGo)
fn.MakeBody(1).
Return(prog.Val(1))
b := pkg.NewFunc("main", NoArgsNoRet, InGo).MakeBody(1)
b.Call(fn.Expr, prog.Val(1), prog.Val(1.2))
b.Return()
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i64 @fn(i64 %0, double %1) {
_llgo_0:
ret i64 1
}
define void @main() {
_llgo_0:
%0 = call i64 @fn(i64 1, double 1.200000e+00)
ret void
}
`)
}
func TestFuncMultiRet(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(
types.NewVar(0, nil, "b", types.Typ[types.Float64]))
rets := types.NewTuple(
types.NewVar(0, nil, "c", types.Typ[types.Int]),
types.NewVar(0, nil, "d", types.Typ[types.Float64]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
a := pkg.NewVar("a", types.NewPointer(types.Typ[types.Int]), InGo)
fn := pkg.NewFunc("fn", sig, InGo)
b := fn.MakeBody(1)
b.Return(a.Expr, fn.Param(0))
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
@a = external global i64, align 8
define { i64, double } @fn(double %0) {
_llgo_0:
%1 = insertvalue { i64, double } { ptr @a, double undef }, double %0, 1
ret { i64, double } %1
}
`)
}
func TestJump(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
fn := pkg.NewFunc("loop", NoArgsNoRet, InGo)
b := fn.MakeBody(1)
b.Jump(fn.Block(0))
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define void @loop() {
_llgo_0:
br label %_llgo_0
}
`)
}
func TestIf(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(types.NewVar(0, nil, "a", types.Typ[types.Int]))
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
fn := pkg.NewFunc("fn", sig, InGo)
b := fn.MakeBody(3)
iftrue := fn.Block(1)
iffalse := fn.Block(2)
if iftrue.Index() != 1 || iftrue.Parent() != fn {
t.Fatal("iftrue")
}
cond := b.BinOp(token.GTR, fn.Param(0), prog.Val(0))
b.If(cond, iftrue, iffalse)
b.SetBlock(iftrue).Return(prog.Val(1))
b.SetBlock(iffalse).Return(prog.Val(0))
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i64 @fn(i64 %0) {
_llgo_0:
%1 = icmp sgt i64 %0, 0
br i1 %1, label %_llgo_1, label %_llgo_2
_llgo_1: ; preds = %_llgo_0
ret i64 1
_llgo_2: ; preds = %_llgo_0
ret i64 0
}
`)
}
func TestPrintf(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
pchar := types.NewPointer(types.Typ[types.Int8])
params := types.NewTuple(types.NewVar(0, nil, "format", pchar), VArg())
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int32]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, true)
pkg.NewFunc("printf", sig, InC)
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
declare i32 @printf(ptr, ...)
`)
}
func TestBinOp(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(
types.NewVar(0, nil, "a", types.Typ[types.Int]),
types.NewVar(0, nil, "b", types.Typ[types.Float64]))
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
fn := pkg.NewFunc("fn", sig, InGo)
b := fn.MakeBody(1)
ret := b.BinOp(token.ADD, fn.Param(0), prog.Val(1))
b.Return(ret)
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i64 @fn(i64 %0, double %1) {
_llgo_0:
%2 = add i64 %0, 1
ret i64 %2
}
`)
}
func TestUnOp(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(
types.NewVar(0, nil, "p", types.NewPointer(types.Typ[types.Int])),
)
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
fn := pkg.NewFunc("fn", sig, InGo)
b := fn.MakeBody(1)
ptr := fn.Param(0)
val := b.UnOp(token.MUL, ptr)
val2 := b.BinOp(token.XOR, val, prog.Val(1))
b.Store(ptr, val2)
b.Return(val2)
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i64 @fn(ptr %0) {
_llgo_0:
%1 = load i64, ptr %0, align 4
%2 = xor i64 %1, 1
store i64 %2, ptr %0, align 4
ret i64 %2
}
`)
}
func TestBasicType(t *testing.T) {
type typeInfo struct {
typ Type
kind types.BasicKind
}
prog := NewProgram(nil)
infos := []*typeInfo{
{prog.Bool(), types.Bool},
{prog.Byte(), types.Byte},
{prog.Int(), types.Int},
{prog.Uint(), types.Uint},
{prog.Int32(), types.Int32},
{prog.Int64(), types.Int64},
{prog.Uint32(), types.Uint32},
{prog.Uint64(), types.Uint64},
{prog.Uintptr(), types.Uintptr},
{prog.VoidPtr(), types.UnsafePointer},
}
for _, info := range infos {
if info.typ.RawType() != types.Typ[info.kind] {
t.Fatal("bad type", info)
}
}
}
func TestCompareSelect(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
params := types.NewTuple(
types.NewVar(0, nil, "a", types.Typ[types.Int]),
types.NewVar(0, nil, "b", types.Typ[types.Int]),
types.NewVar(0, nil, "c", types.Typ[types.Int]),
)
rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, rets, false)
fn := pkg.NewFunc("fn", sig, InGo)
b := fn.MakeBody(1)
result := b.compareSelect(token.GTR, fn.Param(0), fn.Param(1), fn.Param(2))
b.Return(result)
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
define i64 @fn(i64 %0, i64 %1, i64 %2) {
_llgo_0:
%3 = icmp sgt i64 %0, %1
%4 = select i1 %3, i64 %0, i64 %1
%5 = icmp sgt i64 %4, %2
%6 = select i1 %5, i64 %4, i64 %2
ret i64 %6
}
`)
}

58
ssa/ssatest/ssautil.go Normal file
View File

@@ -0,0 +1,58 @@
/*
* 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 ssatest
import (
"go/token"
"go/types"
"testing"
"github.com/goplus/gogen/packages"
"github.com/goplus/llgo/ssa"
)
func NewProgram(t *testing.T, target *ssa.Target) ssa.Program {
fset := token.NewFileSet()
imp := packages.NewImporter(fset)
return NewProgramEx(t, target, imp)
}
func NewProgramEx(t *testing.T, target *ssa.Target, imp types.Importer) ssa.Program {
prog := ssa.NewProgram(target)
prog.SetRuntime(func() *types.Package {
rt, err := imp.Import(ssa.PkgRuntime)
if err != nil {
t.Fatal("load runtime failed:", err)
}
return rt
})
prog.SetPython(func() *types.Package {
rt, err := imp.Import(ssa.PkgPython)
if err != nil {
t.Fatal("load python failed:", err)
}
return rt
})
return prog
}
func Assert(t *testing.T, p ssa.Package, expected string) {
t.Helper()
if v := p.String(); v != expected {
t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected)
}
}

118
ssa/ssatest/ssautil_test.go Normal file
View File

@@ -0,0 +1,118 @@
//go:build !llgo
// +build !llgo
package ssatest
import (
"go/types"
"testing"
"github.com/goplus/llgo/ssa"
"github.com/goplus/llvm"
)
func init() {
llvm.InitializeAllTargets()
llvm.InitializeAllTargetMCs()
llvm.InitializeAllTargetInfos()
llvm.InitializeAllAsmParsers()
llvm.InitializeAllAsmPrinters()
}
type mockImporter struct {
pkgs map[string]*types.Package
}
func newMockImporter() *mockImporter {
return &mockImporter{
pkgs: make(map[string]*types.Package),
}
}
func (m *mockImporter) Import(path string) (*types.Package, error) {
if pkg, ok := m.pkgs[path]; ok {
return pkg, nil
}
pkg := types.NewPackage(path, path)
m.pkgs[path] = pkg
return pkg, nil
}
func TestNewProgram(t *testing.T) {
target := &ssa.Target{
GOOS: "linux",
GOARCH: "amd64",
GOARM: "7",
}
prog := NewProgram(t, target)
if prog == nil {
t.Fatal("NewProgram returned nil")
}
// Set runtime package
rtPkg := types.NewPackage(ssa.PkgRuntime, ssa.PkgRuntime)
prog.SetRuntime(rtPkg)
// Set python package
pyPkg := types.NewPackage(ssa.PkgPython, ssa.PkgPython)
prog.SetRuntime(pyPkg)
}
func TestNewProgramEx(t *testing.T) {
target := &ssa.Target{
GOOS: "linux",
GOARCH: "amd64",
GOARM: "7",
}
imp := newMockImporter()
prog := NewProgramEx(t, target, imp)
if prog == nil {
t.Fatal("NewProgramEx returned nil")
}
// Set runtime package
rtPkg := types.NewPackage(ssa.PkgRuntime, ssa.PkgRuntime)
prog.SetRuntime(rtPkg)
// Set python package
pyPkg := types.NewPackage(ssa.PkgPython, ssa.PkgPython)
prog.SetRuntime(pyPkg)
}
func TestAssert(t *testing.T) {
target := &ssa.Target{
GOOS: "linux",
GOARCH: "amd64",
GOARM: "7",
}
prog := NewProgram(t, target)
if prog == nil {
t.Fatal("NewProgram returned nil")
}
tests := []struct {
name string
pkg ssa.Package
expected string
}{
{
name: "test package path",
pkg: prog.NewPackage("test", "test/path"),
expected: "; ModuleID = 'test/path'\nsource_filename = \"test/path\"\n",
},
{
name: "another package path",
pkg: prog.NewPackage("another", "another/path"),
expected: "; ModuleID = 'another/path'\nsource_filename = \"another/path\"\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Assert(t, tt.pkg, tt.expected)
})
}
}

371
ssa/stmt_builder.go Normal file
View File

@@ -0,0 +1,371 @@
/*
* 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 ssa
import (
"bytes"
"fmt"
"go/token"
"go/types"
"log"
"strings"
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
type aBasicBlock struct {
first llvm.BasicBlock // a Go SSA basic block may have multiple LLVM basic blocks
last llvm.BasicBlock
fn Function
idx int
}
// BasicBlock represents a basic block in a function.
type BasicBlock = *aBasicBlock
// Parent returns the function to which the basic block belongs.
func (p BasicBlock) Parent() Function {
return p.fn
}
// Index returns the index of the basic block in the parent function.
func (p BasicBlock) Index() int {
return p.idx
}
// Addr returns the address of the basic block.
func (p BasicBlock) Addr() Expr {
fn := p.fn
return Expr{llvm.BlockAddress(fn.impl, p.first), fn.Prog.VoidPtr()}
}
// -----------------------------------------------------------------------------
type dbgExpr struct {
ptr Expr
val Expr
}
type aBuilder struct {
impl llvm.Builder
blk BasicBlock
Func Function
Pkg Package
Prog Program
dbgVars map[Expr]dbgExpr // save copied address and values for debug info
diScopeCache map[*types.Scope]DIScope // avoid duplicated DILexicalBlock(s)
}
// Builder represents a builder for creating instructions in a function.
type Builder = *aBuilder
// EndBuild ends the build process of a function.
func (b Builder) EndBuild() {
b.Func.endDefer(b)
}
// Dispose disposes of the builder.
func (b Builder) Dispose() {
b.impl.Dispose()
}
// SetBlock means SetBlockEx(blk, AtEnd, true).
func (b Builder) SetBlock(blk BasicBlock) Builder {
if debugInstr {
log.Printf("Block _llgo_%v:\n", blk.idx)
}
b.SetBlockEx(blk, AtEnd, true)
return b
}
func (b Builder) setBlockMoveLast(blk BasicBlock) (next BasicBlock) {
blkLast := blk.last
last := blkLast.LastInstruction()
last.RemoveFromParentAsInstruction()
impl := b.impl
next = b.Func.MakeBlock()
impl.SetInsertPointAtEnd(next.last)
impl.Insert(last)
impl.SetInsertPointAtEnd(blkLast)
return
}
type InsertPoint int
const (
AtEnd InsertPoint = iota
AtStart
BeforeLast
afterInit
)
// SetBlockEx sets blk as current basic block and pos as its insert point.
func (b Builder) SetBlockEx(blk BasicBlock, pos InsertPoint, setBlk bool) {
if b.Func != blk.fn {
panic("mismatched function")
}
switch pos {
case AtEnd:
b.impl.SetInsertPointAtEnd(blk.last)
case AtStart:
b.impl.SetInsertPointBefore(blk.first.FirstInstruction())
case BeforeLast:
b.impl.SetInsertPointBefore(blk.last.LastInstruction())
case afterInit:
b.impl.SetInsertPointBefore(instrAfterInit(blk.first))
default:
panic("SetBlockEx: invalid pos")
}
if setBlk {
b.blk = blk
}
}
func instrAfterInit(blk llvm.BasicBlock) llvm.Value {
instr := blk.FirstInstruction()
for {
instr = llvm.NextInstruction(instr)
if notInit(instr) {
return instr
}
}
}
func notInit(instr llvm.Value) bool {
switch op := instr.InstructionOpcode(); op {
case llvm.Call:
if n := instr.OperandsCount(); n == 1 {
fn := instr.Operand(0)
return !strings.HasSuffix(fn.Name(), ".init")
}
}
return true
}
// -----------------------------------------------------------------------------
// Return emits a return instruction.
func (b Builder) Return(results ...Expr) {
if debugInstr {
var b bytes.Buffer
fmt.Fprint(&b, "Return ")
for i, arg := range results {
if i > 0 {
fmt.Fprint(&b, ", ")
}
fmt.Fprint(&b, arg.impl)
}
log.Println(b.String())
}
switch n := len(results); n {
case 0:
b.impl.CreateRetVoid()
case 1:
raw := b.Func.raw.Type.(*types.Signature).Results().At(0).Type()
ret := checkExpr(results[0], raw, b)
b.impl.CreateRet(ret.impl)
default:
tret := b.Func.raw.Type.(*types.Signature).Results()
n := tret.Len()
typs := make([]Type, n)
for i := 0; i < n; i++ {
typs[i] = b.Prog.Type(tret.At(i).Type(), InC)
}
typ := b.Prog.Struct(typs...)
expr := b.aggregateValue(typ, llvmParams(0, results, tret, b)...)
b.impl.CreateRet(expr.impl)
}
}
// The Extract instruction yields component Index of Tuple.
//
// This is used to access the results of instructions with multiple
// return values, such as Call, TypeAssert, Next, UnOp(ARROW) and
// IndexExpr(Map).
//
// Example printed form:
//
// t1 = extract t0 #1
func (b Builder) Extract(x Expr, i int) (ret Expr) {
if debugInstr {
log.Printf("Extract %v, %d\n", x.impl, i)
}
return b.getField(x, i)
}
// Jump emits a jump instruction.
func (b Builder) Jump(jmpb BasicBlock) {
if b.Func != jmpb.fn {
panic("mismatched function")
}
if debugInstr {
log.Printf("Jump _llgo_%v\n", jmpb.idx)
}
b.impl.CreateBr(jmpb.first)
}
// IndirectJump emits an indirect jump instruction.
func (b Builder) IndirectJump(addr Expr, dests []BasicBlock) {
if debugInstr {
log.Printf("IndirectJump %v\n", addr.impl)
}
ibr := b.impl.CreateIndirectBr(addr.impl, len(dests))
for _, dest := range dests {
ibr.AddDest(dest.first)
}
}
// If emits an if instruction.
func (b Builder) If(cond Expr, thenb, elseb BasicBlock) {
if b.Func != thenb.fn || b.Func != elseb.fn {
panic("mismatched function")
}
if debugInstr {
log.Printf("If %v, _llgo_%v, _llgo_%v\n", cond.impl, thenb.idx, elseb.idx)
}
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
}
/* TODO(xsw):
// For emits a for-loop instruction.
func (b Builder) For(cond func() Expr, loop func()) {
blks := b.Func.MakeBlocks(3)
b.Jump(blks[0])
b.SetBlockEx(blks[0], AtEnd, false)
b.If(cond(), blks[1], blks[2])
b.SetBlockEx(blks[1], AtEnd, false)
loop()
b.Jump(blks[0])
b.SetBlockEx(blks[2], AtEnd, false)
b.blk.last = blks[2].last
}
*/
// Times emits a times-loop instruction.
func (b Builder) Times(n Expr, loop func(i Expr)) {
at := b.blk
blks := b.Func.MakeBlocks(3)
b.Jump(blks[0])
b.SetBlockEx(blks[0], AtEnd, false)
typ := n.Type
phi := b.Phi(typ)
b.If(b.BinOp(token.LSS, phi.Expr, n), blks[1], blks[2])
b.SetBlockEx(blks[1], AtEnd, false)
loop(phi.Expr)
post := b.BinOp(token.ADD, phi.Expr, b.Prog.IntVal(1, typ))
b.Jump(blks[0])
phi.AddIncoming(b, []BasicBlock{at, blks[1]}, func(i int, blk BasicBlock) Expr {
if i == 0 {
return b.Prog.IntVal(0, typ)
}
return post
})
b.SetBlockEx(blks[2], AtEnd, false)
b.blk.last = blks[2].last
}
// -----------------------------------------------------------------------------
/*
type caseStmt struct {
v llvm.Value
blk llvm.BasicBlock
}
type aSwitch struct {
v llvm.Value
def llvm.BasicBlock
cases []caseStmt
}
// Switch represents a switch statement.
type Switch = *aSwitch
// Case emits a case instruction.
func (p Switch) Case(v Expr, blk BasicBlock) {
if debugInstr {
log.Printf("Case %v, _llgo_%v\n", v.impl, blk.idx)
}
p.cases = append(p.cases, caseStmt{v.impl, blk.first})
}
// End ends a switch statement.
func (p Switch) End(b Builder) {
sw := b.impl.CreateSwitch(p.v, p.def, len(p.cases))
for _, c := range p.cases {
sw.AddCase(c.v, c.blk)
}
}
// Switch starts a switch statement.
func (b Builder) Switch(v Expr, defb BasicBlock) Switch {
if debugInstr {
log.Printf("Switch %v, _llgo_%v\n", v.impl, defb.idx)
}
return &aSwitch{v.impl, defb.first, nil}
}
*/
// -----------------------------------------------------------------------------
// Phi represents a phi node.
type Phi struct {
Expr
}
// AddIncoming adds incoming values to a phi node.
func (p Phi) AddIncoming(b Builder, preds []BasicBlock, f func(i int, blk BasicBlock) Expr) {
raw := p.raw.Type
bs := llvmPredBlocks(preds)
vals := make([]llvm.Value, len(preds))
for iblk, blk := range preds {
val := f(iblk, blk)
vals[iblk] = checkExpr(val, raw, b).impl
}
p.impl.AddIncoming(vals, bs)
}
func llvmPredBlocks(preds []BasicBlock) []llvm.BasicBlock {
ret := make([]llvm.BasicBlock, len(preds))
for i, v := range preds {
ret[i] = v.last
}
return ret
}
// Phi returns a phi node.
func (b Builder) Phi(t Type) Phi {
phi := llvm.CreatePHI(b.impl, t.ll)
return Phi{Expr{phi, t}}
}
// -----------------------------------------------------------------------------

168
ssa/target.go Normal file
View File

@@ -0,0 +1,168 @@
/*
* 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 ssa
import (
"github.com/goplus/llvm"
)
// -----------------------------------------------------------------------------
type Target struct {
GOOS string
GOARCH string
GOARM string // "5", "6", "7" (default)
}
func (p *Target) targetData() llvm.TargetData {
spec := p.toSpec()
if spec.triple == "" {
spec.triple = llvm.DefaultTargetTriple()
}
t, err := llvm.GetTargetFromTriple(spec.triple)
if err != nil {
panic(err)
}
machine := t.CreateTargetMachine(spec.triple, spec.cpu, spec.features, llvm.CodeGenLevelDefault, llvm.RelocDefault, llvm.CodeModelDefault)
return machine.CreateTargetData()
}
/*
func (p *Program) targetMachine() llvm.TargetMachine {
if p.tm.C == nil {
spec := p.target.toSpec()
target, err := llvm.GetTargetFromTriple(spec.triple)
if err != nil {
panic(err)
}
p.tm = target.CreateTargetMachine(
spec.triple,
spec.cpu,
spec.features,
llvm.CodeGenLevelDefault,
llvm.RelocDefault,
llvm.CodeModelDefault,
)
}
return p.tm
}
*/
type targetSpec struct {
triple string
cpu string
features string
}
// TODO config
func (p *Target) toSpec() (spec targetSpec) {
return
}
/*
func (p *Target) toSpec() (spec targetSpec) {
// Configure based on GOOS/GOARCH environment variables (falling back to
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
var llvmarch string
var goarch = p.GOARCH
var goos = p.GOOS
if goarch == "" {
goarch = runtime.GOARCH
}
if goos == "" {
goos = runtime.GOOS
}
switch goarch {
case "386":
llvmarch = "i386"
case "amd64":
llvmarch = "x86_64"
case "arm64":
llvmarch = "aarch64"
case "arm":
switch p.GOARM {
case "5":
llvmarch = "armv5"
case "6":
llvmarch = "armv6"
default:
llvmarch = "armv7"
}
case "wasm":
llvmarch = "wasm32"
default:
llvmarch = goarch
}
llvmvendor := "unknown"
llvmos := goos
switch goos {
case "darwin":
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
// to iOS!
llvmos = "macosx10.12.0"
if llvmarch == "aarch64" {
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
llvmos = "macosx11.0.0"
}
llvmvendor = "apple"
case "wasip1":
llvmos = "wasi"
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
spec.triple = llvmarch + "-" + llvmvendor + "-" + llvmos
if llvmos == "windows" {
spec.triple += "-gnu"
} else if goarch == "arm" {
spec.triple += "-gnueabihf"
}
switch goarch {
case "386":
spec.cpu = "pentium4"
spec.features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
case "amd64":
spec.cpu = "x86-64"
spec.features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
case "arm":
spec.cpu = "generic"
switch llvmarch {
case "armv5":
spec.features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
case "armv6":
spec.features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
case "armv7":
spec.features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
}
case "arm64":
spec.cpu = "generic"
if goos == "darwin" {
spec.features = "+neon"
} else { // windows, linux
spec.features = "+neon,-fmv"
}
case "wasm":
spec.cpu = "generic"
spec.features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext"
}
return
}
*/
// -----------------------------------------------------------------------------

566
ssa/type.go Normal file
View File

@@ -0,0 +1,566 @@
/*
* 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 ssa
import (
"fmt"
"go/types"
"unsafe"
"github.com/goplus/llgo/ssa/abi"
"github.com/goplus/llvm"
)
var (
tyAny = types.NewInterfaceType(nil, nil)
NoArgsNoRet = types.NewSignatureType(nil, nil, nil, nil, nil, false)
)
// -----------------------------------------------------------------------------
type valueKind = int
const (
vkInvalid valueKind = iota
vkSigned
vkUnsigned
vkFloat
vkComplex
vkString
vkBool
vkPtr
vkFuncDecl
vkFuncPtr
vkClosure
vkBuiltin
vkPyFuncRef
vkPyVarRef
vkTuple
vkSlice
vkArray
vkMap
vkEface
vkIface
vkStruct
vkChan
)
// -----------------------------------------------------------------------------
func indexType(t types.Type) types.Type {
typ := t
retry:
switch t := typ.(type) {
case *types.Named:
typ = t.Underlying()
goto retry
case *types.Slice:
return t.Elem()
case *types.Pointer:
switch t := t.Elem().Underlying().(type) {
case *types.Array:
return t.Elem()
}
case *types.Array:
return t.Elem()
}
panic("index: type doesn't support index - " + t.String())
}
// -----------------------------------------------------------------------------
type goProgram aProgram
// Alignof returns the alignment of a variable of type T.
// Alignof must implement the alignment guarantees required by the spec.
// The result must be >= 1.
func (p *goProgram) Alignof(T types.Type) int64 {
return p.sizes.Alignof(T)
}
// Offsetsof returns the offsets of the given struct fields, in bytes.
// Offsetsof must implement the offset guarantees required by the spec.
// A negative entry in the result indicates that the struct is too large.
func (p *goProgram) Offsetsof(fields []*types.Var) (ret []int64) {
prog := Program(unsafe.Pointer(p))
ptrSize := int64(prog.PointerSize())
extra := int64(0)
ret = p.sizes.Offsetsof(fields)
for i, f := range fields {
ret[i] += extra
extra += p.extraSize(f.Type(), ptrSize)
}
return
}
// Sizeof returns the size of a variable of type T.
// Sizeof must implement the size guarantees required by the spec.
// A negative result indicates that T is too large.
func (p *goProgram) Sizeof(T types.Type) int64 {
prog := Program(unsafe.Pointer(p))
ptrSize := int64(prog.PointerSize())
baseSize := prog.sizes.Sizeof(T) + p.extraSize(T, ptrSize)
switch T.Underlying().(type) {
case *types.Struct, *types.Array:
return align(baseSize, prog.sizes.Alignof(T))
}
return baseSize
}
func (p *goProgram) extraSize(typ types.Type, ptrSize int64) (ret int64) {
retry:
switch t := typ.(type) {
case *types.Named:
if v, ok := p.gocvt.typbg.Load(namedLinkname(t)); ok && v.(Background) == InC {
return 0
}
typ = t.Underlying()
goto retry
case *types.Signature:
return ptrSize
case *types.Struct:
n := t.NumFields()
for i := 0; i < n; i++ {
f := t.Field(i)
ret += p.extraSize(f.Type(), ptrSize)
}
return
case *types.Array:
return p.extraSize(t.Elem(), ptrSize) * t.Len()
}
return 0
}
func align(x, a int64) int64 {
return (x + a - 1) &^ (a - 1)
}
// -----------------------------------------------------------------------------
type rawType struct {
Type types.Type
}
type aType struct {
ll llvm.Type
raw rawType
kind valueKind // value kind of llvm.Type
}
type Type = *aType
// RawType returns the raw type.
func (t Type) RawType() types.Type {
return t.raw.Type
}
// TypeSizes returns the sizes of the types.
func (p Program) TypeSizes(sizes types.Sizes) types.Sizes {
p.sizes = sizes
return (*goProgram)(unsafe.Pointer(p))
}
// TODO(xsw):
// how to generate platform independent code?
func (p Program) SizeOf(typ Type, n ...int64) uint64 {
size := p.td.TypeAllocSize(typ.ll)
if len(n) != 0 {
size *= uint64(n[0])
}
return size
}
// OffsetOf returns the offset of a field in a struct.
func (p Program) OffsetOf(typ Type, i int) uint64 {
return p.td.ElementOffset(typ.ll, i)
}
// SizeOf returns the size of a type.
func SizeOf(prog Program, t Type, n ...int64) Expr {
size := prog.SizeOf(t, n...)
return prog.IntVal(size, prog.Uintptr())
}
/*
func OffsetOf(prog Program, t Type, i int) Expr {
offset := prog.OffsetOf(t, i)
return prog.IntVal(offset, prog.Uintptr())
}
*/
func (p Program) PointerSize() int {
return p.ptrSize
}
func (p Program) Slice(typ Type) Type {
return p.rawType(types.NewSlice(typ.raw.Type))
}
func (p Program) Pointer(typ Type) Type {
return p.rawType(types.NewPointer(typ.raw.Type))
}
func (p Program) Elem(typ Type) Type {
elem := typ.raw.Type.Underlying().(interface {
Elem() types.Type
}).Elem()
return p.rawType(elem)
}
func (p Program) Index(typ Type) Type {
return p.rawType(indexType(typ.raw.Type))
}
func (p Program) Field(typ Type, i int) Type {
var fld *types.Var
switch t := typ.raw.Type.Underlying().(type) {
case *types.Tuple:
fld = t.At(i)
case *types.Basic:
switch t.Kind() {
case types.Complex128:
return p.Float64()
case types.Complex64:
return p.Float32()
}
panic("Field: basic type doesn't have fields")
default:
fld = t.(*types.Struct).Field(i)
}
return p.rawType(fld.Type())
}
func (p Program) rawType(raw types.Type) Type {
if v := p.typs.At(raw); v != nil {
return v.(Type)
}
ret := p.toType(raw)
p.typs.Set(raw, ret)
return ret
}
func (p Program) tyVoidPtr() llvm.Type {
if p.voidPtrTy.IsNil() {
p.voidPtrTy = llvm.PointerType(p.tyVoid(), 0)
}
return p.voidPtrTy
}
func (p Program) tyVoid() llvm.Type {
if p.voidType.IsNil() {
p.voidType = p.ctx.VoidType()
}
return p.voidType
}
func (p Program) tyInt1() llvm.Type {
if p.int1Type.IsNil() {
p.int1Type = p.ctx.Int1Type()
}
return p.int1Type
}
func (p Program) tyInt() llvm.Type {
if p.intType.IsNil() {
p.intType = llvmIntType(p.ctx, p.td.PointerSize())
}
return p.intType
}
func llvmIntType(ctx llvm.Context, size int) llvm.Type {
if size <= 4 {
return ctx.Int32Type()
}
return ctx.Int64Type()
}
func (p Program) tyInt8() llvm.Type {
if p.int8Type.IsNil() {
p.int8Type = p.ctx.Int8Type()
}
return p.int8Type
}
func (p Program) tyInt16() llvm.Type {
if p.int16Type.IsNil() {
p.int16Type = p.ctx.Int16Type()
}
return p.int16Type
}
func (p Program) tyInt32() llvm.Type {
if p.int32Type.IsNil() {
p.int32Type = p.ctx.Int32Type()
}
return p.int32Type
}
func (p Program) tyInt64() llvm.Type {
if p.int64Type.IsNil() {
p.int64Type = p.ctx.Int64Type()
}
return p.int64Type
}
/*
func (p Program) toTuple(typ *types.Tuple) Type {
return &aType{p.toLLVMTuple(typ), rawType{typ}, vkTuple}
}
*/
func (p Program) toType(raw types.Type) Type {
typ := rawType{raw}
switch t := raw.(type) {
case *types.Basic:
switch t.Kind() {
case types.Int:
return &aType{p.tyInt(), typ, vkSigned}
case types.Uint, types.Uintptr:
return &aType{p.tyInt(), typ, vkUnsigned}
case types.Bool:
return &aType{p.tyInt1(), typ, vkBool}
case types.Uint8:
return &aType{p.tyInt8(), typ, vkUnsigned}
case types.Int8:
return &aType{p.tyInt8(), typ, vkSigned}
case types.Int16:
return &aType{p.tyInt16(), typ, vkSigned}
case types.Uint16:
return &aType{p.tyInt16(), typ, vkUnsigned}
case types.Int32:
return &aType{p.tyInt32(), typ, vkSigned}
case types.Uint32:
return &aType{p.tyInt32(), typ, vkUnsigned}
case types.Int64:
return &aType{p.tyInt64(), typ, vkSigned}
case types.Uint64:
return &aType{p.tyInt64(), typ, vkUnsigned}
case types.Float32:
return &aType{p.ctx.FloatType(), typ, vkFloat}
case types.Float64:
return &aType{p.ctx.DoubleType(), typ, vkFloat}
case types.Complex64:
return &aType{p.tyComplex64(), typ, vkComplex}
case types.Complex128:
return &aType{p.tyComplex128(), typ, vkComplex}
case types.String:
return &aType{p.rtString(), typ, vkString}
case types.UnsafePointer:
return &aType{p.tyVoidPtr(), typ, vkPtr}
}
case *types.Pointer:
elem := p.rawType(t.Elem())
return &aType{llvm.PointerType(elem.ll, 0), typ, vkPtr}
case *types.Interface:
if t.Empty() {
return &aType{p.rtEface(), typ, vkEface}
}
return &aType{p.rtIface(), typ, vkIface}
case *types.Slice:
return &aType{p.rtSlice(), typ, vkSlice}
case *types.Map:
return &aType{llvm.PointerType(p.rtMap(), 0), typ, vkMap}
case *types.Struct:
ll, kind := p.toLLVMStruct(t)
return &aType{ll, typ, kind}
case *types.Named:
return p.toNamed(t)
case *types.Signature: // represents a C function pointer in raw type
return &aType{p.toLLVMFuncPtr(t), typ, vkFuncPtr}
case *types.Tuple:
return &aType{p.toLLVMTuple(t), typ, vkTuple}
case *types.Array:
elem := p.rawType(t.Elem())
return &aType{llvm.ArrayType(elem.ll, int(t.Len())), typ, vkArray}
case *types.Chan:
return &aType{llvm.PointerType(p.rtChan(), 0), typ, vkChan}
case *types.Alias:
return p.toType(types.Unalias(t))
case *types.TypeParam:
return p.toType(t.Constraint())
}
panic(fmt.Sprintf("toLLVMType: todo - %T\n", raw))
}
func (p Program) toLLVMNamedStruct(name string, raw *types.Struct) llvm.Type {
if typ, ok := p.named[name]; ok {
return typ
}
t := p.ctx.StructCreateNamed(name)
p.named[name] = t
fields := p.toLLVMFields(raw)
t.StructSetBody(fields, false)
return t
}
func (p Program) toLLVMStruct(raw *types.Struct) (ret llvm.Type, kind valueKind) {
fields := p.toLLVMFields(raw)
ret = p.ctx.StructType(fields, false)
if isClosure(raw) {
kind = vkClosure
} else {
kind = vkStruct
}
return
}
func isClosure(raw *types.Struct) bool {
n := raw.NumFields()
if n == 2 {
f1, f2 := raw.Field(0), raw.Field(1)
if _, ok := f1.Type().(*types.Signature); ok && f1.Name() == "$f" {
return f2.Type() == types.Typ[types.UnsafePointer] && f2.Name() == "$data"
}
}
return false
}
func (p Program) toLLVMFields(raw *types.Struct) (fields []llvm.Type) {
n := raw.NumFields()
if n > 0 {
fields = make([]llvm.Type, n)
for i := 0; i < n; i++ {
fields[i] = p.rawType(p.patch(raw.Field(i).Type())).ll
}
}
return
}
func (p Program) toLLVMTuple(t *types.Tuple) llvm.Type {
return p.ctx.StructType(p.toLLVMTypes(t, t.Len()), false)
}
func (p Program) toLLVMTypes(t *types.Tuple, n int) (ret []llvm.Type) {
if n > 0 {
ret = make([]llvm.Type, n)
for i := 0; i < n; i++ {
ret[i] = p.rawType(p.patch(t.At(i).Type())).ll
}
}
return
}
func (p Program) toLLVMFunc(sig *types.Signature) llvm.Type {
tParams := sig.Params()
n := tParams.Len()
hasVArg := hasNameValist(sig)
if hasVArg {
n--
}
params := p.toLLVMTypes(tParams, n)
out := sig.Results()
var ret llvm.Type
switch nret := out.Len(); nret {
case 0:
ret = p.tyVoid()
case 1:
ret = p.rawType(out.At(0).Type()).ll
default:
ret = p.toLLVMTuple(out)
}
return llvm.FunctionType(ret, params, hasVArg)
}
func (p Program) toLLVMFuncPtr(sig *types.Signature) llvm.Type {
ft := p.toLLVMFunc(sig)
return llvm.PointerType(ft, 0)
}
func (p Program) retType(raw *types.Signature) Type {
out := raw.Results()
switch n := out.Len(); n {
case 0:
return p.Void()
case 1:
return p.rawType(out.At(0).Type())
default:
return &aType{p.toLLVMTuple(out), rawType{out}, vkTuple}
}
}
func (p Program) llvmNameOf(named *types.Named) (name string) {
name = NameOf(named)
if obj := named.Obj(); obj != nil && obj.Parent() != nil && obj.Parent() != obj.Pkg().Scope() {
index := p.fnnamed[name]
p.fnnamed[name] = index + 1
name += fmt.Sprintf("#%v", index)
}
return name
}
func (p Program) toNamed(raw *types.Named) Type {
switch t := raw.Underlying().(type) {
case *types.Struct:
name := p.llvmNameOf(raw)
kind := vkStruct
if isClosure(t) {
kind = vkClosure
}
return &aType{p.toLLVMNamedStruct(name, t), rawType{raw}, kind}
default:
typ := p.rawType(t)
return &aType{typ.ll, rawType{raw}, typ.kind}
}
}
// NameOf returns the full name of a named type.
func NameOf(typ *types.Named) string {
return abi.FullName(typ.Obj().Pkg(), abi.NamedName(typ))
}
// FullName returns the full name of a package member.
func FullName(pkg *types.Package, name string) string {
return abi.FullName(pkg, name)
}
// PathOf returns the package path of the specified package.
func PathOf(pkg *types.Package) string {
return abi.PathOf(pkg)
}
// FuncName:
// - func: pkg.name
// - method: pkg.T.name, pkg.(*T).name
func FuncName(pkg *types.Package, name string, recv *types.Var, org bool) string {
if recv != nil {
var tName string
t := recv.Type()
if org {
if tp, ok := t.(*types.Pointer); ok {
tName = "(*" + tp.Elem().(*types.Named).Obj().Name() + ")"
} else {
tName = t.(*types.Named).Obj().Name()
}
} else {
if tp, ok := t.(*types.Pointer); ok {
tName = "(*" + abi.NamedName(tp.Elem().(*types.Named)) + ")"
} else {
tName = abi.NamedName(t.(*types.Named))
}
}
return PathOf(pkg) + "." + tName + "." + name
}
ret := FullName(pkg, name)
return ret
}
func TypeArgs(typeArgs []types.Type) string {
return abi.TypeArgs(typeArgs)
}
// -----------------------------------------------------------------------------

308
ssa/type_cvt.go Normal file
View File

@@ -0,0 +1,308 @@
/*
* 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 ssa
import (
"fmt"
"go/token"
"go/types"
"sync"
"unsafe"
)
// -----------------------------------------------------------------------------
type goTypes struct {
typs map[unsafe.Pointer]unsafe.Pointer
typbg sync.Map
}
func newGoTypes() goTypes {
typs := make(map[unsafe.Pointer]unsafe.Pointer)
return goTypes{typs: typs}
}
type Background int
const (
inUnknown Background = iota
InGo
InC
InPython
)
// Type convert a Go/C type into raw type.
// C type = raw type
// Go type: convert to raw type (because of closure)
func (p Program) Type(typ types.Type, bg Background) Type {
if bg == InGo {
typ, _ = p.gocvt.cvtType(typ)
}
return p.rawType(typ)
}
// FuncDecl converts a Go/C function declaration into raw type.
func (p Program) FuncDecl(sig *types.Signature, bg Background) Type {
recv := sig.Recv()
if bg == InGo {
sig = p.gocvt.cvtFunc(sig, recv)
} else if recv != nil { // even in C, we need to add ctx for method
sig = FuncAddCtx(recv, sig)
}
return &aType{p.toLLVMFunc(sig), rawType{sig}, vkFuncDecl}
}
// Closure creates a closture type for a function.
func (p Program) Closure(sig *types.Signature) Type {
closure := p.gocvt.cvtClosure(sig)
return p.rawType(closure)
}
func (p goTypes) cvtType(typ types.Type) (raw types.Type, cvt bool) {
switch t := typ.(type) {
case *types.Basic:
case *types.Pointer:
if elem, cvt := p.cvtType(t.Elem()); cvt {
return types.NewPointer(elem), true
}
case *types.Interface:
return p.cvtInterface(t)
case *types.Slice:
if elem, cvt := p.cvtType(t.Elem()); cvt {
return types.NewSlice(elem), true
}
case *types.Map:
key, cvt1 := p.cvtType(t.Key())
elem, cvt2 := p.cvtType(t.Elem())
if cvt1 || cvt2 {
return types.NewMap(key, elem), true
}
case *types.Struct:
if isClosure(t) {
return typ, false
}
return p.cvtStruct(t)
case *types.Named:
if v, ok := p.typbg.Load(namedLinkname(t)); ok && v.(Background) == InC {
break
}
return p.cvtNamed(t)
case *types.Signature:
return p.cvtClosure(t), true
case *types.Array:
if elem, cvt := p.cvtType(t.Elem()); cvt {
return types.NewArray(elem, t.Len()), true
}
case *types.Chan:
if elem, cvt := p.cvtType(t.Elem()); cvt {
return types.NewChan(t.Dir(), elem), true
}
case *types.Tuple:
return p.cvtTuple(t)
case *types.TypeParam:
return typ.Underlying(), false
case *types.Alias:
return p.cvtType(types.Unalias(t))
default:
panic(fmt.Sprintf("cvtType: unexpected type - %T", typ))
}
return typ, false
}
func namedLinkname(t *types.Named) string {
obj := t.Obj()
if obj.Pkg() != nil {
return obj.Pkg().Path() + "." + obj.Name()
}
return obj.Name()
}
func (p goTypes) cvtNamed(t *types.Named) (raw *types.Named, cvt bool) {
if v, ok := p.typs[unsafe.Pointer(t)]; ok {
raw = (*types.Named)(v)
cvt = t != raw
return
}
n := t.NumMethods()
methods := make([]*types.Func, n)
for i := 0; i < n; i++ {
m := t.Method(i) // don't need to convert method signature
methods[i] = m
}
named := types.NewNamed(t.Obj(), types.Typ[types.Int], methods)
if tp := t.TypeParams(); tp != nil {
list := make([]*types.TypeParam, tp.Len())
for i := 0; i < tp.Len(); i++ {
param := tp.At(i)
list[i] = types.NewTypeParam(param.Obj(), param.Constraint())
}
named.SetTypeParams(list)
}
p.typs[unsafe.Pointer(t)] = unsafe.Pointer(t)
if tund, cvt := p.cvtType(t.Underlying()); cvt {
named.SetUnderlying(tund)
if typ, ok := Instantiate(named, t); ok {
named = typ.(*types.Named)
}
p.typs[unsafe.Pointer(t)] = unsafe.Pointer(named)
return named, true
}
return t, false
}
func Instantiate(orig types.Type, t *types.Named) (types.Type, bool) {
if tp := t.TypeArgs(); tp != nil {
targs := make([]types.Type, tp.Len())
for i := 0; i < tp.Len(); i++ {
targs[i] = tp.At(i)
}
if typ, err := types.Instantiate(nil, orig, targs, true); err == nil {
return typ, true
}
}
return orig, false
}
func (p goTypes) cvtClosure(sig *types.Signature) *types.Struct {
raw := p.cvtFunc(sig, nil)
flds := []*types.Var{
types.NewField(token.NoPos, nil, "$f", raw, false),
types.NewField(token.NoPos, nil, "$data", types.Typ[types.UnsafePointer], false),
}
return types.NewStruct(flds, nil)
}
func (p goTypes) cvtFunc(sig *types.Signature, recv *types.Var) (raw *types.Signature) {
if recv != nil {
sig = FuncAddCtx(recv, sig)
}
params, cvt1 := p.cvtTuple(sig.Params())
results, cvt2 := p.cvtTuple(sig.Results())
if cvt1 || cvt2 {
return types.NewSignatureType(nil, nil, nil, params, results, sig.Variadic())
}
return sig
}
func (p goTypes) cvtTuple(t *types.Tuple) (*types.Tuple, bool) {
n := t.Len()
vars := make([]*types.Var, n)
needcvt := false
for i := 0; i < n; i++ {
v := t.At(i)
if t, cvt := p.cvtType(v.Type()); cvt {
v = types.NewParam(v.Pos(), v.Pkg(), v.Name(), t)
needcvt = true
}
vars[i] = v
}
if needcvt {
return types.NewTuple(vars...), true
}
return t, false
}
func (p goTypes) cvtExplicitMethods(typ *types.Interface) ([]*types.Func, bool) {
n := typ.NumExplicitMethods()
methods := make([]*types.Func, n)
needcvt := false
for i := 0; i < n; i++ {
m := typ.ExplicitMethod(i)
sig := m.Type().(*types.Signature)
if raw := p.cvtFunc(sig, nil); sig != raw {
m = types.NewFunc(m.Pos(), m.Pkg(), m.Name(), raw)
needcvt = true
}
methods[i] = m
}
return methods, needcvt
}
func (p goTypes) cvtEmbeddedTypes(typ *types.Interface) ([]types.Type, bool) {
n := typ.NumEmbeddeds()
embeddeds := make([]types.Type, n)
needcvt := false
for i := 0; i < n; i++ {
t := typ.EmbeddedType(i)
if raw, cvt := p.cvtType(t); cvt {
t = raw
needcvt = true
}
embeddeds[i] = t
}
return embeddeds, needcvt
}
func (p goTypes) cvtInterface(typ *types.Interface) (raw *types.Interface, cvt bool) {
if v, ok := p.typs[unsafe.Pointer(typ)]; ok {
raw = (*types.Interface)(v)
cvt = typ != raw
return
}
defer func() {
p.typs[unsafe.Pointer(typ)] = unsafe.Pointer(raw)
}()
methods, cvt1 := p.cvtExplicitMethods(typ)
embeddeds, cvt2 := p.cvtEmbeddedTypes(typ)
if cvt1 || cvt2 {
return types.NewInterfaceType(methods, embeddeds), true
}
return typ, false
}
func (p goTypes) cvtStruct(typ *types.Struct) (raw *types.Struct, cvt bool) {
if v, ok := p.typs[unsafe.Pointer(typ)]; ok {
raw = (*types.Struct)(v)
cvt = typ != raw
return
}
defer func() {
p.typs[unsafe.Pointer(typ)] = unsafe.Pointer(raw)
}()
n := typ.NumFields()
flds := make([]*types.Var, n)
needcvt := false
for i := 0; i < n; i++ {
f := typ.Field(i)
if t, cvt := p.cvtType(f.Type()); cvt {
f = types.NewField(f.Pos(), f.Pkg(), f.Name(), t, f.Anonymous())
needcvt = true
}
flds[i] = f
}
if needcvt {
return types.NewStruct(flds, nil), true
}
return typ, false
}
// -----------------------------------------------------------------------------
// FuncAddCtx adds a ctx to a function signature.
func FuncAddCtx(ctx *types.Var, sig *types.Signature) *types.Signature {
tParams := sig.Params()
nParams := tParams.Len()
params := make([]*types.Var, nParams+1)
params[0] = ctx
for i := 0; i < nParams; i++ {
params[i+1] = tParams.At(i)
}
return types.NewSignatureType(
nil, nil, nil, types.NewTuple(params...), sig.Results(), sig.Variadic())
}
// -----------------------------------------------------------------------------