diff --git a/README.md b/README.md index cdb38794..97e95bb9 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ See [github.com/goplus/llgo/py](https://pkg.go.dev/github.com/goplus/llgo/py) fo LLGo can easily import any libraries from the C ecosystem. Currently, this import process is still manual, but in the future, it will be automated similar to Python library imports. -The currently imported libraries include: +The currently supported libraries include: * [llama2.c](https://pkg.go.dev/github.com/goplus/llgo/c/llama2) * [cjson](https://pkg.go.dev/github.com/goplus/llgo/c/cjson) diff --git a/go.mod b/go.mod index d1455dbd..5e702b52 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/goplus/llgo -go 1.18 +go 1.20 require ( github.com/aykevl/go-wasm v0.0.1 diff --git a/internal/abi/llgo_autogen.lla b/internal/abi/llgo_autogen.lla index cccf2f81..4f8bcd99 100644 Binary files a/internal/abi/llgo_autogen.lla and b/internal/abi/llgo_autogen.lla differ diff --git a/internal/abi/type.go b/internal/abi/type.go index 549a135a..17b61967 100644 --- a/internal/abi/type.go +++ b/internal/abi/type.go @@ -213,36 +213,6 @@ type StructType struct { Fields []StructField } -// Name is an encoded type Name with optional extra data. -// -// The first byte is a bit field containing: -// -// 1<<0 the name is exported -// 1<<1 tag data follows the name -// 1<<2 pkgPath nameOff follows the name and tag -// 1<<3 the name is of an embedded (a.k.a. anonymous) field -// -// Following that, there is a varint-encoded length of the name, -// followed by the name itself. -// -// If tag data is present, it also has a varint-encoded length -// followed by the tag itself. -// -// If the import path follows, then 4 bytes at the end of -// the data form a nameOff. The import path is only set for concrete -// methods that are defined in a different package than their type. -// -// If a name starts with "*", then the exported bit represents -// whether the pointed to type is exported. -// -// Note: this encoding must match here and in: -// cmd/compile/internal/reflectdata/reflect.go -// cmd/link/internal/ld/decodesym.go - -type Name struct { - Bytes *byte -} - type InterfaceType struct { Type PkgPath Name // import path @@ -330,3 +300,168 @@ func (t *Type) InterfaceType() *InterfaceType { } // ----------------------------------------------------------------------------- + +// addChecked returns p+x. +// +// The whySafe string is ignored, so that the function still inlines +// as efficiently as p+x, but all call sites should use the string to +// record why the addition is safe, which is to say why the addition +// does not cause x to advance to the very end of p's allocation +// and therefore point incorrectly at the next block in memory. +func addChecked(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer { + _ = whySafe + return unsafe.Pointer(uintptr(p) + x) +} + +// Name is an encoded type Name with optional extra data. +// +// The first byte is a bit field containing: +// +// 1<<0 the name is exported +// 1<<1 tag data follows the name +// 1<<2 pkgPath nameOff follows the name and tag +// 1<<3 the name is of an embedded (a.k.a. anonymous) field +// +// Following that, there is a varint-encoded length of the name, +// followed by the name itself. +// +// If tag data is present, it also has a varint-encoded length +// followed by the tag itself. +// +// If the import path follows, then 4 bytes at the end of +// the data form a nameOff. The import path is only set for concrete +// methods that are defined in a different package than their type. +// +// If a name starts with "*", then the exported bit represents +// whether the pointed to type is exported. +// +// Note: this encoding must match here and in: +// cmd/compile/internal/reflectdata/reflect.go +// cmd/link/internal/ld/decodesym.go + +type Name struct { + Bytes *byte +} + +// DataChecked does pointer arithmetic on n's Bytes, and that arithmetic is asserted to +// be safe for the reason in whySafe (which can appear in a backtrace, etc.) +func (n Name) DataChecked(off int, whySafe string) *byte { + return (*byte)(addChecked(unsafe.Pointer(n.Bytes), uintptr(off), whySafe)) +} + +// Data does pointer arithmetic on n's Bytes, and that arithmetic is asserted to +// be safe because the runtime made the call (other packages use DataChecked) +func (n Name) Data(off int) *byte { + return (*byte)(addChecked(unsafe.Pointer(n.Bytes), uintptr(off), "the runtime doesn't need to give you a reason")) +} + +// IsExported returns "is n exported?" +func (n Name) IsExported() bool { + return (*n.Bytes)&(1<<0) != 0 +} + +// HasTag returns true iff there is tag data following this name +func (n Name) HasTag() bool { + return (*n.Bytes)&(1<<1) != 0 +} + +// IsEmbedded returns true iff n is embedded (an anonymous field). +func (n Name) IsEmbedded() bool { + return (*n.Bytes)&(1<<3) != 0 +} + +// ReadVarint parses a varint as encoded by encoding/binary. +// It returns the number of encoded bytes and the encoded value. +func (n Name) ReadVarint(off int) (int, int) { + v := 0 + for i := 0; ; i++ { + x := *n.DataChecked(off+i, "read varint") + v += int(x&0x7f) << (7 * i) + if x&0x80 == 0 { + return i + 1, v + } + } +} + +// IsBlank indicates whether n is "_". +func (n Name) IsBlank() bool { + if n.Bytes == nil { + return false + } + _, l := n.ReadVarint(1) + return l == 1 && *n.Data(2) == '_' +} + +// writeVarint writes n to buf in varint form. Returns the +// number of bytes written. n must be nonnegative. +// Writes at most 10 bytes. +func writeVarint(buf []byte, n int) int { + for i := 0; ; i++ { + b := byte(n & 0x7f) + n >>= 7 + if n == 0 { + buf[i] = b + return i + 1 + } + buf[i] = b | 0x80 + } +} + +// Name returns the tag string for n, or empty if there is none. +func (n Name) Name() string { + if n.Bytes == nil { + return "" + } + i, l := n.ReadVarint(1) + return unsafe.String(n.DataChecked(1+i, "non-empty string"), l) +} + +// Tag returns the tag string for n, or empty if there is none. +func (n Name) Tag() string { + if !n.HasTag() { + return "" + } + i, l := n.ReadVarint(1) + i2, l2 := n.ReadVarint(1 + i + l) + return unsafe.String(n.DataChecked(1+i+l+i2, "non-empty string"), l2) +} + +func NewName(n, tag string, exported, embedded bool) Name { + if len(n) >= 1<<29 { + panic("abi.NewName: name too long: " + n[:1024] + "...") + } + if len(tag) >= 1<<29 { + panic("abi.NewName: tag too long: " + tag[:1024] + "...") + } + var nameLen [10]byte + var tagLen [10]byte + nameLenLen := writeVarint(nameLen[:], len(n)) + tagLenLen := writeVarint(tagLen[:], len(tag)) + + var bits byte + l := 1 + nameLenLen + len(n) + if exported { + bits |= 1 << 0 + } + if len(tag) > 0 { + l += tagLenLen + len(tag) + bits |= 1 << 1 + } + if embedded { + bits |= 1 << 3 + } + + b := make([]byte, l) + b[0] = bits + copy(b[1:], nameLen[:nameLenLen]) + copy(b[1+nameLenLen:], n) + if len(tag) > 0 { + tb := b[1+nameLenLen+len(n):] + copy(tb, tagLen[:tagLenLen]) + copy(tb[tagLenLen:], tag) + } + + return Name{Bytes: &b[0]} +} + +// ----------------------------------------------------------------------------- diff --git a/internal/runtime/llgo_autogen.lla b/internal/runtime/llgo_autogen.lla index 6257d02d..94569d30 100644 Binary files a/internal/runtime/llgo_autogen.lla and b/internal/runtime/llgo_autogen.lla differ diff --git a/internal/runtime/z_iface.go b/internal/runtime/z_iface.go index e8d8c747..c64449ca 100644 --- a/internal/runtime/z_iface.go +++ b/internal/runtime/z_iface.go @@ -34,7 +34,7 @@ var ( type Interface = iface -func MakeAnyInt(typ *Type, data uintptr) Interface { +func MakeAnyIntptr(typ *Type, data uintptr) Interface { tab := &itab{inter: TyAny, _type: typ, hash: 0, fun: [1]uintptr{0}} return Interface{ tab: tab, data: unsafe.Pointer(data), diff --git a/internal/runtime/z_string.go b/internal/runtime/z_string.go index b137447d..6f9ad609 100644 --- a/internal/runtime/z_string.go +++ b/internal/runtime/z_string.go @@ -40,6 +40,7 @@ func EmptyString() String { return String{nil, 0} } +// TODO(xsw): unsafe.String // NewString creates a new string. func NewString(data unsafe.Pointer, len int) String { return String{data, len} diff --git a/ssa/_abi/abi.go b/ssa/_abi/abi.go new file mode 100644 index 00000000..65578218 --- /dev/null +++ b/ssa/_abi/abi.go @@ -0,0 +1,73 @@ +/* + * 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" +) + +// Builder is a helper for constructing ABI types. +type Builder struct { + h hash.Hash + buf []byte +} + +// New creates a new ABI type Builder. +func New() *Builder { + h := sha256.New() + buf := make([]byte, sha256.Size) + return &Builder{h, buf} +} + +// TypeName returns the ABI type name for the specified type. +func (b *Builder) TypeName(t types.Type) string { + switch t := t.(type) { + case *types.Basic: + return t.Name() + case *types.Pointer: + return "*" + b.TypeName(t.Elem()) + case *types.Struct: + return b.StructName(t) + } + panic("todo") +} + +// StructName returns the ABI type name for the specified struct type. +func (b *Builder) StructName(t *types.Struct) string { + hash := b.structHash(t) + return "struct$" + base64.RawURLEncoding.EncodeToString(hash) +} + +func (b *Builder) structHash(t *types.Struct) []byte { + h := b.h + h.Reset() + n := t.NumFields() + fmt.Fprintln(h, "struct", n) + for i := 0; i < n; i++ { + f := t.Field(i) + name := f.Name() + if f.Embedded() { + name = "-" + } + fmt.Fprintln(h, name, b.TypeName(f.Type())) + } + return h.Sum(b.buf[:0]) +} diff --git a/ssa/cl_test.go b/ssa/cl_test.go index 4d79ec49..21c8bf85 100644 --- a/ssa/cl_test.go +++ b/ssa/cl_test.go @@ -36,6 +36,9 @@ func TestFromTestdata(t *testing.T) { func TestRuntime(t *testing.T) { cltest.Pkg(t, "github.com/goplus/llgo/internal/runtime", "../internal/runtime/llgo_autogen.ll") +} + +func TestAbi(t *testing.T) { cltest.Pkg(t, "github.com/goplus/llgo/internal/abi", "../internal/abi/llgo_autogen.ll") } diff --git a/ssa/expr.go b/ssa/expr.go index 722060a7..1f18f269 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -665,7 +665,18 @@ func (b Builder) Alloc(elem Type, heap bool) (ret Expr) { return } -// Alloca allocates space for n bytes. +// AllocU allocates uninitialized space for n*sizeof(elem) bytes. +func (b Builder) AllocU(elem Type, n ...int64) (ret Expr) { + if debugInstr { + log.Printf("AllocU %v, %v\n", elem.raw.Type, n) + } + size := b.SizeOf(elem, n...) + ret = b.InlineCall(b.Pkg.rtFunc("AllocU"), size) + ret.Type = b.Prog.Pointer(elem) + return +} + +// Alloca allocates uninitialized space for n bytes. func (b Builder) Alloca(n Expr) (ret Expr) { if debugInstr { log.Printf("Alloca %v\n", n.impl) @@ -984,9 +995,10 @@ func (b Builder) BuiltinCall(fn string, args ...Expr) (ret Expr) { for i, arg := range args { if ln && i > 0 { b.InlineCall(b.Pkg.rtFunc("PrintString"), b.Str(" ")) + // TODO(visualfc): maybe use PrintCStr is more efficient } var fn string - var typ Type + var typ Type // TODO(visualfc): typ uninitialized in some cases switch arg.kind { case vkBool: fn = "PrintBool" @@ -1002,6 +1014,8 @@ func (b Builder) BuiltinCall(fn string, args ...Expr) (ret Expr) { case vkSlice: fn = "PrintSlice" case vkPtr, vkFuncPtr, vkFuncDecl, vkClosure, vkPyVarRef, vkPyFuncRef: + // TODO(visualfc): vkClosure is not a pointer + // TODO(visualfc): vkPyVarRef, vkPyFuncRef is pointer of pointer fn = "PrintPointer" typ = b.Prog.VoidPtr() case vkString: @@ -1037,8 +1051,11 @@ func (b Builder) BuiltinCall(fn string, args ...Expr) (ret Expr) { } } } + case "String": // unsafe.String + // TODO(xsw): make this a builtin + return b.InlineCall(b.Pkg.rtFunc("NewString"), args[0], args[1]) } - panic("todo") + panic("todo: " + fn) } // ----------------------------------------------------------------------------- diff --git a/ssa/interface.go b/ssa/interface.go index ee9a451e..16004054 100644 --- a/ssa/interface.go +++ b/ssa/interface.go @@ -27,19 +27,29 @@ import ( // ----------------------------------------------------------------------------- -// AbiBasic returns the abi type of the specified basic kind. -func (b Builder) AbiBasic(kind types.BasicKind) Expr { +// abiBasic returns the abi type of the specified basic kind. +func (b Builder) abiBasic(kind types.BasicKind) Expr { return b.InlineCall(b.Pkg.rtFunc("Basic"), b.Prog.Val(int(kind))) } /* -// AbiStruct returns the abi type of the specified struct type. -func (b Builder) AbiStruct(t *types.Struct) Expr { - panic("todo") - // return b.InlineCall(b.Pkg.rtFunc("Struct"), b.Prog.Val(t.NumFields())) +// abiStruct returns the abi type of the specified struct type. +func (b Builder) abiStruct(t *types.Struct) Expr { + // name := "__llgo_" + b.Prog.abi.StructName(t) } */ +// AbiType returns the abi type of the specified type. +func (b Builder) AbiType(t Type) Expr { + switch tx := t.raw.Type.(type) { + case *types.Basic: + return b.abiBasic(tx.Kind()) + //case *types.Struct: + // return b.abiStruct(tx) + } + panic("todo") +} + // ----------------------------------------------------------------------------- // MakeInterface constructs an instance of an interface type from a @@ -57,37 +67,66 @@ func (b Builder) AbiStruct(t *types.Struct) Expr { // t1 = make interface{} <- int (42:int) // t2 = make Stringer <- t0 func (b Builder) MakeInterface(tinter Type, x Expr) (ret Expr) { - raw := tinter.raw.Type + rawIntf := tinter.raw.Type.Underlying().(*types.Interface) if debugInstr { - log.Printf("MakeInterface %v, %v\n", raw, x.impl) + log.Printf("MakeInterface %v, %v\n", rawIntf, x.impl) } prog := b.Prog - pkg := b.Pkg - switch tx := x.raw.Type.Underlying().(type) { + typ := x.Type + switch tx := typ.raw.Type.Underlying().(type) { case *types.Basic: kind := tx.Kind() switch { case kind >= types.Bool && kind <= types.Uintptr: - t := b.AbiBasic(kind) - tptr := prog.Uintptr() - vptr := Expr{llvm.CreateIntCast(b.impl, x.impl, tptr.ll), tptr} - return Expr{b.InlineCall(pkg.rtFunc("MakeAnyInt"), t, vptr).impl, tinter} + if prog.is32Bits && (kind == types.Int64 || kind == types.Uint64) { + return b.makeIntfAlloc(tinter, rawIntf, typ, x) + } + return b.makeIntfByIntptr(tinter, rawIntf, typ, x.impl) case kind == types.Float32: - t := b.AbiBasic(kind) - tptr := prog.Uintptr() - i32 := llvm.CreateBitCast(b.impl, x.impl, prog.tyInt32()) // TODO(xsw): more effective - vptr := Expr{llvm.CreateIntCast(b.impl, i32, tptr.ll), tptr} - return Expr{b.InlineCall(pkg.rtFunc("MakeAnyInt"), t, vptr).impl, tinter} + i32 := llvm.CreateBitCast(b.impl, x.impl, prog.tyInt32()) + return b.makeIntfByIntptr(tinter, rawIntf, typ, i32) case kind == types.Float64: - t := b.AbiBasic(kind) - tptr := prog.Uintptr() - vptr := Expr{llvm.CreateBitCast(b.impl, x.impl, tptr.ll), tptr} - return Expr{b.InlineCall(pkg.rtFunc("MakeAnyInt"), t, vptr).impl, tinter} + if prog.is32Bits { + return b.makeIntfAlloc(tinter, rawIntf, typ, x) + } + i64 := llvm.CreateBitCast(b.impl, x.impl, prog.tyInt64()) + return b.makeIntfByIntptr(tinter, rawIntf, typ, i64) case kind == types.String: - return Expr{b.InlineCall(pkg.rtFunc("MakeAnyString"), x).impl, tinter} + return Expr{b.InlineCall(b.Pkg.rtFunc("MakeAnyString"), x).impl, tinter} } - // case *types.Struct: - // t := b.AbiStruct(tx) + /* case *types.Struct: + size := int(prog.SizeOf(typ)) + if size > prog.PointerSize() { + return b.makeIntfAlloc(tinter, rawIntf, typ, x) + } + tv := prog.ctx.IntType(size * 8) + iv := llvm.CreateBitCast(b.impl, x.impl, tv) + return b.makeIntfByIntptr(tinter, rawIntf, typ, iv) */ + } + panic("todo") +} + +func (b Builder) makeIntfAlloc(tinter Type, rawIntf *types.Interface, typ Type, x Expr) (ret Expr) { + vptr := b.AllocU(typ) + b.Store(vptr, x) + return b.makeIntfByPtr(tinter, rawIntf, typ, vptr) +} + +func (b Builder) makeIntfByPtr(tinter Type, rawIntf *types.Interface, typ Type, vptr Expr) (ret Expr) { + if rawIntf.Empty() { + ret = b.InlineCall(b.Pkg.rtFunc("MakeAny"), b.AbiType(typ), vptr) + ret.Type = tinter + return + } + panic("todo") +} + +func (b Builder) makeIntfByIntptr(tinter Type, rawIntf *types.Interface, typ Type, x llvm.Value) (ret Expr) { + if rawIntf.Empty() { + tptr := b.Prog.Uintptr() + x = llvm.CreateIntCast(b.impl, x, tptr.ll) + impl := b.InlineCall(b.Pkg.rtFunc("MakeAnyIntptr"), b.AbiType(typ), Expr{x, tptr}).impl + return Expr{impl, tinter} } panic("todo") } diff --git a/ssa/package.go b/ssa/package.go index bc9cb7cc..ca8ec18c 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -99,6 +99,7 @@ type aProgram struct { ctx llvm.Context typs typeutil.Map // rawType -> Type gocvt goTypes + //abi *abi.Builder rt *types.Package rtget func() *types.Package @@ -158,6 +159,7 @@ type aProgram struct { NeedRuntime bool NeedPyInit bool + is32Bits bool } // A Program presents a program. @@ -180,7 +182,12 @@ func NewProgram(target *Target) Program { // TODO(xsw): Finalize may cause panic, so comment it. ctx.Finalize() */ - return &aProgram{ctx: ctx, gocvt: newGoTypes(), target: target, td: td, named: make(map[string]llvm.Type)} + is32Bits := (td.PointerSize() == 4) + return &aProgram{ + ctx: ctx, gocvt: newGoTypes(), // abi: abi.New(), + target: target, td: td, is32Bits: is32Bits, + named: make(map[string]llvm.Type), + } } // SetPython sets the Python package. diff --git a/ssa/type.go b/ssa/type.go index 17f5bd07..b7307c39 100644 --- a/ssa/type.go +++ b/ssa/type.go @@ -99,6 +99,10 @@ func (p Program) SizeOf(typ Type, n ...int64) uint64 { return size } +func (p Program) PointerSize() int { + return p.td.PointerSize() +} + func (p Program) Slice(typ Type) Type { return p.rawType(types.NewSlice(typ.raw.Type)) }