diff --git a/cl/alias.go b/cl/alias.go new file mode 100644 index 00000000..7e20ddc1 --- /dev/null +++ b/cl/alias.go @@ -0,0 +1,44 @@ +/* + * 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 new file mode 100644 index 00000000..3591ffb1 --- /dev/null +++ b/cl/builder.go @@ -0,0 +1,37 @@ +/* + * 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 2c76a4e7..83ac1c15 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -17,6 +17,9 @@ package cl import ( + "go/types" + "sort" + "github.com/goplus/llgo/loader" "github.com/qiniu/x/errors" "golang.org/x/tools/go/ssa" @@ -30,8 +33,11 @@ type Config struct { } type context struct { - mod llvm.Module - ctx llvm.Context + mod llvm.Module + ctx llvm.Context + + embedGlobals map[string][]*loader.EmbedFile + errs errors.List } @@ -49,13 +55,137 @@ func (c *context) dispose() { panic("todo") } -func (c *context) loadASTComments(loader.Package) { - 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") } diff --git a/cl/intrinsics.go b/cl/intrinsics.go new file mode 100644 index 00000000..a99f2e54 --- /dev/null +++ b/cl/intrinsics.go @@ -0,0 +1,60 @@ +/* + * 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 + +// Define unimplemented intrinsic functions. +// +// Some functions are either normally implemented in Go assembly (like +// sync/atomic functions) or intentionally left undefined to be implemented +// directly in the compiler (like runtime/volatile functions). Either way, look +// for these and implement them if this is the case. +func (b *builder) defineIntrinsicFunction() { + panic("todo") +} + +var mathToLLVMMapping = map[string]string{ + "math.Ceil": "llvm.ceil.f64", + "math.Exp": "llvm.exp.f64", + "math.Exp2": "llvm.exp2.f64", + "math.Floor": "llvm.floor.f64", + "math.Log": "llvm.log.f64", + "math.Sqrt": "llvm.sqrt.f64", + "math.Trunc": "llvm.trunc.f64", +} + +// defineMathOp defines a math function body as a call to a LLVM intrinsic, +// instead of the regular Go implementation. This allows LLVM to reason about +// the math operation and (depending on the architecture) allows it to lower the +// operation to very fast floating point instructions. If this is not possible, +// LLVM will emit a call to a libm function that implements the same operation. +// +// One example of an optimization that LLVM can do is to convert +// float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is +// beneficial on architectures where 64-bit floating point operations are (much) +// more expensive than 32-bit ones. +func (b *builder) defineMathOp() { + panic("todo") +} + +// Implement most math/bits functions. +// +// This implements all the functions that operate on bits. It does not yet +// implement the arithmetic functions (like bits.Add), which also have LLVM +// intrinsics. +func (b *builder) defineMathBitsIntrinsic() bool { + panic("todo") +} diff --git a/cl/symbol.go b/cl/symbol.go new file mode 100644 index 00000000..7db0d222 --- /dev/null +++ b/cl/symbol.go @@ -0,0 +1,98 @@ +/* + * 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 ( + "go/types" + + "github.com/goplus/llgo/loader" + "golang.org/x/tools/go/ssa" + llvm "tinygo.org/x/go-llvm" +) + +// functionInfo contains some information about a function or method. In +// particular, it contains information obtained from pragmas. +// +// The linkName value contains a valid link name, even if //go:linkname is not +// present. +type functionInfo struct { + wasmModule string // go:wasm-module + wasmName string // wasm-export-name or wasm-import-name in the IR + linkName string // go:linkname, go:export - the IR function name + section string // go:section - object file section name + exported bool // go:export, CGo + interrupt bool // go:interrupt + nobounds bool // go:nobounds + variadic bool // go:variadic (CGo only) + inline inlineType // go:inline +} + +type inlineType int + +// How much to inline. +const ( + // Default behavior. The compiler decides for itself whether any given + // function will be inlined. Whether any function is inlined depends on the + // optimization level. + inlineDefault inlineType = iota + + // Inline hint, just like the C inline keyword (signalled using + // //go:inline). The compiler will be more likely to inline this function, + // but it is not a guarantee. + inlineHint + + // Don't inline, just like the GCC noinline attribute. Signalled using + // //go:noinline. + inlineNone +) + +// getFunctionInfo returns information about a function that is not directly +// present in *ssa.Function, such as the link name and whether it should be +// exported. +func (c *context) getFunctionInfo(f *ssa.Function) functionInfo { + panic("todo") +} + +// globalInfo contains some information about a specific global. By default, +// linkName is equal to .RelString(nil) on a global and extern is false, but for +// some symbols this is different (due to //go:extern for example). +type globalInfo struct { + linkName string // go:extern + extern bool // go:extern + align int // go:align + section string // go:section +} + +func (c *context) loadASTComments(loader.Package) { + panic("todo") +} + +// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to +// the LLVM IR if it has not been added already. +func (c *context) getGlobal(g *ssa.Global) llvm.Value { + panic("todo") +} + +// getGlobalInfo returns some information about a specific global. +func (c *context) getGlobalInfo(g *ssa.Global) globalInfo { + panic("todo") +} + +// Get all methods of a type. +func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { + panic("todo") +} diff --git a/loader/loader.go b/loader/loader.go index d252d88b..89facef9 100644 --- a/loader/loader.go +++ b/loader/loader.go @@ -21,5 +21,16 @@ import ( ) type Package struct { - SSA *ssa.Package + SSA *ssa.Package + EmbedGlobals map[string][]*EmbedFile +} + +type EmbedFile struct { + /* + Name string + Size uint64 + Hash string // hash of the file (as a hex string) + NeedsData bool // true if this file is embedded as a byte slice + Data []byte // contents of this file (only if NeedsData is set) + */ }