Merge pull request #1057 from cpunion/cross-compiling

Cross compiling
This commit is contained in:
xushiwei
2025-04-09 14:26:09 +08:00
committed by GitHub
39 changed files with 1014 additions and 170 deletions

View File

@@ -15,7 +15,7 @@ runs:
run: |
brew update
brew install llvm@${{inputs.llvm-version}} lld@${{inputs.llvm-version}} bdw-gc openssl libffi libuv
brew link --overwrite lld@${{inputs.llvm-version}} libffi
brew link --overwrite llvm@${{inputs.llvm-version}} lld@${{inputs.llvm-version}} libffi
echo "$(brew --prefix llvm@${{inputs.llvm-version}})/bin" >> $GITHUB_PATH
# Install optional deps for demos.
@@ -35,7 +35,7 @@ runs:
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install -y llvm-${{inputs.llvm-version}}-dev clang-${{inputs.llvm-version}} libclang-${{inputs.llvm-version}}-dev lld-${{inputs.llvm-version}} pkg-config libgc-dev libssl-dev zlib1g-dev libffi-dev libcjson-dev libunwind-dev libuv1-dev
echo "/usr/lib/llvm-${{inputs.llvm-version}}/bin" >> $GITHUB_PATH
echo "PATH=/usr/lib/llvm-${{inputs.llvm-version}}/bin:$PATH" >> $GITHUB_ENV
# Install optional deps for demos.
#

View File

@@ -2,9 +2,9 @@ name: Docs
on:
push:
branches: [ "**" ]
branches: ["**"]
pull_request:
branches: [ "**" ]
branches: ["**"]
jobs:
doc_verify:
@@ -15,7 +15,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
node-version: "20"
- name: Install embedme
run: npm install -g embedme
@@ -43,7 +43,7 @@ jobs:
- name: Set up Go
uses: ./.github/actions/setup-go
with:
go-version: '1.23.6'
go-version: "1.24.2"
- name: Install dependencies on macOS
if: startsWith(matrix.os, 'macos')
@@ -79,7 +79,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
go-version: "1.23"
- name: Install dependencies on macOS
if: startsWith(matrix.os, 'macos')
@@ -130,7 +130,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
go-version: "1.23"
- name: Install dependencies on macOS
if: startsWith(matrix.os, 'macos')
@@ -145,6 +145,7 @@ jobs:
set -e
set -x
source doc/_readme/scripts/install_ubuntu.sh
echo "PATH=/usr/lib/llvm-19/bin:$PATH" >> $GITHUB_ENV
- name: Install llgo with tools
run: |

View File

@@ -2,9 +2,9 @@ name: Format Check
on:
push:
branches: [ "**" ]
branches: ["**"]
pull_request:
branches: [ "**" ]
branches: ["**"]
jobs:
fmt:
@@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: ./.github/actions/setup-go
with:
go-version: '1.24.0'
go-version: "1.24.2"
- name: Check formatting
run: |

View File

@@ -35,7 +35,7 @@ jobs:
- name: Set up Go
uses: ./.github/actions/setup-go
with:
go-version: "1.24.0"
go-version: "1.24.2"
- name: Build
run: go build -v ./...

View File

@@ -34,7 +34,7 @@ jobs:
- macos-latest
- ubuntu-24.04
llvm: [19]
go: ['1.21.13', '1.22.12', '1.23.6', '1.24.0']
go: ["1.21.13", "1.22.12", "1.23.6", "1.24.2"]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
@@ -58,7 +58,7 @@ jobs:
- name: Set up Go for build
uses: ./.github/actions/setup-go
with:
go-version: "1.24.0"
go-version: "1.24.2"
- name: Install
run: |
@@ -105,7 +105,7 @@ jobs:
- macos-latest
- ubuntu-24.04
llvm: [19]
go: ["1.24.0"]
go: ["1.24.2"]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
@@ -124,7 +124,7 @@ jobs:
- name: Set up Go for build
uses: ./.github/actions/setup-go
with:
go-version: "1.24.0"
go-version: "1.24.2"
- name: Install
run: |
@@ -146,7 +146,7 @@ jobs:
matrix:
os: [ubuntu-24.04, macos-latest]
llvm: [19]
go: ['1.21.13', '1.22.12', '1.23.6', '1.24.0']
go: ["1.21.13", "1.22.12", "1.23.6", "1.24.2"]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
@@ -158,7 +158,7 @@ jobs:
- name: Set up Go 1.23 for building llgo
uses: ./.github/actions/setup-go
with:
go-version: "1.24.0"
go-version: "1.24.2"
- name: Install llgo
run: |
@@ -197,3 +197,50 @@ jobs:
with:
go-version: ${{matrix.go}}
mod-version: "1.24"
cross-compile:
continue-on-error: true
strategy:
matrix:
os: [macos-latest]
llvm: [19]
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v4
- name: Install dependencies
uses: ./.github/actions/setup-deps
with:
llvm-version: ${{matrix.llvm}}
- name: Set up Go for building llgo
uses: ./.github/actions/setup-go
with:
go-version: "1.24.2"
- name: Install wasmtime and wasi-libc
run: |
brew install wasmtime
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/libclang_rt.builtins-wasm32-wasi-25.0.tar.gz
tar -xvf libclang_rt.builtins-wasm32-wasi-25.0.tar.gz
mkdir -p `llvm-config --libdir`/clang/19/lib/wasi/
mv libclang_rt.builtins-wasm32-wasi-25.0/libclang_rt.builtins-wasm32.a `llvm-config --libdir`/clang/19/lib/wasi/
- name: Install llgo
run: |
go install ./...
echo "LLGO_ROOT=$GITHUB_WORKSPACE" >> $GITHUB_ENV
- name: Test Cross Compilation (wasm)
shell: bash
working-directory: _demo
run: |
echo "Testing cross-compilation wasm with Go 1.24.2"
# Compile for wasm architecture
GOOS=wasip1 GOARCH=wasm llgo build -o hello -v ./helloc
# Check file type
file hello.wasm
# Run the wasm binary using llgo_wasm
wasmtime hello.wasm

View File

