/* * 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 Expr struct { impl llvm.Value Type } // ----------------------------------------------------------------------------- func llvmValues(vals []Expr) []llvm.Value { ret := make([]llvm.Value, len(vals)) for i, v := range vals { ret[i] = v.impl } return ret } // ----------------------------------------------------------------------------- func (p Program) Null(t Type) Expr { return Expr{llvm.ConstNull(t.ll), t} } 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} } func (p Program) IntVal(v uint64, t Type) Expr { ret := llvm.ConstInt(t.ll, v, false) return Expr{ret, t} } func (p Program) Val(v interface{}) Expr { switch v := v.(type) { case int: return p.IntVal(uint64(v), p.Int()) case bool: return p.BoolVal(v) case float64: t := p.Float64() ret := llvm.ConstFloat(t.ll, v) return Expr{ret, t} } panic("todo") } func (b Builder) Const(v constant.Value, typ Type) Expr { switch t := typ.t.(type) { case *types.Basic: kind := t.Kind() switch { case kind == types.Bool: return b.prog.BoolVal(constant.BoolVal(v)) case kind >= types.Int && kind <= types.Uintptr: if v, exact := constant.Uint64Val(v); exact { return b.prog.IntVal(v, typ) } } } panic("todo") } // ----------------------------------------------------------------------------- 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.FloatONE, token.LSS - predOpBase: llvm.FloatOLT, token.LEQ - predOpBase: llvm.FloatOLE, token.GTR - predOpBase: llvm.FloatOGT, token.GEQ - predOpBase: llvm.FloatOGE, } // 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, vkComplex: panic("todo") } 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: & | ^ << >> &^ if op == token.AND_NOT { panic("todo") } kind := x.kind llop := logicOpToLLVM[op-logicOpBase] if op == token.SHR && kind == vkUnsigned { llop = llvm.LShr // Logical Shift Right } return Expr{llvm.CreateBinOp(b.impl, llop, x.impl, y.impl), x.Type} case isPredOp(op): // op: == != < <= < >= tret := b.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: 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 vkString, vkComplex, vkBool: panic("todo") } } 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) Expr { switch op { case token.MUL: return b.Load(x) } if debugInstr { log.Printf("UnOp %v, %v\n", op, x.impl) } panic("todo") } // Load returns the value at the pointer ptr. func (b Builder) Load(ptr Expr) Expr { if debugInstr { log.Printf("Load %v\n", ptr.impl.Name()) } 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) Builder { if debugInstr { log.Printf("Store %v, %v\n", ptr.impl.Name(), val.impl) } b.impl.CreateStore(val.impl, ptr.impl) return b } // 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) indices := []llvm.Value{idx.impl} return Expr{llvm.CreateInBoundsGEP(b.impl, telem.ll, x.impl, indices), pt} } // 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(t Type, heap bool) (ret Expr) { if debugInstr { log.Printf("Alloc %v, %v\n", t.ll, heap) } telem := b.prog.Elem(t) if heap { ret.impl = llvm.CreateAlloca(b.impl, telem.ll) } else { panic("todo") } // TODO: zero-initialize ret.Type = t return } // ----------------------------------------------------------------------------- // The Call instruction represents a function or method 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() // t7 = invoke t5.Println(...t6) func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { if debugInstr { var b bytes.Buffer fmt.Fprint(&b, "Call @", fn.impl.Name()) for _, arg := range args { fmt.Fprint(&b, ", ", arg.impl) } log.Println(b.String()) } switch t := fn.t.(type) { case *types.Signature: ret.Type = b.prog.retType(t) default: panic("todo") } ret.impl = llvm.CreateCall(b.impl, fn.ll, fn.impl, llvmValues(args)) return } // -----------------------------------------------------------------------------