diff --git a/gossa/decl.go b/ssa/decl.go similarity index 98% rename from gossa/decl.go rename to ssa/decl.go index e6f05579..4d460dc3 100644 --- a/gossa/decl.go +++ b/ssa/decl.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package gossa +package ssa import ( "github.com/goplus/llvm" diff --git a/gossa/func.go b/ssa/func.go similarity index 99% rename from gossa/func.go rename to ssa/func.go index e1c2a226..13ef513a 100644 --- a/gossa/func.go +++ b/ssa/func.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package gossa +package ssa // Function represents the parameters, results, and code of a function // or method. diff --git a/gossa/package.go b/ssa/package.go similarity index 78% rename from gossa/package.go rename to ssa/package.go index e6a1b01e..66908f47 100644 --- a/gossa/package.go +++ b/ssa/package.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package gossa +package ssa import ( "go/constant" @@ -30,7 +30,10 @@ import ( type Program struct { ctx llvm.Context typs typeutil.Map - td llvm.TargetData + + target *Target + td llvm.TargetData + tm llvm.TargetMachine intType llvm.Type int8Type llvm.Type @@ -39,11 +42,14 @@ type Program struct { int64Type llvm.Type } -func NewProgram(targetRep string) *Program { +func NewProgram(target *Target) *Program { + if target == nil { + target = &Target{} + } ctx := llvm.NewContext() ctx.Finalize() - td := llvm.NewTargetData(targetRep) - return &Program{ctx: ctx, td: td} + td := llvm.NewTargetData("") // TODO(xsw): target config + return &Program{ctx: ctx, target: target, td: td} } func (p *Program) NewPackage(name, pkgPath string) *Package { @@ -82,7 +88,24 @@ func (p *Package) NewFunc(name string, sig *types.Signature) *Function { return &Function{} } -func (p *Package) Bytes() []byte { +type CodeGenFileType = llvm.CodeGenFileType + +const ( + AssemblyFile = llvm.AssemblyFile + ObjectFile = llvm.ObjectFile +) + +func (p *Package) CodeGen(ft CodeGenFileType) (ret []byte, err error) { + buf, err := p.prog.targetMachine().EmitToMemoryBuffer(p.mod, ft) + if err != nil { + return + } + ret = buf.Bytes() + buf.Dispose() + return +} + +func (p *Package) Bitcode() []byte { buf := llvm.WriteBitcodeToMemoryBuffer(p.mod) ret := buf.Bytes() buf.Dispose() @@ -90,7 +113,7 @@ func (p *Package) Bytes() []byte { } func (p *Package) WriteTo(w io.Writer) (int64, error) { - n, err := w.Write(p.Bytes()) + n, err := w.Write(p.Bitcode()) return int64(n), err } diff --git a/gossa/gossa_test.go b/ssa/ssa_test.go similarity index 82% rename from gossa/gossa_test.go rename to ssa/ssa_test.go index 63391891..bd5070a9 100644 --- a/gossa/gossa_test.go +++ b/ssa/ssa_test.go @@ -14,28 +14,25 @@ * limitations under the License. */ -package gossa +package ssa import ( "go/types" "testing" - - "github.com/goplus/llvm" ) func assertPkg(t *testing.T, p *Package) { - ctx := llvm.NewContext() - buf := llvm.WriteBitcodeToMemoryBuffer(p.mod) - mod, err := ctx.ParseIR(buf) - // buf.Dispose() + b, err := p.CodeGen(AssemblyFile) if err != nil { t.Fatal("ctx.ParseIR:", err) } - _ = mod + if v := string(b); v != "" { + t.Log(v) + } } func TestVar(t *testing.T) { - prog := NewProgram("") + prog := NewProgram(nil) pkg := prog.NewPackage("foo", "foo") pkg.NewVar("a", types.Typ[types.Int]) assertPkg(t, pkg) diff --git a/ssa/target.go b/ssa/target.go new file mode 100644 index 00000000..1154f10c --- /dev/null +++ b/ssa/target.go @@ -0,0 +1,152 @@ +/* + * 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 ( + "runtime" + + "github.com/goplus/llvm" +) + +func init() { + llvm.InitializeAllTargetInfos() + llvm.InitializeAllTargets() + llvm.InitializeAllTargetMCs() + llvm.InitializeAllAsmParsers() + llvm.InitializeAllAsmPrinters() +} + +type Target struct { + GOOS string + GOARCH string + GOARM string // "5", "6", "7" (default) +} + +func (p *Program) targetMachine() llvm.TargetMachine { + if p.tm.C == nil { + spec := p.target.toSpec() + target, err := llvm.GetTargetFromTriple(spec.triple) + if err != nil { + panic(err) + } + p.tm = target.CreateTargetMachine( + spec.triple, + spec.cpu, + spec.features, + llvm.CodeGenLevelDefault, + llvm.RelocDefault, + llvm.CodeModelDefault, + ) + } + return p.tm +} + +type targetSpec struct { + triple string + cpu string + features string +} + +func (p *Target) toSpec() (spec targetSpec) { + // Configure based on GOOS/GOARCH environment variables (falling back to + // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it. + var llvmarch string + var goarch = p.GOARCH + var goos = p.GOOS + if goarch == "" { + goarch = runtime.GOARCH + } + if goos == "" { + goos = runtime.GOOS + } + switch goarch { + case "386": + llvmarch = "i386" + case "amd64": + llvmarch = "x86_64" + case "arm64": + llvmarch = "aarch64" + case "arm": + switch p.GOARM { + case "5": + llvmarch = "armv5" + case "6": + llvmarch = "armv6" + default: + llvmarch = "armv7" + } + case "wasm": + llvmarch = "wasm32" + default: + llvmarch = goarch + } + llvmvendor := "unknown" + llvmos := goos + switch goos { + case "darwin": + // Use macosx* instead of darwin, otherwise darwin/arm64 will refer + // to iOS! + llvmos = "macosx10.12.0" + if llvmarch == "aarch64" { + // Looks like Apple prefers to call this architecture ARM64 + // instead of AArch64. + llvmarch = "arm64" + llvmos = "macosx11.0.0" + } + llvmvendor = "apple" + case "wasip1": + llvmos = "wasi" + } + // Target triples (which actually have four components, but are called + // triples for historical reasons) have the form: + // arch-vendor-os-environment + spec.triple = llvmarch + "-" + llvmvendor + "-" + llvmos + if llvmos == "windows" { + spec.triple += "-gnu" + } else if goarch == "arm" { + spec.triple += "-gnueabihf" + } + switch goarch { + case "386": + spec.cpu = "pentium4" + spec.features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87" + case "amd64": + spec.cpu = "x86-64" + spec.features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87" + case "arm": + spec.cpu = "generic" + switch llvmarch { + case "armv5": + spec.features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + case "armv6": + spec.features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + case "armv7": + spec.features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" + } + case "arm64": + spec.cpu = "generic" + if goos == "darwin" { + spec.features = "+neon" + } else { // windows, linux + spec.features = "+neon,-fmv" + } + case "wasm": + spec.cpu = "generic" + spec.features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" + } + return +} diff --git a/gossa/type.go b/ssa/type.go similarity index 99% rename from gossa/type.go rename to ssa/type.go index af1e92b6..270561aa 100644 --- a/gossa/type.go +++ b/ssa/type.go @@ -14,7 +14,7 @@ * limitations under the License. */ -package gossa +package ssa import ( "go/types"