@@ -361,7 +361,7 @@ Follow these steps to generate the `llgo` command (its usage is the same as the
brew update
brew install llvm@19 lld@19 bdw-gc openssl cjson libffi libuv pkg-config
brew install python@3.12 # optional
brew link --overwrite lld@19 libffi
brew link --overwrite llvm@19 lld@19 libffi
# curl https://raw.githubusercontent.com/goplus/llgo/refs/heads/main/install.sh | bash
./install.sh
```

View File

@@ -2,4 +2,4 @@ module github.com/goplus/llgo/_demo
go 1.20
require github.com/goplus/lib v0.1.0
require github.com/goplus/lib v0.2.0

View File

@@ -1,2 +1,2 @@
github.com/goplus/lib v0.1.0 h1:kqMAC6FmVPfrw0q8E5yF6Y12WC4GOfi2L/MYS4QoxbQ=
github.com/goplus/lib v0.1.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=

15
_demo/helloc/helloc.go Normal file
View File

@@ -0,0 +1,15 @@
package main
import (
"unsafe"
"github.com/goplus/lib/c"
)
func main() {
c.Printf(c.Str("Hello world by c.Printf\n"))
c.Printf(c.Str("%ld\n"), unsafe.Sizeof(int(0)))
c.Printf(c.Str("%ld\n"), unsafe.Sizeof(uintptr(0)))
// var v any = int(0)
// c.Printf(c.Str("%ld\n"), unsafe.Sizeof(v))
}

View File

@@ -2,4 +2,4 @@ module github.com/goplus/llgo/_pydemo
go 1.20
require github.com/goplus/lib v0.1.0
require github.com/goplus/lib v0.2.0

View File

@@ -1,2 +1,2 @@
github.com/goplus/lib v0.1.0 h1:kqMAC6FmVPfrw0q8E5yF6Y12WC4GOfi2L/MYS4QoxbQ=
github.com/goplus/lib v0.1.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=

View File

@@ -2,4 +2,4 @@ module github.com/goplus/llgo/_xtool
go 1.20
require github.com/goplus/lib v0.1.0
require github.com/goplus/lib v0.2.0

View File

@@ -1,2 +1,2 @@
github.com/goplus/lib v0.1.0 h1:kqMAC6FmVPfrw0q8E5yF6Y12WC4GOfi2L/MYS4QoxbQ=
github.com/goplus/lib v0.1.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=

View File

@@ -8,7 +8,7 @@ source_filename = "github.com/goplus/llgo/cl/_testdata/vargs"
@"github.com/goplus/llgo/cl/_testdata/vargs.init$guard" = global i1 false, align 1
@_llgo_int = linkonce global ptr null, align 8
@0 = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1
@1 = private unnamed_addr constant [40 x i8] c"type assertion interface{} -> int failed", align 1
@1 = private unnamed_addr constant [32 x i8] c"type assertion any -> int failed", align 1
@_llgo_string = linkonce global ptr null, align 8
define void @"github.com/goplus/llgo/cl/_testdata/vargs.init"() {
@@ -87,7 +87,7 @@ _llgo_4: ; preds = %_llgo_2
_llgo_5: ; preds = %_llgo_2
%18 = load ptr, ptr @_llgo_string, align 8
%19 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 40 }, ptr %19, align 8
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 32 }, ptr %19, align 8
%20 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %18, 0
%21 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %20, ptr %19, 1
call void @"github.com/goplus/llgo/runtime/internal/runtime.Panic"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %21)

View File

@@ -7,10 +7,10 @@ source_filename = "github.com/goplus/llgo/cl/_testrt/any"
@"github.com/goplus/llgo/cl/_testrt/any.init$guard" = global i1 false, align 1
@_llgo_int8 = linkonce global ptr null, align 8
@"*_llgo_int8" = linkonce global ptr null, align 8
@0 = private unnamed_addr constant [42 x i8] c"type assertion interface{} -> *int8 failed", align 1
@0 = private unnamed_addr constant [34 x i8] c"type assertion any -> *int8 failed", align 1
@_llgo_string = linkonce global ptr null, align 8
@_llgo_int = linkonce global ptr null, align 8
@1 = private unnamed_addr constant [40 x i8] c"type assertion interface{} -> int failed", align 1
@1 = private unnamed_addr constant [32 x i8] c"type assertion any -> int failed", align 1
@2 = private unnamed_addr constant [7 x i8] c"%s %d\0A\00", align 1
@3 = private unnamed_addr constant [6 x i8] c"Hello\00", align 1
@@ -29,7 +29,7 @@ _llgo_1: ; preds = %_llgo_0
_llgo_2: ; preds = %_llgo_0
%6 = load ptr, ptr @_llgo_string, align 8
%7 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 42 }, ptr %7, align 8
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 34 }, ptr %7, align 8
%8 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %6, 0
%9 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %8, ptr %7, 1
call void @"github.com/goplus/llgo/runtime/internal/runtime.Panic"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %9)
@@ -52,7 +52,7 @@ _llgo_1: ; preds = %_llgo_0
_llgo_2: ; preds = %_llgo_0
%7 = load ptr, ptr @_llgo_string, align 8
%8 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 40 }, ptr %8, align 8
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 32 }, ptr %8, align 8
%9 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %7, 0
%10 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %9, ptr %8, 1
call void @"github.com/goplus/llgo/runtime/internal/runtime.Panic"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %10)

View File

@@ -23,7 +23,7 @@ source_filename = "github.com/goplus/llgo/cl/_testrt/tpabi"
@5 = private unnamed_addr constant [4 x i8] c"Demo", align 1
@"_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac" = linkonce global ptr null, align 8
@6 = private unnamed_addr constant [4 x i8] c"Info", align 1
@7 = private unnamed_addr constant [91 x i8] c"type assertion interface{} -> github.com/goplus/llgo/cl/_testrt/tpabi.T[string, int] failed", align 1
@7 = private unnamed_addr constant [83 x i8] c"type assertion any -> github.com/goplus/llgo/cl/_testrt/tpabi.T[string, int] failed", align 1
@8 = private unnamed_addr constant [5 x i8] c"hello", align 1
@"*_llgo_github.com/goplus/llgo/cl/_testrt/tpabi.T[string,int]" = linkonce global ptr null, align 8
@"_llgo_iface$BP0p_lUsEd-IbbtJVukGmgrdQkqzcoYzSiwgUvgFvUs" = linkonce global ptr null, align 8
@@ -107,7 +107,7 @@ _llgo_1: ; preds = %_llgo_0
_llgo_2: ; preds = %_llgo_0
%38 = load ptr, ptr @_llgo_string, align 8
%39 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 16)
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @7, i64 91 }, ptr %39, align 8
store %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @7, i64 83 }, ptr %39, align 8
%40 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" undef, ptr %38, 0
%41 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.eface" %40, ptr %39, 1
call void @"github.com/goplus/llgo/runtime/internal/runtime.Panic"(%"github.com/goplus/llgo/runtime/internal/runtime.eface" %41)

View File

@@ -37,10 +37,7 @@ func init() {
}
func runCmd(cmd *base.Command, args []string) {
conf := &build.Config{
Mode: build.ModeBuild,
AppExt: build.DefaultAppExt(),
}
conf := build.NewDefaultConf(build.ModeBuild)
if len(args) >= 2 && args[0] == "-o" {
conf.OutFile = args[1]
args = args[2:]

View File

@@ -301,7 +301,7 @@ func main() {
}
}
func _TestCommandHandling(t *testing.T) {
func TestCommandHandling(t *testing.T) {
tests := []struct {
name string
args []string

View File

@@ -2,4 +2,4 @@ module github.com/goplus/llgo/doc/_readme
go 1.20
require github.com/goplus/lib v0.1.0
require github.com/goplus/lib v0.2.0

View File

@@ -1,2 +1,2 @@
github.com/goplus/lib v0.1.0 h1:kqMAC6FmVPfrw0q8E5yF6Y12WC4GOfi2L/MYS4QoxbQ=
github.com/goplus/lib v0.1.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=

View File

@@ -2,6 +2,6 @@
brew update
brew install llvm@19 lld@19 bdw-gc openssl cjson libffi libuv pkg-config
brew install python@3.12 # optional
brew link --overwrite lld@19 libffi
brew link --overwrite llvm@19 lld@19 libffi
# curl https://raw.githubusercontent.com/goplus/llgo/refs/heads/main/install.sh | bash
./install.sh

2
go.mod
View File

@@ -6,7 +6,7 @@ toolchain go1.24.1
require (
github.com/goplus/gogen v1.16.9
github.com/goplus/lib v0.1.0
github.com/goplus/lib v0.2.0
github.com/goplus/llgo/runtime v0.0.0-20250403035532-0a8a4eb6a653
github.com/goplus/llvm v0.8.3
github.com/goplus/mod v0.13.17

4
go.sum
View File

@@ -2,8 +2,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/goplus/gogen v1.16.9 h1:BRNAsRzdyMcLBOLUe6+suVMmOe+D2HLfF7mAkS4/QW4=
github.com/goplus/gogen v1.16.9/go.mod h1:6TQYbabXDF9LCdDkOOzHmfg1R4ENfXQ3XpHa9RhTSD8=
github.com/goplus/lib v0.1.0 h1:kqMAC6FmVPfrw0q8E5yF6Y12WC4GOfi2L/MYS4QoxbQ=
github.com/goplus/lib v0.1.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/lib v0.2.0 h1:AjqkN1XK5H23wZMMlpaUYAMCDAdSBQ2NMFrLtSh7W4g=
github.com/goplus/lib v0.2.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/llvm v0.8.3 h1:is1zOwhiQZWtLnOmSMVPO+1sPa2uK/XJ/FjTSfIjGBU=
github.com/goplus/llvm v0.8.3/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/mod v0.13.17 h1:aWp14xosENrh7t0/0qcIejDmQEiTgI3ou2+KoLDlSlE=

View File

@@ -21,7 +21,6 @@ import (
"debug/macho"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/token"
"go/types"
@@ -31,23 +30,25 @@ import (
"path"
"path/filepath"
"runtime"
"slices"
"strings"
"unsafe"
"golang.org/x/tools/go/ssa"
"github.com/goplus/llgo/cl"
"github.com/goplus/llgo/internal/crosscompile"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/mockable"
"github.com/goplus/llgo/internal/packages"
"github.com/goplus/llgo/internal/typepatch"
llvmTarget "github.com/goplus/llgo/internal/xtool/llvm"
"github.com/goplus/llgo/ssa/abi"
xenv "github.com/goplus/llgo/xtool/env"
"github.com/goplus/llgo/xtool/env/llvm"
llruntime "github.com/goplus/llgo/runtime"
llssa "github.com/goplus/llgo/ssa"
clangCheck "github.com/goplus/llgo/xtool/clang/check"
)
type Mode int
@@ -66,6 +67,8 @@ const (
)
type Config struct {
Goos string
Goarch string
BinPath string
AppExt string // ".exe" on Windows, empty on Unix
OutFile string // only valid for ModeBuild when len(pkgs) == 1
@@ -86,10 +89,19 @@ func NewDefaultConf(mode Mode) *Config {
if err := os.MkdirAll(bin, 0755); err != nil {
panic(fmt.Errorf("cannot create bin directory: %v", err))
}
goos, goarch := os.Getenv("GOOS"), os.Getenv("GOARCH")
if goos == "" {
goos = runtime.GOOS
}
if goarch == "" {
goarch = runtime.GOARCH
}
conf := &Config{
Goos: goos,
Goarch: goarch,
BinPath: bin,
Mode: mode,
AppExt: DefaultAppExt(),
AppExt: DefaultAppExt(goos),
}
return conf
}
@@ -105,9 +117,12 @@ func envGOPATH() (string, error) {
return filepath.Join(home, "go"), nil
}
func DefaultAppExt() string {
if runtime.GOOS == "windows" {
func DefaultAppExt(goos string) string {
switch goos {
case "windows":
return ".exe"
case "wasi", "wasip1", "js":
return ".wasm"
}
return ""
}
@@ -121,9 +136,65 @@ const (
loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo
)
func mergeFlags(flags, extraFlags []string) (newFlags []string, tags []string) {
// Combine all flags
allFlags := append([]string{}, flags...)
allFlags = append(allFlags, extraFlags...)
// Find all -tags flags and extract their values
tagValues := []string{}
for i := 0; i < len(allFlags); i++ {
flag := allFlags[i]
// Handle -tags=value format
if strings.HasPrefix(flag, "-tags=") {
value := strings.TrimPrefix(flag, "-tags=")
if value != "" {
tagValues = append(tagValues, strings.Split(value, ",")...)
}
continue
}
// Handle -tags value format
if flag == "-tags" && i+1 < len(allFlags) {
i++
value := allFlags[i]
if value != "" {
tagValues = append(tagValues, strings.Split(value, ",")...)
}
continue
}
// Keep other flags
newFlags = append(newFlags, flag)
}
// Add combined -tags flag if we found any tag values
if len(tagValues) > 0 {
// Remove duplicates
uniqueTags := make([]string, 0, len(tagValues))
seen := make(map[string]bool)
for _, tag := range tagValues {
tag = strings.TrimSpace(tag)
if tag != "" && !seen[tag] {
seen[tag] = true
uniqueTags = append(uniqueTags, tag)
}
}
if len(uniqueTags) > 0 {
newFlags = append(newFlags, "-tags", strings.Join(uniqueTags, ","))
tags = []string{"-tags", strings.Join(uniqueTags, ",")}
}
}
return newFlags, tags
}
func Do(args []string, conf *Config) ([]Package, error) {
if conf.Goos == "" {
conf.Goos = runtime.GOOS
}
if conf.Goarch == "" {
conf.Goarch = runtime.GOARCH
}
flags, patterns, verbose := ParseArgs(args, buildFlags)
flags = append(flags, "-tags", "llgo")
flags, _ = mergeFlags(flags, []string{"-tags", "llgo"})
cfg := &packages.Config{
Mode: loadSyntax | packages.NeedDeps | packages.NeedModule | packages.NeedExportFile,
BuildFlags: flags,
@@ -148,8 +219,8 @@ func Do(args []string, conf *Config) ([]Package, error) {
llssa.Initialize(llssa.InitAll)
target := &llssa.Target{
GOOS: build.Default.GOOS,
GOARCH: build.Default.GOARCH,
GOOS: conf.Goos,
GOARCH: conf.Goarch,
}
prog := llssa.NewProgram(target)
@@ -221,7 +292,9 @@ func Do(args []string, conf *Config) ([]Package, error) {
os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows
output := conf.OutFile != ""
ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0, output, make(map[*packages.Package]bool), make(map[*packages.Package]bool)}
export, err := crosscompile.UseCrossCompileSDK(conf.Goos, conf.Goarch)
check(err)
ctx := &context{env, cfg, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0, output, make(map[*packages.Package]bool), make(map[*packages.Package]bool), conf, export}
pkgs, err := buildAllPkgs(ctx, initial, verbose)
check(err)
if mode == ModeGen {
@@ -286,6 +359,9 @@ type context struct {
needRt map[*packages.Package]bool
needPyInit map[*packages.Package]bool
buildConf *Config
crossCompile crosscompile.Export
}
func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs []*aPackage, err error) {
@@ -364,7 +440,7 @@ func buildAllPkgs(ctx *context, initial []*packages.Package, verbose bool) (pkgs
ctx.nLibdir++
}
}
if err := clangCheck.CheckLinkArgs(pkgLinkArgs); err != nil {
if err := ctx.env.Clang().CheckLinkArgs(pkgLinkArgs); err != nil {
panic(fmt.Sprintf("test link args '%s' failed\n\texpanded to: %v\n\tresolved to: %v\n\terror: %v", param, expdArgs, pkgLinkArgs, err))
}
aPkg.LinkArgs = append(aPkg.LinkArgs, pkgLinkArgs...)
@@ -401,39 +477,18 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
} else {
app = filepath.Join(conf.BinPath, name+conf.AppExt)
}
} else if !strings.HasSuffix(app, conf.AppExt) {
app += conf.AppExt
}
// Start with output file argument
args := make([]string, 0, len(pkg.Imports)+len(linkArgs)+16)
args = append(
args,
"-o", app,
"-Wl,--error-limit=0",
"-fuse-ld=lld",
"-Wno-override-module",
// "-O2", // FIXME: This will cause TestFinalizer in _test/bdwgc.go to fail on macOS.
)
switch runtime.GOOS {
case "darwin": // ld64.lld (macOS)
args = append(
args,
"-rpath", "@loader_path",
"-rpath", "@loader_path/../lib",
"-Xlinker", "-dead_strip",
)
case "windows": // lld-link (Windows)
// TODO: Add options for Windows.
default: // ld.lld (Unix), wasm-ld (WebAssembly)
args = append(
args,
"-rpath", "$ORIGIN",
"-rpath", "$ORIGIN/../lib",
"-fdata-sections",
"-ffunction-sections",
"-Xlinker", "--gc-sections",
"-lm",
"-latomic",
"-lpthread", // libpthread is built-in since glibc 2.34 (2021-08-01); we need to support earlier versions.
)
}
args = append(args, "-o", app)
// Add common linker arguments based on target OS and architecture
targetTriple := llvmTarget.GetTargetTriple(conf.Goos, conf.Goarch)
args = append(args, buildLdflags(conf.Goos, conf.Goarch, targetTriple)...)
needRuntime := false
needPyInit := false
pkgsMap := make(map[*packages.Package]*aPackage, len(pkgs))
@@ -453,7 +508,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
}
}
})
entryLLFile, err := genMainModuleFile(llssa.PkgRuntime, pkg.PkgPath, needRuntime, needPyInit)
entryLLFile, err := genMainModuleFile(conf, llssa.PkgRuntime, pkg.PkgPath, needRuntime, needPyInit)
if err != nil {
panic(err)
}
@@ -478,11 +533,13 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
// add rpath and find libs
exargs := make([]string, 0, ctx.nLibdir<<1)
libs := make([]string, 0, ctx.nLibdir*3)
for _, arg := range args {
if strings.HasPrefix(arg, "-L") {
exargs = append(exargs, "-rpath", arg[2:])
} else if strings.HasPrefix(arg, "-l") {
libs = append(libs, arg[2:])
if IsRpathChangeEnabled() {
for _, arg := range args {
if strings.HasPrefix(arg, "-L") {
exargs = append(exargs, "-rpath", arg[2:])
} else if strings.HasPrefix(arg, "-l") {
libs = append(libs, arg[2:])
}
}
}
args = append(args, exargs...)
@@ -490,14 +547,15 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
args = append(args, "-gdwarf-4")
}
// TODO(xsw): show work
if verbose {
fmt.Fprintln(os.Stderr, "clang", args)
}
err = ctx.env.Clang().Link(args...)
args = append(args, ctx.crossCompile.CCFLAGS...)
args = append(args, ctx.crossCompile.LDFLAGS...)
cmd := ctx.env.Clang()
cmd.Verbose = verbose
err = cmd.Link(args...)
check(err)
if IsRpathChangeEnabled() && runtime.GOOS == "darwin" {
if IsRpathChangeEnabled() && conf.Goos == "darwin" {
dylibDeps := make([]string, 0, len(libs))
for _, lib := range libs {
dylibDep := findDylibDep(app, lib)
@@ -519,7 +577,16 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
fmt.Fprintf(os.Stderr, "%s: exit code %d\n", app, s.ExitCode())
}
case ModeRun:
cmd := exec.Command(app, conf.RunArgs...)
args := make([]string, 0, len(conf.RunArgs)+1)
copy(args, conf.RunArgs)
if isWasmTarget(conf.Goos) {
args = append(args, app, "--wasm", "multi-memory=true")
args = append(args, conf.RunArgs...)
app = "wasmtime"
} else {
args = conf.RunArgs
}
cmd := exec.Command(app, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -532,7 +599,84 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, linkArgs
}
}
func genMainModuleFile(rtPkgPath, mainPkgPath string, needRuntime, needPyInit bool) (path string, err error) {
func buildCflags(goos, goarch, targetTriple string) []string {
args := []string{}
if goarch == "wasm" {
args = append(args, "-target", targetTriple)
}
return args
}
// buildLdflags builds the common linker arguments based on target OS and architecture
func buildLdflags(goos, goarch, targetTriple string) []string {
args := []string{
"-target", targetTriple,
"-Wno-override-module",
}
if goos == runtime.GOOS {
// Non-cross-compile
args = append(args,
"-Wl,--error-limit=0",
"-fuse-ld=lld",
"-Wno-override-module",
)
}
switch goos {
case "darwin": // ld64.lld (macOS)
if IsRpathChangeEnabled() {
args = append(
args,
"-rpath", "@loader_path",
"-rpath", "@loader_path/../lib",
)
}
args = append(
args,
"-Xlinker", "-dead_strip",
)
case "windows": // lld-link (Windows)
// TODO(xsw): Add options for Windows.
case "wasi", "wasip1", "js": // wasm-ld (WebAssembly)
args = append(
args,
"-fdata-sections",
"-ffunction-sections",
// "-nostdlib",
// "-Wl,--no-entry",
"-Wl,--export-all",
"-Wl,--allow-undefined",
// "-Wl,--import-memory,",
"-Wl,--export-memory",
"-Wl,--initial-memory=16777216", // 16MB
// "-pthread",
// "-matomics",
// "-mbulk-memory",
// "-mmultimemory",
)
default: // ld.lld (Unix)
args = append(
args,
// "-rpath", "$ORIGIN",
// "-rpath", "$ORIGIN/../lib",
"-fdata-sections",
"-ffunction-sections",
"-Xlinker",
"--gc-sections",
"-lm",
"-latomic",
"-lpthread", // libpthread is built-in since glibc 2.34 (2021-08-01); we need to support earlier versions.
)
}
return args
}
func isWasmTarget(goos string) bool {
return slices.Contains([]string{"wasi", "js", "wasip1"}, goos)
}
func genMainModuleFile(conf *Config, rtPkgPath, mainPkgPath string, needRuntime, needPyInit bool) (path string, err error) {
var (
pyInitDecl string
pyInit string
@@ -547,6 +691,10 @@ func genMainModuleFile(rtPkgPath, mainPkgPath string, needRuntime, needPyInit bo
pyInit = "call void @Py_Initialize()"
pyInitDecl = "declare void @Py_Initialize()"
}
mainDefine := "define i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr"
if isWasmTarget(conf.Goos) {
mainDefine = "define hidden noundef i32 @__main_argc_argv(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr"
}
mainCode := fmt.Sprintf(`; ModuleID = 'main'
source_filename = "main"
@@ -566,7 +714,7 @@ define weak void @"syscall.init"() {
ret void
}
define i32 @main(i32 %%0, ptr %%1) {
%s {
_llgo_0:
%s
store i32 %%0, ptr @__llgo_argc, align 4
@@ -577,7 +725,7 @@ _llgo_0:
call void @"%s.main"()
ret i32 0
}
`, pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath,
`, pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath, mainDefine,
pyInit, rtInit, mainPkgPath, mainPkgPath)
f, err := os.CreateTemp("", "main*.ll")
@@ -877,11 +1025,17 @@ func clFiles(ctx *context, files string, pkg *packages.Package, procFile func(li
func clFile(ctx *context, args []string, cFile, expFile string, procFile func(linkFile string), verbose bool) {
llFile := expFile + filepath.Base(cFile) + ".ll"
targetTriple := llvmTarget.GetTargetTriple(ctx.buildConf.Goos, ctx.buildConf.Goarch)
cflags := buildCflags(ctx.buildConf.Goos, ctx.buildConf.Goarch, targetTriple)
args = append(cflags, args...)
args = append(args, "-emit-llvm", "-S", "-o", llFile, "-c", cFile)
args = append(args, ctx.crossCompile.CCFLAGS...)
args = append(args, ctx.crossCompile.CFLAGS...)
if verbose {
fmt.Fprintln(os.Stderr, "clang", args)
}
err := ctx.env.Clang().Compile(args...)
cmd := ctx.env.Clang()
err := cmd.Compile(args...)
check(err)
procFile(llFile)
}

View File

@@ -0,0 +1,62 @@
package crosscompile
import (
"errors"
"io/fs"
"os"
"path/filepath"
"runtime"
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/xtool/llvm"
)
type Export struct {
CCFLAGS []string
CFLAGS []string
LDFLAGS []string
}
const wasiSdkUrl = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-25/wasi-sdk-25.0-x86_64-macos.tar.gz"
func cacheDir() string {
return filepath.Join(env.LLGoCacheDir(), "crosscompile")
}
func UseCrossCompileSDK(goos, goarch string) (export Export, err error) {
if runtime.GOOS == goos && runtime.GOARCH == goarch {
// not cross compile
return
}
if goarch == "wasm" {
sdkDir := filepath.Join(cacheDir(), llvm.GetTargetTriple(goos, goarch))
if _, err = os.Stat(sdkDir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return
}
if err = downloadAndExtract(wasiSdkUrl, sdkDir); err != nil {
return
}
}
// Set up flags for the SDK
wasiSdkRoot := filepath.Join(sdkDir, "wasi-sdk-25.0-x86_64-macos")
sysrootDir := filepath.Join(wasiSdkRoot, "share", "wasi-sysroot")
libclangDir := filepath.Join(wasiSdkRoot, "lib", "clang", "19")
includeDir := filepath.Join(sysrootDir, "include", "wasm32-wasip1")
libDir := filepath.Join(sysrootDir, "lib", "wasm32-wasip1")
export.CCFLAGS = []string{
"--sysroot=" + sysrootDir,
"-resource-dir=" + libclangDir,
}
export.CFLAGS = []string{
"-I" + includeDir,
}
export.LDFLAGS = []string{
"-L" + libDir,
}
return
}
// TODO(lijie): supports other platforms
return
}

View File

@@ -0,0 +1,156 @@
//go:build !llgo
// +build !llgo
package crosscompile
import (
"os"
"runtime"
"testing"
)
const (
sysrootPrefix = "--sysroot="
resourceDirPrefix = "-resource-dir="
includePrefix = "-I"
libPrefix = "-L"
)
func TestUseCrossCompileSDK(t *testing.T) {
// Skip long-running tests unless explicitly enabled
if testing.Short() {
t.Skip("Skipping test in short mode")
}
// Test cases
testCases := []struct {
name string
goos string
goarch string
expectSDK bool
expectCCFlags bool
expectCFlags bool
expectLDFlags bool
}{
{
name: "Same Platform",
goos: runtime.GOOS,
goarch: runtime.GOARCH,
expectSDK: false,
expectCCFlags: false,
expectCFlags: false,
expectLDFlags: false,
},
{
name: "WASM Target",
goos: "wasip1",
goarch: "wasm",
expectSDK: true,
expectCCFlags: true,
expectCFlags: true,
expectLDFlags: true,
},
{
name: "Unsupported Target",
goos: "windows",
goarch: "amd64",
expectSDK: false,
expectCCFlags: false,
expectCFlags: false,
expectLDFlags: false,
},
}
// Create a temporary directory for the cache
tempDir, err := os.MkdirTemp("", "crosscompile_test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
// Set environment variable for cache directory
oldEnv := os.Getenv("LLGO_CACHE_DIR")
os.Setenv("LLGO_CACHE_DIR", tempDir)
defer os.Setenv("LLGO_CACHE_DIR", oldEnv)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
export, err := UseCrossCompileSDK(tc.goos, tc.goarch)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
t.Logf("export: %+v", export)
if tc.expectSDK {
// Check if flags are set correctly
if tc.expectCCFlags && len(export.CCFLAGS) == 0 {
t.Error("Expected CCFLAGS to be set, but they are empty")
}
if tc.expectCFlags && len(export.CFLAGS) == 0 {
t.Error("Expected CFLAGS to be set, but they are empty")
}
if tc.expectLDFlags && len(export.LDFLAGS) == 0 {
t.Error("Expected LDFLAGS to be set, but they are empty")
}
// Check for specific flags
if tc.expectCCFlags {
hasSysroot := false
hasResourceDir := false
for _, flag := range export.CCFLAGS {
if len(flag) >= len(sysrootPrefix) && flag[:len(sysrootPrefix)] == sysrootPrefix {
hasSysroot = true
}
if len(flag) >= len(resourceDirPrefix) && flag[:len(resourceDirPrefix)] == resourceDirPrefix {
hasResourceDir = true
}
}
if !hasSysroot {
t.Error("Missing --sysroot flag in CCFLAGS")
}
if !hasResourceDir {
t.Error("Missing -resource-dir flag in CCFLAGS")
}
}
if tc.expectCFlags {
hasInclude := false
for _, flag := range export.CFLAGS {
if len(flag) >= len(includePrefix) && flag[:len(includePrefix)] == includePrefix {
hasInclude = true
}
}
if !hasInclude {
t.Error("Missing -I flag in CFLAGS")
}
}
if tc.expectLDFlags {
hasLib := false
for _, flag := range export.LDFLAGS {
if len(flag) >= len(libPrefix) && flag[:len(libPrefix)] == libPrefix {
hasLib = true
}
}
if !hasLib {
t.Error("Missing -L flag in LDFLAGS")
}
}
} else {
if len(export.CCFLAGS) != 0 || len(export.CFLAGS) != 0 || len(export.LDFLAGS) != 0 {
t.Errorf("Expected empty export, got CCFLAGS=%v, CFLAGS=%v, LDFLAGS=%v",
export.CCFLAGS, export.CFLAGS, export.LDFLAGS)
}
}
})
}
}

