Files
llgo/ssa/expr.go
2025-09-18 21:16:06 +08:00

1420 lines
41 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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/constant"
"go/token"
"go/types"
"log"
"github.com/goplus/llvm"
)
type AtomicOrdering = llvm.AtomicOrdering
const (
OrderingNotAtomic = llvm.AtomicOrderingNotAtomic
OrderingUnordered = llvm.AtomicOrderingUnordered
OrderingMonotonic = llvm.AtomicOrderingMonotonic
OrderingAcquire = llvm.AtomicOrderingAcquire
OrderingRelease = llvm.AtomicOrderingRelease
OrderingAcquireRelease = llvm.AtomicOrderingAcquireRelease
OrderingSeqConsistent = llvm.AtomicOrderingSequentiallyConsistent
)
// -----------------------------------------------------------------------------
type Expr struct {
impl llvm.Value
Type
}
var Nil Expr // Zero value is a nil Expr
// IsNil checks if the expression is nil or not.
func (v Expr) IsNil() bool {
return v.Type == nil
}
// SetOrdering sets the ordering of the atomic operation.
func (v Expr) SetOrdering(ordering AtomicOrdering) Expr {
v.impl.SetOrdering(ordering)
return v
}
func (v Expr) SetName(alias string) Expr {
v.impl.SetName(alias)
return v
}
func (v Expr) Name() string {
return v.impl.Name()
}
// -----------------------------------------------------------------------------
type builtinTy struct {
name string
}
func (p builtinTy) Underlying() types.Type {
panic("don't call")
}
func (p builtinTy) String() string {
return "builtinTy"
}
// Builtin returns a builtin function expression.
func Builtin(name string) Expr {
tbi := &aType{raw: rawType{&builtinTy{name}}, kind: vkBuiltin}
return Expr{Type: tbi}
}
// -----------------------------------------------------------------------------
type pyVarTy struct {
mod Expr
name string
}
func (p pyVarTy) Underlying() types.Type {
panic("don't call")
}
func (p pyVarTy) String() string {
return "pyVar"
}
func pyVarExpr(mod Expr, name string) Expr {
tvar := &aType{raw: rawType{&pyVarTy{mod, name}}, kind: vkPyVarRef}
return Expr{Type: tvar}
}
// -----------------------------------------------------------------------------
// Zero returns a zero constant expression.
func (p Program) Zero(t Type) Expr {
var ret llvm.Value
switch u := t.raw.Type.Underlying().(type) {
case *types.Basic:
kind := u.Kind()
switch {
case kind >= types.Bool && kind <= types.Uintptr:
ret = llvm.ConstInt(p.rawType(u).ll, 0, false)
case kind == types.String:
ret = p.Zero(p.rtType("String")).impl
case kind == types.UnsafePointer:
ret = llvm.ConstPointerNull(p.tyVoidPtr())
case kind <= types.Float64:
ret = llvm.ConstFloat(p.Float64().ll, 0)
case kind == types.Float32:
ret = llvm.ConstFloat(p.Float32().ll, 0)
case kind == types.Complex64:
v := llvm.ConstFloat(p.Float32().ll, 0)
ret = llvm.ConstStruct([]llvm.Value{v, v}, false)
case kind == types.Complex128:
v := llvm.ConstFloat(p.Float64().ll, 0)
ret = llvm.ConstStruct([]llvm.Value{v, v}, false)
default:
panic("todo")
}
case *types.Pointer, *types.Signature, *types.Chan:
return Expr{llvm.ConstNull(t.ll), t}
case *types.Struct:
n := u.NumFields()
flds := make([]llvm.Value, n)
for i := 0; i < n; i++ {
flds[i] = p.Zero(p.rawType(u.Field(i).Type())).impl
}
ret = llvm.ConstStruct(flds, false)
case *types.Slice:
ret = p.Zero(p.rtType("Slice")).impl
case *types.Array:
ret = llvm.ConstNull(t.ll)
case *types.Interface:
var name string
if u.Empty() {
name = "Eface"
} else {
name = "Iface"
}
ret = p.Zero(p.rtType(name)).impl
case *types.Map:
ret = p.Zero(p.rtType("Map")).impl
default:
log.Panicln("todo:", u)
}
return Expr{ret, t}
}
// Nil returns a null constant expression. t should be a pointer type.
func (p Program) Nil(t Type) Expr {
return Expr{llvm.ConstNull(t.ll), t}
}
// BoolVal returns a boolean constant expression.
func (p Program) BoolVal(v bool) Expr {
t := p.Bool()
var bv uint64
if v {
bv = 1
}
ret := llvm.ConstInt(t.ll, bv, v)
return Expr{ret, t}
}
// IntVal returns an integer constant expression.
func (p Program) IntVal(v uint64, t Type) Expr {
ret := llvm.ConstInt(t.ll, v, false)
return Expr{ret, t}
}
// FloatVal returns a float constant expression.
func (p Program) FloatVal(v float64, t Type) Expr {
ret := llvm.ConstFloat(t.ll, v)
return Expr{ret, t}
}
// ComplexVal returns a complex constant expression.
func (p Program) ComplexVal(v complex128, t Type) Expr {
flt := p.Field(t, 0)
re := p.FloatVal(real(v), flt)
im := p.FloatVal(imag(v), flt)
return Expr{llvm.ConstStruct([]llvm.Value{re.impl, im.impl}, false), t}
}
// Val returns a constant expression.
func (p Program) Val(v interface{}) Expr {
switch v := v.(type) {
case int:
return p.IntVal(uint64(v), p.Int())
case uintptr:
return p.IntVal(uint64(v), p.Uintptr())
case bool:
return p.BoolVal(v)
case float64:
t := p.Float64()
ret := llvm.ConstFloat(t.ll, v)
return Expr{ret, t}
}
panic("todo")
}
// Const returns a constant expression.
func (b Builder) Const(v constant.Value, typ Type) Expr {
prog := b.Prog
if v == nil {
return prog.Nil(typ)
}
raw := typ.raw.Type
switch t := raw.Underlying().(type) {
case *types.Basic:
kind := t.Kind()
switch {
case kind == types.Bool:
return Expr{prog.BoolVal(constant.BoolVal(v)).impl, typ}
case kind >= types.Int && kind <= types.Int64:
if v, exact := constant.Int64Val(constant.ToInt(v)); exact {
return prog.IntVal(uint64(v), typ)
}
case kind >= types.Uint && kind <= types.Uintptr:
if v, exact := constant.Uint64Val(constant.ToInt(v)); exact {
return prog.IntVal(v, typ)
}
case kind == types.Float64 || kind == types.Float32:
v, _ := constant.Float64Val(constant.ToFloat(v))
return prog.FloatVal(v, typ)
case kind == types.String:
return Expr{b.Str(constant.StringVal(v)).impl, typ}
case kind == types.Complex128 || kind == types.Complex64:
v = constant.ToComplex(v)
re, _ := constant.Float64Val(constant.Real(v))
im, _ := constant.Float64Val(constant.Imag(v))
return prog.ComplexVal(complex(re, im), typ)
case kind == types.UnsafePointer:
if v, exact := constant.Uint64Val(v); exact {
return Expr{llvm.ConstIntToPtr(llvm.ConstInt(prog.Uintptr().ll, v, false), typ.ll), typ}
}
}
}
panic(fmt.Sprintf("unsupported Const: %v, %v", v, raw))
}
// CStr returns a c-style string constant expression.
func (b Builder) CStr(v string) Expr {
return Expr{llvm.CreateGlobalStringPtr(b.impl, v), b.Prog.CStr()}
}
// CString returns a c-style string
func (b Builder) CString(v Expr) Expr {
fn := b.Pkg.rtFunc("CString")
return b.Call(fn, v)
}
// CBytes returns a c-style bytes
func (b Builder) CBytes(v Expr) Expr {
fn := b.Pkg.rtFunc("CBytes")
return b.Call(fn, v)
}
// InlineAsm generates inline assembly instruction
func (b Builder) InlineAsm(instruction string) {
if debugInstr {
log.Printf("InlineAsm %s\n", instruction)
}
typ := llvm.FunctionType(b.Prog.tyVoid(), nil, false)
asm := llvm.InlineAsm(typ, instruction, "", true, false, llvm.InlineAsmDialectATT, false)
b.impl.CreateCall(typ, asm, nil, "")
}
func (b Builder) InlineAsmFull(instruction, constraints string, retType Type, exprs []Expr) Expr {
typs := make([]llvm.Type, len(exprs))
vals := make([]llvm.Value, len(exprs))
for i, expr := range exprs {
typs[i], vals[i] = expr.Type.ll, expr.impl
}
ftype := llvm.FunctionType(retType.ll, typs, false)
asm := llvm.InlineAsm(ftype, instruction, constraints, true, false, llvm.InlineAsmDialectATT, false)
return Expr{b.impl.CreateCall(ftype, asm, vals, ""), retType}
}
// GoString returns a Go string
func (b Builder) GoString(v Expr) Expr {
fn := b.Pkg.rtFunc("GoString")
return b.Call(fn, v)
}
// GoStringN returns a Go string
func (b Builder) GoStringN(v Expr, n Expr) Expr {
fn := b.Pkg.rtFunc("GoStringN")
return b.Call(fn, v, n)
}
// GoBytes returns a Go bytes
func (b Builder) GoBytes(v Expr, n Expr) (ret Expr) {
fn := b.Pkg.rtFunc("GoBytes")
return b.Call(fn, v, n)
}
// CMalloc returns a c-style pointer
func (b Builder) CMalloc(n Expr) Expr {
return b.malloc(n)
}
// Str returns a Go string constant expression.
func (b Builder) Str(v string) Expr {
prog := b.Prog
data := b.Pkg.createGlobalStr(v)
size := llvm.ConstInt(prog.tyInt(), uint64(len(v)), false)
return Expr{aggregateValue(b.impl, prog.rtString(), data, size), prog.String()}
}
// unsafeString(data *byte, size int) string
func (b Builder) unsafeString(data, size llvm.Value) Expr {
prog := b.Prog
return Expr{aggregateValue(b.impl, prog.rtString(), data, size), prog.String()}
}
// unsafeSlice(data *T, size, cap int) []T
func (b Builder) unsafeSlice(data Expr, size, cap llvm.Value) Expr {
prog := b.Prog
tslice := prog.Slice(prog.Elem(data.Type))
return Expr{aggregateValue(b.impl, prog.rtSlice(), data.impl, size, cap), tslice}
}
// -----------------------------------------------------------------------------
const (
mathOpBase = token.ADD
mathOpLast = token.REM
)
var mathOpToLLVM = []llvm.Opcode{
int(token.ADD-mathOpBase)<<2 | vkSigned: llvm.Add,
int(token.ADD-mathOpBase)<<2 | vkUnsigned: llvm.Add,
int(token.ADD-mathOpBase)<<2 | vkFloat: llvm.FAdd,
int(token.SUB-mathOpBase)<<2 | vkSigned: llvm.Sub,
int(token.SUB-mathOpBase)<<2 | vkUnsigned: llvm.Sub,
int(token.SUB-mathOpBase)<<2 | vkFloat: llvm.FSub,
int(token.MUL-mathOpBase)<<2 | vkSigned: llvm.Mul,
int(token.MUL-mathOpBase)<<2 | vkUnsigned: llvm.Mul,
int(token.MUL-mathOpBase)<<2 | vkFloat: llvm.FMul,
int(token.QUO-mathOpBase)<<2 | vkSigned: llvm.SDiv,
int(token.QUO-mathOpBase)<<2 | vkUnsigned: llvm.UDiv,
int(token.QUO-mathOpBase)<<2 | vkFloat: llvm.FDiv,
int(token.REM-mathOpBase)<<2 | vkSigned: llvm.SRem,
int(token.REM-mathOpBase)<<2 | vkUnsigned: llvm.URem,
int(token.REM-mathOpBase)<<2 | vkFloat: llvm.FRem,
}
func mathOpIdx(op token.Token, x valueKind) int {
return int(op-mathOpBase)<<2 | x
}
// ADD SUB MUL QUO REM + - * / %
func isMathOp(op token.Token) bool {
return op >= mathOpBase && op <= mathOpLast
}
const (
logicOpBase = token.AND
logicOpLast = token.AND_NOT
)
var logicOpToLLVM = []llvm.Opcode{
token.AND - logicOpBase: llvm.And,
token.OR - logicOpBase: llvm.Or,
token.XOR - logicOpBase: llvm.Xor,
token.SHL - logicOpBase: llvm.Shl,
token.SHR - logicOpBase: llvm.AShr, // Arithmetic Shift Right
}
// AND OR XOR SHL SHR AND_NOT & | ^ << >> &^
func isLogicOp(op token.Token) bool {
return op >= logicOpBase && op <= logicOpLast
}
const (
predOpBase = token.EQL
predOpLast = token.GEQ
)
var intPredOpToLLVM = []llvm.IntPredicate{
token.EQL - predOpBase: llvm.IntEQ,
token.NEQ - predOpBase: llvm.IntNE,
token.LSS - predOpBase: llvm.IntSLT,
token.LEQ - predOpBase: llvm.IntSLE,
token.GTR - predOpBase: llvm.IntSGT,
token.GEQ - predOpBase: llvm.IntSGE,
}
var uintPredOpToLLVM = []llvm.IntPredicate{
token.EQL - predOpBase: llvm.IntEQ,
token.NEQ - predOpBase: llvm.IntNE,
token.LSS - predOpBase: llvm.IntULT,
token.LEQ - predOpBase: llvm.IntULE,
token.GTR - predOpBase: llvm.IntUGT,
token.GEQ - predOpBase: llvm.IntUGE,
}
var floatPredOpToLLVM = []llvm.FloatPredicate{
token.EQL - predOpBase: llvm.FloatOEQ,
token.NEQ - predOpBase: llvm.FloatUNE,
token.LSS - predOpBase: llvm.FloatOLT,
token.LEQ - predOpBase: llvm.FloatOLE,
token.GTR - predOpBase: llvm.FloatOGT,
token.GEQ - predOpBase: llvm.FloatOGE,
}
var boolPredOpToLLVM = []llvm.IntPredicate{
token.EQL - predOpBase: llvm.IntEQ,
token.NEQ - predOpBase: llvm.IntNE,
}
// EQL NEQ LSS LEQ GTR GEQ == != < <= > >=
func isPredOp(op token.Token) bool {
return op >= predOpBase && op <= predOpLast
}
// The BinOp instruction yields the result of binary operation (x op y).
// op can be:
// ADD SUB MUL QUO REM + - * / %
// AND OR XOR SHL SHR AND_NOT & | ^ << >> &^
// EQL NEQ LSS LEQ GTR GEQ == != < <= > >=
func (b Builder) BinOp(op token.Token, x, y Expr) Expr {
if debugInstr {
log.Printf("BinOp %d, %v, %v\n", op, x.impl, y.impl)
}
switch {
case isMathOp(op): // op: + - * / %
kind := x.kind
switch kind {
case vkString:
if op == token.ADD {
return Expr{b.InlineCall(b.Pkg.rtFunc("StringCat"), x, y).impl, x.Type}
}
case vkComplex:
xr, xi := b.impl.CreateExtractValue(x.impl, 0, ""), b.impl.CreateExtractValue(x.impl, 1, "")
yr, yi := b.impl.CreateExtractValue(y.impl, 0, ""), b.impl.CreateExtractValue(y.impl, 1, "")
switch op {
case token.ADD:
r := llvm.CreateBinOp(b.impl, llvm.FAdd, xr, yr)
i := llvm.CreateBinOp(b.impl, llvm.FAdd, xi, yi)
return b.aggregateValue(x.Type, r, i)
case token.SUB:
r := llvm.CreateBinOp(b.impl, llvm.FSub, xr, yr)
i := llvm.CreateBinOp(b.impl, llvm.FSub, xi, yi)
return b.aggregateValue(x.Type, r, i)
case token.MUL:
r := llvm.CreateBinOp(b.impl, llvm.FSub,
llvm.CreateBinOp(b.impl, llvm.FMul, xr, yr),
llvm.CreateBinOp(b.impl, llvm.FMul, xi, yi),
)
i := llvm.CreateBinOp(b.impl, llvm.FAdd,
llvm.CreateBinOp(b.impl, llvm.FMul, xr, yi),
llvm.CreateBinOp(b.impl, llvm.FMul, xi, yr),
)
return b.aggregateValue(x.Type, r, i)
case token.QUO:
d := llvm.CreateBinOp(b.impl, llvm.FAdd, llvm.CreateBinOp(b.impl, llvm.FMul, yr, yr), llvm.CreateBinOp(b.impl, llvm.FMul, yi, yi))
zero := llvm.CreateFCmp(b.impl, llvm.FloatOEQ, d, llvm.ConstNull(d.Type()))
r := llvm.CreateSelect(b.impl, zero,
llvm.CreateBinOp(b.impl, llvm.FDiv, xr, d),
llvm.CreateBinOp(b.impl, llvm.FDiv,
llvm.CreateBinOp(b.impl, llvm.FAdd,
llvm.CreateBinOp(b.impl, llvm.FMul, xr, yr),
llvm.CreateBinOp(b.impl, llvm.FMul, xi, yi),
),
d,
),
)
i := llvm.CreateSelect(b.impl, zero,
llvm.CreateBinOp(b.impl, llvm.FDiv, xi, d),
llvm.CreateBinOp(b.impl, llvm.FDiv,
llvm.CreateBinOp(b.impl, llvm.FSub,
llvm.CreateBinOp(b.impl, llvm.FMul, xr, yi),
llvm.CreateBinOp(b.impl, llvm.FMul, xi, yr),
),
d,
),
)
return b.aggregateValue(x.Type, r, i)
}
default:
idx := mathOpIdx(op, kind)
if llop := mathOpToLLVM[idx]; llop != 0 {
return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type}
}
}
case isLogicOp(op): // op: & | ^ << >> &^
switch op {
case token.AND_NOT:
return Expr{llvm.CreateAnd(b.impl, x.impl, llvm.CreateNot(b.impl, y.impl)), x.Type}
case token.SHL, token.SHR:
if needsNegativeCheck(y) {
zero := llvm.ConstInt(y.ll, 0, false)
check := Expr{llvm.CreateICmp(b.impl, llvm.IntSLT, y.impl, zero), b.Prog.Bool()}
b.InlineCall(b.Pkg.rtFunc("AssertNegativeShift"), check)
}
xsize, ysize := b.Prog.SizeOf(x.Type), b.Prog.SizeOf(y.Type)
if xsize != ysize {
y = b.Convert(x.Type, y)
}
overflows := llvm.CreateICmp(b.impl, llvm.IntUGE, y.impl, llvm.ConstInt(y.ll, xsize*8, false))
xzero := llvm.ConstInt(x.ll, 0, false)
if op == token.SHL {
rhs := llvm.CreateShl(b.impl, x.impl, y.impl)
return Expr{llvm.CreateSelect(b.impl, overflows, xzero, rhs), x.Type}
} else {
if x.kind == vkSigned {
rhs := llvm.CreateSelect(b.impl, overflows, llvm.ConstInt(y.ll, 8*xsize-1, false), y.impl)
return Expr{llvm.CreateAShr(b.impl, x.impl, rhs), x.Type}
} else {
rsh := llvm.CreateLShr(b.impl, x.impl, y.impl)
return Expr{llvm.CreateSelect(b.impl, overflows, xzero, rsh), x.Type}
}
}
default:
llop := logicOpToLLVM[op-logicOpBase]
return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type}
}
case isPredOp(op): // op: == != < <= > >=
prog := b.Prog
tret := prog.Bool()
kind := x.kind
switch kind {
case vkSigned:
pred := intPredOpToLLVM[op-predOpBase]
return Expr{llvm.CreateICmp(b.impl, pred, x.impl, y.impl), tret}
case vkUnsigned, vkPtr:
pred := uintPredOpToLLVM[op-predOpBase]
return Expr{llvm.CreateICmp(b.impl, pred, x.impl, y.impl), tret}
case vkFloat:
pred := floatPredOpToLLVM[op-predOpBase]
return Expr{llvm.CreateFCmp(b.impl, pred, x.impl, y.impl), tret}
case vkBool:
pred := boolPredOpToLLVM[op-predOpBase]
return Expr{llvm.CreateICmp(b.impl, pred, x.impl, y.impl), tret}
case vkComplex:
switch op {
case token.EQL:
xr, xi := b.impl.CreateExtractValue(x.impl, 0, ""), b.impl.CreateExtractValue(x.impl, 1, "")
yr, yi := b.impl.CreateExtractValue(y.impl, 0, ""), b.impl.CreateExtractValue(y.impl, 1, "")
return Expr{llvm.CreateAnd(b.impl,
llvm.CreateFCmp(b.impl, llvm.FloatOEQ, xr, yr),
llvm.CreateFCmp(b.impl, llvm.FloatOEQ, xi, yi),
), tret}
case token.NEQ:
xr, xi := b.impl.CreateExtractValue(x.impl, 0, ""), b.impl.CreateExtractValue(x.impl, 1, "")
yr, yi := b.impl.CreateExtractValue(y.impl, 0, ""), b.impl.CreateExtractValue(y.impl, 1, "")
return Expr{b.impl.CreateOr(
llvm.CreateFCmp(b.impl, llvm.FloatUNE, xr, yr),
llvm.CreateFCmp(b.impl, llvm.FloatUNE, xi, yi),
"",
), tret}
}
case vkString:
switch op {
case token.EQL:
return b.InlineCall(b.Pkg.rtFunc("StringEqual"), x, y)
case token.NEQ:
ret := b.InlineCall(b.Pkg.rtFunc("StringEqual"), x, y)
ret.impl = llvm.CreateNot(b.impl, ret.impl)
return ret
case token.LSS:
return b.InlineCall(b.Pkg.rtFunc("StringLess"), x, y)
case token.LEQ:
ret := b.InlineCall(b.Pkg.rtFunc("StringLess"), y, x)
ret.impl = llvm.CreateNot(b.impl, ret.impl)
return ret
case token.GTR:
return b.InlineCall(b.Pkg.rtFunc("StringLess"), y, x)
case token.GEQ:
ret := b.InlineCall(b.Pkg.rtFunc("StringLess"), x, y)
ret.impl = llvm.CreateNot(b.impl, ret.impl)
return ret
}
case vkClosure:
x = b.Field(x, 0)
if y.kind == vkClosure {
y = b.Field(y, 0)
}
fallthrough
case vkFuncPtr, vkFuncDecl, vkChan, vkMap:
if y.kind == vkClosure {
y = b.Field(y, 0)
}
switch op {
case token.EQL, token.NEQ:
pred := uintPredOpToLLVM[op-predOpBase]
return Expr{llvm.CreateICmp(b.impl, pred, x.impl, y.impl), tret}
}
case vkArray:
typ := x.raw.Type.Underlying().(*types.Array)
elem := b.Prog.Elem(x.Type)
ret := prog.BoolVal(true)
for i, n := 0, int(typ.Len()); i < n; i++ {
fx := b.impl.CreateExtractValue(x.impl, i, "")
fy := b.impl.CreateExtractValue(y.impl, i, "")
r := b.BinOp(token.EQL, Expr{fx, elem}, Expr{fy, elem})
ret = Expr{b.impl.CreateAnd(ret.impl, r.impl, ""), tret}
}
switch op {
case token.EQL:
return ret
case token.NEQ:
return Expr{b.impl.CreateNot(ret.impl, ""), tret}
}
case vkStruct:
typ := x.raw.Type.Underlying().(*types.Struct)
ret := prog.BoolVal(true)
for i, n := 0, typ.NumFields(); i < n; i++ {
ft := prog.Type(typ.Field(i).Type(), InGo)
fx := b.impl.CreateExtractValue(x.impl, i, "")
fy := b.impl.CreateExtractValue(y.impl, i, "")
r := b.BinOp(token.EQL, Expr{fx, ft}, Expr{fy, ft})
ret = Expr{b.impl.CreateAnd(ret.impl, r.impl, ""), tret}
}
switch op {
case token.EQL:
return ret
case token.NEQ:
return Expr{b.impl.CreateNot(ret.impl, ""), tret}
}
case vkSlice:
dx := b.impl.CreateExtractValue(x.impl, 0, "")
dy := b.impl.CreateExtractValue(y.impl, 0, "")
switch op {
case token.EQL:
return Expr{b.impl.CreateICmp(llvm.IntEQ, dx, dy, ""), tret}
case token.NEQ:
return Expr{b.impl.CreateICmp(llvm.IntNE, dx, dy, ""), tret}
}
case vkIface, vkEface:
toEface := func(x Expr, emtpy bool) Expr {
if emtpy {
return x
}
return Expr{b.unsafeEface(b.faceAbiType(x).impl, b.faceData(x.impl)), prog.rtType("Eface")}
}
switch op {
case token.EQL:
return b.InlineCall(b.Pkg.rtFunc("EfaceEqual"), toEface(x, x.kind == vkEface), toEface(y, y.kind == vkEface))
case token.NEQ:
ret := b.InlineCall(b.Pkg.rtFunc("EfaceEqual"), toEface(x, x.kind == vkEface), toEface(y, y.kind == vkEface))
ret.impl = llvm.CreateNot(b.impl, ret.impl)
return ret
}
}
}
panic("todo")
}
// The UnOp instruction yields the result of (op x).
// ARROW is channel receive.
// MUL is pointer indirection (load).
// XOR is bitwise complement.
// SUB is negation.
// NOT is logical negation.
func (b Builder) UnOp(op token.Token, x Expr) (ret Expr) {
if debugInstr {
log.Printf("UnOp %v, %v\n", op, x.impl)
}
switch op {
case token.MUL:
return b.Load(x)
case token.SUB:
switch t := x.raw.Type.Underlying().(type) {
case *types.Basic:
ret.Type = x.Type
if t.Info()&types.IsInteger != 0 {
ret.impl = llvm.CreateNeg(b.impl, x.impl)
} else if t.Info()&types.IsFloat != 0 {
ret.impl = llvm.CreateFNeg(b.impl, x.impl)
} else if t.Info()&types.IsComplex != 0 {
r := b.impl.CreateExtractValue(x.impl, 0, "")
i := b.impl.CreateExtractValue(x.impl, 1, "")
return b.aggregateValue(x.Type, llvm.CreateFNeg(b.impl, r), llvm.CreateFNeg(b.impl, i))
} else {
panic("todo")
}
default:
panic("unreachable")
}
case token.NOT:
ret.Type = x.Type
ret.impl = llvm.CreateNot(b.impl, x.impl)
case token.XOR:
ret.Type = x.Type
ret.impl = llvm.CreateXor(b.impl, x.impl, llvm.ConstInt(x.Type.ll, ^uint64(0), false))
case token.ARROW:
panic("todo")
}
return
}
// -----------------------------------------------------------------------------
// The ChangeType instruction applies to X a value-preserving type
// change to Type().
//
// Type changes are permitted:
// - between a named type and its underlying type.
// - between two named types of the same underlying type.
// - between (possibly named) pointers to identical base types.
// - from a bidirectional channel to a read- or write-channel,
// optionally adding/removing a name.
// - between a type (t) and an instance of the type (tσ), i.e.
// Type() == σ(X.Type()) (or X.Type()== σ(Type())) where
// σ is the type substitution of Parent().TypeParams by
// Parent().TypeArgs.
//
// This operation cannot fail dynamically.
//
// Type changes may to be to or from a type parameter (or both). All
// types in the type set of X.Type() have a value-preserving type
// change to all types in the type set of Type().
//
// Example printed form:
//
// t1 = changetype *int <- IntPtr (t0)
func (b Builder) ChangeType(t Type, x Expr) (ret Expr) {
if debugInstr {
log.Printf("ChangeType %v, %v\n", t.RawType(), x.impl)
}
if t.kind == vkClosure {
switch x.kind {
case vkFuncDecl:
ret.impl = checkExpr(x, t.raw.Type, b).impl
case vkClosure:
// TODO(xsw): change type should be a noop instruction
convType := func() Expr {
r := Expr{llvm.CreateAlloca(b.impl, t.ll), b.Prog.Pointer(t)}
b.Store(r, x)
return b.Load(r)
}
switch t.RawType().(type) {
case *types.Named:
if _, ok := x.RawType().(*types.Struct); ok {
return convType()
}
case *types.Struct:
if _, ok := x.RawType().(*types.Named); ok {
return convType()
}
}
fallthrough
default:
ret.impl = x.impl
}
} else {
ret.impl = x.impl
}
ret.Type = t
return
}
// The Convert instruction yields the conversion of value X to type
// Type(). One or both of those types is basic (but possibly named).
//
// A conversion may change the value and representation of its operand.
// Conversions are permitted:
// - between real numeric types.
// - between complex numeric types.
// - between string and []byte or []rune.
// - between pointers and unsafe.Pointer.
// - between unsafe.Pointer and uintptr.
// - from (Unicode) integer to (UTF-8) string.
//
// A conversion may imply a type name change also.
//
// Conversions may to be to or from a type parameter. All types in
// the type set of X.Type() can be converted to all types in the type
// set of Type().
//
// This operation cannot fail dynamically.
//
// Conversions of untyped string/number/bool constants to a specific
// representation are eliminated during SSA construction.
//
// Example printed form:
//
// t1 = convert []byte <- string (t0)
func (b Builder) Convert(t Type, x Expr) (ret Expr) {
if debugInstr {
log.Printf("Convert %v <- %v\n", t.RawType(), x.RawType())
}
typ := t.raw.Type
ret.Type = b.Prog.rawType(typ)
switch typ := typ.Underlying().(type) {
case *types.Basic:
switch typ.Kind() {
case types.Uintptr:
ret.impl = castUintptr(b, x.impl, t)
return
case types.UnsafePointer:
ret.impl = castPtr(b.impl, x.impl, t.ll)
return
case types.String:
switch xtyp := x.RawType().Underlying().(type) {
case *types.Slice:
if etyp, ok := xtyp.Elem().Underlying().(*types.Basic); ok {
switch etyp.Kind() {
case types.Byte:
ret.impl = b.InlineCall(b.Func.Pkg.rtFunc("StringFromBytes"), x).impl
return
case types.Rune:
ret.impl = b.InlineCall(b.Func.Pkg.rtFunc("StringFromRunes"), x).impl
return
}
}
case *types.Basic:
if x.Type != b.Prog.Int32() {
x.Type = b.Prog.Int32()
x.impl = castInt(b, x.impl, b.Prog.Int32())
}
ret.impl = b.InlineCall(b.Func.Pkg.rtFunc("StringFromRune"), x).impl
return
}
case types.Complex128:
switch xtyp := x.RawType().Underlying().(type) {
case *types.Basic:
if xtyp.Kind() == types.Complex64 {
r := b.impl.CreateExtractValue(x.impl, 0, "")
i := b.impl.CreateExtractValue(x.impl, 1, "")
r = castFloat(b, r, b.Prog.Float64())
i = castFloat(b, i, b.Prog.Float64())
ret.impl = b.aggregateValue(t, r, i).impl
return
}
}
}
switch xtyp := x.RawType().Underlying().(type) {
case *types.Basic:
if typ.Info()&types.IsInteger != 0 {
// int <- int/float
if xtyp.Info()&types.IsInteger != 0 {
ret.impl = castInt(b, x.impl, t)
return
} else if xtyp.Info()&types.IsFloat != 0 {
if typ.Info()&types.IsUnsigned != 0 {
ret.impl = llvm.CreateFPToUI(b.impl, x.impl, t.ll)
} else {
ret.impl = llvm.CreateFPToSI(b.impl, x.impl, t.ll)
}
return
}
} else if typ.Info()&types.IsFloat != 0 {
// float <- int/float
if xtyp.Info()&types.IsInteger != 0 {
if xtyp.Info()&types.IsUnsigned != 0 {
ret.impl = llvm.CreateUIToFP(b.impl, x.impl, t.ll)
} else {
ret.impl = llvm.CreateSIToFP(b.impl, x.impl, t.ll)
}
return
} else if xtyp.Info()&types.IsFloat != 0 {
ret.impl = castFloat(b, x.impl, t)
return
}
}
}
if x.kind == vkComplex && t.kind == vkComplex {
ft := b.Prog.Float64()
if t.raw.Type.Underlying().(*types.Basic).Kind() == types.Complex64 {
ft = b.Prog.Float32()
}
r := b.impl.CreateExtractValue(x.impl, 0, "")
i := b.impl.CreateExtractValue(x.impl, 1, "")
ret.impl = b.Complex(Expr{castFloat(b, r, ft), ft}, Expr{castFloat(b, i, ft), ft}).impl
return
}
case *types.Pointer:
ret.impl = castPtr(b.impl, x.impl, t.ll)
return
case *types.Slice:
if x.kind == vkString {
if etyp, ok := typ.Elem().Underlying().(*types.Basic); ok {
switch etyp.Kind() {
case types.Byte:
ret.impl = b.InlineCall(b.Func.Pkg.rtFunc("StringToBytes"), x).impl
return
case types.Rune:
ret.impl = b.InlineCall(b.Func.Pkg.rtFunc("StringToRunes"), x).impl
return
}
}
}
}
panic("todo")
}
func castUintptr(b Builder, x llvm.Value, typ Type) llvm.Value {
if x.Type().TypeKind() == llvm.PointerTypeKind {
return llvm.CreatePtrToInt(b.impl, x, typ.ll)
}
return castInt(b, x, typ)
}
func castInt(b Builder, x llvm.Value, typ Type) llvm.Value {
xsize := b.Prog.td.TypeAllocSize(x.Type())
size := b.Prog.td.TypeAllocSize(typ.ll)
if xsize > size {
return llvm.CreateTrunc(b.impl, x, typ.ll)
} else if typ.kind == vkUnsigned {
return llvm.CreateZExt(b.impl, x, typ.ll)
} else {
return llvm.CreateSExt(b.impl, x, typ.ll)
}
}
func castFloat(b Builder, x llvm.Value, typ Type) llvm.Value {
xsize := b.Prog.td.TypeAllocSize(x.Type())
size := b.Prog.td.TypeAllocSize(typ.ll)
if xsize > size {
return llvm.CreateFPTrunc(b.impl, x, typ.ll)
} else {
return llvm.CreateFPExt(b.impl, x, typ.ll)
}
}
func castPtr(b llvm.Builder, x llvm.Value, t llvm.Type) llvm.Value {
if x.Type().TypeKind() == llvm.PointerTypeKind {
return llvm.CreatePointerCast(b, x, t)
}
return llvm.CreateIntToPtr(b, x, t)
}
// -----------------------------------------------------------------------------
// The MakeClosure instruction yields a closure value whose code is
// Fn and whose free variables' values are supplied by Bindings.
//
// Type() returns a (possibly named) *types.Signature.
//
// Example printed form:
//
// t0 = make closure anon@1.2 [x y z]
// t1 = make closure bound$(main.I).add [i]
func (b Builder) MakeClosure(fn Expr, bindings []Expr) Expr {
if debugInstr {
log.Printf("MakeClosure %v, %v\n", fn, bindings)
}
prog := b.Prog
tfn := fn.Type
sig := tfn.raw.Type.(*types.Signature)
tctx := sig.Params().At(0).Type().Underlying().(*types.Pointer).Elem().(*types.Struct)
flds := llvmFields(bindings, tctx, b)
data := b.aggregateAllocU(prog.rawType(tctx), flds...)
return b.aggregateValue(prog.Closure(removeCtx(sig)), fn.impl, data)
}
func removeCtx(sig *types.Signature) *types.Signature {
params := sig.Params()
n := params.Len()
args := make([]*types.Var, n-1)
for i := 0; i < n-1; i++ {
args[i] = params.At(i + 1)
}
return types.NewSignature(sig.Recv(), types.NewTuple(args...), sig.Results(), sig.Variadic())
}
// -----------------------------------------------------------------------------
// TODO(xsw): make inline call
func (b Builder) InlineCall(fn Expr, args ...Expr) (ret Expr) {
return b.Call(fn, args...)
}
// The Call instruction represents a function call.
//
// The Call instruction yields the function result if there is exactly
// one. Otherwise it returns a tuple, the components of which are
// accessed via Extract.
//
// Example printed form:
//
// t2 = println(t0, t1)
// t4 = t3()
func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) {
if debugInstr {
logCall("Call", fn, args)
}
var kind = fn.kind
if kind == vkPyFuncRef {
return b.pyCall(fn, args)
}
var ll llvm.Type
var data Expr
var sig *types.Signature
var raw = fn.raw.Type
switch kind {
case vkClosure:
data = b.Field(fn, 1)
fn = b.Field(fn, 0)
ctx := types.NewParam(token.NoPos, nil, closureCtx, types.Typ[types.UnsafePointer])
raw = FuncAddCtx(ctx, fn.raw.Type.(*types.Signature))
fallthrough
case vkFuncPtr:
sig = raw.Underlying().(*types.Signature)
ll = b.Prog.FuncDecl(sig, InC).ll
case vkFuncDecl:
sig = raw.(*types.Signature)
ll = fn.ll
case vkBuiltin:
bi := raw.(*builtinTy)
return b.BuiltinCall(bi.name, args...)
default:
log.Panicf("unreachable: %d(%T), %v\n", kind, raw, fn.RawType())
}
ret.Type = b.Prog.retType(sig)
ret.impl = llvm.CreateCall(b.impl, ll, fn.impl, llvmParamsEx(data, args, sig.Params(), b))
return
}
func logCall(da string, fn Expr, args []Expr) {
if fn.kind == vkBuiltin {
return
}
var b bytes.Buffer
name := fn.impl.Name()
if name == "" {
name = "closure"
}
fmt.Fprint(&b, da, " ", fn.kind, " ", fn.raw.Type, " ", name)
sep := ": "
for _, arg := range args {
fmt.Fprint(&b, sep, arg.impl)
sep = ", "
}
log.Println(b.String())
}
type DoAction int
const (
Call DoAction = iota
Go
DeferAlways // defer statement executes always
DeferInCond // defer statement executes in a conditional block
DeferInLoop // defer statement executes in a loop block
)
// Do call a function with an action.
func (b Builder) Do(da DoAction, fn Expr, args ...Expr) (ret Expr) {
switch da {
case Call:
return b.Call(fn, args...)
case Go:
b.Go(fn, args...)
default:
b.Defer(da, fn, args...)
}
return
}
// compareSelect performs a series of comparisons and selections based on the
// given comparison op. It's used to implement operations like min and max.
//
// The function iterates through the provided expressions, comparing each with
// the current result using the specified comparison op. It selects the
// appropriate value based on the comparison.
func (b Builder) compareSelect(op token.Token, x Expr, y ...Expr) Expr {
ret := x
for _, v := range y {
cond := b.BinOp(op, ret, v)
sel := llvm.CreateSelect(b.impl, cond.impl, ret.impl, v.impl)
ret = Expr{sel, ret.Type}
}
return ret
}
// The SliceToArrayPointer instruction yields the conversion of slice X to
// array pointer.
//
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
// from an explicit conversion in the source.
//
// Conversion may to be to or from a type parameter. All types in
// the type set of X.Type() must be a slice types that can be converted to
// all types in the type set of Type() which must all be pointer to array
// types.
//
// This operation can fail dynamically if the length of the slice is less
// than the length of the array.
//
// Example printed form:
//
// t1 = slice to array pointer *[4]byte <- []byte (t0)
func (b Builder) SliceToArrayPointer(x Expr, typ Type) (ret Expr) {
ret.Type = typ
max := b.Prog.IntVal(uint64(typ.RawType().Underlying().(*types.Pointer).Elem().Underlying().(*types.Array).Len()), b.Prog.Int())
failed := Expr{llvm.CreateICmp(b.impl, llvm.IntSLT, b.SliceLen(x).impl, max.impl), b.Prog.Bool()}
b.IfThen(failed, func() {
b.InlineCall(b.Pkg.rtFunc("PanicSliceConvert"), b.SliceLen(x), max)
})
ret.impl = b.SliceData(x).impl
return
}
// A Builtin represents a specific use of a built-in function, e.g. len.
//
// Builtins are immutable values. Builtins do not have addresses.
//
// `fn` indicates the function: one of the built-in functions from the
// Go spec (excluding "make" and "new").
func (b Builder) BuiltinCall(fn string, args ...Expr) (ret Expr) {
switch fn {
case "len":
if len(args) == 1 {
arg := args[0]
switch arg.kind {
case vkSlice:
return b.SliceLen(arg)
case vkString:
return b.StringLen(arg)
case vkChan:
return b.InlineCall(b.Pkg.rtFunc("ChanLen"), arg)
case vkMap:
return b.InlineCall(b.Pkg.rtFunc("MapLen"), arg)
}
}
case "cap":
if len(args) == 1 {
arg := args[0]
switch arg.kind {
case vkSlice:
return b.SliceCap(arg)
case vkChan:
return b.InlineCall(b.Pkg.rtFunc("ChanCap"), arg)
}
}
case "append":
if len(args) == 2 {
src := args[0]
if src.kind == vkSlice {
elem := args[1]
switch elem.kind {
case vkSlice:
etSize := b.Prog.SizeOf(b.Prog.Elem(elem.Type))
ret.Type = src.Type
ret.impl = b.InlineCall(b.Pkg.rtFunc("SliceAppend"),
src, b.SliceData(elem), b.SliceLen(elem), b.Prog.Val(int(etSize))).impl
return
case vkString:
etSize := b.Prog.SizeOf(b.Prog.Byte())
ret.Type = src.Type
ret.impl = b.InlineCall(b.Pkg.rtFunc("SliceAppend"),
src, b.StringData(elem), b.StringLen(elem), b.Prog.Val(int(etSize))).impl
return
default:
etSize := b.Prog.SizeOf(elem.Type)
ret.Type = src.Type
ret.impl = b.InlineCall(b.Pkg.rtFunc("SliceAppend"),
src, elem, b.Const(constant.MakeInt64(1), b.Prog.Int()), b.Prog.Val(int(etSize))).impl
return
}
}
}
case "copy":
if len(args) == 2 {
dst := args[0]
if dst.kind == vkSlice {
src := args[1]
prog := b.Prog
etSize := prog.Val(int(prog.SizeOf(prog.Elem(dst.Type))))
switch src.kind {
case vkSlice:
return b.InlineCall(b.Pkg.rtFunc("SliceCopy"), dst, b.SliceData(src), b.SliceLen(src), etSize)
case vkString:
return b.InlineCall(b.Pkg.rtFunc("SliceCopy"), dst, b.StringData(src), b.StringLen(src), etSize)
}
}
}
case "close":
if len(args) == 1 {
arg := args[0]
switch arg.kind {
case vkChan:
return b.InlineCall(b.Pkg.rtFunc("ChanClose"), arg)
}
}
case "recover":
return b.Recover()
case "print", "println":
return b.PrintEx(fn == "println", args...)
case "complex":
return b.Complex(args[0], args[1])
case "real":
return b.getField(args[0], 0)
case "imag":
return b.getField(args[0], 1)
case "String": // unsafe.String
return b.unsafeString(args[0].impl, args[1].impl)
case "Slice": // unsafe.Slice
size := b.fitIntSize(args[1])
return b.unsafeSlice(args[0], size.impl, size.impl)
case "StringData":
return b.StringData(args[0]) // TODO(xsw): check return type
case "SliceData":
return b.SliceData(args[0]) // TODO(xsw): check return type
case "delete":
if len(args) == 2 && args[0].kind == vkMap {
m := args[0]
t := b.abiType(m.raw.Type)
ptr := b.mapKeyPtr(args[1])
b.Call(b.Pkg.rtFunc("MapDelete"), t, m, ptr)
return
}
case "clear":
if len(args) == 1 {
arg := args[0]
switch arg.kind {
case vkMap:
m := arg
t := b.abiType(m.raw.Type)
b.Call(b.Pkg.rtFunc("MapClear"), t, m)
return
case vkSlice:
s := arg
t := b.abiType(s.raw.Type)
b.Call(b.Pkg.rtFunc("SliceClear"), t, s)
return
}
}
case "min":
if len(args) > 0 {
return b.compareSelect(token.LSS, args[0], args[1:]...)
}
case "max":
if len(args) > 0 {
return b.compareSelect(token.GTR, args[0], args[1:]...)
}
case "Add":
return b.Advance(args[0], args[1])
case "Sizeof":
// instance of generic function
return b.Prog.Val(int(b.Prog.SizeOf(args[0].Type)))
case "Alignof":
// instance of generic function
return b.Prog.Val(int(b.Prog.td.ABITypeAlignment(args[0].ll)))
case "Offsetof":
// instance of generic function
if load := args[0].impl.IsALoadInst(); !load.IsNil() {
if gep := load.Operand(0).IsAGetElementPtrInst(); !gep.IsNil() {
typ := gep.GEPSourceElementType()
offset := gep.Operand(2).IsAConstantInt()
if typ.TypeKind() == llvm.StructTypeKind && !offset.IsNil() {
return b.Prog.Val(int(b.Prog.td.ElementOffset(typ, int(offset.SExtValue()))))
}
}
}
panic("invalid argument for unsafe.Offsetof: must be a selector expression")
}
panic("todo: " + fn)
}
func (p Program) tyPrintf() *types.Signature {
if p.printfTy == nil {
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]))
p.printfTy = types.NewSignatureType(nil, nil, nil, params, rets, true)
}
return p.printfTy
}
func (b Builder) Printf(fmt string, args ...Expr) Expr {
fn := b.Pkg.cFunc("printf", b.Prog.tyPrintf())
return b.Call(fn, append([]Expr{b.CStr(fmt)}, args...)...)
}
// Println prints the arguments to stderr, followed by a newline.
func (b Builder) Println(args ...Expr) (ret Expr) {
return b.PrintEx(true, args...)
}
// PrintEx prints the arguments to stderr.
func (b Builder) PrintEx(ln bool, args ...Expr) (ret Expr) {
prog := b.Prog
ret.Type = prog.Void()
for i, arg := range args {
if ln && i > 0 {
b.InlineCall(b.Pkg.rtFunc("PrintByte"), prog.IntVal(' ', prog.Byte()))
}
var fn string
typ := arg.Type
switch arg.kind {
case vkBool:
fn = "PrintBool"
case vkSigned:
fn = "PrintInt"
typ = prog.Int64()
case vkUnsigned:
fn = "PrintUint"
typ = prog.Uint64()
case vkFloat:
fn = "PrintFloat"
typ = prog.Float64()
case vkSlice:
fn = "PrintSlice"
case vkClosure:
arg = b.Field(arg, 0)
fallthrough
case vkPtr, vkFuncPtr, vkFuncDecl:
fn = "PrintPointer"
typ = prog.VoidPtr()
case vkString:
fn = "PrintString"
case vkEface:
fn = "PrintEface"
case vkIface:
fn = "PrintIface"
case vkComplex:
fn = "PrintComplex"
typ = prog.Complex128()
case vkChan:
fn = "PrintPointer"
typ = prog.VoidPtr()
case vkMap:
fn = "PrintPointer"
typ = prog.VoidPtr()
default:
panic(fmt.Errorf("illegal types for operand: print %v", arg.RawType()))
}
if typ != arg.Type {
arg = b.Convert(typ, arg)
}
b.InlineCall(b.Pkg.rtFunc(fn), arg)
}
if ln {
b.InlineCall(b.Pkg.rtFunc("PrintByte"), prog.IntVal('\n', prog.Byte()))
}
return
}
// -----------------------------------------------------------------------------
func checkExpr(v Expr, t types.Type, b Builder) Expr {
if st, ok := t.Underlying().(*types.Struct); ok && IsClosure(st) {
if v.kind != vkClosure {
return b.Pkg.closureStub(b, t, v)
}
}
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)
}
ret = llvmParams(1, vals, params, b)
ret[0] = data.impl
return
}
func llvmParams(base int, vals []Expr, params *types.Tuple, b Builder) (ret []llvm.Value) {
n := params.Len()
if n > 0 {
ret = make([]llvm.Value, len(vals)+base)
for idx, v := range vals {
i := base + idx
if i < n {
v = checkExpr(v, params.At(i).Type(), b)
}
ret[i] = v.impl
}
}
return
}
func llvmFields(vals []Expr, t *types.Struct, b Builder) (ret []llvm.Value) {
n := t.NumFields()
if n > 0 {
ret = make([]llvm.Value, len(vals))
for i, v := range vals {
if i < n {
v = checkExpr(v, t.Field(i).Type(), b)
}
ret[i] = v.impl
}
}
return
}
// -----------------------------------------------------------------------------