Merge branch 'main' of https://github.com/goplus/llgo into impl-stacksave

This commit is contained in:
Haolan
2025-10-30 13:48:13 +08:00
26 changed files with 1188 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ on:
branches:
- "**"
- "!dependabot/**"
- "!xgopilot/**"
pull_request:
branches: ["**"]
@@ -20,7 +21,7 @@ jobs:
- uses: actions/checkout@v5
- name: Set up Node.js
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: "20"

View File

@@ -5,6 +5,7 @@ on:
branches:
- "**"
- "!dependabot/**"
- "!xgopilot/**"
pull_request:
branches: ["**"]

View File

@@ -8,6 +8,7 @@ on:
branches:
- "**"
- "!dependabot/**"
- "!xgopilot/**"
pull_request:
branches: ["**"]

View File

@@ -8,6 +8,7 @@ on:
branches:
- "**"
- "!dependabot/**"
- "!xgopilot/**"
pull_request:
branches: ["**"]
@@ -26,7 +27,7 @@ jobs:
wget -P ./_demo/c/llama2-c https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
- name: Upload model as artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: llama2-model
path: ./_demo/c/llama2-c/stories15M.bin
@@ -51,7 +52,7 @@ jobs:
with:
llvm-version: ${{matrix.llvm}}
- name: Download model artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v6
with:
name: llama2-model
path: ./_demo/c/llama2-c/

View File

