diff --git a/cl/compile_test.go b/cl/compile_test.go index 2d601ae2..f1841da6 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -28,6 +28,12 @@ func testCompile(t *testing.T, src, expected string) { cltest.TestCompileEx(t, src, "foo.go", expected) } +/* +func TestFromTestgo(t *testing.T) { + cltest.FromDir(t, "strucintf", "./_testgo", false) +} +*/ + func TestFromTestpy(t *testing.T) { cltest.FromDir(t, "", "./_testpy", false) } diff --git a/ssa/datastruct.go b/ssa/datastruct.go new file mode 100644 index 00000000..75e904c6 --- /dev/null +++ b/ssa/datastruct.go @@ -0,0 +1,337 @@ +/* + * 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" + "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} +} + +// StringData returns the data pointer of a string. +func (b Builder) StringData(x Expr) Expr { + if debugInstr { + log.Printf("StringData %v\n", x.impl) + } + prog := b.Prog + ptr := llvm.CreateExtractValue(b.impl, x.impl, 0) + return Expr{ptr, 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) + } + prog := b.Prog + ptr := llvm.CreateExtractValue(b.impl, x.impl, 1) + return Expr{ptr, 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) + } + prog := b.Prog + ptr := llvm.CreateExtractValue(b.impl, x.impl, 0) + return Expr{ptr, prog.CStr()} +} + +// SliceLen returns the length of a slice. +func (b Builder) SliceLen(x Expr) Expr { + if debugInstr { + log.Printf("SliceLen %v\n", x.impl) + } + prog := b.Prog + ptr := llvm.CreateExtractValue(b.impl, x.impl, 1) + return Expr{ptr, 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) + } + prog := b.Prog + ptr := llvm.CreateExtractValue(b.impl, x.impl, 2) + return Expr{ptr, 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) + } + idx = b.checkIndex(idx) + prog := b.Prog + telem := prog.Index(x.Type) + pt := prog.Pointer(telem) + switch x.raw.Type.Underlying().(type) { + case *types.Slice: + ptr := b.SliceData(x) + indices := []llvm.Value{idx.impl} + return Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, ptr.impl, indices), pt} + } + // case *types.Pointer: + indices := []llvm.Value{idx.impl} + return Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, x.impl, indices), pt} +} + +// check index >= 0 and size to uint +func (b Builder) checkIndex(idx Expr) Expr { + if needsNegativeCheck(idx) { + check := Expr{b.impl.CreateICmp(llvm.IntSLT, idx.impl, llvm.ConstInt(idx.ll, 0, false), ""), b.Prog.Bool()} + b.InlineCall(b.Func.Pkg.rtFunc("AssertIndexRange"), check) + } + typ := b.Prog.Uint() + if b.Prog.SizeOf(idx.Type) < b.Prog.SizeOf(typ) { + idx.Type = typ + idx.impl = castUintptr(b, idx.impl, typ) + } + 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, addr func(Expr) Expr) Expr { + if debugInstr { + log.Printf("Index %v, %v\n", x.impl, idx.impl) + } + prog := b.Prog + var telem Type + var ptr Expr + 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) + case *types.Array: + telem = prog.Index(x.Type) + if addr != nil { + ptr = addr(x) + } else { + size := b.SizeOf(telem, t.Len()) + ptr = b.Alloca(size) + b.Store(ptr, x) + } + } + idx = b.checkIndex(idx) + 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 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) + } + // TODO(xsw) + // panic("todo") + return +} + +// 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 + pkg := b.Func.Pkg + var nCap Expr + var nEltSize Expr + var base Expr + if low.IsNil() { + 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(pkg.rtFunc("NewStringSlice"), x, low, high).impl + return + case *types.Slice: + nEltSize = b.SizeOf(prog.Index(x.Type)) + nCap = b.SliceCap(x) + if high.IsNil() { + high = b.SliceCap(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 = b.SizeOf(elem) + nCap = prog.IntVal(uint64(te.Len()), prog.Int()) + if high.IsNil() { + high = nCap + } + base = x + } + } + if max.IsNil() { + max = nCap + } + ret.impl = b.InlineCall(pkg.rtFunc("NewSlice3"), base, nEltSize, nCap, low, high, max).impl + return +} + +// ----------------------------------------------------------------------------- + +// 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) + } + pkg := b.Func.Pkg + ret.Type = t + ret.impl = b.InlineCall(pkg.rtFunc("MakeSmallMap")).impl + // TODO(xsw): nReserve + return +} + +// 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) + } + pkg := b.Func.Pkg + if cap.IsNil() { + cap = len + } + elemSize := b.SizeOf(b.Prog.Index(t)) + size := b.BinOp(token.MUL, cap, elemSize) + ptr := b.InlineCall(pkg.rtFunc("AllocZ"), size) + ret.impl = b.InlineCall(pkg.rtFunc("NewSlice"), ptr, len, cap).impl + ret.Type = t + return +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/expr.go b/ssa/expr.go index 8132b989..8106abf3 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -24,7 +24,6 @@ import ( "go/types" "log" - "github.com/goplus/llgo/internal/abi" "github.com/goplus/llvm" ) @@ -423,6 +422,16 @@ func checkExpr(v Expr, t types.Type, b Builder) Expr { return v } +func needsNegativeCheck(x Expr) bool { + if x.kind == vkSigned { + if rv := x.impl.IsAConstantInt(); !rv.IsNil() && rv.SExtValue() >= 0 { + return false + } + return true + } + return false +} + func llvmParamsEx(data Expr, vals []Expr, params *types.Tuple, b Builder) (ret []llvm.Value) { if data.IsNil() { return llvmParams(0, vals, params, b) @@ -619,323 +628,6 @@ func (b Builder) MakeClosure(fn Expr, bindings []Expr) Expr { return b.aggregateValue(prog.Closure(tfn), fn.impl, data) } -// 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} -} - -// StringData returns the data pointer of a string. -func (b Builder) StringData(x Expr) Expr { - if debugInstr { - log.Printf("StringData %v\n", x.impl) - } - prog := b.Prog - ptr := llvm.CreateExtractValue(b.impl, x.impl, 0) - return Expr{ptr, 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) - } - prog := b.Prog - ptr := llvm.CreateExtractValue(b.impl, x.impl, 1) - return Expr{ptr, 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) - } - prog := b.Prog - ptr := llvm.CreateExtractValue(b.impl, x.impl, 0) - return Expr{ptr, prog.CStr()} -} - -// SliceLen returns the length of a slice. -func (b Builder) SliceLen(x Expr) Expr { - if debugInstr { - log.Printf("SliceLen %v\n", x.impl) - } - prog := b.Prog - ptr := llvm.CreateExtractValue(b.impl, x.impl, 1) - return Expr{ptr, 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) - } - prog := b.Prog - ptr := llvm.CreateExtractValue(b.impl, x.impl, 2) - return Expr{ptr, 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) - } - idx = b.checkIndex(idx) - prog := b.Prog - telem := prog.Index(x.Type) - pt := prog.Pointer(telem) - switch x.raw.Type.Underlying().(type) { - case *types.Slice: - ptr := b.SliceData(x) - indices := []llvm.Value{idx.impl} - return Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, ptr.impl, indices), pt} - } - // case *types.Pointer: - indices := []llvm.Value{idx.impl} - return Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, x.impl, indices), pt} -} - -func needsNegativeCheck(x Expr) bool { - if x.kind == vkSigned { - if rv := x.impl.IsAConstantInt(); !rv.IsNil() && rv.SExtValue() >= 0 { - return false - } - return true - } - return false -} - -// check index >= 0 and size to uint -func (b Builder) checkIndex(idx Expr) Expr { - if needsNegativeCheck(idx) { - check := Expr{b.impl.CreateICmp(llvm.IntSLT, idx.impl, llvm.ConstInt(idx.ll, 0, false), ""), b.Prog.Bool()} - b.InlineCall(b.Func.Pkg.rtFunc("AssertIndexRange"), check) - } - typ := b.Prog.Uint() - if b.Prog.SizeOf(idx.Type) < b.Prog.SizeOf(typ) { - idx.Type = typ - idx.impl = castUintptr(b, idx.impl, typ) - } - 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, addr func(Expr) Expr) Expr { - if debugInstr { - log.Printf("Index %v, %v\n", x.impl, idx.impl) - } - prog := b.Prog - var telem Type - var ptr Expr - 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) - case *types.Array: - telem = prog.Index(x.Type) - if addr != nil { - ptr = addr(x) - } else { - size := b.SizeOf(telem, t.Len()) - ptr = b.Alloca(size) - b.Store(ptr, x) - } - } - idx = b.checkIndex(idx) - 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 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) - } - // TODO(xsw) - // panic("todo") - return -} - -// 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 - pkg := b.Func.Pkg - var nCap Expr - var nEltSize Expr - var base Expr - if low.IsNil() { - 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(pkg.rtFunc("NewStringSlice"), x, low, high).impl - return - case *types.Slice: - nEltSize = b.SizeOf(prog.Index(x.Type)) - nCap = b.SliceCap(x) - if high.IsNil() { - high = b.SliceCap(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 = b.SizeOf(elem) - nCap = prog.IntVal(uint64(te.Len()), prog.Int()) - if high.IsNil() { - high = nCap - } - base = x - } - } - if max.IsNil() { - max = nCap - } - ret.impl = b.InlineCall(pkg.rtFunc("NewSlice3"), base, nEltSize, nCap, low, high, max).impl - return -} - -// ----------------------------------------------------------------------------- - -// 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) - } - pkg := b.Func.Pkg - ret.Type = t - ret.impl = b.InlineCall(pkg.rtFunc("MakeSmallMap")).impl - // TODO(xsw): nReserve - return -} - -// 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) - } - pkg := b.Func.Pkg - if cap.IsNil() { - cap = len - } - elemSize := b.SizeOf(b.Prog.Index(t)) - size := b.BinOp(token.MUL, cap, elemSize) - ptr := b.InlineCall(pkg.rtFunc("AllocZ"), size) - ret.impl = b.InlineCall(pkg.rtFunc("NewSlice"), ptr, len, cap).impl - ret.Type = t - return -} - // ----------------------------------------------------------------------------- // The Alloc instruction reserves space for a variable of the given type, @@ -1164,156 +856,6 @@ func castPtr(b llvm.Builder, x llvm.Value, t llvm.Type) llvm.Value { return llvm.CreateIntToPtr(b, x, t) } -// 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) { - raw := tinter.raw.Type - if debugInstr { - log.Printf("MakeInterface %v, %v\n", raw, x.impl) - } - prog := b.Prog - pkg := b.Func.Pkg - switch tx := x.raw.Type.Underlying().(type) { - case *types.Basic: - kind := tx.Kind() - switch { - case kind >= types.Bool && kind <= types.Uintptr: - t := b.InlineCall(pkg.rtFunc("Basic"), prog.Val(int(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} - case kind == types.Float32: - t := b.InlineCall(pkg.rtFunc("Basic"), prog.Val(int(kind))) - tptr := prog.Uintptr() - i32 := b.impl.CreateBitCast(x.impl, prog.tyInt32(), "") - vptr := Expr{llvm.CreateIntCast(b.impl, i32, tptr.ll), tptr} - return Expr{b.InlineCall(pkg.rtFunc("MakeAnyInt"), t, vptr).impl, tinter} - case kind == types.Float64: - t := b.InlineCall(pkg.rtFunc("Basic"), prog.Val(int(kind))) - tptr := prog.Uintptr() - vptr := Expr{b.impl.CreateBitCast(x.impl, tptr.ll, ""), tptr} - return Expr{b.InlineCall(pkg.rtFunc("MakeAnyInt"), t, vptr).impl, tinter} - case kind == types.String: - return Expr{b.InlineCall(pkg.rtFunc("MakeAnyString"), x).impl, tinter} - } - } - 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) (ret Expr) { - if debugInstr { - log.Printf("TypeAssert %v, %v, %v\n", x.impl, assertedTyp.raw.Type, commaOk) - } - switch assertedTyp.kind { - case vkSigned, vkUnsigned, vkFloat, vkBool: - pkg := b.Func.Pkg - fnName := "I2Int" - if commaOk { - fnName = "CheckI2Int" - } - fn := pkg.rtFunc(fnName) - var kind types.BasicKind - switch t := assertedTyp.raw.Type.(type) { - case *types.Basic: - kind = t.Kind() - default: - panic("todo") - } - typ := b.InlineCall(pkg.rtFunc("Basic"), b.Prog.Val(int(kind))) - ret = b.InlineCall(fn, x, typ) - if kind != types.Uintptr { - conv := func(v llvm.Value) llvm.Value { - switch kind { - case types.Float32: - v = castInt(b, v, b.Prog.Int32()) - v = b.impl.CreateBitCast(v, assertedTyp.ll, "") - case types.Float64: - v = b.impl.CreateBitCast(v, assertedTyp.ll, "") - default: - v = castInt(b, v, assertedTyp) - } - return v - } - if !commaOk { - ret.Type = assertedTyp - ret.impl = conv(ret.impl) - } else { - ret.Type = b.Prog.toTuple( - types.NewTuple( - types.NewVar(token.NoPos, nil, "", assertedTyp.RawType()), - ret.Type.RawType().(*types.Tuple).At(1), - ), - ) - val0 := conv(b.impl.CreateExtractValue(ret.impl, 0, "")) - val1 := b.impl.CreateExtractValue(ret.impl, 1, "") - ret.impl = llvm.ConstStruct([]llvm.Value{val0, val1}, false) - } - } - return - case vkString: - pkg := b.Func.Pkg - fnName := "I2String" - if commaOk { - fnName = "CheckI2String" - } - fn := pkg.rtFunc(fnName) - typ := b.InlineCall(pkg.rtFunc("Basic"), b.Prog.Val(int(abi.String))) - return b.InlineCall(fn, x, typ) - } - panic("todo") -} - // ----------------------------------------------------------------------------- // TODO(xsw): make inline call diff --git a/ssa/interface.go b/ssa/interface.go new file mode 100644 index 00000000..f0350887 --- /dev/null +++ b/ssa/interface.go @@ -0,0 +1,182 @@ +/* + * 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/llgo/internal/abi" + "github.com/goplus/llvm" +) + +// ----------------------------------------------------------------------------- + +// 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) { + raw := tinter.raw.Type + if debugInstr { + log.Printf("MakeInterface %v, %v\n", raw, x.impl) + } + prog := b.Prog + pkg := b.Func.Pkg + switch tx := x.raw.Type.Underlying().(type) { + case *types.Basic: + kind := tx.Kind() + switch { + case kind >= types.Bool && kind <= types.Uintptr: + t := b.InlineCall(pkg.rtFunc("Basic"), prog.Val(int(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} + case kind == types.Float32: + t := b.InlineCall(pkg.rtFunc("Basic"), prog.Val(int(kind))) + tptr := prog.Uintptr() + i32 := b.impl.CreateBitCast(x.impl, prog.tyInt32(), "") + vptr := Expr{llvm.CreateIntCast(b.impl, i32, tptr.ll), tptr} + return Expr{b.InlineCall(pkg.rtFunc("MakeAnyInt"), t, vptr).impl, tinter} + case kind == types.Float64: + t := b.InlineCall(pkg.rtFunc("Basic"), prog.Val(int(kind))) + tptr := prog.Uintptr() + vptr := Expr{b.impl.CreateBitCast(x.impl, tptr.ll, ""), tptr} + return Expr{b.InlineCall(pkg.rtFunc("MakeAnyInt"), t, vptr).impl, tinter} + case kind == types.String: + return Expr{b.InlineCall(pkg.rtFunc("MakeAnyString"), x).impl, tinter} + } + case *types.Struct: + panic("todo: struct") + } + 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) (ret Expr) { + if debugInstr { + log.Printf("TypeAssert %v, %v, %v\n", x.impl, assertedTyp.raw.Type, commaOk) + } + switch assertedTyp.kind { + case vkSigned, vkUnsigned, vkFloat, vkBool: + pkg := b.Func.Pkg + fnName := "I2Int" + if commaOk { + fnName = "CheckI2Int" + } + fn := pkg.rtFunc(fnName) + var kind types.BasicKind + switch t := assertedTyp.raw.Type.(type) { + case *types.Basic: + kind = t.Kind() + default: + panic("todo") + } + typ := b.InlineCall(pkg.rtFunc("Basic"), b.Prog.Val(int(kind))) + ret = b.InlineCall(fn, x, typ) + if kind != types.Uintptr { + conv := func(v llvm.Value) llvm.Value { + switch kind { + case types.Float32: + v = castInt(b, v, b.Prog.Int32()) + v = b.impl.CreateBitCast(v, assertedTyp.ll, "") + case types.Float64: + v = b.impl.CreateBitCast(v, assertedTyp.ll, "") + default: + v = castInt(b, v, assertedTyp) + } + return v + } + if !commaOk { + ret.Type = assertedTyp + ret.impl = conv(ret.impl) + } else { + ret.Type = b.Prog.toTuple( + types.NewTuple( + types.NewVar(token.NoPos, nil, "", assertedTyp.RawType()), + ret.Type.RawType().(*types.Tuple).At(1), + ), + ) + val0 := conv(b.impl.CreateExtractValue(ret.impl, 0, "")) + val1 := b.impl.CreateExtractValue(ret.impl, 1, "") + ret.impl = llvm.ConstStruct([]llvm.Value{val0, val1}, false) + } + } + return + case vkString: + pkg := b.Func.Pkg + fnName := "I2String" + if commaOk { + fnName = "CheckI2String" + } + fn := pkg.rtFunc(fnName) + typ := b.InlineCall(pkg.rtFunc("Basic"), b.Prog.Val(int(abi.String))) + return b.InlineCall(fn, x, typ) + } + panic("todo") +} + +// -----------------------------------------------------------------------------