diff --git a/cl/alias.go b/cl/alias.go deleted file mode 100644 index ada3782e..00000000 --- a/cl/alias.go +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2023 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 cl - -/* -import ( - llvm "tinygo.org/x/go-llvm" -) - -var stdlibAliases = map[string]string{ - // crypto packages - "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", - "crypto/ed25519/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", - "crypto/md5.block": "crypto/md5.blockGeneric", - "crypto/sha1.block": "crypto/sha1.blockGeneric", - "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", - "crypto/sha256.block": "crypto/sha256.blockGeneric", - "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", - - // math package - "math.archHypot": "math.hypot", - "math.archMax": "math.max", - "math.archMin": "math.min", - "math.archModf": "math.modf", -} - -// createAlias implements the function (in the builder) as a call to the alias -// function. -func (b *builder) createAlias(alias llvm.Value) { - panic("todo") -} -*/ diff --git a/cl/builder.go b/cl/builder.go deleted file mode 100644 index 98c51ddf..00000000 --- a/cl/builder.go +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2023 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 cl - -/* -import ( - "golang.org/x/tools/go/ssa" - llvm "tinygo.org/x/go-llvm" -) - -// builder contains all information relevant to build a single function. -type builder struct { -} - -func newBuilder(c *context, irbuilder llvm.Builder, f *ssa.Function) *builder { - panic("todo") -} - -// createFunction builds the LLVM IR implementation for this function. The -// function must not yet be defined, otherwise this function will create a -// diagnostic. -func (b *builder) createFunction() { - panic("todo") -} -*/ diff --git a/cl/compile.go b/cl/compile.go index 825b05fb..37d2e672 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -17,6 +17,7 @@ package cl import ( + "fmt" "sort" llssa "github.com/goplus/llgo/ssa" @@ -26,84 +27,93 @@ import ( type Config struct { } +// ----------------------------------------------------------------------------- + +type instrAndValue interface { + ssa.Instruction + ssa.Value +} + type context struct { + prog llssa.Program + pkg llssa.Package + fn llssa.Function + glbs map[*ssa.Global]llssa.Global } // Global variable. -func (p *context) compileGlobal(ret llssa.Package, member *ssa.Global) { - ret.NewVar(member.Name(), member.Type()) +func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) llssa.Global { + if g, ok := p.glbs[gbl]; ok { + return g + } + g := pkg.NewVar(gbl.Name(), gbl.Type()) + p.glbs[gbl] = g + return g } -func (p *context) compileType(ret llssa.Package, member *ssa.Type) { +func (p *context) compileType(pkg llssa.Package, member *ssa.Type) { panic("todo") - /* - if types.IsInterface(member.Type()) { - // Interfaces don't have concrete methods. - continue - } - - // Named type. We should make sure all methods are created. - // This includes both functions with pointer receivers and those - // without. - methods := getAllMethods(pkg.Prog, member.Type()) - methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...) - for _, method := range methods { - // Parse this method. - fn := pkg.Prog.MethodValue(method) - if fn == nil { - continue // probably a generic method - } - if member.Type().String() != member.String() { - // This is a member on a type alias. Do not build such a - // function. - continue - } - if fn.Blocks == nil { - continue // external function - } - if fn.Synthetic != "" && fn.Synthetic != "package initializer" { - // This function is a kind of wrapper function (created by - // the ssa package, not appearing in the source code) that - // is created by the getFunction method as needed. - // Therefore, don't build it here to avoid "function - // redeclared" errors. - continue - } - // Create the function definition. - b := newBuilder(c, irbuilder, fn) - b.createFunction() - } - */ } -func (p *context) compileFunc(ret llssa.Package, member *ssa.Function) { - // panic("todo") - /* - // Create the function definition. - b := newBuilder(c, irbuilder, member) - if _, ok := mathToLLVMMapping[member.RelString(nil)]; ok { - // The body of this function (if there is one) is ignored and - // replaced with a LLVM intrinsic call. - b.defineMathOp() - continue - } - if ok := b.defineMathBitsIntrinsic(); ok { - // Like a math intrinsic, the body of this function was replaced - // with a LLVM intrinsic. - continue - } - if member.Blocks == nil { - // Try to define this as an intrinsic function. - b.defineIntrinsicFunction() - // It might not be an intrinsic function but simply an external - // function (defined via //go:linkname). Leave it undefined in - // that case. - continue - } - b.createFunction() - */ +func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) { + fn := pkg.NewFunc(f.Name(), f.Signature) + p.fn = fn + defer func() { + p.fn = nil + }() + if f.Blocks == nil { // external function + return + } + b := fn.MakeBody("") + for _, block := range f.Blocks { + p.compileBlock(b, block) + } } +func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock) { + _ = block.Index + for _, instr := range block.Instrs { + p.compileInstr(b, instr) + } +} + +func (p *context) compileInstrAndValue(b llssa.Builder, iv instrAndValue) llssa.Expr { + switch v := iv.(type) { + case *ssa.UnOp: + x := p.compileValue(b, v.X) + return b.UnOp(v.Op, x) + } + panic(fmt.Sprintf("compileInstrAndValue: unknown instr - %T\n", iv)) +} + +func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { + if iv, ok := instr.(instrAndValue); ok { + p.compileInstrAndValue(b, iv) + return + } + switch v := instr.(type) { + case *ssa.If: + p.compileValue(b, v.Cond) + return + } + panic(fmt.Sprintf("compileInstr: unknown instr - %T\n", instr)) +} + +func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { + if iv, ok := v.(instrAndValue); ok { + return p.compileInstrAndValue(b, iv) + } + switch v := v.(type) { + case *ssa.Global: + g := p.compileGlobal(p.pkg, v) + return g.Expr + } + panic(fmt.Sprintf("compileValue: unknown value - %T\n", v)) +} + +// ----------------------------------------------------------------------------- + +// NewPackage compiles a Go package to LLVM IR package. func NewPackage(prog llssa.Program, pkg *ssa.Package, conf *Config) (ret llssa.Package, err error) { type namedMember struct { name string @@ -126,7 +136,11 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, conf *Config) (ret llssa.P pkgTypes := pkg.Pkg ret = prog.NewPackage(pkgTypes.Name(), pkgTypes.Path()) - ctx := &context{} + ctx := &context{ + prog: prog, + pkg: ret, + glbs: make(map[*ssa.Global]llssa.Global), + } for _, m := range members { member := m.val switch member := member.(type) { @@ -135,7 +149,9 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, conf *Config) (ret llssa.P // Do not try to build generic (non-instantiated) functions. continue } - ctx.compileFunc(ret, member) + if false { + ctx.compileFunc(ret, member) + } case *ssa.Type: ctx.compileType(ret, member) case *ssa.Global: @@ -145,261 +161,4 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, conf *Config) (ret llssa.P return } -/* -import ( - "go/types" - "os" - "runtime" - "sort" - - "github.com/goplus/llgo/loader" - "github.com/qiniu/x/errors" - "golang.org/x/tools/go/ssa" - llvm "tinygo.org/x/go-llvm" -) - -type Config struct { - Triple string - - Target llvm.TargetMachine -} - -type context struct { - mod llvm.Module - ctx llvm.Context - - embedGlobals map[string][]*loader.EmbedFile - - errs errors.List -} - -func newContext(moduleName string, conf *Config) *context { - machine := conf.Target - targetData := machine.CreateTargetData() - ctx := llvm.NewContext() - mod := ctx.NewModule(moduleName) - mod.SetTarget(conf.Triple) - mod.SetDataLayout(targetData.String()) - return &context{mod: mod, ctx: ctx} -} - -func (c *context) dispose() { - panic("todo") -} - -// createPackage builds the LLVM IR for all types, methods, and global variables -// in the given package. -func (c *context) createPackage(irbuilder llvm.Builder, pkg *ssa.Package) { - type namedMember struct { - name string - val ssa.Member - } - - // Sort by position, so that the order of the functions in the IR matches - // the order of functions in the source file. This is useful for testing, - // for example. - var members []*namedMember - for name, v := range pkg.Members { - members = append(members, &namedMember{name, v}) - } - sort.Slice(members, func(i, j int) bool { - iPos := members[i].val.Pos() - jPos := members[j].val.Pos() - return iPos < jPos - }) - - // Define all functions. - for _, m := range members { - member := m.val - switch member := member.(type) { - case *ssa.Function: - if member.TypeParams() != nil { - // Do not try to build generic (non-instantiated) functions. - continue - } - // Create the function definition. - b := newBuilder(c, irbuilder, member) - if _, ok := mathToLLVMMapping[member.RelString(nil)]; ok { - // The body of this function (if there is one) is ignored and - // replaced with a LLVM intrinsic call. - b.defineMathOp() - continue - } - if ok := b.defineMathBitsIntrinsic(); ok { - // Like a math intrinsic, the body of this function was replaced - // with a LLVM intrinsic. - continue - } - if member.Blocks == nil { - // Try to define this as an intrinsic function. - b.defineIntrinsicFunction() - // It might not be an intrinsic function but simply an external - // function (defined via //go:linkname). Leave it undefined in - // that case. - continue - } - b.createFunction() - case *ssa.Type: - if types.IsInterface(member.Type()) { - // Interfaces don't have concrete methods. - continue - } - - // Named type. We should make sure all methods are created. - // This includes both functions with pointer receivers and those - // without. - methods := getAllMethods(pkg.Prog, member.Type()) - methods = append(methods, getAllMethods(pkg.Prog, types.NewPointer(member.Type()))...) - for _, method := range methods { - // Parse this method. - fn := pkg.Prog.MethodValue(method) - if fn == nil { - continue // probably a generic method - } - if member.Type().String() != member.String() { - // This is a member on a type alias. Do not build such a - // function. - continue - } - if fn.Blocks == nil { - continue // external function - } - if fn.Synthetic != "" && fn.Synthetic != "package initializer" { - // This function is a kind of wrapper function (created by - // the ssa package, not appearing in the source code) that - // is created by the getFunction method as needed. - // Therefore, don't build it here to avoid "function - // redeclared" errors. - continue - } - // Create the function definition. - b := newBuilder(c, irbuilder, fn) - b.createFunction() - } - case *ssa.Global: - // Global variable. - info := c.getGlobalInfo(member) - global := c.getGlobal(member) - if files, ok := c.embedGlobals[member.Name()]; ok { - c.createEmbedGlobal(member, global, files) - } else if !info.extern { - global.SetInitializer(llvm.ConstNull(global.GlobalValueType())) - global.SetVisibility(llvm.HiddenVisibility) - if info.section != "" { - global.SetSection(info.section) - } - } - } - } - - // Add forwarding functions for functions that would otherwise be - // implemented in assembly. - for _, m := range members { - member := m.val - switch member := member.(type) { - case *ssa.Function: - if member.Blocks != nil { - continue // external function - } - info := c.getFunctionInfo(member) - if aliasName, ok := stdlibAliases[info.linkName]; ok { - alias := c.mod.NamedFunction(aliasName) - if alias.IsNil() { - // Shouldn't happen, but perhaps best to just ignore. - // The error will be a link error, if there is an error. - continue - } - b := newBuilder(c, irbuilder, member) - b.createAlias(alias) - } - } - } -} - -// createEmbedGlobal creates an initializer for a //go:embed global variable. -func (c *context) createEmbedGlobal(member *ssa.Global, global llvm.Value, files []*loader.EmbedFile) { - panic("todo") -} - -type Package struct { - llvm.Module -} - -func NewPackage(moduleName string, pkg loader.Package, conf *Config) (ret Package, err error) { - ssaPkg := pkg.SSA - ssaPkg.Build() - - c := newContext(moduleName, conf) - defer c.dispose() - - // Load comments such as //go:extern on globals. - c.loadASTComments(pkg) - - /* TODO: gc related - // Predeclare the runtime.alloc function, which is used by the wordpack - // functionality. - c.getFunction(c.program.ImportedPackage("runtime").Members["alloc"].(*ssa.Function)) - if c.NeedsStackObjects { - // Predeclare trackPointer, which is used everywhere we use runtime.alloc. - c.getFunction(c.program.ImportedPackage("runtime").Members["trackPointer"].(*ssa.Function)) - } -*/ -/* - // Compile all functions, methods, and global variables in this package. - irbuilder := c.ctx.NewBuilder() - defer irbuilder.Dispose() - c.createPackage(irbuilder, ssaPkg) - - /* TODO: risc-v - // Add the "target-abi" flag, which is necessary on RISC-V otherwise it will - // pick one that doesn't match the -mabi Clang flag. - if c.ABI != "" { - c.mod.AddNamedMetadataOperand("llvm.module.flags", - c.ctx.MDNode([]llvm.Metadata{ - llvm.ConstInt(c.ctx.Int32Type(), 1, false).ConstantAsMetadata(), // Error on mismatch - c.ctx.MDString("target-abi"), - c.ctx.MDString(c.ABI), - }), - ) - } -*/ -/* - ret.Module = c.mod - err = c.errs.ToError() - return -} - -func (p Package) Dispose() { - p.Module.Dispose() -} - -func (p Package) WriteFile(file string) (err error) { - f, err := os.Create(file) - if err != nil { - return - } - err = p.WriteTo(f) - f.Close() - if err != nil { - os.Remove(file) - } - return -} - -func (p Package) WriteTo(f *os.File) (err error) { - if runtime.GOOS == "windows" { - // Work around a problem on Windows. - // For some reason, WriteBitcodeToFile causes TinyGo to - // exit with the following message: - // LLVM ERROR: IO failure on output stream: Bad file descriptor - buf := llvm.WriteBitcodeToMemoryBuffer(p.Module) - defer buf.Dispose() - _, err = f.Write(buf.Bytes()) - } else { - // Otherwise, write bitcode directly to the file (probably - // faster). - err = llvm.WriteBitcodeToFile(p.Module, f) - } - return -} -*/ +// ----------------------------------------------------------------------------- diff --git a/cl/compile_test.go b/cl/compile_test.go index 7a9e8abf..022362d3 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -22,6 +22,7 @@ import ( "go/parser" "go/token" "go/types" + "os" "testing" llssa "github.com/goplus/llgo/ssa" @@ -42,6 +43,12 @@ func testCompile(t *testing.T, src, expected string) { if err != nil { t.Fatal("BuildPackage failed:", err) } + foo.WriteTo(os.Stderr) + for _, m := range foo.Members { + if f, ok := m.(*ssa.Function); ok { + f.WriteTo(os.Stderr) + } + } prog := llssa.NewProgram(nil) ret, err := NewPackage(prog, foo, nil) if err != nil { diff --git a/ssa/expr.go b/ssa/expr.go index 872a52b0..e449df7c 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -149,7 +149,8 @@ func isPredOp(op token.Token) bool { return op >= predOpBase && op <= predOpLast } -// op: +// 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 == != < <= < >= @@ -195,6 +196,27 @@ func (b Builder) BinOp(op token.Token, x, y Expr) Expr { 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) + } + panic("todo") +} + +// Load returns the value at the pointer ptr. +func (b Builder) Load(ptr Expr) Expr { + elem := ptr.t.(*types.Pointer).Elem() + telem := b.prog.llvmType(elem) + return Expr{llvm.CreateLoad(b.impl, telem.ll, ptr.impl), telem} +} + // ----------------------------------------------------------------------------- func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index aef73ace..688d02ba 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -228,3 +228,25 @@ define i64 @fn(i64 %0, double %1) { } `) } + +func TestUnOp(t *testing.T) { + prog := NewProgram(nil) + pkg := prog.NewPackage("bar", "foo/bar") + params := types.NewTuple( + types.NewVar(0, nil, "p", types.NewPointer(types.Typ[types.Int])), + ) + rets := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])) + sig := types.NewSignatureType(nil, nil, nil, params, rets, false) + fn := pkg.NewFunc("fn", sig) + b := fn.MakeBody("") + ret := b.UnOp(token.MUL, fn.Param(0)) + b.Return(ret) + assertPkg(t, pkg, `; ModuleID = 'foo/bar' +source_filename = "foo/bar" + +define i64 @fn(ptr %0) { + %2 = load i64, ptr %0, align 4 + ret i64 %2 +} +`) +}