@@ -2,7 +2,10 @@ name: Release Build
on:
push:
branches: ["**"]
branches:
- "**"
- "!dependabot/**"
- "!xgopilot/**"
tags: ["*"]
pull_request:
branches: ["**"]
@@ -79,7 +82,7 @@ jobs:
release --verbose --skip=publish,nfpm,snapcraft --snapshot --clean
- name: Upload Darwin AMD64 Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: llgo-darwin-amd64
path: .dist/*darwin-amd64.tar.gz
@@ -87,7 +90,7 @@ jobs:
include-hidden-files: true
- name: Upload Darwin ARM64 Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: llgo-darwin-arm64
path: .dist/*darwin-arm64.tar.gz
@@ -95,7 +98,7 @@ jobs:
include-hidden-files: true
- name: Upload Linux AMD64 Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: llgo-linux-amd64
path: .dist/*linux-amd64.tar.gz
@@ -103,7 +106,7 @@ jobs:
include-hidden-files: true
- name: Upload Linux ARM64 Artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: llgo-linux-arm64
path: .dist/*linux-arm64.tar.gz
@@ -111,7 +114,7 @@ jobs:
include-hidden-files: true
- name: Upload Checksums
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: llgo-checksums
path: .dist/*checksums.txt
@@ -155,7 +158,7 @@ jobs:
with:
go-version: ${{ matrix.go-version }}
- name: Download Platform Artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v6
with:
name: llgo-${{ matrix.goos }}-${{ matrix.goarch }}
path: .

View File

@@ -6,6 +6,7 @@ on:
branches:
- "**"
- "!dependabot/**"
- "!xgopilot/**"
pull_request:
branches: ["**"]

132
CLAUDE.md Normal file
View File

@@ -0,0 +1,132 @@
# LLGo Project AI Assistant Guide
This document provides essential information for AI assistants to help fix bugs and implement features in the LLGo project.
## About LLGo
LLGo is a Go compiler based on LLVM designed to better integrate Go with the C ecosystem, including Python and JavaScript. It's a subproject of the XGo project that aims to expand the boundaries of Go/XGo for game development, AI and data science, WebAssembly, and embedded development.
## Project Structure
- `cmd/llgo` - Main llgo compiler command (usage similar to `go` command)
- `cl/` - Core compiler logic that converts Go packages to LLVM IR
- `ssa/` - LLVM IR file generation using Go SSA semantics
- `internal/build/` - Build process orchestration
- `runtime/` - LLGo runtime library
- `chore/` - Development tools (llgen, llpyg, ssadump, etc.)
- `_demo/` - Example programs demonstrating C/C++ interop (`c/hello`, `c/qsort`) and Python integration (`py/callpy`, `py/numpy`)
- `_cmptest/` - Comparison tests to verify the same program gets the same output with Go and LLGo
## Development Environment
For detailed dependency requirements and installation instructions, see the [Dependencies](README.md#dependencies) and [How to install](README.md#how-to-install) sections in the README.
## Testing & Validation
The following commands and workflows are essential when fixing bugs or implementing features in the LLGo project:
### Run all tests
```bash
go test ./...
```
**Note:** Some tests may fail if optional dependencies (like Python) are not properly configured. The test suite includes comprehensive tests for:
- Compiler functionality
- SSA generation
- C interop
- Python integration (requires Python development headers)
### Write and run tests for your changes
When adding new functionality or fixing bugs, create appropriate test cases:
```bash
# Add your test to the relevant package's *_test.go file
# Then run tests for that package
go test ./path/to/package
# Or run all tests
go test ./...
```
**Important:** The `LLGO_ROOT` environment variable must be set to the repository root when running llgo commands during development.
## Code Quality
Before submitting any code updates, you must run the following formatting and validation commands:
### Format code
```bash
go fmt ./...
```
**Important:** Always run `go fmt ./...` before committing code changes. This ensures consistent code formatting across the project.
### Run static analysis
```bash
go vet ./...
```
**Note:** Currently reports some issues related to lock passing by value in `ssa/type_cvt.go` and a possible unsafe.Pointer misuse in `cl/builtin_test.go`. These are known issues.
## Common Development Tasks
### Build the entire project
```bash
go build -v ./...
```
### Build llgo command specifically
```bash
go build -o llgo ./cmd/llgo
```
### Check llgo version
```bash
llgo version
```
### Install llgo for system-wide use
```bash
./install.sh
```
### Build development tools
```bash
go install -v ./cmd/...
go install -v ./chore/...
```
## Key Modules for Understanding
- `ssa` - Generates LLVM IR using Go SSA semantics
- `cl` - Core compiler converting Go to LLVM IR
- `internal/build` - Orchestrates the compilation process
## Debugging
### Disable Garbage Collection
For testing purposes, you can disable GC:
```bash
LLGO_ROOT=/path/to/llgo llgo run -tags nogc .
```
## LLGO_ROOT Environment Variable
**CRITICAL:** Always set `LLGO_ROOT` to the repository root when running llgo during development:
```bash
export LLGO_ROOT=/path/to/llgo
# or
LLGO_ROOT=/path/to/llgo llgo run .
```
## Important Notes
1. **Testing Requirement:** All bug fixes and features MUST include tests
2. **Demo Directory:** Examples in `_demo` are prefixed with `_` to prevent standard `go` command from trying to compile them
3. **Defer in Loops:** LLGo intentionally does not support `defer` in loops (considered bad practice)
4. **C Ecosystem Integration:** LLGo uses `go:linkname` directive to link external symbols through ABI
5. **Python Integration:** Third-party Python libraries require separate installation of library files

View File

@@ -323,6 +323,7 @@ Here are the Go packages that can be imported correctly:
* [hash/adler32](https://pkg.go.dev/hash/adler32)
* [hash/crc32](https://pkg.go.dev/hash/crc32) (partially)
* [hash/crc64](https://pkg.go.dev/hash/crc64)
* [hash/maphash](https://pkg.go.dev/hash/maphash) (partially)
* [crypto](https://pkg.go.dev/crypto)
* [crypto/md5](https://pkg.go.dev/crypto/md5)
* [crypto/sha1](https://pkg.go.dev/crypto/sha1)

147
_demo/go/gobuild/demo.go Normal file
View File

@@ -0,0 +1,147 @@
package main
import (
"fmt"
"go/build"
"runtime"
"strings"
)
func main() {
fmt.Printf("runtime.Compiler = %q\n", runtime.Compiler)
// Test 1: Check build.Default context
ctx := build.Default
fmt.Printf("build.Default.Compiler = %q\n", ctx.Compiler)
if ctx.Compiler != "gc" {
panic(fmt.Sprintf("expected build.Default.Compiler to be \"gc\", got %q", ctx.Compiler))
}
if len(ctx.ToolTags) == 0 {
panic("expected build.Default.ToolTags to be non-empty")
}
fmt.Printf("build.Default.ToolTags = %v\n", ctx.ToolTags)
if len(ctx.ReleaseTags) == 0 {
panic("expected build.Default.ReleaseTags to be non-empty")
}
fmt.Printf("build.Default.ReleaseTags count = %d\n", len(ctx.ReleaseTags))
// Validate GOOS and GOARCH are set
if ctx.GOOS == "" {
panic("expected build.Default.GOOS to be non-empty")
}
if ctx.GOARCH == "" {
panic("expected build.Default.GOARCH to be non-empty")
}
fmt.Printf("build.Default.GOOS = %q, GOARCH = %q\n", ctx.GOOS, ctx.GOARCH)
// Test 2: Import standard library package with FindOnly
pkg, err := build.Import("fmt", "", build.FindOnly)
if err != nil {
panic(fmt.Sprintf("build.Import(\"fmt\") failed: %v", err))
}
if pkg.ImportPath != "fmt" {
panic(fmt.Sprintf("expected ImportPath \"fmt\", got %q", pkg.ImportPath))
}
if !pkg.Goroot {
panic("expected fmt package to be in GOROOT")
}
fmt.Printf("build.Import(\"fmt\"): ImportPath=%s, Goroot=%v\n", pkg.ImportPath, pkg.Goroot)
// Test 3: Import nested standard library package
osPkg, err := build.Import("os/exec", "", build.FindOnly)
if err != nil {
panic(fmt.Sprintf("build.Import(\"os/exec\") failed: %v", err))
}
if osPkg.ImportPath != "os/exec" {
panic(fmt.Sprintf("expected ImportPath \"os/exec\", got %q", osPkg.ImportPath))
}
if !osPkg.Goroot {
panic("expected os/exec package to be in GOROOT")
}
fmt.Printf("build.Import(\"os/exec\"): ImportPath=%s, Goroot=%v\n", osPkg.ImportPath, osPkg.Goroot)
// Test 4: Import internal package (should succeed with FindOnly)
internalPkg, err := build.Import("internal/cpu", "", build.FindOnly)
if err != nil {
panic(fmt.Sprintf("build.Import(\"internal/cpu\") failed: %v", err))
}
if internalPkg.ImportPath != "internal/cpu" {
panic(fmt.Sprintf("expected ImportPath \"internal/cpu\", got %q", internalPkg.ImportPath))
}
fmt.Printf("build.Import(\"internal/cpu\"): ImportPath=%s\n", internalPkg.ImportPath)
// Test 5: Import with srcDir parameter
runtimePkg, err := build.Import("runtime", "", build.FindOnly)
if err != nil {
panic(fmt.Sprintf("build.Import(\"runtime\") failed: %v", err))
}
if runtimePkg.ImportPath != "runtime" {
panic(fmt.Sprintf("expected ImportPath \"runtime\", got %q", runtimePkg.ImportPath))
}
if runtimePkg.Dir == "" {
panic("expected runtime package Dir to be non-empty")
}
fmt.Printf("build.Import(\"runtime\"): ImportPath=%s, Dir exists=%v\n", runtimePkg.ImportPath, runtimePkg.Dir != "")
// Test 6: ImportDir with current directory
dirPkg, err := build.ImportDir(".", build.FindOnly)
if err != nil {
panic(fmt.Sprintf("build.ImportDir(\".\") failed: %v", err))
}
// Note: Name might be empty with FindOnly mode as it doesn't read source files
fmt.Printf("build.ImportDir(\".\"): Dir=%s, ImportPath=%s\n", dirPkg.Dir, dirPkg.ImportPath)
// Test 7: IsLocalImport with various paths
testCases := []struct {
path string
expected bool
}{
{"./foo", true},
{"../bar", true},
{"./", true},
{"fmt", false},
{"github.com/user/repo", false},
{"", false},
}
for _, tc := range testCases {
result := build.IsLocalImport(tc.path)
if result != tc.expected {
panic(fmt.Sprintf("build.IsLocalImport(%q): expected %v, got %v", tc.path, tc.expected, result))
}
}
fmt.Printf("build.IsLocalImport: all test cases passed\n")
// Test 8: Verify Context has expected fields
if ctx.GOPATH == "" && ctx.GOROOT == "" {
panic("expected either GOPATH or GOROOT to be set")
}
fmt.Printf("build.Default.GOROOT exists = %v\n", ctx.GOROOT != "")
// Test 9: Import with AllowBinary flag
binaryPkg, err := build.Import("fmt", "", build.FindOnly|build.AllowBinary)
if err != nil {
panic(fmt.Sprintf("build.Import with AllowBinary failed: %v", err))
}
if binaryPkg.ImportPath != "fmt" {
panic(fmt.Sprintf("expected ImportPath \"fmt\", got %q", binaryPkg.ImportPath))
}
fmt.Printf("build.Import(\"fmt\") with AllowBinary: success\n")
// Test 10: Verify compiler tag in build context
hasCompilerTag := false
for _, tag := range ctx.ReleaseTags {
if strings.HasPrefix(tag, "go1.") {
hasCompilerTag = true
break
}
}
if !hasCompilerTag {
panic("expected at least one go1.x release tag")
}
fmt.Printf("build.Default.ReleaseTags: contains go1.x tags = %v\n", hasCompilerTag)
fmt.Printf("\nSuccess! All go/build public functions work correctly with llgo\n")
fmt.Printf("Total tests passed: 10\n")
}

124
_demo/go/maphash/maphash.go Normal file
View File

@@ -0,0 +1,124 @@
package main
import (
"fmt"
"hash/maphash"
)
func main() {
testHashBasics()
testMakeSeed()
testSetSeed()
testWriteMethods()
testBytes()
testString()
}
func testHashBasics() {
fmt.Println("=== Test Hash Basics ===")
var h maphash.Hash
n, err := h.WriteString("hello")
if err != nil {
panic(fmt.Sprintf("WriteString failed: %v", err))
}
if n != 5 {
panic(fmt.Sprintf("WriteString returned %d, expected 5", n))
}
hash1 := h.Sum64()
fmt.Printf("Hash of 'hello': 0x%x\n", hash1)
h.Reset()
n, err = h.WriteString("world")
if err != nil {
panic(fmt.Sprintf("WriteString failed: %v", err))
}
hash2 := h.Sum64()
fmt.Printf("Hash of 'world': 0x%x\n", hash2)
h.Reset()
n, err = h.WriteString("hello")
if err != nil {
panic(fmt.Sprintf("WriteString failed: %v", err))
}
hash3 := h.Sum64()
if hash1 != hash3 {
panic(fmt.Sprintf("Hash mismatch: 0x%x != 0x%x", hash1, hash3))
}
fmt.Printf("Hash consistency verified: 0x%x == 0x%x\n", hash1, hash3)
}
func testMakeSeed() {
fmt.Println("\n=== Test MakeSeed ===")
seed1 := maphash.MakeSeed()
seed2 := maphash.MakeSeed()
fmt.Printf("Seed 1: %v\n", seed1)
fmt.Printf("Seed 2: %v\n", seed2)
if seed1 == seed2 {
fmt.Println("Warning: Seeds are identical (rare but possible)")
}
}
func testSetSeed() {
fmt.Println("\n=== Test SetSeed ===")
var h1, h2 maphash.Hash
seed := maphash.MakeSeed()
h1.SetSeed(seed)
_, err := h1.WriteString("test")
if err != nil {
panic(fmt.Sprintf("WriteString failed: %v", err))
}
hash1 := h1.Sum64()
h2.SetSeed(seed)
_, err = h2.WriteString("test")
if err != nil {
panic(fmt.Sprintf("WriteString failed: %v", err))
}
hash2 := h2.Sum64()
if hash1 != hash2 {
panic(fmt.Sprintf("Hashes with same seed should match: 0x%x != 0x%x", hash1, hash2))
}
fmt.Printf("Same seed produces same hash: 0x%x == 0x%x\n", hash1, hash2)
}
func testWriteMethods() {
fmt.Println("\n=== Test Write Methods ===")
var h maphash.Hash
data := []byte("hello")
n, err := h.Write(data)
if err != nil {
panic(fmt.Sprintf("Write failed: %v", err))
}
if n != len(data) {
panic(fmt.Sprintf("Write returned %d, expected %d", n, len(data)))
}
hash1 := h.Sum64()
fmt.Printf("Hash after Write: 0x%x\n", hash1)
h.Reset()
err = h.WriteByte('A')
if err != nil {
panic(fmt.Sprintf("WriteByte failed: %v", err))
}
hash2 := h.Sum64()
fmt.Printf("Hash after WriteByte('A'): 0x%x\n", hash2)
}
func testBytes() {
fmt.Println("\n=== Test Bytes Function ===")
seed := maphash.MakeSeed()
data := []byte("test data")
hash := maphash.Bytes(seed, data)
fmt.Printf("Bytes hash: 0x%x\n", hash)
}
func testString() {
fmt.Println("\n=== Test String Function ===")
seed := maphash.MakeSeed()
str := "test string"
hash := maphash.String(seed, str)
fmt.Printf("String hash: 0x%x\n", hash)
}

View File

@@ -0,0 +1,49 @@
package main
import (
"reflect"
)
func main() {
x := 42
p := &x
// Test 1: Non-pointer value - should return same value
v1 := reflect.Indirect(reflect.ValueOf(x))
if !v1.IsValid() || v1.Interface() != 42 {
panic("Non-pointer test failed: expected 42")
}
// Test 2: Pointer - should dereference
v2 := reflect.Indirect(reflect.ValueOf(p))
if !v2.IsValid() || v2.Interface() != 42 {
panic("Pointer dereference test failed: expected 42")
}
// Test 3: Nil pointer - should return invalid Value
var nilPtr *int
v3 := reflect.Indirect(reflect.ValueOf(nilPtr))
if v3.IsValid() {
panic("Nil pointer test failed: expected invalid Value")
}
// Test 4: Struct value - should return same value
type Person struct {
Name string
Age int
}
person := Person{Name: "Alice", Age: 30}
v4 := reflect.Indirect(reflect.ValueOf(person))
if !v4.IsValid() || v4.Interface().(Person).Name != "Alice" || v4.Interface().(Person).Age != 30 {
panic("Struct value test failed: expected Person{Name: Alice, Age: 30}")
}
// Test 5: Struct pointer - should dereference
personPtr := &Person{Name: "Bob", Age: 25}
v5 := reflect.Indirect(reflect.ValueOf(personPtr))
if !v5.IsValid() || v5.Interface().(Person).Name != "Bob" || v5.Interface().(Person).Age != 25 {
panic("Struct pointer test failed: expected Person{Name: Bob, Age: 25}")
}
println("PASS")
}

2
go.mod
View File

@@ -6,7 +6,7 @@ toolchain go1.24.1
require (
github.com/goplus/cobra v1.9.12 //gop:class
github.com/goplus/gogen v1.19.2
github.com/goplus/gogen v1.19.3
github.com/goplus/lib v0.3.0
github.com/goplus/llgo/runtime v0.0.0-00010101000000-000000000000
github.com/goplus/llvm v0.8.5

4
go.sum
View File

@@ -6,8 +6,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/cobra v1.9.12 h1:0F9EdEbeGyITGz+mqoHoJ5KpUw97p1CkxV74IexHw5s=
github.com/goplus/cobra v1.9.12/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E=
github.com/goplus/gogen v1.19.2 h1:0WCfbMy9V2gGIUG4gnlhbFkLLuEwVuxOgkzjJjeGsP0=
github.com/goplus/gogen v1.19.2/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/gogen v1.19.3 h1:sMTe7xME8lWFdPL6NcULykdJtFs9CtXkNACRbaAKTiQ=
github.com/goplus/gogen v1.19.3/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/lib v0.3.0 h1:y0ZGb5Q/RikW1oMMB4Di7XIZIpuzh/7mlrR8HNbxXCA=
github.com/goplus/lib v0.3.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs=

View File

@@ -22,8 +22,10 @@ var hasAltPkg = map[string]none{
"crypto/sha256": {},
"crypto/sha512": {},
"crypto/subtle": {},
"go/build": {},
"go/parser": {},
"hash/crc32": {},
"hash/maphash": {},
"internal/abi": {},
"internal/bytealg": {},
"internal/chacha8rand": {},

View File

@@ -40,6 +40,20 @@ func Realloc(ptr c.Pointer, size uintptr) c.Pointer
//go:linkname Free C.GC_free
func Free(ptr c.Pointer)
// AddRoots registers a memory region [start, end) as a GC root. The caller
// must ensure that the range remains valid until RemoveRoots is invoked with
// the same boundaries. This is typically used for TLS slots that store Go
// pointers.
//
//go:linkname AddRoots C.GC_add_roots
func AddRoots(start, end c.Pointer)
// RemoveRoots unregisters a region previously registered with AddRoots. The
// start and end pointers must exactly match the earlier AddRoots call.
//
//go:linkname RemoveRoots C.GC_remove_roots
func RemoveRoots(start, end c.Pointer)
// -----------------------------------------------------------------------------
//go:linkname RegisterFinalizer C.GC_register_finalizer

View File

@@ -0,0 +1,128 @@
//go:build llgo
/*
* Copyright (c) 2025 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 tls provides generic thread-local storage backed by POSIX pthread
// TLS. When built with the GC-enabled configuration (llgo && !nogc), TLS slots
// are automatically registered with the BDWGC garbage collector so pointers
// stored in thread-local state remain visible to the collector. Builds without
// GC integration (llgo && nogc) simply fall back to pthread TLS without root
// registration.
//
// Basic usage:
//
// h := tls.Alloc[int](nil)
// h.Set(42)
// val := h.Get() // returns 42
//
// With destructor:
//
// h := tls.Alloc[*Resource](func(r **Resource) {
// if r != nil && *r != nil {
// (*r).Close()
// }
// })
//
// Build tags:
// - llgo && !nogc: Enables GC-aware slot registration via BDWGC
// - llgo && nogc: Disables GC integration; TLS acts as plain pthread TLS
package tls
import (
"unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/pthread"
)
type Handle[T any] struct {
key pthread.Key
destructor func(*T)
}
// Alloc creates a TLS handle backed by pthread TLS.
func Alloc[T any](destructor func(*T)) Handle[T] {
var key pthread.Key
if ret := key.Create(slotDestructor[T]); ret != 0 {
c.Fprintf(c.Stderr, c.Str("tls: pthread_key_create failed (errno=%d)\n"), ret)
panic("tls: failed to create thread local storage key")
}
return Handle[T]{key: key, destructor: destructor}
}
// Get returns the value stored in the current thread's slot.
func (h Handle[T]) Get() T {
if ptr := h.key.Get(); ptr != nil {
return (*slot[T])(ptr).value
}
var zero T
return zero
}
// Set stores v in the current thread's slot, creating it if necessary.
func (h Handle[T]) Set(v T) {
s := h.ensureSlot()
s.value = v
}
// Clear zeroes the current thread's slot value without freeing the slot.
func (h Handle[T]) Clear() {
if ptr := h.key.Get(); ptr != nil {
s := (*slot[T])(ptr)
var zero T
s.value = zero
}
}
func (h Handle[T]) ensureSlot() *slot[T] {
if ptr := h.key.Get(); ptr != nil {
return (*slot[T])(ptr)
}
size := unsafe.Sizeof(slot[T]{})
mem := c.Calloc(1, size)
if mem == nil {
panic("tls: failed to allocate thread slot")
}
s := (*slot[T])(mem)
s.destructor = h.destructor
if existing := h.key.Get(); existing != nil {
c.Free(mem)
return (*slot[T])(existing)
}
if ret := h.key.Set(mem); ret != 0 {
c.Free(mem)
c.Fprintf(c.Stderr, c.Str("tls: pthread_setspecific failed (errno=%d)\n"), ret)
panic("tls: failed to set thread local storage value")
}
registerSlot(s)
return s
}
func slotDestructor[T any](ptr c.Pointer) {
s := (*slot[T])(ptr)
if s == nil {
return
}
if s.destructor != nil {
s.destructor(&s.value)
}
deregisterSlot(s)
var zero T
s.value = zero
s.destructor = nil
c.Free(ptr)
}

View File

@@ -0,0 +1,74 @@
//go:build llgo && !nogc
/*
* Copyright (c) 2025 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 tls
import (
"unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/bdwgc"
)
const slotRegistered = 1 << iota
const maxSlotSize = 1 << 20 // 1 MiB sanity cap
type slot[T any] struct {
value T
state uintptr
destructor func(*T)
}
func registerSlot[T any](s *slot[T]) {
if s.state&slotRegistered != 0 {
return
}
start, end := s.rootRange()
size := uintptr(end) - uintptr(start)
if size == 0 {
return
}
if size > maxSlotSize {
panic("tls: slot size exceeds maximum")
}
bdwgc.AddRoots(start, end)
s.state |= slotRegistered
}
func deregisterSlot[T any](s *slot[T]) {
if s == nil || s.state&slotRegistered == 0 {
return
}
s.state &^= slotRegistered
start, end := s.rootRange()
if uintptr(end) > uintptr(start) {
bdwgc.RemoveRoots(start, end)
}
}
func (s *slot[T]) rootRange() (start, end c.Pointer) {
begin := unsafe.Pointer(s)
size := unsafe.Sizeof(*s)
beginAddr := uintptr(begin)
if beginAddr > ^uintptr(0)-size {
panic("tls: pointer arithmetic overflow in rootRange")
}
endPtr := unsafe.Pointer(beginAddr + size)
return c.Pointer(begin), c.Pointer(endPtr)
}

View File

@@ -0,0 +1,28 @@
//go:build llgo && nogc
/*
* Copyright (c) 2025 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 tls
type slot[T any] struct {
value T
destructor func(*T)
}
func registerSlot[T any](s *slot[T]) {}
func deregisterSlot[T any](s *slot[T]) {}

View File

@@ -0,0 +1,141 @@
//go:build llgo
/*
* Copyright (c) 2025 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 tls_test
import (
"fmt"
"sync"
"testing"
"github.com/goplus/llgo/runtime/internal/clite/tls"
)
func TestAllocReadWrite(t *testing.T) {
h := tls.Alloc[int](nil)
if got := h.Get(); got != 0 {
t.Fatalf("zero slot = %d, want 0", got)
}
h.Set(42)
if got := h.Get(); got != 42 {
t.Fatalf("Set/Get mismatch: got %d", got)
}
h.Clear()
if got := h.Get(); got != 0 {
t.Fatalf("Clear() did not reset slot, got %d", got)
}
}
func TestAllocThreadLocalIsolation(t *testing.T) {
h := tls.Alloc[int](nil)
h.Set(7)
const want = 99
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
if got := h.Get(); got != 0 {
t.Errorf("new goroutine initial value = %d, want 0", got)
}
h.Set(want)
if got := h.Get(); got != want {
t.Errorf("goroutine value = %d, want %d", got, want)
}
}()
wg.Wait()
if got := h.Get(); got != 7 {
t.Fatalf("main goroutine value changed to %d", got)
}
}
func TestDestructorRuns(t *testing.T) {
var mu sync.Mutex
var calls int
values := make([]int, 0, 1)
h := tls.Alloc[*int](func(p **int) {
mu.Lock()
defer mu.Unlock()
if p != nil && *p != nil {
calls++
values = append(values, **p)
}
})
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
val := new(int)
*val = 123
h.Set(val)
}()
wg.Wait()
mu.Lock()
defer mu.Unlock()
if calls == 0 {
t.Fatalf("expected destructor to be invoked")
}
if len(values) != 1 || values[0] != 123 {
t.Fatalf("destructor saw unexpected values: %v", values)
}
}
func TestAllocStress(t *testing.T) {
const sequentialIterations = 200_000
h := tls.Alloc[int](nil)
for i := 0; i < sequentialIterations; i++ {
h.Set(i)
if got := h.Get(); got != i {
t.Fatalf("stress iteration %d: got %d want %d", i, got, i)
}
}
var wg sync.WaitGroup
const (
goroutines = 32
iterationsPerGoroutine = 1_000
)
errs := make(chan error, goroutines)
wg.Add(goroutines)
for g := 0; g < goroutines; g++ {
go func(offset int) {
defer wg.Done()
local := tls.Alloc[int](nil)
for i := 0; i < iterationsPerGoroutine; i++ {
v := offset*iterationsPerGoroutine + i
local.Set(v)
if got := local.Get(); got != v {
errs <- fmt.Errorf("goroutine %d iteration %d: got %d want %d", offset, i, got, v)
return
}
}
}(g)
}
wg.Wait()
close(errs)
for err := range errs {
if err != nil {
t.Fatal(err)
}
}
}

View File

@@ -0,0 +1,19 @@
// Copyright 2024 The GoPlus Authors (goplus.org). All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package build provides alternative implementations for go/build.
// We override build.Default.Compiler in an init function.
package build
import (
"go/build"
)
func init() {
// LLGO PATCH: Override build.Default.Compiler to be "gc" instead of "llgo"
// This prevents "unknown compiler" errors when user code uses go/build package
// Even though runtime.Compiler = "llgo", we set build.Default.Compiler = "gc"
build.Default.Compiler = "gc"
}

View File

@@ -0,0 +1,34 @@
/*
* 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 maphash
import "unsafe"
// NOTE: The following functions are not yet implemented and will panic with "intrinsic":
// - Comparable(seed Seed, v any) uint64
// - (*Hash).WriteComparable(v any) (int, error)
// These functions require runtime intrinsic support that is not currently available.
//go:linkname runtime_rand github.com/goplus/llgo/runtime/internal/runtime.fastrand64
func runtime_rand() uint64
//go:linkname runtime_memhash github.com/goplus/llgo/runtime/internal/runtime.memhash
func runtime_memhash(p unsafe.Pointer, seed, s uintptr) uintptr
func randUint64() uint64 {
return runtime_rand()
}

View File

@@ -23,7 +23,7 @@ import (
"github.com/goplus/llgo/runtime/internal/runtime"
)
// llgo:skip init CompareString
// llgo:skip init
type _bytealg struct{}
func IndexByte(b []byte, ch byte) int {

View File

@@ -1759,6 +1759,16 @@ func ValueOf(i any) Value {
return unpackEface(i)
}
// Indirect returns the value that v points to.
// If v is a nil pointer, Indirect returns a zero Value.
// If v is not a pointer, Indirect returns v.
func Indirect(v Value) Value {
if v.Kind() != Pointer {
return v
}
return v.Elem()
}
// arrayAt returns the i-th element of p,
// an array whose elements are eltSize bytes wide.
// The array pointed at by p must have at least i+1 elements:

View File

@@ -140,6 +140,10 @@ type Func struct {
opaque struct{} // unexported field to disallow conversions
}
func (f *Func) Name() string {
panic("todo")
}
// moduledata records information about the layout of the executable
// image. It is written by the linker. Any changes here must be
// matched changes to the code in cmd/link/internal/ld/symtab.go:symtab.

View File

@@ -53,6 +53,96 @@ func newDIBuilder(prog Program, pkg Package, positioner Positioner) diBuilder {
return b
}
func hasTypeParam(typ types.Type) bool {
visited := make(map[types.Type]bool)
var visit func(types.Type) bool
visit = func(tt types.Type) bool {
if tt == nil {
return false
}
if visited[tt] {
return false
}
visited[tt] = true
switch t := tt.(type) {
case *types.TypeParam:
return true
case *types.Named:
if tp := t.TypeParams(); tp != nil && tp.Len() > 0 {
if ta := t.TypeArgs(); ta == nil || ta.Len() == 0 {
return true
}
}
if ta := t.TypeArgs(); ta != nil {
for i := 0; i < ta.Len(); i++ {
if visit(ta.At(i)) {
return true
}
}
}
return visit(t.Underlying())
case *types.Pointer:
return visit(t.Elem())
case *types.Slice:
return visit(t.Elem())
case *types.Array:
return visit(t.Elem())
case *types.Map:
return visit(t.Key()) || visit(t.Elem())
case *types.Chan:
return visit(t.Elem())
case *types.Signature:
if tp := t.TypeParams(); tp != nil && tp.Len() > 0 {
return true
}
if params := t.Params(); params != nil {
for i := 0; i < params.Len(); i++ {
if visit(params.At(i).Type()) {
return true
}
}
}
if results := t.Results(); results != nil {
for i := 0; i < results.Len(); i++ {
if visit(results.At(i).Type()) {
return true
}
}
}
return false
case *types.Tuple:
for i := 0; i < t.Len(); i++ {
if visit(t.At(i).Type()) {
return true
}
}
return false
case *types.Struct:
for i := 0; i < t.NumFields(); i++ {
if visit(t.Field(i).Type()) {
return true
}
}
return false
case *types.Interface:
for i := 0; i < t.NumMethods(); i++ {
if visit(t.Method(i).Type()) {
return true
}
}
for i := 0; i < t.NumEmbeddeds(); i++ {
if visit(t.EmbeddedType(i)) {
return true
}
}
return false
default:
return false
}
}
return visit(typ)
}
// New method to add named metadata operand
func (b diBuilder) addNamedMetadataOperand(name string, intValue int, stringValue string, intValue2 int) {
ctx := b.m.Context()
@@ -522,6 +612,9 @@ func (b diBuilder) dbgValue(v Expr, dv DIVar, scope DIScope, pos token.Position,
}
func (b diBuilder) diType(t Type, pos token.Position) DIType {
if hasTypeParam(t.RawType()) {
return &aDIType{}
}
name := t.RawType().String()
return b.diTypeEx(name, t, pos)
}

166
ssa/di_test.go Normal file
View File

@@ -0,0 +1,166 @@
//go:build !llgo
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ssa
import (
"go/token"
"go/types"
"testing"
)
func TestHasTypeParam(t *testing.T) {
generic := newGenericNamedType("Box")
instantiated, err := types.Instantiate(types.NewContext(), generic, []types.Type{types.Typ[types.String]}, true)
if err != nil {
t.Fatalf("Instantiate: %v", err)
}
partialArg := newTypeParam("PartialArg")
partialInstance, err := types.Instantiate(types.NewContext(), generic, []types.Type{partialArg}, false)
if err != nil {
t.Fatalf("Instantiate partial: %v", err)
}
arrayType := func() types.Type {
tp := newTypeParam("ArrayElem")
return types.NewArray(tp, 3)
}()
chanType := types.NewChan(types.SendRecv, newTypeParam("ChanElem"))
tupleType := func() types.Type {
tp := newTypeParam("TupleElem")
elem := types.NewVar(token.NoPos, nil, "v", tp)
return types.NewTuple(elem)
}()
structWithParam := func() types.Type {
tp := newTypeParam("StructElem")
field := types.NewVar(token.NoPos, nil, "value", tp)
return types.NewStruct([]*types.Var{field}, nil)
}()
signatureWithParam := func() types.Type {
tp := newTypeParam("SigParam")
params := types.NewTuple(types.NewVar(token.NoPos, nil, "x", tp))
return types.NewSignatureType(nil, nil, []*types.TypeParam{tp}, params, types.NewTuple(), false)
}()
signatureWithResult := func() types.Type {
tp := newTypeParam("SigResult")
results := types.NewTuple(types.NewVar(token.NoPos, nil, "res", tp))
return types.NewSignatureType(nil, nil, []*types.TypeParam{tp}, types.NewTuple(), results, false)
}()
signatureParamWithoutDecl := func() types.Type {
tp := newTypeParam("SigParamExternal")
params := types.NewTuple(types.NewVar(token.NoPos, nil, "x", tp))
return types.NewSignatureType(nil, nil, nil, params, types.NewTuple(), false)
}()
signatureResultWithoutDecl := func() types.Type {
tp := newTypeParam("SigResultExternal")
results := types.NewTuple(types.NewVar(token.NoPos, nil, "res", tp))
return types.NewSignatureType(nil, nil, nil, types.NewTuple(), results, false)
}()
interfaceWithParam := func() types.Type {
tp := newTypeParam("IfaceParam")
params := types.NewTuple(types.NewVar(token.NoPos, nil, "v", tp))
method := types.NewFunc(token.NoPos, nil, "Do", types.NewSignatureType(nil, nil, []*types.TypeParam{tp}, params, types.NewTuple(), false))
iface := types.NewInterfaceType([]*types.Func{method}, nil)
iface.Complete()
return iface
}()
interfaceWithEmbed := func() types.Type {
base := interfaceWithParam
tp := newTypeParam("EmbedParam")
embedMethod := types.NewFunc(token.NoPos, nil, "Run", types.NewSignatureType(nil, nil, []*types.TypeParam{tp}, types.NewTuple(), types.NewTuple(), false))
iface := types.NewInterfaceType([]*types.Func{embedMethod}, []types.Type{base})
iface.Complete()
return iface
}()
interfaceWithEmbeddedOnly := func() types.Type {
embedded := interfaceWithParam
iface := types.NewInterfaceType(nil, []types.Type{embedded})
iface.Complete()
return iface
}()
selfRecursive := func() types.Type {
typeName := types.NewTypeName(token.NoPos, nil, "Node", nil)
placeholder := types.NewStruct(nil, nil)
named := types.NewNamed(typeName, placeholder, nil)
field := types.NewVar(token.NoPos, nil, "next", types.NewPointer(named))
structType := types.NewStruct([]*types.Var{field}, nil)
named.SetUnderlying(structType)
return named
}()
tests := []struct {
name string
typ types.Type
want bool
}{
{"nilType", nil, false},
{"basic", types.Typ[types.Int], false},
{"typeParam", newTypeParam("T"), true},
{"pointerToTypeParam", types.NewPointer(newTypeParam("PtrT")), true},
{"sliceOfTypeParam", types.NewSlice(newTypeParam("SliceT")), true},
{"arrayOfTypeParam", arrayType, true},
{"mapWithTypeParam", types.NewMap(newTypeParam("MapKey"), types.Typ[types.String]), true},
{"chanOfTypeParam", chanType, true},
{"tupleWithTypeParam", tupleType, true},
{"structWithTypeParam", structWithParam, true},
{"signatureWithTypeParam", signatureWithParam, true},
{"signatureWithResultTypeParam", signatureWithResult, true},
{"signatureParamWithoutDecl", signatureParamWithoutDecl, true},
{"signatureResultWithoutDecl", signatureResultWithoutDecl, true},
{"interfaceWithTypeParam", interfaceWithParam, true},
{"interfaceWithEmbeddedTypeParam", interfaceWithEmbed, true},
{"interfaceWithEmbeddedOnlyTypeParam", interfaceWithEmbeddedOnly, true},
{"namedGeneric", generic, true},
{"pointerToNamedGeneric", types.NewPointer(generic), true},
{"namedInstanceWithTypeParamArg", partialInstance, true},
{"namedInstanceNoParam", instantiated, false},
{"selfRecursiveStruct", selfRecursive, false},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
if got := hasTypeParam(tc.typ); got != tc.want {
t.Fatalf("hasTypeParam(%s) = %v, want %v", tc.name, got, tc.want)
}
})
}
}
func newTypeParam(name string) *types.TypeParam {
iface := types.NewInterfaceType(nil, nil)
iface.Complete()
return types.NewTypeParam(types.NewTypeName(token.NoPos, nil, name, nil), iface)
}
func newGenericNamedType(name string) *types.Named {
tp := newTypeParam("T")
field := types.NewVar(token.NoPos, nil, "value", tp)
structType := types.NewStruct([]*types.Var{field}, nil)
named := types.NewNamed(types.NewTypeName(token.NoPos, nil, name, nil), structType, nil)
named.SetTypeParams([]*types.TypeParam{tp})
return named
}