View File

@@ -0,0 +1,109 @@
package crosscompile
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
func downloadAndExtract(url, dir string) (err error) {
if _, err = os.Stat(dir); err == nil {
os.RemoveAll(dir)
}
tempDir := dir + ".temp"
os.RemoveAll(tempDir)
if err = os.MkdirAll(tempDir, 0755); err != nil {
return fmt.Errorf("failed to create temporary directory: %w", err)
}
urlPath := strings.Split(url, "/")
filename := urlPath[len(urlPath)-1]
localFile := filepath.Join(tempDir, filename)
if err = downloadFile(url, localFile); err != nil {
return fmt.Errorf("failed to download file: %w", err)
}
defer os.Remove(localFile)
if strings.HasSuffix(filename, ".tar.gz") || strings.HasSuffix(filename, ".tgz") {
err = extractTarGz(localFile, tempDir)
} else {
return fmt.Errorf("unsupported archive format: %s", filename)
}
if err != nil {
return fmt.Errorf("failed to extract archive: %w", err)
}
if err = os.Rename(tempDir, dir); err != nil {
return fmt.Errorf("failed to rename directory: %w", err)
}
return nil
}
func downloadFile(url, filepath string) error {
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
_, err = io.Copy(out, resp.Body)
return err
}
func extractTarGz(tarGzFile, dest string) error {
file, err := os.Open(tarGzFile)
if err != nil {
return err
}
defer file.Close()
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
target := filepath.Join(dest, header.Name)
if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("%s: illegal file path", target)
}
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
case tar.TypeReg:
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
return err
}
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
if _, err := io.Copy(f, tr); err != nil {
f.Close()
return err
}
f.Close()
}
}
return nil
}

