diff --git a/ssa/builder.go b/ssa/builder.go new file mode 100644 index 00000000..3b6ec4ea --- /dev/null +++ b/ssa/builder.go @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ssa + +import ( + "github.com/goplus/llvm" +) + +type Builder struct { + impl llvm.Builder +} + +type BasicBlock struct { + impl llvm.BasicBlock +} + +func (p BasicBlock) End() { +} diff --git a/ssa/decl.go b/ssa/decl.go index 4d460dc3..582b909a 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -28,14 +28,79 @@ import ( // // NB: a NamedConst is not a Value; it contains a constant Value, which // it augments with the name and position of its 'const' declaration. -type NamedConst struct { +type TyNamedConst struct { } +type NamedConst = *TyNamedConst + // A Global is a named Value holding the address of a package-level // variable. // // Pos() returns the position of the ast.ValueSpec.Names[*] // identifier. -type Global struct { +type TyGlobal struct { impl llvm.Value } + +type Global = *TyGlobal + +// Function represents the parameters, results, and code of a function +// or method. +// +// If Blocks is nil, this indicates an external function for which no +// Go source code is available. In this case, FreeVars, Locals, and +// Params are nil too. Clients performing whole-program analysis must +// handle external functions specially. +// +// Blocks contains the function's control-flow graph (CFG). +// Blocks[0] is the function entry point; block order is not otherwise +// semantically significant, though it may affect the readability of +// the disassembly. +// To iterate over the blocks in dominance order, use DomPreorder(). +// +// Recover is an optional second entry point to which control resumes +// after a recovered panic. The Recover block may contain only a return +// statement, preceded by a load of the function's named return +// parameters, if any. +// +// A nested function (Parent()!=nil) that refers to one or more +// lexically enclosing local variables ("free variables") has FreeVars. +// Such functions cannot be called directly but require a +// value created by MakeClosure which, via its Bindings, supplies +// values for these parameters. +// +// If the function is a method (Signature.Recv() != nil) then the first +// element of Params is the receiver parameter. +// +// A Go package may declare many functions called "init". +// For each one, Object().Name() returns "init" but Name() returns +// "init#1", etc, in declaration order. +// +// Pos() returns the declaring ast.FuncLit.Type.Func or the position +// of the ast.FuncDecl.Name, if the function was explicit in the +// source. Synthetic wrappers, for which Synthetic != "", may share +// the same position as the function they wrap. +// Syntax.Pos() always returns the position of the declaring "func" token. +// +// Type() returns the function's Signature. +// +// A generic function is a function or method that has uninstantiated type +// parameters (TypeParams() != nil). Consider a hypothetical generic +// method, (*Map[K,V]).Get. It may be instantiated with all ground +// (non-parameterized) types as (*Map[string,int]).Get or with +// parameterized types as (*Map[string,U]).Get, where U is a type parameter. +// In both instantiations, Origin() refers to the instantiated generic +// method, (*Map[K,V]).Get, TypeParams() refers to the parameters [K,V] of +// the generic method. TypeArgs() refers to [string,U] or [string,int], +// respectively, and is nil in the generic method. +type TyFunction struct { + impl llvm.Value + prog Program +} + +type Function = *TyFunction + +func (p *TyFunction) BodyStart() *BasicBlock { + body := llvm.AddBasicBlock(p.impl, "entry") + return &BasicBlock{body} +} diff --git a/ssa/expr.go b/ssa/expr.go new file mode 100644 index 00000000..4c151a79 --- /dev/null +++ b/ssa/expr.go @@ -0,0 +1,17 @@ +/* + * 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 diff --git a/ssa/func.go b/ssa/func.go deleted file mode 100644 index 3890bff7..00000000 --- a/ssa/func.go +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ssa - -import ( - "github.com/goplus/llvm" -) - -// Function represents the parameters, results, and code of a function -// or method. -// -// If Blocks is nil, this indicates an external function for which no -// Go source code is available. In this case, FreeVars, Locals, and -// Params are nil too. Clients performing whole-program analysis must -// handle external functions specially. -// -// Blocks contains the function's control-flow graph (CFG). -// Blocks[0] is the function entry point; block order is not otherwise -// semantically significant, though it may affect the readability of -// the disassembly. -// To iterate over the blocks in dominance order, use DomPreorder(). -// -// Recover is an optional second entry point to which control resumes -// after a recovered panic. The Recover block may contain only a return -// statement, preceded by a load of the function's named return -// parameters, if any. -// -// A nested function (Parent()!=nil) that refers to one or more -// lexically enclosing local variables ("free variables") has FreeVars. -// Such functions cannot be called directly but require a -// value created by MakeClosure which, via its Bindings, supplies -// values for these parameters. -// -// If the function is a method (Signature.Recv() != nil) then the first -// element of Params is the receiver parameter. -// -// A Go package may declare many functions called "init". -// For each one, Object().Name() returns "init" but Name() returns -// "init#1", etc, in declaration order. -// -// Pos() returns the declaring ast.FuncLit.Type.Func or the position -// of the ast.FuncDecl.Name, if the function was explicit in the -// source. Synthetic wrappers, for which Synthetic != "", may share -// the same position as the function they wrap. -// Syntax.Pos() always returns the position of the declaring "func" token. -// -// Type() returns the function's Signature. -// -// A generic function is a function or method that has uninstantiated type -// parameters (TypeParams() != nil). Consider a hypothetical generic -// method, (*Map[K,V]).Get. It may be instantiated with all ground -// (non-parameterized) types as (*Map[string,int]).Get or with -// parameterized types as (*Map[string,U]).Get, where U is a type parameter. -// In both instantiations, Origin() refers to the instantiated generic -// method, (*Map[K,V]).Get, TypeParams() refers to the parameters [K,V] of -// the generic method. TypeArgs() refers to [string,U] or [string,int], -// respectively, and is nil in the generic method. -type Function struct { - impl llvm.Value -} diff --git a/ssa/operator.go b/ssa/operator.go new file mode 100644 index 00000000..31232763 --- /dev/null +++ b/ssa/operator.go @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ssa + +import ( + "go/token" + + "github.com/goplus/llvm" +) + +type valueKind = int + +type Value struct { + impl llvm.Value + kind valueKind +} + +const ( + mathOpBase = token.ADD + mathOpLast = token.REM +) + +const ( + vkInvalid valueKind = iota + vkSigned + vkUnsigned + vkFloat + vkComplex + vkString + vkBool +) + +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.LShr, +} + +// 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 +} + +// op: +// 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 Value) (v Value) { + switch { + case isMathOp(op): // op: + - * / % + switch x.kind { + case vkString, vkComplex: + panic("todo") + } + idx := mathOpIdx(op, x.kind) + if llop := mathOpToLLVM[idx]; llop != 0 { + v.impl = llvm.CreateBinOp(b.impl, llop, x.impl, y.impl) + return + } + case isLogicOp(op): // op: & | ^ << >> &^ + if op == token.AND_NOT { + panic("todo") + } + llop := logicOpToLLVM[op-logicOpBase] + if op == token.SHR && x.kind == vkUnsigned { + llop = llvm.AShr + } + v.impl = llvm.CreateBinOp(b.impl, llop, x.impl, y.impl) + return + case isPredOp(op): // op: == != < <= < >= + switch x.kind { + case vkSigned: + pred := intPredOpToLLVM[op-predOpBase] + v.impl = llvm.CreateICmp(b.impl, pred, x.impl, y.impl) + return + case vkUnsigned: + pred := uintPredOpToLLVM[op-predOpBase] + v.impl = llvm.CreateICmp(b.impl, pred, x.impl, y.impl) + return + case vkFloat: + pred := floatPredOpToLLVM[op-predOpBase] + v.impl = llvm.ConstFCmp(pred, x.impl, y.impl) + return + case vkString, vkComplex, vkBool: + panic("todo") + } + } + return +} diff --git a/ssa/package.go b/ssa/package.go index 1c897fc7..5bc7fe8c 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -25,8 +25,9 @@ import ( ) // A Program is a partial or complete Go program converted to SSA form. -type Program struct { +type TyProgram struct { ctx llvm.Context + b Builder typs typeutil.Map target *Target @@ -42,20 +43,28 @@ type Program struct { voidPtrTy llvm.Type } -func NewProgram(target *Target) *Program { +type Program = *TyProgram + +func NewProgram(target *Target) Program { if target == nil { target = &Target{} } ctx := llvm.NewContext() ctx.Finalize() + b := ctx.NewBuilder() + b.Finalize() td := llvm.NewTargetData("") // TODO(xsw): target config - return &Program{ctx: ctx, target: target, td: td} + return &TyProgram{ctx: ctx, b: Builder{b}, target: target, td: td} } -func (p *Program) NewPackage(name, pkgPath string) *Package { +func (p *TyProgram) NewPackage(name, pkgPath string) Package { mod := p.ctx.NewModule(pkgPath) mod.Finalize() - return &Package{mod, p} + return &TyPackage{mod, p} +} + +func (p *TyProgram) Builder() Builder { + return p.b } // A Package is a single analyzed Go package containing Members for @@ -66,26 +75,28 @@ func (p *Program) NewPackage(name, pkgPath string) *Package { // Members also contains entries for "init" (the synthetic package // initializer) and "init#%d", the nth declared init function, // and unspecified other things too. -type Package struct { +type TyPackage struct { mod llvm.Module - prog *Program + prog Program } -func (p *Package) NewConst(name string, val constant.Value) *NamedConst { - return &NamedConst{} +type Package = *TyPackage + +func (p *TyPackage) NewConst(name string, val constant.Value) NamedConst { + return &TyNamedConst{} } -func (p *Package) NewVar(name string, typ types.Type) *Global { +func (p *TyPackage) NewVar(name string, typ types.Type) Global { gbl := llvm.AddGlobal(p.mod, p.prog.llvmType(typ), name) - return &Global{gbl} + return &TyGlobal{gbl} } -func (p *Package) NewFunc(name string, sig *types.Signature) *Function { +func (p *TyPackage) NewFunc(name string, sig *types.Signature) Function { fn := llvm.AddFunction(p.mod, name, p.prog.llvmSignature(sig)) - return &Function{fn} + return &TyFunction{fn, p.prog} } -func (p *Package) String() string { +func (p *TyPackage) String() string { return p.mod.String() } diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index eed37539..8bd2aabe 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -33,7 +33,7 @@ func asmPkg(t *testing.T, p *Package) { } */ -func assertPkg(t *testing.T, p *Package, expected string) { +func assertPkg(t *testing.T, p Package, expected string) { if v := p.String(); v != expected { t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected) } diff --git a/ssa/type.go b/ssa/type.go index d4a87ea9..91a660f3 100644 --- a/ssa/type.go +++ b/ssa/type.go @@ -29,7 +29,7 @@ type Type struct { } */ -func (p *Program) llvmType(typ types.Type) llvm.Type { +func (p *TyProgram) llvmType(typ types.Type) llvm.Type { if v := p.typs.At(typ); v != nil { return v.(llvm.Type) } @@ -38,7 +38,7 @@ func (p *Program) llvmType(typ types.Type) llvm.Type { return ret } -func (p *Program) llvmSignature(sig *types.Signature) llvm.Type { +func (p *TyProgram) llvmSignature(sig *types.Signature) llvm.Type { if v := p.typs.At(sig); v != nil { return v.(llvm.Type) } @@ -47,56 +47,56 @@ func (p *Program) llvmSignature(sig *types.Signature) llvm.Type { return ret } -func (p *Program) tyVoidPtr() llvm.Type { +func (p *TyProgram) tyVoidPtr() llvm.Type { if p.voidPtrTy.IsNil() { p.voidPtrTy = llvm.PointerType(p.tyVoid(), 0) } return p.voidPtrTy } -func (p *Program) tyVoid() llvm.Type { +func (p *TyProgram) tyVoid() llvm.Type { if p.voidType.IsNil() { p.voidType = p.ctx.VoidType() } return p.voidType } -func (p *Program) tyInt() llvm.Type { +func (p *TyProgram) tyInt() llvm.Type { if p.intType.IsNil() { p.intType = llvmIntType(p.ctx, p.td.PointerSize()) } return p.intType } -func (p *Program) tyInt8() llvm.Type { +func (p *TyProgram) tyInt8() llvm.Type { if p.int8Type.IsNil() { p.int8Type = p.ctx.Int8Type() } return p.int8Type } -func (p *Program) tyInt16() llvm.Type { +func (p *TyProgram) tyInt16() llvm.Type { if p.int16Type.IsNil() { p.int16Type = p.ctx.Int16Type() } return p.int16Type } -func (p *Program) tyInt32() llvm.Type { +func (p *TyProgram) tyInt32() llvm.Type { if p.int32Type.IsNil() { p.int32Type = p.ctx.Int32Type() } return p.int32Type } -func (p *Program) tyInt64() llvm.Type { +func (p *TyProgram) tyInt64() llvm.Type { if p.int64Type.IsNil() { p.int64Type = p.ctx.Int64Type() } return p.int64Type } -func (p *Program) toLLVMType(typ types.Type) llvm.Type { +func (p *TyProgram) toLLVMType(typ types.Type) llvm.Type { switch t := typ.(type) { case *types.Basic: switch t.Kind() { @@ -146,19 +146,19 @@ func llvmIntType(ctx llvm.Context, size int) llvm.Type { return ctx.Int64Type() } -func (p *Program) toLLVMNamedStruct(name string, typ *types.Struct) llvm.Type { +func (p *TyProgram) toLLVMNamedStruct(name string, typ *types.Struct) llvm.Type { t := p.ctx.StructCreateNamed(name) fields := p.toLLVMFields(typ) t.StructSetBody(fields, false) return t } -func (p *Program) toLLVMStruct(typ *types.Struct) llvm.Type { +func (p *TyProgram) toLLVMStruct(typ *types.Struct) llvm.Type { fields := p.toLLVMFields(typ) return p.ctx.StructType(fields, false) } -func (p *Program) toLLVMFields(typ *types.Struct) []llvm.Type { +func (p *TyProgram) toLLVMFields(typ *types.Struct) []llvm.Type { n := typ.NumFields() fields := make([]llvm.Type, n) for i := 0; i < n; i++ { @@ -167,11 +167,11 @@ func (p *Program) toLLVMFields(typ *types.Struct) []llvm.Type { return fields } -func (p *Program) toLLVMTuple(t *types.Tuple) llvm.Type { +func (p *TyProgram) toLLVMTuple(t *types.Tuple) llvm.Type { return p.ctx.StructType(p.toLLVMTypes(t), false) } -func (p *Program) toLLVMTypes(t *types.Tuple) []llvm.Type { +func (p *TyProgram) toLLVMTypes(t *types.Tuple) []llvm.Type { n := t.Len() ret := make([]llvm.Type, n) for i := 0; i < n; i++ { @@ -180,7 +180,7 @@ func (p *Program) toLLVMTypes(t *types.Tuple) []llvm.Type { return ret } -func (p *Program) toLLVMFunc(sig *types.Signature) llvm.Type { +func (p *TyProgram) toLLVMFunc(sig *types.Signature) llvm.Type { params := p.toLLVMTypes(sig.Params()) results := sig.Results() var ret llvm.Type @@ -195,7 +195,7 @@ func (p *Program) toLLVMFunc(sig *types.Signature) llvm.Type { return llvm.FunctionType(ret, params, sig.Variadic()) } -func (p *Program) toLLVMNamed(typ *types.Named) llvm.Type { +func (p *TyProgram) toLLVMNamed(typ *types.Named) llvm.Type { name := typ.Obj().Name() switch typ := typ.Underlying().(type) { case *types.Struct: