Compare commits

...

40 Commits

Author SHA1 Message Date
xushiwei
bc1821e7a5 Merge pull request #47 from xushiwei/q
llgo run
2024-04-25 01:43:04 +08:00
xushiwei
3f49fe9e98 llgo run 2024-04-25 01:41:44 +08:00
xushiwei
6aee76be72 Merge pull request #46 from xushiwei/q
llgo build/install: a.out generated; verbose
2024-04-25 00:55:53 +08:00
xushiwei
a2c6e5d7fc llgo install: /appName 2024-04-25 00:53:42 +08:00
xushiwei
cbaf9e21b2 llgo build/install: a.out generated 2024-04-25 00:14:02 +08:00
xushiwei
81b3add443 build.Do: verbose 2024-04-24 14:27:14 +08:00
xushiwei
c3e681a7b3 Merge pull request #45 from xushiwei/q
llgo build/install
2024-04-24 12:01:06 +08:00
xushiwei
1567989142 llgo install: use clang to link 2024-04-24 11:55:20 +08:00
xushiwei
5b73480540 disable debug info 2024-04-24 11:20:31 +08:00
xushiwei
b1342d8d97 build.Do 2024-04-24 11:13:17 +08:00
xushiwei
2cbcc53c54 llgo build/install 2024-04-24 07:55:51 +08:00
xushiwei
b4da61df86 Merge pull request #44 from xushiwei/q
ssadump
2024-04-23 19:23:25 +08:00
xushiwei
edb7a4e1a5 ssadump 2024-04-23 18:57:46 +08:00
xushiwei
0387a35112 Merge pull request #43 from xushiwei/q
cl: varOf
2024-04-23 01:18:16 +08:00
xushiwei
aafe5a8600 cl: varOf 2024-04-23 01:16:31 +08:00
xushiwei
67af68ae10 llgo gen: todo 2024-04-23 00:47:38 +08:00
xushiwei
bc472a4fac Merge pull request #42 from xushiwei/q
remove dep of goplus/gop
2024-04-22 21:18:35 +08:00
xushiwei
2d8f5dbc51 remove dep of goplus/gop 2024-04-22 21:16:43 +08:00
xushiwei
9b7d5e2c57 Merge pull request #41 from xushiwei/q
llgen: support go.mod
2024-04-22 21:07:00 +08:00
xushiwei
f1e676a14f go mod tidy 2024-04-22 21:05:01 +08:00
xushiwei
34d83813ec merge 2024-04-22 21:04:26 +08:00
xushiwei
92b7d61b55 llgen: support go.mod 2024-04-22 21:03:06 +08:00
xushiwei
5d48e42069 Merge pull request #40 from xushiwei/q
cl.NewPackage: rm fset *token.FileSet
2024-04-22 20:25:50 +08:00
xushiwei
ead09d94aa cl.NewPackage: rm fset *token.FileSet 2024-04-22 20:23:01 +08:00
xushiwei
7240da07b4 Merge pull request #39 from xushiwei/q
cl: funcOf; use gogen/packages.Importer
2024-04-22 20:11:59 +08:00
xushiwei
72084b5648 cl: _testdata/importpkg 2024-04-22 20:09:23 +08:00
xushiwei
2d75c55d36 cl: funcOf; use gogen/packages.Importer 2024-04-22 17:49:21 +08:00
xushiwei
da71e7c01b Merge pull request #38 from xushiwei/q
cl: support go:linkname
2024-04-22 15:11:10 +08:00
xushiwei
5343a55395 cl: support go:linkname 2024-04-22 15:09:08 +08:00
xushiwei
bbfe2a051c Merge pull request #37 from goplus/dependabot/go_modules/github.com/goplus/llvm-0.7.1
Bump github.com/goplus/llvm from 0.7.1-0.20240420180312-6230a4ea7a47 to 0.7.1
2024-04-22 01:41:33 +08:00
dependabot[bot]
c1b0751ea5 Bump github.com/goplus/llvm
Bumps [github.com/goplus/llvm](https://github.com/goplus/llvm) from 0.7.1-0.20240420180312-6230a4ea7a47 to 0.7.1.
- [Release notes](https://github.com/goplus/llvm/releases)
- [Commits](https://github.com/goplus/llvm/commits/v0.7.1)

---
updated-dependencies:
- dependency-name: github.com/goplus/llvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 17:36:57 +00:00
xushiwei
2ee1f3373b Merge pull request #34 from goplus/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2024-04-22 01:30:05 +08:00
xushiwei
088995088c Merge pull request #35 from goplus/dependabot/github_actions/actions/setup-go-5
Bump actions/setup-go from 4 to 5
2024-04-22 01:29:50 +08:00
xushiwei
b9ab96c89b Merge pull request #36 from goplus/dependabot/go_modules/golang.org/x/tools-0.20.0
Bump golang.org/x/tools from 0.19.0 to 0.20.0
2024-04-22 01:29:31 +08:00
dependabot[bot]
576c276f33 Bump golang.org/x/tools from 0.19.0 to 0.20.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.19.0 to 0.20.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.19.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 17:24:13 +00:00
dependabot[bot]
0a879cb0be Bump actions/setup-go from 4 to 5
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 17:24:12 +00:00
dependabot[bot]
177ba6de48 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 17:24:09 +00:00
xushiwei
4d1079261b Create dependabot.yml 2024-04-22 01:23:51 +08:00
xushiwei
f6ab8834fa Update README.md 2024-04-22 01:04:38 +08:00
xushiwei
2142e14b6d llgo/internal/llgen 2024-04-22 00:15:40 +08:00
32 changed files with 1136 additions and 297 deletions

19
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: github-actions
directory: /
labels:
- dependabot
- actions
schedule:
interval: daily
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@@ -17,7 +17,7 @@ jobs:
matrix:
llvm: [17]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Update Homebrew
if: matrix.llvm == 17 # needed as long as LLVM 17 is still fresh
@@ -26,7 +26,7 @@ jobs:
run: HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@${{ matrix.llvm }}
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: '1.20'
@@ -42,7 +42,7 @@ jobs:
matrix:
llvm: [17]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install LLVM ${{ matrix.llvm }}
run: |
@@ -52,7 +52,7 @@ jobs:
sudo apt-get install --no-install-recommends llvm-${{ matrix.llvm }}-dev
- name: Set up Go
uses: actions/setup-go@v4
uses: actions/setup-go@v5
with:
go-version: '1.20'

View File

@@ -3,8 +3,8 @@ llgo - A Go compiler based on LLVM
[![Build Status](https://github.com/goplus/llgo/actions/workflows/go.yml/badge.svg)](https://github.com/goplus/llgo/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/goplus/llgo)](https://goreportcard.com/report/github.com/goplus/llgo)
[![GitHub release](https://img.shields.io/github/v/tag/goplus/llgo.svg?label=release)](https://github.com/goplus/llgo/releases)
[![GoDoc](https://pkg.go.dev/badge/github.com/goplus/llgo.svg)](https://pkg.go.dev/github.com/goplus/llgo)
<!--
[![GitHub release](https://img.shields.io/github/v/tag/goplus/llgo.svg?label=release)](https://github.com/goplus/llgo/releases)
[![Coverage Status](https://codecov.io/gh/goplus/llgo/branch/main/graph/badge.svg)](https://codecov.io/gh/goplus/llgo)
-->

View File

@@ -1,57 +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 build
import (
"go/build"
)
// An ImportMode controls the behavior of the Import method.
type ImportMode = build.ImportMode
// A Package describes the Go package found in a directory.
type Package struct {
*build.Package
}
// A Context specifies the supporting context for a build.
type Context struct {
*build.Context
}
// Import returns details about the Go package named by the import path,
// interpreting local import paths relative to the srcDir directory.
// If the path is a local import path naming a package that can be imported
// using a standard import path, the returned package will set p.ImportPath
// to that path.
//
// If an error occurs, Import returns a non-nil error and a non-nil
// *Package containing partial information.
func (ctxt Context) Import(path string, srcDir string, mode ImportMode) (ret Package, err error) {
pkg, err := build.Import(path, srcDir, mode)
if err != nil {
return
}
ret = Package{pkg}
return
}
// ImportDir is like Import but processes the Go package found in
// the named directory.
func (ctxt *Context) ImportDir(dir string, mode ImportMode) (Package, error) {
return ctxt.Import(".", dir, mode)
}

View File

@@ -21,12 +21,12 @@ import (
"os"
"path/filepath"
"github.com/goplus/llgo/x/llgen"
"github.com/goplus/llgo/internal/llgen"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "Usage: llgen xxx.go")
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "Usage: llgen xxx.go [pkgPath]")
return
}
@@ -35,6 +35,13 @@ func main() {
dir, _ := filepath.Split(inFile)
outFile := dir + "out.ll"
pkgPath := ""
if len(os.Args) == 3 {
pkgPath = os.Args[2]
} else {
pkgPath = llgen.PkgPath(dir)
}
llgen.Init()
llgen.Do(inFile, outFile)
llgen.Do(pkgPath, inFile, outFile)
}

201
chore/ssadump/ssadump.go Normal file
View File

@@ -0,0 +1,201 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// ssadump: a tool for displaying and interpreting the SSA form of Go programs.
package main // import "golang.org/x/tools/cmd/ssadump"
import (
"flag"
"fmt"
"go/build"
"go/types"
"os"
"runtime"
"runtime/pprof"
"golang.org/x/tools/go/buildutil"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/interp"
"golang.org/x/tools/go/ssa/ssautil"
)
const (
loadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
loadImports = loadFiles | packages.NeedImports
loadTypes = loadImports | packages.NeedTypes | packages.NeedTypesSizes
loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo
)
// flags
var (
mode = ssa.BuilderMode(0)
testFlag = flag.Bool("test", false, "include implicit test packages and executables")
runFlag = flag.Bool("run", false, "interpret the SSA program")
interpFlag = flag.String("interp", "", `Options controlling the SSA test interpreter.
The value is a sequence of zero or more more of these letters:
R disable [R]ecover() from panic; show interpreter crash instead.
T [T]race execution of the program. Best for single-threaded programs!
`)
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
args stringListValue
)
func init() {
flag.Var(&mode, "build", ssa.BuilderModeDoc)
flag.Var((*buildutil.TagsFlag)(&build.Default.BuildTags), "tags", buildutil.TagsFlagDoc)
flag.Var(&args, "arg", "add argument to interpreted program")
}
const usage = `SSA builder and interpreter.
Usage: ssadump [-build=[DBCSNFLG]] [-test] [-run] [-interp=[TR]] [-arg=...] package...
Use -help flag to display options.
Examples:
% ssadump -build=F hello.go # dump SSA form of a single package
% ssadump -build=F -test fmt # dump SSA form of a package and its tests
% ssadump -run -interp=T hello.go # interpret a program, with tracing
The -run flag causes ssadump to build the code in a runnable form and run the first
package named main.
Interpretation of the standard "testing" package is no longer supported.
`
func main() {
if err := doMain(); err != nil {
fmt.Fprintf(os.Stderr, "ssadump: %s\n", err)
os.Exit(1)
}
}
func doMain() error {
flag.Parse()
if len(flag.Args()) == 0 {
fmt.Fprint(os.Stderr, usage)
os.Exit(1)
}
cfg := &packages.Config{
Mode: loadSyntax,
Tests: *testFlag,
}
// Choose types.Sizes from conf.Build.
// TODO(adonovan): remove this when go/packages provides a better way.
var wordSize int64 = 8
switch build.Default.GOARCH {
case "386", "arm":
wordSize = 4
}
sizes := &types.StdSizes{
MaxAlign: 8,
WordSize: wordSize,
}
var interpMode interp.Mode
for _, c := range *interpFlag {
switch c {
case 'T':
interpMode |= interp.EnableTracing
case 'R':
interpMode |= interp.DisableRecover
default:
return fmt.Errorf("unknown -interp option: '%c'", c)
}
}
// Profiling support.
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
// Load, parse and type-check the initial packages,
// and, if -run, their dependencies.
if *runFlag {
cfg.Mode = loadSyntax | packages.NeedDeps
}
initial, err := packages.Load(cfg, flag.Args()...)
if err != nil {
return err
}
if len(initial) == 0 {
return fmt.Errorf("no packages")
}
if packages.PrintErrors(initial) > 0 {
return fmt.Errorf("packages contain errors")
}
// Turn on instantiating generics during build if the program will be run.
if *runFlag {
mode |= ssa.InstantiateGenerics
}
// Create SSA-form program representation.
prog, pkgs := ssautil.AllPackages(initial, mode)
for i, p := range pkgs {
if p == nil {
return fmt.Errorf("cannot build SSA for package %s", initial[i])
}
}
if !*runFlag {
// Build and display only the initial packages
// (and synthetic wrappers).
for _, p := range pkgs {
p.Build()
}
} else {
// Run the interpreter.
// Build SSA for all packages.
prog.Build()
// Earlier versions of the interpreter needed the runtime
// package; however, interp cannot handle unsafe constructs
// used during runtime's package initialization at the moment.
// The key construct blocking support is:
// *((*T)(unsafe.Pointer(p)))
// Unfortunately, this means only trivial programs can be
// interpreted by ssadump.
if prog.ImportedPackage("runtime") != nil {
return fmt.Errorf("-run: program depends on runtime package (interpreter can run only trivial programs)")
}
if runtime.GOARCH != build.Default.GOARCH {
return fmt.Errorf("cross-interpretation is not supported (target has GOARCH %s, interpreter has %s)",
build.Default.GOARCH, runtime.GOARCH)
}
// Run first main package.
for _, main := range ssautil.MainPackages(pkgs) {
fmt.Fprintf(os.Stderr, "Running: %s\n", main.Pkg.Path())
os.Exit(interp.Interpret(main, interpMode, sizes, main.Pkg.Path(), args))
}
return fmt.Errorf("no main package")
}
return nil
}
// stringListValue is a flag.Value that accumulates strings.
// e.g. --flag=one --flag=two would produce []string{"one", "two"}.
type stringListValue []string
func (ss *stringListValue) Get() interface{} { return []string(*ss) }
func (ss *stringListValue) String() string { return fmt.Sprintf("%q", *ss) }
func (ss *stringListValue) Set(s string) error { *ss = append(*ss, s); return nil }

8
cl/_testdata/apkg/in.go Normal file
View File

@@ -0,0 +1,8 @@
package apkg
func Max(a, b float64) float64 {
if a > b {
return a
}
return b
}

29
cl/_testdata/apkg/out.ll Normal file
View File

@@ -0,0 +1,29 @@
; ModuleID = 'apkg'
source_filename = "apkg"
@"apkg.init$guard" = global ptr null
define void @apkg.init() {
_llgo_0:
%0 = load i1, ptr @"apkg.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"apkg.init$guard", align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define double @apkg.Max(double %0, double %1) {
_llgo_0:
%2 = fcmp ogt double %0, %1
br i1 %2, label %_llgo_1, label %_llgo_2
_llgo_1: ; preds = %_llgo_0
ret double %0
_llgo_2: ; preds = %_llgo_0
ret double %1
}

View File

@@ -1,22 +1,22 @@
; ModuleID = 'main'
source_filename = "main"
@"init$guard" = global ptr null
@"main.init$guard" = global ptr null
define void @init() {
define void @main.init() {
_llgo_0:
%0 = load i1, ptr @"init$guard", align 1
%0 = load i1, ptr @"main.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"init$guard", align 1
store i1 true, ptr @"main.init$guard", align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define i64 @max(i64 %0, i64 %1) {
define i64 @main.max(i64 %0, i64 %1) {
_llgo_0:
%2 = icmp sgt i64 %0, %1
br i1 %2, label %_llgo_1, label %_llgo_2
@@ -30,7 +30,7 @@ _llgo_2: ; preds = %_llgo_0
define void @main() {
_llgo_0:
call void @init()
%0 = call i64 @max(i64 1, i64 2)
call void @main.init()
%0 = call i64 @main.max(i64 1, i64 2)
ret void
}

View File

@@ -0,0 +1,10 @@
package main
import "github.com/goplus/llgo/cl/internal/stdio"
var hello = [...]int8{'H', 'e', 'l', 'l', 'o', '\n', 0}
func main() {
_ = stdio.Max(2, 100)
stdio.Printf(&hello[0])
}

View File

@@ -0,0 +1,40 @@
; ModuleID = 'main'
source_filename = "main"
@"main.init$guard" = global ptr null
@main.hello = global ptr null
define void @main.init() {
_llgo_0:
%0 = load i1, ptr @"main.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"main.init$guard", align 1
call void @"github.com/goplus/llgo/cl/internal/stdio.init"()
store i8 72, ptr @main.hello, align 1
store i8 101, ptr getelementptr inbounds (i8, ptr @main.hello, i64 1), align 1
store i8 108, ptr getelementptr inbounds (i8, ptr @main.hello, i64 2), align 1
store i8 108, ptr getelementptr inbounds (i8, ptr @main.hello, i64 3), align 1
store i8 111, ptr getelementptr inbounds (i8, ptr @main.hello, i64 4), align 1
store i8 10, ptr getelementptr inbounds (i8, ptr @main.hello, i64 5), align 1
store i8 0, ptr getelementptr inbounds (i8, ptr @main.hello, i64 6), align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define void @main() {
_llgo_0:
call void @main.init()
%0 = call i64 @"github.com/goplus/llgo/cl/internal/stdio.Max"(i64 2, i64 100)
call void (ptr, ...) @printf(ptr @main.hello)
ret void
}
declare void @"github.com/goplus/llgo/cl/internal/stdio.init"()
declare i64 @"github.com/goplus/llgo/cl/internal/stdio.Max"(i64, i64)
declare void @printf(ptr, ...)

View File

@@ -2,7 +2,7 @@ package main
import _ "unsafe"
//go:linkname printf _printf
//go:linkname printf printf
func printf(format *int8, __llgo_va_list ...any)
var hello = [...]int8{'H', 'e', 'l', 'l', 'o', '\n', 0}

View File

@@ -1,23 +1,23 @@
; ModuleID = 'main'
source_filename = "main"
@"init$guard" = global ptr null
@hello = global ptr null
@"main.init$guard" = global ptr null
@main.hello = global ptr null
define void @init() {
define void @main.init() {
_llgo_0:
%0 = load i1, ptr @"init$guard", align 1
%0 = load i1, ptr @"main.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"init$guard", align 1
store i8 72, ptr @hello, align 1
store i8 101, ptr getelementptr inbounds (i8, ptr @hello, i64 1), align 1
store i8 108, ptr getelementptr inbounds (i8, ptr @hello, i64 2), align 1
store i8 108, ptr getelementptr inbounds (i8, ptr @hello, i64 3), align 1
store i8 111, ptr getelementptr inbounds (i8, ptr @hello, i64 4), align 1
store i8 10, ptr getelementptr inbounds (i8, ptr @hello, i64 5), align 1
store i8 0, ptr getelementptr inbounds (i8, ptr @hello, i64 6), align 1
store i1 true, ptr @"main.init$guard", align 1
store i8 72, ptr @main.hello, align 1
store i8 101, ptr getelementptr inbounds (i8, ptr @main.hello, i64 1), align 1
store i8 108, ptr getelementptr inbounds (i8, ptr @main.hello, i64 2), align 1
store i8 108, ptr getelementptr inbounds (i8, ptr @main.hello, i64 3), align 1
store i8 111, ptr getelementptr inbounds (i8, ptr @main.hello, i64 4), align 1
store i8 10, ptr getelementptr inbounds (i8, ptr @main.hello, i64 5), align 1
store i8 0, ptr getelementptr inbounds (i8, ptr @main.hello, i64 6), align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
@@ -28,7 +28,7 @@ declare void @printf(ptr, ...)
define void @main() {
_llgo_0:
call void @init()
call void (ptr, ...) @printf(ptr @hello)
call void @main.init()
call void (ptr, ...) @printf(ptr @main.hello)
ret void
}

View File

@@ -1,17 +1,17 @@
; ModuleID = 'main'
source_filename = "main"
@"init$guard" = global ptr null
@a = global ptr null
@"main.init$guard" = global ptr null
@main.a = global ptr null
define void @init() {
define void @main.init() {
_llgo_0:
%0 = load i1, ptr @"init$guard", align 1
%0 = load i1, ptr @"main.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"init$guard", align 1
store i64 100, ptr @a, align 4
store i1 true, ptr @"main.init$guard", align 1
store i64 100, ptr @main.a, align 4
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
@@ -20,10 +20,10 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0
define void @main() {
_llgo_0:
call void @init()
%0 = load i64, ptr @a, align 4
call void @main.init()
%0 = load i64, ptr @main.a, align 4
%1 = add i64 %0, 1
store i64 %1, ptr @a, align 4
%2 = load i64, ptr @a, align 4
store i64 %1, ptr @main.a, align 4
%2 = load i64, ptr @main.a, align 4
ret void
}

View File

@@ -18,6 +18,9 @@ package cl
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"log"
"os"
"sort"
@@ -57,14 +60,15 @@ const (
)
func funcKind(vfn ssa.Value) int {
if fn, ok := vfn.(*ssa.Function); ok {
n := len(fn.Params)
if fn, ok := vfn.(*ssa.Function); ok && fn.Signature.Recv() == nil {
params := fn.Signature.Params()
n := params.Len()
if n == 0 {
if fn.Name() == "init" && fn.Pkg.Pkg.Path() == "unsafe" {
return fnUnsafeInit
}
} else {
last := fn.Params[n-1]
last := params.At(n - 1)
if last.Name() == llssa.NameValist {
return fnHasVArg
}
@@ -73,12 +77,10 @@ func funcKind(vfn ssa.Value) int {
return fnNormal
}
func isMainFunc(fn *ssa.Function) bool {
return fn.Name() == "main" && fn.Pkg.Pkg.Path() == "main"
}
// -----------------------------------------------------------------------------
type none = struct{}
type instrAndValue interface {
ssa.Instruction
ssa.Value
@@ -88,7 +90,11 @@ type context struct {
prog llssa.Program
pkg llssa.Package
fn llssa.Function
fset *token.FileSet
goTyps *types.Package
goPkg *ssa.Package
link map[string]string // pkgPath.nameInPkg => linkname
loaded map[*types.Package]none // loaded packages
bvals map[ssa.Value]llssa.Expr // block values
inits []func()
}
@@ -99,7 +105,8 @@ func (p *context) compileType(pkg llssa.Package, member *ssa.Type) {
// Global variable.
func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
name, typ := gbl.Name(), gbl.Type()
typ := gbl.Type()
name := fullName(gbl.Pkg.Pkg, gbl.Name())
if debugInstr {
log.Println("==> NewVar", name, typ)
}
@@ -108,7 +115,10 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
}
func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) {
name := f.Name()
name := p.funcName(f.Pkg.Pkg, f)
if name == "unsafe.init" {
return
}
if debugInstr {
log.Println("==> NewFunc", name)
}
@@ -130,9 +140,8 @@ func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) {
}
fn.MakeBlocks(nblk)
b := fn.NewBuilder()
isMain := isMainFunc(f)
for i, block := range f.Blocks {
p.compileBlock(b, block, isMain && i == 0)
p.compileBlock(b, block, i == 0 && name == "main")
}
})
}
@@ -142,7 +151,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, doInit bo
b.SetBlock(ret)
p.bvals = make(map[ssa.Value]llssa.Expr)
if doInit {
fn := p.pkg.FuncOf("init")
fn := p.pkg.FuncOf("main.init")
b.Call(fn.Expr)
}
for _, instr := range block.Instrs {
@@ -238,16 +247,10 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr {
}
}
case *ssa.Function:
if v.Pkg != p.goPkg {
panic("todo")
}
fn := p.pkg.FuncOf(v.Name())
fn := p.funcOf(v)
return fn.Expr
case *ssa.Global:
if v.Pkg != p.goPkg {
panic("todo")
}
g := p.pkg.VarOf(v.Name())
g := p.varOf(v)
return g.Expr
case *ssa.Const:
t := v.Type()
@@ -281,11 +284,8 @@ func (p *context) compileValues(b llssa.Builder, vals []ssa.Value, hasVArg int)
// -----------------------------------------------------------------------------
type Config struct {
}
// NewPackage compiles a Go package to LLVM IR package.
func NewPackage(prog llssa.Program, pkg *ssa.Package, conf *Config) (ret llssa.Package, err error) {
func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) {
type namedMember struct {
name string
val ssa.Member
@@ -305,13 +305,19 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, conf *Config) (ret llssa.P
})
pkgTypes := pkg.Pkg
ret = prog.NewPackage(pkgTypes.Name(), pkgTypes.Path())
pkgName, pkgPath := pkgTypes.Name(), pkgTypes.Path()
ret = prog.NewPackage(pkgName, pkgPath)
ctx := &context{
prog: prog,
pkg: ret,
fset: pkg.Prog.Fset,
goTyps: pkgTypes,
goPkg: pkg,
link: make(map[string]string),
loaded: make(map[*types.Package]none),
}
ctx.initFiles(pkgPath, files)
for _, m := range members {
member := m.val
switch member := member.(type) {

View File

@@ -18,7 +18,6 @@ package cl
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
@@ -28,9 +27,11 @@ import (
"strings"
"testing"
llssa "github.com/goplus/llgo/ssa"
"github.com/goplus/gogen/packages"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
llssa "github.com/goplus/llgo/ssa"
)
func TestFromTestdata(t *testing.T) {
@@ -88,14 +89,15 @@ func testCompileEx(t *testing.T, src any, fname, expected string) {
files := []*ast.File{f}
name := f.Name.Name
pkg := types.NewPackage(name, name)
imp := packages.NewImporter(fset)
foo, _, err := ssautil.BuildPackage(
&types.Config{Importer: importer.Default()}, fset, pkg, files, ssa.SanityCheckFunctions)
&types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions)
if err != nil {
t.Fatal("BuildPackage failed:", err)
}
foo.WriteTo(os.Stderr)
prog := llssa.NewProgram(nil)
ret, err := NewPackage(prog, foo, nil)
ret, err := NewPackage(prog, foo, files)
if err != nil {
t.Fatal("cl.NewPackage failed:", err)
}
@@ -116,16 +118,16 @@ var a int
`, `; ModuleID = 'foo'
source_filename = "foo"
@"init$guard" = global ptr null
@a = global ptr null
@"foo.init$guard" = global ptr null
@foo.a = global ptr null
define void @init() {
define void @foo.init() {
_llgo_0:
%0 = load i1, ptr @"init$guard", align 1
%0 = load i1, ptr @"foo.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"init$guard", align 1
store i1 true, ptr @"foo.init$guard", align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
@@ -143,22 +145,22 @@ func fn(a int, b float64) int {
`, `; ModuleID = 'foo'
source_filename = "foo"
@"init$guard" = global ptr null
@"foo.init$guard" = global ptr null
define void @init() {
define void @foo.init() {
_llgo_0:
%0 = load i1, ptr @"init$guard", align 1
%0 = load i1, ptr @"foo.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"init$guard", align 1
store i1 true, ptr @"foo.init$guard", align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define i64 @fn(i64 %0, double %1) {
define i64 @foo.fn(i64 %0, double %1) {
_llgo_0:
ret i64 1
}

160
cl/import.go Normal file
View File

@@ -0,0 +1,160 @@
/*
* 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 cl
import (
"bytes"
"go/ast"
"go/token"
"go/types"
"os"
"strings"
llssa "github.com/goplus/llgo/ssa"
"golang.org/x/tools/go/ssa"
)
type contentLines = [][]byte
type contentMap = map[string]contentLines
func contentOf(m contentMap, file string) (lines contentLines, err error) {
if v, ok := m[file]; ok {
return v, nil
}
b, err := os.ReadFile(file)
if err == nil {
lines = bytes.Split(b, []byte{'\n'})
m[file] = lines
}
return
}
func (p *context) importPkg(pkg *types.Package) {
scope := pkg.Scope()
if scope.Lookup("LLGoPackage") == nil {
return
}
fset := p.fset
names := scope.Names()
contents := make(contentMap)
pkgPath := pkg.Path()
for _, name := range names {
if token.IsExported(name) {
obj := scope.Lookup(name)
if obj, ok := obj.(*types.Func); ok {
if pos := obj.Pos(); pos != token.NoPos {
f := fset.File(pos)
if fp := f.Position(pos); fp.Line > 2 {
lines, err := contentOf(contents, fp.Filename)
if err != nil {
panic(err)
}
if i := fp.Line - 2; i < len(lines) {
line := string(lines[i])
p.initLinkname(pkgPath, line)
}
}
}
}
}
}
}
func (p *context) initFiles(pkgPath string, files []*ast.File) {
for _, file := range files {
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
if decl.Recv == nil {
if doc := decl.Doc; doc != nil {
if n := len(doc.List); n > 0 {
line := doc.List[n-1].Text
p.initLinkname(pkgPath, line)
}
}
}
}
}
}
}
func (p *context) initLinkname(pkgPath, line string) {
const (
linkname = "//go:linkname "
)
if strings.HasPrefix(line, linkname) {
text := strings.TrimSpace(line[len(linkname):])
if idx := strings.IndexByte(text, ' '); idx > 0 {
name := pkgPath + "." + text[:idx]
link := strings.TrimLeft(text[idx+1:], " ")
p.link[name] = link
}
}
}
func fullName(pkg *types.Package, name string) string {
pkgPath := pkg.Name()
if pkgPath != "main" {
pkgPath = pkg.Path()
}
return pkgPath + "." + name
}
func funcName(pkg *types.Package, fn *ssa.Function) string {
ret := fullName(pkg, fn.Name())
if ret == "main.main" {
ret = "main"
}
return ret
}
func (p *context) funcName(pkg *types.Package, fn *ssa.Function) string {
name := funcName(pkg, fn)
if v, ok := p.link[name]; ok {
return v
}
return name
}
func (p *context) funcOf(fn *ssa.Function) llssa.Function {
pkgTypes := p.ensureLoaded(fn.Pkg.Pkg)
pkg := p.pkg
name := p.funcName(pkgTypes, fn)
if ret := pkg.FuncOf(name); ret != nil {
return ret
}
return pkg.NewFunc(name, fn.Signature)
}
func (p *context) varOf(v *ssa.Global) llssa.Global {
pkgTypes := p.ensureLoaded(v.Pkg.Pkg)
pkg := p.pkg
name := fullName(pkgTypes, v.Name())
if ret := pkg.VarOf(name); ret != nil {
return ret
}
return pkg.NewVar(name, v.Type())
}
func (p *context) ensureLoaded(pkgTypes *types.Package) *types.Package {
if p.goTyps != pkgTypes {
if _, ok := p.loaded[pkgTypes]; !ok {
p.loaded[pkgTypes] = none{}
p.importPkg(pkgTypes)
}
}
return pkgTypes
}

View File

@@ -0,0 +1,17 @@
package stdio
import _ "unsafe"
const (
LLGoPackage = true
)
//go:linkname Printf printf
func Printf(format *int8, __llgo_va_list ...any)
func Max(a, b int) int {
if a > b {
return a
}
return b
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
* 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.
@@ -14,93 +14,27 @@
* limitations under the License.
*/
// Package build implements the llgo build command.
// Package build implements the "llgo build" command.
package build
import (
"fmt"
"log"
"os"
"path/filepath"
"reflect"
"github.com/goplus/gop"
"github.com/goplus/gop/x/gopprojs"
"github.com/goplus/llgo"
"github.com/goplus/llgo/cmd/internal/base"
"github.com/goplus/llgo/x/gocmd"
"github.com/goplus/llgo/internal/build"
)
// llgo build
var Cmd = &base.Command{
UsageLine: "llgo build [flags] [packages]",
Short: "Build Go files",
UsageLine: "llgo build [-o output] [build flags] [packages]",
Short: "Compile packages and dependencies",
}
var (
flagOutput = flag.String("o", "", "build output file")
_ = flag.Bool("v", false, "print verbose information")
flag = &Cmd.Flag
)
func init() {
Cmd.Run = runCmd
}
func runCmd(cmd *base.Command, args []string) {
err := flag.Parse(args)
if err != nil {
log.Panicln("parse input arguments failed:", err)
}
args = flag.Args()
if len(args) == 0 {
args = []string{"."}
}
proj, args, err := gopprojs.ParseOne(args...)
if err != nil {
log.Panicln(err)
}
if len(args) != 0 {
log.Panicln("too many arguments:", args)
}
conf := &llgo.Config{}
confCmd := &gocmd.BuildConfig{}
if *flagOutput != "" {
output, err := filepath.Abs(*flagOutput)
if err != nil {
log.Panicln(err)
}
confCmd.Output = output
}
build(proj, conf, confCmd)
build.Do(args, &build.Config{
Mode: build.ModeBuild,
AppExt: build.DefaultAppExt(),
})
}
func build(proj gopprojs.Proj, conf *llgo.Config, build *gocmd.BuildConfig) {
var obj string
var err error
switch v := proj.(type) {
case *gopprojs.DirProj:
obj = v.Dir
err = llgo.BuildDir(obj, conf, build)
case *gopprojs.PkgPathProj:
obj = v.Path
err = llgo.BuildPkgPath("", obj, conf, build)
case *gopprojs.FilesProj:
err = llgo.BuildFiles(v.Files, conf, build)
default:
log.Panicln("`llgo build` doesn't support", reflect.TypeOf(v))
}
if gop.NotFound(err) {
fmt.Fprintf(os.Stderr, "llgo build %v: not found\n", obj)
} else if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
return
}
os.Exit(1)
}
// -----------------------------------------------------------------------------

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
* 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.
@@ -14,11 +14,25 @@
* limitations under the License.
*/
package llgo
// Package install implements the "llgo install" command.
package install
// -----------------------------------------------------------------------------
import (
"github.com/goplus/llgo/cmd/internal/base"
"github.com/goplus/llgo/internal/build"
)
type Config struct {
// llgo install
var Cmd = &base.Command{
UsageLine: "llgo install [build flags] [packages]",
Short: "Compile and install packages and dependencies",
}
// -----------------------------------------------------------------------------
func init() {
Cmd.Run = runCmd
}
func runCmd(cmd *base.Command, args []string) {
conf := build.NewDefaultConf(build.ModeInstall)
build.Do(args, conf)
}

85
cmd/internal/run/run.go Normal file
View File

@@ -0,0 +1,85 @@
/*
* 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 run implements the "llgo run" command.
package run
import (
"errors"
"path/filepath"
"strings"
"github.com/goplus/llgo/cmd/internal/base"
"github.com/goplus/llgo/internal/build"
)
var (
errNoProj = errors.New("llgo: no go files listed")
)
// llgo run
var Cmd = &base.Command{
UsageLine: "llgo run [build flags] package [arguments...]",
Short: "Compile and run Go program",
}
func init() {
Cmd.Run = runCmd
}
func runCmd(cmd *base.Command, args []string) {
args, runArgs, err := parseRunArgs(args)
check(err)
conf := build.NewDefaultConf(build.ModeRun)
conf.RunArgs = runArgs
build.Do(args, conf)
}
func parseRunArgs(args []string) ([]string, []string, error) {
n := parseArgs(args)
if n < 0 {
return nil, nil, errNoProj
}
arg := args[n]
if isGoFile(arg) {
n++
for n < len(args) && isGoFile(args[n]) {
n++
}
return args[:n], args[n:], nil
}
return args[:n+1], args[n+1:], nil
}
func parseArgs(args []string) int {
for i, arg := range args {
if !strings.HasPrefix(arg, "-") {
return i
}
}
return -1
}
func isGoFile(fname string) bool {
return filepath.Ext(fname) == ".go"
}
func check(err error) {
if err != nil {
panic(err)
}
}

View File

@@ -27,6 +27,8 @@ import (
"github.com/goplus/llgo/cmd/internal/base"
"github.com/goplus/llgo/cmd/internal/build"
"github.com/goplus/llgo/cmd/internal/help"
"github.com/goplus/llgo/cmd/internal/install"
"github.com/goplus/llgo/cmd/internal/run"
)
func mainUsage() {
@@ -38,6 +40,8 @@ func init() {
flag.Usage = mainUsage
base.Llgo.Commands = []*base.Command{
build.Cmd,
install.Cmd,
run.Cmd,
}
}

10
go.mod
View File

@@ -4,14 +4,14 @@ go 1.18
require (
github.com/aykevl/go-wasm v0.0.1
github.com/goplus/gop v1.2.6
github.com/goplus/llvm v0.7.1-0.20240420180312-6230a4ea7a47
github.com/goplus/gogen v1.15.2
github.com/goplus/llvm v0.7.1
github.com/goplus/mod v0.13.10
github.com/qiniu/x v1.13.10
golang.org/x/tools v0.19.0
golang.org/x/tools v0.20.0
)
require (
github.com/goplus/gogen v1.15.2 // indirect
github.com/goplus/mod v0.13.10 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
)

15
go.sum
View File

@@ -2,12 +2,8 @@ github.com/aykevl/go-wasm v0.0.1 h1:lPxy8l48P39W7I0tLrtCrLfZBOUq9IWZ7odGdyJP2AM=
github.com/aykevl/go-wasm v0.0.1/go.mod h1:b4nggwg3lEkNKOU4wzhtLKz2q2sLxSHFnc98aGt6z/Y=
github.com/goplus/gogen v1.15.2 h1:Q6XaSx/Zi5tWnjfAziYsQI6Jv6MgODRpFtOYqNkiiqM=
github.com/goplus/gogen v1.15.2/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
github.com/goplus/gop v1.2.6 h1:kog3c5Js+8EopqmI4+CwueXsqibnBwYVt5q5N7juRVY=
github.com/goplus/gop v1.2.6/go.mod h1:uREWbR1MrFaviZ4Mbx4ZCcAYDoqzO0iv1Qo6Np0Xx4E=
github.com/goplus/llvm v0.7.1-0.20240418160956-6233231cbcc9 h1:E/NBN5tDh6COcJmygdBb9RAJhE4uIHfT51VBlP3tglU=
github.com/goplus/llvm v0.7.1-0.20240418160956-6233231cbcc9/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/llvm v0.7.1-0.20240420180312-6230a4ea7a47 h1:B3nWTLOQh4+Yqt6NryE/cVQdo/+NLiT8AtD4YaeKScg=
github.com/goplus/llvm v0.7.1-0.20240420180312-6230a4ea7a47/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/llvm v0.7.1 h1:B12Fr/wc3pAsq5PLuac9u9IuKpLRuCufdVAeGDP/MRw=
github.com/goplus/llvm v0.7.1/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE=
github.com/goplus/mod v0.13.10/go.mod h1:HDuPZgpWiaTp3PUolFgsiX+Q77cbUWB/mikVHfYND3c=
github.com/qiniu/x v1.13.10 h1:J4Z3XugYzAq85SlyAfqlKVrbf05glMbAOh+QncsDQpE=
@@ -31,7 +27,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -56,6 +53,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

216
internal/build/build.go Normal file
View File

@@ -0,0 +1,216 @@
/*
* 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 build
import (
"fmt"
"go/token"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/ssa"
"github.com/goplus/llgo/cl"
"github.com/goplus/llgo/x/clang"
llssa "github.com/goplus/llgo/ssa"
)
type Mode int
const (
ModeBuild Mode = iota
ModeInstall
ModeRun
)
func needLLFile(mode Mode) bool {
return mode != ModeBuild
}
type Config struct {
BinPath string
AppExt string // ".exe" on Windows, empty on Unix
RunArgs []string
Mode Mode
}
func NewDefaultConf(mode Mode) *Config {
bin := os.Getenv("GOBIN")
if bin == "" {
bin = filepath.Join(runtime.GOROOT(), "bin")
}
conf := &Config{
BinPath: bin,
Mode: mode,
AppExt: DefaultAppExt(),
}
return conf
}
func DefaultAppExt() string {
if runtime.GOOS == "windows" {
return ".exe"
}
return ""
}
// -----------------------------------------------------------------------------
const (
loadFiles = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles
loadImports = loadFiles | packages.NeedImports
loadTypes = loadImports | packages.NeedTypes | packages.NeedTypesSizes
loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo
)
func Do(args []string, conf *Config) {
flags, patterns, verbose := parseArgs(args)
cfg := &packages.Config{
Mode: loadSyntax | packages.NeedDeps | packages.NeedExportFile,
BuildFlags: flags,
}
if patterns == nil {
patterns = []string{"."}
}
initial, err := packages.Load(cfg, patterns...)
check(err)
// Create SSA-form program representation.
ssaProg, pkgs, errPkgs := allPkgs(initial, ssa.SanityCheckFunctions)
ssaProg.Build()
for _, errPkg := range errPkgs {
log.Println("cannot build SSA for package", errPkg)
}
llssa.Initialize(llssa.InitAll)
if verbose {
llssa.SetDebug(llssa.DbgFlagAll)
cl.SetDebug(cl.DbgFlagAll)
}
prog := llssa.NewProgram(nil)
mode := conf.Mode
for _, pkg := range pkgs {
buildPkg(prog, pkg, mode)
}
if mode != ModeBuild || len(initial) == 1 {
for _, pkg := range initial {
if pkg.Name == "main" {
linkMainPkg(pkg, conf, mode)
}
}
}
}
func linkMainPkg(pkg *packages.Package, conf *Config, mode Mode) {
pkgPath := pkg.PkgPath
name := path.Base(pkgPath)
app := filepath.Join(conf.BinPath, name+conf.AppExt)
args := make([]string, 2, len(pkg.Imports)+3)
args[0] = "-o"
args[1] = app
packages.Visit([]*packages.Package{pkg}, nil, func(p *packages.Package) {
if p.PkgPath != "unsafe" { // TODO(xsw): remove this special case
args = append(args, p.ExportFile+".ll")
}
})
// TODO(xsw): show work
// fmt.Fprintln(os.Stderr, "clang", args)
fmt.Fprintln(os.Stderr, "#", pkgPath)
err := clang.New("").Exec(args...)
check(err)
if mode == ModeRun {
cmd := exec.Command(app, conf.RunArgs...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
}
}
func buildPkg(prog llssa.Program, aPkg aPackage, mode Mode) {
pkg := aPkg.Package
pkgPath := pkg.PkgPath
if mode != ModeRun {
fmt.Fprintln(os.Stderr, pkgPath)
}
if pkgPath == "unsafe" { // TODO(xsw): remove this special case
return
}
ret, err := cl.NewPackage(prog, aPkg.SSA, pkg.Syntax)
check(err)
if needLLFile(mode) {
file := pkg.ExportFile + ".ll"
os.WriteFile(file, []byte(ret.String()), 0644)
}
}
type aPackage struct {
*packages.Package
SSA *ssa.Package
}
func allPkgs(initial []*packages.Package, mode ssa.BuilderMode) (prog *ssa.Program, all []aPackage, errs []*packages.Package) {
var fset *token.FileSet
if len(initial) > 0 {
fset = initial[0].Fset
}
prog = ssa.NewProgram(fset, mode)
packages.Visit(initial, nil, func(p *packages.Package) {
if p.Types != nil && !p.IllTyped {
ssaPkg := prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true)
all = append(all, aPackage{p, ssaPkg})
} else {
errs = append(errs, p)
}
})
return
}
func parseArgs(args []string) (flags, patterns []string, verbose bool) {
for i, arg := range args {
if !strings.HasPrefix(arg, "-") {
flags, patterns = args[:i], args[i:]
return
}
if arg == "-v" {
verbose = true
}
}
flags = args
return
}
func check(err error) {
if err != nil {
panic(err)
}
}
// -----------------------------------------------------------------------------

View File

@@ -18,13 +18,14 @@ package llgen
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"github.com/goplus/gogen/packages"
"github.com/goplus/llgo/cl"
"github.com/goplus/llgo/internal/mod"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
@@ -37,28 +38,38 @@ func Init() {
cl.SetDebug(cl.DbgFlagAll)
}
func Do(inFile, outFile string) {
ret := Gen(inFile, nil)
func PkgPath(dir string) string {
_, pkgPath, err := mod.Load(dir)
check(err)
return pkgPath
}
func Do(pkgPath, inFile, outFile string) {
ret := Gen(pkgPath, inFile, nil)
err := os.WriteFile(outFile, []byte(ret), 0644)
check(err)
}
func Gen(inFile string, src any) string {
func Gen(pkgPath, inFile string, src any) string {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, inFile, src, parser.ParseComments)
check(err)
files := []*ast.File{f}
name := f.Name.Name
pkg := types.NewPackage(name, name)
if pkgPath == "" {
pkgPath = name
}
pkg := types.NewPackage(pkgPath, name)
imp := packages.NewImporter(fset)
ssaPkg, _, err := ssautil.BuildPackage(
&types.Config{Importer: importer.Default()}, fset, pkg, files, ssa.SanityCheckFunctions)
&types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions)
check(err)
ssaPkg.WriteTo(os.Stderr)
prog := llssa.NewProgram(nil)
ret, err := cl.NewPackage(prog, ssaPkg, nil)
ret, err := cl.NewPackage(prog, ssaPkg, files)
check(err)
return ret.String()

48
internal/mod/mod.go Normal file
View File

@@ -0,0 +1,48 @@
/*
* 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 mod
import (
"path"
"path/filepath"
"github.com/goplus/mod"
"github.com/goplus/mod/gopmod"
)
// Module represents a Go module.
type Module = gopmod.Module
// Load loads a Go module from a directory.
func Load(dir string) (ret *Module, pkgPath string, err error) {
if dir, err = filepath.Abs(dir); err != nil {
return
}
_, gomod, err := mod.FindGoMod(dir)
if err != nil {
return
}
if ret, err = gopmod.LoadFrom(gomod, ""); err != nil {
return
}
relPath, err := filepath.Rel(ret.Root(), dir)
if err != nil {
return
}
pkgPath = path.Join(ret.Path(), filepath.ToSlash(relPath))
return
}

110
internal/projs/proj.go Normal file
View File

@@ -0,0 +1,110 @@
/*
* 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 projs
import (
"errors"
"path/filepath"
"syscall"
)
// -----------------------------------------------------------------------------
type Proj = interface {
projObj()
}
type FilesProj struct {
Files []string
}
type PkgPathProj struct {
Path string
}
type DirProj struct {
Dir string
}
func (p *FilesProj) projObj() {}
func (p *PkgPathProj) projObj() {}
func (p *DirProj) projObj() {}
// -----------------------------------------------------------------------------
func ParseOne(args ...string) (proj Proj, next []string, err error) {
if len(args) == 0 {
return nil, nil, syscall.ENOENT
}
arg := args[0]
if isFile(arg) {
n := 1
for n < len(args) && isFile(args[n]) {
n++
}
return &FilesProj{Files: args[:n]}, args[n:], nil
}
if isLocal(arg) {
return &DirProj{Dir: arg}, args[1:], nil
}
return &PkgPathProj{Path: arg}, args[1:], nil
}
func isFile(fname string) bool {
n := len(filepath.Ext(fname))
return n > 1
}
func isLocal(ns string) bool {
if len(ns) > 0 {
switch c := ns[0]; c {
case '/', '\\', '.':
return true
default:
return len(ns) >= 2 && ns[1] == ':' && ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z')
}
}
return false
}
// -----------------------------------------------------------------------------
func ParseAll(args ...string) (projs []Proj, err error) {
var hasFiles, hasNotFiles bool
for {
proj, next, e := ParseOne(args...)
if e != nil {
if hasFiles && hasNotFiles {
return nil, ErrMixedFilesProj
}
return
}
if _, ok := proj.(*FilesProj); ok {
hasFiles = true
} else {
hasNotFiles = true
}
projs = append(projs, proj)
args = next
}
}
var (
ErrMixedFilesProj = errors.New("mixed files project")
)
// -----------------------------------------------------------------------------

View File

@@ -230,7 +230,7 @@ func (b Builder) BinOp(op token.Token, x, y Expr) Expr {
return Expr{llvm.CreateICmp(b.impl, pred, x.impl, y.impl), tret}
case vkFloat:
pred := floatPredOpToLLVM[op-predOpBase]
return Expr{llvm.ConstFCmp(pred, x.impl, y.impl), tret}
return Expr{llvm.CreateFCmp(b.impl, pred, x.impl, y.impl), tret}
case vkString, vkComplex, vkBool:
panic("todo")
}

View File

@@ -87,6 +87,9 @@ func TestNamedStruct(t *testing.T) {
prog := NewProgram(nil)
pkg := prog.NewPackage("bar", "foo/bar")
pkg.NewVar("a", empty)
if pkg.VarOf("a") == nil {
t.Fatal("VarOf failed")
}
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"
@@ -102,6 +105,12 @@ func TestDeclFunc(t *testing.T) {
params := types.NewTuple(types.NewVar(0, nil, "a", types.Typ[types.Int]))
sig := types.NewSignatureType(nil, nil, nil, params, nil, false)
pkg.NewFunc("fn", sig)
if pkg.FuncOf("fn") == nil {
t.Fatal("FuncOf failed")
}
if prog.retType(sig) != prog.Void() {
t.Fatal("retType failed")
}
assertPkg(t, pkg, `; ModuleID = 'foo/bar'
source_filename = "foo/bar"

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
* 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.
@@ -14,24 +14,33 @@
* limitations under the License.
*/
package llgo
package clang
import (
"github.com/goplus/llgo/x/gocmd"
"os"
"os/exec"
)
// -----------------------------------------------------------------------------
func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
panic("todo")
// Cmd represents a nm command.
type Cmd struct {
app string
}
func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) {
panic("todo")
// New creates a new nm command.
func New(app string) *Cmd {
if app == "" {
app = "clang"
}
return &Cmd{app}
}
func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) {
panic("todo")
func (p *Cmd) Exec(args ...string) error {
cmd := exec.Command(p.app, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// -----------------------------------------------------------------------------

View File

@@ -1,40 +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 llexportdata
import (
"go/token"
"go/types"
"io"
)
// Read reads export data from in, decodes it, and returns type information for the package.
//
// The package path (effectively its linker symbol prefix) is specified by path, since unlike
// the package name, this information may not be recorded in the export data.
//
// File position information is added to fset.
//
// Read may inspect and add to the imports map to ensure that references within the export data
// to other packages are consistent. The caller must ensure that imports[path] does not exist,
// or exists but is incomplete (see types.Package.Complete), and Read inserts the resulting package
// into this map entry.
//
// On return, the state of the reader is undefined.
func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
panic("todo")
}