Merge branch 'main' of https://github.com/goplus/llgo into impl-stacksave
This commit is contained in:
3
.github/workflows/doc.yml
vendored
3
.github/workflows/doc.yml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
- "!dependabot/**"
|
- "!dependabot/**"
|
||||||
|
- "!xgopilot/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v5
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "20"
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/fmt.yml
vendored
1
.github/workflows/fmt.yml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
- "!dependabot/**"
|
- "!dependabot/**"
|
||||||
|
- "!xgopilot/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/go.yml
vendored
1
.github/workflows/go.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
- "!dependabot/**"
|
- "!dependabot/**"
|
||||||
|
- "!xgopilot/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
|
|
||||||
|
|||||||
5
.github/workflows/llgo.yml
vendored
5
.github/workflows/llgo.yml
vendored
@@ -8,6 +8,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
- "!dependabot/**"
|
- "!dependabot/**"
|
||||||
|
- "!xgopilot/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ jobs:
|
|||||||
wget -P ./_demo/c/llama2-c https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
|
wget -P ./_demo/c/llama2-c https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
|
||||||
|
|
||||||
- name: Upload model as artifact
|
- name: Upload model as artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: llama2-model
|
name: llama2-model
|
||||||
path: ./_demo/c/llama2-c/stories15M.bin
|
path: ./_demo/c/llama2-c/stories15M.bin
|
||||||
@@ -51,7 +52,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
llvm-version: ${{matrix.llvm}}
|
llvm-version: ${{matrix.llvm}}
|
||||||
- name: Download model artifact
|
- name: Download model artifact
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: llama2-model
|
name: llama2-model
|
||||||
path: ./_demo/c/llama2-c/
|
path: ./_demo/c/llama2-c/
|
||||||
|
|||||||
17
.github/workflows/release-build.yml
vendored
17
.github/workflows/release-build.yml
vendored
@@ -2,7 +2,10 @@ name: Release Build
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: ["**"]
|
branches:
|
||||||
|
- "**"
|
||||||
|
- "!dependabot/**"
|
||||||
|
- "!xgopilot/**"
|
||||||
tags: ["*"]
|
tags: ["*"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
@@ -79,7 +82,7 @@ jobs:
|
|||||||
release --verbose --skip=publish,nfpm,snapcraft --snapshot --clean
|
release --verbose --skip=publish,nfpm,snapcraft --snapshot --clean
|
||||||
|
|
||||||
- name: Upload Darwin AMD64 Artifacts
|
- name: Upload Darwin AMD64 Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: llgo-darwin-amd64
|
name: llgo-darwin-amd64
|
||||||
path: .dist/*darwin-amd64.tar.gz
|
path: .dist/*darwin-amd64.tar.gz
|
||||||
@@ -87,7 +90,7 @@ jobs:
|
|||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
|
|
||||||
- name: Upload Darwin ARM64 Artifacts
|
- name: Upload Darwin ARM64 Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: llgo-darwin-arm64
|
name: llgo-darwin-arm64
|
||||||
path: .dist/*darwin-arm64.tar.gz
|
path: .dist/*darwin-arm64.tar.gz
|
||||||
@@ -95,7 +98,7 @@ jobs:
|
|||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
|
|
||||||
- name: Upload Linux AMD64 Artifacts
|
- name: Upload Linux AMD64 Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: llgo-linux-amd64
|
name: llgo-linux-amd64
|
||||||
path: .dist/*linux-amd64.tar.gz
|
path: .dist/*linux-amd64.tar.gz
|
||||||
@@ -103,7 +106,7 @@ jobs:
|
|||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
|
|
||||||
- name: Upload Linux ARM64 Artifacts
|
- name: Upload Linux ARM64 Artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: llgo-linux-arm64
|
name: llgo-linux-arm64
|
||||||
path: .dist/*linux-arm64.tar.gz
|
path: .dist/*linux-arm64.tar.gz
|
||||||
@@ -111,7 +114,7 @@ jobs:
|
|||||||
include-hidden-files: true
|
include-hidden-files: true
|
||||||
|
|
||||||
- name: Upload Checksums
|
- name: Upload Checksums
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: llgo-checksums
|
name: llgo-checksums
|
||||||
path: .dist/*checksums.txt
|
path: .dist/*checksums.txt
|
||||||
@@ -155,7 +158,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
- name: Download Platform Artifact
|
- name: Download Platform Artifact
|
||||||
uses: actions/download-artifact@v5
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: llgo-${{ matrix.goos }}-${{ matrix.goarch }}
|
name: llgo-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||||
path: .
|
path: .
|
||||||
|
|||||||
1
.github/workflows/targets.yml
vendored
1
.github/workflows/targets.yml
vendored
@@ -6,6 +6,7 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
- "!dependabot/**"
|
- "!dependabot/**"
|
||||||
|
- "!xgopilot/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["**"]
|
branches: ["**"]
|
||||||
|
|
||||||
|
|||||||
132
CLAUDE.md
Normal file
132
CLAUDE.md
Normal 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
|
||||||
|
|
||||||
@@ -323,6 +323,7 @@ Here are the Go packages that can be imported correctly:
|
|||||||
* [hash/adler32](https://pkg.go.dev/hash/adler32)
|
* [hash/adler32](https://pkg.go.dev/hash/adler32)
|
||||||
* [hash/crc32](https://pkg.go.dev/hash/crc32) (partially)
|
* [hash/crc32](https://pkg.go.dev/hash/crc32) (partially)
|
||||||
* [hash/crc64](https://pkg.go.dev/hash/crc64)
|
* [hash/crc64](https://pkg.go.dev/hash/crc64)
|
||||||
|
* [hash/maphash](https://pkg.go.dev/hash/maphash) (partially)
|
||||||
* [crypto](https://pkg.go.dev/crypto)
|
* [crypto](https://pkg.go.dev/crypto)
|
||||||
* [crypto/md5](https://pkg.go.dev/crypto/md5)
|
* [crypto/md5](https://pkg.go.dev/crypto/md5)
|
||||||
* [crypto/sha1](https://pkg.go.dev/crypto/sha1)
|
* [crypto/sha1](https://pkg.go.dev/crypto/sha1)
|
||||||
|
|||||||
147
_demo/go/gobuild/demo.go
Normal file
147
_demo/go/gobuild/demo.go
Normal 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
124
_demo/go/maphash/maphash.go
Normal 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)
|
||||||
|
}
|
||||||
49
_demo/go/reflectindirect/reflect-indirect.go
Normal file
49
_demo/go/reflectindirect/reflect-indirect.go
Normal 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
2
go.mod
@@ -6,7 +6,7 @@ toolchain go1.24.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/goplus/cobra v1.9.12 //gop:class
|
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/lib v0.3.0
|
||||||
github.com/goplus/llgo/runtime v0.0.0-00010101000000-000000000000
|
github.com/goplus/llgo/runtime v0.0.0-00010101000000-000000000000
|
||||||
github.com/goplus/llvm v0.8.5
|
github.com/goplus/llvm v0.8.5
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -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/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 h1:0F9EdEbeGyITGz+mqoHoJ5KpUw97p1CkxV74IexHw5s=
|
||||||
github.com/goplus/cobra v1.9.12/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E=
|
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.3 h1:sMTe7xME8lWFdPL6NcULykdJtFs9CtXkNACRbaAKTiQ=
|
||||||
github.com/goplus/gogen v1.19.2/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
|
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 h1:y0ZGb5Q/RikW1oMMB4Di7XIZIpuzh/7mlrR8HNbxXCA=
|
||||||
github.com/goplus/lib v0.3.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
|
github.com/goplus/lib v0.3.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
|
||||||
github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs=
|
github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs=
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ var hasAltPkg = map[string]none{
|
|||||||
"crypto/sha256": {},
|
"crypto/sha256": {},
|
||||||
"crypto/sha512": {},
|
"crypto/sha512": {},
|
||||||
"crypto/subtle": {},
|
"crypto/subtle": {},
|
||||||
|
"go/build": {},
|
||||||
"go/parser": {},
|
"go/parser": {},
|
||||||
"hash/crc32": {},
|
"hash/crc32": {},
|
||||||
|
"hash/maphash": {},
|
||||||
"internal/abi": {},
|
"internal/abi": {},
|
||||||
"internal/bytealg": {},
|
"internal/bytealg": {},
|
||||||
"internal/chacha8rand": {},
|
"internal/chacha8rand": {},
|
||||||
|
|||||||
@@ -40,6 +40,20 @@ func Realloc(ptr c.Pointer, size uintptr) c.Pointer
|
|||||||
//go:linkname Free C.GC_free
|
//go:linkname Free C.GC_free
|
||||||
func Free(ptr c.Pointer)
|
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
|
//go:linkname RegisterFinalizer C.GC_register_finalizer
|
||||||
|
|||||||
128
runtime/internal/clite/tls/tls_common.go
Normal file
128
runtime/internal/clite/tls/tls_common.go
Normal 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)
|
||||||
|
}
|
||||||
74
runtime/internal/clite/tls/tls_gc.go
Normal file
74
runtime/internal/clite/tls/tls_gc.go
Normal 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)
|
||||||
|
}
|
||||||
28
runtime/internal/clite/tls/tls_nogc.go
Normal file
28
runtime/internal/clite/tls/tls_nogc.go
Normal 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]) {}
|
||||||
141
runtime/internal/clite/tls/tls_test.go
Normal file
141
runtime/internal/clite/tls/tls_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
runtime/internal/lib/go/build/build.go
Normal file
19
runtime/internal/lib/go/build/build.go
Normal 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"
|
||||||
|
}
|
||||||
34
runtime/internal/lib/hash/maphash/maphash.go
Normal file
34
runtime/internal/lib/hash/maphash/maphash.go
Normal 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()
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ import (
|
|||||||
"github.com/goplus/llgo/runtime/internal/runtime"
|
"github.com/goplus/llgo/runtime/internal/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// llgo:skip init CompareString
|
// llgo:skip init
|
||||||
type _bytealg struct{}
|
type _bytealg struct{}
|
||||||
|
|
||||||
func IndexByte(b []byte, ch byte) int {
|
func IndexByte(b []byte, ch byte) int {
|
||||||
|
|||||||
@@ -1759,6 +1759,16 @@ func ValueOf(i any) Value {
|
|||||||
return unpackEface(i)
|
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,
|
// arrayAt returns the i-th element of p,
|
||||||
// an array whose elements are eltSize bytes wide.
|
// an array whose elements are eltSize bytes wide.
|
||||||
// The array pointed at by p must have at least i+1 elements:
|
// The array pointed at by p must have at least i+1 elements:
|
||||||
|
|||||||
@@ -140,6 +140,10 @@ type Func struct {
|
|||||||
opaque struct{} // unexported field to disallow conversions
|
opaque struct{} // unexported field to disallow conversions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Func) Name() string {
|
||||||
|
panic("todo")
|
||||||
|
}
|
||||||
|
|
||||||
// moduledata records information about the layout of the executable
|
// moduledata records information about the layout of the executable
|
||||||
// image. It is written by the linker. Any changes here must be
|
// 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.
|
// matched changes to the code in cmd/link/internal/ld/symtab.go:symtab.
|
||||||
|
|||||||
93
ssa/di.go
93
ssa/di.go
@@ -53,6 +53,96 @@ func newDIBuilder(prog Program, pkg Package, positioner Positioner) diBuilder {
|
|||||||
return b
|
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
|
// New method to add named metadata operand
|
||||||
func (b diBuilder) addNamedMetadataOperand(name string, intValue int, stringValue string, intValue2 int) {
|
func (b diBuilder) addNamedMetadataOperand(name string, intValue int, stringValue string, intValue2 int) {
|
||||||
ctx := b.m.Context()
|
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 {
|
func (b diBuilder) diType(t Type, pos token.Position) DIType {
|
||||||
|
if hasTypeParam(t.RawType()) {
|
||||||
|
return &aDIType{}
|
||||||
|
}
|
||||||
name := t.RawType().String()
|
name := t.RawType().String()
|
||||||
return b.diTypeEx(name, t, pos)
|
return b.diTypeEx(name, t, pos)
|
||||||
}
|
}
|
||||||
|
|||||||
166
ssa/di_test.go
Normal file
166
ssa/di_test.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user