8
internal/env/env.go vendored
View File

@@ -32,6 +32,14 @@ func GOROOT() string {
panic("cannot get GOROOT: " + err.Error())
}
func LLGoCacheDir() string {
userCacheDir, err := os.UserCacheDir()
if err != nil {
panic(err)
}
return filepath.Join(userCacheDir, "llgo")
}
func LLGoRuntimeDir() string {
root := LLGoROOT()
if root != "" {

View File

@@ -20,6 +20,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"github.com/goplus/llgo/internal/build"
@@ -46,7 +47,7 @@ func genFrom(pkgPath string) (build.Package, error) {
conf := &build.Config{
Mode: build.ModeGen,
AppExt: build.DefaultAppExt(),
AppExt: build.DefaultAppExt(runtime.GOOS),
}
pkgs, err := build.Do([]string{pkgPath}, conf)
if err != nil {

View File

@@ -0,0 +1,55 @@
package llvm
import "runtime"
func GetTargetTriple(goos, goarch string) string {
var llvmarch string
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 goarch {
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 = "macosx"
if llvmarch == "aarch64" {
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
llvmos = "macosx"
}
llvmvendor = "apple"
case "wasip1":
llvmos = "wasip1"
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
return llvmarch + "-" + llvmvendor + "-" + llvmos
}

View File

@@ -0,0 +1,150 @@
//go:build !llgo
// +build !llgo
package llvm
import (
"os/exec"
"strings"
"testing"
)
func TestGetTargetTriple(t *testing.T) {
// Get the list of supported architectures from clang
cmd := exec.Command("clang", "--print-targets")
output, err := cmd.Output()
if err != nil {
t.Fatalf("Failed to run clang --print-targets: %v", err)
}
// Parse the output to get the list of supported architectures
supportedArchs := make(map[string]bool)
lines := strings.Split(string(output), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" && !strings.HasPrefix(line, "Registered Targets:") {
// Extract the architecture from the line
parts := strings.SplitN(line, " - ", 2)
if len(parts) > 0 {
arch := strings.TrimSpace(parts[0])
supportedArchs[arch] = true
}
}
}
if len(supportedArchs) == 0 {
t.Fatal("No supported architectures found from clang --print-targets")
}
t.Logf("Found %d supported architectures from clang", len(supportedArchs))
// Map our architecture names to clang's architecture names
clangArchMap := map[string][]string{
"x86_64": {"x86-64", "x86_64"},
"i386": {"x86", "i386"},
"aarch64": {"aarch64", "arm64"},
"arm64": {"arm64", "aarch64"},
"armv7": {"arm", "thumb"},
"wasm32": {"wasm32"},
}
// Define a function to check if the architecture is supported by clang
isArchSupported := func(archPart string) (bool, string) {
if mappedArchs, ok := clangArchMap[archPart]; ok {
for _, mappedArch := range mappedArchs {
if supportedArchs[mappedArch] {
return true, mappedArch
}
}
} else if supportedArchs[archPart] {
// Direct match
return true, archPart
}
return false, ""
}
// Define a function to verify OS name
isOSValid := func(os, goos string) bool {
validOSMap := map[string][]string{
"linux": {"linux", "linux-gnu"},
"darwin": {"macosx", "darwin"},
"windows": {"windows", "win32"},
"wasip1": {"wasip1", "wasi"},
"js": {"js", "javascript"},
}
if validVariants, ok := validOSMap[goos]; ok {
for _, validVariant := range validVariants {
if strings.HasPrefix(os, validVariant) {
return true
}
}
}
return false
}
// Define a function to check if vendor is valid
isVendorValid := func(vendor string) bool {
validVendors := map[string]bool{
"unknown": true,
"apple": true,
"pc": true,
"ibm": true,
}
return validVendors[vendor]
}
// Define the test function
checkTriple := func(t *testing.T, testName, goos, goarch, expected string) {
t.Helper()
got := GetTargetTriple(goos, goarch)
// Check if the generated triple matches the expected value
if got != expected {
t.Errorf("getTargetTriple(%q, %q) = %q, want %q",
goos, goarch, got, expected)
}
// Extract the architecture part from the triple (first component)
parts := strings.Split(got, "-")
if len(parts) < 3 {
t.Errorf("Invalid target triple format: %s, should have at least 3 components", got)
return
}
archPart := parts[0]
vendor := parts[1]
os := parts[2]
// Check if the architecture is supported by clang
supported, mappedArch := isArchSupported(archPart)
if supported {
t.Logf("Architecture %s (mapped to %s) is supported by clang", archPart, mappedArch)
} else {
t.Logf("WARNING: Architecture %s from triple %q for %s/%s not found in clang's supported architectures",
archPart, got, goos, goarch)
}
// Verify vendor
if !isVendorValid(vendor) {
t.Errorf("Invalid vendor in triple: %s", vendor)
}
// Verify OS
if !isOSValid(os, goos) {
t.Errorf("OS in triple %q doesn't match expected OS %q", os, goos)
}
}
// Run tests for different OS/arch combinations
checkTriple(t, "wasip1/wasm", "wasip1", "wasm", "wasm32-unknown-wasip1")
checkTriple(t, "linux/amd64", "linux", "amd64", "x86_64-unknown-linux")
checkTriple(t, "linux/386", "linux", "386", "i386-unknown-linux")
checkTriple(t, "linux/arm64", "linux", "arm64", "aarch64-unknown-linux")
checkTriple(t, "linux/arm", "linux", "arm", "armv7-unknown-linux")
checkTriple(t, "darwin/amd64", "darwin", "amd64", "x86_64-apple-macosx")
checkTriple(t, "darwin/arm64", "darwin", "arm64", "arm64-apple-macosx")
checkTriple(t, "windows/amd64", "windows", "amd64", "x86_64-unknown-windows")
checkTriple(t, "windows/386", "windows", "386", "i386-unknown-windows")
checkTriple(t, "js/wasm", "js", "wasm", "wasm32-unknown-js")
}

11
llgo_wasm Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
set -e
WORKDIR=''
WORKDIR=$(pwd)
LLGO_ROOT=''
LLGO_ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )
export LLGO_ROOT
cd $LLGO_ROOT
go install ./cmd/llgo
cd $WORKDIR
GOOS=wasip1 GOARCH=wasm llgo "$@"

View File

@@ -24,6 +24,8 @@ import (
"go/types"
"io"
"log"
"os"
"runtime"
"testing"
"github.com/goplus/llgo/cl/cltest"
@@ -65,7 +67,13 @@ func TestFromTestdata(t *testing.T) {
}
func TestMakeInterface(t *testing.T) {
prog := ssatest.NewProgram(t, &ssa.Target{GOARCH: "x86"})
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
os.Chdir("../runtime")
defer os.Chdir(wd)
prog := ssatest.NewProgram(t, &ssa.Target{GOARCH: runtime.GOARCH})
pkg := prog.NewPackage("foo", "foo")
fn := pkg.NewFunc("main", types.NewSignatureType(nil, nil, nil, nil, nil, false), ssa.InC)
b := fn.MakeBody(1)

View File

@@ -245,7 +245,7 @@ func (b Builder) checkIndex(idx Expr, max Expr) Expr {
} else {
typ = prog.Uint()
}
if prog.SizeOf(idx.Type) < prog.SizeOf(typ) {
if prog.SizeOf(idx.Type) != prog.SizeOf(typ) {
idx.Type = typ
idx.impl = castUintptr(b, idx.impl, typ)
}

View File

@@ -210,6 +210,22 @@ type aProgram struct {
// A Program presents a program.
type Program = *aProgram
var arch32 = map[string]bool{
"386": true,
"arm": true,
"mips": true,
"mipsle": true,
"s390x": true,
"wasm": true,
}
func is32Bits(arch string) bool {
if v, ok := arch32[arch]; ok {
return v
}
return false
}
// NewProgram creates a new program.
func NewProgram(target *Target) Program {
if target == nil {
@@ -231,7 +247,7 @@ func NewProgram(target *Target) Program {
// TODO(xsw): Finalize may cause panic, so comment it.
ctx.Finalize()
*/
is32Bits := (td.PointerSize() == 4 || target.GOARCH == "x86") // TODO(xsw): remove temp code
is32Bits := (td.PointerSize() == 4 || is32Bits(target.GOARCH))
return &aProgram{
ctx: ctx, gocvt: newGoTypes(), fnsCompiled: fnsCompiled,
target: target, td: td, is32Bits: is32Bits,
@@ -379,6 +395,12 @@ func (p Program) tyComplex128() llvm.Type {
// NewPackage creates a new package.
func (p Program) NewPackage(name, pkgPath string) Package {
mod := p.ctx.NewModule(pkgPath)
// TODO(lijie): enable target output will check module override, but can't
// pass the snapshot test, so disable it for now
// if p.target.GOARCH != runtime.GOARCH && p.target.GOOS != runtime.GOOS {
// mod.SetTarget(p.target.Spec().Triple)
// }
// TODO(xsw): Finalize may cause panic, so comment it.
// mod.Finalize()
gbls := make(map[string]Global)

View File

@@ -23,6 +23,7 @@ import (
"go/constant"
"go/token"
"go/types"
"os"
"testing"
"unsafe"
@@ -40,6 +41,12 @@ func TestEndDefer(t *testing.T) {
}
func TestUnsafeString(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
os.Chdir("../../runtime")
defer os.Chdir(wd)
prog := NewProgram(nil)
prog.SetRuntime(func() *types.Package {
fset := token.NewFileSet()

View File

@@ -17,6 +17,8 @@
package ssa
import (
"runtime"
"github.com/goplus/llvm"
)
@@ -29,15 +31,15 @@ type Target struct {
}
func (p *Target) targetData() llvm.TargetData {
spec := p.toSpec()
if spec.triple == "" {
spec.triple = llvm.DefaultTargetTriple()
spec := p.Spec()
if spec.Triple == "" {
spec.Triple = llvm.DefaultTargetTriple()
}
t, err := llvm.GetTargetFromTriple(spec.triple)
t, err := llvm.GetTargetFromTriple(spec.Triple)
if err != nil {
panic(err)
}
machine := t.CreateTargetMachine(spec.triple, spec.cpu, spec.features, llvm.CodeGenLevelDefault, llvm.RelocDefault, llvm.CodeModelDefault)
machine := t.CreateTargetMachine(spec.Triple, spec.CPU, spec.Features, llvm.CodeGenLevelDefault, llvm.RelocDefault, llvm.CodeModelDefault)
return machine.CreateTargetData()
}
@@ -62,19 +64,13 @@ func (p *Program) targetMachine() llvm.TargetMachine {
}
*/
type targetSpec struct {
triple string
cpu string
features string
type TargetSpec struct {
Triple string
CPU string
Features string
}
// TODO config
func (p *Target) toSpec() (spec targetSpec) {
return
}
/*
func (p *Target) toSpec() (spec targetSpec) {
func (p *Target) Spec() (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
@@ -113,56 +109,55 @@ func (p *Target) toSpec() (spec targetSpec) {
case "darwin":
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
// to iOS!
llvmos = "macosx10.12.0"
llvmos = "macosx"
if llvmarch == "aarch64" {
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
llvmos = "macosx11.0.0"
llvmos = "macosx"
}
llvmvendor = "apple"
case "wasip1":
llvmos = "wasi"
llvmos = "wasip1"
}
// 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
spec.Triple = llvmarch + "-" + llvmvendor + "-" + llvmos
if llvmos == "windows" {
spec.triple += "-gnu"
spec.Triple += "-gnu"
} else if goarch == "arm" {
spec.triple += "-gnueabihf"
spec.Triple += "-gnueabihf"
}
switch goarch {
case "386":
spec.cpu = "pentium4"
spec.features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
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"
spec.CPU = "x86-64"
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
case "arm":
spec.cpu = "generic"
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"
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"
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"
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"
spec.CPU = "generic"
if goos == "darwin" {
spec.features = "+neon"
spec.Features = "+neon"
} else { // windows, linux
spec.features = "+neon,-fmv"
spec.Features = "+neon,-fmv"
}
case "wasm":
spec.cpu = "generic"
spec.features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext"
spec.CPU = "generic"
spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext"
}
return
}
*/
// -----------------------------------------------------------------------------

View File

@@ -17,9 +17,11 @@
package clang
import (
"fmt"
"io"
"os"
"os/exec"
"runtime"
"strings"
)
@@ -27,10 +29,12 @@ import (
// Cmd represents a clang command.
type Cmd struct {
app string
Stdout io.Writer
Stderr io.Writer
app string
Env []string
Verbose bool
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
// New creates a new clang command.
@@ -38,33 +42,56 @@ func New(app string) *Cmd {
if app == "" {
app = "clang"
}
return &Cmd{app, os.Stdout, os.Stderr}
return &Cmd{app, nil, false, nil, os.Stdout, os.Stderr}
}
func (p *Cmd) Compile(args ...string) error {
// Parse CFLAGS environment variable into separate arguments
cflags := strings.Fields(os.Getenv("CFLAGS"))
if len(cflags) > 0 {
// Create a new slice with capacity for all arguments
newArgs := make([]string, 0, len(cflags)+len(args))
newArgs = append(newArgs, cflags...)
newArgs = append(newArgs, args...)
args = newArgs
}
return p.Exec(args...)
return p.execWithFlags([]string{"CFLAGS", "CCFLAGS"}, args...)
}
func (p *Cmd) Link(args ...string) error {
// Parse LDFLAGS environment variable into separate arguments
ldflags := strings.Fields(os.Getenv("LDFLAGS"))
if len(ldflags) > 0 {
// Create a new slice with capacity for all arguments
newArgs := make([]string, 0, len(ldflags)+len(args))
newArgs = append(newArgs, ldflags...)
newArgs = append(newArgs, args...)
args = newArgs
return p.execWithFlags([]string{"LDFLAGS", "CCFLAGS"}, args...)
}
// execWithFlags executes a clang command with flags from environment variables.
func (p *Cmd) execWithFlags(flags []string, args ...string) error {
var allFlags []string
if p.Env != nil {
for _, env := range p.Env {
parts := strings.SplitN(env, "=", 2)
if len(parts) != 2 {
continue
}
key, value := parts[0], parts[1]
for _, flagName := range flags {
if key == flagName {
allFlags = append(allFlags, strings.Fields(value)...)
break
}
}
}
} else {
for _, flagName := range flags {
envValue := os.Getenv(flagName)
if envValue != "" {
allFlags = append(allFlags, strings.Fields(envValue)...)
}
}
}
return p.Exec(args...)
cmdArgs := make([]string, 0, len(allFlags)+len(args))
cmdArgs = append(cmdArgs, allFlags...)
cmdArgs = append(cmdArgs, args...)
cmd := exec.Command(p.app, cmdArgs...)
if p.Verbose {
fmt.Fprintf(os.Stderr, "%v\n", cmd)
}
cmd.Stdin = p.Stdin
cmd.Stdout = p.Stdout
cmd.Stderr = p.Stderr
if p.Env != nil {
cmd.Env = p.Env
}
return cmd.Run()
}
// Exec executes a clang command.
@@ -72,7 +99,23 @@ func (p *Cmd) Exec(args ...string) error {
cmd := exec.Command(p.app, args...)
cmd.Stdout = p.Stdout
cmd.Stderr = p.Stderr
if p.Env != nil {
cmd.Env = p.Env
}
return cmd.Run()
}
func (p *Cmd) CheckLinkArgs(cmdArgs []string) error {
nul := "/dev/null"
if runtime.GOOS == "windows" {
nul = "NUL"
}
args := append([]string{}, cmdArgs...)
args = append(args, []string{"-x", "c", "-o", nul, "-"}...)
src := "int main() {return 0;}"
srcIn := strings.NewReader(src)
p.Stdin = srcIn
return p.execWithFlags([]string{"LDFLAGS", "CCFLAGS"}, args...)
}
// -----------------------------------------------------------------------------

View File

@@ -1,3 +1,6 @@
//go:build !llgo
// +build !llgo
/*
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
*